about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--gn_auth/__init__.py3
-rw-r--r--gn_auth/errors/__init__.py46
-rw-r--r--gn_auth/errors/authlib.py34
-rw-r--r--gn_auth/errors/common.py41
-rw-r--r--gn_auth/errors/http/http_4xx_errors.py26
-rw-r--r--gn_auth/errors/tracing.py18
-rw-r--r--gn_auth/templates/404.html13
-rw-r--r--gn_auth/templates/http-error-4xx.html20
-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