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 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
115 self.broker_my_pubkey, self.broker_my_privkey = genkeypair(local_username=_CURRENT_LOCAL_USER, client_address='127.0.0.1')
116
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
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
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
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
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
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
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
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
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
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
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
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
359 if self._broker_profile_cache.has_key(unicode(profile_id)):
360 del self._broker_profile_cache[unicode(profile_id)]
361
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
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
398 if type(self._mutable_profile_ids) is types.ListType:
399 return True
400 return False
401
403 print "not suported, yet"
404
406 del self.session_profiles[unicode(profile_id)]
407
409 if type(value) is types.StringType:
410 value = unicode(value)
411 self.session_profiles[unicode(profile_id)][unicode(option)] = value
412
414 return key_type(self.session_profiles[unicode(profile_id)][unicode(option)])
415
417 return self.session_profiles[unicode(profile_id)].keys()
418
420 self.session_profiles.keys()
421 return self.session_profiles.keys()
422
426
428 selected_session = self.broker_selectsession(profile_id)
429 return int(selected_session['port'])
430
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