Added a Unauthorized exception.

Also added a few tests related to this new exception.
This commit is contained in:
François KUBLER 2017-06-23 16:12:15 +02:00
parent b5369e611c
commit cf1713b085
2 changed files with 64 additions and 1 deletions

View File

@ -198,6 +198,34 @@ class InvalidRangeType(ContentRangeError):
pass pass
@add_status_code(401)
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 others: 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)
"""
pass
def __init__(self, message, scheme, realm="", others=None):
super().__init__(message)
adds = ""
if others is not None:
values = ["{!s}={!r}".format(k, v) for k, v in others.items()]
adds = ', '.join(values)
adds = ', {}'.format(adds)
self.headers = {
"WWW-Authenticate": "{} realm='{}'{}".format(scheme, realm, adds)
}
def abort(status_code, message=None): def abort(status_code, message=None):
""" """
Raise an exception based on SanicException. Returns the HTTP response Raise an exception based on SanicException. Returns the HTTP response

View File

@ -3,7 +3,8 @@ from bs4 import BeautifulSoup
from sanic import Sanic from sanic import Sanic
from sanic.response import text from sanic.response import text
from sanic.exceptions import InvalidUsage, ServerError, NotFound, abort from sanic.exceptions import InvalidUsage, ServerError, NotFound, Unauthorized
from sanic.exceptions import abort
class SanicExceptionTestException(Exception): class SanicExceptionTestException(Exception):
@ -26,6 +27,20 @@ def exception_app():
def handler_404(request): def handler_404(request):
raise NotFound("OK") raise NotFound("OK")
@app.route('/401/basic')
def handler_401_basic(request):
raise Unauthorized("Unauthorized", "Basic", "Sanic")
@app.route('/401/digest')
def handler_401_digest(request):
challenge = {
"qop": "auth, auth-int",
"algorithm": "MD5",
"nonce": "abcdef",
"opaque": "zyxwvu",
}
raise Unauthorized("Unauthorized", "Digest", "Sanic", challenge)
@app.route('/invalid') @app.route('/invalid')
def handler_invalid(request): def handler_invalid(request):
raise InvalidUsage("OK") raise InvalidUsage("OK")
@ -49,8 +64,10 @@ def exception_app():
return app return app
def test_catch_exception_list(): def test_catch_exception_list():
app = Sanic('exception_list') app = Sanic('exception_list')
@app.exception([SanicExceptionTestException, NotFound]) @app.exception([SanicExceptionTestException, NotFound])
def exception_list(request, exception): def exception_list(request, exception):
return text("ok") return text("ok")
@ -91,6 +108,24 @@ def test_not_found_exception(exception_app):
assert response.status == 404 assert response.status == 404
def test_unauthorized_exception(exception_app):
"""Test the built-in Unauthorized exception"""
request, response = exception_app.test_client.get('/401/basic')
assert response.status == 401
assert response.headers.get('WWW-Authenticate') is not None
assert response.headers.get('WWW-Authenticate') == "Basic realm='Sanic'"
request, response = exception_app.test_client.get('/401/digest')
assert response.status == 401
auth_header = response.headers.get('WWW-Authenticate')
expected = ("Digest realm='Sanic', qop='auth, auth-int', algorithm='MD5', "
"nonce='abcdef', opaque='zyxwvu'")
assert auth_header is not None
assert auth_header == expected
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"""
request, response = exception_app.test_client.get('/divide_by_zero') request, response = exception_app.test_client.get('/divide_by_zero')