diff --git a/sanic/exceptions.py b/sanic/exceptions.py index 0edb0562..9663ea7c 100644 --- a/sanic/exceptions.py +++ b/sanic/exceptions.py @@ -209,6 +209,7 @@ class Unauthorized(SanicException): Unauthorized exception (401 HTTP status code). :param message: Message describing the exception. + :param status_code: HTTP Status code. :param scheme: Name of the authentication scheme to be used. When present, kwargs is used to complete the WWW-Authentication header. @@ -216,11 +217,13 @@ class Unauthorized(SanicException): Examples:: # With a Basic auth-scheme, realm MUST be present: - raise Unauthorized("Auth required.", "Basic", realm="Restricted Area") + raise Unauthorized("Auth required.", + scheme="Basic", + realm="Restricted Area") # With a Digest auth-scheme, things are a bit more complicated: raise Unauthorized("Auth required.", - "Digest", + scheme="Digest", realm="Restricted Area", qop="auth, auth-int", algorithm="MD5", @@ -228,20 +231,24 @@ class Unauthorized(SanicException): opaque="zyxwvu") # With a Bearer auth-scheme, realm is optional so you can write: - raise Unauthorized("Auth required.", "Bearer") + raise Unauthorized("Auth required.", scheme="Bearer") # or, if you want to specify the realm: - raise Unauthorized("Auth required.", "Bearer", realm="Restricted Area") + raise Unauthorized("Auth required.", + scheme="Bearer", + realm="Restricted Area") """ - def __init__(self, message, scheme, **kwargs): - super().__init__(message) + def __init__(self, message, status_code=None, scheme=None, **kwargs): + super().__init__(message, status_code) - values = ["{!s}={!r}".format(k, v) for k, v in kwargs.items()] - challenge = ', '.join(values) + # if auth-scheme is specified, set "WWW-Authenticate" header + if scheme is not None: + values = ["{!s}={!r}".format(k, v) for k, v in kwargs.items()] + challenge = ', '.join(values) - self.headers = { - "WWW-Authenticate": "{} {}".format(scheme, challenge).rstrip() - } + self.headers = { + "WWW-Authenticate": "{} {}".format(scheme, challenge).rstrip() + } def abort(status_code, message=None): diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 1521c9ed..c535059c 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -31,14 +31,18 @@ def exception_app(): def handler_403(request): raise Forbidden("Forbidden") + @app.route('/401') + def handler_401(request): + raise Unauthorized("Unauthorized") + @app.route('/401/basic') def handler_401_basic(request): - raise Unauthorized("Unauthorized", "Basic", realm="Sanic") + raise Unauthorized("Unauthorized", scheme="Basic", realm="Sanic") @app.route('/401/digest') def handler_401_digest(request): raise Unauthorized("Unauthorized", - "Digest", + scheme="Digest", realm="Sanic", qop="auth, auth-int", algorithm="MD5", @@ -47,12 +51,16 @@ def exception_app(): @app.route('/401/bearer') def handler_401_bearer(request): - raise Unauthorized("Unauthorized", "Bearer") + raise Unauthorized("Unauthorized", scheme="Bearer") @app.route('/invalid') def handler_invalid(request): raise InvalidUsage("OK") + @app.route('/abort/401') + def handler_invalid(request): + abort(401) + @app.route('/abort') def handler_invalid(request): abort(500) @@ -124,6 +132,9 @@ def test_forbidden_exception(exception_app): def test_unauthorized_exception(exception_app): """Test the built-in Unauthorized exception""" + request, response = exception_app.test_client.get('/401') + assert response.status == 401 + request, response = exception_app.test_client.get('/401/basic') assert response.status == 401 assert response.headers.get('WWW-Authenticate') is not None @@ -186,5 +197,8 @@ def test_exception_in_exception_handler_debug_off(exception_app): def test_abort(exception_app): """Test the abort function""" + request, response = exception_app.test_client.get('/abort/401') + assert response.status == 401 + request, response = exception_app.test_client.get('/abort') assert response.status == 500