*** empty log message ***
[pli.git] / pli / net / rpcsession.py
blob0bd8545ba8659eb4b9aa6a9c2cc50965898e7523
1 #=======================================================================
3 __version__ = '''0.1.77'''
4 __sub_version__ = '''20060227195221'''
5 __copyright__ = '''(c) Alex A. Naanou 2003'''
8 #=======================================================================
10 import time
11 import random
12 import sha
14 import pli.interface as interface
15 from pli.logictypes import ANY
16 ##!! remove ...
17 import pli.misc.acl as acl
18 import pli.misc.passcrypt as passcrypt
21 #-----------------------------------------------------------------------
22 #--------------------------------------------------------SessionError---
23 class SessionError(Exception):
24 '''
25 '''
29 #-----------------------------------------------------------------------
30 #-------------------------------------------------------------Session---
31 # use this for ACL and basic session event handlers...
32 # TODO persistent session objects...
33 # TODO revise security settings!!!
34 # TODO revise mix-in criteria...
35 class Session(object):
36 '''
37 this is the generic RPC session class.
39 this provides basic password support.
40 '''
41 # this is True if this object is public
42 # NOTE: for the session object this MUST be True
43 # NOTE: this will override the settings of the containers'
44 # private/public attrs...
45 __public__ = True
47 # setup interface...
48 interface.inherit(iname='ISession')
49 # private...
50 interface.private('__public__')
51 interface.private('__private_attrs__')
52 interface.private('__public_attrs__')
53 interface.private('__selector_class__')
54 interface.private('__timeout__')
55 interface.private('_onSessionOpen')
56 interface.private('_onSessionClose')
57 interface.private('onSessionOpen')
58 interface.private('onSessionClose')
59 interface.private('checkpassword')
60 interface.private('_setpassword')
61 interface.private('password')
62 # public...
63 interface.add('changepassword', writable=False, deleteable=False)
65 # this will define the private attributes
66 __private_attrs__ = (
67 '__public__',
68 '__private_attrs__',
69 '__public_attrs__',
70 '__selector_class__',
71 '__timeout__',
72 '_onSessionOpen',
73 '_onSessionClose',
74 'onSessionOpen',
75 'onSessionClose',
76 'checkpassword',
77 '_setpassword',
78 'password',
81 def __getattr__(self, name):
82 '''
83 '''
84 if hasattr(self, '__session_dict__') and name in self.__session_dict__:
85 return self.__session_dict__[name]
86 raise AttributeError, '%s object has no attribute "%s"' % (self, name)
87 def __getstate__(self):
88 '''
89 this is a generic getstate...
90 '''
91 d = self.__dict__.copy()
92 if '__session_dict__' in d:
93 del d['__session_dict__']
94 return d
95 def __setstate__(self, data):
96 '''
97 this is a generic setstate mothod...
98 '''
99 self.__dict__.update(data)
100 # LL password and security...
101 def _setpassword(self, password):
104 self.password = passcrypt.passcrypt_md5(password)
105 def checkpassword(self, password):
108 if hasattr(self, 'password') and self.password not in ('', None):
109 return passcrypt.check_password_md5(password, self.password)
110 return True
111 # User level password and security...
112 def changepassword(self, old_pswd, new_pswd):
115 # check the old password...
116 if not self.checkpassword(old_pswd):
117 raise SessionError, 'can\'t change password.'
118 # change the password...
119 self._setpassword(new_pswd)
120 # low level event handlers...
121 def _onSessionOpen(self, manager):
124 # set the temporary namespace...
125 ## if not hasattr(self, '__session_dict__'):
126 self.__session_dict__ = {}
127 # fire the event...
128 if hasattr(self, 'onSessionOpen'):
129 self.onSessionOpen(manager)
130 def _onSessionClose(self, manager):
133 # delete the temporary namespace...
134 if hasattr(self, '__session_dict__'):
135 del self.__session_dict__
136 # fire the event...
137 if hasattr(self, 'onSessionClose'):
138 self.onSessionClose(manager)
139 # Session event handlers
140 # NOTE: these are here for documentation only...
141 ## def onSessionOpen(self, manager):
142 ## '''
143 ## this is called on session open.
145 ## a session manager object is passed in...
146 ## '''
147 ## def onSessionClose(self, manager):
148 ## '''
149 ## this is called on session open.
151 ## a session manager object is passed in...
152 ## '''
156 #-----------------------------------------------------------------------
157 #---------------------------------------------------RPCSessionManager---
158 # TODO revise __persistent_sessions__ mechanism.... (curently seems to
159 # be "hackish" :) )
160 # TODO split this into diferent functionality primitives...
161 # - *global method call* functionality.
162 # - session
163 # _ key/pass
164 # - ...
165 # TODO global method install/unistall (may be session speciffic...)
166 # TODO define a help protocol:
167 # __doc__
168 # __attrhelp__
169 # __signaturehelp__
170 # ...
172 class RPCSessionManager(object):
174 this class provides the generic RPC session interface.
176 # this will define the active session container object (defaults to dict).
177 # NOTE: the item format of this is: <sid>: (<obj-id>, <obj>)
178 __active_sessions__ = None
179 # this will define the session object container (dict-like).
180 __session_objects__ = None
181 # this if True will prevent opening two connection for one session
182 # object (default: False).
183 __unique_session__ = True
184 # this will define the proxy object that will be used to wrap the
185 # session object...
186 # this will take the session object as argument and return the
187 # proxy.
188 __session_proxy__ = None
189 # if this is set password checking will be enabled...
190 # NOTE: for this to function the session object must have a
191 # "checkpassword" method (see the Session class for details).
192 __password_check__ = False
193 # define the acl lib...
194 __acl_lib__ = acl
195 # enable acl for object access (this should be handled by the
196 # object its self)....
197 __path_acl_check__ = True
198 # if set for an object the path form that object and down will not
199 # be acl checked by the interface...
200 ##!!! test !!##
201 __acl_check_cutoff__ = False
202 # charactes allowed in a SID...
203 # NOTE: canging this in runtime will have no effect...
204 __sid_chars__ = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' \
205 'abcdefghijklmnopqrstuvwxyz' \
206 '0123456789' \
207 '._-@#'
208 # this sets the legth of the sid...
209 __sid_len__ = 27
210 # Global method data:
211 # NOTE: there are two interfaces currently supported, one of which
212 # might get depricated...
213 # interface_I:
214 # this defines the list of accesible global methods...
215 __global_methods__ = (
216 ## '_test_I',
218 # this defines the list of non=system methods available without a
219 # session...
220 __public_methods__ = (
222 # this defines system (special-case) methods...
223 __system_methods__ = (
224 'new_session',
225 'close_session',
226 'isalive',
228 # interface_II:
229 # global method naming convention:
230 # <__globalmethodprefix__> % <public_method_name>
231 __globalmethodnameformat__ = 'GLOBAL_%s'
233 # this sets the session global timeout (idle time before session
234 # close), if None timeout checks will be disabled.
235 __timeout__ = 1800.0
236 # this will enable the session to set its own timeout...
237 __session_timeout__ = False
238 # this if True will enable persistent session objects...
239 __persistent_sessions__ = False
241 def __init__(self):
244 if self.__active_sessions__ == None:
245 self.__active_sessions__ = {}
246 if hasattr(self, '__sid_len__') and self.__sid_len__ < 8:
247 print >> sys.stderr, "WARNING: it is recommended that the SID be longer than 8 chars."
248 # System methods (external):
249 def new_session(self, obj_id, password='', *p, **n):
251 this will create a new session.
253 sid_len = hasattr(self, '__sid_len__') and self.__sid_len__ or 32
254 # make sure the sid is unique... (just in case... be pedantic! :) )
255 while 1:
256 # generate a unique sid...
257 sid = self._new_sid(length=sid_len)
258 if sid not in self.__active_sessions__:
259 # select a session object...
260 obj = self.__session_objects__.get(obj_id, None)
261 ## if obj_id not in self.__session_objects__:
262 if obj == None:
263 raise SessionError, 'no such object ("%s").' % obj_id
264 ## obj = self.__session_objects__[obj_id]
265 # check password...
266 if hasattr(self, '__password_check__') and self.__password_check__ and \
267 hasattr(obj, 'checkpassword') and not obj.checkpassword(password):
268 raise SessionError, 'no such object ("%s").' % obj_id
269 # check uniqueness...
270 if (obj_id, ANY) in self.__active_sessions__.values() and \
271 ((hasattr(self, '__unique_session__') and self.__unique_session__) \
272 or \
273 (hasattr(obj, '__unique_session__') and obj.__unique_session__)):
274 raise SessionError, 'can\'t open two sessions for this object (%s).' % obj_id
275 ## if hasattr(obj, '__unique_session__') and obj.__unique_session__ and (obj_id, ANY) in self.__active_sessions__.values():
276 ## raise SessionError, 'can\'t open two sessions for this object (%s).' % obj_id
277 # proxy...
278 ## if hasattr(self, '__session_proxy__') and self.__session_proxy__ != None:
279 ## obj = self.__session_proxy__(obj)
280 # add to list of active sessions...
281 self.__active_sessions__[sid] = (obj_id, obj)
282 # set the time...
283 ## if self.__timeout__ != None:
284 ##!!! TEST !!!##
285 # set the timeout if the timeouts are enabled (in the
286 # manager) and if persistent sessions are enabled and
287 # the session timout is either not present or is not None
288 if self.__timeout__ != None \
289 and (self.__persistent_sessions__ \
290 and (not hasattr(obj, '__timeout__') or obj.__timeout__ != None) \
291 or True) \
292 or False:
293 obj._last_accessed = time.time()
294 # fire _onSessionOpen event
295 if hasattr(obj, '_onSessionOpen'):
296 obj._onSessionOpen(self)
297 break
298 return sid
299 def close_session(self, sid, *p, **n):
301 this will close an existing session.
303 if sid not in self.__active_sessions__:
304 return
305 # fire _onSessionClose event
306 obj = self.__active_sessions__[sid][1]
307 if hasattr(obj, '_onSessionClose'):
308 obj._onSessionClose(self)
309 del self.__active_sessions__[sid]
310 def isalive(self, sid):
312 this will test if a session exists.
314 NOTE: this does not extend the life of the session object.
316 ## if sid not in self.__active_sessions__ or \
317 ## self.__timeout__ != None and \
318 ## (time.time() - self.__active_sessions__[sid][1]._last_accessed) > self.__timeout__:
319 ##!!! TEST !!!##
320 if sid in self.__active_sessions__:
321 obj = self.__active_sessions__[sid][1]
322 if self.__session_timeout__ == True \
323 and None != getattr(obj, '__timeout__', -1) > 0 \
324 and hasattr(obj, '_last_accessed') \
325 and (time.time() - obj._last_accessed) > obj.__timeout__:
326 self.close_session(sid)
327 elif self.__timeout__ != None \
328 and hasattr(obj, '_last_accessed') \
329 and (time.time() - obj._last_accessed) > self.__timeout__:
330 self.close_session(sid)
331 else:
332 return True
333 return False
334 # System methods (internal):
335 def dispatch(self, method, *pargs, **nargs):
338 # system methods...
339 if method in self.__system_methods__ + self.__public_methods__:
340 return getattr(self, method)(*pargs, **nargs)
341 # dispatch...
342 else:
343 # dispatch the call to a valid session object...
344 if len(pargs) < 1:
345 raise TypeError, 'argument list too short (must be 1 or more arguments; got %d).' % len(pargs)
346 sid = str(pargs[0])
347 # check session object age...
348 if not self.isalive(sid):
349 raise SessionError, 'no such session.'
350 # get a nice path...
351 path = self._getpath(method)
352 # get the session...
353 session_obj = self.__active_sessions__[sid][1]
354 # proxy...
355 if hasattr(self, '__session_proxy__') and self.__session_proxy__ != None:
356 session_obj = self.__session_proxy__(session_obj)
357 # set access time...
358 ## if self.__timeout__ != None:
359 ##!!! TEST !!!##
360 # set the timeout if the timeouts are enabled (in the
361 # manager) and if persistent sessions are enabled and
362 # the session timout is either not present or is not None
363 if self.__timeout__ != None \
364 and (self.__persistent_sessions__ \
365 and (not hasattr(session_obj, '__timeout__') or session_obj.__timeout__ != None) \
366 or True) \
367 or False:
368 session_obj._last_accessed = time.time()
369 # check if we are using a global method...
370 if path[-1] in self.__global_methods__:
371 # call the global method...
372 return self._callglobal(sid, path[:-1], path[-1], *pargs[1:], **nargs)
373 if hasattr(self, self.__globalmethodnameformat__ % path[-1]):
374 # call the global method...
375 return self._callglobal(sid, path[:-1], self.__globalmethodnameformat__ % path[-1], *pargs[1:], **nargs)
376 # dispatch the call to the actual object...
377 return self._getobject(sid, session_obj, path)(*pargs[1:], **nargs)
378 # Utility Methods:
379 def _getpath(self, path):
382 if type(path) in (list, tuple):
383 return path
384 return path.split('.')
385 def _callglobal(self, sid, path, meth, *p, **n):
387 this will call the global method and handel security...
389 ## if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
390 ## acl = self.__acl_lib__
391 ## else:
392 ## global acl
393 obj = self._getobject(sid, self.__active_sessions__[sid][1], path)
394 # check acl...
395 if acl.isglobalmethodallowed(obj, meth):
396 # call the method
397 return getattr(self, meth)(sid, path, obj, *p, **n)
398 ##!!! recheck security !!!##
399 def _getobject(self, sid, obj, path):
401 get an object by its path (with ACL).
403 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
404 acl = self.__acl_lib__
405 ## else:
406 ## global acl
407 if hasattr(self, '__path_acl_check__') and self.__path_acl_check__:
408 acl_check_cutoff = False
409 for obj_name in path:
410 ##!!! REWRITE !!!##
411 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None and not acl_check_cutoff:
412 # check if this attr is accessible...
413 obj = acl.getattr(obj, obj_name)
414 if hasattr(obj, '__acl_check_cutoff__') and obj.__acl_check_cutoff__:
415 acl_check_cutoff = True
416 else:
417 obj = getattr(obj, obj_name)
418 else:
419 for obj_name in path:
420 obj = getattr(obj, obj_name)
421 return obj
422 def _new_sid(self, length=20, seed=1):
424 will generate a unique session id...
426 if seed == 0 or type(seed) not in (int, long, float):
427 raise TypeError, 'the seed can not be: %s' % seed
428 # define some data...
429 res = ''
430 # cache the vars...
431 c = self.__sid_chars__
432 lc = len(c) - 1
433 rr = random.randrange
434 # NOTE: this is quite brain-dead so will likely be rewritten...
435 while len(res) < length:
436 # generate the translation table...
437 translation_table = ''.join([c[rr(0,lc)] for i in range(256)])
438 # generate the key...
439 res += str(sha.new(str(time.time()*seed)).digest()).translate(translation_table)
440 # truncate the result to the desired length....
441 return res[:length]
442 def check_sessions(self):
445 for session in self.__active_sessions__:
446 if not self.isalive(session):
447 name = self.__active_sessions__[session][0]
448 ##!! rewrite (with logger...) !!##
449 print '[%s] session for "%s" terminated on timeout.' % (time.strftime('%Y%m%d%H%M%S'), name)
450 # Global Methods:
451 # these are generic clobal methods.
452 # all global methods must be of the form:
453 # <method>(sid, path, obj, ...) -> res
454 # where:
455 # sid : session id
456 # path : list of path nodes
457 # obj : the actual target object.
458 # NOTE: these are not intended for direct use...
459 # the following two are for testing and illustration of the
460 # interface usage...
461 ## def _test_I(self, sid, path, obj, *p, **n):
462 ## '''
463 ## this is here for testing only (interface I)...
464 ## '''
465 ## return str({'sid':sid, 'path':path, 'obj':obj, 'p':p, 'n':n})
466 ## def GLOBAL__test_II(self, sid, path, obj, *p, **n):
467 ## '''
468 ## this is here for testing only (interface II)...
469 ## '''
470 ## return str({'sid':sid, 'path':path, 'obj':obj, 'p':p, 'n':n})
473 #-----------------------------------------------BaseRPCSessionManager---
474 class BaseRPCSessionManager(RPCSessionManager):
476 this class defines the basic object interface for RPCSessionManager.
478 __active_sessions__ = None
479 __session_objects__ = None
481 # Global method data:
482 # this defines the list of accesible global methods...
483 __global_methods__ = RPCSessionManager.__global_methods__ + \
485 'hasattr',
486 'getattr',
487 'getattrs',
488 'setattr',
489 'setattrs',
490 'get_methods',
491 'get_doc',
493 # this defines the list of non-system methods available without a
494 # session...
495 __public_methods__ = RPCSessionManager.__public_methods__ + \
497 'listMethods',
498 ## 'methodSignature',
499 'methodHelp',
500 'new_session'
503 # TODO make acl optional for these.... (ACL is the objects
504 # responsibility (not the interface...))
505 # Global Methods:
506 def hasattr(self, sid, path, obj, name):
509 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
510 acl = self.__acl_lib__
511 return acl.hasattr(obj, name)
512 else:
513 return hasattr(obj, name)
514 def getattr(self, sid, path, obj, name):
517 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
518 acl = self.__acl_lib__
519 # form the result
520 return acl.getattr(obj, name)
521 else:
522 # form the result
523 return getattr(obj, name)
524 def getattrs(self, sid, path, obj, *names):
527 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
528 acl = self.__acl_lib__
529 _getattr = acl.getattr
530 else:
531 _getattr = getattr
532 ## # get the actual target...
533 ## obj = self._getobject(sid, self.__active_sessions__[sid][1], path)
534 # form the result
535 res = {}
536 for name in names:
537 # this will return only the allowed attributes...
538 try:
539 res[name] = _getattr(obj, name)
540 except:
541 pass
542 return res
543 def setattr(self, sid, path, obj, name, val):
546 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
547 acl = self.__acl_lib__
548 return acl.setattr(obj, name, val)
549 else:
550 return setattr(obj, name, val)
551 def setattrs(self, sid, path, obj, data):
554 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
555 acl = self.__acl_lib__
556 _setattr = acl.setattr
557 else:
558 _setattr = setattr
559 err = False
560 for key in data:
561 try:
562 _setattr(obj, key, data[key])
563 except:
564 err = True
565 if err:
566 return False
567 def delattr(self, sid, path):
570 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
571 acl = self.__acl_lib__
572 # form the result
573 return acl.delattr(obj, name)
574 else:
575 return delattr(obj, name)
576 ##!!! remove dep on __public_attrs__... (???)
577 def get_methods(self, sid, path, obj):
580 if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
581 acl = self.__acl_lib__
582 ## else:
583 ## global acl
584 # call the doc makic method if it exists...
585 if hasattr(obj, '__method_doc__'):
586 ##!!!
587 return obj.__method_doc__()
588 res = {}
589 # session/object specific methods...
590 if hasattr(obj, '__public_attrs__'):
591 lst = list(obj.__public_attrs__)
592 else:
593 lst = dir(obj)
594 for attr in lst:
595 try:
596 o = acl.getattr(obj, attr)
597 except AttributeError:
598 continue
599 else:
600 if callable(o) and acl.isaccessible(o):
601 if hasattr(o, '__doc__') and type(o.__doc__) is str:
602 res[attr] = o.__doc__
603 else:
604 res[attr] = ''
605 # do global methods (interface I)...
606 if hasattr(self, '__global_methods__') or True:
607 for meth in self.__global_methods__:
608 o = getattr(self, meth)
609 if hasattr(o, '__doc__') and type(o.__doc__) is str:
610 res[meth] = o.__doc__
611 else:
612 res[meth] = ''
613 # do global methods (interface II)...
614 lst = dir(self)
615 pattern = self.__globalmethodnameformat__.split('%s')
616 for attr in lst:
617 if attr.startswith(pattern[0]) and attr.endswith(pattern[-1]):
618 o = getattr(self, attr)
619 meth = '' not in pattern and attr.split(pattern[0])[-1].split(pattern[-1])[0] or \
620 pattern[0] != '' and attr.split(pattern[0])[-1] or \
621 pattern[-1] != '' and attr.split(pattern[-1])[0]
622 # sanity check...
623 if meth == '':
624 continue
625 if hasattr(o, '__doc__') and type(o.__doc__) is str:
626 res[meth] = o.__doc__
627 else:
628 res[meth] = ''
629 return res
630 # NOTE: this may change in the future...
631 # XXX this appears not to work only for StoreClient.type....
632 def get_doc(self, sid, path, obj):
634 this will print the objects __doc__ and the return
635 of the __help__ method if it exists...
637 res = ''
638 if hasattr(obj, '__doc__'):
639 res = str(obj.__doc__) + '\n\n'
640 if hasattr(obj, '__help__'):
641 res += str(obj.__help__())
642 if res == '':
643 res = 'no documentation present for this object.'
644 return res
645 # Standard XMLRPC methods:
646 # NOTE: these are only available outside of a session...
647 def listMethods(self):
649 This method returns a list of strings, one for each (non-system)
650 method supported by the XML-RPC server
652 return dict.fromkeys(self.__public_methods__ + self.__system_methods__).keys()
653 def methodHelp(self, name):
655 This method returns a documentation string for a method.
657 if name in self.__public_methods__ + self.__system_methods__:
658 return getattr(self, name).__doc__
659 # this is not expected to get implemeted soon...
660 ## def methodSignature(self, name):
661 ## '''
662 ## '''
663 ## if name in self.__public_methods__:
664 ## return
668 #=======================================================================
669 # vim:set ts=4 sw=4 nowrap :