diff --git a/sanic/exceptions.py b/sanic/exceptions.py index 8998aab8..21ab2a94 100644 --- a/sanic/exceptions.py +++ b/sanic/exceptions.py @@ -209,25 +209,43 @@ class Unauthorized(SanicException): Unauthorized exception (401 HTTP status code). :param scheme: Name of the authentication scheme to be used. - :param realm: Description of the protected area. (optional) :param challenge: A dict containing values to add to the WWW-Authenticate - header that is generated. This is especially useful when - dealing with the Digest scheme. (optional) + header that is generated. This is especially useful when dealing with + the Digest scheme. (optional) + + Examples:: + + # With a Basic auth-scheme, realm MUST be present: + challenge = {"realm": "Restricted Area"} + raise Unauthorized("Auth required.", "Basic", challenge) + + # With a Digest auth-scheme, things are a bit more complicated: + challenge = { + "realm": "Restricted Area", + "qop": "auth, auth-int", + "algorithm": "MD5", + "nonce": "abcdef", + "opaque": "zyxwvu" + } + raise Unauthorized("Auth required.", "Digest", challenge) + + # With a Bearer auth-scheme, realm is optional: + challenge = {"realm": "Restricted Area"} + raise Unauthorized("Auth required.", "Bearer", challenge) """ pass - def __init__(self, message, scheme, realm="", challenge=None): + def __init__(self, message, scheme, challenge=None): super().__init__(message) - adds = "" + chal = "" if challenge is not None: values = ["{!s}={!r}".format(k, v) for k, v in challenge.items()] - adds = ', '.join(values) - adds = ', {}'.format(adds) + chal = ', '.join(values) self.headers = { - "WWW-Authenticate": "{} realm='{}'{}".format(scheme, realm, adds) + "WWW-Authenticate": "{} {}".format(scheme, chal).rstrip() } diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 45bdab88..620e7891 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -33,17 +33,22 @@ def exception_app(): @app.route('/401/basic') def handler_401_basic(request): - raise Unauthorized("Unauthorized", "Basic", "Sanic") + raise Unauthorized("Unauthorized", "Basic", {"realm": "Sanic"}) @app.route('/401/digest') def handler_401_digest(request): challenge = { + "realm": "Sanic", "qop": "auth, auth-int", "algorithm": "MD5", "nonce": "abcdef", "opaque": "zyxwvu", } - raise Unauthorized("Unauthorized", "Digest", "Sanic", challenge) + raise Unauthorized("Unauthorized", "Digest", challenge) + + @app.route('/401/bearer') + def handler_401_bearer(request): + raise Unauthorized("Unauthorized", "Bearer") @app.route('/invalid') def handler_invalid(request): @@ -136,6 +141,10 @@ def test_unauthorized_exception(exception_app): assert "nonce='abcdef'" in auth_header assert "opaque='zyxwvu'" in auth_header + request, response = exception_app.test_client.get('/401/bearer') + assert response.status == 401 + assert response.headers.get('WWW-Authenticate') == "Bearer" + def test_handled_unhandled_exception(exception_app): """Test that an exception not built into sanic is handled"""