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 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
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
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