From cc4210dc555678b603e80da198f512bcc15b663b Mon Sep 17 00:00:00 2001 From: Helmut Grohne Date: Wed, 30 Nov 2011 18:48:33 +0100 Subject: shrink AuthenticationMiddleware.authenticate interface The method no longer receives a start_response and is no longer responsible for calling self.app. Instead it returns a dictionary with the result of the authentication. --- wsgitools/authentication.py | 38 +++++++++++++++++++++++++++++++++----- wsgitools/digest.py | 11 +++-------- wsgitools/middlewares.py | 9 ++++----- 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): -- cgit v1.2.3