diff options
-rw-r--r-- | gn_auth/__init__.py | 3 | ||||
-rw-r--r-- | gn_auth/errors/__init__.py | 46 | ||||
-rw-r--r-- | gn_auth/errors/authlib.py | 34 | ||||
-rw-r--r-- | gn_auth/errors/common.py | 41 | ||||
-rw-r--r-- | gn_auth/errors/http/http_4xx_errors.py | 26 | ||||
-rw-r--r-- | gn_auth/errors/tracing.py | 18 | ||||
-rw-r--r-- | gn_auth/templates/404.html | 13 | ||||
-rw-r--r-- | gn_auth/templates/http-error-4xx.html | 20 | ||||
-rw-r--r-- | gn_auth/templates/http-error-5xx.html (renamed from gn_auth/templates/50x.html) | 0 |
9 files changed, 122 insertions, 79 deletions
diff --git a/gn_auth/__init__.py b/gn_auth/__init__.py index cf8989d..aebc63b 100644 --- a/gn_auth/__init__.py +++ b/gn_auth/__init__.py @@ -72,7 +72,8 @@ def gunicorn_loggers(appl: Flask) -> None: _LOGGABLE_MODULES_ = ( "gn_auth.errors", - "gn_auth.errors.tracing", + "gn_auth.errors.common", + "gn_auth.errors.authlib", "gn_auth.errors.http.http_4xx_errors", "gn_auth.errors.http.http_5xx_errors" ) diff --git a/gn_auth/errors/__init__.py b/gn_auth/errors/__init__.py index 533a842..97d1e9e 100644 --- a/gn_auth/errors/__init__.py +++ b/gn_auth/errors/__init__.py @@ -10,8 +10,9 @@ from flask import (Flask, from gn_auth.auth.errors import AuthorisationError -from .tracing import add_trace from .http import http_error_handlers +from .authlib import authlib_error_handlers +from .common import add_trace, build_handler logger = logging.getLogger(__name__) @@ -20,43 +21,28 @@ __all__ = ["register_error_handlers"] def handle_general_exception(exc: Exception): """Handle generic unhandled exceptions.""" - logger.error("Error occurred!", exc_info=True) - content_type = request.content_type - if bool(content_type) and content_type.lower() == "application/json": - exc_args = [str(x) for x in exc.args] - msg = ("The following exception was raised while attempting to access " - f"{request.url}: {' '.join(exc_args)}") - return jsonify(add_trace(exc, { - "error": type(exc).__name__, - "error_description": msg - })), 500 - - return render_template("50x.html", - page=request.url, - error=exc, - trace=traceback.format_exception(exc)), 500 + exc_args = [str(x) for x in exc.args] + _handle = build_handler("A generic exception occurred: " + " ".join(exc_args)) + return _handle(exc) def handle_authorisation_error(exc: AuthorisationError): """Handle AuthorisationError if not handled anywhere else.""" - logger.error("Error occurred!", exc_info=True) - logger.error(exc) - return jsonify(add_trace(exc, { - "error": type(exc).__name__, - "error_description": " :: ".join(exc.args) - })), exc.error_code - -__error_handlers__ = { - Exception: handle_general_exception, - AuthorisationError: handle_authorisation_error -} + exc_args = [str(x) for x in exc.args] + _handle = build_handler("A generic authorisation error occurred: " + " ".join(exc_args)) + return _handle(exc) def register_error_handlers(app: Flask): """Register ALL defined error handlers""" _handlers = { - **__error_handlers__, - **http_error_handlers() + **authlib_error_handlers(), + **http_error_handlers(), + Exception: handle_general_exception, + AuthorisationError: handle_authorisation_error } - for class_, error_handler in __error_handlers__.items(): + for class_, error_handler in _handlers.items(): + logger.debug("Register handler for %s", class_.__name__) app.register_error_handler(class_, error_handler) diff --git a/gn_auth/errors/authlib.py b/gn_auth/errors/authlib.py new file mode 100644 index 0000000..09862e3 --- /dev/null +++ b/gn_auth/errors/authlib.py @@ -0,0 +1,34 @@ +"""Handle authlib errors.""" +import json +import logging + +from authlib.integrations.flask_oauth2.errors import _HTTPException + +from gn_auth.errors.common import build_handler + +logger = logging.getLogger(__name__) + + +def __description__(body): + """Improve description for errors in authlib.oauth2.rfc6749.errors""" + _desc = body["error_description"] + match body["error"]: + case "missing_authorization": + return ( + 'The expected "Authorization: Bearer ..." token was not found ' + 'in the headers. Do please try again with the token provided.') + case _: + return _desc + + +def _http_exception_handler_(exc: _HTTPException): + """Handle Authlib's `_HTTPException` errors.""" + _handle = build_handler(__description__(json.loads(exc.body))) + return _handle(exc) + + +def authlib_error_handlers() -> dict: + """Return handlers for Authlib errors""" + return { + _HTTPException: _http_exception_handler_ + } diff --git a/gn_auth/errors/common.py b/gn_auth/errors/common.py new file mode 100644 index 0000000..7ef18cf --- /dev/null +++ b/gn_auth/errors/common.py @@ -0,0 +1,41 @@ +"""Common utilities.""" +import logging +import traceback + +from flask import request, jsonify, render_template + +logger = logging.getLogger(__name__) + + +def add_trace(exc: Exception, errobj: dict) -> dict: + """Add the traceback to the error handling object.""" + return { + **errobj, + "error-trace": "".join(traceback.format_exception(exc)) + } + + +def build_handler(description: str): + """Generic utility to build error handlers.""" + def __handler__(exc: Exception): + error = (exc.name if hasattr(exc, "name") else exc.__class__.__name__) + status_code = exc.code if hasattr(exc, "code") else 500 + content_type = request.content_type + if bool(content_type) and content_type.lower() == "application/json": + return ( + jsonify(add_trace(exc, + { + "requested-uri": request.url, + "error": error, + "error_description": description + })), + status_code) + + return (render_template(f"http-error-{str(status_code)[0:-2]}xx.html", + error=exc, + page=request.url, + description=description, + trace=traceback.format_exception(exc)), + status_code) + + return __handler__ diff --git a/gn_auth/errors/http/http_4xx_errors.py b/gn_auth/errors/http/http_4xx_errors.py index 704f11b..3a2ed88 100644 --- a/gn_auth/errors/http/http_4xx_errors.py +++ b/gn_auth/errors/http/http_4xx_errors.py @@ -1,31 +1,23 @@ """Handlers for HTTP 4** errors""" import logging -from werkzeug.exceptions import NotFound -from flask import request, jsonify, render_template +from werkzeug.exceptions import NotFound, Forbidden, Unauthorized -from gn_auth.errors.tracing import add_trace +from gn_auth.errors.common import build_handler __all__ = ["http_4xx_error_handlers"] logger = logging.getLogger(__name__) -def page_not_found(exc): - """404 handler.""" - logger.error("Page '%s' was not found.", request.url, exc_info=True) - content_type = request.content_type - if bool(content_type) and content_type.lower() == "application/json": - return jsonify(add_trace(exc, { - "error": exc.name, - "error_description": (f"The page '{request.url}' does not exist on " - "this server.") - })), exc.code - - return render_template("404.html", page=request.url), exc.code - def http_4xx_error_handlers() -> dict: """Return handlers for HTTP errors in the 400-499 range""" return { - NotFound: page_not_found + Forbidden: build_handler( + "You do not have the necessary privileges to access the requested " + "resource."), + NotFound: build_handler( + "The requested page does not exist on this server."), + Unauthorized: build_handler( + "You are not authorised to access the requested resource.") } diff --git a/gn_auth/errors/tracing.py b/gn_auth/errors/tracing.py deleted file mode 100644 index 25b544f..0000000 --- a/gn_auth/errors/tracing.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Utilities for improving error tracing.""" -import logging -import traceback - -from flask import request - -logger = logging.getLogger(__name__) - - -def add_trace(exc: Exception, errobj: dict) -> dict: - """Add the traceback to the error handling object.""" - logger.error("Endpoint: %s\n%s", - request.url, - traceback.format_exception(exc)) - return { - **errobj, - "error-trace": "".join(traceback.format_exception(exc)) - } diff --git a/gn_auth/templates/404.html b/gn_auth/templates/404.html deleted file mode 100644 index e17bfe8..0000000 --- a/gn_auth/templates/404.html +++ /dev/null @@ -1,13 +0,0 @@ -{%extends "base.html"%} - -{%block title%}404: Page Not Found{%endblock%} - -{%block pagetitle%}404: Could Not Find the Requested Page{%endblock%} - -{%block content%} - -<p> - The page "<strong>{{page}}</strong>" does not exist on this server. -</p> - -{%endblock%} diff --git a/gn_auth/templates/http-error-4xx.html b/gn_auth/templates/http-error-4xx.html new file mode 100644 index 0000000..16c4581 --- /dev/null +++ b/gn_auth/templates/http-error-4xx.html @@ -0,0 +1,20 @@ +{%extends "base.html"%} + +{%block title%}{{error.code}}: {{error.name}}{%endblock%} + +{%block pagetitle%}{{error.code}}: {{error.name}}{%endblock%} + +{%block content%} + +<dl> + <dt>status code</dt> + <dd>{{error.code}}: {{error.name}}</dd> + + <dt><strong>URI</strong></dt> + <dd>{{page}}</dd> + + <dt>error description</dt> + <dd>{{description}}</dd> +</dl> + +{%endblock%} diff --git a/gn_auth/templates/50x.html b/gn_auth/templates/http-error-5xx.html index 859a232..859a232 100644 --- a/gn_auth/templates/50x.html +++ b/gn_auth/templates/http-error-5xx.html |