1 #=======================================================================
3 __version__
= '''0.1.77'''
4 __sub_version__
= '''20060227195221'''
5 __copyright__
= '''(c) Alex A. Naanou 2003'''
8 #=======================================================================
14 import pli
.interface
as interface
15 from pli
.logictypes
import ANY
17 import pli
.misc
.acl
as acl
18 import pli
.misc
.passcrypt
as passcrypt
21 #-----------------------------------------------------------------------
22 #--------------------------------------------------------SessionError---
23 class SessionError(Exception):
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):
37 this is the generic RPC session class.
39 this provides basic password support.
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...
48 interface
.inherit(iname
='ISession')
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')
63 interface
.add('changepassword', writable
=False, deleteable
=False)
65 # this will define the private attributes
81 def __getattr__(self
, name
):
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
):
89 this is a generic getstate...
91 d
= self
.__dict
__.copy()
92 if '__session_dict__' in d
:
93 del d
['__session_dict__']
95 def __setstate__(self
, data
):
97 this is a generic setstate mothod...
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
)
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
__ = {}
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
__
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):
143 ## this is called on session open.
145 ## a session manager object is passed in...
147 ## def onSessionClose(self, manager):
149 ## this is called on session open.
151 ## a session manager object is passed in...
156 #-----------------------------------------------------------------------
157 #---------------------------------------------------RPCSessionManager---
158 # TODO revise __persistent_sessions__ mechanism.... (curently seems to
160 # TODO split this into diferent functionality primitives...
161 # - *global method call* functionality.
165 # TODO global method install/unistall (may be session speciffic...)
166 # TODO define a help protocol:
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
186 # this will take the session object as argument and return the
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...
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...
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' \
208 # this sets the legth of the sid...
210 # Global method data:
211 # NOTE: there are two interfaces currently supported, one of which
212 # might get depricated...
214 # this defines the list of accesible global methods...
215 __global_methods__
= (
218 # this defines the list of non=system methods available without a
220 __public_methods__
= (
222 # this defines system (special-case) methods...
223 __system_methods__
= (
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.
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
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! :) )
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__:
263 raise SessionError
, 'no such object ("%s").' % obj_id
264 ## obj = self.__session_objects__[obj_id]
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
__) \
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
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
)
283 ## if self.__timeout__ != None:
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) \
293 obj
._last
_accessed
= time
.time()
294 # fire _onSessionOpen event
295 if hasattr(obj
, '_onSessionOpen'):
296 obj
._onSessionOpen
(self
)
299 def close_session(self
, sid
, *p
, **n
):
301 this will close an existing session.
303 if sid
not in self
.__active
_sessions
__:
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__:
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
)
334 # System methods (internal):
335 def dispatch(self
, method
, *pargs
, **nargs
):
339 if method
in self
.__system
_methods
__ + self
.__public
_methods
__:
340 return getattr(self
, method
)(*pargs
, **nargs
)
343 # dispatch the call to a valid session object...
345 raise TypeError, 'argument list too short (must be 1 or more arguments; got %d).' % len(pargs
)
347 # check session object age...
348 if not self
.isalive(sid
):
349 raise SessionError
, 'no such session.'
351 path
= self
._getpath
(method
)
353 session_obj
= self
.__active
_sessions
__[sid
][1]
355 if hasattr(self
, '__session_proxy__') and self
.__session
_proxy
__ != None:
356 session_obj
= self
.__session
_proxy
__(session_obj
)
358 ## if self.__timeout__ != None:
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) \
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
)
379 def _getpath(self
, path
):
382 if type(path
) in (list, tuple):
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__
393 obj
= self
._getobject
(sid
, self
.__active
_sessions
__[sid
][1], path
)
395 if acl
.isglobalmethodallowed(obj
, meth
):
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
__
407 if hasattr(self
, '__path_acl_check__') and self
.__path
_acl
_check
__:
408 acl_check_cutoff
= False
409 for obj_name
in path
:
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
417 obj
= getattr(obj
, obj_name
)
419 for obj_name
in path
:
420 obj
= getattr(obj
, obj_name
)
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...
431 c
= self
.__sid
_chars
__
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....
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
)
451 # these are generic clobal methods.
452 # all global methods must be of the form:
453 # <method>(sid, path, obj, ...) -> res
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
461 ## def _test_I(self, sid, path, obj, *p, **n):
463 ## this is here for testing only (interface I)...
465 ## return str({'sid':sid, 'path':path, 'obj':obj, 'p':p, 'n':n})
466 ## def GLOBAL__test_II(self, sid, path, obj, *p, **n):
468 ## this is here for testing only (interface II)...
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
__ + \
493 # this defines the list of non-system methods available without a
495 __public_methods__
= RPCSessionManager
.__public
_methods
__ + \
498 ## 'methodSignature',
503 # TODO make acl optional for these.... (ACL is the objects
504 # responsibility (not the interface...))
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
)
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
__
520 return acl
.getattr(obj
, name
)
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
532 ## # get the actual target...
533 ## obj = self._getobject(sid, self.__active_sessions__[sid][1], path)
537 # this will return only the allowed attributes...
539 res
[name
] = _getattr(obj
, name
)
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
)
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
562 _setattr(obj
, key
, data
[key
])
567 def delattr(self
, sid
, path
):
570 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
571 acl
= self
.__acl
_lib
__
573 return acl
.delattr(obj
, name
)
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
__
584 # call the doc makic method if it exists...
585 if hasattr(obj
, '__method_doc__'):
587 return obj
.__method
_doc
__()
589 # session/object specific methods...
590 if hasattr(obj
, '__public_attrs__'):
591 lst
= list(obj
.__public
_attrs
__)
596 o
= acl
.getattr(obj
, attr
)
597 except AttributeError:
600 if callable(o
) and acl
.isaccessible(o
):
601 if hasattr(o
, '__doc__') and type(o
.__doc
__) is str:
602 res
[attr
] = o
.__doc
__
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
__
613 # do global methods (interface II)...
615 pattern
= self
.__globalmethodnameformat
__.split('%s')
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]
625 if hasattr(o
, '__doc__') and type(o
.__doc
__) is str:
626 res
[meth
] = o
.__doc
__
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...
638 if hasattr(obj
, '__doc__'):
639 res
= str(obj
.__doc
__) + '\n\n'
640 if hasattr(obj
, '__help__'):
641 res
+= str(obj
.__help
__())
643 res
= 'no documentation present for this object.'
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):
663 ## if name in self.__public_methods__:
668 #=======================================================================
669 # vim:set ts=4 sw=4 nowrap :