Simplified the Unauthorized.__init__ signature.

It doesn't really make sense to have a `realm` parameter in the method signature.
Instead, one can simply set the realm in the `challenge` dict if necessary.

Also fixed the tests accordingly (and added a new one for "Bearer" auth-scheme).
This commit is contained in:
François KUBLER 2017-06-29 12:34:52 +02:00
parent 60aa60f48e
commit e427e38da8
2 changed files with 36 additions and 8 deletions

View File

@ -204,25 +204,44 @@ class Unauthorized(SanicException):
Unauthorized exception (401 HTTP status code). Unauthorized exception (401 HTTP status code).
:param scheme: Name of the authentication scheme to be used. :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 :param challenge: A dict containing values to add to the WWW-Authenticate
header that is generated. This is especially useful when dealing with the header that is generated. This is especially useful when dealing with the
Digest scheme. (optional) 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 pass
def __init__(self, message, scheme, realm="", challenge=None): def __init__(self, message, scheme, challenge=None):
super().__init__(message) super().__init__(message)
adds = "" chal = ""
if challenge is not None: if challenge is not None:
values = ["{!s}={!r}".format(k, v) for k, v in challenge.items()] values = ["{!s}={!r}".format(k, v) for k, v in challenge.items()]
adds = ', '.join(values) chal = ', '.join(values)
adds = ', {}'.format(adds)
self.headers = { self.headers = {
"WWW-Authenticate": "{} realm='{}'{}".format(scheme, realm, adds) "WWW-Authenticate": "{} {}".format(scheme, chal).rstrip()
} }

View File

@ -29,17 +29,22 @@ def exception_app():
@app.route('/401/basic') @app.route('/401/basic')
def handler_401_basic(request): def handler_401_basic(request):
raise Unauthorized("Unauthorized", "Basic", "Sanic") raise Unauthorized("Unauthorized", "Basic", {"realm": "Sanic"})
@app.route('/401/digest') @app.route('/401/digest')
def handler_401_digest(request): def handler_401_digest(request):
challenge = { challenge = {
"realm": "Sanic",
"qop": "auth, auth-int", "qop": "auth, auth-int",
"algorithm": "MD5", "algorithm": "MD5",
"nonce": "abcdef", "nonce": "abcdef",
"opaque": "zyxwvu", "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') @app.route('/invalid')
def handler_invalid(request): def handler_invalid(request):
@ -126,6 +131,10 @@ def test_unauthorized_exception(exception_app):
assert "nonce='abcdef'" in auth_header assert "nonce='abcdef'" in auth_header
assert "opaque='zyxwvu'" 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): def test_handled_unhandled_exception(exception_app):
"""Test that an exception not built into sanic is handled""" """Test that an exception not built into sanic is handled"""