From 20572c728d58f063f0dfd159197fb50bb638ff06 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 23 Aug 2021 16:05:29 +0200 Subject: [PATCH] core: add new token intent and auth backend Signed-off-by: Jens Langhammer --- authentik/core/models.py | 3 +++ authentik/core/token_auth.py | 29 +++++++++++++++++++++++++++ authentik/stages/password/__init__.py | 1 + authentik/stages/password/models.py | 10 ++++++--- 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 authentik/core/token_auth.py diff --git a/authentik/core/models.py b/authentik/core/models.py index bc3fa43ae..214e4217f 100644 --- a/authentik/core/models.py +++ b/authentik/core/models.py @@ -408,6 +408,9 @@ class TokenIntents(models.TextChoices): # Recovery use for the recovery app INTENT_RECOVERY = "recovery" + # App-specific passwords + INTENT_APP_PASSWORD = "app_password" + class Token(ManagedModel, ExpiringModel): """Token used to authenticate the User for API Access or confirm another Stage like Email.""" diff --git a/authentik/core/token_auth.py b/authentik/core/token_auth.py new file mode 100644 index 000000000..1c95ce996 --- /dev/null +++ b/authentik/core/token_auth.py @@ -0,0 +1,29 @@ +"""Authenticate with tokens""" + +from typing import Any, Optional + +from django.contrib.auth.backends import ModelBackend +from django.http.request import HttpRequest + +from authentik.core.models import Token, TokenIntents, User + + +class TokenBackend(ModelBackend): + """Authenticate with token""" + + def authenticate( + self, request: HttpRequest, username: Optional[str], password: Optional[str], **kwargs: Any + ) -> Optional[User]: + try: + user = User._default_manager.get_by_natural_key(username) + except User.DoesNotExist: + # Run the default password hasher once to reduce the timing + # difference between an existing and a nonexistent user (#20760). + User().set_password(password) + return None + tokens = Token.filter_not_expired( + user=user, key=password, intent=TokenIntents.INTENT_APP_PASSWORD + ) + if not tokens.exists(): + return None + return user diff --git a/authentik/stages/password/__init__.py b/authentik/stages/password/__init__.py index a9c71fe82..9e42ec060 100644 --- a/authentik/stages/password/__init__.py +++ b/authentik/stages/password/__init__.py @@ -1,3 +1,4 @@ """Backend paths""" BACKEND_DJANGO = "django.contrib.auth.backends.ModelBackend" BACKEND_LDAP = "authentik.sources.ldap.auth.LDAPBackend" +BACKEND_TOKEN = "authentik.core.token_auth.TokenBackend" diff --git a/authentik/stages/password/models.py b/authentik/stages/password/models.py index e0df6b77b..66fd5d043 100644 --- a/authentik/stages/password/models.py +++ b/authentik/stages/password/models.py @@ -9,7 +9,7 @@ from rest_framework.serializers import BaseSerializer from authentik.core.types import UserSettingSerializer from authentik.flows.models import ConfigurableStage, Stage -from authentik.stages.password import BACKEND_DJANGO, BACKEND_LDAP +from authentik.stages.password import BACKEND_DJANGO, BACKEND_LDAP, BACKEND_TOKEN def get_authentication_backends(): @@ -17,11 +17,15 @@ def get_authentication_backends(): return [ ( BACKEND_DJANGO, - _("authentik-internal Userdatabase"), + _("User database + standard password"), + ), + ( + BACKEND_TOKEN, + _("User database + app passwords"), ), ( BACKEND_LDAP, - _("authentik LDAP"), + _("User database + LDAP password"), ), ]