*** empty log message ***
[pli.git] / pli / net / rpcsession.py
blob2b2792252f1372f8f4d769abf235ce795cb474ba
1 #=======================================================================
3 __version__ = '''0.1.42'''
4 __sub_version__ = '''20041008154139'''
5 __copyright__ = '''(c) Alex A. Naanou 2003'''
8 #=======================================================================
10 import time
11 import random
12 import sha
14 ##!! remove ...
15 import pli.misc.acl as acl
16 import pli.misc.passcrypt as passcrypt
19 #-----------------------------------------------------------------------
20 #--------------------------------------------------------SessionError---
21 class SessionError(Exception):
22 '''
23 '''
27 #-----------------------------------------------------------------------
28 #-------------------------------------------------------------Session---
29 # use this for ACL and basic session event handlers...
30 # TODO revise security settings!!!
31 # TODO revise mix-in criteria...
32 class Session(object):
33 '''
34 this is the generic RPC session class.
36 this provides basic password support.
37 '''
38 # this is True if this object is public
39 # NOTE: for the session object this MUST be True
40 # NOTE: this will override the settings of the containers'
41 # private/public attrs...
42 __public__ = True
43 # this will define the private attributes
44 __private_attrs__ = (
45 '__public__',
46 '__private_attrs__',
47 '__public_attrs__',
48 '__selector_class__',
49 '_onSessionOpen',
50 '_onSessionClose',
51 'onSessionOpen',
52 'onSessionClose',
53 'checkpassword',
54 '_setpassword',
55 'password',
58 def __getattr__(self, name):
59 '''
60 '''
61 if hasattr(self, '__session_dict__') and name in self.__session_dict__:
62 return self.__session_dict__[name]
63 raise AttributeError, '%s object has no attribute "%s"' % (self, name)
64 def __getstate__(self):
65 '''
66 this is a generic getstate...
67 '''
68 d = self.__dict__.copy()
69 if '__session_dict__' in d:
70 del d['__session_dict__']
71 return d
72 def __setstate__(self, data):
73 '''
74 this is a generic setstate mothod...
75 '''
76 self.__dict__.update(data)
77 # LL password and security...
78 def _setpassword(self, password):
79 '''
80 '''
81 self.password = passcrypt.passcrypt_md5(password)
82 def checkpassword(self, password):
83 '''
84 '''
85 if hasattr(self, 'password') and self.password not in ('', None):
86 return passcrypt.check_password_md5(password, self.password)
87 return True
88 # User level password and security...
89 def changepassword(self, old_pswd, new_pswd):
90 '''
91 '''
92 # check the old password...
93 if not self.checkpassword(old_pswd):
94 raise SessionError, 'can\'t change password.'
95 # change the password...
96 self._setpassword(new_pswd)
97 # low level event handlers...
98 def _onSessionOpen(self, manager):
99 '''
101 # set the temporary namespace...
102 ## if not hasattr(self, '__session_dict__'):
103 self.__session_dict__ = {}
104 # fire the event...
105 if hasattr(self, 'onSessionOpen'):
106 self.onSessionOpen(manager)
107 def _onSessionClose(self, manager):
110 # delete the temporary namespace...
111 if hasattr(self, '__session_dict__'):
112 del self.__session_dict__
113 # fire the event...
114 if hasattr(self, 'onSessionClose'):
115 self.onSessionClose(manager)
116 # Session event handlers
117 # NOTE: these are here for documentation only...
118 ## def onSessionOpen(self, manager):
119 ## '''
120 ## this is called on session open.
122 ## a session manager object is passed in...
123 ## '''
124 ## def onSessionClose(self, manager):
125 ## '''
126 ## this is called on session open.
128 ## a session manager object is passed in...
129 ## '''
133 #-----------------------------------------------------------------------
134 #---------------------------------------------------RPCSessionManager---
135 # TODO split this into diferent functionality primitives...
136 # - *global method call* functionality.
137 # - session
138 # - ...
139 # TODO global method install/unistall (may be session speciffic...)
140 # TODO define a help protocol:
141 # __doc__
142 # __attrhelp__
143 # __signaturehelp__
144 # ...
146 class RPCSessionManager(object):
148 this class provides the generic RPC session interface.
150 # this will define the active session container object (defaults to dict).
151 __active_sessions__ = None
152 # this will define the session object container.
153 __session_objects__ = None
154 # if this is set password checking will be enabled...
155 # NOTE: for this to function the session object must have a
156 # "checkpassword" method (see the Session class for details).
157 __password_check__ = False
158 # define the acl lib...
159 __acl_lib__ = acl
160 # enable acl for object access (this should be handled by the
161 # object its self)....
162 __path_acl_check__ = True
163 # if set for an object the path form that object and down will not
164 # be acl checked by the interface...
165 ##!!! test !!##
166 __acl_check_cutoff__ = False
167 # charactes allowed in a SID...
168 # NOTE: canging this in runtime will have no effect...
169 __sid_chars__ = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' \
170 'abcdefghijklmnopqrstuvwxyz' \
171 '0123456789' \
172 './_-&%@#'
173 # this sets the legth of the sid...
174 __sid_len__ = 27
175 # Global method data:
176 # NOTE: there are two interfaces currently supported, one of which
177 # might get depricated...
178 # interface_I:
179 # this defines the list of accesible global methods...
180 __global_methods__ = (
181 ## '_test_I',
183 # this defines the list of non=system methods available without a
184 # session...
185 __public_methods__ = (
187 # this defines system (special-case) methods...
188 __system_methods__ = (
189 'new_session',
190 'close_session',
191 'isalive',
193 # interface_II:
194 # global method naming convention:
195 # <__globalmethodprefix__> % <public_method_name>
196 __globalmethodnameformat__ = 'GLOBAL_%s'
198 # this sets the session global timeout (idle time before session
199 # close), if None timeout checks will be disabled.
200 __timeout__ = 1800.0
202 def __init__(self):
205 if self.__active_sessions__ == None:
206 self.__active_sessions__ = {}
207 if hasattr(self, '__sid_len__') and self.__sid_len__ < 8:
208 print >> sys.stderr, "WARNING: it is recommended that the SID be longer than 8 chars."
209 # System methods (external):
210 def new_session(self, obj_id, password='', *p, **n):
212 this will create a new session.
214 sid_len = hasattr(self, '__sid_len__') and self.__sid_len__ or 32
215 # make sure the sid is unique... (just in case... be pedantic! :) )
216 while 1:
217 # generate a sid...
218 sid = self._new_sid(length=sid_len)
219 if sid not in self.__active_sessions__:
220 # select a session object...
221 if obj_id not in self.__session_objects__:
222 raise SessionError, 'no such object ("%s").' % obj_id
223 obj = self.__session_objects__[obj_id]
224 # check password...
225 if hasattr(self, '__password_check__') and self.__password_check__ and \
226 hasattr(obj, 'checkpassword') and not obj.checkpassword(password):
227 raise SessionError, 'no such object ("%s").' % obj_id
228 # check uniqueness...
229 if hasattr(obj, '__unique_session__') and obj.__unique_session__ and obj in self.__active_sessions__.values():
230 raise SessionError, 'can\'t open two sessions for this object (%s).' % obj_id
231 # add to list of active sessions...
232 self.__active_sessions__[sid] = obj
233 # set the time...
234 if self.__timeout__ != None:
235 obj._last_accessed = time.time()
236 # fire _onSessionOpen event
237 if hasattr(obj, '_onSessionOpen'):
238 obj._onSessionOpen(self)
239 break
240 return sid
241 def close_session(self, sid, *p, **n):
243 this will close an existing session.
245 if sid not in self.__active_sessions__:
246 return
247 # fire _onSessionClose event
248 obj = self.__active_sessions__[sid]
249 if hasattr(obj, '_onSessionClose'):
250 obj._onSessionClose(self)
251 del self.__active_sessions__[sid]
252 def isalive(self, sid):
254 this will test if a session exists.
256 NOTE: this does not extend the life of the session object.
258 if sid not in self.__active_sessions__ or \
259 self.__timeout__ != None and \
260 (time.time() - self.__active_sessions__[sid]._last_accessed) > self.__timeout__:
261 self.close_session(sid)
262 return False
263 return True
264 # System methods (internal):
265 def dispatch(self, method, *pargs, **nargs):
268 # system methods...
269 if method in self.__system_methods__ + self.__public_methods__:
270 return getattr(self, method)(*pargs, **nargs)
271 # dispatch...
272 else:
273 # dispatch the call to a valid session object...
274 if len(pargs) < 1:
275 raise TypeError, 'argument list too short (must be 1 or more arguments; got %d).' % len(pargs)
276 sid = str(pargs[0])
277 # check session object age...
278 if not self.isalive(sid):
279 raise SessionError, 'no such session.'
280 path = method.split('.')
281 # get the session...
282 session_obj = self.__active_sessions__[sid]
283 # set access time...
284 if self.__timeout__ != None:
285 session_obj._last_accessed = time.time()
286 # check if we are using a global method...
287 if path[-1] in self.__global_methods__:
288 # call the global method...
289 return self._callglobal(sid, path[:-1], path[-1], *pargs[1:], **nargs)
290 if hasattr(self, self.__globalmethodnameformat__ % path[-1]):
291 # call the global method...
292 return self._callglobal(sid, path[:-1], self.__globalmethodnameformat__ % path[-1], *pargs[1:], **nargs)
293 # dispatch the call to the actual object...
294 return self._getobject(sid, session_obj, path)(*pargs[1:], **nargs)
295 # Utility Methods:
296 def _callglobal(self, sid, path, meth, *p, **n):
298 this will call the global method and handel security...
300 ## if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
301 ## acl = self.__acl_lib__
302 ## else:
303 ## global acl
304 obj = self._getobject(sid, self.__active_sessions__[sid], path)
305 # check acl...
306 if acl.isglobalmethodallowed(obj, meth):
307 # call the method
308 return getattr(self, meth)(sid, path, obj, *p, **n)
309 ##!!! recheck security !!!##
310 def _getobject(self, sid, obj, path):
312 get an object by its path (with ACL).
314 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
315 acl = self.__acl_lib__
316 ## else:
317 ## global acl
318 if hasattr(self, '__path_acl_check__') and self.__path_acl_check__:
319 acl_check_cutoff = False
320 for obj_name in path:
321 if not acl_check_cutoff:
322 # check if this attr is accessible...
323 obj = acl.getattr(obj, obj_name)
324 if hasattr(obj, '__acl_check_cutoff__') and obj.__acl_check_cutoff__:
325 print '!!!!!!!!!!!!'
326 acl_check_cutoff = True
327 else:
328 obj = getattr(obj, obj_name)
329 else:
330 for obj_name in path:
331 obj = getattr(obj, obj_name)
332 return obj
333 def _new_sid(self, length=20, seed=1):
335 will generate a unique session id...
337 if seed == 0 or type(seed) not in (int, long, float):
338 raise TypeError, 'the seed can not be: %s' % seed
339 # define some data...
340 res = ''
341 # cache the vars...
342 c = self.__sid_chars__
343 lc = len(c) - 1
344 rr = random.randrange
345 # NOTE: this is quite brain-dead so will likely be rewritten...
346 while len(res) < length:
347 # generate the translation table...
348 translation_table = ''.join([c[rr(0,lc)] for i in range(256)])
349 # generate the key...
350 res += str(sha.new(str(time.time()*seed)).digest()).translate(translation_table)
351 # truncate the result to the desired length....
352 return res[:length]
353 def check_sessions(self):
356 for session in self.__active_sessions__.keys():
357 if not self.isalive(session):
358 name = self.__active_sessions__[session].__name__
359 ##!! rewrite (with logger...) !!##
360 print '[%s] session for "%s" terminated on timeout.' % (time.strftime('%Y%m%d%H%M%S'), name)
361 # Global Methods:
362 # these are generic clobal methods.
363 # all global methods must be of the form:
364 # <method>(sid, path, obj, ...) -> res
365 # where:
366 # sid : session id
367 # path : list of path nodes
368 # obj : the actual target object.
369 # NOTE: these are not intended for direct use...
370 # the following two are for testing and illustration of the
371 # interface usage...
372 ## def _test_I(self, sid, path, obj, *p, **n):
373 ## '''
374 ## this is here for testing only (interface I)...
375 ## '''
376 ## return str({'sid':sid, 'path':path, 'obj':obj, 'p':p, 'n':n})
377 ## def GLOBAL__test_II(self, sid, path, obj, *p, **n):
378 ## '''
379 ## this is here for testing only (interface II)...
380 ## '''
381 ## return str({'sid':sid, 'path':path, 'obj':obj, 'p':p, 'n':n})
384 #-----------------------------------------------BaseRPCSessionManager---
385 class BaseRPCSessionManager(RPCSessionManager):
387 this class defines the basic object interface for RPCSessionManager.
389 __active_sessions__ = None
390 __session_objects__ = None
392 # Global method data:
393 # this defines the list of accesible global methods...
394 __global_methods__ = RPCSessionManager.__global_methods__ + \
396 'hasattr',
397 'getattr',
398 'getattrs',
399 'setattr',
400 'setattrs',
401 'get_methods',
402 'get_doc',
404 # this defines the list of non-system methods available without a
405 # session...
406 __public_methods__ = RPCSessionManager.__public_methods__ + \
408 'listMethods',
409 ## 'methodSignature',
410 'methodHelp',
411 'new_session'
414 # TODO make acl optional for these.... (ACL is the objects
415 # responsibility (not the interface...))
416 # Global Methods:
417 def hasattr(self, sid, path, obj, name):
420 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
421 acl = self.__acl_lib__
422 ## else:
423 ## global acl
424 return acl.hasattr(obj, name)
425 def getattr(self, sid, path, obj, name):
428 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
429 acl = self.__acl_lib__
430 ## else:
431 ## global acl
432 # form the result
433 return acl.getattr(obj, name)
434 def getattrs(self, sid, path, obj, *names):
437 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
438 acl = self.__acl_lib__
439 ## else:
440 ## global acl
441 ## # get the actual target...
442 ## obj = self._getobject(sid, self.__active_sessions__[sid], path)
443 # form the result
444 res = {}
445 for name in names:
446 # this will return only the allowed attributes...
447 try:
448 res[name] = acl.getattr(obj, name)
449 except:
450 pass
451 return res
452 def setattr(self, sid, path, obj, name, val):
455 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
456 acl = self.__acl_lib__
457 ## else:
458 ## global acl
459 return acl.setattr(obj, name, val)
460 def setattrs(self, sid, path, obj, data):
463 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
464 acl = self.__acl_lib__
465 ## else:
466 ## global acl
467 err = False
468 for key in data:
469 try:
470 acl.setattr(obj, key, data[key])
471 except:
472 err = True
473 if err:
474 return False
475 def get_methods(self, sid, path, obj):
478 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
479 acl = self.__acl_lib__
480 ## else:
481 ## global acl
482 # call the doc makic method if it exists...
483 if hasattr(obj, '__method_doc__'):
484 ##!!!
485 return obj.__method_doc__()
486 res = {}
487 # session/object specific methods...
488 if hasattr(obj, '__public_attrs__'):
489 lst = list(obj.__public_attrs__)
490 else:
491 lst = dir(obj)
492 for attr in lst:
493 try:
494 o = acl.getattr(obj, attr)
495 except AttributeError:
496 continue
497 else:
498 if callable(o) and acl.isaccessible(o):
499 if hasattr(o, '__doc__') and type(o.__doc__) is str:
500 res[attr] = o.__doc__
501 else:
502 res[attr] = ''
503 # do global methods (interface I)...
504 if hasattr(self, '__global_methods__') or True:
505 for meth in self.__global_methods__:
506 o = getattr(self, meth)
507 if hasattr(o, '__doc__') and type(o.__doc__) is str:
508 res[meth] = o.__doc__
509 else:
510 res[meth] = ''
511 # do global methods (interface II)...
512 lst = dir(self)
513 pattern = self.__globalmethodnameformat__.split('%s')
514 for attr in lst:
515 if attr.startswith(pattern[0]) and attr.endswith(pattern[-1]):
516 o = getattr(self, attr)
517 meth = '' not in pattern and attr.split(pattern[0])[-1].split(pattern[-1])[0] or \
518 pattern[0] != '' and attr.split(pattern[0])[-1] or \
519 pattern[-1] != '' and attr.split(pattern[-1])[0]
520 # sanity check...
521 if meth == '':
522 continue
523 if hasattr(o, '__doc__') and type(o.__doc__) is str:
524 res[meth] = o.__doc__
525 else:
526 res[meth] = ''
527 return res
528 # NOTE: this may change in the future...
529 # XXX this appears not to work only for StoreClient.type....
530 def get_doc(self, sid, path, obj):
532 this will print the objects __doc__ and the return
533 of the __help__ method if it exists...
535 res = ''
536 if hasattr(obj, '__doc__'):
537 res = str(obj.__doc__) + '\n\n'
538 if hasattr(obj, '__help__'):
539 res += str(obj.__help__())
540 if res == '':
541 res = 'no documentation present for this object.'
542 return res
543 # these need ACL!!
544 ## def delattr(self, sid, path):
545 ## '''
546 ## '''
547 ## pass
548 # Standard XMLRPC methods:
549 # NOTE: these are only available outside of a session...
550 def listMethods(self):
552 This method returns a list of strings, one for each (non-system)
553 method supported by the XML-RPC server
555 return dict.fromkeys(self.__public_methods__ + self.__system_methods__).keys()
556 def methodHelp(self, name):
558 This method returns a documentation string for a method.
560 if name in self.__public_methods__ + self.__system_methods__:
561 return getattr(self, name).__doc__
562 # this is not expected to get implemeted soon...
563 ## def methodSignature(self, name):
564 ## '''
565 ## '''
566 ## if name in self.__public_methods__:
567 ## return
571 #=======================================================================
572 # vim:set ts=4 sw=4 nowrap :