diff --git a/authentik/root/asgi/__init__.py b/authentik/root/asgi/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/root/asgi/app.py b/authentik/root/asgi/app.py new file mode 100644 index 000000000..81e6cb051 --- /dev/null +++ b/authentik/root/asgi/app.py @@ -0,0 +1,42 @@ +""" +ASGI config for authentik project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ +""" +from time import time + +import django +from asgiref.compatibility import guarantee_single_callable +from channels.routing import ProtocolTypeRouter, URLRouter +from defusedxml import defuse_stdlib +from django.core.asgi import get_asgi_application +from sentry_sdk.integrations.asgi import SentryAsgiMiddleware + +from authentik.root.asgi.error_handler import ASGIErrorHandler +from authentik.root.asgi.logger import ASGILogger + +# DJANGO_SETTINGS_MODULE is set in gunicorn.conf.py + +defuse_stdlib() +django.setup() + +# pylint: disable=wrong-import-position +from authentik.root import websocket # noqa # isort:skip + +application = ASGIErrorHandler( + ASGILogger( + guarantee_single_callable( + SentryAsgiMiddleware( + ProtocolTypeRouter( + { + "http": get_asgi_application(), + "websocket": URLRouter(websocket.websocket_urlpatterns), + } + ) + ) + ) + ) +) diff --git a/authentik/root/asgi/error_handler.py b/authentik/root/asgi/error_handler.py new file mode 100644 index 000000000..8e76c5777 --- /dev/null +++ b/authentik/root/asgi/error_handler.py @@ -0,0 +1,31 @@ +"""ASGI Error handler""" +from structlog.stdlib import get_logger + +from authentik.root.asgi.types import ASGIApp, Receive, Scope, Send + +LOGGER = get_logger("authentik.asgi") + + +class ASGIErrorHandler: + """ASGI Error handler""" + + app: ASGIApp + + def __init__(self, app: ASGIApp): + self.app = app + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + try: + return await self.app(scope, receive, send) + except Exception as exc: # pylint: disable= + LOGGER.warning("Fatal ASGI exception", exc=exc) + return await self.error_handler(send) + + async def error_handler(self, send: Send) -> None: + return await send( + { + "type": "http.request", + "body": b"Internal server error", + "more_body": False, + } + ) diff --git a/authentik/root/asgi.py b/authentik/root/asgi/logger.py similarity index 66% rename from authentik/root/asgi.py rename to authentik/root/asgi/logger.py index 4d55e795a..926099477 100644 --- a/authentik/root/asgi.py +++ b/authentik/root/asgi/logger.py @@ -1,41 +1,10 @@ -""" -ASGI config for authentik project. - -It exposes the ASGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ -""" -import typing +"""ASGI Logger""" from time import time -import django -from asgiref.compatibility import guarantee_single_callable -from channels.routing import ProtocolTypeRouter, URLRouter -from defusedxml import defuse_stdlib -from django.core.asgi import get_asgi_application -from sentry_sdk.integrations.asgi import SentryAsgiMiddleware from structlog.stdlib import get_logger from authentik.core.middleware import RESPONSE_HEADER_ID - -# DJANGO_SETTINGS_MODULE is set in gunicorn.conf.py - -defuse_stdlib() -django.setup() - -# pylint: disable=wrong-import-position -from authentik.root import websocket # noqa # isort:skip - - -# See https://github.com/encode/starlette/blob/master/starlette/types.py -Scope = typing.MutableMapping[str, typing.Any] -Message = typing.MutableMapping[str, typing.Any] - -Receive = typing.Callable[[], typing.Awaitable[Message]] -Send = typing.Callable[[Message], typing.Awaitable[None]] - -ASGIApp = typing.Callable[[Scope, Receive, Send], typing.Awaitable[None]] +from authentik.root.asgi.types import ASGIApp, Message, Receive, Scope, Send ASGI_IP_HEADERS = ( b"x-forwarded-for", @@ -86,7 +55,7 @@ class ASGILogger: # https://code.djangoproject.com/ticket/31508 # https://github.com/encode/uvicorn/issues/266 return - await self.app(scope, receive, send_hooked) + return await self.app(scope, receive, send_hooked) def _get_ip(self, scope: Scope) -> str: client_ip = None @@ -115,17 +84,3 @@ class ASGILogger: runtime=runtime, **kwargs, ) - - -application = ASGILogger( - guarantee_single_callable( - SentryAsgiMiddleware( - ProtocolTypeRouter( - { - "http": get_asgi_application(), - "websocket": URLRouter(websocket.websocket_urlpatterns), - } - ) - ) - ) -) diff --git a/authentik/root/asgi/types.py b/authentik/root/asgi/types.py new file mode 100644 index 000000000..842410a75 --- /dev/null +++ b/authentik/root/asgi/types.py @@ -0,0 +1,10 @@ +import typing + +# See https://github.com/encode/starlette/blob/master/starlette/types.py +Scope = typing.MutableMapping[str, typing.Any] +Message = typing.MutableMapping[str, typing.Any] + +Receive = typing.Callable[[], typing.Awaitable[Message]] +Send = typing.Callable[[Message], typing.Awaitable[None]] + +ASGIApp = typing.Callable[[Scope, Receive, Send], typing.Awaitable[None]] diff --git a/authentik/root/settings.py b/authentik/root/settings.py index 2e9ca1392..ab3f59cc5 100644 --- a/authentik/root/settings.py +++ b/authentik/root/settings.py @@ -251,7 +251,7 @@ TEMPLATES = [ }, ] -ASGI_APPLICATION = "authentik.root.asgi.application" +ASGI_APPLICATION = "authentik.root.asgi.app.application" CHANNEL_LAYERS = { "default": { diff --git a/internal/gounicorn/gounicorn.go b/internal/gounicorn/gounicorn.go index e2aed980c..a6b246403 100644 --- a/internal/gounicorn/gounicorn.go +++ b/internal/gounicorn/gounicorn.go @@ -28,7 +28,7 @@ func NewGoUnicorn() *GoUnicorn { func (g *GoUnicorn) initCmd() { command := "gunicorn" - args := []string{"-c", "./lifecycle/gunicorn.conf.py", "authentik.root.asgi:application"} + args := []string{"-c", "./lifecycle/gunicorn.conf.py", "authentik.root.asgi.app:application"} if config.G.Debug { command = "python" args = []string{"manage.py", "runserver", "localhost:8000"}