summaryrefslogtreecommitdiff
path: root/repoze/bfg/authentication.py
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-05-30 05:25:50 +0000
committerChris McDonough <chrism@agendaless.com>2009-05-30 05:25:50 +0000
commitfcc272631432acdbc98131ee0c2113bdfef99e40 (patch)
tree018f9c400555a2de19981770477b7ed7bba4dbf6 /repoze/bfg/authentication.py
parentdfc29c83390ee06afa904eee620b9d2dead2657b (diff)
downloadpyramid-fcc272631432acdbc98131ee0c2113bdfef99e40.tar.gz
pyramid-fcc272631432acdbc98131ee0c2113bdfef99e40.tar.bz2
pyramid-fcc272631432acdbc98131ee0c2113bdfef99e40.zip
- Add an AuthTktAuthenticationPolicy. This policy retrieves
credentials from an auth_tkt cookie managed by the application itself (instead of relying on an upstream data source for authentication data). See the Security API chapter of the documentation for more info. - Allow RemoteUserAuthenticationPolicy and RepozeWho1AuthenticationPolicy to accept various constructor arguments. See the Security API chapter of the documentation for more info.
Diffstat (limited to 'repoze/bfg/authentication.py')
-rw-r--r--repoze/bfg/authentication.py320
1 files changed, 294 insertions, 26 deletions
diff --git a/repoze/bfg/authentication.py b/repoze/bfg/authentication.py
index 487a5e6a8..e7d4ef06e 100644
--- a/repoze/bfg/authentication.py
+++ b/repoze/bfg/authentication.py
@@ -1,13 +1,78 @@
+from codecs import utf_8_decode
+from codecs import utf_8_encode
+import crypt
+import os
+import stat
+import StringIO
+import time
+import traceback
+
+from paste.request import get_cookies
+from paste.auth import auth_tkt
+
from zope.interface import implements
+from zope.component import queryUtility
+
from repoze.bfg.interfaces import IAuthenticationPolicy
from repoze.bfg.security import Everyone
from repoze.bfg.security import Authenticated
-class RepozeWho1AuthenticationPolicy(object):
+class CallbackAuthenticationPolicy(object):
+ """ Abstract class """
+ def authenticated_userid(self, context, request):
+ userid = self._get_userid(request)
+ if userid is None:
+ return None
+ if self.callback is None:
+ return userid
+ if self.callback(userid) is not None: # is not None!
+ return userid
+
+ def effective_principals(self, context, request):
+ effective_principals = [Everyone]
+ userid = self._get_userid(request)
+ if userid is None:
+ return effective_principals
+ if self.callback is None:
+ groups = []
+ else:
+ groups = self.callback(userid)
+ if groups is None: # is None!
+ return effective_principals
+ effective_principals.append(Authenticated)
+ effective_principals.append(userid)
+ effective_principals.extend(groups)
+
+ return effective_principals
+
+
+class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy):
""" A BFG authentication policy which obtains data from the
- repoze.who 1.X WSGI API """
+ repoze.who 1.X WSGI 'API' (the ``repoze.who.identity`` key in the
+ WSGI environment).
+
+ Constructor Arguments
+ ---------------------
+
+ ``identifier_name``
+
+ Default: ``auth_tkt``. The who plugin name that performs
+ remember/forget. Optional.
+
+ ``callback``
+
+ Default: ``None``. A callback passed the repoze.who identity,
+ expected to return None if the user represented by the
+ identity doesn't exist or a sequence of group identifiers
+ (possibly empty) if the user does exist. If ``callback`` is
+ None, the userid will be assumed to exist with no groups.
+
+ """
implements(IAuthenticationPolicy)
- identifier_name = 'auth_tkt'
+
+ def __init__(self, identifier_name='auth_tkt', callback=None):
+ self.identifier_name = identifier_name
+ self.callback = callback
def _get_identity(self, request):
return request.environ.get('repoze.who.identity')
@@ -23,17 +88,24 @@ class RepozeWho1AuthenticationPolicy(object):
identity = self._get_identity(request)
if identity is None:
return None
- return identity['repoze.who.userid']
+ if self.callback is None:
+ return identity['repoze.who.userid']
+ if self.callback(identity) is not None: # is not None!
+ return identity['repoze.who.userid']
def effective_principals(self, context, request):
effective_principals = [Everyone]
identity = self._get_identity(request)
if identity is None:
return effective_principals
-
- effective_principals.append(Authenticated)
+ if self.callback is None:
+ groups = []
+ else:
+ groups = self.callback(identity)
+ if groups is None: # is None!
+ return effective_principals
userid = identity['repoze.who.userid']
- groups = identity.get('groups', [])
+ effective_principals.append(Authenticated)
effective_principals.append(userid)
effective_principals.extend(groups)
@@ -53,34 +125,230 @@ class RepozeWho1AuthenticationPolicy(object):
return []
identity = self._get_identity(request)
return identifier.forget(request.environ, identity)
-
-class RemoteUserAuthenticationPolicy(object):
+
+class RemoteUserAuthenticationPolicy(CallbackAuthenticationPolicy):
""" A BFG authentication policy which obtains data from the
- REMOTE_USER WSGI envvar """
- implements(IAuthenticationPolicy)
+ REMOTE_USER WSGI envvar.
- def _get_identity(self, request):
- return request.environ.get('REMOTE_USER')
+ Constructor Arguments
+ ---------------------
- def authenticated_userid(self, context, request):
- identity = self._get_identity(request)
- if identity is None:
- return None
- return identity
+ ``environ_key``
- def effective_principals(self, context, request):
- effective_principals = [Everyone]
- identity = self._get_identity(request)
- if identity is None:
- return effective_principals
+ Default: ``REMOTE_USER``. The key in the WSGI environ which
+ provides the userid.
- effective_principals.append(Authenticated)
- effective_principals.append(identity)
+ ``callback``
- return effective_principals
+ Default: ``None``. A callback passed the userid, expected to return
+ None if the userid doesn't exist or a sequence of group identifiers
+ (possibly empty) if the user does exist. If ``callback`` is None,
+ the userid will be assumed to exist with no groups.
+ """
+ implements(IAuthenticationPolicy)
+
+ def __init__(self, environ_key='REMOTE_USER', callback=None):
+ self.environ_key = environ_key
+ self.callback = callback
+
+ def _get_userid(self, request):
+ return request.environ.get(self.environ_key)
def remember(self, context, request, principal, **kw):
return []
def forget(self, context, request):
return []
+
+class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
+ """ A BFG authentication policy which obtains data from an
+ auth_tkt cookie.
+
+ Constructor Arguments
+ ---------------------
+
+ ``secret``
+
+ The secret (a string) used for auth_tkt cookie encryption.
+ Required.
+
+ ``callback``
+
+ Default: ``None``. A callback passed the userid, expected to return
+ None if the userid doesn't exist or a sequence of group identifiers
+ (possibly empty) if the user does exist. If ``callback`` is None,
+ the userid will be assumed to exist with no groups.
+
+ ``cookie_name``
+
+ Default: ``repoze.bfg.auth_tkt``. The cookie name used
+ (string). Optional.
+
+ ``secure``
+
+ Default: ``False``. Only send the cookie back over a secure
+ conn. Optional.
+
+ ``include_ip``
+
+ Default: ``False``. Make the requesting IP address part of
+ the authentication data in the cookie. Optional.
+
+ """
+ implements(IAuthenticationPolicy)
+ def __init__(self,
+ secret,
+ callback=None,
+ cookie_name='repoze.bfg.auth_tkt',
+ secure=False,
+ include_ip=False):
+ self.cookie = AuthTktCookieHelper(
+ secret,
+ cookie_name=cookie_name,
+ secure=secure,
+ include_ip=include_ip,
+ )
+ self.callback = callback
+
+ def _get_userid(self, request):
+ result = self.cookie.identify(request)
+ if result:
+ return result['userid']
+
+ def remember(self, context, request, principal, **kw):
+ return self.cookie.remember(request, principal)
+
+ def forget(self, context, request):
+ return self.cookie.forget(request)
+
+class AuthTktCookieHelper(object):
+ userid_type_decoders = {
+ 'int':int,
+ 'unicode':lambda x: utf_8_decode(x)[0],
+ }
+
+ userid_type_encoders = {
+ int: ('int', str),
+ long: ('int', str),
+ unicode: ('unicode', lambda x: utf_8_encode(x)[0]),
+ }
+
+ def __init__(self, secret, cookie_name='auth_tkt', secure=False,
+ include_ip=False):
+ self.secret = secret
+ self.cookie_name = cookie_name
+ self.include_ip = include_ip
+ self.secure = secure
+
+ # IIdentifier
+ def identify(self, request):
+ environ = request.environ
+ cookies = get_cookies(environ)
+ cookie = cookies.get(self.cookie_name)
+
+ if cookie is None or not cookie.value:
+ return None
+
+ if self.include_ip:
+ remote_addr = environ['REMOTE_ADDR']
+ else:
+ remote_addr = '0.0.0.0'
+
+ try:
+ timestamp, userid, tokens, user_data = auth_tkt.parse_ticket(
+ self.secret, cookie.value, remote_addr)
+ except auth_tkt.BadTicket:
+ return None
+
+ userid_typename = 'userid_type:'
+ user_data_info = user_data.split('|')
+ for datum in filter(None, user_data_info):
+ if datum.startswith(userid_typename):
+ userid_type = datum[len(userid_typename):]
+ decoder = self.userid_type_decoders.get(userid_type)
+ if decoder:
+ userid = decoder(userid)
+
+ environ['REMOTE_USER_TOKENS'] = tokens
+ environ['REMOTE_USER_DATA'] = user_data
+ environ['AUTH_TYPE'] = 'cookie'
+
+ identity = {}
+ identity['timestamp'] = timestamp
+ identity['userid'] = userid
+ identity['tokens'] = tokens
+ identity['userdata'] = user_data
+ return identity
+
+ def _get_cookies(self, environ, value):
+ cur_domain = environ.get('HTTP_HOST', environ.get('SERVER_NAME'))
+ wild_domain = '.' + cur_domain
+ cookies = [
+ ('Set-Cookie', '%s="%s"; Path=/' % (
+ self.cookie_name, value)),
+ ('Set-Cookie', '%s="%s"; Path=/; Domain=%s' % (
+ self.cookie_name, value, cur_domain)),
+ ('Set-Cookie', '%s="%s"; Path=/; Domain=%s' % (
+ self.cookie_name, value, wild_domain))
+ ]
+ return cookies
+
+ # IIdentifier
+ def forget(self, request):
+ # return a set of expires Set-Cookie headers
+ environ = request.environ
+ return self._get_cookies(environ, '""')
+
+ # IIdentifier
+ def remember(self, request, userid, tokens='', userdata=''):
+ environ = request.environ
+ if self.include_ip:
+ remote_addr = environ['REMOTE_ADDR']
+ else:
+ remote_addr = '0.0.0.0'
+
+ cookies = get_cookies(environ)
+ old_cookie = cookies.get(self.cookie_name)
+ existing = cookies.get(self.cookie_name)
+ old_cookie_value = getattr(existing, 'value', None)
+
+ timestamp, old_userid, old_tokens, old_userdata = None, '', '', ''
+
+ if old_cookie_value:
+ try:
+ (timestamp,old_userid,old_tokens,
+ old_userdata) = auth_tkt.parse_ticket(
+ self.secret, old_cookie_value, remote_addr)
+ except auth_tkt.BadTicket:
+ pass
+
+ encoding_data = self.userid_type_encoders.get(type(userid))
+ if encoding_data:
+ encoding, encoder = encoding_data
+ userid = encoder(userid)
+ userdata = 'userid_type:%s' % encoding
+
+ if not isinstance(tokens, basestring):
+ tokens = ','.join(tokens)
+ if not isinstance(old_tokens, basestring):
+ old_tokens = ','.join(old_tokens)
+ old_data = (old_userid, old_tokens, old_userdata)
+ new_data = (userid, tokens, userdata)
+
+ if old_data != new_data:
+ ticket = auth_tkt.AuthTicket(
+ self.secret,
+ userid,
+ remote_addr,
+ tokens=tokens,
+ user_data=userdata,
+ cookie_name=self.cookie_name,
+ secure=self.secure)
+ new_cookie_value = ticket.cookie_value()
+
+ cur_domain = environ.get('HTTP_HOST', environ.get('SERVER_NAME'))
+ wild_domain = '.' + cur_domain
+ if old_cookie_value != new_cookie_value:
+ # return a set of Set-Cookie headers
+ return self._get_cookies(environ, new_cookie_value)
+