Trees | Indices | Help |
---|
|
1 # 2 # Copyright (c) 2011 Red Hat, Inc. 3 # 4 # This software is licensed to you under the GNU Lesser General Public 5 # License as published by the Free Software Foundation; either version 6 # 2 of the License (LGPLv2) or (at your option) any later version. 7 # There is NO WARRANTY for this software, express or implied, 8 # including the implied warranties of MERCHANTABILITY, 9 # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should 10 # have received a copy of LGPLv2 along with this software; if not, see 11 # http://www.gnu.org/licenses/old-licenses/lgpl-2.0.txt. 12 # 13 # Jeff Ortel <jortel@redhat.com> 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__)32 33 34 # --- Exceptions ------------------------------------------------------------- 35 36 37 -class DispatchError(Exception):39 48 58 68 7578 """ 79 Authentication method not supported. 80 """ 8186 96 106 116 12683 message = \ 84 '%s(), auth (%s) not supported' % (method.name, name) 85 NotAuthorized.__init__(self, message)129 """ 130 Not authenticated, user/password failed 131 PAM authentication. 132 """ 133137135 message = '%s(), user "%s" not authenticted' % (method.name, user) 136 NotAuthorized.__init__(self, message)140 """ 141 The specified user is not authorized to invoke the RMI. 142 """ 143150145 message = '%s(), user must be: %s, passed: %s' \ 146 % (method.name, 147 expected, 148 passed) 149 NotAuthorized.__init__(self, message)153 """ 154 Specified secret, not matched. 155 """ 156163158 message = '%s(), secret: %s not in: %s' \ 159 % (method.name, 160 passed, 161 expected) 162 NotAuthorized.__init__(self, message)166 """ 167 The re-raised (propagated) exception base class. 168 """ 169 170 @classmethod197172 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 inst188 189 @classmethod191 try: 192 import new 193 return new.instance(C) 194 except: 195 pass 196 return Exception.__new__(C)198 199 # --- RMI Classes ------------------------------------------------------------ 200 201 202 -class Reply(Envelope):203 """ 204 Envelope for examining replies. 205 """ 206239208 """ 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 215217 """ 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 )223225 """ 226 Test whether the reply indicates status (started). 227 @return: True when indicates started. 228 @rtype: bool 229 """ 230 return ( self.status == 'started' )231242 """ 243 Return envelope. 244 """ 245 246 @classmethod313 320248 """ 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() # validate 257 return inst258 259 @classmethod261 """ 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()270272 """ 273 Test whether the return indicates success. 274 @return: True when indicates success. 275 @rtype: bool 276 """ 277 return ( 'retval' in self )278280 """ 281 Test whether the return indicates failure. 282 @return: True when indicates failure. 283 @rtype: bool 284 """ 285 return ( 'exval' in self )286 287 @classmethod289 """ 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() # validate 312 return inst323 """ 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 """ 330480332 """ 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.kws347 348 @staticmethod350 """ 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 inst369 370 @staticmethod372 """ 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 @staticmethod390 """ 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 fn403 404 @staticmethod406 """ 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 pass418 419 @staticmethod421 """ 422 Get (optional) constructor arguments. 423 @return: cntr: ([],{}) 424 """ 425 cntr = request.cntr 426 if not cntr: 427 cntr = ([],{}) 428 return cntr429 448450 """ 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)460462 """ 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 477481 482 # --- Security classes ------------------------------------------------------- 483 484 485 -class Security: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 """ 493590495 """ 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.security503505 """ 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]526528 """ 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)539541 """ 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)562564 """ 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)591 592 # --- Dispatcher ------------------------------------------------------------- 593 594 595 -class Dispatcher:596 """ 597 The remote invocation dispatcher. 598 @ivar __catalog: The (catalog) of target classes. 599 @type __catalog: dict 600 """ 601 602 @staticmethod638604 return Options( 605 uuid=envelope.routing[-1], 606 secret=envelope.secret, 607 pam=envelope.pam,)608610 """ 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])616618 return ( name in self.catalog )619621 """ 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()
Trees | Indices | Help |
---|
Generated by Epydoc 3.0.1 on Thu May 2 09:10:29 2013 | http://epydoc.sourceforge.net |