diff options
| author | cce <devnull@localhost> | 2005-12-31 08:04:38 +0000 |
|---|---|---|
| committer | cce <devnull@localhost> | 2005-12-31 08:04:38 +0000 |
| commit | 91c8ef70a2b92b44bece7bcf1dd52af0fee271fd (patch) | |
| tree | d259194edd727b283071346803cdf8bbbf079522 /paste/auth | |
| parent | 98f2eef2897f1670bf5540e6199724aaf56ea5b4 (diff) | |
| download | paste-91c8ef70a2b92b44bece7bcf1dd52af0fee271fd.tar.gz | |
- upgraded docs for paste.auth.digest and paste.auth.multi
Diffstat (limited to 'paste/auth')
| -rw-r--r-- | paste/auth/basic.py | 11 | ||||
| -rw-r--r-- | paste/auth/digest.py | 153 | ||||
| -rw-r--r-- | paste/auth/multi.py | 55 |
3 files changed, 116 insertions, 103 deletions
diff --git a/paste/auth/basic.py b/paste/auth/basic.py index b3e8f36..a255c59 100644 --- a/paste/auth/basic.py +++ b/paste/auth/basic.py @@ -5,14 +5,14 @@ """ Basic HTTP/1.0 Authentication -This module implements ``Basic`` authentication as described in HTTP/1.0 -specification [1]_ . Do not use this module unless you need to work -with very out-dated clients, instead use ``digest`` authentication. -Basically, you just put this module before your application, and it -takes care of requesting and handling authentication requests. +This module implements ``Basic`` authentication as described in +HTTP/1.0 specification [1]_ . Do not use this module unless you +are using SSL or need to work with very out-dated clients, instead +use ``digest`` authentication. >>> from paste.wsgilib import dump_environ >>> from paste.util.httpserver import serve +>>> from paste.auth.basic import AuthBasicHandler >>> realm = 'Test Realm' >>> def authfunc(username, password): ... return username == password @@ -97,7 +97,6 @@ middleware = AuthBasicHandler __all__ = ['AuthBasicHandler'] - if "__main__" == __name__: import doctest doctest.testmod(optionflags=doctest.ELLIPSIS) diff --git a/paste/auth/digest.py b/paste/auth/digest.py index 3a7c478..4fcef65 100644 --- a/paste/auth/digest.py +++ b/paste/auth/digest.py @@ -3,26 +3,42 @@ # the MIT License: http://www.opensource.org/licenses/mit-license.php # This code was written with funding by http://prometheusresearch.com """ -HTTP Digest Authentication (RFC 2617) - -NOTE: This has not been audited by a security expert, please use - with caution (or better yet, report security holes). - - At this time, this implementation does not provide for further - challenges, nor does it support Authentication-Info header. It - also uses md5, and an option to use sha would be a good thing. +Digest HTTP/1.1 Authentication + +This module implements ``Digest`` authentication as described by +RFC 2617 [1]_ . + +Basically, you just put this module before your application, and it +takes care of requesting and handling authentication requests. This +module has been tested with several common browsers "out-in-the-wild". + +>>> from paste.wsgilib import dump_environ +>>> from paste.util.httpserver import serve +>>> from paste.auth.digest import digest_password, AuthDigestHandler +>>> realm = 'Test Realm' +>>> def authfunc(realm, username): +... return digest_password(username, realm, username) +>>> serve(AuthDigestHandler(dump_environ, realm, authfunc)) +serving on... + +This code has not been audited by a security expert, please use with +caution (or better yet, report security holes). At this time, this +implementation does not provide for further challenges, nor does it +support Authentication-Info header. It also uses md5, and an option +to use sha would be a good thing. + +.. [1] http://www.faqs.org/rfcs/rfc2617.html """ from paste.httpexceptions import HTTPUnauthorized import md5, time, random, urllib2 def digest_password(username, realm, password): - """ Constructs the appropriate hashcode needed for HTTP Digest """ + """ construct the appropriate hashcode needed for HTTP digest """ return md5.md5("%s:%s:%s" % (username,realm,password)).hexdigest() -def response(challenge, realm, path, username, password): +def digest_response(challenge, realm, path, username, password): """ - Build an authorization response for a given challenge. This - implementation uses urllib2 to do the dirty work. + builds an authorization response for a given challenge """ auth = urllib2.AbstractDigestAuthHandler() auth.add_password(realm,path,username,password) @@ -38,19 +54,15 @@ def response(challenge, realm, path, username, password): get_selector = get_full_url return "Digest %s" % auth.get_authorization(FakeRequest(), chal) -class DigestAuthenticator: - """ Simple implementation of RFC 2617 - HTTP Digest Authentication """ - def __init__(self, realm, userfunc): - """ - realm is a globally unique URI, like tag:clarkevans.com,2005:bing - userfunc(realm, username) -> MD5('%s:%s:%s') % (user,realm,pass) - """ +class AuthDigestAuthenticator: + """ implementation of RFC 2617 - HTTP Digest Authentication """ + def __init__(self, realm, authfunc): self.nonce = {} # list to prevent replay attacks - self.userfunc = userfunc + self.authfunc = authfunc self.realm = realm def build_authentication(self, stale = ''): - """ raises an authentication exception """ + """ builds the authentication error """ nonce = md5.md5("%s:%s" % (time.time(),random.random())).hexdigest() opaque = md5.md5("%s:%s" % (time.time(),random.random())).hexdigest() self.nonce[nonce] = None @@ -62,7 +74,7 @@ class DigestAuthenticator: head = [("WWW-Authenticate", 'Digest %s' % head)] return HTTPUnauthorized(headers=head) - def compute(self, ha1, username, response, method, + def compute(self, ha1, username, response, method, path, nonce, nc, cnonce, qop): """ computes the authentication, raises error if unsuccessful """ if not ha1: @@ -88,7 +100,7 @@ class DigestAuthenticator: """ This function takes the value of the 'Authorization' header, the method used (e.g. GET), and the path of the request relative to the server. The function either returns an - authenticated user, or it raises an exception. + authenticated user or it returns the authentication error. """ if not authorization: return self.build_authentication() @@ -115,79 +127,82 @@ class DigestAuthenticator: assert nonce and nc except: return self.build_authentication() - ha1 = self.userfunc(realm,username) + ha1 = self.authfunc(realm,username) return self.compute(ha1, username, response, method, authpath, nonce, nc, cnonce, qop) __call__ = authenticate -def AuthDigestHandler(application, realm, userfunc): +class AuthDigestHandler: """ - This middleware implements HTTP Digest authentication (RFC 2617) on - the incoming request. There are several possible outcomes: + middleware for HTTP Digest authentication (RFC 2617) + + This component follows the procedure below: - 0. If the REMOTE_USER environment variable is already populated; - then this middleware is a no-op, and the request is passed along - to the application. + 0. If the REMOTE_USER environment variable is already populated; + then this middleware is a no-op, and the request is passed + along to the application. - 1. If the HTTP_AUTHORIZATION header was not provided, then a - HTTPUnauthorized exception is raised containing the challenge. + 1. If the HTTP_AUTHORIZATION header was not provided or specifies + an algorithem other than ``digest``, then a HTTPUnauthorized + response is generated with the challenge. - 2. If the HTTP_AUTHORIZATION header specifies anything other - than digest; the REMOTE_USER is left unset and application - processing continues. + 2. If the response is malformed or or if the user's credientials + do not pass muster, another HTTPUnauthorized is raised. - 3. If the response is malformed or or if the user's credientials - do not pass muster, another HTTPUnauthorized is raised. + 3. If all goes well, and the user's credintials pass; then + REMOTE_USER environment variable is filled in and the + AUTH_TYPE is listed as 'digest'. - 4. IF all goes well, and the user's credintials pass; then - REMOTE_USER environment variable is filled in and the - AUTH_TYPE is listed as 'digest'. + Parameters: - Besides the application to delegate requests, this middleware - requires two additional arguments: + ``application`` - realm: - This is a globally unique identifier used to indicate the - authority that is performing the authentication. The taguri - such as tag:yourdomain.com,2006 is sufficient. + The application object is called only upon successful + authentication, and can assume ``environ['REMOTE_USER']`` + is set. If the ``REMOTE_USER`` is already set, this + middleware is simply pass-through. - userfunc: - This is a callback function which performs the actual - authentication; the signature of this callback is: + ``realm`` - userfunc(realm, username) -> hashcode + This is a identifier for the authority that is requesting + authorization. It is shown to the user and should be unique + within the domain it is being used. - This module provides a 'digest_password' helper function which - can help construct the hashcode; it is recommended that the - hashcode is stored in a database, not the user's actual password. + ``authfunc`` + + This is a callback function which performs the actual + authentication; the signature of this callback is: + + authfunc(realm, username) -> hashcode + + This module provides a 'digest_password' helper function + which can help construct the hashcode; it is recommended + that the hashcode is stored in a database, not the user's + actual password (since you only need the hashcode). """ - authenticator = DigestAuthenticator(realm, userfunc) - def digest_application(environ, start_response): + def __init__(self, application, realm, authfunc): + self.authenticate = AuthDigestAuthenticator(realm, authfunc) + self.application = application + + def __call__(self, environ, start_response): username = environ.get('REMOTE_USER','') if not username: method = environ['REQUEST_METHOD'] fullpath = environ['SCRIPT_NAME'] + environ["PATH_INFO"] authorization = environ.get('HTTP_AUTHORIZATION','') - result = authenticator(authorization, fullpath, method) + result = self.authenticate(authorization, fullpath, method) if isinstance(result, str): environ['AUTH_TYPE'] = 'digest' environ['REMOTE_USER'] = result else: return result.wsgi_application(environ, start_response) - return application(environ, start_response) - return digest_application + return self.application(environ, start_response) middleware = AuthDigestHandler -__all__ = ['digest_password', 'AuthDigestHandler' ] - -if '__main__' == __name__: - realm = 'tag:clarkevans.com,2005:digest' - def userfunc(realm, username): - return digest_password(username, realm, username) - from paste.wsgilib import dump_environ - from paste.util.httpserver import serve - from paste.httpexceptions import * - serve(HTTPExceptionHandler( - AuthDigestHandler(dump_environ, realm, userfunc))) +__all__ = ['digest_password', 'digest_response', 'AuthDigestHandler' ] + +if "__main__" == __name__: + import doctest + doctest.testmod(optionflags=doctest.ELLIPSIS) diff --git a/paste/auth/multi.py b/paste/auth/multi.py index f3370b9..646962c 100644 --- a/paste/auth/multi.py +++ b/paste/auth/multi.py @@ -8,11 +8,29 @@ Authentication via Multiple Methods In some environments, the choice of authentication method to be used depends upon the environment and is not "fixed". This middleware allows N authentication methods to be registered along with a goodness function -which determines which method should be used. +which determines which method should be used. The following example +demonstrates how to use both form and digest authentication in a server +stack; by default it uses form-based authentication unless +``*authmeth=digest`` is specified as a query argument. + +>>> from paste.auth import form, cookie, digest +>>> from paste.wsgilib import dump_environ +>>> from paste.util.httpserver import serve +>>> +>>> multi = MultiHandler(dump_environ) +>>> def authfunc(realm, user): +... return digest.digest_password(user, realm, user) +>>> multi.add_method('digest', digest.middleware, "Test Realm", authfunc) +>>> multi.set_query_argument('digest') +>>> +>>> def authfunc(username, password): +... return username == password +>>> factory = lambda app: form.middleware(app, authfunc) +>>> multi.add_method('form', factory) +>>> multi.set_default('form') +>>> serve(cookie.middleware(multi)) +serving on... -Strictly speaking this is not limited to authentication, but it is a -common requirement in that domain; this is why it isn't named -AuthMultiHandler (for now). """ class MultiHandler: @@ -44,7 +62,7 @@ class MultiHandler: def set_query_argument(self, name, key = '*authmeth', value = None): """ choose authentication method based on a query argument """ lookfor = "%s=%s" % (key, value or name) - self.add_predicate(name, + self.add_predicate(name, lambda environ: lookfor in environ.get('QUERY_STRING','')) def __call__(self, environ, start_response): for (checker,binding) in self.predicate: @@ -56,26 +74,7 @@ middleware = MultiHandler __all__ = ['MultiHandler'] -if '__main__' == __name__: - import basic, digest, cas, cookie, form - from paste.httpexceptions import * - from paste.wsgilib import dump_environ - from paste.util.httpserver import serve - multi = MultiHandler(dump_environ) - multi.add_method('basic',basic.middleware, - 'tag:clarkevans.com,2005:basic', - lambda n,p: n == p ) - multi.set_query_argument('basic') - multi.add_method('digest',digest.middleware, - 'tag:clarkevans.com,2005:digest', - lambda r,u: digest.digest_password(u,r,u)) - multi.set_query_argument('digest') - multi.add_method('form',lambda ap: cookie.middleware( - form.middleware(ap, - lambda n,p: n == p))) - multi.set_query_argument('form') - #authority = "https://secure.its.yale.edu/cas/servlet/" - #multi.add_method('cas',lambda ap: cookie.middleware( - # cas.middleware(ap,authority))) - #multi.set_default('cas') - serve(HTTPExceptionHandler(multi)) +if "__main__" == __name__: + import doctest + doctest.testmod(optionflags=doctest.ELLIPSIS) + |
