core: add new token intent and auth backend (#1284)

* core: add new token intent and auth backend

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* root: update schema

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/admin: allow users to create app password tokens

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/admin: display token's intents

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* stages/password: auto-enable app password backend

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/admin: fix missing app passwords backend

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* core: use custom inbuilt backend, set backend login information in flow plan for events

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* website/docs: add docs for `auth_method` and `auth_method_args` fields

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* website: fix example flows using incorrect backend

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* root: add alias for akflow files

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* core: fix token intent not defaulting correctly

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* website: update akflows orders

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/admin: improve delete modal for stage bindings and policy bindings

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* events: fix linting

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* website: make default login-2fa flow ignore 2fa with app passwords

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/admin: select all password stage backends by default

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* root: fix mis-matched postgres version for CI

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web: fix lint error

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* core: fix authentication error when no request is given

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* ci: set debug log level

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* stages/user_write: fix wrong fallback authentication backend

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* core: add token tests for invalid intent and token auth

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L 2021-08-23 21:21:39 +02:00 committed by GitHub
commit 6b7a8b6ac7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 623 additions and 194 deletions

22
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,22 @@
{
"cSpell.words": [
"asgi",
"authentik",
"authn",
"goauthentik",
"jwks",
"oidc",
"openid",
"plex",
"saml",
"totp",
"webauthn"
],
"python.linting.pylintEnabled": true,
"todo-tree.tree.showCountsInTree": true,
"todo-tree.tree.showBadges": true,
"python.formatting.provider": "black",
"files.associations": {
"*.akflow": "json"
}
}

View File

@ -1,7 +1,10 @@
"""Tokens API Viewset""" """Tokens API Viewset"""
from typing import Any
from django.http.response import Http404 from django.http.response import Http404
from drf_spectacular.utils import OpenApiResponse, extend_schema from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField from rest_framework.fields import CharField
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
@ -22,6 +25,13 @@ class TokenSerializer(ManagedSerializer, ModelSerializer):
user = UserSerializer(required=False) user = UserSerializer(required=False)
def validate(self, attrs: dict[Any, str]) -> dict[Any, str]:
"""Ensure only API or App password tokens are created."""
attrs.setdefault("intent", TokenIntents.INTENT_API)
if attrs.get("intent") not in [TokenIntents.INTENT_API, TokenIntents.INTENT_APP_PASSWORD]:
raise ValidationError(f"Invalid intent {attrs.get('intent')}")
return attrs
class Meta: class Meta:
model = Token model = Token
@ -69,7 +79,6 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
def perform_create(self, serializer: TokenSerializer): def perform_create(self, serializer: TokenSerializer):
serializer.save( serializer.save(
user=self.request.user, user=self.request.user,
intent=TokenIntents.INTENT_API,
expiring=self.request.user.attributes.get(USER_ATTRIBUTE_TOKEN_EXPIRING, True), expiring=self.request.user.attributes.get(USER_ATTRIBUTE_TOKEN_EXPIRING, True),
) )

58
authentik/core/auth.py Normal file
View File

@ -0,0 +1,58 @@
"""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
from authentik.flows.planner import FlowPlan
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
class InbuiltBackend(ModelBackend):
"""Inbuilt backend"""
def authenticate(
self, request: HttpRequest, username: Optional[str], password: Optional[str], **kwargs: Any
) -> Optional[User]:
user = super().authenticate(request, username=username, password=password, **kwargs)
if not user:
return None
self.set_method("password", request)
return user
def set_method(self, method: str, request: Optional[HttpRequest], **kwargs):
"""Set method data on current flow, if possbiel"""
if not request:
return
# Since we can't directly pass other variables to signals, and we want to log the method
# and the token used, we assume we're running in a flow and set a variable in the context
flow_plan: FlowPlan = request.session[SESSION_KEY_PLAN]
flow_plan.context[PLAN_CONTEXT_METHOD] = method
flow_plan.context[PLAN_CONTEXT_METHOD_ARGS] = kwargs
request.session[SESSION_KEY_PLAN] = flow_plan
class TokenBackend(InbuiltBackend):
"""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
token = tokens.first()
self.set_method("password", request, token=token)
return token.user

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.6 on 2021-08-23 14:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0027_bootstrap_token"),
]
operations = [
migrations.AlterField(
model_name="token",
name="intent",
field=models.TextField(
choices=[
("verification", "Intent Verification"),
("api", "Intent Api"),
("recovery", "Intent Recovery"),
("app_password", "Intent App Password"),
],
default="verification",
),
),
]

View File

@ -411,6 +411,9 @@ class TokenIntents(models.TextChoices):
# Recovery use for the recovery app # Recovery use for the recovery app
INTENT_RECOVERY = "recovery" INTENT_RECOVERY = "recovery"
# App-specific passwords
INTENT_APP_PASSWORD = "app_password" # nosec
class Token(ManagedModel, ExpiringModel): class Token(ManagedModel, ExpiringModel):
"""Token used to authenticate the User for API Access or confirm another Stage like Email.""" """Token used to authenticate the User for API Access or confirm another Stage like Email."""

View File

@ -25,7 +25,7 @@ from authentik.flows.planner import (
from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN 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.lib.utils.urls import redirect_with_qs
from authentik.policies.utils import delete_none_keys from authentik.policies.utils import delete_none_keys
from authentik.stages.password import BACKEND_DJANGO from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
@ -189,7 +189,7 @@ class SourceFlowManager:
kwargs.update( kwargs.update(
{ {
# Since we authenticate the user by their token, they have no backend set # Since we authenticate the user by their token, they have no backend set
PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_DJANGO, PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_INBUILT,
PLAN_CONTEXT_SSO: True, PLAN_CONTEXT_SSO: True,
PLAN_CONTEXT_SOURCE: self.source, PLAN_CONTEXT_SOURCE: self.source,
PLAN_CONTEXT_REDIRECT: final_redirect, PLAN_CONTEXT_REDIRECT: final_redirect,

View File

@ -1,16 +1,13 @@
"""Test Source flow_manager""" """Test Source flow_manager"""
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.http.request import HttpRequest
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
from guardian.utils import get_anonymous_user from guardian.utils import get_anonymous_user
from authentik.core.models import SourceUserMatchingModes, User from authentik.core.models import SourceUserMatchingModes, User
from authentik.core.sources.flow_manager import Action from authentik.core.sources.flow_manager import Action
from authentik.flows.tests.test_planner import dummy_get_response
from authentik.lib.generators import generate_id from authentik.lib.generators import generate_id
from authentik.lib.tests.utils import get_request
from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from authentik.sources.oauth.views.callback import OAuthSourceFlowManager from authentik.sources.oauth.views.callback import OAuthSourceFlowManager
@ -24,22 +21,10 @@ class TestSourceFlowManager(TestCase):
self.factory = RequestFactory() self.factory = RequestFactory()
self.identifier = generate_id() self.identifier = generate_id()
def get_request(self, user: User) -> HttpRequest:
"""Helper to create a get request with session and message middleware"""
request = self.factory.get("/")
request.user = user
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
middleware = MessageMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
return request
def test_unauthenticated_enroll(self): def test_unauthenticated_enroll(self):
"""Test un-authenticated user enrolling""" """Test un-authenticated user enrolling"""
flow_manager = OAuthSourceFlowManager( flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {} self.source, get_request("/", user=AnonymousUser()), self.identifier, {}
) )
action, _ = flow_manager.get_action() action, _ = flow_manager.get_action()
self.assertEqual(action, Action.ENROLL) self.assertEqual(action, Action.ENROLL)
@ -52,7 +37,7 @@ class TestSourceFlowManager(TestCase):
) )
flow_manager = OAuthSourceFlowManager( flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {} self.source, get_request("/", user=AnonymousUser()), self.identifier, {}
) )
action, _ = flow_manager.get_action() action, _ = flow_manager.get_action()
self.assertEqual(action, Action.AUTH) self.assertEqual(action, Action.AUTH)
@ -65,7 +50,7 @@ class TestSourceFlowManager(TestCase):
) )
user = User.objects.create(username="foo", email="foo@bar.baz") user = User.objects.create(username="foo", email="foo@bar.baz")
flow_manager = OAuthSourceFlowManager( flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(user), self.identifier, {} self.source, get_request("/", user=user), self.identifier, {}
) )
action, _ = flow_manager.get_action() action, _ = flow_manager.get_action()
self.assertEqual(action, Action.LINK) self.assertEqual(action, Action.LINK)
@ -78,7 +63,7 @@ class TestSourceFlowManager(TestCase):
# Without email, deny # Without email, deny
flow_manager = OAuthSourceFlowManager( flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {} self.source, get_request("/", user=AnonymousUser()), self.identifier, {}
) )
action, _ = flow_manager.get_action() action, _ = flow_manager.get_action()
self.assertEqual(action, Action.DENY) self.assertEqual(action, Action.DENY)
@ -86,7 +71,7 @@ class TestSourceFlowManager(TestCase):
# With email # With email
flow_manager = OAuthSourceFlowManager( flow_manager = OAuthSourceFlowManager(
self.source, self.source,
self.get_request(AnonymousUser()), get_request("/", user=AnonymousUser()),
self.identifier, self.identifier,
{"email": "foo@bar.baz"}, {"email": "foo@bar.baz"},
) )
@ -101,7 +86,7 @@ class TestSourceFlowManager(TestCase):
# Without username, deny # Without username, deny
flow_manager = OAuthSourceFlowManager( flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {} self.source, get_request("/", user=AnonymousUser()), self.identifier, {}
) )
action, _ = flow_manager.get_action() action, _ = flow_manager.get_action()
self.assertEqual(action, Action.DENY) self.assertEqual(action, Action.DENY)
@ -109,7 +94,7 @@ class TestSourceFlowManager(TestCase):
# With username # With username
flow_manager = OAuthSourceFlowManager( flow_manager = OAuthSourceFlowManager(
self.source, self.source,
self.get_request(AnonymousUser()), get_request("/", user=AnonymousUser()),
self.identifier, self.identifier,
{"username": "foo"}, {"username": "foo"},
) )
@ -125,7 +110,7 @@ class TestSourceFlowManager(TestCase):
# With non-existent username, enroll # With non-existent username, enroll
flow_manager = OAuthSourceFlowManager( flow_manager = OAuthSourceFlowManager(
self.source, self.source,
self.get_request(AnonymousUser()), get_request("/", user=AnonymousUser()),
self.identifier, self.identifier,
{ {
"username": "bar", "username": "bar",
@ -137,7 +122,7 @@ class TestSourceFlowManager(TestCase):
# With username # With username
flow_manager = OAuthSourceFlowManager( flow_manager = OAuthSourceFlowManager(
self.source, self.source,
self.get_request(AnonymousUser()), get_request("/", user=AnonymousUser()),
self.identifier, self.identifier,
{"username": "foo"}, {"username": "foo"},
) )
@ -151,7 +136,7 @@ class TestSourceFlowManager(TestCase):
flow_manager = OAuthSourceFlowManager( flow_manager = OAuthSourceFlowManager(
self.source, self.source,
self.get_request(AnonymousUser()), get_request("/", user=AnonymousUser()),
self.identifier, self.identifier,
{"username": "foo"}, {"username": "foo"},
) )

View File

@ -27,6 +27,14 @@ class TestTokenAPI(APITestCase):
self.assertEqual(token.intent, TokenIntents.INTENT_API) self.assertEqual(token.intent, TokenIntents.INTENT_API)
self.assertEqual(token.expiring, True) self.assertEqual(token.expiring, True)
def test_token_create_invalid(self):
"""Test token creation endpoint (invalid data)"""
response = self.client.post(
reverse("authentik_api:token-list"),
{"identifier": "test-token", "intent": TokenIntents.INTENT_RECOVERY},
)
self.assertEqual(response.status_code, 400)
def test_token_create_non_expiring(self): def test_token_create_non_expiring(self):
"""Test token creation endpoint""" """Test token creation endpoint"""
self.user.attributes[USER_ATTRIBUTE_TOKEN_EXPIRING] = False self.user.attributes[USER_ATTRIBUTE_TOKEN_EXPIRING] = False

View File

@ -0,0 +1,40 @@
"""Test token auth"""
from django.test import TestCase
from authentik.core.auth import TokenBackend
from authentik.core.models import Token, TokenIntents, User
from authentik.flows.planner import FlowPlan
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.lib.tests.utils import get_request
class TestTokenAuth(TestCase):
"""Test token auth"""
def setUp(self) -> None:
self.user = User.objects.create(username="test-user")
self.token = Token.objects.create(
expiring=False, user=self.user, intent=TokenIntents.INTENT_APP_PASSWORD
)
# To test with session we need to create a request and pass it through all middlewares
self.request = get_request("/")
self.request.session[SESSION_KEY_PLAN] = FlowPlan("test")
def test_token_auth(self):
"""Test auth with token"""
self.assertEqual(
TokenBackend().authenticate(self.request, "test-user", self.token.key), self.user
)
def test_token_auth_none(self):
"""Test auth with token (non-existent user)"""
self.assertIsNone(
TokenBackend().authenticate(self.request, "test-user-foo", self.token.key), self.user
)
def test_token_auth_invalid(self):
"""Test auth with token (invalid token)"""
self.assertIsNone(
TokenBackend().authenticate(self.request, "test-user", self.token.key + "foo"),
self.user,
)

View File

@ -15,6 +15,7 @@ from authentik.flows.planner import PLAN_CONTEXT_SOURCE, FlowPlan
from authentik.flows.views import SESSION_KEY_PLAN from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.invitation.models import Invitation from authentik.stages.invitation.models import Invitation
from authentik.stages.invitation.signals import invitation_used from authentik.stages.invitation.signals import invitation_used
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
from authentik.stages.user_write.signals import user_write from authentik.stages.user_write.signals import user_write
@ -46,7 +47,13 @@ def on_user_logged_in(sender, request: HttpRequest, user: User, **_):
flow_plan: FlowPlan = request.session[SESSION_KEY_PLAN] flow_plan: FlowPlan = request.session[SESSION_KEY_PLAN]
if PLAN_CONTEXT_SOURCE in flow_plan.context: if PLAN_CONTEXT_SOURCE in flow_plan.context:
# Login request came from an external source, save it in the context # Login request came from an external source, save it in the context
thread.kwargs["using_source"] = flow_plan.context[PLAN_CONTEXT_SOURCE] thread.kwargs[PLAN_CONTEXT_SOURCE] = flow_plan.context[PLAN_CONTEXT_SOURCE]
if PLAN_CONTEXT_METHOD in flow_plan.context:
thread.kwargs[PLAN_CONTEXT_METHOD] = flow_plan.context[PLAN_CONTEXT_METHOD]
# Save the login method used
thread.kwargs[PLAN_CONTEXT_METHOD_ARGS] = flow_plan.context.get(
PLAN_CONTEXT_METHOD_ARGS, {}
)
thread.user = user thread.user = user
thread.run() thread.run()

View File

@ -6,7 +6,7 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from authentik.flows.models import FlowDesignation from authentik.flows.models import FlowDesignation
from authentik.stages.identification.models import UserFields from authentik.stages.identification.models import UserFields
from authentik.stages.password import BACKEND_DJANGO, BACKEND_LDAP from authentik.stages.password import BACKEND_APP_PASSWORD, BACKEND_INBUILT, BACKEND_LDAP
def create_default_authentication_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): def create_default_authentication_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
@ -26,7 +26,7 @@ def create_default_authentication_flow(apps: Apps, schema_editor: BaseDatabaseSc
password_stage, _ = PasswordStage.objects.using(db_alias).update_or_create( password_stage, _ = PasswordStage.objects.using(db_alias).update_or_create(
name="default-authentication-password", name="default-authentication-password",
defaults={"backends": [BACKEND_DJANGO, BACKEND_LDAP]}, defaults={"backends": [BACKEND_INBUILT, BACKEND_LDAP, BACKEND_APP_PASSWORD]},
) )
login_stage, _ = UserLoginStage.objects.using(db_alias).update_or_create( login_stage, _ = UserLoginStage.objects.using(db_alias).update_or_create(

View File

@ -3,7 +3,6 @@ from unittest.mock import MagicMock, Mock, PropertyMock, patch
from django.contrib.sessions.middleware import SessionMiddleware from django.contrib.sessions.middleware import SessionMiddleware
from django.core.cache import cache from django.core.cache import cache
from django.http import HttpRequest
from django.test import RequestFactory, TestCase from django.test import RequestFactory, TestCase
from django.urls import reverse from django.urls import reverse
from guardian.shortcuts import get_anonymous_user from guardian.shortcuts import get_anonymous_user
@ -13,6 +12,7 @@ from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableExce
from authentik.flows.markers import ReevaluateMarker, StageMarker from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key
from authentik.lib.tests.utils import dummy_get_response
from authentik.policies.dummy.models import DummyPolicy from authentik.policies.dummy.models import DummyPolicy
from authentik.policies.models import PolicyBinding from authentik.policies.models import PolicyBinding
from authentik.policies.types import PolicyResult from authentik.policies.types import PolicyResult
@ -24,11 +24,6 @@ CACHE_MOCK = Mock(wraps=cache)
POLICY_RETURN_TRUE = MagicMock(return_value=PolicyResult(True)) POLICY_RETURN_TRUE = MagicMock(return_value=PolicyResult(True))
def dummy_get_response(request: HttpRequest): # pragma: no cover
"""Dummy get_response for SessionMiddleware"""
return None
class TestFlowPlanner(TestCase): class TestFlowPlanner(TestCase):
"""Test planner logic""" """Test planner logic"""

View File

@ -25,6 +25,8 @@ def get_attrs(obj: SerializerModel) -> dict[str, Any]:
"component", "component",
"flow_set", "flow_set",
"promptstage_set", "promptstage_set",
"policybindingmodel_ptr_id",
"export_url",
) )
for to_remove_name in to_remove: for to_remove_name in to_remove:
if to_remove_name in data: if to_remove_name in data:

View File

@ -0,0 +1,27 @@
"""Test utils"""
from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.http import HttpRequest
from django.test.client import RequestFactory
from guardian.utils import get_anonymous_user
def dummy_get_response(request: HttpRequest): # pragma: no cover
"""Dummy get_response for SessionMiddleware"""
return None
def get_request(*args, user=None, **kwargs):
"""Get a request with usable session"""
request = RequestFactory().get(*args, **kwargs)
if user:
request.user = user
else:
request.user = get_anonymous_user()
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
middleware = MessageMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
return request

View File

@ -1,16 +1,14 @@
"""Test AuthN Request generator and parser""" """Test AuthN Request generator and parser"""
from base64 import b64encode from base64 import b64encode
from django.contrib.sessions.middleware import SessionMiddleware
from django.http.request import QueryDict from django.http.request import QueryDict
from django.test import RequestFactory, TestCase from django.test import RequestFactory, TestCase
from guardian.utils import get_anonymous_user
from authentik.core.models import User from authentik.core.models import User
from authentik.crypto.models import CertificateKeyPair from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
from authentik.flows.models import Flow from authentik.flows.models import Flow
from authentik.flows.tests.test_planner import dummy_get_response from authentik.lib.tests.utils import get_request
from authentik.managed.manager import ObjectManager from authentik.managed.manager import ObjectManager
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.assertion import AssertionProcessor from authentik.providers.saml.processors.assertion import AssertionProcessor
@ -99,11 +97,7 @@ class TestAuthNRequest(TestCase):
def test_signed_valid(self): def test_signed_valid(self):
"""Test generated AuthNRequest with valid signature""" """Test generated AuthNRequest with valid signature"""
http_request = self.factory.get("/") http_request = get_request("/")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
# First create an AuthNRequest # First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state") request_proc = RequestProcessor(self.source, http_request, "test_state")
@ -117,12 +111,7 @@ class TestAuthNRequest(TestCase):
def test_request_full_signed(self): def test_request_full_signed(self):
"""Test full SAML Request/Response flow, fully signed""" """Test full SAML Request/Response flow, fully signed"""
http_request = self.factory.get("/") http_request = get_request("/")
http_request.user = get_anonymous_user()
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
# First create an AuthNRequest # First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state") request_proc = RequestProcessor(self.source, http_request, "test_state")
@ -145,12 +134,7 @@ class TestAuthNRequest(TestCase):
def test_request_id_invalid(self): def test_request_id_invalid(self):
"""Test generated AuthNRequest with invalid request ID""" """Test generated AuthNRequest with invalid request ID"""
http_request = self.factory.get("/") http_request = get_request("/")
http_request.user = get_anonymous_user()
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
# First create an AuthNRequest # First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state") request_proc = RequestProcessor(self.source, http_request, "test_state")
@ -179,11 +163,7 @@ class TestAuthNRequest(TestCase):
def test_signed_valid_detached(self): def test_signed_valid_detached(self):
"""Test generated AuthNRequest with valid signature (detached)""" """Test generated AuthNRequest with valid signature (detached)"""
http_request = self.factory.get("/") http_request = get_request("/")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
# First create an AuthNRequest # First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state") request_proc = RequestProcessor(self.source, http_request, "test_state")
@ -243,12 +223,7 @@ class TestAuthNRequest(TestCase):
def test_request_attributes(self): def test_request_attributes(self):
"""Test full SAML Request/Response flow, fully signed""" """Test full SAML Request/Response flow, fully signed"""
http_request = self.factory.get("/") http_request = get_request("/", user=User.objects.get(username="akadmin"))
http_request.user = User.objects.get(username="akadmin")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
# First create an AuthNRequest # First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state") request_proc = RequestProcessor(self.source, http_request, "test_state")
@ -264,12 +239,7 @@ class TestAuthNRequest(TestCase):
def test_request_attributes_invalid(self): def test_request_attributes_invalid(self):
"""Test full SAML Request/Response flow, fully signed""" """Test full SAML Request/Response flow, fully signed"""
http_request = self.factory.get("/") http_request = get_request("/", user=User.objects.get(username="akadmin"))
http_request.user = User.objects.get(username="akadmin")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
# First create an AuthNRequest # First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state") request_proc = RequestProcessor(self.source, http_request, "test_state")

View File

@ -1,18 +1,16 @@
"""Test Requests and Responses against schema""" """Test Requests and Responses against schema"""
from base64 import b64encode from base64 import b64encode
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import RequestFactory, TestCase from django.test import RequestFactory, TestCase
from guardian.utils import get_anonymous_user
from lxml import etree # nosec from lxml import etree # nosec
from authentik.crypto.models import CertificateKeyPair from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow from authentik.flows.models import Flow
from authentik.lib.tests.utils import get_request
from authentik.managed.manager import ObjectManager from authentik.managed.manager import ObjectManager
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.assertion import AssertionProcessor from authentik.providers.saml.processors.assertion import AssertionProcessor
from authentik.providers.saml.processors.request_parser import AuthNRequestParser from authentik.providers.saml.processors.request_parser import AuthNRequestParser
from authentik.providers.saml.tests.test_auth_n_request import dummy_get_response
from authentik.sources.saml.models import SAMLSource from authentik.sources.saml.models import SAMLSource
from authentik.sources.saml.processors.request import RequestProcessor from authentik.sources.saml.processors.request import RequestProcessor
@ -43,11 +41,7 @@ class TestSchema(TestCase):
def test_request_schema(self): def test_request_schema(self):
"""Test generated AuthNRequest against Schema""" """Test generated AuthNRequest against Schema"""
http_request = self.factory.get("/") http_request = get_request("/")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
# First create an AuthNRequest # First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state") request_proc = RequestProcessor(self.source, http_request, "test_state")
@ -60,12 +54,7 @@ class TestSchema(TestCase):
def test_response_schema(self): def test_response_schema(self):
"""Test generated AuthNRequest against Schema""" """Test generated AuthNRequest against Schema"""
http_request = self.factory.get("/") http_request = get_request("/")
http_request.user = get_anonymous_user()
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
# First create an AuthNRequest # First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state") request_proc = RequestProcessor(self.source, http_request, "test_state")

View File

@ -7,7 +7,7 @@ from django.utils.translation import gettext as _
from django.views import View from django.views import View
from authentik.core.models import Token, TokenIntents from authentik.core.models import Token, TokenIntents
from authentik.stages.password import BACKEND_DJANGO from authentik.stages.password import BACKEND_INBUILT
class UseTokenView(View): class UseTokenView(View):
@ -19,7 +19,7 @@ class UseTokenView(View):
if not tokens.exists(): if not tokens.exists():
raise Http404 raise Http404
token = tokens.first() token = tokens.first()
login(request, token.user, backend=BACKEND_DJANGO) login(request, token.user, backend=BACKEND_INBUILT)
token.delete() token.delete()
messages.warning(request, _("Used recovery-link to authenticate.")) messages.warning(request, _("Used recovery-link to authenticate."))
return redirect("authentik_core:if-admin") return redirect("authentik_core:if-admin")

View File

@ -73,7 +73,8 @@ LANGUAGE_COOKIE_NAME = f"authentik_language{_cookie_suffix}"
SESSION_COOKIE_NAME = f"authentik_session{_cookie_suffix}" SESSION_COOKIE_NAME = f"authentik_session{_cookie_suffix}"
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend", "authentik.core.auth.InbuiltBackend",
"authentik.core.auth.TokenBackend",
"guardian.backends.ObjectPermissionBackend", "guardian.backends.ObjectPermissionBackend",
] ]

View File

@ -2,10 +2,10 @@
from typing import Optional from typing import Optional
import ldap3 import ldap3
from django.contrib.auth.backends import ModelBackend
from django.http import HttpRequest from django.http import HttpRequest
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.auth import InbuiltBackend
from authentik.core.models import User from authentik.core.models import User
from authentik.sources.ldap.models import LDAPSource from authentik.sources.ldap.models import LDAPSource
@ -13,7 +13,7 @@ LOGGER = get_logger()
LDAP_DISTINGUISHED_NAME = "distinguishedName" LDAP_DISTINGUISHED_NAME = "distinguishedName"
class LDAPBackend(ModelBackend): class LDAPBackend(InbuiltBackend):
"""Authenticate users against LDAP Server""" """Authenticate users against LDAP Server"""
def authenticate(self, request: HttpRequest, **kwargs): def authenticate(self, request: HttpRequest, **kwargs):
@ -24,6 +24,7 @@ class LDAPBackend(ModelBackend):
LOGGER.debug("LDAP Auth attempt", source=source) LOGGER.debug("LDAP Auth attempt", source=source)
user = self.auth_user(source, **kwargs) user = self.auth_user(source, **kwargs)
if user: if user:
self.set_method("ldap", request, source=source)
return user return user
return None return None

View File

@ -1,10 +1,6 @@
"""LDAP Settings""" """LDAP Settings"""
from celery.schedules import crontab from celery.schedules import crontab
AUTHENTICATION_BACKENDS = [
"authentik.sources.ldap.auth.LDAPBackend",
]
CELERY_BEAT_SCHEDULE = { CELERY_BEAT_SCHEDULE = {
"sources_ldap_sync": { "sources_ldap_sync": {
"task": "authentik.sources.ldap.tasks.ldap_sync_all", "task": "authentik.sources.ldap.tasks.ldap_sync_all",

View File

@ -39,7 +39,7 @@ from authentik.sources.saml.processors.constants import (
from authentik.sources.saml.processors.request import SESSION_REQUEST_ID from authentik.sources.saml.processors.request import SESSION_REQUEST_ID
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
from authentik.stages.user_login.stage import BACKEND_DJANGO from authentik.stages.user_login.stage import BACKEND_INBUILT
LOGGER = get_logger() LOGGER = get_logger()
if TYPE_CHECKING: if TYPE_CHECKING:
@ -136,7 +136,7 @@ class ResponseProcessor:
self._source.authentication_flow, self._source.authentication_flow,
**{ **{
PLAN_CONTEXT_PENDING_USER: user, PLAN_CONTEXT_PENDING_USER: user,
PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_DJANGO, PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_INBUILT,
}, },
) )
@ -199,7 +199,7 @@ class ResponseProcessor:
self._source.authentication_flow, self._source.authentication_flow,
**{ **{
PLAN_CONTEXT_PENDING_USER: matching_users.first(), PLAN_CONTEXT_PENDING_USER: matching_users.first(),
PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_DJANGO, PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_INBUILT,
PLAN_CONTEXT_REDIRECT: final_redirect, PLAN_CONTEXT_REDIRECT: final_redirect,
}, },
) )

View File

@ -1,7 +1,6 @@
"""Test validator stage""" """Test validator stage"""
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
from django.urls.base import reverse from django.urls.base import reverse
@ -12,8 +11,8 @@ from rest_framework.exceptions import ValidationError
from authentik.core.models import User from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow, FlowStageBinding, NotConfiguredAction from authentik.flows.models import Flow, FlowStageBinding, NotConfiguredAction
from authentik.flows.tests.test_planner import dummy_get_response
from authentik.lib.generators import generate_id, generate_key from authentik.lib.generators import generate_id, generate_key
from authentik.lib.tests.utils import get_request
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
from authentik.stages.authenticator_validate.api import AuthenticatorValidateStageSerializer from authentik.stages.authenticator_validate.api import AuthenticatorValidateStageSerializer
from authentik.stages.authenticator_validate.challenge import ( from authentik.stages.authenticator_validate.challenge import (
@ -97,11 +96,8 @@ class AuthenticatorValidateStageTests(TestCase):
def test_device_challenge_webauthn(self): def test_device_challenge_webauthn(self):
"""Test webauthn""" """Test webauthn"""
request = self.request_factory.get("/") request = get_request("/")
request.user = self.user request.user = self.user
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
webauthn_device = WebAuthnDevice.objects.create( webauthn_device = WebAuthnDevice.objects.create(
user=self.user, user=self.user,

View File

@ -9,7 +9,7 @@ from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.lib.generators import generate_key from authentik.lib.generators import generate_key
from authentik.sources.oauth.models import OAuthSource from authentik.sources.oauth.models import OAuthSource
from authentik.stages.identification.models import IdentificationStage, UserFields from authentik.stages.identification.models import IdentificationStage, UserFields
from authentik.stages.password import BACKEND_DJANGO from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.models import PasswordStage from authentik.stages.password.models import PasswordStage
@ -68,7 +68,7 @@ class TestIdentificationStage(TestCase):
def test_valid_with_password(self): def test_valid_with_password(self):
"""Test with valid email and password in single step""" """Test with valid email and password in single step"""
pw_stage = PasswordStage.objects.create(name="password", backends=[BACKEND_DJANGO]) pw_stage = PasswordStage.objects.create(name="password", backends=[BACKEND_INBUILT])
self.stage.password_stage = pw_stage self.stage.password_stage = pw_stage
self.stage.save() self.stage.save()
form_data = {"uid_field": self.user.email, "password": self.password} form_data = {"uid_field": self.user.email, "password": self.password}
@ -86,7 +86,7 @@ class TestIdentificationStage(TestCase):
def test_invalid_with_password(self): def test_invalid_with_password(self):
"""Test with valid email and invalid password in single step""" """Test with valid email and invalid password in single step"""
pw_stage = PasswordStage.objects.create(name="password", backends=[BACKEND_DJANGO]) pw_stage = PasswordStage.objects.create(name="password", backends=[BACKEND_INBUILT])
self.stage.password_stage = pw_stage self.stage.password_stage = pw_stage
self.stage.save() self.stage.save()
form_data = { form_data = {

View File

@ -17,7 +17,7 @@ from authentik.flows.tests.test_views import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views import SESSION_KEY_PLAN from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.invitation.models import Invitation, InvitationStage from authentik.stages.invitation.models import Invitation, InvitationStage
from authentik.stages.invitation.stage import INVITATION_TOKEN_KEY, PLAN_CONTEXT_PROMPT from authentik.stages.invitation.stage import INVITATION_TOKEN_KEY, PLAN_CONTEXT_PROMPT
from authentik.stages.password import BACKEND_DJANGO from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
@ -45,7 +45,7 @@ class TestUserLoginStage(TestCase):
"""Test without any invitation, continue_flow_without_invitation not set.""" """Test without any invitation, continue_flow_without_invitation not set."""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_INBUILT
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
session.save() session.save()
@ -74,7 +74,7 @@ class TestUserLoginStage(TestCase):
self.stage.save() self.stage.save()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_INBUILT
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
session.save() session.save()

View File

@ -1,3 +1,4 @@
"""Backend paths""" """Backend paths"""
BACKEND_DJANGO = "django.contrib.auth.backends.ModelBackend" BACKEND_INBUILT = "authentik.core.auth.InbuiltBackend"
BACKEND_LDAP = "authentik.sources.ldap.auth.LDAPBackend" BACKEND_LDAP = "authentik.sources.ldap.auth.LDAPBackend"
BACKEND_APP_PASSWORD = "authentik.core.auth.TokenBackend" # nosec

View File

@ -0,0 +1,60 @@
# Generated by Django 3.2.6 on 2021-08-23 14:34
import django.contrib.postgres.fields
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from authentik.stages.password import BACKEND_APP_PASSWORD, BACKEND_INBUILT
def update_default_backends(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
PasswordStage = apps.get_model("authentik_stages_password", "passwordstage")
db_alias = schema_editor.connection.alias
stages = PasswordStage.objects.using(db_alias).filter(name="default-authentication-password")
if not stages.exists():
return
stage = stages.first()
stage.backends.append(BACKEND_APP_PASSWORD)
stage.save()
def replace_inbuilt(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
PasswordStage = apps.get_model("authentik_stages_password", "passwordstage")
db_alias = schema_editor.connection.alias
for stage in PasswordStage.objects.using(db_alias).all():
if "django.contrib.auth.backends.ModelBackend" not in stage.backends:
continue
stage.backends.remove("django.contrib.auth.backends.ModelBackend")
stage.backends.append(BACKEND_INBUILT)
stage.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_password", "0006_passwordchange_rename"),
]
operations = [
migrations.AlterField(
model_name="passwordstage",
name="backends",
field=django.contrib.postgres.fields.ArrayField(
base_field=models.TextField(
choices=[
("authentik.core.auth.InbuiltBackend", "User database + standard password"),
("authentik.core.auth.TokenBackend", "User database + app passwords"),
(
"authentik.sources.ldap.auth.LDAPBackend",
"User database + LDAP password",
),
]
),
help_text="Selection of backends to test the password against.",
size=None,
),
),
migrations.RunPython(update_default_backends),
]

View File

@ -9,19 +9,23 @@ from rest_framework.serializers import BaseSerializer
from authentik.core.types import UserSettingSerializer from authentik.core.types import UserSettingSerializer
from authentik.flows.models import ConfigurableStage, Stage from authentik.flows.models import ConfigurableStage, Stage
from authentik.stages.password import BACKEND_DJANGO, BACKEND_LDAP from authentik.stages.password import BACKEND_APP_PASSWORD, BACKEND_INBUILT, BACKEND_LDAP
def get_authentication_backends(): def get_authentication_backends():
"""Return all available authentication backends as tuple set""" """Return all available authentication backends as tuple set"""
return [ return [
( (
BACKEND_DJANGO, BACKEND_INBUILT,
_("authentik-internal Userdatabase"), _("User database + standard password"),
),
(
BACKEND_APP_PASSWORD,
_("User database + app passwords"),
), ),
( (
BACKEND_LDAP, BACKEND_LDAP,
_("authentik LDAP"), _("User database + LDAP password"),
), ),
] ]

View File

@ -27,6 +27,8 @@ from authentik.stages.password.models import PasswordStage
LOGGER = get_logger() LOGGER = get_logger()
PLAN_CONTEXT_AUTHENTICATION_BACKEND = "user_backend" PLAN_CONTEXT_AUTHENTICATION_BACKEND = "user_backend"
PLAN_CONTEXT_METHOD = "auth_method"
PLAN_CONTEXT_METHOD_ARGS = "auth_method_args"
SESSION_INVALID_TRIES = "user_invalid_tries" SESSION_INVALID_TRIES = "user_invalid_tries"

View File

@ -14,7 +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.tests.test_views import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views import SESSION_KEY_PLAN from authentik.flows.views import SESSION_KEY_PLAN
from authentik.lib.generators import generate_key from authentik.lib.generators import generate_key
from authentik.stages.password import BACKEND_DJANGO from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.models import PasswordStage from authentik.stages.password.models import PasswordStage
MOCK_BACKEND_AUTHENTICATE = MagicMock(side_effect=PermissionDenied("test")) MOCK_BACKEND_AUTHENTICATE = MagicMock(side_effect=PermissionDenied("test"))
@ -36,7 +36,7 @@ class TestPasswordStage(TestCase):
slug="test-password", slug="test-password",
designation=FlowDesignation.AUTHENTICATION, designation=FlowDesignation.AUTHENTICATION,
) )
self.stage = PasswordStage.objects.create(name="password", backends=[BACKEND_DJANGO]) self.stage = PasswordStage.objects.create(name="password", backends=[BACKEND_INBUILT])
self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2)
@patch( @patch(
@ -158,7 +158,7 @@ class TestPasswordStage(TestCase):
TO_STAGE_RESPONSE_MOCK, TO_STAGE_RESPONSE_MOCK,
) )
@patch( @patch(
"django.contrib.auth.backends.ModelBackend.authenticate", "authentik.core.auth.InbuiltBackend.authenticate",
MOCK_BACKEND_AUTHENTICATE, MOCK_BACKEND_AUTHENTICATE,
) )
def test_permission_denied(self): def test_permission_denied(self):

View File

@ -8,7 +8,7 @@ from structlog.stdlib import get_logger
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import StageView from authentik.flows.stage import StageView
from authentik.lib.utils.time import timedelta_from_string from authentik.lib.utils.time import timedelta_from_string
from authentik.stages.password import BACKEND_DJANGO from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
LOGGER = get_logger() LOGGER = get_logger()
@ -26,7 +26,7 @@ class UserLoginStageView(StageView):
LOGGER.debug(message) LOGGER.debug(message)
return self.executor.stage_invalid() return self.executor.stage_invalid()
backend = self.executor.plan.context.get( backend = self.executor.plan.context.get(
PLAN_CONTEXT_AUTHENTICATION_BACKEND, BACKEND_DJANGO PLAN_CONTEXT_AUTHENTICATION_BACKEND, BACKEND_INBUILT
) )
login( login(
self.request, self.request,

View File

@ -9,7 +9,7 @@ from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.views import SESSION_KEY_PLAN from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.password import BACKEND_DJANGO from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from authentik.stages.user_logout.models import UserLogoutStage from authentik.stages.user_logout.models import UserLogoutStage
@ -34,7 +34,7 @@ class TestUserLogoutStage(TestCase):
"""Test with a valid pending user and backend""" """Test with a valid pending user and backend"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_INBUILT
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
session.save() session.save()

View File

@ -1,7 +1,6 @@
"""Write stage logic""" """Write stage logic"""
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import update_session_auth_hash from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.backends import ModelBackend
from django.db import transaction from django.db import transaction
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
@ -13,7 +12,7 @@ from authentik.core.models import USER_ATTRIBUTE_SOURCES, User, UserSourceConnec
from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import StageView from authentik.flows.stage import StageView
from authentik.lib.utils.reflection import class_to_path from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
from authentik.stages.user_write.signals import user_write from authentik.stages.user_write.signals import user_write
@ -42,9 +41,7 @@ class UserWriteStageView(StageView):
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User( self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User(
is_active=not self.executor.current_stage.create_users_as_inactive is_active=not self.executor.current_stage.create_users_as_inactive
) )
self.executor.plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = class_to_path( self.executor.plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_INBUILT
ModelBackend
)
LOGGER.debug( LOGGER.debug(
"Created new user", "Created new user",
flow_slug=self.executor.flow.slug, flow_slug=self.executor.flow.slug,

View File

@ -2515,6 +2515,7 @@ paths:
type: string type: string
enum: enum:
- api - api
- app_password
- recovery - recovery
- verification - verification
- name: ordering - name: ordering
@ -20381,7 +20382,8 @@ components:
- url - url
BackendsEnum: BackendsEnum:
enum: enum:
- django.contrib.auth.backends.ModelBackend - authentik.core.auth.InbuiltBackend
- authentik.core.auth.TokenBackend
- authentik.sources.ldap.auth.LDAPBackend - authentik.sources.ldap.auth.LDAPBackend
type: string type: string
BindingTypeEnum: BindingTypeEnum:
@ -22211,6 +22213,7 @@ components:
- verification - verification
- api - api
- recovery - recovery
- app_password
type: string type: string
InvalidResponseActionEnum: InvalidResponseActionEnum:
enum: enum:

View File

@ -3,7 +3,7 @@ version: '3.7'
services: services:
postgresql: postgresql:
container_name: postgres container_name: postgres
image: library/postgres:11 image: library/postgres:12
volumes: volumes:
- db-data:/var/lib/postgresql/data - db-data:/var/lib/postgresql/data
environment: environment:

View File

@ -3,7 +3,7 @@ version: '3.7'
services: services:
postgresql: postgresql:
container_name: postgres container_name: postgres
image: library/postgres:11 image: library/postgres:12
volumes: volumes:
- db-data:/var/lib/postgresql/data - db-data:/var/lib/postgresql/data
environment: environment:

View File

@ -4,5 +4,6 @@ from yaml import safe_dump
with open("local.env.yml", "w") as _config: with open("local.env.yml", "w") as _config:
safe_dump({ safe_dump({
"secret_key": generate_id() "log_level": "debug",
"secret_key": generate_id(),
}, _config, default_flow_style=False) }, _config, default_flow_style=False)

View File

@ -90,6 +90,7 @@ export default [
// Main Application // Main Application
{ {
input: "./src/interfaces/AdminInterface.ts", input: "./src/interfaces/AdminInterface.ts",
context: "window",
output: [ output: [
{ {
format: "es", format: "es",
@ -122,6 +123,7 @@ export default [
// Flow executor // Flow executor
{ {
input: "./src/interfaces/FlowInterface.ts", input: "./src/interfaces/FlowInterface.ts",
context: "window",
output: [ output: [
{ {
format: "es", format: "es",

View File

@ -63,6 +63,10 @@ msgstr "ANY, any policy must match to grant access."
msgid "ANY, any policy must match to include this stage access." msgid "ANY, any policy must match to include this stage access."
msgstr "ANY, any policy must match to include this stage access." msgstr "ANY, any policy must match to include this stage access."
#: src/pages/tokens/TokenListPage.ts
msgid "API Access"
msgstr "API Access"
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts #: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
msgid "API Hostname" msgid "API Hostname"
msgstr "API Hostname" msgstr "API Hostname"
@ -231,6 +235,10 @@ msgstr "Always require consent"
msgid "App" msgid "App"
msgstr "App" msgstr "App"
#: src/pages/tokens/TokenListPage.ts
msgid "App password"
msgstr "App password"
#: src/elements/user/UserConsentList.ts #: src/elements/user/UserConsentList.ts
#: src/pages/admin-overview/TopApplicationsTable.ts #: src/pages/admin-overview/TopApplicationsTable.ts
#: src/pages/providers/ProviderListPage.ts #: src/pages/providers/ProviderListPage.ts
@ -952,6 +960,11 @@ msgstr "Copy recovery link"
msgid "Create" msgid "Create"
msgstr "Create" msgstr "Create"
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
msgid "Create App password"
msgstr "Create App password"
#: src/pages/applications/ApplicationListPage.ts #: src/pages/applications/ApplicationListPage.ts
#: src/pages/providers/RelatedApplicationButton.ts #: src/pages/providers/RelatedApplicationButton.ts
msgid "Create Application" msgid "Create Application"
@ -1018,6 +1031,7 @@ msgstr "Create Stage binding"
msgid "Create Tenant" msgid "Create Tenant"
msgstr "Create Tenant" msgstr "Create Tenant"
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/user-settings/tokens/UserTokenList.ts #: src/pages/user-settings/tokens/UserTokenList.ts
msgid "Create Token" msgid "Create Token"
msgstr "Create Token" msgstr "Create Token"
@ -2047,6 +2061,11 @@ msgstr "Integration key"
msgid "Integrations" msgid "Integrations"
msgstr "Integrations" msgstr "Integrations"
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
msgid "Intent"
msgstr "Intent"
#: src/pages/providers/proxy/ProxyProviderViewPage.ts #: src/pages/providers/proxy/ProxyProviderViewPage.ts
msgid "Internal Host" msgid "Internal Host"
msgstr "Internal Host" msgstr "Internal Host"
@ -2807,6 +2826,7 @@ msgstr "Optionally set this to your parent domain, if you want authentication an
#: src/pages/flows/BoundStagesList.ts #: src/pages/flows/BoundStagesList.ts
#: src/pages/flows/StageBindingForm.ts #: src/pages/flows/StageBindingForm.ts
#: src/pages/policies/BoundPoliciesList.ts #: src/pages/policies/BoundPoliciesList.ts
#: src/pages/policies/BoundPoliciesList.ts
#: src/pages/policies/PolicyBindingForm.ts #: src/pages/policies/PolicyBindingForm.ts
#: src/pages/stages/prompt/PromptForm.ts #: src/pages/stages/prompt/PromptForm.ts
#: src/pages/stages/prompt/PromptListPage.ts #: src/pages/stages/prompt/PromptListPage.ts
@ -2954,6 +2974,7 @@ msgstr "Policy / Group / User Bindings"
msgid "Policy / Policies" msgid "Policy / Policies"
msgstr "Policy / Policies" msgstr "Policy / Policies"
#: src/pages/policies/BoundPoliciesList.ts
#: src/pages/policies/BoundPoliciesList.ts #: src/pages/policies/BoundPoliciesList.ts
msgid "Policy / User / Group" msgid "Policy / User / Group"
msgstr "Policy / User / Group" msgstr "Policy / User / Group"
@ -3203,6 +3224,7 @@ msgid "Receive a push notification on your phone to prove your identity."
msgstr "Receive a push notification on your phone to prove your identity." msgstr "Receive a push notification on your phone to prove your identity."
#: src/pages/flows/FlowForm.ts #: src/pages/flows/FlowForm.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/users/UserListPage.ts #: src/pages/users/UserListPage.ts
msgid "Recovery" msgid "Recovery"
msgstr "Recovery" msgstr "Recovery"
@ -3696,6 +3718,7 @@ msgstr "Sources"
msgid "Sources of identities, which can either be synced into authentik's database, like LDAP, or can be used by users to authenticate and enroll themselves, like OAuth and social logins" msgid "Sources of identities, which can either be synced into authentik's database, like LDAP, or can be used by users to authenticate and enroll themselves, like OAuth and social logins"
msgstr "Sources of identities, which can either be synced into authentik's database, like LDAP, or can be used by users to authenticate and enroll themselves, like OAuth and social logins" msgstr "Sources of identities, which can either be synced into authentik's database, like LDAP, or can be used by users to authenticate and enroll themselves, like OAuth and social logins"
#: src/pages/flows/BoundStagesList.ts
#: src/pages/flows/StageBindingForm.ts #: src/pages/flows/StageBindingForm.ts
msgid "Stage" msgid "Stage"
msgstr "Stage" msgstr "Stage"
@ -3716,6 +3739,10 @@ msgstr "Stage Configuration"
msgid "Stage binding(s)" msgid "Stage binding(s)"
msgstr "Stage binding(s)" msgstr "Stage binding(s)"
#: src/pages/flows/BoundStagesList.ts
msgid "Stage type"
msgstr "Stage type"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." msgstr "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
@ -4388,10 +4415,13 @@ msgstr "Token(s)"
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts #: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
#: src/interfaces/AdminInterface.ts #: src/interfaces/AdminInterface.ts
#: src/pages/tokens/TokenListPage.ts #: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/UserSettingsPage.ts
msgid "Tokens" msgid "Tokens"
msgstr "Tokens" msgstr "Tokens"
#: src/pages/user-settings/UserSettingsPage.ts
msgid "Tokens and App passwords"
msgstr "Tokens and App passwords"
#: src/pages/tokens/TokenListPage.ts #: src/pages/tokens/TokenListPage.ts
msgid "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access." msgid "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access."
msgstr "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access." msgstr "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access."
@ -4740,6 +4770,18 @@ msgstr "User Reputation"
msgid "User Settings" msgid "User Settings"
msgstr "User Settings" msgstr "User Settings"
#: src/pages/stages/password/PasswordStageForm.ts
msgid "User database + LDAP password"
msgstr "User database + LDAP password"
#: src/pages/stages/password/PasswordStageForm.ts
msgid "User database + app passwords"
msgstr "User database + app passwords"
#: src/pages/stages/password/PasswordStageForm.ts
msgid "User database + standard password"
msgstr "User database + standard password"
#: src/pages/user-settings/UserSettingsPage.ts #: src/pages/user-settings/UserSettingsPage.ts
msgid "User details" msgid "User details"
msgstr "User details" msgstr "User details"
@ -4859,6 +4901,10 @@ msgstr "Validation Policies"
msgid "Validity days" msgid "Validity days"
msgstr "Validity days" msgstr "Validity days"
#: src/pages/tokens/TokenListPage.ts
msgid "Verification"
msgstr "Verification"
#: src/pages/providers/saml/SAMLProviderForm.ts #: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Verification Certificate" msgid "Verification Certificate"
msgstr "Verification Certificate" msgstr "Verification Certificate"
@ -5022,13 +5068,13 @@ msgstr "You can only select providers that match the type of the outpost."
msgid "You're currently impersonating {0}. Click to stop." msgid "You're currently impersonating {0}. Click to stop."
msgstr "You're currently impersonating {0}. Click to stop." msgstr "You're currently impersonating {0}. Click to stop."
#: src/pages/stages/password/PasswordStageForm.ts #:
msgid "authentik Builtin Database" #~ msgid "authentik Builtin Database"
msgstr "authentik Builtin Database" #~ msgstr "authentik Builtin Database"
#: src/pages/stages/password/PasswordStageForm.ts #:
msgid "authentik LDAP Backend" #~ msgid "authentik LDAP Backend"
msgstr "authentik LDAP Backend" #~ msgstr "authentik LDAP Backend"
#: src/elements/forms/DeleteForm.ts #: src/elements/forms/DeleteForm.ts
msgid "connecting object will be deleted" msgid "connecting object will be deleted"

View File

@ -63,6 +63,10 @@ msgstr ""
msgid "ANY, any policy must match to include this stage access." msgid "ANY, any policy must match to include this stage access."
msgstr "" msgstr ""
#: src/pages/tokens/TokenListPage.ts
msgid "API Access"
msgstr ""
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts #: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
msgid "API Hostname" msgid "API Hostname"
msgstr "" msgstr ""
@ -231,6 +235,10 @@ msgstr ""
msgid "App" msgid "App"
msgstr "" msgstr ""
#: src/pages/tokens/TokenListPage.ts
msgid "App password"
msgstr ""
#: src/elements/user/UserConsentList.ts #: src/elements/user/UserConsentList.ts
#: src/pages/admin-overview/TopApplicationsTable.ts #: src/pages/admin-overview/TopApplicationsTable.ts
#: src/pages/providers/ProviderListPage.ts #: src/pages/providers/ProviderListPage.ts
@ -946,6 +954,11 @@ msgstr ""
msgid "Create" msgid "Create"
msgstr "" msgstr ""
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
msgid "Create App password"
msgstr ""
#: src/pages/applications/ApplicationListPage.ts #: src/pages/applications/ApplicationListPage.ts
#: src/pages/providers/RelatedApplicationButton.ts #: src/pages/providers/RelatedApplicationButton.ts
msgid "Create Application" msgid "Create Application"
@ -1012,6 +1025,7 @@ msgstr ""
msgid "Create Tenant" msgid "Create Tenant"
msgstr "" msgstr ""
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/user-settings/tokens/UserTokenList.ts #: src/pages/user-settings/tokens/UserTokenList.ts
msgid "Create Token" msgid "Create Token"
msgstr "" msgstr ""
@ -2039,6 +2053,11 @@ msgstr ""
msgid "Integrations" msgid "Integrations"
msgstr "" msgstr ""
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
msgid "Intent"
msgstr ""
#: src/pages/providers/proxy/ProxyProviderViewPage.ts #: src/pages/providers/proxy/ProxyProviderViewPage.ts
msgid "Internal Host" msgid "Internal Host"
msgstr "" msgstr ""
@ -2799,6 +2818,7 @@ msgstr ""
#: src/pages/flows/BoundStagesList.ts #: src/pages/flows/BoundStagesList.ts
#: src/pages/flows/StageBindingForm.ts #: src/pages/flows/StageBindingForm.ts
#: src/pages/policies/BoundPoliciesList.ts #: src/pages/policies/BoundPoliciesList.ts
#: src/pages/policies/BoundPoliciesList.ts
#: src/pages/policies/PolicyBindingForm.ts #: src/pages/policies/PolicyBindingForm.ts
#: src/pages/stages/prompt/PromptForm.ts #: src/pages/stages/prompt/PromptForm.ts
#: src/pages/stages/prompt/PromptListPage.ts #: src/pages/stages/prompt/PromptListPage.ts
@ -2946,6 +2966,7 @@ msgstr ""
msgid "Policy / Policies" msgid "Policy / Policies"
msgstr "" msgstr ""
#: src/pages/policies/BoundPoliciesList.ts
#: src/pages/policies/BoundPoliciesList.ts #: src/pages/policies/BoundPoliciesList.ts
msgid "Policy / User / Group" msgid "Policy / User / Group"
msgstr "" msgstr ""
@ -3195,6 +3216,7 @@ msgid "Receive a push notification on your phone to prove your identity."
msgstr "" msgstr ""
#: src/pages/flows/FlowForm.ts #: src/pages/flows/FlowForm.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/users/UserListPage.ts #: src/pages/users/UserListPage.ts
msgid "Recovery" msgid "Recovery"
msgstr "" msgstr ""
@ -3688,6 +3710,7 @@ msgstr ""
msgid "Sources of identities, which can either be synced into authentik's database, like LDAP, or can be used by users to authenticate and enroll themselves, like OAuth and social logins" msgid "Sources of identities, which can either be synced into authentik's database, like LDAP, or can be used by users to authenticate and enroll themselves, like OAuth and social logins"
msgstr "" msgstr ""
#: src/pages/flows/BoundStagesList.ts
#: src/pages/flows/StageBindingForm.ts #: src/pages/flows/StageBindingForm.ts
msgid "Stage" msgid "Stage"
msgstr "" msgstr ""
@ -3708,6 +3731,10 @@ msgstr ""
msgid "Stage binding(s)" msgid "Stage binding(s)"
msgstr "" msgstr ""
#: src/pages/flows/BoundStagesList.ts
msgid "Stage type"
msgstr ""
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again." msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr "" msgstr ""
@ -4373,10 +4400,13 @@ msgstr ""
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts #: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
#: src/interfaces/AdminInterface.ts #: src/interfaces/AdminInterface.ts
#: src/pages/tokens/TokenListPage.ts #: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/UserSettingsPage.ts
msgid "Tokens" msgid "Tokens"
msgstr "" msgstr ""
#: src/pages/user-settings/UserSettingsPage.ts
msgid "Tokens and App passwords"
msgstr ""
#: src/pages/tokens/TokenListPage.ts #: src/pages/tokens/TokenListPage.ts
msgid "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access." msgid "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access."
msgstr "" msgstr ""
@ -4725,6 +4755,18 @@ msgstr ""
msgid "User Settings" msgid "User Settings"
msgstr "" msgstr ""
#: src/pages/stages/password/PasswordStageForm.ts
msgid "User database + LDAP password"
msgstr ""
#: src/pages/stages/password/PasswordStageForm.ts
msgid "User database + app passwords"
msgstr ""
#: src/pages/stages/password/PasswordStageForm.ts
msgid "User database + standard password"
msgstr ""
#: src/pages/user-settings/UserSettingsPage.ts #: src/pages/user-settings/UserSettingsPage.ts
msgid "User details" msgid "User details"
msgstr "" msgstr ""
@ -4844,6 +4886,10 @@ msgstr ""
msgid "Validity days" msgid "Validity days"
msgstr "" msgstr ""
#: src/pages/tokens/TokenListPage.ts
msgid "Verification"
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts #: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Verification Certificate" msgid "Verification Certificate"
msgstr "" msgstr ""
@ -5005,13 +5051,13 @@ msgstr ""
msgid "You're currently impersonating {0}. Click to stop." msgid "You're currently impersonating {0}. Click to stop."
msgstr "" msgstr ""
#: src/pages/stages/password/PasswordStageForm.ts #:
msgid "authentik Builtin Database" #~ msgid "authentik Builtin Database"
msgstr "" #~ msgstr ""
#: src/pages/stages/password/PasswordStageForm.ts #:
msgid "authentik LDAP Backend" #~ msgid "authentik LDAP Backend"
msgstr "" #~ msgstr ""
#: src/elements/forms/DeleteForm.ts #: src/elements/forms/DeleteForm.ts
msgid "connecting object will be deleted" msgid "connecting object will be deleted"

View File

@ -48,6 +48,12 @@ export class BoundStagesList extends Table<FlowStageBinding> {
return html`<ak-forms-delete-bulk return html`<ak-forms-delete-bulk
objectLabel=${t`Stage binding(s)`} objectLabel=${t`Stage binding(s)`}
.objects=${this.selectedElements} .objects=${this.selectedElements}
.metadata=${(item: FlowStageBinding) => {
return [
{ key: t`Stage`, value: item.stageObj?.name },
{ key: t`Stage type`, value: item.stageObj?.verboseName },
];
}}
.usedBy=${(item: FlowStageBinding) => { .usedBy=${(item: FlowStageBinding) => {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsUsedByList({ return new FlowsApi(DEFAULT_CONFIG).flowsBindingsUsedByList({
fsbUuid: item.pk, fsbUuid: item.pk,

View File

@ -100,6 +100,12 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
return html`<ak-forms-delete-bulk return html`<ak-forms-delete-bulk
objectLabel=${t`Policy binding(s)`} objectLabel=${t`Policy binding(s)`}
.objects=${this.selectedElements} .objects=${this.selectedElements}
.metadata=${(item: PolicyBinding) => {
return [
{ key: t`Order`, value: item.order.toString() },
{ key: t`Policy / User / Group`, value: this.getPolicyUserGroupRow(item) },
];
}}
.usedBy=${(item: PolicyBinding) => { .usedBy=${(item: PolicyBinding) => {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsUsedByList({ return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsUsedByList({
policyBindingUuid: item.pk, policyBindingUuid: item.pk,

View File

@ -156,11 +156,20 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri
.sourcesAllList({}) .sourcesAllList({})
.then((sources) => { .then((sources) => {
return sources.results.map((source) => { return sources.results.map((source) => {
const selected = Array.from( let selected = Array.from(
this.instance?.sources || [], this.instance?.sources || [],
).some((su) => { ).some((su) => {
return su == source.pk; return su == source.pk;
}); });
// Creating a new instance, auto-select built-in source
// Only when no other sources exist
if (
!this.instance &&
source.component === "" &&
sources.results.length < 2
) {
selected = true;
}
return html`<option return html`<option
value=${ifDefined(source.pk)} value=${ifDefined(source.pk)}
?selected=${selected} ?selected=${selected}

View File

@ -46,8 +46,11 @@ export class PasswordStageForm extends ModelForm<PasswordStage, string> {
}; };
isBackendSelected(field: BackendsEnum): boolean { isBackendSelected(field: BackendsEnum): boolean {
if (!this.instance) {
return true;
}
return ( return (
(this.instance?.backends || []).filter((isField) => { this.instance.backends.filter((isField) => {
return field === isField; return field === isField;
}).length > 0 }).length > 0
); );
@ -76,20 +79,28 @@ export class PasswordStageForm extends ModelForm<PasswordStage, string> {
> >
<select name="users" class="pf-c-form-control" multiple> <select name="users" class="pf-c-form-control" multiple>
<option <option
value=${BackendsEnum.DjangoContribAuthBackendsModelBackend} value=${BackendsEnum.CoreAuthInbuiltBackend}
?selected=${this.isBackendSelected( ?selected=${this.isBackendSelected(
BackendsEnum.DjangoContribAuthBackendsModelBackend, BackendsEnum.CoreAuthInbuiltBackend,
)} )}
> >
${t`authentik Builtin Database`} ${t`User database + standard password`}
</option> </option>
<option <option
value=${BackendsEnum.AuthentikSourcesLdapAuthLdapBackend} value=${BackendsEnum.CoreAuthTokenBackend}
?selected=${this.isBackendSelected( ?selected=${this.isBackendSelected(
BackendsEnum.AuthentikSourcesLdapAuthLdapBackend, BackendsEnum.CoreAuthTokenBackend,
)} )}
> >
${t`authentik LDAP Backend`} ${t`User database + app passwords`}
</option>
<option
value=${BackendsEnum.SourcesLdapAuthLdapBackend}
?selected=${this.isBackendSelected(
BackendsEnum.SourcesLdapAuthLdapBackend,
)}
>
${t`User database + LDAP password`}
</option> </option>
</select> </select>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">

View File

@ -8,9 +8,22 @@ import "../../elements/buttons/TokenCopyButton";
import "../../elements/forms/DeleteBulkForm"; import "../../elements/forms/DeleteBulkForm";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { PAGE_SIZE } from "../../constants"; import { PAGE_SIZE } from "../../constants";
import { CoreApi, Token } from "@goauthentik/api"; import { CoreApi, IntentEnum, Token } from "@goauthentik/api";
import { DEFAULT_CONFIG } from "../../api/Config"; import { DEFAULT_CONFIG } from "../../api/Config";
export function IntentToLabel(intent: IntentEnum): string {
switch (intent) {
case IntentEnum.Api:
return t`API Access`;
case IntentEnum.AppPassword:
return t`App password`;
case IntentEnum.Recovery:
return t`Recovery`;
case IntentEnum.Verification:
return t`Verification`;
}
}
@customElement("ak-token-list") @customElement("ak-token-list")
export class TokenListPage extends TablePage<Token> { export class TokenListPage extends TablePage<Token> {
searchEnabled(): boolean { searchEnabled(): boolean {
@ -46,6 +59,7 @@ export class TokenListPage extends TablePage<Token> {
new TableColumn(t`User`, "user"), new TableColumn(t`User`, "user"),
new TableColumn(t`Expires?`, "expiring"), new TableColumn(t`Expires?`, "expiring"),
new TableColumn(t`Expiry date`, "expires"), new TableColumn(t`Expiry date`, "expires"),
new TableColumn(t`Intent`, "intent"),
new TableColumn(t`Actions`), new TableColumn(t`Actions`),
]; ];
} }
@ -78,6 +92,7 @@ export class TokenListPage extends TablePage<Token> {
html`${item.user?.username}`, html`${item.user?.username}`,
html`${item.expiring ? t`Yes` : t`No`}`, html`${item.expiring ? t`Yes` : t`No`}`,
html`${item.expiring ? item.expires?.toLocaleString() : "-"}`, html`${item.expiring ? item.expires?.toLocaleString() : "-"}`,
html`${IntentToLabel(item.intent || IntentEnum.Api)}`,
html` html`
<ak-token-copy-button identifier="${item.identifier}"> <ak-token-copy-button identifier="${item.identifier}">
${t`Copy Key`} ${t`Copy Key`}

View File

@ -148,7 +148,7 @@ export class UserSettingsPage extends LitElement {
</section> </section>
<section <section
slot="page-tokens" slot="page-tokens"
data-tab-title="${t`Tokens`}" data-tab-title="${t`Tokens and App passwords`}"
class="pf-c-page__main-section pf-m-no-padding-mobile" class="pf-c-page__main-section pf-m-no-padding-mobile"
> >
<ak-user-token-list></ak-user-token-list> <ak-user-token-list></ak-user-token-list>

View File

@ -1,6 +1,6 @@
import { CoreApi, Token } from "@goauthentik/api"; import { CoreApi, IntentEnum, Token } from "@goauthentik/api";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { customElement } from "lit-element"; import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html"; import { html, TemplateResult } from "lit-html";
import { DEFAULT_CONFIG } from "../../../api/Config"; import { DEFAULT_CONFIG } from "../../../api/Config";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
@ -9,6 +9,9 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-user-token-form") @customElement("ak-user-token-form")
export class UserTokenForm extends ModelForm<Token, string> { export class UserTokenForm extends ModelForm<Token, string> {
@property()
intent: IntentEnum = IntentEnum.Api;
loadInstance(pk: string): Promise<Token> { loadInstance(pk: string): Promise<Token> {
return new CoreApi(DEFAULT_CONFIG).coreTokensRetrieve({ return new CoreApi(DEFAULT_CONFIG).coreTokensRetrieve({
identifier: pk, identifier: pk,
@ -30,6 +33,7 @@ export class UserTokenForm extends ModelForm<Token, string> {
tokenRequest: data, tokenRequest: data,
}); });
} else { } else {
data.intent = this.intent;
return new CoreApi(DEFAULT_CONFIG).coreTokensCreate({ return new CoreApi(DEFAULT_CONFIG).coreTokensCreate({
tokenRequest: data, tokenRequest: data,
}); });

View File

@ -10,9 +10,10 @@ import "../../../elements/buttons/Dropdown";
import "../../../elements/buttons/TokenCopyButton"; import "../../../elements/buttons/TokenCopyButton";
import { Table, TableColumn } from "../../../elements/table/Table"; import { Table, TableColumn } from "../../../elements/table/Table";
import { PAGE_SIZE } from "../../../constants"; import { PAGE_SIZE } from "../../../constants";
import { CoreApi, Token } from "@goauthentik/api"; import { CoreApi, IntentEnum, Token } from "@goauthentik/api";
import { DEFAULT_CONFIG } from "../../../api/Config"; import { DEFAULT_CONFIG } from "../../../api/Config";
import "./UserTokenForm"; import "./UserTokenForm";
import { IntentToLabel } from "../../tokens/TokenListPage";
@customElement("ak-user-token-list") @customElement("ak-user-token-list")
export class UserTokenList extends Table<Token> { export class UserTokenList extends Table<Token> {
@ -48,8 +49,19 @@ export class UserTokenList extends Table<Token> {
<ak-forms-modal> <ak-forms-modal>
<span slot="submit"> ${t`Create`} </span> <span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Token`} </span> <span slot="header"> ${t`Create Token`} </span>
<ak-user-token-form slot="form"> </ak-user-token-form> <ak-user-token-form intent=${IntentEnum.Api} slot="form"> </ak-user-token-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${t`Create`}</button> <button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Create Token`}
</button>
</ak-forms-modal>
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create App password`} </span>
<ak-user-token-form intent=${IntentEnum.AppPassword} slot="form">
</ak-user-token-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Create App password`}
</button>
</ak-forms-modal> </ak-forms-modal>
${super.renderToolbar()} ${super.renderToolbar()}
`; `;
@ -89,6 +101,16 @@ export class UserTokenList extends Table<Token> {
</div> </div>
</dd> </dd>
</div> </div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Intent`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${IntentToLabel(item.intent || IntentEnum.Api)}
</div>
</dd>
</div>
</dl> </dl>
</div> </div>
</td> </td>

View File

@ -59,3 +59,31 @@ This includes the following:
- `prompt_data`: Data which has been saved from a prompt stage or an external source. - `prompt_data`: Data which has been saved from a prompt stage or an external source.
- `application`: The application the user is in the process of authorizing. - `application`: The application the user is in the process of authorizing.
- `pending_user`: The currently pending user, see [User](/docs/expressions/reference/user-object) - `pending_user`: The currently pending user, see [User](/docs/expressions/reference/user-object)
- `auth_method`: Authentication method set (this value is set by password stages)
Depending on method, `auth_method_args` is also set.
Can be any of:
- `password`: Standard password login
- `app_password`: App passowrd (token)
Sets `auth_method_args` to
```json
{
"token": {
"pk": "f6d639aac81940f38dcfdc6e0fe2a786",
"app": "authentik_core",
"name": "test (expires=2021-08-23 15:45:54.725880+00:00)",
"model_name": "token"
}
}
```
- `ldap`: LDAP bind authentication
Sets `auth_method_args` to
```json
{
"source": {} // Information about the source used
}
```

View File

@ -133,7 +133,7 @@
"pk": "34e1e7d5-8eed-4549-bc7a-305069ff7df0", "pk": "34e1e7d5-8eed-4549-bc7a-305069ff7df0",
"target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3", "target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3",
"stage": "20375f30-7fa7-4562-8f6e-0f61889f2963", "stage": "20375f30-7fa7-4562-8f6e-0f61889f2963",
"order": 0 "order": 10
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -145,7 +145,7 @@
"pk": "e40467a6-3052-488c-a1b5-1ad7a80fe7b3", "pk": "e40467a6-3052-488c-a1b5-1ad7a80fe7b3",
"target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3", "target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3",
"stage": "6c342b94-790d-425a-ae31-6196b6570722", "stage": "6c342b94-790d-425a-ae31-6196b6570722",
"order": 1 "order": 11
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -157,7 +157,7 @@
"pk": "76bc594e-2715-49ab-bd40-994abd9a7b70", "pk": "76bc594e-2715-49ab-bd40-994abd9a7b70",
"target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3", "target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3",
"stage": "a4090add-f483-4ac6-8917-10b493ef843e", "stage": "a4090add-f483-4ac6-8917-10b493ef843e",
"order": 2 "order": 20
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -169,7 +169,7 @@
"pk": "2f324f6d-7646-4108-a6e2-e7f90985477f", "pk": "2f324f6d-7646-4108-a6e2-e7f90985477f",
"target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3", "target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3",
"stage": "77090897-eb3f-40db-81e6-b4074b1998c4", "stage": "77090897-eb3f-40db-81e6-b4074b1998c4",
"order": 3 "order": 100
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {

View File

@ -154,7 +154,7 @@
"pk": "34e1e7d5-8eed-4549-bc7a-305069ff7df0", "pk": "34e1e7d5-8eed-4549-bc7a-305069ff7df0",
"target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3", "target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3",
"stage": "20375f30-7fa7-4562-8f6e-0f61889f2963", "stage": "20375f30-7fa7-4562-8f6e-0f61889f2963",
"order": 0 "order": 10
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -166,7 +166,7 @@
"pk": "e40467a6-3052-488c-a1b5-1ad7a80fe7b3", "pk": "e40467a6-3052-488c-a1b5-1ad7a80fe7b3",
"target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3", "target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3",
"stage": "6c342b94-790d-425a-ae31-6196b6570722", "stage": "6c342b94-790d-425a-ae31-6196b6570722",
"order": 1 "order": 11
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -178,7 +178,7 @@
"pk": "76bc594e-2715-49ab-bd40-994abd9a7b70", "pk": "76bc594e-2715-49ab-bd40-994abd9a7b70",
"target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3", "target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3",
"stage": "a4090add-f483-4ac6-8917-10b493ef843e", "stage": "a4090add-f483-4ac6-8917-10b493ef843e",
"order": 2 "order": 20
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -190,7 +190,7 @@
"pk": "1db34a14-8985-4184-b5c9-254cd585d94f", "pk": "1db34a14-8985-4184-b5c9-254cd585d94f",
"target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3", "target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3",
"stage": "096e6282-6b30-4695-bd03-3b143eab5580", "stage": "096e6282-6b30-4695-bd03-3b143eab5580",
"order": 3 "order": 30
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -202,7 +202,7 @@
"pk": "2f324f6d-7646-4108-a6e2-e7f90985477f", "pk": "2f324f6d-7646-4108-a6e2-e7f90985477f",
"target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3", "target": "773c6673-e4a2-423f-8d32-95b7b4a41cf3",
"stage": "77090897-eb3f-40db-81e6-b4074b1998c4", "stage": "77090897-eb3f-40db-81e6-b4074b1998c4",
"order": 4 "order": 40
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {

View File

@ -13,6 +13,18 @@
"designation": "authentication" "designation": "authentication"
} }
}, },
{
"identifiers": {
"pk": "7db93f1e-788b-4af6-8dc6-5cdeb59d8be7"
},
"model": "authentik_policies_expression.expressionpolicy",
"attrs": {
"name": "test-not-app-password",
"execution_logging": false,
"bound_to": 1,
"expression": "return auth_method != \"app_password\""
}
},
{ {
"identifiers": { "identifiers": {
"pk": "69d41125-3987-499b-8d74-ef27b54b88c8", "pk": "69d41125-3987-499b-8d74-ef27b54b88c8",
@ -39,7 +51,7 @@
{ {
"identifiers": { "identifiers": {
"pk": "37f709c3-8817-45e8-9a93-80a925d293c2", "pk": "37f709c3-8817-45e8-9a93-80a925d293c2",
"name": "default-authentication-flow-totp" "name": "default-authentication-flow-mfa"
}, },
"model": "authentik_stages_authenticator_validate.AuthenticatorValidateStage", "model": "authentik_stages_authenticator_validate.AuthenticatorValidateStage",
"attrs": {} "attrs": {}
@ -52,7 +64,8 @@
"model": "authentik_stages_password.passwordstage", "model": "authentik_stages_password.passwordstage",
"attrs": { "attrs": {
"backends": [ "backends": [
"django.contrib.auth.backends.ModelBackend", "authentik.core.auth.InbuiltBackend",
"authentik.core.auth.TokenBackend",
"authentik.sources.ldap.auth.LDAPBackend" "authentik.sources.ldap.auth.LDAPBackend"
] ]
} }
@ -62,7 +75,7 @@
"pk": "a3056482-b692-4e3a-93f1-7351c6a351c7", "pk": "a3056482-b692-4e3a-93f1-7351c6a351c7",
"target": "563ece21-e9a4-47e5-a264-23ffd923e393", "target": "563ece21-e9a4-47e5-a264-23ffd923e393",
"stage": "5f594f27-0def-488d-9855-fe604eb13de5", "stage": "5f594f27-0def-488d-9855-fe604eb13de5",
"order": 0 "order": 10
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -74,7 +87,7 @@
"pk": "4e8538cf-3e18-4a68-82ae-6df6725fa2e6", "pk": "4e8538cf-3e18-4a68-82ae-6df6725fa2e6",
"target": "563ece21-e9a4-47e5-a264-23ffd923e393", "target": "563ece21-e9a4-47e5-a264-23ffd923e393",
"stage": "d8affa62-500c-4c5c-a01f-5835e1ffdf40", "stage": "d8affa62-500c-4c5c-a01f-5835e1ffdf40",
"order": 1 "order": 20
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -86,7 +99,22 @@
"pk": "688aec6f-5622-42c6-83a5-d22072d7e798", "pk": "688aec6f-5622-42c6-83a5-d22072d7e798",
"target": "563ece21-e9a4-47e5-a264-23ffd923e393", "target": "563ece21-e9a4-47e5-a264-23ffd923e393",
"stage": "37f709c3-8817-45e8-9a93-80a925d293c2", "stage": "37f709c3-8817-45e8-9a93-80a925d293c2",
"order": 2 "order": 30
},
"model": "authentik_flows.flowstagebinding",
"attrs": {
"evaluate_on_plan": false,
"re_evaluate_policies": true,
"policy_engine_mode": "any",
"invalid_response_action": "retry"
}
},
{
"identifiers": {
"pk": "f3fede3a-a9b5-4232-9ec7-be7ff4194b27",
"target": "563ece21-e9a4-47e5-a264-23ffd923e393",
"stage": "69d41125-3987-499b-8d74-ef27b54b88c8",
"order": 100
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -95,14 +123,16 @@
}, },
{ {
"identifiers": { "identifiers": {
"pk": "f3fede3a-a9b5-4232-9ec7-be7ff4194b27", "pk": "6e40ae4d-a4ed-4bd7-a784-27b1fe5859d2",
"target": "563ece21-e9a4-47e5-a264-23ffd923e393", "policy": "7db93f1e-788b-4af6-8dc6-5cdeb59d8be7",
"stage": "69d41125-3987-499b-8d74-ef27b54b88c8", "target": "688aec6f-5622-42c6-83a5-d22072d7e798",
"order": 3 "order": 0
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_policies.policybinding",
"attrs": { "attrs": {
"re_evaluate_policies": false "negate": false,
"enabled": true,
"timeout": 30
} }
} }
] ]

View File

@ -55,7 +55,8 @@
"model": "authentik_stages_password.passwordstage", "model": "authentik_stages_password.passwordstage",
"attrs": { "attrs": {
"backends": [ "backends": [
"django.contrib.auth.backends.ModelBackend", "authentik.core.auth.InbuiltBackend",
"authentik.core.auth.TokenBackend",
"authentik.sources.ldap.auth.LDAPBackend" "authentik.sources.ldap.auth.LDAPBackend"
] ]
} }
@ -65,7 +66,7 @@
"pk": "a3056482-b692-4e3a-93f1-7351c6a351c7", "pk": "a3056482-b692-4e3a-93f1-7351c6a351c7",
"target": "563ece21-e9a4-47e5-a264-23ffd923e393", "target": "563ece21-e9a4-47e5-a264-23ffd923e393",
"stage": "5f594f27-0def-488d-9855-fe604eb13de5", "stage": "5f594f27-0def-488d-9855-fe604eb13de5",
"order": 0 "order": 10
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -77,7 +78,7 @@
"pk": "4e8538cf-3e18-4a68-82ae-6df6725fa2e6", "pk": "4e8538cf-3e18-4a68-82ae-6df6725fa2e6",
"target": "563ece21-e9a4-47e5-a264-23ffd923e393", "target": "563ece21-e9a4-47e5-a264-23ffd923e393",
"stage": "d8affa62-500c-4c5c-a01f-5835e1ffdf40", "stage": "d8affa62-500c-4c5c-a01f-5835e1ffdf40",
"order": 1 "order": 20
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -89,7 +90,7 @@
"pk": "3bcd6af0-48a6-4e18-87f3-d251a1a58226", "pk": "3bcd6af0-48a6-4e18-87f3-d251a1a58226",
"target": "563ece21-e9a4-47e5-a264-23ffd923e393", "target": "563ece21-e9a4-47e5-a264-23ffd923e393",
"stage": "a368cafc-1494-45e9-b75b-b5e7ac2bd3e4", "stage": "a368cafc-1494-45e9-b75b-b5e7ac2bd3e4",
"order": 2 "order": 30
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -102,7 +103,7 @@
"pk": "f3fede3a-a9b5-4232-9ec7-be7ff4194b27", "pk": "f3fede3a-a9b5-4232-9ec7-be7ff4194b27",
"target": "563ece21-e9a4-47e5-a264-23ffd923e393", "target": "563ece21-e9a4-47e5-a264-23ffd923e393",
"stage": "69d41125-3987-499b-8d74-ef27b54b88c8", "stage": "69d41125-3987-499b-8d74-ef27b54b88c8",
"order": 3 "order": 100
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {

View File

@ -123,7 +123,7 @@
"pk": "7af7558e-2196-4b9f-a08e-d38420b7cfbb", "pk": "7af7558e-2196-4b9f-a08e-d38420b7cfbb",
"target": "a5993183-89c0-43d2-a7f4-ddffb17baba7", "target": "a5993183-89c0-43d2-a7f4-ddffb17baba7",
"stage": "e54045a7-6ecb-4ad9-ad37-28e72d8e565e", "stage": "e54045a7-6ecb-4ad9-ad37-28e72d8e565e",
"order": 0 "order": 10
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -135,7 +135,7 @@
"pk": "29446fd6-dd93-4e92-9830-2d81debad5ae", "pk": "29446fd6-dd93-4e92-9830-2d81debad5ae",
"target": "a5993183-89c0-43d2-a7f4-ddffb17baba7", "target": "a5993183-89c0-43d2-a7f4-ddffb17baba7",
"stage": "66f948dc-3f74-42b2-b26b-b8b9df109efb", "stage": "66f948dc-3f74-42b2-b26b-b8b9df109efb",
"order": 1 "order": 20
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -147,7 +147,7 @@
"pk": "1219d06e-2c06-4c5b-a162-78e3959c6cf0", "pk": "1219d06e-2c06-4c5b-a162-78e3959c6cf0",
"target": "a5993183-89c0-43d2-a7f4-ddffb17baba7", "target": "a5993183-89c0-43d2-a7f4-ddffb17baba7",
"stage": "975d5502-1e22-4d10-b560-fbc5bd70ff4d", "stage": "975d5502-1e22-4d10-b560-fbc5bd70ff4d",
"order": 2 "order": 30
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -159,7 +159,7 @@
"pk": "66de86ba-0707-46a0-8475-ff2e260d6935", "pk": "66de86ba-0707-46a0-8475-ff2e260d6935",
"target": "a5993183-89c0-43d2-a7f4-ddffb17baba7", "target": "a5993183-89c0-43d2-a7f4-ddffb17baba7",
"stage": "3909fd60-b013-4668-8806-12e9507dab97", "stage": "3909fd60-b013-4668-8806-12e9507dab97",
"order": 3 "order": 40
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {
@ -171,7 +171,7 @@
"pk": "9cec2334-d4a2-4895-a2b2-bc5ae4e9639a", "pk": "9cec2334-d4a2-4895-a2b2-bc5ae4e9639a",
"target": "a5993183-89c0-43d2-a7f4-ddffb17baba7", "target": "a5993183-89c0-43d2-a7f4-ddffb17baba7",
"stage": "fcdd4206-0d35-4ad2-a59f-5a72422936bb", "stage": "fcdd4206-0d35-4ad2-a59f-5a72422936bb",
"order": 4 "order": 100
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {

View File

@ -26,7 +26,7 @@
"pk": "eb9aff2b-b95d-40b3-ad08-233aa77bbcf3", "pk": "eb9aff2b-b95d-40b3-ad08-233aa77bbcf3",
"target": "59a576ce-2f23-4a63-b63a-d18dc7e550f5", "target": "59a576ce-2f23-4a63-b63a-d18dc7e550f5",
"stage": "c62ac2a4-2735-4a0f-abd0-8523d68c1209", "stage": "c62ac2a4-2735-4a0f-abd0-8523d68c1209",
"order": 0 "order": 10
}, },
"model": "authentik_flows.flowstagebinding", "model": "authentik_flows.flowstagebinding",
"attrs": { "attrs": {