From f44fba57ef88ec9c11223c2765d49fb3573305a0 Mon Sep 17 00:00:00 2001 From: Kurt Griffiths Date: Thu, 21 Apr 2016 08:43:54 -0500 Subject: [PATCH] doc(quickstart): Sync HTTPUnauthorized example with post-0.3 changes (#760) Update the complex example given in the quickstart and README so that it now works in light of a breaking change to the HTTPUnauthorized class. Note this breaking change in the changelog. Closes #757 --- CHANGES.rst | 4 ++++ README.rst | 29 ++++++++++++++++++----------- doc/changes/1.0.0.rst | 4 ++++ doc/user/quickstart.rst | 24 +++++++++++++++--------- falcon/errors.py | 3 ++- tests/test_example.py | 24 +++++++++++++++--------- tests/test_httperror.py | 6 +++--- 7 files changed, 61 insertions(+), 33 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index ad2bebc..cd3de3a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -29,6 +29,10 @@ Breaking Changes app = falcon.API() app.req_options.auto_parse_form_urlencoded = True +- The ``HTTPUnauthorized`` initializer now requires an + additional argument, `challenges`. Per RFC 7235, a server returning a + 401 must include a WWW-Authenticate header field containing at least + one challenge. - The performance of composing the response body was improved. As part of this work, the ``Response.body_encoded`` attribute was removed. This property was only intended to be used by diff --git a/README.rst b/README.rst index 6c9b568..08dfe5c 100644 --- a/README.rst +++ b/README.rst @@ -152,14 +152,14 @@ API. # things.py - # Let's get this party started + # Let's get this party started! import falcon # Falcon follows the REST architectural style, meaning (among # other things) that you think in terms of resources and state # transitions, which map to HTTP verbs. - class ThingsResource: + class ThingsResource(object): def on_get(self, req, resp): """Handles GET requests""" resp.status = falcon.HTTP_200 # This is the default status @@ -251,8 +251,10 @@ bodies. class AuthMiddleware(object): def process_request(self, req, resp): - token = req.get_header('X-Auth-Token') - project = req.get_header('X-Project-ID') + token = req.get_header('Authorization') + account_id = req.get_header('Account-ID') + + challenges = ['Token type="Fernet"'] if token is None: description = ('Please provide an auth token ' @@ -260,18 +262,19 @@ bodies. raise falcon.HTTPUnauthorized('Auth token required', description, + challenges, href='http://docs.example.com/auth') - if not self._token_is_valid(token, project): + if not self._token_is_valid(token, account_id): description = ('The provided auth token is not valid. ' 'Please request a new token and try again.') raise falcon.HTTPUnauthorized('Authentication required', description, - href='http://docs.example.com/auth', - scheme='Token; UUID') + challenges, + href='http://docs.example.com/auth') - def _token_is_valid(self, token, project): + def _token_is_valid(self, token, account_id): return True # Suuuuuure it's valid... @@ -337,7 +340,7 @@ bodies. return hook - class ThingsResource: + class ThingsResource(object): def __init__(self, db): self.db = db @@ -367,7 +370,7 @@ bodies. # that would serialize to JSON under the covers. req.context['result'] = result - resp.set_header('X-Powered-By', 'Small Furry Creatures') + resp.set_header('Powered-By', 'Falcon') resp.status = falcon.HTTP_200 @falcon.before(max_body(64 * 1024)) @@ -406,11 +409,15 @@ bodies. sink = SinkAdapter() app.add_sink(sink, r'/search/(?Pddg|y)\Z') - # Useful for debugging problems in your API; works with pdb.set_trace() + # Useful for debugging problems in your API; works with pdb.set_trace(). You + # can also use Gunicorn to host your app. Gunicorn can be configured to + # auto-restart workers when it detects a code change, and it also works + # with pdb. if __name__ == '__main__': httpd = simple_server.make_server('127.0.0.1', 8000, app) httpd.serve_forever() + Community --------- diff --git a/doc/changes/1.0.0.rst b/doc/changes/1.0.0.rst index 3c4bd56..dcbdf58 100644 --- a/doc/changes/1.0.0.rst +++ b/doc/changes/1.0.0.rst @@ -29,6 +29,10 @@ Breaking Changes app = falcon.API() app.req_options.auto_parse_form_urlencoded = True +- The :class:`~falcon.HTTPUnauthorized` initializer now requires an + additional argument, `challenges`. Per RFC 7235, a server returning a + 401 must include a WWW-Authenticate header field containing at least + one challenge. - The performance of composing the response body was improved. As part of this work, the :attr:`Response.body_encoded` attribute was removed. This property was only intended to be used by diff --git a/doc/user/quickstart.rst b/doc/user/quickstart.rst index 53252d6..583e401 100644 --- a/doc/user/quickstart.rst +++ b/doc/user/quickstart.rst @@ -21,7 +21,7 @@ started writing an API: # things.py - # Let's get this party started + # Let's get this party started! import falcon @@ -121,8 +121,10 @@ parameters, handling errors, and working with request and response bodies. class AuthMiddleware(object): def process_request(self, req, resp): - token = req.get_header('X-Auth-Token') - project = req.get_header('X-Project-ID') + token = req.get_header('Authorization') + account_id = req.get_header('Account-ID') + + challenges = ['Token type="Fernet"'] if token is None: description = ('Please provide an auth token ' @@ -130,18 +132,19 @@ parameters, handling errors, and working with request and response bodies. raise falcon.HTTPUnauthorized('Auth token required', description, + challenges, href='http://docs.example.com/auth') - if not self._token_is_valid(token, project): + if not self._token_is_valid(token, account_id): description = ('The provided auth token is not valid. ' 'Please request a new token and try again.') raise falcon.HTTPUnauthorized('Authentication required', description, - href='http://docs.example.com/auth', - scheme='Token; UUID') + challenges, + href='http://docs.example.com/auth') - def _token_is_valid(self, token, project): + def _token_is_valid(self, token, account_id): return True # Suuuuuure it's valid... @@ -237,7 +240,7 @@ parameters, handling errors, and working with request and response bodies. # that would serialize to JSON under the covers. req.context['result'] = result - resp.set_header('X-Powered-By', 'Small Furry Creatures') + resp.set_header('Powered-By', 'Falcon') resp.status = falcon.HTTP_200 @falcon.before(max_body(64 * 1024)) @@ -276,7 +279,10 @@ parameters, handling errors, and working with request and response bodies. sink = SinkAdapter() app.add_sink(sink, r'/search/(?Pddg|y)\Z') - # Useful for debugging problems in your API; works with pdb.set_trace() + # Useful for debugging problems in your API; works with pdb.set_trace(). You + # can also use Gunicorn to host your app. Gunicorn can be configured to + # auto-restart workers when it detects a code change, and it also works + # with pdb. if __name__ == '__main__': httpd = simple_server.make_server('127.0.0.1', 8000, app) httpd.serve_forever() diff --git a/falcon/errors.py b/falcon/errors.py index 4519947..6c67be5 100644 --- a/falcon/errors.py +++ b/falcon/errors.py @@ -52,7 +52,8 @@ class HTTPUnauthorized(HTTPError): a helpful suggestion or two. challenges (iterable of str): One or more authentication challenges to use as the value of the WWW-Authenticate header in - the response. + the response. See also: + http://tools.ietf.org/html/rfc7235#section-2.1 kwargs (optional): Same as for ``HTTPError``. """ diff --git a/tests/test_example.py b/tests/test_example.py index 33a05ca..b267292 100644 --- a/tests/test_example.py +++ b/tests/test_example.py @@ -49,8 +49,10 @@ class SinkAdapter(object): class AuthMiddleware(object): def process_request(self, req, resp): - token = req.get_header('X-Auth-Token') - project = req.get_header('X-Project-ID') + token = req.get_header('Authorization') + account_id = req.get_header('Account-ID') + + challenges = ['Token type="Fernet"'] if token is None: description = ('Please provide an auth token ' @@ -58,18 +60,19 @@ class AuthMiddleware(object): raise falcon.HTTPUnauthorized('Auth token required', description, + challenges, href='http://docs.example.com/auth') - if not self._token_is_valid(token, project): + if not self._token_is_valid(token, account_id): description = ('The provided auth token is not valid. ' 'Please request a new token and try again.') raise falcon.HTTPUnauthorized('Authentication required', description, - href='http://docs.example.com/auth', - scheme='Token; UUID') + challenges, + href='http://docs.example.com/auth') - def _token_is_valid(self, token, project): + def _token_is_valid(self, token, account_id): return True # Suuuuuure it's valid... @@ -135,7 +138,7 @@ def max_body(limit): return hook -class ThingsResource: +class ThingsResource(object): def __init__(self, db): self.db = db @@ -165,7 +168,7 @@ class ThingsResource: # that would serialize to JSON under the covers. req.context['result'] = result - resp.set_header('X-Powered-By', 'Small Furry Creatures') + resp.set_header('Powered-By', 'Falcon') resp.status = falcon.HTTP_200 @falcon.before(max_body(64 * 1024)) @@ -204,7 +207,10 @@ app.add_error_handler(StorageError, StorageError.handle) sink = SinkAdapter() app.add_sink(sink, r'/search/(?Pddg|y)\Z') -# Useful for debugging problems in your API; works with pdb.set_trace() +# Useful for debugging problems in your API; works with pdb.set_trace(). You +# can also use Gunicorn to host your app. Gunicorn can be configured to +# auto-restart workers when it detects a code change, and it also works +# with pdb. if __name__ == '__main__': httpd = simple_server.make_server('127.0.0.1', 8000, app) httpd.serve_forever() diff --git a/tests/test_httperror.py b/tests/test_httperror.py index 9c2a4df..09e6bd2 100644 --- a/tests/test_httperror.py +++ b/tests/test_httperror.py @@ -73,18 +73,18 @@ class UnauthorizedResource: def on_get(self, req, resp): raise falcon.HTTPUnauthorized('Authentication Required', - 'Missing or invalid token header.', + 'Missing or invalid authorization.', ['Basic realm="simple"']) def on_post(self, req, resp): raise falcon.HTTPUnauthorized('Authentication Required', - 'Missing or invalid token header.', + 'Missing or invalid authorization.', ['Newauth realm="apps"', 'Basic realm="simple"']) def on_put(self, req, resp): raise falcon.HTTPUnauthorized('Authentication Required', - 'Missing or invalid token header.', []) + 'Missing or invalid authorization.', []) class NotFoundResource: