1 #=======================================================================
3 __version__
= '''0.1.42'''
4 __sub_version__
= '''20041008154139'''
5 __copyright__
= '''(c) Alex A. Naanou 2003'''
8 #=======================================================================
15 import pli
.misc
.acl
as acl
16 import pli
.misc
.passcrypt
as passcrypt
19 #-----------------------------------------------------------------------
20 #--------------------------------------------------------SessionError---
21 class SessionError(Exception):
27 #-----------------------------------------------------------------------
28 #-------------------------------------------------------------Session---
29 # use this for ACL and basic session event handlers...
30 # TODO revise security settings!!!
31 # TODO revise mix-in criteria...
32 class Session(object):
34 this is the generic RPC session class.
36 this provides basic password support.
38 # this is True if this object is public
39 # NOTE: for the session object this MUST be True
40 # NOTE: this will override the settings of the containers'
41 # private/public attrs...
43 # this will define the private attributes
58 def __getattr__(self
, name
):
61 if hasattr(self
, '__session_dict__') and name
in self
.__session
_dict
__:
62 return self
.__session
_dict
__[name
]
63 raise AttributeError, '%s object has no attribute "%s"' % (self
, name
)
64 def __getstate__(self
):
66 this is a generic getstate...
68 d
= self
.__dict
__.copy()
69 if '__session_dict__' in d
:
70 del d
['__session_dict__']
72 def __setstate__(self
, data
):
74 this is a generic setstate mothod...
76 self
.__dict
__.update(data
)
77 # LL password and security...
78 def _setpassword(self
, password
):
81 self
.password
= passcrypt
.passcrypt_md5(password
)
82 def checkpassword(self
, password
):
85 if hasattr(self
, 'password') and self
.password
not in ('', None):
86 return passcrypt
.check_password_md5(password
, self
.password
)
88 # User level password and security...
89 def changepassword(self
, old_pswd
, new_pswd
):
92 # check the old password...
93 if not self
.checkpassword(old_pswd
):
94 raise SessionError
, 'can\'t change password.'
95 # change the password...
96 self
._setpassword
(new_pswd
)
97 # low level event handlers...
98 def _onSessionOpen(self
, manager
):
101 # set the temporary namespace...
102 ## if not hasattr(self, '__session_dict__'):
103 self
.__session
_dict
__ = {}
105 if hasattr(self
, 'onSessionOpen'):
106 self
.onSessionOpen(manager
)
107 def _onSessionClose(self
, manager
):
110 # delete the temporary namespace...
111 if hasattr(self
, '__session_dict__'):
112 del self
.__session
_dict
__
114 if hasattr(self
, 'onSessionClose'):
115 self
.onSessionClose(manager
)
116 # Session event handlers
117 # NOTE: these are here for documentation only...
118 ## def onSessionOpen(self, manager):
120 ## this is called on session open.
122 ## a session manager object is passed in...
124 ## def onSessionClose(self, manager):
126 ## this is called on session open.
128 ## a session manager object is passed in...
133 #-----------------------------------------------------------------------
134 #---------------------------------------------------RPCSessionManager---
135 # TODO split this into diferent functionality primitives...
136 # - *global method call* functionality.
139 # TODO global method install/unistall (may be session speciffic...)
140 # TODO define a help protocol:
146 class RPCSessionManager(object):
148 this class provides the generic RPC session interface.
150 # this will define the active session container object (defaults to dict).
151 __active_sessions__
= None
152 # this will define the session object container.
153 __session_objects__
= None
154 # if this is set password checking will be enabled...
155 # NOTE: for this to function the session object must have a
156 # "checkpassword" method (see the Session class for details).
157 __password_check__
= False
158 # define the acl lib...
160 # enable acl for object access (this should be handled by the
161 # object its self)....
162 __path_acl_check__
= True
163 # if set for an object the path form that object and down will not
164 # be acl checked by the interface...
166 __acl_check_cutoff__
= False
167 # charactes allowed in a SID...
168 # NOTE: canging this in runtime will have no effect...
169 __sid_chars__
= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' \
170 'abcdefghijklmnopqrstuvwxyz' \
173 # this sets the legth of the sid...
175 # Global method data:
176 # NOTE: there are two interfaces currently supported, one of which
177 # might get depricated...
179 # this defines the list of accesible global methods...
180 __global_methods__
= (
183 # this defines the list of non=system methods available without a
185 __public_methods__
= (
187 # this defines system (special-case) methods...
188 __system_methods__
= (
194 # global method naming convention:
195 # <__globalmethodprefix__> % <public_method_name>
196 __globalmethodnameformat__
= 'GLOBAL_%s'
198 # this sets the session global timeout (idle time before session
199 # close), if None timeout checks will be disabled.
205 if self
.__active
_sessions
__ == None:
206 self
.__active
_sessions
__ = {}
207 if hasattr(self
, '__sid_len__') and self
.__sid
_len
__ < 8:
208 print >> sys
.stderr
, "WARNING: it is recommended that the SID be longer than 8 chars."
209 # System methods (external):
210 def new_session(self
, obj_id
, password
='', *p
, **n
):
212 this will create a new session.
214 sid_len
= hasattr(self
, '__sid_len__') and self
.__sid
_len
__ or 32
215 # make sure the sid is unique... (just in case... be pedantic! :) )
218 sid
= self
._new
_sid
(length
=sid_len
)
219 if sid
not in self
.__active
_sessions
__:
220 # select a session object...
221 if obj_id
not in self
.__session
_objects
__:
222 raise SessionError
, 'no such object ("%s").' % obj_id
223 obj
= self
.__session
_objects
__[obj_id
]
225 if hasattr(self
, '__password_check__') and self
.__password
_check
__ and \
226 hasattr(obj
, 'checkpassword') and not obj
.checkpassword(password
):
227 raise SessionError
, 'no such object ("%s").' % obj_id
228 # check uniqueness...
229 if hasattr(obj
, '__unique_session__') and obj
.__unique
_session
__ and obj
in self
.__active
_sessions
__.values():
230 raise SessionError
, 'can\'t open two sessions for this object (%s).' % obj_id
231 # add to list of active sessions...
232 self
.__active
_sessions
__[sid
] = obj
234 if self
.__timeout
__ != None:
235 obj
._last
_accessed
= time
.time()
236 # fire _onSessionOpen event
237 if hasattr(obj
, '_onSessionOpen'):
238 obj
._onSessionOpen
(self
)
241 def close_session(self
, sid
, *p
, **n
):
243 this will close an existing session.
245 if sid
not in self
.__active
_sessions
__:
247 # fire _onSessionClose event
248 obj
= self
.__active
_sessions
__[sid
]
249 if hasattr(obj
, '_onSessionClose'):
250 obj
._onSessionClose
(self
)
251 del self
.__active
_sessions
__[sid
]
252 def isalive(self
, sid
):
254 this will test if a session exists.
256 NOTE: this does not extend the life of the session object.
258 if sid
not in self
.__active
_sessions
__ or \
259 self
.__timeout
__ != None and \
260 (time
.time() - self
.__active
_sessions
__[sid
]._last
_accessed
) > self
.__timeout
__:
261 self
.close_session(sid
)
264 # System methods (internal):
265 def dispatch(self
, method
, *pargs
, **nargs
):
269 if method
in self
.__system
_methods
__ + self
.__public
_methods
__:
270 return getattr(self
, method
)(*pargs
, **nargs
)
273 # dispatch the call to a valid session object...
275 raise TypeError, 'argument list too short (must be 1 or more arguments; got %d).' % len(pargs
)
277 # check session object age...
278 if not self
.isalive(sid
):
279 raise SessionError
, 'no such session.'
280 path
= method
.split('.')
282 session_obj
= self
.__active
_sessions
__[sid
]
284 if self
.__timeout
__ != None:
285 session_obj
._last
_accessed
= time
.time()
286 # check if we are using a global method...
287 if path
[-1] in self
.__global
_methods
__:
288 # call the global method...
289 return self
._callglobal
(sid
, path
[:-1], path
[-1], *pargs
[1:], **nargs
)
290 if hasattr(self
, self
.__globalmethodnameformat
__ % path
[-1]):
291 # call the global method...
292 return self
._callglobal
(sid
, path
[:-1], self
.__globalmethodnameformat
__ % path
[-1], *pargs
[1:], **nargs
)
293 # dispatch the call to the actual object...
294 return self
._getobject
(sid
, session_obj
, path
)(*pargs
[1:], **nargs
)
296 def _callglobal(self
, sid
, path
, meth
, *p
, **n
):
298 this will call the global method and handel security...
300 ## if hasattr(self, '__acl_lib__') and self.__acl_lib__ != None:
301 ## acl = self.__acl_lib__
304 obj
= self
._getobject
(sid
, self
.__active
_sessions
__[sid
], path
)
306 if acl
.isglobalmethodallowed(obj
, meth
):
308 return getattr(self
, meth
)(sid
, path
, obj
, *p
, **n
)
309 ##!!! recheck security !!!##
310 def _getobject(self
, sid
, obj
, path
):
312 get an object by its path (with ACL).
314 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
315 acl
= self
.__acl
_lib
__
318 if hasattr(self
, '__path_acl_check__') and self
.__path
_acl
_check
__:
319 acl_check_cutoff
= False
320 for obj_name
in path
:
321 if not acl_check_cutoff
:
322 # check if this attr is accessible...
323 obj
= acl
.getattr(obj
, obj_name
)
324 if hasattr(obj
, '__acl_check_cutoff__') and obj
.__acl
_check
_cutoff
__:
326 acl_check_cutoff
= True
328 obj
= getattr(obj
, obj_name
)
330 for obj_name
in path
:
331 obj
= getattr(obj
, obj_name
)
333 def _new_sid(self
, length
=20, seed
=1):
335 will generate a unique session id...
337 if seed
== 0 or type(seed
) not in (int, long, float):
338 raise TypeError, 'the seed can not be: %s' % seed
339 # define some data...
342 c
= self
.__sid
_chars
__
344 rr
= random
.randrange
345 # NOTE: this is quite brain-dead so will likely be rewritten...
346 while len(res
) < length
:
347 # generate the translation table...
348 translation_table
= ''.join([c
[rr(0,lc
)] for i
in range(256)])
349 # generate the key...
350 res
+= str(sha
.new(str(time
.time()*seed
)).digest()).translate(translation_table
)
351 # truncate the result to the desired length....
353 def check_sessions(self
):
356 for session
in self
.__active
_sessions
__.keys():
357 if not self
.isalive(session
):
358 name
= self
.__active
_sessions
__[session
].__name
__
359 ##!! rewrite (with logger...) !!##
360 print '[%s] session for "%s" terminated on timeout.' % (time
.strftime('%Y%m%d%H%M%S'), name
)
362 # these are generic clobal methods.
363 # all global methods must be of the form:
364 # <method>(sid, path, obj, ...) -> res
367 # path : list of path nodes
368 # obj : the actual target object.
369 # NOTE: these are not intended for direct use...
370 # the following two are for testing and illustration of the
372 ## def _test_I(self, sid, path, obj, *p, **n):
374 ## this is here for testing only (interface I)...
376 ## return str({'sid':sid, 'path':path, 'obj':obj, 'p':p, 'n':n})
377 ## def GLOBAL__test_II(self, sid, path, obj, *p, **n):
379 ## this is here for testing only (interface II)...
381 ## return str({'sid':sid, 'path':path, 'obj':obj, 'p':p, 'n':n})
384 #-----------------------------------------------BaseRPCSessionManager---
385 class BaseRPCSessionManager(RPCSessionManager
):
387 this class defines the basic object interface for RPCSessionManager.
389 __active_sessions__
= None
390 __session_objects__
= None
392 # Global method data:
393 # this defines the list of accesible global methods...
394 __global_methods__
= RPCSessionManager
.__global
_methods
__ + \
404 # this defines the list of non-system methods available without a
406 __public_methods__
= RPCSessionManager
.__public
_methods
__ + \
409 ## 'methodSignature',
414 # TODO make acl optional for these.... (ACL is the objects
415 # responsibility (not the interface...))
417 def hasattr(self
, sid
, path
, obj
, name
):
420 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
421 acl
= self
.__acl
_lib
__
424 return acl
.hasattr(obj
, name
)
425 def getattr(self
, sid
, path
, obj
, name
):
428 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
429 acl
= self
.__acl
_lib
__
433 return acl
.getattr(obj
, name
)
434 def getattrs(self
, sid
, path
, obj
, *names
):
437 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
438 acl
= self
.__acl
_lib
__
441 ## # get the actual target...
442 ## obj = self._getobject(sid, self.__active_sessions__[sid], path)
446 # this will return only the allowed attributes...
448 res
[name
] = acl
.getattr(obj
, name
)
452 def setattr(self
, sid
, path
, obj
, name
, val
):
455 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
456 acl
= self
.__acl
_lib
__
459 return acl
.setattr(obj
, name
, val
)
460 def setattrs(self
, sid
, path
, obj
, data
):
463 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
464 acl
= self
.__acl
_lib
__
470 acl
.setattr(obj
, key
, data
[key
])
475 def get_methods(self
, sid
, path
, obj
):
478 if hasattr(self
, '__acl_lib__') and self
.__acl
_lib
__ != None:
479 acl
= self
.__acl
_lib
__
482 # call the doc makic method if it exists...
483 if hasattr(obj
, '__method_doc__'):
485 return obj
.__method
_doc
__()
487 # session/object specific methods...
488 if hasattr(obj
, '__public_attrs__'):
489 lst
= list(obj
.__public
_attrs
__)
494 o
= acl
.getattr(obj
, attr
)
495 except AttributeError:
498 if callable(o
) and acl
.isaccessible(o
):
499 if hasattr(o
, '__doc__') and type(o
.__doc
__) is str:
500 res
[attr
] = o
.__doc
__
503 # do global methods (interface I)...
504 if hasattr(self
, '__global_methods__') or True:
505 for meth
in self
.__global
_methods
__:
506 o
= getattr(self
, meth
)
507 if hasattr(o
, '__doc__') and type(o
.__doc
__) is str:
508 res
[meth
] = o
.__doc
__
511 # do global methods (interface II)...
513 pattern
= self
.__globalmethodnameformat
__.split('%s')
515 if attr
.startswith(pattern
[0]) and attr
.endswith(pattern
[-1]):
516 o
= getattr(self
, attr
)
517 meth
= '' not in pattern
and attr
.split(pattern
[0])[-1].split(pattern
[-1])[0] or \
518 pattern
[0] != '' and attr
.split(pattern
[0])[-1] or \
519 pattern
[-1] != '' and attr
.split(pattern
[-1])[0]
523 if hasattr(o
, '__doc__') and type(o
.__doc
__) is str:
524 res
[meth
] = o
.__doc
__
528 # NOTE: this may change in the future...
529 # XXX this appears not to work only for StoreClient.type....
530 def get_doc(self
, sid
, path
, obj
):
532 this will print the objects __doc__ and the return
533 of the __help__ method if it exists...
536 if hasattr(obj
, '__doc__'):
537 res
= str(obj
.__doc
__) + '\n\n'
538 if hasattr(obj
, '__help__'):
539 res
+= str(obj
.__help
__())
541 res
= 'no documentation present for this object.'
544 ## def delattr(self, sid, path):
548 # Standard XMLRPC methods:
549 # NOTE: these are only available outside of a session...
550 def listMethods(self
):
552 This method returns a list of strings, one for each (non-system)
553 method supported by the XML-RPC server
555 return dict.fromkeys(self
.__public
_methods
__ + self
.__system
_methods
__).keys()
556 def methodHelp(self
, name
):
558 This method returns a documentation string for a method.
560 if name
in self
.__public
_methods
__ + self
.__system
_methods
__:
561 return getattr(self
, name
).__doc
__
562 # this is not expected to get implemeted soon...
563 ## def methodSignature(self, name):
566 ## if name in self.__public_methods__:
571 #=======================================================================
572 # vim:set ts=4 sw=4 nowrap :