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:
commit
6b7a8b6ac7
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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."""
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"},
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
)
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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"""
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
|
@ -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")
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
|
]
|
|
@ -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"),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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`}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
Reference in New Issue