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-2016 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              else:
     93                  p['port'] = ''
     94
     95              self.broker_url = "{protocol}://{hostname}{port}{path}".format(**p)
     96
     97          else:
     98              self.broker_username = broker_username
     99              self.broker_password = broker_password
    100              self.broker_url = broker_url
    101
    102          self.broker_noauth = False
    103          self.broker_authid = None
    104          self._broker_profile_cache = {}
    105          self._mutable_profile_ids = None
    106          self._broker_auth_successful = None
    107
    108          self._broker_type = "http"
    109
    110          base.X2GoSessionProfiles.__init__(self, session_profile_defaults=session_profile_defaults, logger=logger, loglevel=loglevel)
    111          if self.broker_url != "HTTP":
    112              self.logger("Using session broker at URL: %s" % self.broker_url, log.loglevel_NOTICE)
    113
    114          # for broker based autologin, we have to be able to provide public/private key pair
    115          self.broker_my_pubkey, self.broker_my_privkey = genkeypair(local_username=_CURRENT_LOCAL_USER, client_address='127.0.0.1')
    116
    117 -    def get_broker_noauth(self):
    118          """\
    119          Accessor for the class's C{broker_noauth} property.
    120
    121          @return: C{True} if the broker probably does not expect authentication.
    122          @rtype: C{bool}
    123
    124          """
    125          return self.broker_noauth
    126
    127 -    def get_broker_username(self):
    128          """\
    129          Accessor for the class's C{broker_username} property.
    130
    131          @return: the username used for authentication against the session broker URL
    132          @rtype: C{str}
    133
    134          """
    135          return self.broker_username
    136
    137 -    def get_broker_url(self):
    138          """\
    139          Accessor for the class's C{broker_url} property.
    140
    141          @return: the session broker URL that was used at broker session instantiation
    142          @rtype: C{str}
    143
    144          """
    145          return self.broker_url
    146
    147 -    def set_broker_url(self, broker_url):
    148          """\
    149          Mutator for the class's C{broker_url} property.
    150
    151          @param broker_url: A new broker URL to use with this instance. Format is
    152              C{<protocol>://<hostname>:<port>/<path>} (where protocol has to be C{http}
    153              or C{https}.
    154          @type broker_url: C{str}
    155
    156          @return: the session broker URL that was used at broker session instantiation
    157          @rtype: C{str}
    158
    159          """
    160          self.broker_url = broker_url
    161
    162 -    def get_broker_type(self):
    163          """\
    164          Accessor of the class's {_broker_type} property.
    165
    166          @return: either C{http} or C{https}.
    167          @rtype: C{str}
    168
    169          """
    170          return self._broker_type
    171
    172 -    def broker_simpleauth(self, broker_username, broker_password):
    173          """\
    174          Attempt a username / password authentication against the instance's
    175          broker URL.
    176
    177          @param broker_username: username to use for authentication
    178          @type broker_username: C{str}
    179          @param broker_password: password to use for authentication
    180          @type broker_password: C{str}
    181
    182          @return: C{True} if authentication has been successful
    183          @rtype: C{bool}
    184
    185          @raise X2GoBrokerConnectionException: Raised on any kind of connection /
    186              authentication failure.
    187
    188          """
    189          if self.broker_url is not None:
    190              request_data = {
    191                  'user': broker_username or '',
    192              }
    193              if self.broker_authid is not None:
    194                  request_data['authid'] = self.broker_authid
    195                  self.logger("Sending request to broker: user: {user}, authid: {authid}".format(**request_data), log.loglevel_DEBUG)
    196              else:
    197                  if broker_password:
    198                      request_data['password'] = "<hidden>"
    199                  else:
    200                      request_data['password'] = "<EMPTY>"
    201                  self.logger("Sending request to broker: user: {user}, password: {password}".format(**request_data), log.loglevel_DEBUG)
    202                  request_data['password'] = broker_password or ''
    203              try:
    204                  r = requests.post(self.broker_url, data=request_data)
    205              except (requests.exceptions.ConnectionError, requests.exceptions.MissingSchema, urllib3.exceptions.LocationParseError):
    206                  raise x2go.x2go_exceptions.X2GoBrokerConnectionException('Failed to connect to URL %s' % self.broker_url)
    207              if r.status_code == 200:
    208                  payload = json.loads(r.text)
    209                  if not self.broker_authid and not self.broker_password:
    210                      self.broker_noauth = True
    211                  elif payload.has_key('next-authid'):
    212                      self.broker_authid = payload['next-authid']
    213                  self.broker_username = broker_username or ''
    214                  self.broker_password = broker_password or ''
    215                  self._broker_auth_successful = True
    216                  self.populate_session_profiles()
    217                  return True
    218          self._broker_auth_successful = False
    219          self.broker_authid = None
    220          return False
    221
    222 -    def broker_disconnect(self):
    223          """\
    224          Disconnect from an (already) authenticated broker session.
    225
    226          All authentication parameters will be dropped (forgotten) and
    227          this instance has to re-authenticate against / re-connect to the
    228          session broker before any new interaction with the broker is possible.
    229
    230          """
    231          _profile_ids = copy.deepcopy(self.profile_ids)
    232
    233          # forget nearly everything...
    234          for profile_id in _profile_ids:
    235              self.init_profile_cache(profile_id)
    236              try: del self._profile_metatypes[profile_id]
    237              except KeyError: pass
    238              try: self._profiles_need_profile_id_renewal.remove(profile_id)
    239              except ValueError: pass
    240              try: del self._cached_profile_ids[profile_id]
    241              except KeyError: pass
    242              del self.session_profiles[profile_id]
    243          self._mutable_profile_ids = None
    244          self._broker_auth_successful = False
    245          self.broker_authid = None
    246          self.broker_password = None
    247          self.broker_noauth = False
    248
    249 -    def is_broker_authenticated(self):
    250          """\
    251          Detect if an authenticated broker session has already been
    252          initiated. Todo so, a simple re-authentication (username, password)
    253          will be attempted. If that fails, user credentials are not provided /
    254          valid.
    255
    256          @return: C{True} if the broker session has already been authenticated
    257              and user credentials are known / valid
    258          @rtype: C{bool}
    259
    260          """
    261          if self._broker_auth_successful is None:
    262              # do a test auth against the given broker URL
    263              try:
    264                  self.broker_simpleauth(self.broker_username, self.broker_password)
    265              except x2go.x2go_exceptions.X2GoBrokerConnectionException:
    266                  self._broker_auth_successful = False
    267          return self._broker_auth_successful
    268
    269 -    def broker_listprofiles(self):
    270          """\
    271          Obtain a session profile list from the X2Go Session Broker.
    272
    273          @return: session profiles as a Python dictionary.
    274          @rtype: C{dict}
    275
    276          """
    277          if self.broker_url is not None:
    278              request_data = {
    279                  'task': 'listprofiles',
    280                  'user': self.broker_username,
    281              }
    282              if self.broker_authid is not None:
    283                  request_data['authid'] = self.broker_authid
    284                  self.logger("Sending request to broker: user: {user}, authid: {authid}, task: {task}".format(**request_data), log.loglevel_DEBUG)
    285              else:
    286                  if self.broker_password:
    287                      request_data['password'] = "<hidden>"
    288                  else:
    289                      request_data['password'] = "<EMPTY>"
    290                  self.logger("Sending request to broker: user: {user}, password: {password}, task: {task}".format(**request_data), log.loglevel_DEBUG)
    291                  request_data['password'] = self.broker_password or ''
    292              try:
    293                  r = requests.post(self.broker_url, data=request_data)
    294              except requests.exceptions.ConnectionError:
    295                  raise x2go.x2go_exceptions.X2GoBrokerConnectionException('Failed to connect to URL %s' % self.broker_url)
    296              if r.status_code == 200 and r.headers['content-type'].startswith("text/json"):
    297                  payload = json.loads(r.text)
    298                  if payload.has_key('next-authid'):
    299                      self.broker_authid = payload['next-authid']
    300                  if payload.has_key('mutable_profile_ids'):
    301                      self._mutable_profile_ids = payload['mutable_profile_ids']
    302                  self._broker_auth_successful = True
    303                  return payload['profiles'] if payload['task'] == 'listprofiles' else {}
    304          self._broker_auth_successful = False
    305          self.broker_authid = None
    306          return {}
    307
    308 -    def broker_selectsession(self, profile_id):
    309          """\
    310          Select a session from the list of available session profiles (presented by
    311          L{broker_listprofiles}). This method requests a session information dictionary
    312          (server, port, SSH keys, already running / suspended sessions, etc.) from the
    313          session broker for the provided C{profile_id}.
    314
    315          @param profile_id: profile ID of the selected session profile
    316          @type profile_id: C{str}
    317
    318          @return: session information (server, port, SSH keys, etc.) for a selected
    319              session profile (i.e. C{profile_id})
    320          @rtype: C{dict}
    321
    322          """
    323          if self.broker_url is not None:
    324              if not self._broker_profile_cache.has_key(profile_id) or not self._broker_profile_cache[profile_id]:
    325                  request_data = {
    326                      'task': 'selectsession',
    327                      'profile-id': profile_id,
    328                      'user': self.broker_username,
    329                      'pubkey': self.broker_my_pubkey,
    330                  }
    331                  if self.broker_authid is not None:
    332                      request_data['authid'] = self.broker_authid
    333                      self.logger("Sending request to broker: user: {user}, authid: {authid}, task: {task}".format(**request_data), log.loglevel_DEBUG)
    334                  else:
    335                      if self.broker_password:
    336                          request_data['password'] = "<hidden>"
    337                      else:
    338                          request_data['password'] = "<EMPTY>"
    339                      self.logger("Sending request to broker: user: {user}, password: {password}, task: {task}".format(**request_data), log.loglevel_DEBUG)
    340                      request_data['password'] = self.broker_password or ''
    341                  try:
    342                      r = requests.post(self.broker_url, data=request_data)
    343                  except requests.exceptions.ConnectionError:
    344                      raise x2go.x2go_exceptions.X2GoBrokerConnectionException('Failed to connect to URL %s' % self.broker_url)
    345                  if r.status_code == 200 and r.headers['content-type'].startswith("text/json"):
    346                      payload = json.loads(r.text)
    347                      if payload.has_key('next-authid'):
    348                          self.broker_authid = payload['next-authid']
    349                      self._broker_profile_cache[profile_id] = payload['selected_session'] if payload['task'] == 'selectsession' else {}
    350                      self._broker_auth_successful = True
    351                  else:
    352                      self.broker_authid = None
    353                      self._broker_auth_successful = False
    354              self._broker_profile_cache[profile_id]
    355              return self._broker_profile_cache[profile_id]
    356          return {}
    357
    358 -    def _init_profile_cache(self, profile_id):
    359          if self._broker_profile_cache.has_key(unicode(profile_id)):
    360              del self._broker_profile_cache[unicode(profile_id)]
    361
    362 -    def _populate_session_profiles(self):
    363          """\
    364          Populate the set of session profiles by loading the session
    365          profile configuration from a file in INI format.
    366
    367          @return: a set of session profiles
    368          @rtype: C{dict}
    369
    370          """
    371          if self.is_broker_authenticated() and \
    372             self.broker_noauth or \
    373             self.broker_username and self.broker_password:
    374
    375              session_profiles = self.broker_listprofiles()
    376              _session_profiles = copy.deepcopy(session_profiles)
    377
    378              for session_profile in _session_profiles:
    379                  session_profile = unicode(session_profile)
    380                  for key, default_value in self.defaultSessionProfile.iteritems():
    381                      key = unicode(key)
    382                      if type(default_value) is types.StringType:
    383                          default_value = unicode(default_value)
    384                      if not session_profiles[session_profile].has_key(key):
    385                          session_profiles[session_profile][key] = default_value
    386
    387          else:
    388              session_profiles = {}
    389
    390          return session_profiles
    391
    392 -    def _is_mutable(self, profile_id):
    393          if type(self._mutable_profile_ids) is types.ListType and profile_id in self._mutable_profile_ids:
    394              return True
    395          return False
    396
    397 -    def _supports_mutable_profiles(self):
    398          if type(self._mutable_profile_ids) is types.ListType:
    399              return True
    400          return False
    401
    402 -    def _write(self):
    403          print "not suported, yet"
    404
    405 -    def _delete_profile(self, profile_id):
    406          del self.session_profiles[unicode(profile_id)]
    407
    408 -    def _update_value(self, profile_id, option, value):
    409          if type(value) is types.StringType:
    410              value = unicode(value)
    411          self.session_profiles[unicode(profile_id)][unicode(option)] = value
    412
    413 -    def _get_profile_parameter(self, profile_id, option, key_type):
    414          return key_type(self.session_profiles[unicode(profile_id)][unicode(option)])
    415
    416 -    def _get_profile_options(self, profile_id):
    417          return self.session_profiles[unicode(profile_id)].keys()
    418
    419 -    def _get_profile_ids(self):
    420          self.session_profiles.keys()
    421          return self.session_profiles.keys()
    422
    423 -    def _get_server_hostname(self, profile_id):
    424          selected_session = self.broker_selectsession(profile_id)
    425          return selected_session['server']
    426
    427 -    def _get_server_port(self, profile_id):
    428          selected_session = self.broker_selectsession(profile_id)
    429          return int(selected_session['port'])
    430
    431 -    def _get_pkey_object(self, profile_id):
    432          selected_session = self.broker_selectsession(profile_id)
    433          if selected_session.has_key('authentication_pubkey') and selected_session['authentication_pubkey'] == 'ACCEPTED':
    434              time.sleep(2)
    435              return self.broker_my_privkey
    436          return None
    437