# Copyright 2020-2023 Enrico Zini <enrico@debian.org>
# Copyright 2023 The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""
Authentication middleware that authenticates using signon Providers.

It adds a `request.signon` member that is a Signon object, providing an entry
point for managing externally authenticated identities.
"""
from typing import Callable, Protocol, cast, runtime_checkable

import django.http
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, MiddlewareNotUsed

from debusine.server.signon.signon import Signon


@runtime_checkable
class RequestSignonProtocol(Protocol):
    """A Django request that has been processed by :class:`SignonMiddleware`."""

    signon: Signon


class SignonMiddleware:
    """Authenticate via external signon providers."""

    def __init__(
        self,
        get_response: Callable[
            [django.http.HttpRequest], django.http.HttpResponse
        ],
    ):
        """Middleware API entry point."""
        self.providers = getattr(settings, "SIGNON_PROVIDERS", ())
        if not self.providers:
            raise MiddlewareNotUsed()
        self.get_response = get_response

    def __call__(self, request: django.http.HttpRequest):
        """Middleware API entry point."""
        # AuthenticationMiddleware is required so that request.user exists.
        if not hasattr(request, 'user'):
            raise ImproperlyConfigured(
                "The signon middleware requires the authentication middleware"
                " to be installed.  Edit your MIDDLEWARE setting to insert"
                " 'django.contrib.auth.middleware.AuthenticationMiddleware'"
                " before the SignonMiddleware class."
            )

        # Add request.signon
        cast(RequestSignonProtocol, request).signon = Signon(request)

        return self.get_response(request)
