1 #=======================================================================
3 __version__
= '''0.1.71'''
4 __sub_version__
= '''20041027054900'''
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 revise security settings!!!
33 # TODO revise mix-in criteria...
34 class Session(object):
36 this is the generic RPC session class.
38 this provides basic password support.
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...
47 interface
.inherit(iname
='ISession')
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')
61 interface
.add('changepassword', writable
=False, deleteable
=False)
63 # this will define the private attributes
78 def __getattr__(self
, name
):
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
):
86 this is a generic getstate...
88 d
= self
.__dict
__.copy()
89 if '__session_dict__' in d
:
90 del d
['__session_dict__']
92 def __setstate__(self
, data
):
94 this is a generic setstate mothod...
96 self
.__dict
__.update(data
)
97 # LL password and security...
98 def _setpassword(self
, password
):
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
)
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
__ = {}
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
__
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):
140 ## this is called on session open.
142 ## a session manager object is passed in...
144 ## def onSessionClose(self, manager):
146 ## this is called on session open.
148 ## a session manager object is passed in...
153 #-----------------------------------------------------------------------
154 #---------------------------------------------------RPCSessionManager---
155 # TODO split this into diferent functionality primitives...
156 # - *global method call* functionality.
159 # TODO global method install/unistall (may be session speciffic...)
160 # TODO define a help protocol:
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
180 # this will take the session object as argument and return the
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...
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...
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' \
202 # this sets the legth of the sid...
204 # Global method data:
205 # NOTE: there are two interfaces currently supported, one of which
206 # might get depricated...
208 # this defines the list of accesible global methods...
209 __global_methods__
= (
212 # this defines the list of non=system methods available without a
214 __public_methods__
= (
216 # this defines system (special-case) methods...
217 __system_methods__
= (
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.
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! :) )
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__:
253 raise SessionError
, 'no such object ("%s").' % obj_id
254 ## obj = self.__session_objects__[obj_id]
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
__) \
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
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
)
273 if self
.__timeout
__ != None:
274 obj
._last
_accessed
= time
.time()
275 # fire _onSessionOpen event
276 if hasattr(obj
, '_onSessionOpen'):
277 obj
._onSessionOpen
(self
)
280 def close_session(self
, sid
, *p
, **n
):
282 this will close an existing session.
284 if sid
not in self
.__active
_sessions
__:
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
)
303 # System methods (internal):
304 def dispatch(self
, method
, *pargs
, **nargs
):
308 if method
in self
.__system
_methods
__ + self
.__public
_methods
__:
309 return getattr(self
, method
)(*pargs
, **nargs
)
312 # dispatch the call to a valid session object...
314 raise TypeError, 'argument list too short (must be 1 or more arguments; got %d).' % len(pargs
)
316 # check session object age...
317 if not self
.isalive(sid
):
318 raise SessionError
, 'no such session.'
319 path
= method
.split('.')
321 session_obj
= self
.__active
_sessions
__[sid
][1]
323 if hasattr(self
, '__session_proxy__') and self
.__session
_proxy
__ != None:
324 session_obj
= self
.__session
_proxy
__(session_obj
)
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
)
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__
346 obj
= self
._getobject
(sid
, self
.__active
_sessions
__[sid
][1], path
)
348 if acl
.isglobalmethodallowed(obj
, meth
):
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
__
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
369 obj
= getattr(obj
, obj_name
)
371 for obj_name
in path
:
372 obj
= getattr(obj
, obj_name
)
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...
383 c
= self
.__sid
_chars
__
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....
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
)
403 # these are generic clobal methods.
404 # all global methods must be of the form:
405 # <method>(sid, path, obj, ...) -> res
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
413 ## def _test_I(self, sid, path, obj, *p, **n):
415 ## this is here for testing only (interface I)...
417 ## return str({'sid':sid, 'path':path, 'obj':obj, 'p':p, 'n':n})
418 ## def GLOBAL__test_II(self, sid, path, obj, *p, **n):
420 ## this is here for testing only (interface II)...
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
__ + \
445 # this defines the list of non-system methods available without a
447 __public_methods__
= RPCSessionManager
.__public
_methods
__ + \
450 ## 'methodSignature',
455 # TODO make acl optional for these.... (ACL is the objects
456 # responsibility (not the interface...))
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
)
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
__
472 return acl
.getattr(obj
, name
)
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
484 ## # get the actual target...
485 ## obj = self._getobject(sid, self.__active_sessions__[sid][1], path)
489 # this will return only the allowed attributes...
491 res
[name
] = _getattr(obj
, name
)
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
)
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
514 _setattr(obj
, key
, data
[key
])
519 def delattr(self
, sid
, path
):
522 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
523 acl
= self
.__acl
_lib
__
525 return acl
.delattr(obj
, name
)
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
__
536 # call the doc makic method if it exists...
537 if hasattr(obj
, '__method_doc__'):
539 return obj
.__method
_doc
__()
541 # session/object specific methods...
542 if hasattr(obj
, '__public_attrs__'):
543 lst
= list(obj
.__public
_attrs
__)
548 o
= acl
.getattr(obj
, attr
)
549 except AttributeError:
552 if callable(o
) and acl
.isaccessible(o
):
553 if hasattr(o
, '__doc__') and type(o
.__doc
__) is str:
554 res
[attr
] = o
.__doc
__
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
__
565 # do global methods (interface II)...
567 pattern
= self
.__globalmethodnameformat
__.split('%s')
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]
577 if hasattr(o
, '__doc__') and type(o
.__doc
__) is str:
578 res
[meth
] = o
.__doc
__
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...
590 if hasattr(obj
, '__doc__'):
591 res
= str(obj
.__doc
__) + '\n\n'
592 if hasattr(obj
, '__help__'):
593 res
+= str(obj
.__help
__())
595 res
= 'no documentation present for this object.'
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):
615 ## if name in self.__public_methods__:
620 #=======================================================================
621 # vim:set ts=4 sw=4 nowrap :