diff options
Diffstat (limited to 'gn_auth/errors')
-rw-r--r-- | gn_auth/errors/__init__.py | 48 | ||||
-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/__init__.py | 13 | ||||
-rw-r--r-- | gn_auth/errors/http/http_4xx_errors.py | 23 | ||||
-rw-r--r-- | gn_auth/errors/http/http_5xx_errors.py | 7 |
6 files changed, 166 insertions, 0 deletions
diff --git a/gn_auth/errors/__init__.py b/gn_auth/errors/__init__.py new file mode 100644 index 0000000..97d1e9e --- /dev/null +++ b/gn_auth/errors/__init__.py @@ -0,0 +1,48 @@ +"""Handle application level errors.""" +import logging +import traceback + +from werkzeug.exceptions import NotFound, HTTPException +from flask import (Flask, + request, + jsonify, + render_template) + +from gn_auth.auth.errors import AuthorisationError + +from .http import http_error_handlers +from .authlib import authlib_error_handlers +from .common import add_trace, build_handler + +logger = logging.getLogger(__name__) + +__all__ = ["register_error_handlers"] + + +def handle_general_exception(exc: Exception): + """Handle generic unhandled exceptions.""" + 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.""" + 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 = { + **authlib_error_handlers(), + **http_error_handlers(), + Exception: handle_general_exception, + AuthorisationError: handle_authorisation_error + } + 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/__init__.py b/gn_auth/errors/http/__init__.py new file mode 100644 index 0000000..f4164d1 --- /dev/null +++ b/gn_auth/errors/http/__init__.py @@ -0,0 +1,13 @@ +"""HTTP error handlers.""" + +from .http_4xx_errors import http_4xx_error_handlers +from .http_5xx_errors import http_5xx_error_handlers + +__all__ = ["http_error_handlers"] + +def http_error_handlers() -> dict: + """Return *ALL* HTTP error handlers.""" + return { + **http_4xx_error_handlers(), + **http_5xx_error_handlers() + } diff --git a/gn_auth/errors/http/http_4xx_errors.py b/gn_auth/errors/http/http_4xx_errors.py new file mode 100644 index 0000000..3a2ed88 --- /dev/null +++ b/gn_auth/errors/http/http_4xx_errors.py @@ -0,0 +1,23 @@ +"""Handlers for HTTP 4** errors""" +import logging + +from werkzeug.exceptions import NotFound, Forbidden, Unauthorized + +from gn_auth.errors.common import build_handler + +__all__ = ["http_4xx_error_handlers"] + +logger = logging.getLogger(__name__) + + +def http_4xx_error_handlers() -> dict: + """Return handlers for HTTP errors in the 400-499 range""" + return { + 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/http/http_5xx_errors.py b/gn_auth/errors/http/http_5xx_errors.py new file mode 100644 index 0000000..71d09d8 --- /dev/null +++ b/gn_auth/errors/http/http_5xx_errors.py @@ -0,0 +1,7 @@ +"""Handlers for HTTP 5** errors.""" + +__all__ = ["http_5xx_error_handlers"] + +def http_5xx_error_handlers() -> dict: + """Return handlers for HTTP errors in the 500-599 range""" + return {} |