diff --git a/authentik/core/sources/flow_manager.py b/authentik/core/sources/flow_manager.py index c02cef90f..d871d03e6 100644 --- a/authentik/core/sources/flow_manager.py +++ b/authentik/core/sources/flow_manager.py @@ -33,6 +33,7 @@ from authentik.flows.planner import ( from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN from authentik.lib.utils.urls import redirect_with_qs from authentik.policies.utils import delete_none_keys +from authentik.stages.password import BACKEND_DJANGO from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT @@ -198,7 +199,7 @@ class SourceFlowManager: kwargs.update( { # Since we authenticate the user by their token, they have no backend set - PLAN_CONTEXT_AUTHENTICATION_BACKEND: "django.contrib.auth.backends.ModelBackend", + PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_DJANGO, PLAN_CONTEXT_SSO: True, PLAN_CONTEXT_SOURCE: self.source, PLAN_CONTEXT_REDIRECT: final_redirect, diff --git a/authentik/flows/migrations/0008_default_flows.py b/authentik/flows/migrations/0008_default_flows.py index 6bb0a0feb..7f92644eb 100644 --- a/authentik/flows/migrations/0008_default_flows.py +++ b/authentik/flows/migrations/0008_default_flows.py @@ -6,6 +6,7 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor from authentik.flows.models import FlowDesignation from authentik.stages.identification.models import UserFields +from authentik.stages.password import BACKEND_DJANGO, BACKEND_LDAP def create_default_authentication_flow( @@ -31,7 +32,7 @@ def create_default_authentication_flow( password_stage, _ = PasswordStage.objects.using(db_alias).update_or_create( name="default-authentication-password", - defaults={"backends": ["django.contrib.auth.backends.ModelBackend"]}, + defaults={"backends": [BACKEND_DJANGO, BACKEND_LDAP]}, ) login_stage, _ = UserLoginStage.objects.using(db_alias).update_or_create( diff --git a/authentik/flows/migrations/0018_oob_flows.py b/authentik/flows/migrations/0018_oob_flows.py index 5d6395e44..c6caee84b 100644 --- a/authentik/flows/migrations/0018_oob_flows.py +++ b/authentik/flows/migrations/0018_oob_flows.py @@ -15,9 +15,6 @@ PREFILL_POLICY_EXPRESSION = """# This policy sets the user for the currently run # by injecting "pending_user" akadmin = ak_user_by(username="akadmin") context["pending_user"] = akadmin -# We're also setting the backend for the user, so we can -# directly login without having to identify again -context["user_backend"] = "django.contrib.auth.backends.ModelBackend" return True""" diff --git a/authentik/recovery/views.py b/authentik/recovery/views.py index 76cb9636a..27b0023af 100644 --- a/authentik/recovery/views.py +++ b/authentik/recovery/views.py @@ -7,6 +7,7 @@ from django.utils.translation import gettext as _ from django.views import View from authentik.core.models import Token, TokenIntents +from authentik.stages.password import BACKEND_DJANGO class UseTokenView(View): @@ -18,7 +19,7 @@ class UseTokenView(View): if not tokens.exists(): raise Http404 token = tokens.first() - login(request, token.user, backend="django.contrib.auth.backends.ModelBackend") + login(request, token.user, backend=BACKEND_DJANGO) token.delete() messages.warning(request, _("Used recovery-link to authenticate.")) return redirect("authentik_core:if-admin") diff --git a/authentik/sources/saml/processors/response.py b/authentik/sources/saml/processors/response.py index 94630305b..8ccf26232 100644 --- a/authentik/sources/saml/processors/response.py +++ b/authentik/sources/saml/processors/response.py @@ -39,7 +39,7 @@ from authentik.sources.saml.processors.constants import ( from authentik.sources.saml.processors.request import SESSION_REQUEST_ID from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT -from authentik.stages.user_login.stage import DEFAULT_BACKEND +from authentik.stages.user_login.stage import BACKEND_DJANGO LOGGER = get_logger() if TYPE_CHECKING: @@ -141,7 +141,7 @@ class ResponseProcessor: self._source.authentication_flow, **{ PLAN_CONTEXT_PENDING_USER: user, - PLAN_CONTEXT_AUTHENTICATION_BACKEND: DEFAULT_BACKEND, + PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_DJANGO, }, ) @@ -204,7 +204,7 @@ class ResponseProcessor: self._source.authentication_flow, **{ PLAN_CONTEXT_PENDING_USER: matching_users.first(), - PLAN_CONTEXT_AUTHENTICATION_BACKEND: DEFAULT_BACKEND, + PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_DJANGO, PLAN_CONTEXT_REDIRECT: final_redirect, }, ) diff --git a/authentik/stages/identification/tests.py b/authentik/stages/identification/tests.py index b8bcfe7c2..158db23ab 100644 --- a/authentik/stages/identification/tests.py +++ b/authentik/stages/identification/tests.py @@ -9,6 +9,7 @@ from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding from authentik.providers.oauth2.generators import generate_client_secret from authentik.sources.oauth.models import OAuthSource from authentik.stages.identification.models import IdentificationStage, UserFields +from authentik.stages.password import BACKEND_DJANGO from authentik.stages.password.models import PasswordStage @@ -70,7 +71,7 @@ class TestIdentificationStage(TestCase): def test_valid_with_password(self): """Test with valid email and password in single step""" pw_stage = PasswordStage.objects.create( - name="password", backends=["django.contrib.auth.backends.ModelBackend"] + name="password", backends=[BACKEND_DJANGO] ) self.stage.password_stage = pw_stage self.stage.save() @@ -92,7 +93,7 @@ class TestIdentificationStage(TestCase): def test_invalid_with_password(self): """Test with valid email and invalid password in single step""" pw_stage = PasswordStage.objects.create( - name="password", backends=["django.contrib.auth.backends.ModelBackend"] + name="password", backends=[BACKEND_DJANGO] ) self.stage.password_stage = pw_stage self.stage.save() diff --git a/authentik/stages/invitation/tests.py b/authentik/stages/invitation/tests.py index 3a30742ce..509e5fcb8 100644 --- a/authentik/stages/invitation/tests.py +++ b/authentik/stages/invitation/tests.py @@ -17,6 +17,7 @@ from authentik.flows.tests.test_views import TO_STAGE_RESPONSE_MOCK from authentik.flows.views import SESSION_KEY_PLAN from authentik.stages.invitation.models import Invitation, InvitationStage from authentik.stages.invitation.stage import INVITATION_TOKEN_KEY, PLAN_CONTEXT_PROMPT +from authentik.stages.password import BACKEND_DJANGO from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND @@ -46,9 +47,7 @@ class TestUserLoginStage(TestCase): flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] ) plan.context[PLAN_CONTEXT_PENDING_USER] = self.user - plan.context[ - PLAN_CONTEXT_AUTHENTICATION_BACKEND - ] = "django.contrib.auth.backends.ModelBackend" + plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO session = self.client.session session[SESSION_KEY_PLAN] = plan session.save() @@ -79,9 +78,7 @@ class TestUserLoginStage(TestCase): flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] ) plan.context[PLAN_CONTEXT_PENDING_USER] = self.user - plan.context[ - PLAN_CONTEXT_AUTHENTICATION_BACKEND - ] = "django.contrib.auth.backends.ModelBackend" + plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO session = self.client.session session[SESSION_KEY_PLAN] = plan session.save() diff --git a/authentik/stages/password/__init__.py b/authentik/stages/password/__init__.py index e69de29bb..a9c71fe82 100644 --- a/authentik/stages/password/__init__.py +++ b/authentik/stages/password/__init__.py @@ -0,0 +1,3 @@ +"""Backend paths""" +BACKEND_DJANGO = "django.contrib.auth.backends.ModelBackend" +BACKEND_LDAP = "authentik.sources.ldap.auth.LDAPBackend" diff --git a/authentik/stages/password/models.py b/authentik/stages/password/models.py index a4e14e51f..e0df6b77b 100644 --- a/authentik/stages/password/models.py +++ b/authentik/stages/password/models.py @@ -9,17 +9,18 @@ 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 def get_authentication_backends(): """Return all available authentication backends as tuple set""" return [ ( - "django.contrib.auth.backends.ModelBackend", + BACKEND_DJANGO, _("authentik-internal Userdatabase"), ), ( - "authentik.sources.ldap.auth.LDAPBackend", + BACKEND_LDAP, _("authentik LDAP"), ), ] diff --git a/authentik/stages/password/tests.py b/authentik/stages/password/tests.py index 1707eef83..846360421 100644 --- a/authentik/stages/password/tests.py +++ b/authentik/stages/password/tests.py @@ -14,6 +14,7 @@ from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan from authentik.flows.tests.test_views import TO_STAGE_RESPONSE_MOCK from authentik.flows.views import SESSION_KEY_PLAN from authentik.providers.oauth2.generators import generate_client_secret +from authentik.stages.password import BACKEND_DJANGO from authentik.stages.password.models import PasswordStage MOCK_BACKEND_AUTHENTICATE = MagicMock(side_effect=PermissionDenied("test")) @@ -36,7 +37,7 @@ class TestPasswordStage(TestCase): designation=FlowDesignation.AUTHENTICATION, ) self.stage = PasswordStage.objects.create( - name="password", backends=["django.contrib.auth.backends.ModelBackend"] + name="password", backends=[BACKEND_DJANGO] ) FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) diff --git a/authentik/stages/user_login/stage.py b/authentik/stages/user_login/stage.py index e6e521e11..773ccc3ab 100644 --- a/authentik/stages/user_login/stage.py +++ b/authentik/stages/user_login/stage.py @@ -8,10 +8,10 @@ from structlog.stdlib import get_logger from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.stage import StageView from authentik.lib.utils.time import timedelta_from_string +from authentik.stages.password import BACKEND_DJANGO from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND LOGGER = get_logger() -DEFAULT_BACKEND = "django.contrib.auth.backends.ModelBackend" USER_LOGIN_AUTHENTICATED = "user_login_authenticated" @@ -26,7 +26,7 @@ class UserLoginStageView(StageView): LOGGER.debug(message) return self.executor.stage_invalid() backend = self.executor.plan.context.get( - PLAN_CONTEXT_AUTHENTICATION_BACKEND, DEFAULT_BACKEND + PLAN_CONTEXT_AUTHENTICATION_BACKEND, BACKEND_DJANGO ) login( self.request, diff --git a/authentik/stages/user_logout/tests.py b/authentik/stages/user_logout/tests.py index 04c2d5111..9f706ba5d 100644 --- a/authentik/stages/user_logout/tests.py +++ b/authentik/stages/user_logout/tests.py @@ -9,6 +9,7 @@ from authentik.flows.markers import StageMarker from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan from authentik.flows.views import SESSION_KEY_PLAN +from authentik.stages.password import BACKEND_DJANGO from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND from authentik.stages.user_logout.models import UserLogoutStage @@ -35,9 +36,7 @@ class TestUserLogoutStage(TestCase): flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] ) plan.context[PLAN_CONTEXT_PENDING_USER] = self.user - plan.context[ - PLAN_CONTEXT_AUTHENTICATION_BACKEND - ] = "django.contrib.auth.backends.ModelBackend" + plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO session = self.client.session session[SESSION_KEY_PLAN] = plan session.save() diff --git a/website/static/flows/login-2fa.akflow b/website/static/flows/login-2fa.akflow index c37f71f37..35e9fc87c 100644 --- a/website/static/flows/login-2fa.akflow +++ b/website/static/flows/login-2fa.akflow @@ -51,7 +51,10 @@ }, "model": "authentik_stages_password.passwordstage", "attrs": { - "backends": ["django.contrib.auth.backends.ModelBackend"] + "backends": [ + "django.contrib.auth.backends.ModelBackend", + "authentik.sources.ldap.auth.LDAPBackend" + ] } }, { diff --git a/website/static/flows/login-conditional-captcha.akflow b/website/static/flows/login-conditional-captcha.akflow index b08969683..f5e040ad8 100644 --- a/website/static/flows/login-conditional-captcha.akflow +++ b/website/static/flows/login-conditional-captcha.akflow @@ -54,7 +54,10 @@ }, "model": "authentik_stages_password.passwordstage", "attrs": { - "backends": ["django.contrib.auth.backends.ModelBackend"] + "backends": [ + "django.contrib.auth.backends.ModelBackend", + "authentik.sources.ldap.auth.LDAPBackend" + ] } }, {