summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--wsgitools/authentication.py38
-rw-r--r--wsgitools/digest.py11
-rw-r--r--wsgitools/middlewares.py9
3 files changed, 40 insertions, 18 deletions
diff --git a/wsgitools/authentication.py b/wsgitools/authentication.py
index a36b794..0c69f95 100644
--- a/wsgitools/authentication.py
+++ b/wsgitools/authentication.py
@@ -12,10 +12,10 @@ class ProtocolViolation(AuthenticationRequired):
class AuthenticationMiddleware:
"""Base class for HTTP authorization schemes.
- @cvar authorization_required: the implemented Authorization method. It will
+ @cvar authorization_method: the implemented Authorization method. It will
be verified against Authorization headers. Subclasses must define this
attribute.
- @type authorization_required: str
+ @type authorization_method: str
"""
authorization_method = None
def __init__(self, app):
@@ -25,14 +25,31 @@ class AuthenticationMiddleware:
assert self.authorization_method is not None
self.app = app
- def authenticate(self, auth, environ, start_response):
- """
+ def authenticate(self, auth, environ):
+ """Try to authenticate a request. The Authorization header is examined
+ and checked agains the L{authorization_method} before being passed to
+ this method. This method must either raise an AuthenticationRequired
+ instance or return a dictionary explaining what was successfully
+ authenticated.
+
@type auth: str
@param auth: is the part of the Authorization header after the method
+ @type environ: {str: object}
+ @param environ: is the environment passed with a WSGI request
+ @rtype: {str: object}
+ @returns: a dictionary that provides a key "user" listing the
+ authenticated username as a string. It may also provide the key
+ "outheaders" with a [(str, str)] value to extend the response
+ headers.
+ @raises AuthenticationRequired: if the authentication was unsuccessful
"""
raise NotImplementedError
def __call__(self, environ, start_response):
+ """wsgi interface
+
+ @type environ: {str: object}
+ """
assert isinstance(environ, dict)
try:
try:
@@ -46,9 +63,20 @@ class AuthenticationMiddleware:
if method.lower() != self.authorization_method:
raise AuthenticationRequired(
"authorization method not implemented: %r" % method)
- return self.authenticate(rest, environ, start_response)
+ result = self.authenticate(rest, environ)
except AuthenticationRequired, exc:
return self.authorization_required(environ, start_response, exc)
+ assert isinstance(result, dict)
+ assert "user" in result
+ environ["REMOTE_USER"] = result["user"]
+ if "outheaders" in result:
+ def modified_start_response(status, headers, exc_info=None):
+ assert isinstance(headers, list)
+ headers.extend(result["outheaders"])
+ return start_response(status, headers, exc_info)
+ else:
+ modified_start_response = start_response
+ return self.app(environ, modified_start_response)
def www_authenticate(self, exception):
"""Generates a WWW-Authenticate header. Subclasses must implement this
diff --git a/wsgitools/digest.py b/wsgitools/digest.py
index 628eac5..fc8e310 100644
--- a/wsgitools/digest.py
+++ b/wsgitools/digest.py
@@ -657,9 +657,7 @@ class AuthDigestMiddleware(AuthenticationMiddleware):
assert hasattr(store, "checknonce")
self.noncestore = store
- def authenticate(self, auth, environ, start_response):
- """wsgi interface"""
-
+ def authenticate(self, auth, environ):
try:
credentials = parse_digest_response(auth)
except ValueError:
@@ -702,17 +700,14 @@ class AuthDigestMiddleware(AuthenticationMiddleware):
if response is None or response != credresponse:
raise AuthenticationRequired("wrong response")
- environ["REMOTE_USER"] = credentials["username"]
digest = dict(nextnonce=self.noncestore.newnonce())
if "qop" in credentials:
digest["qop"] = "auth"
digest["cnonce"] = credentials["cnonce"] # no KeyError
digest["rspauth"] = self.auth_response(credentials, "")
challenge = ", ".join(map('%s="%s"'.__mod__, digest.items()))
- def modified_start_response(status, headers, exc_info=None):
- headers.append(("Authentication-Info", challenge))
- return start_response(status, headers, exc_info)
- return self.app(environ, modified_start_response)
+ return dict(user=credentials["username"],
+ outheaders=[("Authentication-Info", challenge)])
def auth_response(self, credentials, reqmethod):
"""internal method generating authentication tokens
diff --git a/wsgitools/middlewares.py b/wsgitools/middlewares.py
index 13ba41c..b385e91 100644
--- a/wsgitools/middlewares.py
+++ b/wsgitools/middlewares.py
@@ -337,9 +337,9 @@ class BasicAuthMiddleware(AuthenticationMiddleware):
self.realm = realm
self.app401 = app401
- def authenticate(self, auth, environ, start_response):
- """wsgi interface
- @type environ: {str: str}
+ def authenticate(self, auth, environ):
+ """
+ @type environ: {str: object}
"""
assert isinstance(environ, dict)
try:
@@ -355,8 +355,7 @@ class BasicAuthMiddleware(AuthenticationMiddleware):
except TypeError: # catch old interface
result = self.check_function(username, password)
if result:
- environ["REMOTE_USER"] = username
- return self.app(environ, start_response)
+ return dict(user=username)
raise AuthenticationRequired("credentials not valid")
def www_authenticate(self, exception):