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