import sys import logging from paste import httpexceptions from repoze.decsec.interfaces import Everyone from repoze.decsec.interfaces import Authenticated from repoze.decsec.interfaces import Allow from repoze.decsec.interfaces import Deny _LOGTMPL = ('%(action)s:: path: %(path)s, acl: %(acl)s, ' 'principals: %(principals)s, permission: %(permission)s') class DecsecMiddleware(object): def __init__(self, app, global_conf, request_principals, request_acl, request_permission, initialize = None, before_check = None, after_check = None, log_stream = None, log_level=logging.INFO, nomatch_allow = False, remote_user_key = 'REMOTE_USER', deny_exception = httpexceptions.HTTPUnauthorized, ): self.app = app self.remote_user_key = remote_user_key self.deny_exception = deny_exception self.logger = None if log_stream: handler = logging.StreamHandler(log_stream) fmt = '%(asctime)s %(message)s' formatter = logging.Formatter(fmt) handler.setFormatter(formatter) self.logger = logging.Logger('repoze.decsec') self.logger.addHandler(handler) self.logger.setLevel(log_level) self.nomatch_allow = nomatch_allow self.request_principals = request_principals self.request_acl = request_acl self.request_permission = request_permission self.before_check = before_check self.after_check = after_check if initialize: initialize(global_conf, self) def __call__(self, environ, start_response): result = self.check(environ) allowed = result['allowed'] if self.logger is not None: result['path'] = environ.get('PATH_INFO') msg = _LOGTMPL % result if allowed: self.logger.debug(msg) else: self.logger.info(msg) if allowed: return self.app(environ, start_response) else: raise self.deny_exception(result) def check(self, environ): if self.before_check: self.before_check(environ) principals = [Everyone] request_principals = flatten(self.request_principals(environ)) if request_principals: principals.append(Authenticated) principals.extend(request_principals) permission = self.request_permission(environ) acl = self.request_acl(environ) allowed = self.acl_allows(acl, permission, principals) result = {'acl': acl, 'principals':principals, 'permission':permission, 'allowed':allowed} if allowed: result['action'] = Allow else: result['action'] = Deny if self.after_check: self.after_check(environ) return result def acl_allows(self, acl, protected_by, principals): for ace in acl: for principal in principals: if ace['principal'] == principal: permissions = flatten(ace['permission']) if protected_by in permissions: action = ace['action'] if action == Allow: return True return False # no acl matched return self.nomatch_allow def _resolve(dotted_or_ep): """ Resolve a dotted name or setuptools entry point to a callable. """ from pkg_resources import EntryPoint return EntryPoint.parse('x=%s' % dotted_or_ep).load(False) def flatten(x): """flatten(sequence) -> list Returns a single, flat list which contains all elements retrieved from the sequence and all recursively contained sub-sequences (iterables). Examples: >>> [1, 2, [3,4], (5,6)] [1, 2, [3, 4], (5, 6)] >>> flatten([[[1,2,3], (42,None)], [4,5], [6], 7, MyVector(8,9,10)]) [1, 2, 3, 42, None, 4, 5, 6, 7, 8, 9, 10]""" if isinstance(x, basestring): return [x] result = [] for el in x: if hasattr(el, "__iter__") and not isinstance(el, basestring): result.extend(flatten(el)) else: result.append(el) return result def make_middleware(app, global_conf, request_principals_hook=None, request_acl_hook=None, request_permission_hook=None, before_check_hook=None, after_check_hook=None, initialize_hook=None, log_filename=None, log_level='info', nomatch_allow='false', remote_user_key='REMOTE_USER', deny_exception='paste.httpexceptions:HTTPUnauthorized', ): if request_principals_hook is None: raise ValueError('request_principals_hook must be specified') if request_acl_hook is None: raise ValueError('request_acl_hook must be specified') if request_permission_hook is None: raise ValueError('request_permission_hook must be specified') if nomatch_allow.lower().startswith('true'): nomatch_allow = True else: nomatch_allow = False deny_exception = _resolve(deny_exception) request_principals = _resolve(request_principals_hook) request_acl = _resolve(request_acl_hook) request_permission = _resolve(request_permission_hook) initialize = None before_check = None after_check = None if initialize_hook is not None: initialize = _resolve(initialize_hook) if before_check_hook is not None: before_check = _resolve(before_check_hook) if after_check_hook is not None: after_check = _resolve(after_check_hook) log_level = getattr(logging, log_level.upper()) if log_filename in (None, 'NONE'): log_stream = None elif log_filename == 'STDOUT': log_stream = sys.stdout elif log_filename == 'STDERR': log_stream = sys.stderr else: log_stream = open(log_filename, 'a') return DecsecMiddleware(app, global_conf, request_principals = request_principals, request_acl = request_acl, request_permission = request_permission, initialize = initialize, before_check = before_check, after_check = after_check, log_stream = log_stream, log_level = log_level, nomatch_allow = nomatch_allow, remote_user_key = remote_user_key, deny_exception = deny_exception, )