1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """
17 Provides RMI dispatcher classes.
18 """
19
20 import sys
21 import inspect
22 import traceback as tb
23
24 from gofer import NAME
25 from gofer.messaging import *
26 from gofer.pam import PAM
27
28 from logging import getLogger
29
30
31 log = getLogger(__name__)
39
42 """
43 Target class not found.
44 """
45
48
51 """
52 Target method not found.
53 """
54
58
61 """
62 Called method not decorated as I{remote}.
63 """
64
68
71 """
72 Not authorized.
73 """
74 pass
75
78 """
79 Authentication method not supported.
80 """
81
86
89 """
90 Method not shared between UUIDs.
91 """
92
96
99 """
100 Shared secret required and not passed.
101 """
102
106
109 """
110 User (name) required and not passed.
111 """
112
116
119 """
120 Password required and not passed.
121 """
122
126
129 """
130 Not authenticated, user/password failed
131 PAM authentication.
132 """
133
137
140 """
141 The specified user is not authorized to invoke the RMI.
142 """
143
144 - def __init__(self, method, expected, passed):
145 message = '%s(), user must be: %s, passed: %s' \
146 % (method.name,
147 expected,
148 passed)
149 NotAuthorized.__init__(self, message)
150
153 """
154 Specified secret, not matched.
155 """
156
157 - def __init__(self, method, expected, passed):
158 message = '%s(), secret: %s not in: %s' \
159 % (method.name,
160 passed,
161 expected)
162 NotAuthorized.__init__(self, message)
163
166 """
167 The re-raised (propagated) exception base class.
168 """
169
170 @classmethod
172 classname = reply.xclass
173 mod = reply.xmodule
174 state = reply.xstate
175 args = reply.xargs
176 try:
177 C = globals().get(classname)
178 if not C:
179 mod = __import__(mod, {}, {}, [classname,])
180 C = getattr(mod, classname)
181 inst = cls.__new(C)
182 inst.__dict__.update(state)
183 if isinstance(inst, Exception):
184 inst.args = args
185 except:
186 inst = RemoteException(reply.exval)
187 return inst
188
189 @classmethod
191 try:
192 import new
193 return new.instance(C)
194 except:
195 pass
196 return Exception.__new__(C)
197
198
199
200
201
202 -class Reply(Envelope):
203 """
204 Envelope for examining replies.
205 """
206
208 """
209 Test whether the reply indicates success.
210 @return: True when indicates success.
211 @rtype: bool
212 """
213 return ( self.result and 'retval' in self.result )
214
215
217 """
218 Test whether the reply indicates failure.
219 @return: True when indicates failure.
220 @rtype: bool
221 """
222 return ( self.result and 'exval' in self.result )
223
225 """
226 Test whether the reply indicates status (started).
227 @return: True when indicates started.
228 @rtype: bool
229 """
230 return ( self.status == 'started' )
231
233 """
234 Test whether the reply indicates status (progress).
235 @return: True when indicates progress.
236 @rtype: bool
237 """
238 return ( self.status == 'progress' )
239
242 """
243 Return envelope.
244 """
245
246 @classmethod
248 """
249 Return successful
250 @param x: The returned value.
251 @type x: any
252 @return: A return envelope.
253 @rtype: L{Return}
254 """
255 inst = Return(retval=x)
256 inst.dump()
257 return inst
258
259 @classmethod
261 """
262 Return raised exception.
263 @return: A return envelope.
264 @rtype: L{Return}
265 """
266 try:
267 return cls.__exception()
268 except TypeError:
269 return cls.__exception()
270
272 """
273 Test whether the return indicates success.
274 @return: True when indicates success.
275 @rtype: bool
276 """
277 return ( 'retval' in self )
278
280 """
281 Test whether the return indicates failure.
282 @return: True when indicates failure.
283 @rtype: bool
284 """
285 return ( 'exval' in self )
286
287 @classmethod
289 """
290 Return raised exception.
291 @return: A return envelope.
292 @rtype: L{Return}
293 """
294 info = sys.exc_info()
295 inst = info[1]
296 xclass = inst.__class__
297 exval = '\n'.join(tb.format_exception(*info))
298 mod = inspect.getmodule(xclass)
299 if mod:
300 mod = mod.__name__
301 args = None
302 if issubclass(xclass, Exception):
303 args = inst.args
304 state = dict(inst.__dict__)
305 state['trace'] = exval
306 inst = Return(exval=exval,
307 xmodule=mod,
308 xclass=xclass.__name__,
309 xstate=state,
310 xargs=args)
311 inst.dump()
312 return inst
313
316 """
317 An RMI request envelope.
318 """
319 pass
320
321
322 -class RMI(object):
323 """
324 The RMI object performs the invocation.
325 @ivar request: The request envelope.
326 @type request: L{Request}
327 @ivar catalog: A dict of class mappings.
328 @type catalog: dict
329 """
330
331 - def __init__(self, request, auth, catalog):
332 """
333 @param request: The request envelope.
334 @type request: L{Request}
335 @param auth: Authentication properties.
336 @type auth: L{Options}
337 @param catalog: A dict of class mappings.
338 @type catalog: dict
339 """
340 self.name = '.'.join((request.classname, request.method))
341 self.request = request
342 self.auth = auth
343 self.inst = self.getclass(request, catalog)
344 self.method = self.getmethod(request, self.inst)
345 self.args = request.args
346 self.kwargs = request.kws
347
348 @staticmethod
350 """
351 Get an instance of the class or module specified in
352 the request using the catalog.
353 @param request: The request envelope.
354 @type request: L{Request}
355 @param catalog: A dict of class mappings.
356 @type catalog: dict
357 @return: An instance.
358 @rtype: (class|module)
359 """
360 key = request.classname
361 inst = catalog.get(key, None)
362 if inst is None:
363 raise ClassNotFound(key)
364 if inspect.isclass(inst):
365 args, keywords = RMI.constructor(request)
366 return inst(*args, **keywords)
367 else:
368 return inst
369
370 @staticmethod
372 """
373 Get method of the class specified in the request.
374 Ensures that remote invocation is permitted.
375 @param request: The request envelope.
376 @type request: L{Request}
377 @param inst: A class or module object.
378 @type inst: (class|module)
379 @return: The requested method.
380 @rtype: (method|function)
381 """
382 cn, fn = (request.classname, request.method)
383 if hasattr(inst, fn):
384 return getattr(inst, fn)
385 else:
386 raise MethodNotFound(cn, fn)
387
388 @staticmethod
390 """
391 Return the method's function (if a method) or
392 the I{method} assuming it's a function.
393 @param method: An instance method.
394 @type method: instancemethod
395 @return: The function
396 @rtype: function
397 """
398 if inspect.ismethod(method):
399 fn = method.im_func
400 else:
401 fn = method
402 return fn
403
404 @staticmethod
406 """
407 Get the I{gofer} metadata embedded in the function
408 by the @remote decorator.
409 @param method: An instance method.
410 @type method: instancemethod
411 @return: The I{gofer} attribute.
412 @rtype: L{Options}
413 """
414 try:
415 return getattr(RMI.__fn(method), NAME)
416 except:
417 pass
418
419 @staticmethod
421 """
422 Get (optional) constructor arguments.
423 @return: cntr: ([],{})
424 """
425 cntr = request.cntr
426 if not cntr:
427 cntr = ([],{})
428 return cntr
429
431 """
432 Validate the method is either marked as I{shared}
433 or that the request was received on the method's
434 contributing plugin UUID.
435 @param fninfo: The decorated function info.
436 @type fninfo: L{Options}
437 @raise NotShared: On sharing violation.
438 """
439 if fninfo.shared:
440 return
441 uuid = fninfo.plugin.getuuid()
442 if not uuid:
443 return
444 log.debug('match uuid: "%s" = "%s"', self.auth.uuid, uuid)
445 if self.auth.uuid == uuid:
446 return
447 raise NotShared(self)
448
450 """
451 Check whether remote invocation of the specified method is permitted.
452 Applies security model using L{Security}.
453 """
454 fninfo = RMI.__fninfo(self.method)
455 if fninfo is None:
456 raise NotPermitted(self)
457 self.__shared(fninfo)
458 security = Security(self, fninfo)
459 security.apply(self.auth)
460
462 """
463 Invoke the method.
464 @return: The invocation result.
465 @rtype: L{Return}
466 """
467 try:
468 self.permitted()
469 retval = self.method(*self.args, **self.kwargs)
470 return Return.succeed(retval)
471 except Exception:
472 log.exception(str(self.method))
473 return Return.exception()
474
476 return str(self.request)
477
480
486 """
487 Layered Security.
488 @ivar method: The method name.
489 @type method: str
490 @ivar stack: The security stack; list of auth specifications defined by decorators.
491 @type stack: list
492 """
493
495 """
496 @param method: The method name.
497 @type method: str
498 @param fninfo: The decorated function info.
499 @type fninfo: L{Options}
500 """
501 self.method = method
502 self.stack = fninfo.security
503
504 - def apply(self, passed):
505 """
506 Apply auth specifications.
507 @param passed: The request's I{auth} info passed.
508 @type passed: L{Options}.
509 @raise SecretRequired: On secret required and not passed.
510 @raise SecretNotMatched: On not matched.
511 @raise UserRequired: On user required and not passed.
512 @raise PasswordRequired: On password required and not passed.
513 @raise UserNotAuthorized: On user not authorized.
514 @raise NotAuthenticated: On PAM auth failed.
515 """
516 failed = []
517 for name, required in self.stack:
518 try:
519 fn = self.impl(name)
520 return fn(required, passed)
521 except NotAuthorized, e:
522 log.debug(e)
523 failed.append(e)
524 if failed:
525 raise failed[-1]
526
527 - def impl(self, name):
528 """
529 Find auth implementation by name.
530 @param name: auth type (name)
531 @type name: str
532 @return: The implementation method
533 @rtype: instancemethod
534 """
535 try:
536 return getattr(self, name)
537 except AttributeError:
538 raise AuthMethod(self.method, name)
539
540 - def secret(self, required, passed):
541 """
542 Perform shared secret auth.
543 @param required: Method specific auth specification.
544 @type required: L{Options}
545 @param passed: The credentials passed.
546 @type passed: L{Options}
547 @raise SecretRequired: On secret required and not passed.
548 @raise SecretNotMatched: On not matched.
549 """
550 secret = required.secret
551 if callable(secret):
552 secret = secret()
553 if not secret:
554 return
555 if not isinstance(secret, (list,tuple)):
556 secret = (secret,)
557 if not passed.secret:
558 raise SecretRequired(self.method)
559 if passed.secret in secret:
560 return
561 raise SecretNotMatched(self.method, passed.secret, secret)
562
563 - def pam(self, required, passed):
564 """
565 Perform PAM authentication.
566 @param required: Method specific auth specification.
567 @type required: L{Options}
568 @param passed: The credentials passed.
569 @type passed: L{Options}
570 @raise UserRequired: On user required and not passed.
571 @raise PasswordRequired: On password required and not passed.
572 @raise UserNotAuthorized: On user not authorized.
573 @raise NotAuthenticated: On PAM auth failed.
574 """
575 if passed.pam:
576 passed = Options(passed.pam)
577 else:
578 passed = Options()
579 if not passed.user:
580 raise UserRequired(self.method)
581 if not passed.password:
582 raise PasswordRequired(self.method)
583 if passed.user != required.user:
584 raise UserNotAuthorized(self.method, required.user, passed.user)
585 pam = PAM()
586 try:
587 pam.authenticate(passed.user, passed.password, required.service)
588 except Exception:
589 raise NotAuthenticated(self.method, passed.user)
590
596 """
597 The remote invocation dispatcher.
598 @ivar __catalog: The (catalog) of target classes.
599 @type __catalog: dict
600 """
601
602 @staticmethod
603 - def auth(envelope):
608
610 """
611 @param classes: The (catalog) of target classes.
612 @type classes: list
613 """
614 self.catalog = \
615 dict([(c.__name__, c) for c in classes])
616
618 return ( name in self.catalog )
619
621 """
622 Dispatch the requested RMI.
623 @param envelope: A request envelope.
624 @type envelope: L{Envelope}
625 @return: The result.
626 @rtype: any
627 """
628 try:
629 auth = self.auth(envelope)
630 request = Request(envelope.request)
631 log.info('request: %s', request)
632 method = RMI(request, auth, self.catalog)
633 log.debug('method: %s', method)
634 return method()
635 except Exception:
636 log.exception(str(envelope))
637 return Return.exception()
638