1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
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
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
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
113 self.broker_my_pubkey, self.broker_my_privkey = genkeypair(local_username=_CURRENT_LOCAL_USER, client_address='127.0.0.1')
114
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
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
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
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
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
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
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
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
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
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
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
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
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
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
401 print "not suported, yet"
402
404 del self.session_profiles[unicode(profile_id)]
405
407 if type(value) is types.StringType:
408 value = unicode(value)
409 self.session_profiles[unicode(profile_id)][unicode(option)] = value
410
412 return key_type(self.session_profiles[unicode(profile_id)][unicode(option)])
413
415 return self.session_profiles[unicode(profile_id)].keys()
416
418 self.session_profiles.keys()
419 return self.session_profiles.keys()
420
424
426 selected_session = self.broker_selectsession(profile_id)
427 return int(selected_session['port'])
428
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