summaryrefslogtreecommitdiff
path: root/repoze/bfg/scifi.py
diff options
context:
space:
mode:
Diffstat (limited to 'repoze/bfg/scifi.py')
-rw-r--r--repoze/bfg/scifi.py257
1 files changed, 257 insertions, 0 deletions
diff --git a/repoze/bfg/scifi.py b/repoze/bfg/scifi.py
new file mode 100644
index 000000000..dcc8fa1c1
--- /dev/null
+++ b/repoze/bfg/scifi.py
@@ -0,0 +1,257 @@
+# Tweak BFG slightly to allow for separate authentication and
+# authorization policies, so we don't have security policies named
+# e.g. "RepozeWhoInheritingACLSecurityPolicy". We'll add
+# IAuthenticationPolicy objects and IAuthorizationPolicy objects;
+# these will be adapters. We'll also change ISecurityPolicy to be an
+# adapter rather than a utility. We'll tweak the function API to use
+# these adapters.
+
+# b/w incompats: the "authorization" policy needs access to the
+# context, which the APIs it maps to in a current BFG security policy
+# don't have; code which depends on these APIs will need to change.
+
+from zope.interface import implements
+from zope.interface import Interface
+
+class IAuthenticationPolicy(Interface):
+ """ A multi-adapter on context and request """
+ def authenticated_userid():
+ """ Return the authenticated userid or None if no
+ authenticated userid can be found """
+
+ def effective_principals():
+ """ Return a sequence representing the effective principals
+ (including the userid and any groups belonged to by the
+ current user, including 'system' groups such as Everyone and
+ Authenticated"""
+
+ def challenge():
+ """ Return an IResponse object representing a challenge, such
+ as a login form or a basic auth dialog """
+
+ def remember(self, principal, token):
+ """ Return a set of headers suitable for 'remembering' the
+ principal on subsequent requests """
+
+ def forget():
+ """ Return a set of headers suitable for 'forgetting' the
+ current user on subsequent requests"""
+
+class IAuthorizationPolicy(Interface):
+ """ An adapter on context """
+ def permits(self, principals, permission):
+ """ Return True if any of the principals is allowed the
+ permission in the current context, else return False """
+
+ def principals_allowed_by_permission(self, permission):
+ """ Return a set of principal identifiers allowed by the permission """
+
+class ISecurityPolicy(Interface):
+ """ A multi-adapter on context and request """
+ def permits(permission):
+ """ Returns True if the combination of the authorization
+ information in the context and the authentication data in
+ the request allow the action implied by the permission """
+
+ def authenticated_userid():
+ """ Return the userid of the currently authenticated user or
+ None if there is no currently authenticated user """
+
+ def effective_principals():
+ """ Return the list of 'effective' principals for the request.
+ This must include the userid of the currently authenticated
+ user if a user is currently authenticated."""
+
+ def principals_allowed_by_permission(permission):
+ """ Return a sequence of principal identifiers allowed by the
+ ``permission`` in the model implied by ``context``. This
+ method may not be supported by a given security policy
+ implementation, in which case, it should raise a
+ ``NotImplementedError`` exception."""
+
+ def forbidden():
+ """ This method should return an IResponse object (an object
+ with the attributes ``status``, ``headerlist``, and
+ ``app_iter``) as a result of a view invocation denial. The
+ ``forbidden`` method of a security policy will be called by
+ ``repoze.bfg`` when view invocation is denied (usually as a
+ result of the ``permit`` method of the same security policy
+ returning False to the Router).
+
+ The ``forbidden`` method of a security will not be called when
+ an ``IForbiddenResponseFactory`` utility is registered;
+ instead the ``IForbiddenResponseFactory`` utility will serve
+ the forbidden response.
+
+ Note that the ``repoze.bfg.message`` key in the environ passed
+ to the WSGI app will contain the 'raw' reason that view
+ invocation was denied by repoze.bfg. The ``context`` object
+ passed in will be the context found by ``repoze.bfg`` when the
+ denial was found and the ``request`` will be the request which
+ caused the denial."""
+
+# an implementation of an authentication policy that uses repoze.who
+
+class RepozeWhoAuthenticationPolicy(object):
+ """ A BFG authentication policy which obtains data from the
+ repoze.who API """
+ implements(IAuthenticationPolicy)
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+ from repoze.who.api import api_from_environ
+ self.api = api_from_environ(request.environ)
+
+ def authenticated_userid(self):
+ identity = self.api.authenticate()
+ if identity is None:
+ return None
+ return identity['repoze.who.userid']
+
+ def effective_principals(self):
+ effective_principals = [Everyone]
+ identity = self.api.authenticate()
+ if identity is None:
+ return effective_principals
+
+ effective_principals.append(Authenticated)
+ userid = identity['repoze.who.userid']
+ groups = identity.get('groups', [])
+ effective_principals.append(userid)
+ effective_principals.extend(groups)
+
+ return effective_principals
+
+ def challenge(self):
+ return self.api.challenge()
+
+ def remember(self, principal, token):
+ return self.api.remember({'repoze.who.userid':principal,
+ 'password':token})
+
+ def forget(self):
+ return self.api.forget()
+
+# an implementation of an authentication policy that uses a cookie
+
+class StandaloneAuthenticationPolicy:
+ """ A BFG authentication policy which obtains data from a cookie
+ and a local storage system """
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def _check_password(self, userid, password):
+ """ Return true if the password is good for the userid """
+ raise NotImplementedError # you get the idea
+
+ def _groups_from_userid(self, userid):
+ """ Return a sequence of groups given a user id """
+ raise NotImplementedError # you get the idea
+
+ def _userid_from_login(self, login):
+ """ Return a userid given a login name """
+ raise NotImplementedError # you get the idea
+
+ def _decrypt(self, cookieval):
+ """ Return decrypted login and password"""
+ raise NotImplementedError # you get the idea
+
+ def _encrypt(self, userid, password):
+ """ Return encrypted hash of userid and password for cookie val """
+ raise NotImplementedError # you get the idea
+
+ def authenticated_userid(self):
+ cookieval = request.cookies.get('oatmeal')
+ try:
+ login, password = self._decrypt(cookieval)
+ except:
+ return None
+ userid = self._userid_from_login(login)
+ if self._check_password(userid, password):
+ return userid
+
+ def effective_principals(self):
+ effective_principals = [Everyone]
+ userid = self.authenticated_userid()
+ if userid is None:
+ return effective_principals
+
+ effective_principals.append(Authenticated)
+ groups = self._groups_from_userid(userid)
+ effective_principals.append(userid)
+ effective_principals.extend(groups)
+
+ return effective_principals
+
+ def challenge(self):
+ userid = self.authenticated_userid()
+ if userid:
+ return render_template_to_response('templates/forbidden.pt')
+ else:
+ return render_template_to_response('templates/login_form.pt')
+
+ def remember(self, principal, token):
+ cookieval = self._encrypt(principal, token)
+ return [ ('Set-Cookie', 'oatmeal=%s' % cookieval) ]
+
+ def forget(self):
+ return [ ('Set-Cookie', 'oatmeal=') ]
+
+# an implementation of an authorization policy that uses ACLs
+
+class ACLAuthorizationPolicy(object):
+ implements(IAuthorizationPolicy)
+ def __init__(self, context):
+ self.context = context
+
+ def permits(self, principals, permission):
+ """ """
+ # do stuff to figure out of any of the principals is allowed
+ # the permission by any ACL in the current context's hierarchy
+
+ def principals_allowed_by_permission(self, permission):
+ """ """
+ # return the sequence of principals allowed by the permission
+ # according to the ACLs in the current context' hierarchy
+
+# present a rolled up "face" to both authn and autz policies in the
+# form of a "security policy"; users will interact with this API
+# rather than the authn or authz policies directly.
+
+class SecurityPolicy(object):
+ """ Use separate authn and authz to form a BFG security policy;
+ this will never be overridden, it is concrete. It is an adapter. """
+ implements(ISecurityPolicy)
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+ self.authn = getMultiAdapter((self.context, self.request),
+ IAuthenticationPolicy)
+ self.authz = getAdapter(context, IAuthorizationPolicy)
+
+ def permits(self, permission):
+ principals = set(self.authn.effective_principals())
+
+ if authz.permits(principals, permission):
+ return True
+
+ return False
+
+ def authenticated_userid(self):
+ return self.authn.authenticated_userid()
+
+ def effective_principals(self):
+ return self.authn.effective_principals()
+
+ def principals_allowed_by_permission(self):
+ return self.authz.principals_allowed_by_permission()
+
+ def forbidden(self):
+ return self.authn.challenge()
+
+ def remember(self, principal, token):
+ return self.authn.remember(principal, token)
+
+ def forget(self):
+ return self.authn.forget()