Package x2go :: Package backends :: Package profiles :: Module httpbroker

[frames] | no frames]

# Source Code for Module x2go.backends.profiles.httpbroker

      1  # -*- coding: utf-8 -*-
      2
      3  # Copyright (C) 2010-2015 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
      4  #
      5  # Python X2Go is free software; you can redistribute it and/or modify
      6  # it under the terms of the GNU Affero General Public License as published by
      7  # the Free Software Foundation; either version 3 of the License, or
      8  # (at your option) any later version.
      9  #
     10  # Python X2Go is distributed in the hope that it will be useful,
     11  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  # GNU Affero General Public License for more details.
     14  #
     15  # You should have received a copy of the GNU Affero General Public License
     16  # along with this program; if not, write to the
     17  # Free Software Foundation, Inc.,
     18  # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
     19
     20  """\
     21  L{X2GoSessionProfiles} class - managing x2goclient session profiles.
     22
     23  L{X2GoSessionProfiles} is a public API class. Use this class in your Python X2Go based
     24  applications.
     25
     26  """
     27  __NAME__ = 'x2gosessionprofiles-pylib'
     28
     29  import re
     30  import requests
     31  import urllib3.exceptions
     32  import copy
     33  import types
     34  import time
     35  try: import simplejson as json
     36  except ImportError: import json
     37
     38  # Python X2Go modules
     39  from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS as _X2GO_SESSIONPROFILE_DEFAULTS
     40  from x2go.defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER
     41  import x2go.backends.profiles.base as base
     42  import x2go.log as log
     43  from x2go.utils import genkeypair
     44  import x2go.x2go_exceptions
     45
     46 -class X2GoSessionProfiles(base.X2GoSessionProfiles):
     47
     48      defaultSessionProfile = copy.deepcopy(_X2GO_SESSIONPROFILE_DEFAULTS)
     49
     50 -    def __init__(self, session_profile_defaults=None,
     51                   broker_url="http://localhost:8080/json/",
     52                   broker_username=None,
     53                   broker_password=None,
     54                   logger=None, loglevel=log.loglevel_DEFAULT,
     55                   **kwargs):
     56          """\
     57          Retrieve X2Go session profiles from a HTTP(S) session broker.
     58
     59          @param session_profile_defaults: a default session profile
     60          @type session_profile_defaults: C{dict}
     61          @param broker_url: URL for accessing the X2Go Session Broker
     62          @type broker_url: C{str}
     63          @param broker_password: use this password for authentication against the X2Go Session Broker (avoid
     64              password string in the C{broker_URL} parameter is highly recommended)
     65          @type broker_password: C{str}
     66          @param logger: you can pass an L{X2GoLogger} object to the
     67              L{x2go.backends.profiles.httpbroker.X2GoSessionProfiles} constructor
     68          @type logger: L{X2GoLogger} instance
     69          @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be
     70              constructed with the given loglevel
     71          @type loglevel: C{int}
     72
     73          """
     74          if broker_url.upper() != "HTTP":
     75              match = re.match('^(?P<protocol>(http(|s)))://(|(?P<user>[a-zA-Z0-9_\.-]+)(|:(?P<password>.*))@)(?P<hostname>[a-zA-Z0-9\.-]+)(|:(?P<port>[0-9]+))($|/(?P<path>.*)$)', broker_url)
     76              p = match.groupdict()
     77              if p['user']:
     78                  self.broker_username = p['user']
     79              else:
     80                  self.broker_username = broker_username
     81              if p['password']:
     82                  self.broker_password = p['password']
     83              elif broker_password:
     84                  self.broker_password = broker_password
     85              else:
     86                  self.broker_password = None
     87
     88              # fine-tune the URL
     89              p['path'] = "/{path}".format(**p)
     90              if p['port'] is not None:
     91                  p['port'] = ":{port}".format(**p)
     92
     93              self.broker_url = "{protocol}://{hostname}{port}{path}".format(**p)
     94
     95          else:
     96              self.broker_username = broker_username
     97              self.broker_password = broker_password
     98              self.broker_url = broker_url
     99
    100          self.broker_noauth = False
    101          self.broker_authid = None
    102          self._broker_profile_cache = {}
    103          self._mutable_profile_ids = None
    104          self._broker_auth_successful = None
    105
    106          self._broker_type = "http"
    107
    108          base.X2GoSessionProfiles.__init__(self, session_profile_defaults=session_profile_defaults, logger=logger, loglevel=loglevel)
    109          if self.broker_url != "HTTP":
    110              self.logger("Using session broker at URL: %s" % self.broker_url, log.loglevel_NOTICE)
    111
    112          # for broker based autologin, we have to be able to provide public/private key pair
    113          self.broker_my_pubkey, self.broker_my_privkey = genkeypair(local_username=_CURRENT_LOCAL_USER, client_address='127.0.0.1')
    114
    115 -    def get_broker_noauth(self):
    116          """\
    117          Accessor for the class's C{broker_noauth} property.
    118
    119          @return: C{True} if the broker probably does not expect authentication.
    120          @rtype: C{bool}
    121
    122          """
    123          return self.broker_noauth
    124
    125 -    def get_broker_username(self):
    126          """\
    127          Accessor for the class's C{broker_username} property.
    128
    129          @return: the username used for authentication against the session broker URL
    130          @rtype: C{str}
    131
    132          """
    133          return self.broker_username
    134
    135 -    def get_broker_url(self):
    136          """\
    137          Accessor for the class's C{broker_url} property.
    138
    139          @return: the session broker URL that was used at broker session instantiation
    140          @rtype: C{str}
    141
    142          """
    143          return self.broker_url
    144
    145 -    def set_broker_url(self, broker_url):
    146          """\
    147          Mutator for the class's C{broker_url} property.
    148
    149          @param broker_url: A new broker URL to use with this instance. Format is
    150              C{<protocol>://<hostname>:<port>/<path>} (where protocol has to be C{http}
    151              or C{https}.
    152          @type broker_url: C{str}
    153
    154          @return: the session broker URL that was used at broker session instantiation
    155          @rtype: C{str}
    156
    157          """
    158          self.broker_url = broker_url
    159
    160 -    def get_broker_type(self):
    161          """\
    162          Accessor of the class's {_broker_type} property.
    163
    164          @return: either C{http} or C{https}.
    165          @rtype: C{str}
    166
    167          """
    168          return self._broker_type
    169
    170 -    def broker_simpleauth(self, broker_username, broker_password):
    171          """\
    172          Attempt a username / password authentication against the instance's
    173          broker URL.
    174
    175          @param broker_username: username to use for authentication
    176          @type broker_username: C{str}
    177          @param broker_password: password to use for authentication
    178          @type broker_password: C{str}
    179
    180          @return: C{True} if authentication has been successful
    181          @rtype: C{bool}
    182
    183          @raise X2GoBrokerConnectionException: Raised on any kind of connection /
    184              authentication failure.
    185
    186          """
    187          if self.broker_url is not None:
    188              request_data = {
    189                  'user': broker_username or '',
    190              }
    191              if self.broker_authid is not None:
    192                  request_data['authid'] = self.broker_authid
    193                  self.logger("Sending request to broker: user: {user}, authid: {authid}".format(**request_data), log.loglevel_DEBUG)
    194              else:
    195                  if broker_password:
    196                      request_data['password'] = "<hidden>"
    197                  else:
    198                      request_data['password'] = "<EMPTY>"
    199                  self.logger("Sending request to broker: user: {user}, password: {password}".format(**request_data), log.loglevel_DEBUG)
    200                  request_data['password'] = broker_password or ''
    201              try:
    202                  r = requests.post(self.broker_url, data=request_data)
    203              except (requests.exceptions.ConnectionError, requests.exceptions.MissingSchema, urllib3.exceptions.LocationParseError):
    204                  raise x2go.x2go_exceptions.X2GoBrokerConnectionException('Failed to connect to URL %s' % self.broker_url)
    205              if r.status_code == 200:
    206                  payload = json.loads(r.text)
    207                  if not self.broker_authid and not self.broker_password:
    208                      self.broker_noauth = True
    209                  elif payload.has_key('next-authid'):
    210                      self.broker_authid = payload['next-authid']
    211                  self.broker_username = broker_username or ''
    212                  self.broker_password = broker_password or ''
    213                  self._broker_auth_successful = True
    214                  self.populate_session_profiles()
    215                  return True
    216          self._broker_auth_successful = False
    217          self.broker_authid = None
    218          return False
    219
    220 -    def broker_disconnect(self):
    221          """\
    222          Disconnect from an (already) authenticated broker session.
    223
    224          All authentication parameters will be dropped (forgotten) and
    225          this instance has to re-authenticate against / re-connect to the
    226          session broker before any new interaction with the broker is possible.
    227
    228          """
    229          _profile_ids = copy.deepcopy(self.profile_ids)
    230
    231          # forget nearly everything...
    232          for profile_id in _profile_ids:
    233              self.init_profile_cache(profile_id)
    234              try: del self._profile_metatypes[profile_id]
    235              except KeyError: pass
    236              try: self._profiles_need_profile_id_renewal.remove(profile_id)
    237              except ValueError: pass
    238              try: del self._cached_profile_ids[profile_id]
    239              except KeyError: pass
    240              del self.session_profiles[profile_id]
    241          self._mutable_profile_ids = None
    242          self._broker_auth_successful = False
    243          self.broker_authid = None
    244          self.broker_password = None
    245          self.broker_noauth = False
    246
    247 -    def is_broker_authenticated(self):
    248          """\
    249          Detect if an authenticated broker session has already been
    250          initiated. Todo so, a simple re-authentication (username, password)
    251          will be attempted. If that fails, user credentials are not provided /
    252          valid.
    253
    254          @return: C{True} if the broker session has already been authenticated
    255              and user credentials are known / valid
    256          @rtype: C{bool}
    257
    258          """
    259          if self._broker_auth_successful is None:
    260              # do a test auth against the given broker URL
    261              try:
    262                  self.broker_simpleauth(self.broker_username, self.broker_password)
    263              except x2go.x2go_exceptions.X2GoBrokerConnectionException:
    264                  self._broker_auth_successful = False
    265          return self._broker_auth_successful
    266
    267 -    def broker_listprofiles(self):
    268          """\
    269          Obtain a session profile list from the X2Go Session Broker.
    270
    271          @return: session profiles as a Python dictionary.
    272          @rtype: C{dict}
    273
    274          """
    275          if self.broker_url is not None:
    276              request_data = {
    277                  'task': 'listprofiles',
    278                  'user': self.broker_username,
    279              }
    280              if self.broker_authid is not None:
    281                  request_data['authid'] = self.broker_authid
    282                  self.logger("Sending request to broker: user: {user}, authid: {authid}, task: {task}".format(**request_data), log.loglevel_DEBUG)
    283              else:
    284                  if self.broker_password:
    285                      request_data['password'] = "<hidden>"
    286                  else:
    287                      request_data['password'] = "<EMPTY>"
    288                  self.logger("Sending request to broker: user: {user}, password: {password}, task: {task}".format(**request_data), log.loglevel_DEBUG)
    289                  request_data['password'] = self.broker_password or ''
    290              try:
    291                  r = requests.post(self.broker_url, data=request_data)
    292              except requests.exceptions.ConnectionError:
    293                  raise x2go.x2go_exceptions.X2GoBrokerConnectionException('Failed to connect to URL %s' % self.broker_url)
    294              if r.status_code == 200 and r.headers['content-type'].startswith("text/json"):
    295                  payload = json.loads(r.text)
    296                  if payload.has_key('next-authid'):
    297                      self.broker_authid = payload['next-authid']
    298                  if payload.has_key('mutable_profile_ids'):
    299                      self._mutable_profile_ids = payload['mutable_profile_ids']
    300                  self._broker_auth_successful = True
    301                  return payload['profiles'] if payload['task'] == 'listprofiles' else {}
    302          self._broker_auth_successful = False
    303          self.broker_authid = None
    304          return {}
    305
    306 -    def broker_selectsession(self, profile_id):
    307          """\
    308          Select a session from the list of available session profiles (presented by
    309          L{broker_listprofiles}). This method requests a session information dictionary
    310          (server, port, SSH keys, already running / suspended sessions, etc.) from the
    311          session broker for the provided C{profile_id}.
    312
    313          @param profile_id: profile ID of the selected session profile
    314          @type profile_id: C{str}
    315
    316          @return: session information (server, port, SSH keys, etc.) for a selected
    317              session profile (i.e. C{profile_id})
    318          @rtype: C{dict}
    319
    320          """
    321          if self.broker_url is not None:
    322              if not self._broker_profile_cache.has_key(profile_id) or not self._broker_profile_cache[profile_id]:
    323                  request_data = {
    324                      'task': 'selectsession',
    325                      'profile-id': profile_id,
    326                      'user': self.broker_username,
    327                      'pubkey': self.broker_my_pubkey,
    328                  }
    329                  if self.broker_authid is not None:
    330                      request_data['authid'] = self.broker_authid
    331                      self.logger("Sending request to broker: user: {user}, authid: {authid}, task: {task}".format(**request_data), log.loglevel_DEBUG)
    332                  else:
    333                      if self.broker_password:
    334                          request_data['password'] = "<hidden>"
    335                      else:
    336                          request_data['password'] = "<EMPTY>"
    337                      self.logger("Sending request to broker: user: {user}, password: {password}, task: {task}".format(**request_data), log.loglevel_DEBUG)
    338                      request_data['password'] = self.broker_password or ''
    339                  try:
    340                      r = requests.post(self.broker_url, data=request_data)
    341                  except requests.exceptions.ConnectionError:
    342                      raise x2go.x2go_exceptions.X2GoBrokerConnectionException('Failed to connect to URL %s' % self.broker_url)
    343                  if r.status_code == 200 and r.headers['content-type'].startswith("text/json"):
    344                      payload = json.loads(r.text)
    345                      if payload.has_key('next-authid'):
    346                          self.broker_authid = payload['next-authid']
    347                      self._broker_profile_cache[profile_id] = payload['selected_session'] if payload['task'] == 'selectsession' else {}
    348                      self._broker_auth_successful = True
    349                  else:
    350                      self.broker_authid = None
    351                      self._broker_auth_successful = False
    352              self._broker_profile_cache[profile_id]
    353              return self._broker_profile_cache[profile_id]
    354          return {}
    355
    356 -    def _init_profile_cache(self, profile_id):
    357          if self._broker_profile_cache.has_key(unicode(profile_id)):
    358              del self._broker_profile_cache[unicode(profile_id)]
    359
    360 -    def _populate_session_profiles(self):
    361          """\
    362          Populate the set of session profiles by loading the session
    363          profile configuration from a file in INI format.
    364
    365          @return: a set of session profiles
    366          @rtype: C{dict}
    367
    368          """
    369          if self.is_broker_authenticated() and \
    370             self.broker_noauth or \
    371             self.broker_username and self.broker_password:
    372
    373              session_profiles = self.broker_listprofiles()
    374              _session_profiles = copy.deepcopy(session_profiles)
    375
    376              for session_profile in _session_profiles:
    377                  session_profile = unicode(session_profile)
    378                  for key, default_value in self.defaultSessionProfile.iteritems():
    379                      key = unicode(key)
    380                      if type(default_value) is types.StringType:
    381                          default_value = unicode(default_value)
    382                      if not session_profiles[session_profile].has_key(key):
    383                          session_profiles[session_profile][key] = default_value
    384
    385          else:
    386              session_profiles = {}
    387
    388          return session_profiles
    389
    390 -    def _is_mutable(self, profile_id):
    391          if type(self._mutable_profile_ids) is types.ListType and profile_id in self._mutable_profile_ids:
    392              return True
    393          return False
    394
    395 -    def _supports_mutable_profiles(self):
    396          if type(self._mutable_profile_ids) is types.ListType:
    397              return True
    398          return False
    399
    400 -    def _write(self):
    401          print "not suported, yet"
    402
    403 -    def _delete_profile(self, profile_id):
    404          del self.session_profiles[unicode(profile_id)]
    405
    406 -    def _update_value(self, profile_id, option, value):
    407          if type(value) is types.StringType:
    408              value = unicode(value)
    409          self.session_profiles[unicode(profile_id)][unicode(option)] = value
    410
    411 -    def _get_profile_parameter(self, profile_id, option, key_type):
    412          return key_type(self.session_profiles[unicode(profile_id)][unicode(option)])
    413
    414 -    def _get_profile_options(self, profile_id):
    415          return self.session_profiles[unicode(profile_id)].keys()
    416
    417 -    def _get_profile_ids(self):
    418          self.session_profiles.keys()
    419          return self.session_profiles.keys()
    420
    421 -    def _get_server_hostname(self, profile_id):
    422          selected_session = self.broker_selectsession(profile_id)
    423          return selected_session['server']
    424
    425 -    def _get_server_port(self, profile_id):
    426          selected_session = self.broker_selectsession(profile_id)
    427          return int(selected_session['port'])
    428
    429 -    def _get_pkey_object(self, profile_id):
    430          selected_session = self.broker_selectsession(profile_id)
    431          if selected_session.has_key('authentication_pubkey') and selected_session['authentication_pubkey'] == 'ACCEPTED':
    432              time.sleep(2)
    433              return self.broker_my_privkey
    434          return None
    435