src/pyams_utils/request.py
changeset 100 119b9c2f3022
parent 72 9049384a2bd4
child 129 d0f4ef05c378
--- a/src/pyams_utils/request.py	Wed Jul 12 13:55:47 2017 +0200
+++ b/src/pyams_utils/request.py	Wed Jul 12 13:56:41 2017 +0200
@@ -16,15 +16,82 @@
 # import standard library
 
 # import interfaces
-from pyams_utils.interfaces import MissingRequestError
+from pyams_utils.interfaces import MissingRequestError, ICacheKeyValue
+from pyramid.interfaces import IAuthenticationPolicy, IAuthorizationPolicy
 from zope.annotation.interfaces import IAttributeAnnotatable, IAnnotations
 
 # import packages
 from pyramid.request import Request
+from pyramid.security import Allowed
 from pyramid.threadlocal import get_current_request, get_current_registry
 from zope.interface import alsoProvides
 
 
+_marker = object()
+
+
+def request_property(key=None, prefix=None):
+    """Define a method decorator used to store result into current request's annotations
+
+    If no request is currently running, a new one is created.
+    `key` is a required argument; if None, the key will be the method's object
+
+    :param str key: annotations value key; if *None*, the key will be the method's object; if *key* is a callable
+        object, it will be called to get the actual session key
+    :param prefix: str; prefix to use for session key; if *None*, the prefix will be the property name
+    """
+
+    def request_decorator(func):
+
+        def wrapper(obj, key, *args, **kwargs):
+            request = check_request()
+            if callable(key):
+                key = key(obj, *args, **kwargs)
+            if not key:
+                key = '{0}::{1}'.format(prefix or func.__name__, ICacheKeyValue(obj))
+                if args:
+                    key += '::' + '::'.join((ICacheKeyValue(arg) for arg in args))
+                if kwargs:
+                    key += '::' + '::'.join((ICacheKeyValue(arg) for arg in kwargs.items()))
+            data = get_request_data(request, key, _marker)
+            if data is _marker:
+                data = func
+                if callable(data):
+                    data = data(obj, *args, **kwargs)
+                set_request_data(request, key, data)
+            return data
+
+        return lambda x, *args, **kwargs: wrapper(x, key, *args, **kwargs)
+
+    return request_decorator
+
+
+class PyAMSRequest(Request):
+    """Custom request factory
+
+    Used to add 'context' argument to 'effective_principals' method call
+    to be able to get 'roles' principals
+    """
+
+    @request_property(key=None)
+    def has_permission(self, permission, context=None):
+        if context is None:
+            context = self.context
+        try:
+            reg = self.registry
+        except AttributeError:
+            reg = get_current_registry()
+        authn_policy = reg.queryUtility(IAuthenticationPolicy)
+        if authn_policy is None:
+            return Allowed('No authentication policy in use.')
+        authz_policy = reg.queryUtility(IAuthorizationPolicy)
+        if authz_policy is None:
+            raise ValueError('Authentication policy registered without '
+                             'authorization policy')  # should never happen
+        principals = authn_policy.effective_principals(self, context)
+        return authz_policy.permits(context, principals, permission)
+
+
 def get_request(raise_exception=True):
     """Get current request