1 #=======================================================================
3 __version__
= '''0.1.42'''
4 __sub_version__
= '''20041019040755'''
5 __copyright__
= '''(c) Alex A. Naanou 2003'''
8 #=======================================================================
14 import pli
.interface
as interface
16 import pli
.misc
.acl
as acl
17 import pli
.misc
.passcrypt
as passcrypt
20 #-----------------------------------------------------------------------
21 #--------------------------------------------------------SessionError---
22 class SessionError(Exception):
28 #-----------------------------------------------------------------------
29 #-------------------------------------------------------------Session---
30 # use this for ACL and basic session event handlers...
31 # TODO revise security settings!!!
32 # TODO revise mix-in criteria...
33 class Session(object):
35 this is the generic RPC session class.
37 this provides basic password support.
39 # this is True if this object is public
40 # NOTE: for the session object this MUST be True
41 # NOTE: this will override the settings of the containers'
42 # private/public attrs...
46 interface
.inherit(iname
='ISession')
48 interface
.private('__public__')
49 interface
.private('__private_attrs__')
50 interface
.private('__public_attrs__')
51 interface
.private('__selector_class__')
52 interface
.private('_onSessionOpen')
53 interface
.private('_onSessionClose')
54 interface
.private('onSessionOpen')
55 interface
.private('onSessionClose')
56 interface
.private('checkpassword')
57 interface
.private('_setpassword')
58 interface
.private('password')
60 interface
.add('changepassword', writable
=False, deleteable
=False)
62 # this will define the private attributes
77 def __getattr__(self
, name
):
80 if hasattr(self
, '__session_dict__') and name
in self
.__session
_dict
__:
81 return self
.__session
_dict
__[name
]
82 raise AttributeError, '%s object has no attribute "%s"' % (self
, name
)
83 def __getstate__(self
):
85 this is a generic getstate...
87 d
= self
.__dict
__.copy()
88 if '__session_dict__' in d
:
89 del d
['__session_dict__']
91 def __setstate__(self
, data
):
93 this is a generic setstate mothod...
95 self
.__dict
__.update(data
)
96 # LL password and security...
97 def _setpassword(self
, password
):
100 self
.password
= passcrypt
.passcrypt_md5(password
)
101 def checkpassword(self
, password
):
104 if hasattr(self
, 'password') and self
.password
not in ('', None):
105 return passcrypt
.check_password_md5(password
, self
.password
)
107 # User level password and security...
108 def changepassword(self
, old_pswd
, new_pswd
):
111 # check the old password...
112 if not self
.checkpassword(old_pswd
):
113 raise SessionError
, 'can\'t change password.'
114 # change the password...
115 self
._setpassword
(new_pswd
)
116 # low level event handlers...
117 def _onSessionOpen(self
, manager
):
120 # set the temporary namespace...
121 ## if not hasattr(self, '__session_dict__'):
122 self
.__session
_dict
__ = {}
124 if hasattr(self
, 'onSessionOpen'):
125 self
.onSessionOpen(manager
)
126 def _onSessionClose(self
, manager
):
129 # delete the temporary namespace...
130 if hasattr(self
, '__session_dict__'):
131 del self
.__session
_dict
__
133 if hasattr(self
, 'onSessionClose'):
134 self
.onSessionClose(manager
)
135 # Session event handlers
136 # NOTE: these are here for documentation only...
137 ## def onSessionOpen(self, manager):
139 ## this is called on session open.
141 ## a session manager object is passed in...
143 ## def onSessionClose(self, manager):
145 ## this is called on session open.
147 ## a session manager object is passed in...
152 #-----------------------------------------------------------------------
153 #---------------------------------------------------RPCSessionManager---
154 # TODO split this into diferent functionality primitives...
155 # - *global method call* functionality.
158 # TODO global method install/unistall (may be session speciffic...)
159 # TODO define a help protocol:
165 class RPCSessionManager(object):
167 this class provides the generic RPC session interface.
169 # this will define the active session container object (defaults to dict).
170 __active_sessions__
= None
171 # this will define the session object container (dict-like).
172 __session_objects__
= None
173 # this will define the proxy object that will be used to wrap the
175 # this will take the session object as argument and return the
177 __session_proxy__
= None
178 # if this is set password checking will be enabled...
179 # NOTE: for this to function the session object must have a
180 # "checkpassword" method (see the Session class for details).
181 __password_check__
= False
182 # define the acl lib...
184 # enable acl for object access (this should be handled by the
185 # object its self)....
186 __path_acl_check__
= True
187 # if set for an object the path form that object and down will not
188 # be acl checked by the interface...
190 __acl_check_cutoff__
= False
191 # charactes allowed in a SID...
192 # NOTE: canging this in runtime will have no effect...
193 __sid_chars__
= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' \
194 'abcdefghijklmnopqrstuvwxyz' \
197 # this sets the legth of the sid...
199 # Global method data:
200 # NOTE: there are two interfaces currently supported, one of which
201 # might get depricated...
203 # this defines the list of accesible global methods...
204 __global_methods__
= (
207 # this defines the list of non=system methods available without a
209 __public_methods__
= (
211 # this defines system (special-case) methods...
212 __system_methods__
= (
218 # global method naming convention:
219 # <__globalmethodprefix__> % <public_method_name>
220 __globalmethodnameformat__
= 'GLOBAL_%s'
222 # this sets the session global timeout (idle time before session
223 # close), if None timeout checks will be disabled.
229 if self
.__active
_sessions
__ == None:
230 self
.__active
_sessions
__ = {}
231 if hasattr(self
, '__sid_len__') and self
.__sid
_len
__ < 8:
232 print >> sys
.stderr
, "WARNING: it is recommended that the SID be longer than 8 chars."
233 # System methods (external):
234 def new_session(self
, obj_id
, password
='', *p
, **n
):
236 this will create a new session.
238 sid_len
= hasattr(self
, '__sid_len__') and self
.__sid
_len
__ or 32
239 # make sure the sid is unique... (just in case... be pedantic! :) )
241 # generate a unique sid...
242 sid
= self
._new
_sid
(length
=sid_len
)
243 if sid
not in self
.__active
_sessions
__:
244 # select a session object...
245 obj
= self
.__session
_objects
__.get(obj_id
, None)
246 ## if obj_id not in self.__session_objects__:
248 raise SessionError
, 'no such object ("%s").' % obj_id
249 ## obj = self.__session_objects__[obj_id]
251 if hasattr(self
, '__password_check__') and self
.__password
_check
__ and \
252 hasattr(obj
, 'checkpassword') and not obj
.checkpassword(password
):
253 raise SessionError
, 'no such object ("%s").' % obj_id
254 # check uniqueness...
255 if hasattr(obj
, '__unique_session__') and obj
.__unique
_session
__ and obj
in self
.__active
_sessions
__.values():
256 raise SessionError
, 'can\'t open two sessions for this object (%s).' % obj_id
258 if hasattr(self
, '__session_proxy__') and self
.__session
_proxy
__ != None:
259 obj
= self
.__session
_proxy
__(obj
)
260 # add to list of active sessions...
261 self
.__active
_sessions
__[sid
] = obj
263 if self
.__timeout
__ != None:
264 obj
._last
_accessed
= time
.time()
265 # fire _onSessionOpen event
266 if hasattr(obj
, '_onSessionOpen'):
267 obj
._onSessionOpen
(self
)
270 def close_session(self
, sid
, *p
, **n
):
272 this will close an existing session.
274 if sid
not in self
.__active
_sessions
__:
276 # fire _onSessionClose event
277 obj
= self
.__active
_sessions
__[sid
]
278 if hasattr(obj
, '_onSessionClose'):
279 obj
._onSessionClose
(self
)
280 del self
.__active
_sessions
__[sid
]
281 def isalive(self
, sid
):
283 this will test if a session exists.
285 NOTE: this does not extend the life of the session object.
287 if sid
not in self
.__active
_sessions
__ or \
288 self
.__timeout
__ != None and \
289 (time
.time() - self
.__active
_sessions
__[sid
]._last
_accessed
) > self
.__timeout
__:
290 self
.close_session(sid
)
293 # System methods (internal):
294 def dispatch(self
, method
, *pargs
, **nargs
):
298 if method
in self
.__system
_methods
__ + self
.__public
_methods
__:
299 return getattr(self
, method
)(*pargs
, **nargs
)
302 # dispatch the call to a valid session object...
304 raise TypeError, 'argument list too short (must be 1 or more arguments; got %d).' % len(pargs
)
306 # check session object age...
307 if not self
.isalive(sid
):
308 raise SessionError
, 'no such session.'
309 path
= method
.split('.')
311 session_obj
= self
.__active
_sessions
__[sid
]
313 if self
.__timeout
__ != None:
314 session_obj
._last
_accessed
= time
.time()
315 # check if we are using a global method...
316 if path
[-1] in self
.__global
_methods
__:
317 # call the global method...
318 return self
._callglobal
(sid
, path
[:-1], path
[-1], *pargs
[1:], **nargs
)
319 if hasattr(self
, self
.__globalmethodnameformat
__ % path
[-1]):
320 # call the global method...
321 return self
._callglobal
(sid
, path
[:-1], self
.__globalmethodnameformat
__ % path
[-1], *pargs
[1:], **nargs
)
322 # dispatch the call to the actual object...
323 return self
._getobject
(sid
, session_obj
, path
)(*pargs
[1:], **nargs
)
325 def _callglobal(self
, sid
, path
, meth
, *p
, **n
):
327 this will call the global method and handel security...
329 ## if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
330 ## acl = self.__acl_lib__
333 obj
= self
._getobject
(sid
, self
.__active
_sessions
__[sid
], path
)
335 if acl
.isglobalmethodallowed(obj
, meth
):
337 return getattr(self
, meth
)(sid
, path
, obj
, *p
, **n
)
338 ##!!! recheck security !!!##
339 def _getobject(self
, sid
, obj
, path
):
341 get an object by its path (with ACL).
343 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
344 acl
= self
.__acl
_lib
__
347 if hasattr(self
, '__path_acl_check__') and self
.__path
_acl
_check
__:
348 acl_check_cutoff
= False
349 for obj_name
in path
:
350 if not acl_check_cutoff
:
351 # check if this attr is accessible...
352 obj
= acl
.getattr(obj
, obj_name
)
353 if hasattr(obj
, '__acl_check_cutoff__') and obj
.__acl
_check
_cutoff
__:
354 acl_check_cutoff
= True
356 obj
= getattr(obj
, obj_name
)
358 for obj_name
in path
:
359 obj
= getattr(obj
, obj_name
)
361 def _new_sid(self
, length
=20, seed
=1):
363 will generate a unique session id...
365 if seed
== 0 or type(seed
) not in (int, long, float):
366 raise TypeError, 'the seed can not be: %s' % seed
367 # define some data...
370 c
= self
.__sid
_chars
__
372 rr
= random
.randrange
373 # NOTE: this is quite brain-dead so will likely be rewritten...
374 while len(res
) < length
:
375 # generate the translation table...
376 translation_table
= ''.join([c
[rr(0,lc
)] for i
in range(256)])
377 # generate the key...
378 res
+= str(sha
.new(str(time
.time()*seed
)).digest()).translate(translation_table
)
379 # truncate the result to the desired length....
381 def check_sessions(self
):
384 for session
in self
.__active
_sessions
__.keys():
385 if not self
.isalive(session
):
386 name
= self
.__active
_sessions
__[session
].__name
__
387 ##!! rewrite (with logger...) !!##
388 print '[%s] session for "%s" terminated on timeout.' % (time
.strftime('%Y%m%d%H%M%S'), name
)
390 # these are generic clobal methods.
391 # all global methods must be of the form:
392 # <method>(sid, path, obj, ...) -> res
395 # path : list of path nodes
396 # obj : the actual target object.
397 # NOTE: these are not intended for direct use...
398 # the following two are for testing and illustration of the
400 ## def _test_I(self, sid, path, obj, *p, **n):
402 ## this is here for testing only (interface I)...
404 ## return str({'sid':sid, 'path':path, 'obj':obj, 'p':p, 'n':n})
405 ## def GLOBAL__test_II(self, sid, path, obj, *p, **n):
407 ## this is here for testing only (interface II)...
409 ## return str({'sid':sid, 'path':path, 'obj':obj, 'p':p, 'n':n})
412 #-----------------------------------------------BaseRPCSessionManager---
413 class BaseRPCSessionManager(RPCSessionManager
):
415 this class defines the basic object interface for RPCSessionManager.
417 __active_sessions__
= None
418 __session_objects__
= None
420 # Global method data:
421 # this defines the list of accesible global methods...
422 __global_methods__
= RPCSessionManager
.__global
_methods
__ + \
432 # this defines the list of non-system methods available without a
434 __public_methods__
= RPCSessionManager
.__public
_methods
__ + \
437 ## 'methodSignature',
442 # TODO make acl optional for these.... (ACL is the objects
443 # responsibility (not the interface...))
445 def hasattr(self
, sid
, path
, obj
, name
):
448 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
449 acl
= self
.__acl
_lib
__
450 return acl
.hasattr(obj
, name
)
452 return hasattr(obj
, name
)
453 def getattr(self
, sid
, path
, obj
, name
):
456 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
457 acl
= self
.__acl
_lib
__
459 return acl
.getattr(obj
, name
)
462 return getattr(obj
, name
)
463 def getattrs(self
, sid
, path
, obj
, *names
):
466 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
467 acl
= self
.__acl
_lib
__
468 _getattr
= acl
.getattr
471 ## # get the actual target...
472 ## obj = self._getobject(sid, self.__active_sessions__[sid], path)
476 # this will return only the allowed attributes...
478 res
[name
] = _getattr(obj
, name
)
482 def setattr(self
, sid
, path
, obj
, name
, val
):
485 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
486 acl
= self
.__acl
_lib
__
487 return acl
.setattr(obj
, name
, val
)
489 return setattr(obj
, name
, val
)
490 def setattrs(self
, sid
, path
, obj
, data
):
493 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
494 acl
= self
.__acl
_lib
__
495 _setattr
= acl
.setattr
501 _setattr(obj
, key
, data
[key
])
506 def delattr(self
, sid
, path
):
509 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
510 acl
= self
.__acl
_lib
__
512 return acl
.delattr(obj
, name
)
514 return delattr(obj
, name
)
515 ##!!! remove dep on __public_attrs__... (???)
516 def get_methods(self
, sid
, path
, obj
):
519 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
520 acl
= self
.__acl
_lib
__
523 # call the doc makic method if it exists...
524 if hasattr(obj
, '__method_doc__'):
526 return obj
.__method
_doc
__()
528 # session/object specific methods...
529 if hasattr(obj
, '__public_attrs__'):
530 lst
= list(obj
.__public
_attrs
__)
535 o
= acl
.getattr(obj
, attr
)
536 except AttributeError:
539 if callable(o
) and acl
.isaccessible(o
):
540 if hasattr(o
, '__doc__') and type(o
.__doc
__) is str:
541 res
[attr
] = o
.__doc
__
544 # do global methods (interface I)...
545 if hasattr(self
, '__global_methods__') or True:
546 for meth
in self
.__global
_methods
__:
547 o
= getattr(self
, meth
)
548 if hasattr(o
, '__doc__') and type(o
.__doc
__) is str:
549 res
[meth
] = o
.__doc
__
552 # do global methods (interface II)...
554 pattern
= self
.__globalmethodnameformat
__.split('%s')
556 if attr
.startswith(pattern
[0]) and attr
.endswith(pattern
[-1]):
557 o
= getattr(self
, attr
)
558 meth
= '' not in pattern
and attr
.split(pattern
[0])[-1].split(pattern
[-1])[0] or \
559 pattern
[0] != '' and attr
.split(pattern
[0])[-1] or \
560 pattern
[-1] != '' and attr
.split(pattern
[-1])[0]
564 if hasattr(o
, '__doc__') and type(o
.__doc
__) is str:
565 res
[meth
] = o
.__doc
__
569 # NOTE: this may change in the future...
570 # XXX this appears not to work only for StoreClient.type....
571 def get_doc(self
, sid
, path
, obj
):
573 this will print the objects __doc__ and the return
574 of the __help__ method if it exists...
577 if hasattr(obj
, '__doc__'):
578 res
= str(obj
.__doc
__) + '\n\n'
579 if hasattr(obj
, '__help__'):
580 res
+= str(obj
.__help
__())
582 res
= 'no documentation present for this object.'
584 # Standard XMLRPC methods:
585 # NOTE: these are only available outside of a session...
586 def listMethods(self
):
588 This method returns a list of strings, one for each (non-system)
589 method supported by the XML-RPC server
591 return dict.fromkeys(self
.__public
_methods
__ + self
.__system
_methods
__).keys()
592 def methodHelp(self
, name
):
594 This method returns a documentation string for a method.
596 if name
in self
.__public
_methods
__ + self
.__system
_methods
__:
597 return getattr(self
, name
).__doc
__
598 # this is not expected to get implemeted soon...
599 ## def methodSignature(self, name):
602 ## if name in self.__public_methods__:
607 #=======================================================================
608 # vim:set ts=4 sw=4 nowrap :