summaryrefslogtreecommitdiff
path: root/paste/auth
diff options
context:
space:
mode:
authorcce <devnull@localhost>2005-12-31 08:04:38 +0000
committercce <devnull@localhost>2005-12-31 08:04:38 +0000
commit91c8ef70a2b92b44bece7bcf1dd52af0fee271fd (patch)
treed259194edd727b283071346803cdf8bbbf079522 /paste/auth
parent98f2eef2897f1670bf5540e6199724aaf56ea5b4 (diff)
downloadpaste-91c8ef70a2b92b44bece7bcf1dd52af0fee271fd.tar.gz
- upgraded docs for paste.auth.digest and paste.auth.multi
Diffstat (limited to 'paste/auth')
-rw-r--r--paste/auth/basic.py11
-rw-r--r--paste/auth/digest.py153
-rw-r--r--paste/auth/multi.py55
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)
+