Merge branch 'master' into inbuilt-proxy

This commit is contained in:
Jens Langhammer 2021-06-29 16:20:24 +02:00
commit 8429dd19b2
82 changed files with 1224 additions and 464 deletions

64
Pipfile.lock generated
View File

@ -76,11 +76,11 @@
}, },
"asgiref": { "asgiref": {
"hashes": [ "hashes": [
"sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee", "sha256:05914d0fa65a21711e732adc6572edad6c8da5f1435c3f0c060689ced5e85195",
"sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78" "sha256:d36fa91dd90e3aa3c81a6bd426ccc8fb20bd3d22b0cf14a12800289e9c3e2563"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==3.3.4" "version": "==3.4.0"
}, },
"async-timeout": { "async-timeout": {
"hashes": [ "hashes": [
@ -122,19 +122,19 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:2c2f70608934b03f9c08f4cd185de223b5abd18245dd4d4800e1fbc2a2523e31", "sha256:6300e9ee9a404038113250bd218e2c4827f5e676efb14e77de2ad2dcb67679bc",
"sha256:fccfa81cda69bb2317ed97e7149d7d84d19e6ec3bfbe3f721139e7ac0c407c73" "sha256:be4714f0475c1f5183eea09ddbf568ced6fa41b0fc9976f2698b8442e1b17303"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.17.98" "version": "==1.17.102"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:b2a49de4ee04b690142c8e7240f0f5758e3f7673dd39cf398efe893bf5e11c3f", "sha256:2f57f7ceed1598d96cc497aeb45317db5d3b21a5aafea4732d0e561d0fc2a8fa",
"sha256:b955b23fe2fbdbbc8e66f37fe2970de6b5d8169f940b200bcf434751709d38f6" "sha256:bdf08a4f7f01ead00d386848f089c08270499711447569c18d0db60023619c06"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.20.98" "version": "==1.20.102"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
@ -165,11 +165,11 @@
}, },
"celery": { "celery": {
"hashes": [ "hashes": [
"sha256:54436cd97b031bf2e08064223240e2a83d601d9414bcb1b702f94c6c33c29485", "sha256:8d9a3de9162965e97f8e8cc584c67aad83b3f7a267584fa47701ed11c3e0d4b0",
"sha256:b5399d76cf70d5cfac3ec993f8796ec1aa90d4cef55972295751f384758a80d7" "sha256:9dab2170b4038f7bf10ef2861dbf486ddf1d20592290a1040f7b7a1259705d42"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.1.1" "version": "==5.1.2"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
@ -948,10 +948,30 @@
}, },
"pyrsistent": { "pyrsistent": {
"hashes": [ "hashes": [
"sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2",
"sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7",
"sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea",
"sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426",
"sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710",
"sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1",
"sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396",
"sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2",
"sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680",
"sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35",
"sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427",
"sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b",
"sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b",
"sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f",
"sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef",
"sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c",
"sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4",
"sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d",
"sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78",
"sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b",
"sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72"
], ],
"markers": "python_version >= '3.5'", "markers": "python_version >= '3.6'",
"version": "==0.17.3" "version": "==0.18.0"
}, },
"python-dateutil": { "python-dateutil": {
"hashes": [ "hashes": [
@ -1167,11 +1187,11 @@
"secure" "secure"
], ],
"hashes": [ "hashes": [
"sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
"sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.26.5" "version": "==1.26.6"
}, },
"uvicorn": { "uvicorn": {
"extras": [ "extras": [
@ -1565,7 +1585,7 @@
"sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56", "sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56",
"sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c" "sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c"
], ],
"markers": "python_version < '4.0' and python_full_version >= '3.6.1'", "markers": "python_version < '4' and python_full_version >= '3.6.1'",
"version": "==5.9.1" "version": "==5.9.1"
}, },
"lazy-object-proxy": { "lazy-object-proxy": {
@ -1838,11 +1858,11 @@
"secure" "secure"
], ],
"hashes": [ "hashes": [
"sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
"sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.26.5" "version": "==1.26.6"
}, },
"wrapt": { "wrapt": {
"hashes": [ "hashes": [

View File

@ -19,7 +19,7 @@ def token_from_header(raw_header: bytes) -> Optional[Token]:
auth_credentials = raw_header.decode() auth_credentials = raw_header.decode()
if auth_credentials == "" or " " not in auth_credentials: if auth_credentials == "" or " " not in auth_credentials:
return None return None
auth_type, auth_credentials = auth_credentials.split() auth_type, _, auth_credentials = auth_credentials.partition(" ")
if auth_type.lower() not in ["basic", "bearer"]: if auth_type.lower() not in ["basic", "bearer"]:
LOGGER.debug("Unsupported authentication type, denying", type=auth_type.lower()) LOGGER.debug("Unsupported authentication type, denying", type=auth_type.lower())
raise AuthenticationFailed("Unsupported authentication type") raise AuthenticationFailed("Unsupported authentication type")

View File

@ -213,7 +213,7 @@ class SourceFlowManager:
planner = FlowPlanner(flow) planner = FlowPlanner(flow)
plan = planner.plan(self.request, kwargs) plan = planner.plan(self.request, kwargs)
for stage in self.get_stages_to_append(flow): for stage in self.get_stages_to_append(flow):
plan.append(stage) plan.append_stage(stage=stage)
self.request.session[SESSION_KEY_PLAN] = plan self.request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs( return redirect_with_qs(
"authentik_core:if-flow", "authentik_core:if-flow",

View File

@ -13,7 +13,7 @@
<script src="{% static 'dist/FlowInterface.js' %}?v={{ ak_version }}" type="module"></script> <script src="{% static 'dist/FlowInterface.js' %}?v={{ ak_version }}" type="module"></script>
<style> <style>
.pf-c-background-image::before { .pf-c-background-image::before {
background-image: url("{{ flow.background_url }}"); --ak-flow-background: url("{{ flow.background_url }}");
} }
</style> </style>
{% endblock %} {% endblock %}

View File

@ -10,7 +10,7 @@
{% block head %} {% block head %}
<style> <style>
.pf-c-background-image::before { .pf-c-background-image::before {
background-image: url("/static/dist/assets/images/flow_background.jpg"); --ak-flow-background: url("/static/dist/assets/images/flow_background.jpg");
} }
</style> </style>
{% endblock %} {% endblock %}

View File

@ -6,11 +6,11 @@ from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema from drf_spectacular.utils import OpenApiParameter, extend_schema
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import CharField, DictField, IntegerField from rest_framework.fields import DictField, IntegerField
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ReadOnlyModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.utils import PassiveSerializer, TypeCreateSerializer from authentik.core.api.utils import PassiveSerializer, TypeCreateSerializer
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
@ -19,11 +19,6 @@ from authentik.events.models import Event, EventAction
class EventSerializer(ModelSerializer): class EventSerializer(ModelSerializer):
"""Event Serializer""" """Event Serializer"""
# Since we only use this serializer for read-only operations,
# no checking of the action is done here.
# This allows clients to check wildcards, prefixes and custom types
action = CharField()
class Meta: class Meta:
model = Event model = Event
@ -96,7 +91,7 @@ class EventsFilter(django_filters.FilterSet):
fields = ["action", "client_ip", "username"] fields = ["action", "client_ip", "username"]
class EventViewSet(ReadOnlyModelViewSet): class EventViewSet(ModelViewSet):
"""Event Read-Only Viewset""" """Event Read-Only Viewset"""
queryset = Event.objects.all() queryset = Event.objects.all()

View File

@ -13,6 +13,7 @@ from authentik.core.models import User
from authentik.events.models import Event, EventAction, Notification from authentik.events.models import Event, EventAction, Notification
from authentik.events.signals import EventNewThread from authentik.events.signals import EventNewThread
from authentik.events.utils import model_to_dict from authentik.events.utils import model_to_dict
from authentik.lib.sentry import before_send
from authentik.lib.utils.errors import exception_to_string from authentik.lib.utils.errors import exception_to_string
@ -62,12 +63,13 @@ class AuditMiddleware:
if settings.DEBUG: if settings.DEBUG:
return return
thread = EventNewThread( if before_send({}, {"exc_info": (None, exception, None)}) is not None:
EventAction.SYSTEM_EXCEPTION, thread = EventNewThread(
request, EventAction.SYSTEM_EXCEPTION,
message=exception_to_string(exception), request,
) message=exception_to_string(exception),
thread.run() )
thread.run()
@staticmethod @staticmethod
# pylint: disable=unused-argument # pylint: disable=unused-argument

View File

@ -105,7 +105,11 @@ def notification_transport(
"""Send notification over specified transport""" """Send notification over specified transport"""
self.save_on_success = False self.save_on_success = False
try: try:
notification: Notification = Notification.objects.get(pk=notification_pk) notification: Notification = Notification.objects.filter(
pk=notification_pk
).first()
if not notification:
return
transport: NotificationTransport = NotificationTransport.objects.get( transport: NotificationTransport = NotificationTransport.objects.get(
pk=transport_pk pk=transport_pk
) )

View File

@ -25,6 +25,7 @@ class FlowStageBindingSerializer(ModelSerializer):
"re_evaluate_policies", "re_evaluate_policies",
"order", "order",
"policy_engine_mode", "policy_engine_mode",
"invalid_response_action",
] ]

View File

@ -5,8 +5,7 @@ from typing import TYPE_CHECKING, Optional
from django.http.request import HttpRequest from django.http.request import HttpRequest
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.models import User from authentik.flows.models import FlowStageBinding
from authentik.flows.models import Stage
from authentik.policies.engine import PolicyEngine from authentik.policies.engine import PolicyEngine
from authentik.policies.models import PolicyBinding from authentik.policies.models import PolicyBinding
@ -22,11 +21,14 @@ class StageMarker:
# pylint: disable=unused-argument # pylint: disable=unused-argument
def process( def process(
self, plan: "FlowPlan", stage: Stage, http_request: Optional[HttpRequest] self,
) -> Optional[Stage]: plan: "FlowPlan",
binding: FlowStageBinding,
http_request: HttpRequest,
) -> Optional[FlowStageBinding]:
"""Process callback for this marker. This should be overridden by sub-classes. """Process callback for this marker. This should be overridden by sub-classes.
If a stage should be removed, return None.""" If a stage should be removed, return None."""
return stage return binding
@dataclass @dataclass
@ -34,24 +36,34 @@ class ReevaluateMarker(StageMarker):
"""Reevaluate Marker, forces stage's policies to be evaluated again.""" """Reevaluate Marker, forces stage's policies to be evaluated again."""
binding: PolicyBinding binding: PolicyBinding
user: User
def process( def process(
self, plan: "FlowPlan", stage: Stage, http_request: Optional[HttpRequest] self,
) -> Optional[Stage]: plan: "FlowPlan",
binding: FlowStageBinding,
http_request: HttpRequest,
) -> Optional[FlowStageBinding]:
"""Re-evaluate policies bound to stage, and if they fail, remove from plan""" """Re-evaluate policies bound to stage, and if they fail, remove from plan"""
engine = PolicyEngine(self.binding, self.user) from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
LOGGER.debug(
"f(plan_inst)[re-eval marker]: running re-evaluation",
binding=binding,
policy_binding=self.binding,
)
engine = PolicyEngine(
self.binding, plan.context.get(PLAN_CONTEXT_PENDING_USER, http_request.user)
)
engine.use_cache = False engine.use_cache = False
if http_request: engine.request.set_http_request(http_request)
engine.request.set_http_request(http_request)
engine.request.context = plan.context engine.request.context = plan.context
engine.build() engine.build()
result = engine.result result = engine.result
if result.passing: if result.passing:
return stage return binding
LOGGER.warning( LOGGER.warning(
"f(plan_inst)[re-eval marker]: stage failed re-evaluation", "f(plan_inst)[re-eval marker]: binding failed re-evaluation",
stage=stage, binding=binding,
messages=result.messages, messages=result.messages,
) )
return None return None

View File

@ -135,7 +135,7 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
("authentik_flows", "0017_auto_20210329_1334"), ("authentik_flows", "0017_auto_20210329_1334"),
("authentik_stages_user_write", "__latest__"), ("authentik_stages_user_write", "0002_auto_20200918_1653"),
("authentik_stages_user_login", "__latest__"), ("authentik_stages_user_login", "__latest__"),
("authentik_stages_password", "0002_passwordstage_change_flow"), ("authentik_stages_password", "0002_passwordstage_change_flow"),
("authentik_policies", "0001_initial"), ("authentik_policies", "0001_initial"),

View File

@ -0,0 +1,22 @@
# Generated by Django 3.2.4 on 2021-06-27 16:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0020_flow_compatibility_mode"),
]
operations = [
migrations.AddField(
model_name="flowstagebinding",
name="invalid_response_action",
field=models.TextField(
choices=[("retry", "Retry"), ("continue", "Continue")],
default="retry",
help_text="Configure how the flow executor should handle an invalid response to a challenge. RETRY returns the error message and a similar challenge to the executor while CONTINUE continues with the next stage.",
),
),
]

View File

@ -27,6 +27,14 @@ class NotConfiguredAction(models.TextChoices):
CONFIGURE = "configure" CONFIGURE = "configure"
class InvalidResponseAction(models.TextChoices):
"""Configure how the flow executor should handle invalid responses to challenges"""
RETRY = "retry"
RESTART = "restart"
RESTART_WITH_CONTEXT = "restart_with_context"
class FlowDesignation(models.TextChoices): class FlowDesignation(models.TextChoices):
"""Designation of what a Flow should be used for. At a later point, this """Designation of what a Flow should be used for. At a later point, this
should be replaced by a database entry.""" should be replaced by a database entry."""
@ -201,6 +209,17 @@ class FlowStageBinding(SerializerModel, PolicyBindingModel):
help_text=_("Evaluate policies when the Stage is present to the user."), help_text=_("Evaluate policies when the Stage is present to the user."),
) )
invalid_response_action = models.TextField(
choices=InvalidResponseAction.choices,
default=InvalidResponseAction.RETRY,
help_text=_(
"Configure how the flow executor should handle an invalid response to a "
"challenge. RETRY returns the error message and a similar challenge to the "
"executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT "
"restarts the flow while keeping the current context."
),
)
order = models.IntegerField() order = models.IntegerField()
objects = InheritanceManager() objects = InheritanceManager()

View File

@ -52,33 +52,41 @@ class FlowPlan:
flow_pk: str flow_pk: str
stages: list[Stage] = field(default_factory=list) bindings: list[FlowStageBinding] = field(default_factory=list)
context: dict[str, Any] = field(default_factory=dict) context: dict[str, Any] = field(default_factory=dict)
markers: list[StageMarker] = field(default_factory=list) markers: list[StageMarker] = field(default_factory=list)
def append(self, stage: Stage, marker: Optional[StageMarker] = None): def append_stage(self, stage: Stage, marker: Optional[StageMarker] = None):
"""Append `stage` to all stages, optionall with stage marker""" """Append `stage` to all stages, optionall with stage marker"""
self.stages.append(stage) return self.append(FlowStageBinding(stage=stage), marker)
def append(self, binding: FlowStageBinding, marker: Optional[StageMarker] = None):
"""Append `stage` to all stages, optionall with stage marker"""
self.bindings.append(binding)
self.markers.append(marker or StageMarker()) self.markers.append(marker or StageMarker())
def insert(self, stage: Stage, marker: Optional[StageMarker] = None): def insert_stage(self, stage: Stage, marker: Optional[StageMarker] = None):
"""Insert stage into plan, as immediate next stage""" """Insert stage into plan, as immediate next stage"""
self.stages.insert(1, stage) self.bindings.insert(1, FlowStageBinding(stage=stage, order=0))
self.markers.insert(1, marker or StageMarker()) self.markers.insert(1, marker or StageMarker())
def next(self, http_request: Optional[HttpRequest]) -> Optional[Stage]: def next(self, http_request: Optional[HttpRequest]) -> Optional[FlowStageBinding]:
"""Return next pending stage from the bottom of the list""" """Return next pending stage from the bottom of the list"""
if not self.has_stages: if not self.has_stages:
return None return None
stage = self.stages[0] binding = self.bindings[0]
marker = self.markers[0] marker = self.markers[0]
if marker.__class__ is not StageMarker: if marker.__class__ is not StageMarker:
LOGGER.debug("f(plan_inst): stage has marker", stage=stage, marker=marker) LOGGER.debug(
marked_stage = marker.process(self, stage, http_request) "f(plan_inst): stage has marker", binding=binding, marker=marker
)
marked_stage = marker.process(self, binding, http_request)
if not marked_stage: if not marked_stage:
LOGGER.debug("f(plan_inst): marker returned none, next stage", stage=stage) LOGGER.debug(
self.stages.remove(stage) "f(plan_inst): marker returned none, next stage", binding=binding
)
self.bindings.remove(binding)
self.markers.remove(marker) self.markers.remove(marker)
if not self.has_stages: if not self.has_stages:
return None return None
@ -89,12 +97,12 @@ class FlowPlan:
def pop(self): def pop(self):
"""Pop next pending stage from bottom of list""" """Pop next pending stage from bottom of list"""
self.markers.pop(0) self.markers.pop(0)
self.stages.pop(0) self.bindings.pop(0)
@property @property
def has_stages(self) -> bool: def has_stages(self) -> bool:
"""Check if there are any stages left in this plan""" """Check if there are any stages left in this plan"""
return len(self.markers) + len(self.stages) > 0 return len(self.markers) + len(self.bindings) > 0
class FlowPlanner: class FlowPlanner:
@ -161,7 +169,7 @@ class FlowPlanner:
plan = self._build_plan(user, request, default_context) plan = self._build_plan(user, request, default_context)
cache.set(cache_key(self.flow, user), plan, CACHE_TIMEOUT) cache.set(cache_key(self.flow, user), plan, CACHE_TIMEOUT)
GAUGE_FLOWS_CACHED.update() GAUGE_FLOWS_CACHED.update()
if not plan.stages and not self.allow_empty_flows: if not plan.bindings and not self.allow_empty_flows:
raise EmptyFlowException() raise EmptyFlowException()
return plan return plan
@ -216,9 +224,9 @@ class FlowPlanner:
"f(plan): stage has re-evaluate marker", "f(plan): stage has re-evaluate marker",
stage=binding.stage, stage=binding.stage,
) )
marker = ReevaluateMarker(binding=binding, user=user) marker = ReevaluateMarker(binding=binding)
if stage: if stage:
plan.append(stage, marker) plan.append(binding, marker)
HIST_FLOWS_PLAN_TIME.labels(flow_slug=self.flow.slug) HIST_FLOWS_PLAN_TIME.labels(flow_slug=self.flow.slug)
self._logger.debug( self._logger.debug(
"f(plan): finished building", "f(plan): finished building",

View File

@ -16,6 +16,7 @@ from authentik.flows.challenge import (
HttpChallengeResponse, HttpChallengeResponse,
WithUserInfoChallenge, WithUserInfoChallenge,
) )
from authentik.flows.models import InvalidResponseAction
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.views import FlowExecutorView from authentik.flows.views import FlowExecutorView
@ -69,7 +70,13 @@ class ChallengeStageView(StageView):
"""Return a challenge for the frontend to solve""" """Return a challenge for the frontend to solve"""
challenge = self._get_challenge(*args, **kwargs) challenge = self._get_challenge(*args, **kwargs)
if not challenge.is_valid(): if not challenge.is_valid():
LOGGER.warning(challenge.errors, stage_view=self, challenge=challenge) LOGGER.warning(
"f(ch): Invalid challenge",
binding=self.executor.current_binding,
errors=challenge.errors,
stage_view=self,
challenge=challenge,
)
return HttpChallengeResponse(challenge) return HttpChallengeResponse(challenge)
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -77,6 +84,21 @@ class ChallengeStageView(StageView):
"""Handle challenge response""" """Handle challenge response"""
challenge: ChallengeResponse = self.get_response_instance(data=request.data) challenge: ChallengeResponse = self.get_response_instance(data=request.data)
if not challenge.is_valid(): if not challenge.is_valid():
if self.executor.current_binding.invalid_response_action in [
InvalidResponseAction.RESTART,
InvalidResponseAction.RESTART_WITH_CONTEXT,
]:
keep_context = (
self.executor.current_binding.invalid_response_action
== InvalidResponseAction.RESTART_WITH_CONTEXT
)
LOGGER.debug(
"f(ch): Invalid response, restarting flow",
binding=self.executor.current_binding,
stage_view=self,
keep_context=keep_context,
)
return self.executor.restart_flow(keep_context)
return self.challenge_invalid(challenge) return self.challenge_invalid(challenge)
return self.challenge_valid(challenge) return self.challenge_valid(challenge)
@ -126,5 +148,10 @@ class ChallengeStageView(StageView):
) )
challenge_response.initial_data["response_errors"] = full_errors challenge_response.initial_data["response_errors"] = full_errors
if not challenge_response.is_valid(): if not challenge_response.is_valid():
LOGGER.warning(challenge_response.errors) LOGGER.warning(
"f(ch): invalid challenge response",
binding=self.executor.current_binding,
errors=challenge_response.errors,
stage_view=self,
)
return HttpChallengeResponse(challenge_response) return HttpChallengeResponse(challenge_response)

View File

@ -182,8 +182,8 @@ class TestFlowPlanner(TestCase):
planner = FlowPlanner(flow) planner = FlowPlanner(flow)
plan = planner.plan(request) plan = planner.plan(request)
self.assertEqual(plan.stages[0], binding.stage) self.assertEqual(plan.bindings[0], binding)
self.assertEqual(plan.stages[1], binding2.stage) self.assertEqual(plan.bindings[1], binding2)
self.assertIsInstance(plan.markers[0], StageMarker) self.assertIsInstance(plan.markers[0], StageMarker)
self.assertIsInstance(plan.markers[1], ReevaluateMarker) self.assertIsInstance(plan.markers[1], ReevaluateMarker)

View File

@ -11,15 +11,23 @@ from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes from authentik.flows.challenge import ChallengeTypes
from authentik.flows.exceptions import FlowNonApplicableException from authentik.flows.exceptions import FlowNonApplicableException
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,
InvalidResponseAction,
)
from authentik.flows.planner import FlowPlan, FlowPlanner from authentik.flows.planner import FlowPlan, FlowPlanner
from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageView from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageView
from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_PLAN, FlowExecutorView from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_PLAN, FlowExecutorView
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
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.reputation.models import ReputationPolicy
from authentik.policies.types import PolicyResult from authentik.policies.types import PolicyResult
from authentik.stages.deny.models import DenyStage
from authentik.stages.dummy.models import DummyStage from authentik.stages.dummy.models import DummyStage
from authentik.stages.identification.models import IdentificationStage, UserFields
POLICY_RETURN_FALSE = PropertyMock(return_value=PolicyResult(False)) POLICY_RETURN_FALSE = PropertyMock(return_value=PolicyResult(False))
POLICY_RETURN_TRUE = MagicMock(return_value=PolicyResult(True)) POLICY_RETURN_TRUE = MagicMock(return_value=PolicyResult(True))
@ -52,8 +60,9 @@ class TestFlowExecutor(TestCase):
designation=FlowDesignation.AUTHENTICATION, designation=FlowDesignation.AUTHENTICATION,
) )
stage = DummyStage.objects.create(name="dummy") stage = DummyStage.objects.create(name="dummy")
binding = FlowStageBinding(target=flow, stage=stage, order=0)
plan = FlowPlan( plan = FlowPlan(
flow_pk=flow.pk.hex + "a", stages=[stage], markers=[StageMarker()] flow_pk=flow.pk.hex + "a", bindings=[binding], markers=[StageMarker()]
) )
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
@ -163,7 +172,7 @@ class TestFlowExecutor(TestCase):
# Check that two stages are in plan # Check that two stages are in plan
session = self.client.session session = self.client.session
plan: FlowPlan = session[SESSION_KEY_PLAN] plan: FlowPlan = session[SESSION_KEY_PLAN]
self.assertEqual(len(plan.stages), 2) self.assertEqual(len(plan.bindings), 2)
# Second request, submit form, one stage left # Second request, submit form, one stage left
response = self.client.post(exec_url) response = self.client.post(exec_url)
# Second request redirects to the same URL # Second request redirects to the same URL
@ -172,7 +181,7 @@ class TestFlowExecutor(TestCase):
# Check that two stages are in plan # Check that two stages are in plan
session = self.client.session session = self.client.session
plan: FlowPlan = session[SESSION_KEY_PLAN] plan: FlowPlan = session[SESSION_KEY_PLAN]
self.assertEqual(len(plan.stages), 1) self.assertEqual(len(plan.bindings), 1)
@patch( @patch(
"authentik.flows.views.to_stage_response", "authentik.flows.views.to_stage_response",
@ -213,8 +222,8 @@ class TestFlowExecutor(TestCase):
plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] plan: FlowPlan = self.client.session[SESSION_KEY_PLAN]
self.assertEqual(plan.stages[0], binding.stage) self.assertEqual(plan.bindings[0], binding)
self.assertEqual(plan.stages[1], binding2.stage) self.assertEqual(plan.bindings[1], binding2)
self.assertIsInstance(plan.markers[0], StageMarker) self.assertIsInstance(plan.markers[0], StageMarker)
self.assertIsInstance(plan.markers[1], ReevaluateMarker) self.assertIsInstance(plan.markers[1], ReevaluateMarker)
@ -267,9 +276,9 @@ class TestFlowExecutor(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] plan: FlowPlan = self.client.session[SESSION_KEY_PLAN]
self.assertEqual(plan.stages[0], binding.stage) self.assertEqual(plan.bindings[0], binding)
self.assertEqual(plan.stages[1], binding2.stage) self.assertEqual(plan.bindings[1], binding2)
self.assertEqual(plan.stages[2], binding3.stage) self.assertEqual(plan.bindings[2], binding3)
self.assertIsInstance(plan.markers[0], StageMarker) self.assertIsInstance(plan.markers[0], StageMarker)
self.assertIsInstance(plan.markers[1], ReevaluateMarker) self.assertIsInstance(plan.markers[1], ReevaluateMarker)
@ -281,8 +290,8 @@ class TestFlowExecutor(TestCase):
plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] plan: FlowPlan = self.client.session[SESSION_KEY_PLAN]
self.assertEqual(plan.stages[0], binding2.stage) self.assertEqual(plan.bindings[0], binding2)
self.assertEqual(plan.stages[1], binding3.stage) self.assertEqual(plan.bindings[1], binding3)
self.assertIsInstance(plan.markers[0], StageMarker) self.assertIsInstance(plan.markers[0], StageMarker)
self.assertIsInstance(plan.markers[1], StageMarker) self.assertIsInstance(plan.markers[1], StageMarker)
@ -338,9 +347,9 @@ class TestFlowExecutor(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] plan: FlowPlan = self.client.session[SESSION_KEY_PLAN]
self.assertEqual(plan.stages[0], binding.stage) self.assertEqual(plan.bindings[0], binding)
self.assertEqual(plan.stages[1], binding2.stage) self.assertEqual(plan.bindings[1], binding2)
self.assertEqual(plan.stages[2], binding3.stage) self.assertEqual(plan.bindings[2], binding3)
self.assertIsInstance(plan.markers[0], StageMarker) self.assertIsInstance(plan.markers[0], StageMarker)
self.assertIsInstance(plan.markers[1], ReevaluateMarker) self.assertIsInstance(plan.markers[1], ReevaluateMarker)
@ -352,8 +361,8 @@ class TestFlowExecutor(TestCase):
plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] plan: FlowPlan = self.client.session[SESSION_KEY_PLAN]
self.assertEqual(plan.stages[0], binding2.stage) self.assertEqual(plan.bindings[0], binding2)
self.assertEqual(plan.stages[1], binding3.stage) self.assertEqual(plan.bindings[1], binding3)
self.assertIsInstance(plan.markers[0], StageMarker) self.assertIsInstance(plan.markers[0], StageMarker)
self.assertIsInstance(plan.markers[1], StageMarker) self.assertIsInstance(plan.markers[1], StageMarker)
@ -364,7 +373,7 @@ class TestFlowExecutor(TestCase):
plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] plan: FlowPlan = self.client.session[SESSION_KEY_PLAN]
self.assertEqual(plan.stages[0], binding3.stage) self.assertEqual(plan.bindings[0], binding3)
self.assertIsInstance(plan.markers[0], StageMarker) self.assertIsInstance(plan.markers[0], StageMarker)
@ -438,10 +447,10 @@ class TestFlowExecutor(TestCase):
plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] plan: FlowPlan = self.client.session[SESSION_KEY_PLAN]
self.assertEqual(plan.stages[0], binding.stage) self.assertEqual(plan.bindings[0], binding)
self.assertEqual(plan.stages[1], binding2.stage) self.assertEqual(plan.bindings[1], binding2)
self.assertEqual(plan.stages[2], binding3.stage) self.assertEqual(plan.bindings[2], binding3)
self.assertEqual(plan.stages[3], binding4.stage) self.assertEqual(plan.bindings[3], binding4)
self.assertIsInstance(plan.markers[0], StageMarker) self.assertIsInstance(plan.markers[0], StageMarker)
self.assertIsInstance(plan.markers[1], ReevaluateMarker) self.assertIsInstance(plan.markers[1], ReevaluateMarker)
@ -512,3 +521,78 @@ class TestFlowExecutor(TestCase):
stage_view = StageView(executor) stage_view = StageView(executor)
self.assertEqual(ident, stage_view.get_pending_user(for_display=True).username) self.assertEqual(ident, stage_view.get_pending_user(for_display=True).username)
def test_invalid_restart(self):
"""Test flow that restarts on invalid entry"""
flow = Flow.objects.create(
name="restart-on-invalid",
slug="restart-on-invalid",
designation=FlowDesignation.AUTHENTICATION,
)
# Stage 0 is a deny stage that is added dynamically
# when the reputation policy says so
deny_stage = DenyStage.objects.create(name="deny")
reputation_policy = ReputationPolicy.objects.create(
name="reputation", threshold=-1, check_ip=False
)
deny_binding = FlowStageBinding.objects.create(
target=flow,
stage=deny_stage,
order=0,
evaluate_on_plan=False,
re_evaluate_policies=True,
)
PolicyBinding.objects.create(
policy=reputation_policy, target=deny_binding, order=0
)
# Stage 1 is an identification stage
ident_stage = IdentificationStage.objects.create(
name="ident",
user_fields=[UserFields.E_MAIL],
)
FlowStageBinding.objects.create(
target=flow,
stage=ident_stage,
order=1,
invalid_response_action=InvalidResponseAction.RESTART_WITH_CONTEXT,
)
exec_url = reverse(
"authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}
)
# First request, run the planner
response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-identification",
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"password_fields": False,
"primary_action": "Log in",
"sources": [],
"user_fields": [UserFields.E_MAIL],
},
)
response = self.client.post(
exec_url, {"uid_field": "invalid-string"}, follow=True
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-access-denied",
"error_message": None,
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"type": ChallengeTypes.NATIVE.value,
},
)

View File

@ -4,6 +4,7 @@ from typing import Any, Optional
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.cache import cache
from django.http import Http404, HttpRequest, HttpResponse, HttpResponseRedirect from django.http import Http404, HttpRequest, HttpResponse, HttpResponseRedirect
from django.http.request import QueryDict from django.http.request import QueryDict
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
@ -37,7 +38,13 @@ from authentik.flows.challenge import (
WithUserInfoChallenge, WithUserInfoChallenge,
) )
from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException
from authentik.flows.models import ConfigurableStage, Flow, FlowDesignation, Stage from authentik.flows.models import (
ConfigurableStage,
Flow,
FlowDesignation,
FlowStageBinding,
Stage,
)
from authentik.flows.planner import ( from authentik.flows.planner import (
PLAN_CONTEXT_PENDING_USER, PLAN_CONTEXT_PENDING_USER,
PLAN_CONTEXT_REDIRECT, PLAN_CONTEXT_REDIRECT,
@ -107,6 +114,7 @@ class FlowExecutorView(APIView):
flow: Flow flow: Flow
plan: Optional[FlowPlan] = None plan: Optional[FlowPlan] = None
current_binding: FlowStageBinding
current_stage: Stage current_stage: Stage
current_stage_view: View current_stage_view: View
@ -159,11 +167,12 @@ class FlowExecutorView(APIView):
request.session[SESSION_KEY_GET] = QueryDict(request.GET.get("query", "")) request.session[SESSION_KEY_GET] = QueryDict(request.GET.get("query", ""))
# We don't save the Plan after getting the next stage # We don't save the Plan after getting the next stage
# as it hasn't been successfully passed yet # as it hasn't been successfully passed yet
next_stage = self.plan.next(self.request) next_binding = self.plan.next(self.request)
if not next_stage: if not next_binding:
self._logger.debug("f(exec): no more stages, flow is done.") self._logger.debug("f(exec): no more stages, flow is done.")
return self._flow_done() return self._flow_done()
self.current_stage = next_stage self.current_binding = next_binding
self.current_stage = next_binding.stage
self._logger.debug( self._logger.debug(
"f(exec): Current stage", "f(exec): Current stage",
current_stage=self.current_stage, current_stage=self.current_stage,
@ -268,8 +277,31 @@ class FlowExecutorView(APIView):
planner = FlowPlanner(self.flow) planner = FlowPlanner(self.flow)
plan = planner.plan(self.request) plan = planner.plan(self.request)
self.request.session[SESSION_KEY_PLAN] = plan self.request.session[SESSION_KEY_PLAN] = plan
try:
# Call the has_stages getter to check that
# there are no issues with the class we might've gotten
# from the cache. If there are errors, just delete all cached flows
_ = plan.has_stages
except Exception: # pylint: disable=broad-except
keys = cache.keys("flow_*")
cache.delete_many(keys)
return self._initiate_plan()
return plan return plan
def restart_flow(self, keep_context=False) -> HttpResponse:
"""Restart the currently active flow, optionally keeping the current context"""
planner = FlowPlanner(self.flow)
default_context = None
if keep_context:
default_context = self.plan.context
plan = planner.plan(self.request, default_context)
self.request.session[SESSION_KEY_PLAN] = plan
kwargs = self.kwargs
kwargs.update({"flow_slug": self.flow.slug})
return redirect_with_qs(
"authentik_api:flow-executor", self.request.GET, **kwargs
)
def _flow_done(self) -> HttpResponse: def _flow_done(self) -> HttpResponse:
"""User Successfully passed all stages""" """User Successfully passed all stages"""
# Since this is wrapped by the ExecutorShell, the next argument is saved in the session # Since this is wrapped by the ExecutorShell, the next argument is saved in the session
@ -293,10 +325,10 @@ class FlowExecutorView(APIView):
) )
self.plan.pop() self.plan.pop()
self.request.session[SESSION_KEY_PLAN] = self.plan self.request.session[SESSION_KEY_PLAN] = self.plan
if self.plan.stages: if self.plan.bindings:
self._logger.debug( self._logger.debug(
"f(exec): Continuing with next stage", "f(exec): Continuing with next stage",
remaining=len(self.plan.stages), remaining=len(self.plan.bindings),
) )
kwargs = self.kwargs kwargs = self.kwargs
kwargs.update({"flow_slug": self.flow.slug}) kwargs.update({"flow_slug": self.flow.slug})

View File

@ -53,6 +53,27 @@ class DockerController(BaseController):
return True return True
return False return False
def _comp_ports(self, container: Container) -> bool:
"""Check that the container has the correct ports exposed. Return true if container needs
to be rebuilt."""
# with TEST enabled, we use host-network
if settings.TEST:
return False
# When the container isn't running, the API doesn't report any port mappings
if container.status != "running":
return False
# {'6379/tcp': [{'HostIp': '127.0.0.1', 'HostPort': '6379'}]}
for port in self.deployment_ports:
key = f"{port.inner_port or port.port}/{port.protocol.lower()}"
if key not in container.ports:
return True
host_matching = False
for host_port in container.ports[key]:
host_matching = host_port.get("HostPort") == port.port
if not host_matching:
return True
return False
def _get_container(self) -> tuple[Container, bool]: def _get_container(self) -> tuple[Container, bool]:
container_name = f"authentik-proxy-{self.outpost.uuid.hex}" container_name = f"authentik-proxy-{self.outpost.uuid.hex}"
try: try:
@ -98,6 +119,11 @@ class DockerController(BaseController):
) )
self.down() self.down()
return self.up() return self.up()
# Check container's ports
if self._comp_ports(container):
self.logger.info("Container has mis-matched ports, re-creating...")
self.down()
return self.up()
# Check that container values match our values # Check that container values match our values
if self._comp_env(container): if self._comp_env(container):
self.logger.info("Container has outdated config, re-creating...") self.logger.info("Container has outdated config, re-creating...")

View File

@ -406,7 +406,10 @@ class Outpost(ManagedModel):
def get_required_objects(self) -> Iterable[Union[models.Model, str]]: def get_required_objects(self) -> Iterable[Union[models.Model, str]]:
"""Get an iterator of all objects the user needs read access to""" """Get an iterator of all objects the user needs read access to"""
objects: list[Union[models.Model, str]] = [self] objects: list[Union[models.Model, str]] = [
self,
"authentik_events.add_event",
]
for provider in ( for provider in (
Provider.objects.filter(outpost=self).select_related().select_subclasses() Provider.objects.filter(outpost=self).select_related().select_subclasses()
): ):

View File

@ -33,21 +33,21 @@ class ReputationPolicy(Policy):
def passes(self, request: PolicyRequest) -> PolicyResult: def passes(self, request: PolicyRequest) -> PolicyResult:
remote_ip = get_client_ip(request.http_request) remote_ip = get_client_ip(request.http_request)
passing = True passing = False
if self.check_ip: if self.check_ip:
score = cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0) score = cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0)
passing = passing and score <= self.threshold passing += passing or score <= self.threshold
LOGGER.debug("Score for IP", ip=remote_ip, score=score, passing=passing) LOGGER.debug("Score for IP", ip=remote_ip, score=score, passing=passing)
if self.check_username: if self.check_username:
score = cache.get_or_set(CACHE_KEY_USER_PREFIX + request.user.username, 0) score = cache.get_or_set(CACHE_KEY_USER_PREFIX + request.user.username, 0)
passing = passing and score <= self.threshold passing += passing or score <= self.threshold
LOGGER.debug( LOGGER.debug(
"Score for Username", "Score for Username",
username=request.user.username, username=request.user.username,
score=score, score=score,
passing=passing, passing=passing,
) )
return PolicyResult(passing) return PolicyResult(bool(passing))
class Meta: class Meta:

View File

@ -474,7 +474,7 @@ class RefreshToken(ExpiringModel, BaseGrantModel):
now = int(time.time()) now = int(time.time())
iat_time = now iat_time = now
exp_time = int( exp_time = int(
now + timedelta_from_string(self.provider.token_validity).seconds now + timedelta_from_string(self.provider.token_validity).total_seconds()
) )
# We use the timestamp of the user's last successful login (EventAction.LOGIN) for auth_time # We use the timestamp of the user's last successful login (EventAction.LOGIN) for auth_time
auth_events = Event.objects.filter( auth_events = Event.objects.filter(

View File

@ -374,9 +374,9 @@ class OAuthFulfillmentStage(StageView):
query_fragment["code"] = code.code query_fragment["code"] = code.code
query_fragment["token_type"] = "bearer" query_fragment["token_type"] = "bearer"
query_fragment["expires_in"] = timedelta_from_string( query_fragment["expires_in"] = int(
self.provider.token_validity timedelta_from_string(self.provider.token_validity).total_seconds()
).seconds )
query_fragment["state"] = self.params.state if self.params.state else "" query_fragment["state"] = self.params.state if self.params.state else ""
return query_fragment return query_fragment
@ -468,14 +468,14 @@ class AuthorizationFlowInitView(PolicyAccessView):
# OpenID clients can specify a `prompt` parameter, and if its set to consent we # OpenID clients can specify a `prompt` parameter, and if its set to consent we
# need to inject a consent stage # need to inject a consent stage
if PROMPT_CONSNET in self.params.prompt: if PROMPT_CONSNET in self.params.prompt:
if not any(isinstance(x, ConsentStageView) for x in plan.stages): if not any(isinstance(x.stage, ConsentStageView) for x in plan.bindings):
# Plan does not have any consent stage, so we add an in-memory one # Plan does not have any consent stage, so we add an in-memory one
stage = ConsentStage( stage = ConsentStage(
name="OAuth2 Provider In-memory consent stage", name="OAuth2 Provider In-memory consent stage",
mode=ConsentMode.ALWAYS_REQUIRE, mode=ConsentMode.ALWAYS_REQUIRE,
) )
plan.append(stage) plan.append_stage(stage)
plan.append(in_memory_stage(OAuthFulfillmentStage)) plan.append_stage(in_memory_stage(OAuthFulfillmentStage))
self.request.session[SESSION_KEY_PLAN] = plan self.request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs( return redirect_with_qs(
"authentik_core:if-flow", "authentik_core:if-flow",

View File

@ -215,9 +215,11 @@ class TokenView(View):
"access_token": refresh_token.access_token, "access_token": refresh_token.access_token,
"refresh_token": refresh_token.refresh_token, "refresh_token": refresh_token.refresh_token,
"token_type": "bearer", "token_type": "bearer",
"expires_in": timedelta_from_string( "expires_in": int(
self.params.provider.token_validity timedelta_from_string(
).seconds, self.params.provider.token_validity
).total_seconds()
),
"id_token": refresh_token.provider.encode(refresh_token.id_token.to_dict()), "id_token": refresh_token.provider.encode(refresh_token.id_token.to_dict()),
} }
@ -258,9 +260,11 @@ class TokenView(View):
"access_token": refresh_token.access_token, "access_token": refresh_token.access_token,
"refresh_token": refresh_token.refresh_token, "refresh_token": refresh_token.refresh_token,
"token_type": "bearer", "token_type": "bearer",
"expires_in": timedelta_from_string( "expires_in": int(
refresh_token.provider.token_validity timedelta_from_string(
).seconds, refresh_token.provider.token_validity
).total_seconds()
),
"id_token": self.params.provider.encode(refresh_token.id_token.to_dict()), "id_token": self.params.provider.encode(refresh_token.id_token.to_dict()),
} }

View File

@ -79,7 +79,7 @@ class SAMLSSOView(PolicyAccessView):
PLAN_CONTEXT_CONSENT_PERMISSIONS: [], PLAN_CONTEXT_CONSENT_PERMISSIONS: [],
}, },
) )
plan.append(in_memory_stage(SAMLFlowFinalView)) plan.append_stage(in_memory_stage(SAMLFlowFinalView))
request.session[SESSION_KEY_PLAN] = plan request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs( return redirect_with_qs(
"authentik_core:if-flow", "authentik_core:if-flow",

View File

@ -153,6 +153,7 @@ SPECTACULAR_SETTINGS = {
"url": "https://github.com/goauthentik/authentik/blob/master/LICENSE", "url": "https://github.com/goauthentik/authentik/blob/master/LICENSE",
}, },
"ENUM_NAME_OVERRIDES": { "ENUM_NAME_OVERRIDES": {
"EventActions": "authentik.events.models.EventAction",
"ChallengeChoices": "authentik.flows.challenge.ChallengeTypes", "ChallengeChoices": "authentik.flows.challenge.ChallengeTypes",
"FlowDesignationEnum": "authentik.flows.models.FlowDesignation", "FlowDesignationEnum": "authentik.flows.models.FlowDesignation",
"PolicyEngineMode": "authentik.policies.models.PolicyEngineMode", "PolicyEngineMode": "authentik.policies.models.PolicyEngineMode",

View File

@ -90,7 +90,7 @@ class InitiateView(View):
planner.allow_empty_flows = True planner.allow_empty_flows = True
plan = planner.plan(self.request, kwargs) plan = planner.plan(self.request, kwargs)
for stage in stages_to_append: for stage in stages_to_append:
plan.append(stage) plan.append_stage(stage)
self.request.session[SESSION_KEY_PLAN] = plan self.request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs( return redirect_with_qs(
"authentik_core:if-flow", "authentik_core:if-flow",

View File

@ -63,7 +63,7 @@ class AuthenticatorDuoStageView(ChallengeStageView):
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"activation_barcode": enroll["activation_barcode"], "activation_barcode": enroll["activation_barcode"],
"activation_code": enroll["activation_code"], "activation_code": enroll["activation_code"],
"stage_uuid": stage.stage_uuid, "stage_uuid": str(stage.stage_uuid),
} }
) )

View File

@ -148,7 +148,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
stage = Stage.objects.get_subclass(pk=stage.configuration_stage.pk) stage = Stage.objects.get_subclass(pk=stage.configuration_stage.pk)
# plan.insert inserts at 1 index, so when stage_ok pops 0, # plan.insert inserts at 1 index, so when stage_ok pops 0,
# the configuration stage is next # the configuration stage is next
self.executor.plan.insert(stage) self.executor.plan.insert_stage(stage)
return self.executor.stage_ok() return self.executor.stage_ok()
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)

View File

@ -36,12 +36,14 @@ class TestCaptchaStage(TestCase):
public_key=RECAPTCHA_PUBLIC_KEY, public_key=RECAPTCHA_PUBLIC_KEY,
private_key=RECAPTCHA_PRIVATE_KEY, private_key=RECAPTCHA_PRIVATE_KEY,
) )
FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) self.binding = FlowStageBinding.objects.create(
target=self.flow, stage=self.stage, order=2
)
def test_valid(self): def test_valid(self):
"""Test valid captcha""" """Test valid captcha"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan

View File

@ -39,9 +39,11 @@ class TestConsentStage(TestCase):
stage = ConsentStage.objects.create( stage = ConsentStage.objects.create(
name="consent", mode=ConsentMode.ALWAYS_REQUIRE name="consent", mode=ConsentMode.ALWAYS_REQUIRE
) )
FlowStageBinding.objects.create(target=flow, stage=stage, order=2) binding = FlowStageBinding.objects.create(target=flow, stage=stage, order=2)
plan = FlowPlan(flow_pk=flow.pk.hex, stages=[stage], markers=[StageMarker()]) plan = FlowPlan(
flow_pk=flow.pk.hex, bindings=[binding], markers=[StageMarker()]
)
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
session.save() session.save()
@ -69,11 +71,11 @@ class TestConsentStage(TestCase):
designation=FlowDesignation.AUTHENTICATION, designation=FlowDesignation.AUTHENTICATION,
) )
stage = ConsentStage.objects.create(name="consent", mode=ConsentMode.PERMANENT) stage = ConsentStage.objects.create(name="consent", mode=ConsentMode.PERMANENT)
FlowStageBinding.objects.create(target=flow, stage=stage, order=2) binding = FlowStageBinding.objects.create(target=flow, stage=stage, order=2)
plan = FlowPlan( plan = FlowPlan(
flow_pk=flow.pk.hex, flow_pk=flow.pk.hex,
stages=[stage], bindings=[binding],
markers=[StageMarker()], markers=[StageMarker()],
context={PLAN_CONTEXT_APPLICATION: self.application}, context={PLAN_CONTEXT_APPLICATION: self.application},
) )
@ -110,11 +112,11 @@ class TestConsentStage(TestCase):
stage = ConsentStage.objects.create( stage = ConsentStage.objects.create(
name="consent", mode=ConsentMode.EXPIRING, consent_expire_in="seconds=1" name="consent", mode=ConsentMode.EXPIRING, consent_expire_in="seconds=1"
) )
FlowStageBinding.objects.create(target=flow, stage=stage, order=2) binding = FlowStageBinding.objects.create(target=flow, stage=stage, order=2)
plan = FlowPlan( plan = FlowPlan(
flow_pk=flow.pk.hex, flow_pk=flow.pk.hex,
stages=[stage], bindings=[binding],
markers=[StageMarker()], markers=[StageMarker()],
context={PLAN_CONTEXT_APPLICATION: self.application}, context={PLAN_CONTEXT_APPLICATION: self.application},
) )

View File

@ -26,12 +26,14 @@ class TestUserDenyStage(TestCase):
designation=FlowDesignation.AUTHENTICATION, designation=FlowDesignation.AUTHENTICATION,
) )
self.stage = DenyStage.objects.create(name="logout") self.stage = DenyStage.objects.create(name="logout")
FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) self.binding = FlowStageBinding.objects.create(
target=self.flow, stage=self.stage, order=2
)
def test_valid_password(self): def test_valid_password(self):
"""Test with a valid pending user and backend""" """Test with a valid pending user and backend"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan

View File

@ -34,12 +34,14 @@ class TestEmailStageSending(TestCase):
self.stage = EmailStage.objects.create( self.stage = EmailStage.objects.create(
name="email", name="email",
) )
FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) self.binding = FlowStageBinding.objects.create(
target=self.flow, stage=self.stage, order=2
)
def test_pending_user(self): def test_pending_user(self):
"""Test with pending user""" """Test with pending user"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] 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
session = self.client.session session = self.client.session
@ -67,7 +69,7 @@ class TestEmailStageSending(TestCase):
def test_send_error(self): def test_send_error(self):
"""Test error during sending (sending will be retried)""" """Test error during sending (sending will be retried)"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] 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
session = self.client.session session = self.client.session

View File

@ -35,12 +35,14 @@ class TestEmailStage(TestCase):
self.stage = EmailStage.objects.create( self.stage = EmailStage.objects.create(
name="email", name="email",
) )
FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) self.binding = FlowStageBinding.objects.create(
target=self.flow, stage=self.stage, order=2
)
def test_rendering(self): def test_rendering(self):
"""Test with pending user""" """Test with pending user"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] 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
session = self.client.session session = self.client.session
@ -56,7 +58,7 @@ class TestEmailStage(TestCase):
def test_without_user(self): def test_without_user(self):
"""Test without pending user""" """Test without pending user"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
@ -71,7 +73,7 @@ class TestEmailStage(TestCase):
def test_pending_user(self): def test_pending_user(self):
"""Test with pending user""" """Test with pending user"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] 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
session = self.client.session session = self.client.session
@ -102,7 +104,7 @@ class TestEmailStage(TestCase):
# Make sure token exists # Make sure token exists
self.test_pending_user() self.test_pending_user()
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan

View File

@ -85,6 +85,18 @@ class IdentificationChallengeResponse(ChallengeResponse):
identification_failed.send( identification_failed.send(
sender=self, request=self.stage.request, uid_field=uid_field sender=self, request=self.stage.request, uid_field=uid_field
) )
# We set the pending_user even on failure so it's part of the context, even
# when the input is invalid
# This is so its part of the current flow plan, and on flow restart can be kept, and
# policies can be applied.
self.stage.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User(
username=uid_field,
email=uid_field,
)
if not current_stage.show_matched_user:
self.stage.executor.plan.context[
PLAN_CONTEXT_PENDING_USER_IDENTIFIER
] = uid_field
raise ValidationError("Failed to authenticate.") raise ValidationError("Failed to authenticate.")
self.pre_user = pre_user self.pre_user = pre_user
if not current_stage.password_stage: if not current_stage.password_stage:

View File

@ -35,7 +35,9 @@ class TestUserLoginStage(TestCase):
designation=FlowDesignation.AUTHENTICATION, designation=FlowDesignation.AUTHENTICATION,
) )
self.stage = InvitationStage.objects.create(name="invitation") self.stage = InvitationStage.objects.create(name="invitation")
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(
"authentik.flows.views.to_stage_response", "authentik.flows.views.to_stage_response",
@ -44,7 +46,7 @@ class TestUserLoginStage(TestCase):
def test_without_invitation_fail(self): def test_without_invitation_fail(self):
"""Test without any invitation, continue_flow_without_invitation not set.""" """Test without any invitation, continue_flow_without_invitation not set."""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] 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_DJANGO
@ -75,7 +77,7 @@ class TestUserLoginStage(TestCase):
self.stage.continue_flow_without_invitation = True self.stage.continue_flow_without_invitation = True
self.stage.save() self.stage.save()
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] 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_DJANGO
@ -103,7 +105,7 @@ class TestUserLoginStage(TestCase):
def test_with_invitation_get(self): def test_with_invitation_get(self):
"""Test with invitation, check data in session""" """Test with invitation, check data in session"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
@ -143,7 +145,7 @@ class TestUserLoginStage(TestCase):
) )
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
plan.context[PLAN_CONTEXT_PROMPT] = {INVITATION_TOKEN_KEY: invite.pk.hex} plan.context[PLAN_CONTEXT_PROMPT] = {INVITATION_TOKEN_KEY: invite.pk.hex}
session = self.client.session session = self.client.session

View File

@ -39,7 +39,9 @@ class TestPasswordStage(TestCase):
self.stage = PasswordStage.objects.create( self.stage = PasswordStage.objects.create(
name="password", backends=[BACKEND_DJANGO] name="password", backends=[BACKEND_DJANGO]
) )
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(
"authentik.flows.views.to_stage_response", "authentik.flows.views.to_stage_response",
@ -48,7 +50,7 @@ class TestPasswordStage(TestCase):
def test_without_user(self): def test_without_user(self):
"""Test without user""" """Test without user"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
@ -84,7 +86,7 @@ class TestPasswordStage(TestCase):
) )
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
@ -101,7 +103,7 @@ class TestPasswordStage(TestCase):
def test_valid_password(self): def test_valid_password(self):
"""Test with a valid pending user and valid password""" """Test with a valid pending user and valid password"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] 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
session = self.client.session session = self.client.session
@ -129,7 +131,7 @@ class TestPasswordStage(TestCase):
def test_invalid_password(self): def test_invalid_password(self):
"""Test with a valid pending user and invalid password""" """Test with a valid pending user and invalid password"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] 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
session = self.client.session session = self.client.session
@ -148,7 +150,7 @@ class TestPasswordStage(TestCase):
def test_invalid_password_lockout(self): def test_invalid_password_lockout(self):
"""Test with a valid pending user and invalid password (trigger logout counter)""" """Test with a valid pending user and invalid password (trigger logout counter)"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] 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
session = self.client.session session = self.client.session
@ -189,7 +191,7 @@ class TestPasswordStage(TestCase):
"""Test with a valid pending user and valid password. """Test with a valid pending user and valid password.
Backend is patched to return PermissionError""" Backend is patched to return PermissionError"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] 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
session = self.client.session session = self.client.session

View File

@ -90,6 +90,14 @@ class PromptChallengeResponse(ChallengeResponse):
raise ValidationError(_("Passwords don't match.")) raise ValidationError(_("Passwords don't match."))
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
# Check if we have any static or hidden fields, and ensure they
# still have the same value
static_hidden_fields: QuerySet[Prompt] = self.stage.fields.filter(
type__in=[FieldTypes.HIDDEN, FieldTypes.STATIC]
)
for static_hidden in static_hidden_fields:
attrs[static_hidden.field_key] = static_hidden.placeholder
# Check if we have two password fields, and make sure they are the same # Check if we have two password fields, and make sure they are the same
password_fields: QuerySet[Prompt] = self.stage.fields.filter( password_fields: QuerySet[Prompt] = self.stage.fields.filter(
type=FieldTypes.PASSWORD type=FieldTypes.PASSWORD

View File

@ -78,6 +78,12 @@ class TestPromptStage(TestCase):
required=True, required=True,
placeholder="HIDDEN_PLACEHOLDER", placeholder="HIDDEN_PLACEHOLDER",
) )
static_prompt = Prompt.objects.create(
field_key="static_prompt",
type=FieldTypes.STATIC,
required=True,
placeholder="static",
)
self.stage = PromptStage.objects.create(name="prompt-stage") self.stage = PromptStage.objects.create(name="prompt-stage")
self.stage.fields.set( self.stage.fields.set(
[ [
@ -88,6 +94,7 @@ class TestPromptStage(TestCase):
password2_prompt, password2_prompt,
number_prompt, number_prompt,
hidden_prompt, hidden_prompt,
static_prompt,
] ]
) )
self.stage.save() self.stage.save()
@ -100,14 +107,17 @@ class TestPromptStage(TestCase):
password2_prompt.field_key: "test", password2_prompt.field_key: "test",
number_prompt.field_key: 3, number_prompt.field_key: 3,
hidden_prompt.field_key: hidden_prompt.placeholder, hidden_prompt.field_key: hidden_prompt.placeholder,
static_prompt.field_key: static_prompt.placeholder,
} }
FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) self.binding = FlowStageBinding.objects.create(
target=self.flow, stage=self.stage, order=2
)
def test_render(self): def test_render(self):
"""Test render of form, check if all prompts are rendered correctly""" """Test render of form, check if all prompts are rendered correctly"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
@ -125,7 +135,7 @@ class TestPromptStage(TestCase):
def test_valid_challenge_with_policy(self) -> PromptChallengeResponse: def test_valid_challenge_with_policy(self) -> PromptChallengeResponse:
"""Test challenge_response validation""" """Test challenge_response validation"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
expr = "return request.context['password_prompt'] == request.context['password2_prompt']" expr = "return request.context['password_prompt'] == request.context['password2_prompt']"
expr_policy = ExpressionPolicy.objects.create( expr_policy = ExpressionPolicy.objects.create(
@ -142,7 +152,7 @@ class TestPromptStage(TestCase):
def test_invalid_challenge(self) -> PromptChallengeResponse: def test_invalid_challenge(self) -> PromptChallengeResponse:
"""Test challenge_response validation""" """Test challenge_response validation"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
expr = "False" expr = "False"
expr_policy = ExpressionPolicy.objects.create( expr_policy = ExpressionPolicy.objects.create(
@ -159,7 +169,7 @@ class TestPromptStage(TestCase):
def test_valid_challenge_request(self): def test_valid_challenge_request(self):
"""Test a request with valid challenge_response data""" """Test a request with valid challenge_response data"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
@ -196,7 +206,7 @@ class TestPromptStage(TestCase):
def test_invalid_password(self): def test_invalid_password(self):
"""Test challenge_response validation""" """Test challenge_response validation"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
self.prompt_data["password2_prompt"] = "qwerqwerqr" self.prompt_data["password2_prompt"] = "qwerqwerqr"
challenge_response = PromptChallengeResponse( challenge_response = PromptChallengeResponse(
@ -215,7 +225,7 @@ class TestPromptStage(TestCase):
def test_invalid_username(self): def test_invalid_username(self):
"""Test challenge_response validation""" """Test challenge_response validation"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
self.prompt_data["username_prompt"] = "akadmin" self.prompt_data["username_prompt"] = "akadmin"
challenge_response = PromptChallengeResponse( challenge_response = PromptChallengeResponse(
@ -230,3 +240,17 @@ class TestPromptStage(TestCase):
] ]
}, },
) )
def test_static_hidden_overwrite(self):
"""Test that static and hidden fields ignore any value sent to them"""
plan = FlowPlan(
flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
)
self.prompt_data["hidden_prompt"] = "foo"
self.prompt_data["static_prompt"] = "foo"
challenge_response = PromptChallengeResponse(
None, stage=self.stage, plan=plan, data=self.prompt_data
)
self.assertEqual(challenge_response.is_valid(), True)
self.assertNotEqual(challenge_response.validated_data["hidden_prompt"], "foo")
self.assertNotEqual(challenge_response.validated_data["static_prompt"], "foo")

View File

@ -30,7 +30,9 @@ class TestUserDeleteStage(TestCase):
designation=FlowDesignation.AUTHENTICATION, designation=FlowDesignation.AUTHENTICATION,
) )
self.stage = UserDeleteStage.objects.create(name="delete") self.stage = UserDeleteStage.objects.create(name="delete")
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(
"authentik.flows.views.to_stage_response", "authentik.flows.views.to_stage_response",
@ -39,7 +41,7 @@ class TestUserDeleteStage(TestCase):
def test_no_user(self): def test_no_user(self):
"""Test without user set""" """Test without user set"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
@ -66,7 +68,7 @@ class TestUserDeleteStage(TestCase):
def test_user_delete_get(self): def test_user_delete_get(self):
"""Test Form render""" """Test Form render"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] 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
session = self.client.session session = self.client.session

View File

@ -30,12 +30,14 @@ class TestUserLoginStage(TestCase):
designation=FlowDesignation.AUTHENTICATION, designation=FlowDesignation.AUTHENTICATION,
) )
self.stage = UserLoginStage.objects.create(name="login") self.stage = UserLoginStage.objects.create(name="login")
FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) self.binding = FlowStageBinding.objects.create(
target=self.flow, stage=self.stage, order=2
)
def test_valid_password(self): def test_valid_password(self):
"""Test with a valid pending user and backend""" """Test with a valid pending user and backend"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] 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
session = self.client.session session = self.client.session
@ -61,7 +63,7 @@ class TestUserLoginStage(TestCase):
self.stage.session_duration = "seconds=2" self.stage.session_duration = "seconds=2"
self.stage.save() self.stage.save()
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] 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
session = self.client.session session = self.client.session
@ -92,7 +94,7 @@ class TestUserLoginStage(TestCase):
def test_without_user(self): def test_without_user(self):
"""Test a plan without any pending user, resulting in a denied""" """Test a plan without any pending user, resulting in a denied"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan

View File

@ -28,12 +28,14 @@ class TestUserLogoutStage(TestCase):
designation=FlowDesignation.AUTHENTICATION, designation=FlowDesignation.AUTHENTICATION,
) )
self.stage = UserLogoutStage.objects.create(name="logout") self.stage = UserLogoutStage.objects.create(name="logout")
FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) self.binding = FlowStageBinding.objects.create(
target=self.flow, stage=self.stage, order=2
)
def test_valid_password(self): def test_valid_password(self):
"""Test with a valid pending user and backend""" """Test with a valid pending user and backend"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] 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_DJANGO

View File

@ -12,7 +12,7 @@ class UserWriteStageSerializer(StageSerializer):
class Meta: class Meta:
model = UserWriteStage model = UserWriteStage
fields = StageSerializer.Meta.fields fields = StageSerializer.Meta.fields + ["create_users_as_inactive"]
class UserWriteStageViewSet(UsedByMixin, ModelViewSet): class UserWriteStageViewSet(UsedByMixin, ModelViewSet):

View File

@ -0,0 +1,21 @@
# Generated by Django 3.2.4 on 2021-06-28 20:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_user_write", "0002_auto_20200918_1653"),
]
operations = [
migrations.AddField(
model_name="userwritestage",
name="create_users_as_inactive",
field=models.BooleanField(
default=False,
help_text="When set, newly created users are inactive and cannot login.",
),
),
]

View File

@ -1,6 +1,7 @@
"""write stage models""" """write stage models"""
from typing import Type from typing import Type
from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer from rest_framework.serializers import BaseSerializer
@ -12,6 +13,11 @@ class UserWriteStage(Stage):
"""Writes currently pending data into the pending user, or if no user exists, """Writes currently pending data into the pending user, or if no user exists,
creates a new user with the data.""" creates a new user with the data."""
create_users_as_inactive = models.BooleanField(
default=False,
help_text=_("When set, newly created users are inactive and cannot login."),
)
@property @property
def serializer(self) -> BaseSerializer: def serializer(self) -> BaseSerializer:
from authentik.stages.user_write.api import UserWriteStageSerializer from authentik.stages.user_write.api import UserWriteStageSerializer

View File

@ -35,7 +35,9 @@ class UserWriteStageView(StageView):
data = self.executor.plan.context[PLAN_CONTEXT_PROMPT] data = self.executor.plan.context[PLAN_CONTEXT_PROMPT]
user_created = False user_created = False
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context: if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
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
)
self.executor.plan.context[ self.executor.plan.context[
PLAN_CONTEXT_AUTHENTICATION_BACKEND PLAN_CONTEXT_AUTHENTICATION_BACKEND
] = class_to_path(ModelBackend) ] = class_to_path(ModelBackend)

View File

@ -37,7 +37,9 @@ class TestUserWriteStage(TestCase):
designation=FlowDesignation.AUTHENTICATION, designation=FlowDesignation.AUTHENTICATION,
) )
self.stage = UserWriteStage.objects.create(name="write") self.stage = UserWriteStage.objects.create(name="write")
FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) self.binding = FlowStageBinding.objects.create(
target=self.flow, stage=self.stage, order=2
)
self.source = Source.objects.create(name="fake_source") self.source = Source.objects.create(name="fake_source")
def test_user_create(self): def test_user_create(self):
@ -48,7 +50,7 @@ class TestUserWriteStage(TestCase):
) )
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
plan.context[PLAN_CONTEXT_PROMPT] = { plan.context[PLAN_CONTEXT_PROMPT] = {
"username": "test-user", "username": "test-user",
@ -92,7 +94,7 @@ class TestUserWriteStage(TestCase):
for _ in range(8) for _ in range(8)
) )
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
plan.context[PLAN_CONTEXT_PENDING_USER] = User.objects.create( plan.context[PLAN_CONTEXT_PENDING_USER] = User.objects.create(
username="unittest", email="test@beryju.org" username="unittest", email="test@beryju.org"
@ -135,7 +137,7 @@ class TestUserWriteStage(TestCase):
def test_without_data(self): def test_without_data(self):
"""Test without data results in error""" """Test without data results in error"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
@ -167,7 +169,7 @@ class TestUserWriteStage(TestCase):
def test_blank_username(self): def test_blank_username(self):
"""Test with blank username results in error""" """Test with blank username results in error"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
session = self.client.session session = self.client.session
plan.context[PLAN_CONTEXT_PROMPT] = { plan.context[PLAN_CONTEXT_PROMPT] = {
@ -204,7 +206,7 @@ class TestUserWriteStage(TestCase):
def test_duplicate_data(self): def test_duplicate_data(self):
"""Test with duplicate data, should trigger error""" """Test with duplicate data, should trigger error"""
plan = FlowPlan( plan = FlowPlan(
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
) )
session = self.client.session session = self.client.session
plan.context[PLAN_CONTEXT_PROMPT] = { plan.context[PLAN_CONTEXT_PROMPT] = {

View File

@ -54,6 +54,9 @@ class CurrentTenantSerializer(PassiveSerializer):
default=CONFIG.y("footer_links", []), default=CONFIG.y("footer_links", []),
) )
flow_authentication = CharField(source="flow_authentication.slug", required=False)
flow_invalidation = CharField(source="flow_invalidation.slug", required=False)
flow_recovery = CharField(source="flow_recovery.slug", required=False)
flow_unenrollment = CharField(source="flow_unenrollment.slug", required=False) flow_unenrollment = CharField(source="flow_unenrollment.slug", required=False)

View File

@ -20,6 +20,8 @@ class TestTenants(TestCase):
"branding_title": "authentik", "branding_title": "authentik",
"matched_domain": "authentik-default", "matched_domain": "authentik-default",
"ui_footer_links": CONFIG.y("footer_links"), "ui_footer_links": CONFIG.y("footer_links"),
"flow_authentication": "default-authentication-flow",
"flow_invalidation": "default-invalidation-flow",
}, },
) )

View File

@ -99,15 +99,15 @@ func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry {
} }
if *u.IsActive { if *u.IsActive {
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}}) attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}})
} else { } else {
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}}) attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}})
} }
if u.IsSuperuser { if u.IsSuperuser {
attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"inactive"}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"active"}}) attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"active"}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"inactive"}})
} }
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberOf", Values: pi.GroupsForUser(u)}) attrs = append(attrs, &ldap.EntryAttribute{Name: "memberOf", Values: pi.GroupsForUser(u)})

View File

@ -3572,6 +3572,37 @@ paths:
$ref: '#/components/schemas/ValidationError' $ref: '#/components/schemas/ValidationError'
'403': '403':
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
post:
operationId: events_events_create
description: Event Read-Only Viewset
tags:
- events
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EventRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/EventRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/EventRequest'
required: true
security:
- authentik: []
- cookieAuth: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/Event'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/api/v2beta/events/events/{event_uuid}/: /api/v2beta/events/events/{event_uuid}/:
get: get:
operationId: events_events_retrieve operationId: events_events_retrieve
@ -3600,6 +3631,106 @@ paths:
$ref: '#/components/schemas/ValidationError' $ref: '#/components/schemas/ValidationError'
'403': '403':
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
put:
operationId: events_events_update
description: Event Read-Only Viewset
parameters:
- in: path
name: event_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Event.
required: true
tags:
- events
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EventRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/EventRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/EventRequest'
required: true
security:
- authentik: []
- cookieAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Event'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
patch:
operationId: events_events_partial_update
description: Event Read-Only Viewset
parameters:
- in: path
name: event_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Event.
required: true
tags:
- events
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedEventRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/PatchedEventRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/PatchedEventRequest'
security:
- authentik: []
- cookieAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Event'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
delete:
operationId: events_events_destroy
description: Event Read-Only Viewset
parameters:
- in: path
name: event_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Event.
required: true
tags:
- events
security:
- authentik: []
- cookieAuth: []
responses:
'204':
description: No response body
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/api/v2beta/events/events/actions/: /api/v2beta/events/events/actions/:
get: get:
operationId: events_events_actions_list operationId: events_events_actions_list
@ -4441,6 +4572,18 @@ paths:
schema: schema:
type: string type: string
format: uuid format: uuid
- in: query
name: invalid_response_action
schema:
type: string
enum:
- restart
- restart_with_context
- retry
description: Configure how the flow executor should handle an invalid response
to a challenge. RETRY returns the error message and a similar challenge
to the executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT
restarts the flow while keeping the current context.
- in: query - in: query
name: order name: order
schema: schema:
@ -18759,6 +18902,12 @@ components:
name: Documentation name: Documentation
- href: https://goauthentik.io/ - href: https://goauthentik.io/
name: authentik Website name: authentik Website
flow_authentication:
type: string
flow_invalidation:
type: string
flow_recovery:
type: string
flow_unenrollment: flow_unenrollment:
type: string type: string
required: required:
@ -19242,7 +19391,7 @@ components:
type: object type: object
additionalProperties: {} additionalProperties: {}
action: action:
type: string $ref: '#/components/schemas/EventActions'
app: app:
type: string type: string
context: context:
@ -19266,6 +19415,34 @@ components:
- app - app
- created - created
- pk - pk
EventActions:
enum:
- login
- login_failed
- logout
- user_write
- suspicious_request
- password_set
- secret_view
- invitation_used
- authorize_application
- source_linked
- impersonation_started
- impersonation_ended
- policy_execution
- policy_exception
- property_mapping_exception
- system_task_execution
- system_task_exception
- system_exception
- configuration_error
- model_created
- model_updated
- model_deleted
- email_sent
- update_available
- custom_
type: string
EventMatcherPolicy: EventMatcherPolicy:
type: object type: object
description: Event Matcher Policy Serializer description: Event Matcher Policy Serializer
@ -19296,7 +19473,7 @@ components:
readOnly: true readOnly: true
action: action:
allOf: allOf:
- $ref: '#/components/schemas/EventMatcherPolicyActionEnum' - $ref: '#/components/schemas/EventActions'
description: Match created events with this action type. When left empty, description: Match created events with this action type. When left empty,
all action types will be matched. all action types will be matched.
client_ip: client_ip:
@ -19314,34 +19491,6 @@ components:
- pk - pk
- verbose_name - verbose_name
- verbose_name_plural - verbose_name_plural
EventMatcherPolicyActionEnum:
enum:
- login
- login_failed
- logout
- user_write
- suspicious_request
- password_set
- secret_view
- invitation_used
- authorize_application
- source_linked
- impersonation_started
- impersonation_ended
- policy_execution
- policy_exception
- property_mapping_exception
- system_task_execution
- system_task_exception
- system_exception
- configuration_error
- model_created
- model_updated
- model_deleted
- email_sent
- update_available
- custom_
type: string
EventMatcherPolicyRequest: EventMatcherPolicyRequest:
type: object type: object
description: Event Matcher Policy Serializer description: Event Matcher Policy Serializer
@ -19355,7 +19504,7 @@ components:
will be logged. By default, only execution errors are logged. will be logged. By default, only execution errors are logged.
action: action:
allOf: allOf:
- $ref: '#/components/schemas/EventMatcherPolicyActionEnum' - $ref: '#/components/schemas/EventActions'
description: Match created events with this action type. When left empty, description: Match created events with this action type. When left empty,
all action types will be matched. all action types will be matched.
client_ip: client_ip:
@ -19375,7 +19524,7 @@ components:
type: object type: object
additionalProperties: {} additionalProperties: {}
action: action:
type: string $ref: '#/components/schemas/EventActions'
app: app:
type: string type: string
context: context:
@ -19673,6 +19822,13 @@ components:
minimum: -2147483648 minimum: -2147483648
policy_engine_mode: policy_engine_mode:
$ref: '#/components/schemas/PolicyEngineMode' $ref: '#/components/schemas/PolicyEngineMode'
invalid_response_action:
allOf:
- $ref: '#/components/schemas/InvalidResponseActionEnum'
description: Configure how the flow executor should handle an invalid response
to a challenge. RETRY returns the error message and a similar challenge
to the executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT
restarts the flow while keeping the current context.
required: required:
- order - order
- pk - pk
@ -19703,6 +19859,13 @@ components:
minimum: -2147483648 minimum: -2147483648
policy_engine_mode: policy_engine_mode:
$ref: '#/components/schemas/PolicyEngineMode' $ref: '#/components/schemas/PolicyEngineMode'
invalid_response_action:
allOf:
- $ref: '#/components/schemas/InvalidResponseActionEnum'
description: Configure how the flow executor should handle an invalid response
to a challenge. RETRY returns the error message and a similar challenge
to the executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT
restarts the flow while keeping the current context.
required: required:
- order - order
- stage - stage
@ -20048,6 +20211,12 @@ components:
- api - api
- recovery - recovery
type: string type: string
InvalidResponseActionEnum:
enum:
- retry
- restart
- restart_with_context
type: string
Invitation: Invitation:
type: object type: object
description: Invitation Serializer description: Invitation Serializer
@ -24445,7 +24614,7 @@ components:
will be logged. By default, only execution errors are logged. will be logged. By default, only execution errors are logged.
action: action:
allOf: allOf:
- $ref: '#/components/schemas/EventMatcherPolicyActionEnum' - $ref: '#/components/schemas/EventActions'
description: Match created events with this action type. When left empty, description: Match created events with this action type. When left empty,
all action types will be matched. all action types will be matched.
client_ip: client_ip:
@ -24457,6 +24626,29 @@ components:
- $ref: '#/components/schemas/AppEnum' - $ref: '#/components/schemas/AppEnum'
description: Match events created by selected application. When left empty, description: Match events created by selected application. When left empty,
all applications are matched. all applications are matched.
PatchedEventRequest:
type: object
description: Event Serializer
properties:
user:
type: object
additionalProperties: {}
action:
$ref: '#/components/schemas/EventActions'
app:
type: string
context:
type: object
additionalProperties: {}
client_ip:
type: string
nullable: true
expires:
type: string
format: date-time
tenant:
type: object
additionalProperties: {}
PatchedExpressionPolicyRequest: PatchedExpressionPolicyRequest:
type: object type: object
description: Group Membership Policy Serializer description: Group Membership Policy Serializer
@ -24518,6 +24710,13 @@ components:
minimum: -2147483648 minimum: -2147483648
policy_engine_mode: policy_engine_mode:
$ref: '#/components/schemas/PolicyEngineMode' $ref: '#/components/schemas/PolicyEngineMode'
invalid_response_action:
allOf:
- $ref: '#/components/schemas/InvalidResponseActionEnum'
description: Configure how the flow executor should handle an invalid response
to a challenge. RETRY returns the error message and a similar challenge
to the executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT
restarts the flow while keeping the current context.
PatchedGroupRequest: PatchedGroupRequest:
type: object type: object
description: Group Serializer description: Group Serializer
@ -25603,6 +25802,9 @@ components:
type: array type: array
items: items:
$ref: '#/components/schemas/FlowRequest' $ref: '#/components/schemas/FlowRequest'
create_users_as_inactive:
type: boolean
description: When set, newly created users are inactive and cannot login.
PatchedWebAuthnDeviceRequest: PatchedWebAuthnDeviceRequest:
type: object type: object
description: Serializer for WebAuthn authenticator devices description: Serializer for WebAuthn authenticator devices
@ -28097,6 +28299,9 @@ components:
type: array type: array
items: items:
$ref: '#/components/schemas/Flow' $ref: '#/components/schemas/Flow'
create_users_as_inactive:
type: boolean
description: When set, newly created users are inactive and cannot login.
required: required:
- component - component
- name - name
@ -28113,6 +28318,9 @@ components:
type: array type: array
items: items:
$ref: '#/components/schemas/FlowRequest' $ref: '#/components/schemas/FlowRequest'
create_users_as_inactive:
type: boolean
description: When set, newly created users are inactive and cannot login.
required: required:
- name - name
ValidationError: ValidationError:

348
web/package-lock.json generated
View File

@ -18,24 +18,24 @@
"@lingui/cli": "^3.10.2", "@lingui/cli": "^3.10.2",
"@lingui/core": "^3.10.4", "@lingui/core": "^3.10.4",
"@lingui/macro": "^3.10.2", "@lingui/macro": "^3.10.2",
"@patternfly/patternfly": "^4.108.2", "@patternfly/patternfly": "^4.115.2",
"@polymer/iron-form": "^3.0.1", "@polymer/iron-form": "^3.0.1",
"@polymer/paper-input": "^3.2.1", "@polymer/paper-input": "^3.2.1",
"@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-replace": "^2.4.2", "@rollup/plugin-replace": "^2.4.2",
"@rollup/plugin-typescript": "^8.2.1", "@rollup/plugin-typescript": "^8.2.1",
"@sentry/browser": "^6.7.2", "@sentry/browser": "^6.8.0",
"@sentry/tracing": "^6.7.2", "@sentry/tracing": "^6.8.0",
"@types/chart.js": "^2.9.32", "@types/chart.js": "^2.9.32",
"@types/codemirror": "5.60.0", "@types/codemirror": "5.60.1",
"@types/grecaptcha": "^3.0.2", "@types/grecaptcha": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^4.28.0", "@typescript-eslint/eslint-plugin": "^4.28.1",
"@typescript-eslint/parser": "^4.28.0", "@typescript-eslint/parser": "^4.28.1",
"@webcomponents/webcomponentsjs": "^2.5.0", "@webcomponents/webcomponentsjs": "^2.5.0",
"authentik-api": "file:api", "authentik-api": "file:api",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"chart.js": "^3.3.2", "chart.js": "^3.4.0",
"chartjs-adapter-moment": "^1.0.0", "chartjs-adapter-moment": "^1.0.0",
"codemirror": "^5.62.0", "codemirror": "^5.62.0",
"construct-style-sheets-polyfill": "^2.4.16", "construct-style-sheets-polyfill": "^2.4.16",
@ -48,7 +48,7 @@
"lit-html": "^1.4.1", "lit-html": "^1.4.1",
"moment": "^2.29.1", "moment": "^2.29.1",
"rapidoc": "^9.0.0", "rapidoc": "^9.0.0",
"rollup": "^2.52.2", "rollup": "^2.52.3",
"rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-copy": "^3.4.0", "rollup-plugin-copy": "^3.4.0",
"rollup-plugin-cssimport": "^1.0.2", "rollup-plugin-cssimport": "^1.0.2",
@ -2120,9 +2120,9 @@
} }
}, },
"node_modules/@patternfly/patternfly": { "node_modules/@patternfly/patternfly": {
"version": "4.108.2", "version": "4.115.2",
"resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.108.2.tgz", "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.115.2.tgz",
"integrity": "sha512-z0VB+1CXcH+eoClYQABwapX5FURSvm1nPr6asLWwg/Z4Wuxs0RjZpC6Gb+KRm8nGQwSAcMKZY1jLfPqVnznQnw==" "integrity": "sha512-7hbJ4pRmj+rlXclD2F/UwceO6fS+9flGsgHc4eUc7NyTN2GXl6PLcqrjE2CtiKEPV90+KwsGQGJXZj8bz9HweA=="
}, },
"node_modules/@polymer/font-roboto": { "node_modules/@polymer/font-roboto": {
"version": "3.0.2", "version": "3.0.2",
@ -2314,13 +2314,13 @@
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="
}, },
"node_modules/@sentry/browser": { "node_modules/@sentry/browser": {
"version": "6.7.2", "version": "6.8.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.8.0.tgz",
"integrity": "sha512-Lv0Ne1QcesyGAhVcQDfQa3hDPR/MhPSDTMg3xFi+LxqztchVc4w/ynzR0wCZFb8KIHpTj5SpJHfxpDhXYMtS9g==", "integrity": "sha512-nxa71csHlG5sMHUxI4e4xxuCWtbCv/QbBfMsYw7ncJSfCKG3yNlCVh8NJ7NS0rZW/MJUT6S6+r93zw0HetNDOA==",
"dependencies": { "dependencies": {
"@sentry/core": "6.7.2", "@sentry/core": "6.8.0",
"@sentry/types": "6.7.2", "@sentry/types": "6.8.0",
"@sentry/utils": "6.7.2", "@sentry/utils": "6.8.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2333,14 +2333,14 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/core": { "node_modules/@sentry/core": {
"version": "6.7.2", "version": "6.8.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.8.0.tgz",
"integrity": "sha512-NTZqwN5nR94yrXmSfekoPs1mIFuKvf8esdIW/DadwSKWAdLJwQTJY9xK/8PQv+SEzd7wiitPAx+mCw2By1xiNQ==", "integrity": "sha512-vJzWt/znEB+JqVwtwfjkRrAYRN+ep+l070Ti8GhJnvwU4IDtVlV3T/jVNrj6rl6UChcczaJQMxVxtG5x0crlAA==",
"dependencies": { "dependencies": {
"@sentry/hub": "6.7.2", "@sentry/hub": "6.8.0",
"@sentry/minimal": "6.7.2", "@sentry/minimal": "6.8.0",
"@sentry/types": "6.7.2", "@sentry/types": "6.8.0",
"@sentry/utils": "6.7.2", "@sentry/utils": "6.8.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2353,12 +2353,12 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/hub": { "node_modules/@sentry/hub": {
"version": "6.7.2", "version": "6.8.0",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.8.0.tgz",
"integrity": "sha512-05qVW6ymChJsXag4+fYCQokW3AcABIgcqrVYZUBf6GMU/Gbz5SJqpV7y1+njwWvnPZydMncP9LaDVpMKbE7UYQ==", "integrity": "sha512-hFrI2Ss1fTov7CH64FJpigqRxH7YvSnGeqxT9Jc1BL7nzW/vgCK+Oh2mOZbosTcrzoDv+lE8ViOnSN3w/fo+rg==",
"dependencies": { "dependencies": {
"@sentry/types": "6.7.2", "@sentry/types": "6.8.0",
"@sentry/utils": "6.7.2", "@sentry/utils": "6.8.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2371,12 +2371,12 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/minimal": { "node_modules/@sentry/minimal": {
"version": "6.7.2", "version": "6.8.0",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.8.0.tgz",
"integrity": "sha512-jkpwFv2GFHoVl5vnK+9/Q+Ea8eVdbJ3hn3/Dqq9MOLFnVK7ED6MhdHKLT79puGSFj+85OuhM5m2Q44mIhyS5mw==", "integrity": "sha512-MRxUKXiiYwKjp8mOQMpTpEuIby1Jh3zRTU0cmGZtfsZ38BC1JOle8xlwC4FdtOH+VvjSYnPBMya5lgNHNPUJDQ==",
"dependencies": { "dependencies": {
"@sentry/hub": "6.7.2", "@sentry/hub": "6.8.0",
"@sentry/types": "6.7.2", "@sentry/types": "6.8.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2389,14 +2389,14 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/tracing": { "node_modules/@sentry/tracing": {
"version": "6.7.2", "version": "6.8.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.8.0.tgz",
"integrity": "sha512-juKlI7FICKONWJFJxDxerj0A+8mNRhmtrdR+OXFqOkqSAy/QXlSFZcA/j//O19k2CfwK1BrvoMcQ/4gnffUOVg==", "integrity": "sha512-3gDkQnmOuOjHz5rY7BOatLEUksANU3efR8wuBa2ujsPQvoLSLFuyZpRjPPsxuUHQOqAYIbSNAoDloXECvQeHjw==",
"dependencies": { "dependencies": {
"@sentry/hub": "6.7.2", "@sentry/hub": "6.8.0",
"@sentry/minimal": "6.7.2", "@sentry/minimal": "6.8.0",
"@sentry/types": "6.7.2", "@sentry/types": "6.8.0",
"@sentry/utils": "6.7.2", "@sentry/utils": "6.8.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2409,19 +2409,19 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/types": { "node_modules/@sentry/types": {
"version": "6.7.2", "version": "6.8.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.8.0.tgz",
"integrity": "sha512-h21Go/PfstUN+ZV6SbwRSZVg9GXRJWdLfHoO5PSVb3TVEMckuxk8tAE57/u+UZDwX8wu+Xyon2TgsKpiWKxqUg==", "integrity": "sha512-PbSxqlh6Fd5thNU5f8EVYBVvX+G7XdPA+ThNb2QvSK8yv3rIf0McHTyF6sIebgJ38OYN7ZFK7vvhC/RgSAfYTA==",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/@sentry/utils": { "node_modules/@sentry/utils": {
"version": "6.7.2", "version": "6.8.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.8.0.tgz",
"integrity": "sha512-9COL7aaBbe61Hp5BlArtXZ1o/cxli1NGONLPrVT4fMyeQFmLonhUiy77NdsW19XnvhvaA+2IoV5dg3dnFiF/og==", "integrity": "sha512-OYlI2JNrcWKMdvYbWNdQwR4QBVv2V0y5wK0U6f53nArv6RsyO5TzwRu5rMVSIZofUUqjoE5hl27jqnR+vpUrsA==",
"dependencies": { "dependencies": {
"@sentry/types": "6.7.2", "@sentry/types": "6.8.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2451,9 +2451,9 @@
} }
}, },
"node_modules/@types/codemirror": { "node_modules/@types/codemirror": {
"version": "5.60.0", "version": "5.60.1",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.0.tgz", "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.1.tgz",
"integrity": "sha512-xgzXZyCzedLRNC67/Nn8rpBtTFnAsX2C+Q/LGoH6zgcpF/LqdNHJMHEOhqT1bwUcSp6kQdOIuKzRbeW9DYhEhg==", "integrity": "sha512-yV14LQ5VvghnW0uSuCw2bEfZC6NvxHQEckl2w3dEk5l0yPGzQh14dCaWvG5KD/2l3cgFSifR+6nIUD7LDLdUTg==",
"dependencies": { "dependencies": {
"@types/tern": "*" "@types/tern": "*"
} }
@ -2579,12 +2579,12 @@
"integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA=="
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "4.28.0", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.1.tgz",
"integrity": "sha512-KcF6p3zWhf1f8xO84tuBailV5cN92vhS+VT7UJsPzGBm9VnQqfI9AsiMUFUCYHTYPg1uCCo+HyiDnpDuvkAMfQ==", "integrity": "sha512-9yfcNpDaNGQ6/LQOX/KhUFTR1sCKH+PBr234k6hI9XJ0VP5UqGxap0AnNwBnWFk1MNyWBylJH9ZkzBXC+5akZQ==",
"dependencies": { "dependencies": {
"@typescript-eslint/experimental-utils": "4.28.0", "@typescript-eslint/experimental-utils": "4.28.1",
"@typescript-eslint/scope-manager": "4.28.0", "@typescript-eslint/scope-manager": "4.28.1",
"debug": "^4.3.1", "debug": "^4.3.1",
"functional-red-black-tree": "^1.0.1", "functional-red-black-tree": "^1.0.1",
"regexpp": "^3.1.0", "regexpp": "^3.1.0",
@ -2609,14 +2609,14 @@
} }
}, },
"node_modules/@typescript-eslint/experimental-utils": { "node_modules/@typescript-eslint/experimental-utils": {
"version": "4.28.0", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.1.tgz",
"integrity": "sha512-9XD9s7mt3QWMk82GoyUpc/Ji03vz4T5AYlHF9DcoFNfJ/y3UAclRsfGiE2gLfXtyC+JRA3trR7cR296TEb1oiQ==", "integrity": "sha512-n8/ggadrZ+uyrfrSEchx3jgODdmcx7MzVM2sI3cTpI/YlfSm0+9HEUaWw3aQn2urL2KYlWYMDgn45iLfjDYB+Q==",
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.7", "@types/json-schema": "^7.0.7",
"@typescript-eslint/scope-manager": "4.28.0", "@typescript-eslint/scope-manager": "4.28.1",
"@typescript-eslint/types": "4.28.0", "@typescript-eslint/types": "4.28.1",
"@typescript-eslint/typescript-estree": "4.28.0", "@typescript-eslint/typescript-estree": "4.28.1",
"eslint-scope": "^5.1.1", "eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0" "eslint-utils": "^3.0.0"
}, },
@ -2649,13 +2649,13 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "4.28.0", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.1.tgz",
"integrity": "sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A==", "integrity": "sha512-UjrMsgnhQIIK82hXGaD+MCN8IfORS1CbMdu7VlZbYa8LCZtbZjJA26De4IPQB7XYZbL8gJ99KWNj0l6WD0guJg==",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "4.28.0", "@typescript-eslint/scope-manager": "4.28.1",
"@typescript-eslint/types": "4.28.0", "@typescript-eslint/types": "4.28.1",
"@typescript-eslint/typescript-estree": "4.28.0", "@typescript-eslint/typescript-estree": "4.28.1",
"debug": "^4.3.1" "debug": "^4.3.1"
}, },
"engines": { "engines": {
@ -2675,12 +2675,12 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "4.28.0", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.1.tgz",
"integrity": "sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg==", "integrity": "sha512-o95bvGKfss6705x7jFGDyS7trAORTy57lwJ+VsYwil/lOUxKQ9tA7Suuq+ciMhJc/1qPwB3XE2DKh9wubW8YYA==",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "4.28.0", "@typescript-eslint/types": "4.28.1",
"@typescript-eslint/visitor-keys": "4.28.0" "@typescript-eslint/visitor-keys": "4.28.1"
}, },
"engines": { "engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1" "node": "^8.10.0 || ^10.13.0 || >=11.10.1"
@ -2691,9 +2691,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "4.28.0", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.1.tgz",
"integrity": "sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA==", "integrity": "sha512-4z+knEihcyX7blAGi7O3Fm3O6YRCP+r56NJFMNGsmtdw+NCdpG5SgNz427LS9nQkRVTswZLhz484hakQwB8RRg==",
"engines": { "engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1" "node": "^8.10.0 || ^10.13.0 || >=11.10.1"
}, },
@ -2703,12 +2703,12 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "4.28.0", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.1.tgz",
"integrity": "sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ==", "integrity": "sha512-GhKxmC4sHXxHGJv8e8egAZeTZ6HI4mLU6S7FUzvFOtsk7ZIDN1ksA9r9DyOgNqowA9yAtZXV0Uiap61bIO81FQ==",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "4.28.0", "@typescript-eslint/types": "4.28.1",
"@typescript-eslint/visitor-keys": "4.28.0", "@typescript-eslint/visitor-keys": "4.28.1",
"debug": "^4.3.1", "debug": "^4.3.1",
"globby": "^11.0.3", "globby": "^11.0.3",
"is-glob": "^4.0.1", "is-glob": "^4.0.1",
@ -2748,11 +2748,11 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "4.28.0", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.1.tgz",
"integrity": "sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw==", "integrity": "sha512-K4HMrdFqr9PFquPu178SaSb92CaWe2yErXyPumc8cYWxFmhgJsNY9eSePmO05j0JhBvf2Cdhptd6E6Yv9HVHcg==",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "4.28.0", "@typescript-eslint/types": "4.28.1",
"eslint-visitor-keys": "^2.0.0" "eslint-visitor-keys": "^2.0.0"
}, },
"engines": { "engines": {
@ -3316,9 +3316,9 @@
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
}, },
"node_modules/chart.js": { "node_modules/chart.js": {
"version": "3.3.2", "version": "3.4.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.3.2.tgz", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.4.0.tgz",
"integrity": "sha512-H0hSO7xqTIrwxoACqnSoNromEMfXvfuVnrbuSt2TuXfBDDofbnto4zuZlRtRvC73/b37q3wGAWZyUU41QPvNbA==" "integrity": "sha512-mJsRm2apQm5mwz2OgYqGNG4erZh/qljcRZkWSa0kLkFr3UC3e1wKRMgnIh6WdhUrNu0w/JT9PkjLyylqEqHXEQ=="
}, },
"node_modules/chartjs-adapter-moment": { "node_modules/chartjs-adapter-moment": {
"version": "1.0.0", "version": "1.0.0",
@ -6770,9 +6770,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "2.52.2", "version": "2.52.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.3.tgz",
"integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==", "integrity": "sha512-QF3Sju8Kl2z0osI4unyOLyUudyhOMK6G0AeqJWgfiyigqLAlnNrfBcDWDx+f1cqn+JU2iIYVkDrgQ6/KtwEfrg==",
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
}, },
@ -9482,9 +9482,9 @@
} }
}, },
"@patternfly/patternfly": { "@patternfly/patternfly": {
"version": "4.108.2", "version": "4.115.2",
"resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.108.2.tgz", "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.115.2.tgz",
"integrity": "sha512-z0VB+1CXcH+eoClYQABwapX5FURSvm1nPr6asLWwg/Z4Wuxs0RjZpC6Gb+KRm8nGQwSAcMKZY1jLfPqVnznQnw==" "integrity": "sha512-7hbJ4pRmj+rlXclD2F/UwceO6fS+9flGsgHc4eUc7NyTN2GXl6PLcqrjE2CtiKEPV90+KwsGQGJXZj8bz9HweA=="
}, },
"@polymer/font-roboto": { "@polymer/font-roboto": {
"version": "3.0.2", "version": "3.0.2",
@ -9669,13 +9669,13 @@
} }
}, },
"@sentry/browser": { "@sentry/browser": {
"version": "6.7.2", "version": "6.8.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.8.0.tgz",
"integrity": "sha512-Lv0Ne1QcesyGAhVcQDfQa3hDPR/MhPSDTMg3xFi+LxqztchVc4w/ynzR0wCZFb8KIHpTj5SpJHfxpDhXYMtS9g==", "integrity": "sha512-nxa71csHlG5sMHUxI4e4xxuCWtbCv/QbBfMsYw7ncJSfCKG3yNlCVh8NJ7NS0rZW/MJUT6S6+r93zw0HetNDOA==",
"requires": { "requires": {
"@sentry/core": "6.7.2", "@sentry/core": "6.8.0",
"@sentry/types": "6.7.2", "@sentry/types": "6.8.0",
"@sentry/utils": "6.7.2", "@sentry/utils": "6.8.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -9687,14 +9687,14 @@
} }
}, },
"@sentry/core": { "@sentry/core": {
"version": "6.7.2", "version": "6.8.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.8.0.tgz",
"integrity": "sha512-NTZqwN5nR94yrXmSfekoPs1mIFuKvf8esdIW/DadwSKWAdLJwQTJY9xK/8PQv+SEzd7wiitPAx+mCw2By1xiNQ==", "integrity": "sha512-vJzWt/znEB+JqVwtwfjkRrAYRN+ep+l070Ti8GhJnvwU4IDtVlV3T/jVNrj6rl6UChcczaJQMxVxtG5x0crlAA==",
"requires": { "requires": {
"@sentry/hub": "6.7.2", "@sentry/hub": "6.8.0",
"@sentry/minimal": "6.7.2", "@sentry/minimal": "6.8.0",
"@sentry/types": "6.7.2", "@sentry/types": "6.8.0",
"@sentry/utils": "6.7.2", "@sentry/utils": "6.8.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -9706,12 +9706,12 @@
} }
}, },
"@sentry/hub": { "@sentry/hub": {
"version": "6.7.2", "version": "6.8.0",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.8.0.tgz",
"integrity": "sha512-05qVW6ymChJsXag4+fYCQokW3AcABIgcqrVYZUBf6GMU/Gbz5SJqpV7y1+njwWvnPZydMncP9LaDVpMKbE7UYQ==", "integrity": "sha512-hFrI2Ss1fTov7CH64FJpigqRxH7YvSnGeqxT9Jc1BL7nzW/vgCK+Oh2mOZbosTcrzoDv+lE8ViOnSN3w/fo+rg==",
"requires": { "requires": {
"@sentry/types": "6.7.2", "@sentry/types": "6.8.0",
"@sentry/utils": "6.7.2", "@sentry/utils": "6.8.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -9723,12 +9723,12 @@
} }
}, },
"@sentry/minimal": { "@sentry/minimal": {
"version": "6.7.2", "version": "6.8.0",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.8.0.tgz",
"integrity": "sha512-jkpwFv2GFHoVl5vnK+9/Q+Ea8eVdbJ3hn3/Dqq9MOLFnVK7ED6MhdHKLT79puGSFj+85OuhM5m2Q44mIhyS5mw==", "integrity": "sha512-MRxUKXiiYwKjp8mOQMpTpEuIby1Jh3zRTU0cmGZtfsZ38BC1JOle8xlwC4FdtOH+VvjSYnPBMya5lgNHNPUJDQ==",
"requires": { "requires": {
"@sentry/hub": "6.7.2", "@sentry/hub": "6.8.0",
"@sentry/types": "6.7.2", "@sentry/types": "6.8.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -9740,14 +9740,14 @@
} }
}, },
"@sentry/tracing": { "@sentry/tracing": {
"version": "6.7.2", "version": "6.8.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.8.0.tgz",
"integrity": "sha512-juKlI7FICKONWJFJxDxerj0A+8mNRhmtrdR+OXFqOkqSAy/QXlSFZcA/j//O19k2CfwK1BrvoMcQ/4gnffUOVg==", "integrity": "sha512-3gDkQnmOuOjHz5rY7BOatLEUksANU3efR8wuBa2ujsPQvoLSLFuyZpRjPPsxuUHQOqAYIbSNAoDloXECvQeHjw==",
"requires": { "requires": {
"@sentry/hub": "6.7.2", "@sentry/hub": "6.8.0",
"@sentry/minimal": "6.7.2", "@sentry/minimal": "6.8.0",
"@sentry/types": "6.7.2", "@sentry/types": "6.8.0",
"@sentry/utils": "6.7.2", "@sentry/utils": "6.8.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -9759,16 +9759,16 @@
} }
}, },
"@sentry/types": { "@sentry/types": {
"version": "6.7.2", "version": "6.8.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.8.0.tgz",
"integrity": "sha512-h21Go/PfstUN+ZV6SbwRSZVg9GXRJWdLfHoO5PSVb3TVEMckuxk8tAE57/u+UZDwX8wu+Xyon2TgsKpiWKxqUg==" "integrity": "sha512-PbSxqlh6Fd5thNU5f8EVYBVvX+G7XdPA+ThNb2QvSK8yv3rIf0McHTyF6sIebgJ38OYN7ZFK7vvhC/RgSAfYTA=="
}, },
"@sentry/utils": { "@sentry/utils": {
"version": "6.7.2", "version": "6.8.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.8.0.tgz",
"integrity": "sha512-9COL7aaBbe61Hp5BlArtXZ1o/cxli1NGONLPrVT4fMyeQFmLonhUiy77NdsW19XnvhvaA+2IoV5dg3dnFiF/og==", "integrity": "sha512-OYlI2JNrcWKMdvYbWNdQwR4QBVv2V0y5wK0U6f53nArv6RsyO5TzwRu5rMVSIZofUUqjoE5hl27jqnR+vpUrsA==",
"requires": { "requires": {
"@sentry/types": "6.7.2", "@sentry/types": "6.8.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -9797,9 +9797,9 @@
} }
}, },
"@types/codemirror": { "@types/codemirror": {
"version": "5.60.0", "version": "5.60.1",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.0.tgz", "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.1.tgz",
"integrity": "sha512-xgzXZyCzedLRNC67/Nn8rpBtTFnAsX2C+Q/LGoH6zgcpF/LqdNHJMHEOhqT1bwUcSp6kQdOIuKzRbeW9DYhEhg==", "integrity": "sha512-yV14LQ5VvghnW0uSuCw2bEfZC6NvxHQEckl2w3dEk5l0yPGzQh14dCaWvG5KD/2l3cgFSifR+6nIUD7LDLdUTg==",
"requires": { "requires": {
"@types/tern": "*" "@types/tern": "*"
} }
@ -9925,12 +9925,12 @@
"integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA=="
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "4.28.0", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.1.tgz",
"integrity": "sha512-KcF6p3zWhf1f8xO84tuBailV5cN92vhS+VT7UJsPzGBm9VnQqfI9AsiMUFUCYHTYPg1uCCo+HyiDnpDuvkAMfQ==", "integrity": "sha512-9yfcNpDaNGQ6/LQOX/KhUFTR1sCKH+PBr234k6hI9XJ0VP5UqGxap0AnNwBnWFk1MNyWBylJH9ZkzBXC+5akZQ==",
"requires": { "requires": {
"@typescript-eslint/experimental-utils": "4.28.0", "@typescript-eslint/experimental-utils": "4.28.1",
"@typescript-eslint/scope-manager": "4.28.0", "@typescript-eslint/scope-manager": "4.28.1",
"debug": "^4.3.1", "debug": "^4.3.1",
"functional-red-black-tree": "^1.0.1", "functional-red-black-tree": "^1.0.1",
"regexpp": "^3.1.0", "regexpp": "^3.1.0",
@ -9939,14 +9939,14 @@
} }
}, },
"@typescript-eslint/experimental-utils": { "@typescript-eslint/experimental-utils": {
"version": "4.28.0", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.1.tgz",
"integrity": "sha512-9XD9s7mt3QWMk82GoyUpc/Ji03vz4T5AYlHF9DcoFNfJ/y3UAclRsfGiE2gLfXtyC+JRA3trR7cR296TEb1oiQ==", "integrity": "sha512-n8/ggadrZ+uyrfrSEchx3jgODdmcx7MzVM2sI3cTpI/YlfSm0+9HEUaWw3aQn2urL2KYlWYMDgn45iLfjDYB+Q==",
"requires": { "requires": {
"@types/json-schema": "^7.0.7", "@types/json-schema": "^7.0.7",
"@typescript-eslint/scope-manager": "4.28.0", "@typescript-eslint/scope-manager": "4.28.1",
"@typescript-eslint/types": "4.28.0", "@typescript-eslint/types": "4.28.1",
"@typescript-eslint/typescript-estree": "4.28.0", "@typescript-eslint/typescript-estree": "4.28.1",
"eslint-scope": "^5.1.1", "eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0" "eslint-utils": "^3.0.0"
}, },
@ -9962,37 +9962,37 @@
} }
}, },
"@typescript-eslint/parser": { "@typescript-eslint/parser": {
"version": "4.28.0", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.1.tgz",
"integrity": "sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A==", "integrity": "sha512-UjrMsgnhQIIK82hXGaD+MCN8IfORS1CbMdu7VlZbYa8LCZtbZjJA26De4IPQB7XYZbL8gJ99KWNj0l6WD0guJg==",
"requires": { "requires": {
"@typescript-eslint/scope-manager": "4.28.0", "@typescript-eslint/scope-manager": "4.28.1",
"@typescript-eslint/types": "4.28.0", "@typescript-eslint/types": "4.28.1",
"@typescript-eslint/typescript-estree": "4.28.0", "@typescript-eslint/typescript-estree": "4.28.1",
"debug": "^4.3.1" "debug": "^4.3.1"
} }
}, },
"@typescript-eslint/scope-manager": { "@typescript-eslint/scope-manager": {
"version": "4.28.0", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.1.tgz",
"integrity": "sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg==", "integrity": "sha512-o95bvGKfss6705x7jFGDyS7trAORTy57lwJ+VsYwil/lOUxKQ9tA7Suuq+ciMhJc/1qPwB3XE2DKh9wubW8YYA==",
"requires": { "requires": {
"@typescript-eslint/types": "4.28.0", "@typescript-eslint/types": "4.28.1",
"@typescript-eslint/visitor-keys": "4.28.0" "@typescript-eslint/visitor-keys": "4.28.1"
} }
}, },
"@typescript-eslint/types": { "@typescript-eslint/types": {
"version": "4.28.0", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.1.tgz",
"integrity": "sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA==" "integrity": "sha512-4z+knEihcyX7blAGi7O3Fm3O6YRCP+r56NJFMNGsmtdw+NCdpG5SgNz427LS9nQkRVTswZLhz484hakQwB8RRg=="
}, },
"@typescript-eslint/typescript-estree": { "@typescript-eslint/typescript-estree": {
"version": "4.28.0", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.1.tgz",
"integrity": "sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ==", "integrity": "sha512-GhKxmC4sHXxHGJv8e8egAZeTZ6HI4mLU6S7FUzvFOtsk7ZIDN1ksA9r9DyOgNqowA9yAtZXV0Uiap61bIO81FQ==",
"requires": { "requires": {
"@typescript-eslint/types": "4.28.0", "@typescript-eslint/types": "4.28.1",
"@typescript-eslint/visitor-keys": "4.28.0", "@typescript-eslint/visitor-keys": "4.28.1",
"debug": "^4.3.1", "debug": "^4.3.1",
"globby": "^11.0.3", "globby": "^11.0.3",
"is-glob": "^4.0.1", "is-glob": "^4.0.1",
@ -10016,11 +10016,11 @@
} }
}, },
"@typescript-eslint/visitor-keys": { "@typescript-eslint/visitor-keys": {
"version": "4.28.0", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.1.tgz",
"integrity": "sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw==", "integrity": "sha512-K4HMrdFqr9PFquPu178SaSb92CaWe2yErXyPumc8cYWxFmhgJsNY9eSePmO05j0JhBvf2Cdhptd6E6Yv9HVHcg==",
"requires": { "requires": {
"@typescript-eslint/types": "4.28.0", "@typescript-eslint/types": "4.28.1",
"eslint-visitor-keys": "^2.0.0" "eslint-visitor-keys": "^2.0.0"
} }
}, },
@ -10461,9 +10461,9 @@
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
}, },
"chart.js": { "chart.js": {
"version": "3.3.2", "version": "3.4.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.3.2.tgz", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.4.0.tgz",
"integrity": "sha512-H0hSO7xqTIrwxoACqnSoNromEMfXvfuVnrbuSt2TuXfBDDofbnto4zuZlRtRvC73/b37q3wGAWZyUU41QPvNbA==" "integrity": "sha512-mJsRm2apQm5mwz2OgYqGNG4erZh/qljcRZkWSa0kLkFr3UC3e1wKRMgnIh6WdhUrNu0w/JT9PkjLyylqEqHXEQ=="
}, },
"chartjs-adapter-moment": { "chartjs-adapter-moment": {
"version": "1.0.0", "version": "1.0.0",
@ -13200,9 +13200,9 @@
} }
}, },
"rollup": { "rollup": {
"version": "2.52.2", "version": "2.52.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.3.tgz",
"integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==", "integrity": "sha512-QF3Sju8Kl2z0osI4unyOLyUudyhOMK6G0AeqJWgfiyigqLAlnNrfBcDWDx+f1cqn+JU2iIYVkDrgQ6/KtwEfrg==",
"requires": { "requires": {
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }

View File

@ -47,24 +47,24 @@
"@lingui/cli": "^3.10.2", "@lingui/cli": "^3.10.2",
"@lingui/core": "^3.10.4", "@lingui/core": "^3.10.4",
"@lingui/macro": "^3.10.2", "@lingui/macro": "^3.10.2",
"@patternfly/patternfly": "^4.108.2", "@patternfly/patternfly": "^4.115.2",
"@polymer/iron-form": "^3.0.1", "@polymer/iron-form": "^3.0.1",
"@polymer/paper-input": "^3.2.1", "@polymer/paper-input": "^3.2.1",
"@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-replace": "^2.4.2", "@rollup/plugin-replace": "^2.4.2",
"@rollup/plugin-typescript": "^8.2.1", "@rollup/plugin-typescript": "^8.2.1",
"@sentry/browser": "^6.7.2", "@sentry/browser": "^6.8.0",
"@sentry/tracing": "^6.7.2", "@sentry/tracing": "^6.8.0",
"@types/chart.js": "^2.9.32", "@types/chart.js": "^2.9.32",
"@types/codemirror": "5.60.0", "@types/codemirror": "5.60.1",
"@types/grecaptcha": "^3.0.2", "@types/grecaptcha": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^4.28.0", "@typescript-eslint/eslint-plugin": "^4.28.1",
"@typescript-eslint/parser": "^4.28.0", "@typescript-eslint/parser": "^4.28.1",
"@webcomponents/webcomponentsjs": "^2.5.0", "@webcomponents/webcomponentsjs": "^2.5.0",
"authentik-api": "file:api", "authentik-api": "file:api",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"chart.js": "^3.3.2", "chart.js": "^3.4.0",
"chartjs-adapter-moment": "^1.0.0", "chartjs-adapter-moment": "^1.0.0",
"codemirror": "^5.62.0", "codemirror": "^5.62.0",
"construct-style-sheets-polyfill": "^2.4.16", "construct-style-sheets-polyfill": "^2.4.16",
@ -77,7 +77,7 @@
"lit-html": "^1.4.1", "lit-html": "^1.4.1",
"moment": "^2.29.1", "moment": "^2.29.1",
"rapidoc": "^9.0.0", "rapidoc": "^9.0.0",
"rollup": "^2.52.2", "rollup": "^2.52.3",
"rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-copy": "^3.4.0", "rollup-plugin-copy": "^3.4.0",
"rollup-plugin-cssimport": "^1.0.2", "rollup-plugin-cssimport": "^1.0.2",

View File

@ -139,6 +139,7 @@ body {
/* Card */ /* Card */
.pf-c-card { .pf-c-card {
--pf-c-card--BackgroundColor: var(--ak-dark-background-light); --pf-c-card--BackgroundColor: var(--ak-dark-background-light);
color: var(--ak-dark-foreground);
} }
.pf-c-card__title, .pf-c-card__title,
.pf-c-card__body { .pf-c-card__body {

View File

@ -1,5 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { PlexAuthenticationChallenge } from "authentik-api"; import { PlexAuthenticationChallenge, PlexAuthenticationChallengeResponseRequest } from "authentik-api";
import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
@ -15,7 +15,6 @@ import { DEFAULT_CONFIG } from "../../../api/Config";
import { SourcesApi } from "authentik-api"; import { SourcesApi } from "authentik-api";
import { showMessage } from "../../../elements/messages/MessageContainer"; import { showMessage } from "../../../elements/messages/MessageContainer";
import { MessageLevel } from "../../../elements/messages/Message"; import { MessageLevel } from "../../../elements/messages/Message";
import { PlexAuthenticationChallengeResponseRequest } from "authentik-api/dist/models/PlexAuthenticationChallengeResponseRequest";
@customElement("ak-flow-sources-plex") @customElement("ak-flow-sources-plex")

View File

@ -11,9 +11,8 @@ import { BaseStage } from "../base";
import "../../../elements/forms/FormElement"; import "../../../elements/forms/FormElement";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import "../../FormStatic"; import "../../FormStatic";
import { AuthenticatorDuoChallenge, StagesApi } from "authentik-api"; import { AuthenticatorDuoChallenge, AuthenticatorDuoChallengeResponseRequest, StagesApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../../../api/Config"; import { DEFAULT_CONFIG } from "../../../api/Config";
import { AuthenticatorDuoChallengeResponseRequest } from "authentik-api/dist/models/AuthenticatorDuoChallengeResponseRequest";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-stage-authenticator-duo") @customElement("ak-stage-authenticator-duo")

View File

@ -11,8 +11,7 @@ import { BaseStage } from "../base";
import "../../../elements/forms/FormElement"; import "../../../elements/forms/FormElement";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import "../../FormStatic"; import "../../FormStatic";
import { AuthenticatorStaticChallenge } from "authentik-api"; import { AuthenticatorStaticChallenge, AuthenticatorStaticChallengeResponseRequest } from "authentik-api";
import { AuthenticatorStaticChallengeResponseRequest } from "authentik-api/dist/models/AuthenticatorStaticChallengeResponseRequest";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
export const STATIC_TOKEN_STYLE = css` export const STATIC_TOKEN_STYLE = css`

View File

@ -12,8 +12,7 @@ import "./AuthenticatorValidateStageWebAuthn";
import "./AuthenticatorValidateStageCode"; import "./AuthenticatorValidateStageCode";
import "./AuthenticatorValidateStageDuo"; import "./AuthenticatorValidateStageDuo";
import { PasswordManagerPrefill } from "../identification/IdentificationStage"; import { PasswordManagerPrefill } from "../identification/IdentificationStage";
import { AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; import { AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api";
import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge";
export enum DeviceClasses { export enum DeviceClasses {
STATIC = "static", STATIC = "static",

View File

@ -13,8 +13,7 @@ import "../../../elements/forms/FormElement";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import { PasswordManagerPrefill } from "../identification/IdentificationStage"; import { PasswordManagerPrefill } from "../identification/IdentificationStage";
import "../../FormStatic"; import "../../FormStatic";
import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge"; import { AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api";
import { AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-stage-authenticator-validate-code") @customElement("ak-stage-authenticator-validate-code")

View File

@ -12,8 +12,7 @@ import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage";
import "../../../elements/forms/FormElement"; import "../../../elements/forms/FormElement";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import "../../FormStatic"; import "../../FormStatic";
import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge"; import { AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api";
import { AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-stage-authenticator-validate-duo") @customElement("ak-stage-authenticator-validate-duo")

View File

@ -11,8 +11,7 @@ import { PFSize } from "../../../elements/Spinner";
import { transformAssertionForServer, transformCredentialRequestOptions } from "../authenticator_webauthn/utils"; import { transformAssertionForServer, transformCredentialRequestOptions } from "../authenticator_webauthn/utils";
import { BaseStage } from "../base"; import { BaseStage } from "../base";
import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage"; import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage";
import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge"; import { AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api";
import { AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api";
@customElement("ak-stage-authenticator-validate-webauthn") @customElement("ak-stage-authenticator-validate-webauthn")
export class AuthenticatorValidateStageWebAuthn extends BaseStage<AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest> { export class AuthenticatorValidateStageWebAuthn extends BaseStage<AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest> {

View File

@ -9,8 +9,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import AKGlobal from "../../../authentik.css"; import AKGlobal from "../../../authentik.css";
import { BaseStage } from "../base"; import { BaseStage } from "../base";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import { AutosubmitChallenge } from "authentik-api"; import { AutosubmitChallenge, AutoSubmitChallengeResponseRequest } from "authentik-api";
import { AutoSubmitChallengeResponseRequest } from "authentik-api/dist/models/AutoSubmitChallengeResponseRequest";
@customElement("ak-stage-autosubmit") @customElement("ak-stage-autosubmit")
export class AutosubmitStage extends BaseStage<AutosubmitChallenge, AutoSubmitChallengeResponseRequest> { export class AutosubmitStage extends BaseStage<AutosubmitChallenge, AutoSubmitChallengeResponseRequest> {

View File

@ -698,6 +698,10 @@ msgstr "Configure how long refresh tokens and their id_tokens are valid for."
msgid "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected." msgid "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected."
msgstr "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected." msgstr "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected."
#: src/pages/flows/StageBindingForm.ts
msgid "Configure how the flow executor should handle an invalid response to a challenge."
msgstr "Configure how the flow executor should handle an invalid response to a challenge."
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts #: src/pages/providers/oauth2/OAuth2ProviderForm.ts
msgid "Configure how the issuer field of the ID Token should be filled." msgid "Configure how the issuer field of the ID Token should be filled."
msgstr "Configure how the issuer field of the ID Token should be filled." msgstr "Configure how the issuer field of the ID Token should be filled."
@ -941,6 +945,10 @@ msgstr "Create User"
msgid "Create provider" msgid "Create provider"
msgstr "Create provider" msgstr "Create provider"
#: src/pages/stages/user_write/UserWriteStageForm.ts
msgid "Create users as inactive"
msgstr "Create users as inactive"
#: src/pages/applications/ApplicationForm.ts #: src/pages/applications/ApplicationForm.ts
#: src/pages/flows/BoundStagesList.ts #: src/pages/flows/BoundStagesList.ts
#: src/pages/outposts/ServiceConnectionListPage.ts #: src/pages/outposts/ServiceConnectionListPage.ts
@ -1372,8 +1380,8 @@ msgid "Evaluate policies before the Stage is present to the user."
msgstr "Evaluate policies before the Stage is present to the user." msgstr "Evaluate policies before the Stage is present to the user."
#: src/pages/flows/StageBindingForm.ts #: src/pages/flows/StageBindingForm.ts
msgid "Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with this option disabled, policies are **not** evaluated." msgid "Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with both options disabled, policies are **not** evaluated."
msgstr "Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with this option disabled, policies are **not** evaluated." msgstr "Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with both options disabled, policies are **not** evaluated."
#: src/pages/events/EventListPage.ts #: src/pages/events/EventListPage.ts
msgid "Event Log" msgid "Event Log"
@ -1451,9 +1459,14 @@ msgid "Explicit Consent"
msgstr "Explicit Consent" msgstr "Explicit Consent"
#: src/pages/flows/FlowListPage.ts #: src/pages/flows/FlowListPage.ts
#: src/pages/flows/FlowViewPage.ts
msgid "Export" msgid "Export"
msgstr "Export" msgstr "Export"
#: src/pages/flows/FlowViewPage.ts
msgid "Export flow"
msgstr "Export flow"
#: src/pages/events/EventInfo.ts #: src/pages/events/EventInfo.ts
#: src/pages/policies/expression/ExpressionPolicyForm.ts #: src/pages/policies/expression/ExpressionPolicyForm.ts
#: src/pages/property-mappings/PropertyMappingLDAPForm.ts #: src/pages/property-mappings/PropertyMappingLDAPForm.ts
@ -1876,6 +1889,10 @@ msgstr "Internal host"
msgid "Internal host SSL Validation" msgid "Internal host SSL Validation"
msgstr "Internal host SSL Validation" msgstr "Internal host SSL Validation"
#: src/pages/flows/StageBindingForm.ts
msgid "Invalid response action"
msgstr "Invalid response action"
#: src/pages/flows/FlowForm.ts #: src/pages/flows/FlowForm.ts
msgid "Invalidation" msgid "Invalidation"
msgstr "Invalidation" msgstr "Invalidation"
@ -2138,6 +2155,10 @@ msgstr "Logs"
msgid "Long-running operations which authentik executes in the background." msgid "Long-running operations which authentik executes in the background."
msgstr "Long-running operations which authentik executes in the background." msgstr "Long-running operations which authentik executes in the background."
#: src/pages/stages/user_write/UserWriteStageForm.ts
msgid "Mark newly created users as inactive."
msgstr "Mark newly created users as inactive."
#: src/pages/policies/event_matcher/EventMatcherPolicyForm.ts #: src/pages/policies/event_matcher/EventMatcherPolicyForm.ts
msgid "Match created events with this action type. When left empty, all action types will be matched." msgid "Match created events with this action type. When left empty, all action types will be matched."
msgstr "Match created events with this action type. When left empty, all action types will be matched." msgstr "Match created events with this action type. When left empty, all action types will be matched."
@ -2842,6 +2863,18 @@ msgstr "Public key, acquired from https://www.google.com/recaptcha/intro/v3.html
msgid "Publisher" msgid "Publisher"
msgstr "Publisher" msgstr "Publisher"
#: src/pages/flows/StageBindingForm.ts
msgid "RESTART restarts the flow from the beginning, while keeping the flow context."
msgstr "RESTART restarts the flow from the beginning, while keeping the flow context."
#: src/pages/flows/StageBindingForm.ts
msgid "RESTART restarts the flow from the beginning."
msgstr "RESTART restarts the flow from the beginning."
#: src/pages/flows/StageBindingForm.ts
msgid "RETRY returns the error message and a similar challenge to the executor."
msgstr "RETRY returns the error message and a similar challenge to the executor."
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts #: src/pages/providers/oauth2/OAuth2ProviderForm.ts
msgid "RS256 (Asymmetric Encryption)" msgid "RS256 (Asymmetric Encryption)"
msgstr "RS256 (Asymmetric Encryption)" msgstr "RS256 (Asymmetric Encryption)"
@ -3359,6 +3392,7 @@ msgstr "Stage used to validate any authenticator. This stage should be used duri
#: src/pages/stages/password/PasswordStageForm.ts #: src/pages/stages/password/PasswordStageForm.ts
#: src/pages/stages/prompt/PromptStageForm.ts #: src/pages/stages/prompt/PromptStageForm.ts
#: src/pages/stages/user_login/UserLoginStageForm.ts #: src/pages/stages/user_login/UserLoginStageForm.ts
#: src/pages/stages/user_write/UserWriteStageForm.ts
msgid "Stage-specific settings" msgid "Stage-specific settings"
msgstr "Stage-specific settings" msgstr "Stage-specific settings"
@ -3816,6 +3850,16 @@ msgstr "The external URL you'll authenticate at. Can be the same domain as authe
msgid "The following objects use {objName}" msgid "The following objects use {objName}"
msgstr "The following objects use {objName}" msgstr "The following objects use {objName}"
#: src/pages/policies/reputation/ReputationPolicyForm.ts
msgid ""
"The policy passes when the reputation score is above the threshold, and\n"
"doesn't pass when either or both of the selected options are equal or less than the\n"
"threshold."
msgstr ""
"The policy passes when the reputation score is above the threshold, and\n"
"doesn't pass when either or both of the selected options are equal or less than the\n"
"threshold."
#: src/pages/policies/dummy/DummyPolicyForm.ts #: src/pages/policies/dummy/DummyPolicyForm.ts
msgid "The policy takes a random time to execute. This controls the minimum time it will take." msgid "The policy takes a random time to execute. This controls the minimum time it will take."
msgstr "The policy takes a random time to execute. This controls the minimum time it will take." msgstr "The policy takes a random time to execute. This controls the minimum time it will take."

View File

@ -692,6 +692,10 @@ msgstr ""
msgid "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected." msgid "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected."
msgstr "" msgstr ""
#:
msgid "Configure how the flow executor should handle an invalid response to a challenge."
msgstr ""
#: #:
msgid "Configure how the issuer field of the ID Token should be filled." msgid "Configure how the issuer field of the ID Token should be filled."
msgstr "" msgstr ""
@ -935,6 +939,10 @@ msgstr ""
msgid "Create provider" msgid "Create provider"
msgstr "" msgstr ""
#:
msgid "Create users as inactive"
msgstr ""
#: #:
#: #:
#: #:
@ -1364,7 +1372,7 @@ msgid "Evaluate policies before the Stage is present to the user."
msgstr "" msgstr ""
#: #:
msgid "Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with this option disabled, policies are **not** evaluated." msgid "Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with both options disabled, policies are **not** evaluated."
msgstr "" msgstr ""
#: #:
@ -1442,10 +1450,15 @@ msgstr ""
msgid "Explicit Consent" msgid "Explicit Consent"
msgstr "" msgstr ""
#:
#: #:
msgid "Export" msgid "Export"
msgstr "" msgstr ""
#:
msgid "Export flow"
msgstr ""
#: #:
#: #:
#: #:
@ -1868,6 +1881,10 @@ msgstr ""
msgid "Internal host SSL Validation" msgid "Internal host SSL Validation"
msgstr "" msgstr ""
#:
msgid "Invalid response action"
msgstr ""
#: #:
msgid "Invalidation" msgid "Invalidation"
msgstr "" msgstr ""
@ -2130,6 +2147,10 @@ msgstr ""
msgid "Long-running operations which authentik executes in the background." msgid "Long-running operations which authentik executes in the background."
msgstr "" msgstr ""
#:
msgid "Mark newly created users as inactive."
msgstr ""
#: #:
msgid "Match created events with this action type. When left empty, all action types will be matched." msgid "Match created events with this action type. When left empty, all action types will be matched."
msgstr "" msgstr ""
@ -2834,6 +2855,18 @@ msgstr ""
msgid "Publisher" msgid "Publisher"
msgstr "" msgstr ""
#:
msgid "RESTART restarts the flow from the beginning, while keeping the flow context."
msgstr ""
#:
msgid "RESTART restarts the flow from the beginning."
msgstr ""
#:
msgid "RETRY returns the error message and a similar challenge to the executor."
msgstr ""
#: #:
msgid "RS256 (Asymmetric Encryption)" msgid "RS256 (Asymmetric Encryption)"
msgstr "" msgstr ""
@ -3351,6 +3384,7 @@ msgstr ""
#: #:
#: #:
#: #:
#:
msgid "Stage-specific settings" msgid "Stage-specific settings"
msgstr "" msgstr ""
@ -3808,6 +3842,13 @@ msgstr ""
msgid "The following objects use {objName}" msgid "The following objects use {objName}"
msgstr "" msgstr ""
#:
msgid ""
"The policy passes when the reputation score is above the threshold, and\n"
"doesn't pass when either or both of the selected options are equal or less than the\n"
"threshold."
msgstr ""
#: #:
msgid "The policy takes a random time to execute. This controls the minimum time it will take." msgid "The policy takes a random time to execute. This controls the minimum time it will take."
msgstr "" msgstr ""

View File

@ -1,7 +1,7 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { EventMatcherPolicyActionEnum, FlowsApi } from "authentik-api"; import { EventActions, FlowsApi } from "authentik-api";
import "../../elements/Spinner"; import "../../elements/Spinner";
import "../../elements/Expand"; import "../../elements/Expand";
import { PFSize } from "../../elements/Spinner"; import { PFSize } from "../../elements/Spinner";
@ -189,14 +189,14 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)}
return html`<ak-spinner size=${PFSize.Medium}></ak-spinner>`; return html`<ak-spinner size=${PFSize.Medium}></ak-spinner>`;
} }
switch (this.event?.action) { switch (this.event?.action) {
case EventMatcherPolicyActionEnum.ModelCreated: case EventActions.ModelCreated:
case EventMatcherPolicyActionEnum.ModelUpdated: case EventActions.ModelUpdated:
case EventMatcherPolicyActionEnum.ModelDeleted: case EventActions.ModelDeleted:
return html` return html`
<h3>${t`Affected model:`}</h3> <h3>${t`Affected model:`}</h3>
${this.getModelInfo(this.event.context?.model as EventModel)} ${this.getModelInfo(this.event.context?.model as EventModel)}
`; `;
case EventMatcherPolicyActionEnum.AuthorizeApplication: case EventActions.AuthorizeApplication:
return html`<div class="pf-l-flex"> return html`<div class="pf-l-flex">
<div class="pf-l-flex__item"> <div class="pf-l-flex__item">
<h3>${t`Authorized application:`}</h3> <h3>${t`Authorized application:`}</h3>
@ -213,17 +213,17 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)}
</div> </div>
</div> </div>
<ak-expand>${this.defaultResponse()}</ak-expand>`; <ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventMatcherPolicyActionEnum.EmailSent: case EventActions.EmailSent:
return html`<h3>${t`Email info:`}</h3> return html`<h3>${t`Email info:`}</h3>
${this.getEmailInfo(this.event.context)} ${this.getEmailInfo(this.event.context)}
<ak-expand> <ak-expand>
<iframe srcdoc=${this.event.context.body}></iframe> <iframe srcdoc=${this.event.context.body}></iframe>
</ak-expand>`; </ak-expand>`;
case EventMatcherPolicyActionEnum.SecretView: case EventActions.SecretView:
return html` return html`
<h3>${t`Secret:`}</h3> <h3>${t`Secret:`}</h3>
${this.getModelInfo(this.event.context.secret as EventModel)}`; ${this.getModelInfo(this.event.context.secret as EventModel)}`;
case EventMatcherPolicyActionEnum.SystemException: case EventActions.SystemException:
return html` return html`
<a <a
class="pf-c-button pf-m-primary" class="pf-c-button pf-m-primary"
@ -240,7 +240,7 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)}
</div> </div>
</div> </div>
<ak-expand>${this.defaultResponse()}</ak-expand>`; <ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventMatcherPolicyActionEnum.PropertyMappingException: case EventActions.PropertyMappingException:
return html`<div class="pf-l-flex"> return html`<div class="pf-l-flex">
<div class="pf-l-flex__item"> <div class="pf-l-flex__item">
<h3>${t`Exception`}</h3> <h3>${t`Exception`}</h3>
@ -252,7 +252,7 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)}
</div> </div>
</div> </div>
<ak-expand>${this.defaultResponse()}</ak-expand>`; <ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventMatcherPolicyActionEnum.PolicyException: case EventActions.PolicyException:
return html`<div class="pf-l-flex"> return html`<div class="pf-l-flex">
<div class="pf-l-flex__item"> <div class="pf-l-flex__item">
<h3>${t`Binding`}</h3> <h3>${t`Binding`}</h3>
@ -271,7 +271,7 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)}
</div> </div>
</div> </div>
<ak-expand>${this.defaultResponse()}</ak-expand>`; <ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventMatcherPolicyActionEnum.PolicyExecution: case EventActions.PolicyExecution:
return html`<div class="pf-l-flex"> return html`<div class="pf-l-flex">
<div class="pf-l-flex__item"> <div class="pf-l-flex__item">
<h3>${t`Binding`}</h3> <h3>${t`Binding`}</h3>
@ -299,10 +299,10 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)}
</div> </div>
</div> </div>
<ak-expand>${this.defaultResponse()}</ak-expand>`; <ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventMatcherPolicyActionEnum.ConfigurationError: case EventActions.ConfigurationError:
return html`<h3>${this.event.context.message}</h3> return html`<h3>${this.event.context.message}</h3>
<ak-expand>${this.defaultResponse()}</ak-expand>`; <ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventMatcherPolicyActionEnum.UpdateAvailable: case EventActions.UpdateAvailable:
return html`<h3>${t`New version available!`}</h3> return html`<h3>${t`New version available!`}</h3>
<a <a
target="_blank" target="_blank"
@ -311,7 +311,7 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)}
</a>`; </a>`;
// Action types which typically don't record any extra context. // Action types which typically don't record any extra context.
// If context is not empty, we fall to the default response. // If context is not empty, we fall to the default response.
case EventMatcherPolicyActionEnum.Login: case EventActions.Login:
if ("using_source" in this.event.context) { if ("using_source" in this.event.context) {
return html`<div class="pf-l-flex"> return html`<div class="pf-l-flex">
<div class="pf-l-flex__item"> <div class="pf-l-flex__item">
@ -321,11 +321,11 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)}
</div>`; </div>`;
} }
return this.defaultResponse(); return this.defaultResponse();
case EventMatcherPolicyActionEnum.LoginFailed: case EventActions.LoginFailed:
return html` return html`
<h3>${t`Attempted to log in as ${this.event.context.username}`}</h3> <h3>${t`Attempted to log in as ${this.event.context.username}`}</h3>
<ak-expand>${this.defaultResponse()}</ak-expand>`; <ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventMatcherPolicyActionEnum.Logout: case EventActions.Logout:
if (this.event.context === {}) { if (this.event.context === {}) {
return html`<span>${t`No additional data available.`}</span>`; return html`<span>${t`No additional data available.`}</span>`;
} }

View File

@ -1,5 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { EventsApi } from "authentik-api"; import { EventsApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../../api/Config"; import { DEFAULT_CONFIG } from "../../api/Config";
import { EventWithContext } from "../../api/Events"; import { EventWithContext } from "../../api/Events";
@ -27,11 +27,7 @@ export class EventInfoPage extends LitElement {
event!: EventWithContext; event!: EventWithContext;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [PFBase, PFPage, PFContent, PFCard, AKGlobal].concat(css` return [PFBase, PFPage, PFContent, PFCard, AKGlobal];
.pf-c-card {
color: var(--ak-dark-foreground);
}
`);
} }
render(): TemplateResult { render(): TemplateResult {

View File

@ -51,9 +51,9 @@ export class FlowViewPage extends LitElement {
return html``; return html``;
} }
return html`<ak-page-header return html`<ak-page-header
icon="pf-icon pf-icon-process-automation" icon="pf-icon pf-icon-process-automation"
header=${this.flow.name} header=${this.flow.name}
description=${this.flow.title}> description=${this.flow.title}>
</ak-page-header> </ak-page-header>
<ak-tabs> <ak-tabs>
<div slot="page-overview" data-tab-title="${t`Flow Overview`}" class="pf-c-page__main-section pf-m-no-padding-mobile"> <div slot="page-overview" data-tab-title="${t`Flow Overview`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
@ -69,7 +69,7 @@ export class FlowViewPage extends LitElement {
<dd class="pf-c-description-list__description"> <dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text"> <div class="pf-c-description-list__text">
<button <button
class="pf-c-button pf-m-secondary" class="pf-c-button pf-m-primary"
@click=${() => { @click=${() => {
new FlowsApi(DEFAULT_CONFIG).flowsInstancesExecuteRetrieve({ new FlowsApi(DEFAULT_CONFIG).flowsInstancesExecuteRetrieve({
slug: this.flow.slug slug: this.flow.slug
@ -82,6 +82,16 @@ export class FlowViewPage extends LitElement {
</button> </button>
</div> </div>
</dd> </dd>
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Export flow`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<a class="pf-c-button pf-m-secondary" href=${this.flow.exportUrl}>
${t`Export`}
</a>
</div>
</dd>
</div> </div>
</dl> </dl>
</div> </div>

View File

@ -1,4 +1,4 @@
import { FlowsApi, FlowStageBinding, PolicyEngineMode, Stage, StagesApi } from "authentik-api"; import { FlowsApi, FlowStageBinding, InvalidResponseActionEnum, PolicyEngineMode, Stage, StagesApi } from "authentik-api";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { customElement, property } from "lit-element"; import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html"; import { html, TemplateResult } from "lit-html";
@ -123,7 +123,7 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
</label> </label>
</div> </div>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${t`Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with this option disabled, policies are **not** evaluated.`} ${t`Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with both options disabled, policies are **not** evaluated.`}
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal name="reEvaluatePolicies"> <ak-form-element-horizontal name="reEvaluatePolicies">
@ -135,6 +135,23 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
</div> </div>
<p class="pf-c-form__helper-text">${t`Evaluate policies before the Stage is present to the user.`}</p> <p class="pf-c-form__helper-text">${t`Evaluate policies before the Stage is present to the user.`}</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Invalid response action`}
?required=${true}
name="invalidResponseAction">
<select class="pf-c-form-control">
<option value=${InvalidResponseActionEnum.Retry} ?selected=${this.instance?.invalidResponseAction === InvalidResponseActionEnum.Retry}>
${t`RETRY returns the error message and a similar challenge to the executor.`}
</option>
<option value=${InvalidResponseActionEnum.Restart} ?selected=${this.instance?.invalidResponseAction === InvalidResponseActionEnum.Restart}>
${t`RESTART restarts the flow from the beginning.`}
</option>
<option value=${InvalidResponseActionEnum.RestartWithContext} ?selected=${this.instance?.invalidResponseAction === InvalidResponseActionEnum.RestartWithContext}>
${t`RESTART restarts the flow from the beginning, while keeping the flow context.`}
</option>
</select>
<p class="pf-c-form__helper-text">${t`Configure how the flow executor should handle an invalid response to a challenge.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-element-horizontal
label=${t`Policy engine mode`} label=${t`Policy engine mode`}
?required=${true} ?required=${true}

View File

@ -44,6 +44,11 @@ export class ReputationPolicyForm extends ModelForm<ReputationPolicy, string> {
<div class="form-help-text"> <div class="form-help-text">
${t`Allows/denys requests based on the users and/or the IPs reputation.`} ${t`Allows/denys requests based on the users and/or the IPs reputation.`}
</div> </div>
<div class="form-help-text">
${t`The policy passes when the reputation score is above the threshold, and
doesn't pass when either or both of the selected options are equal or less than the
threshold.`}
</div>
<ak-form-element-horizontal <ak-form-element-horizontal
label=${t`Name`} label=${t`Name`}
?required=${true} ?required=${true}

View File

@ -102,7 +102,7 @@ export class LDAPProviderViewPage extends LitElement {
</span> </span>
<ak-provider-ldap-form <ak-provider-ldap-form
slot="form" slot="form"
.instancePk=${this.provider.pk || 0}> .instancePk=${this.provider.pk}>
</ak-provider-ldap-form> </ak-provider-ldap-form>
<button slot="trigger" class="pf-c-button pf-m-primary"> <button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`} ${t`Edit`}

View File

@ -5,7 +5,9 @@ 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";
import "../../../elements/forms/HorizontalFormElement"; import "../../../elements/forms/HorizontalFormElement";
import "../../../elements/forms/FormGroup";
import { ModelForm } from "../../../elements/forms/ModelForm"; import { ModelForm } from "../../../elements/forms/ModelForm";
import { first } from "../../../utils";
@customElement("ak-stage-user-write-form") @customElement("ak-stage-user-write-form")
export class UserWriteStageForm extends ModelForm<UserWriteStage, string> { export class UserWriteStageForm extends ModelForm<UserWriteStage, string> {
@ -49,6 +51,22 @@ export class UserWriteStageForm extends ModelForm<UserWriteStage, string> {
name="name"> name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required> <input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Stage-specific settings`}
</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="createUsersAsInactive">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.createUsersAsInactive, true)}>
<label class="pf-c-check__label">
${t`Create users as inactive`}
</label>
</div>
<p class="pf-c-form__helper-text">${t`Mark newly created users as inactive.`}</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`; </form>`;
} }

View File

@ -150,7 +150,7 @@ export class TenantForm extends ModelForm<Tenant, string> {
<option value="" ?selected=${this.instance?.flowUnenrollment === undefined}>---------</option> <option value="" ?selected=${this.instance?.flowUnenrollment === undefined}>---------</option>
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({ ${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk", ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Recovery, designation: FlowsInstancesListDesignationEnum.Unenrollment,
}).then(flows => { }).then(flows => {
return flows.results.map(flow => { return flows.results.map(flow => {
const selected = this.instance?.flowUnenrollment === flow.pk; const selected = this.instance?.flowUnenrollment === flow.pk;

View File

@ -1,5 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css";
@ -27,6 +27,7 @@ import "./settings/UserSettingsAuthenticatorTOTP";
import "./settings/UserSettingsAuthenticatorWebAuthn"; import "./settings/UserSettingsAuthenticatorWebAuthn";
import "./settings/UserSettingsPassword"; import "./settings/UserSettingsPassword";
import "./settings/SourceSettingsOAuth"; import "./settings/SourceSettingsOAuth";
import { EVENT_REFRESH } from "../../constants";
@customElement("ak-user-settings") @customElement("ak-user-settings")
export class UserSettingsPage extends LitElement { export class UserSettingsPage extends LitElement {
@ -35,6 +36,24 @@ export class UserSettingsPage extends LitElement {
return [PFBase, PFPage, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, PFForm, PFFormControl, AKGlobal]; return [PFBase, PFPage, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, PFForm, PFFormControl, AKGlobal];
} }
@property({attribute: false})
userSettings?: Promise<UserSetting[]>;
@property({attribute: false})
sourceSettings?: Promise<UserSetting[]>;
constructor() {
super();
this.addEventListener(EVENT_REFRESH, () => {
this.firstUpdated();
});
}
firstUpdated(): void {
this.userSettings = new StagesApi(DEFAULT_CONFIG).stagesAllUserSettingsList();
this.sourceSettings = new SourcesApi(DEFAULT_CONFIG).sourcesAllUserSettingsList();
}
renderStageSettings(stage: UserSetting): TemplateResult { renderStageSettings(stage: UserSetting): TemplateResult {
switch (stage.component) { switch (stage.component) {
case "ak-user-settings-authenticator-webauthn": case "ak-user-settings-authenticator-webauthn":
@ -82,14 +101,14 @@ export class UserSettingsPage extends LitElement {
<section slot="page-tokens" data-tab-title="${t`Tokens`}" class="pf-c-page__main-section pf-m-no-padding-mobile"> <section slot="page-tokens" data-tab-title="${t`Tokens`}" 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>
</section> </section>
${until(new StagesApi(DEFAULT_CONFIG).stagesAllUserSettingsList().then((stages) => { ${until(this.userSettings?.then((stages) => {
return stages.map((stage) => { return stages.map((stage) => {
return html`<section slot="page-${stage.objectUid}" data-tab-title="${ifDefined(stage.title)}" class="pf-c-page__main-section pf-m-no-padding-mobile"> return html`<section slot="page-${stage.objectUid}" data-tab-title="${ifDefined(stage.title)}" class="pf-c-page__main-section pf-m-no-padding-mobile">
${this.renderStageSettings(stage)} ${this.renderStageSettings(stage)}
</section>`; </section>`;
}); });
}))} }))}
${until(new SourcesApi(DEFAULT_CONFIG).sourcesAllUserSettingsList().then((source) => { ${until(this.sourceSettings?.then((source) => {
return source.map((stage) => { return source.map((stage) => {
return html`<section slot="page-${stage.objectUid}" data-tab-title="${ifDefined(stage.title)}" class="pf-c-page__main-section pf-m-no-padding-mobile"> return html`<section slot="page-${stage.objectUid}" data-tab-title="${ifDefined(stage.title)}" class="pf-c-page__main-section pf-m-no-padding-mobile">
${this.renderSourceSettings(stage)} ${this.renderSourceSettings(stage)}

View File

@ -4,6 +4,7 @@ import { customElement, html, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config"; import { DEFAULT_CONFIG } from "../../../api/Config";
import { BaseUserSettings } from "./BaseUserSettings"; import { BaseUserSettings } from "./BaseUserSettings";
import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-user-settings-authenticator-duo") @customElement("ak-user-settings-authenticator-duo")
export class UserSettingsAuthenticatorDuo extends BaseUserSettings { export class UserSettingsAuthenticatorDuo extends BaseUserSettings {
@ -27,7 +28,12 @@ export class UserSettingsAuthenticatorDuo extends BaseUserSettings {
return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsDuoDestroy({ return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsDuoDestroy({
id: devices.results[0].pk || 0 id: devices.results[0].pk || 0
}).then(() => { }).then(() => {
this.requestUpdate(); this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
})
);
}); });
}); });
}}> }}>

View File

@ -5,6 +5,7 @@ import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config"; import { DEFAULT_CONFIG } from "../../../api/Config";
import { STATIC_TOKEN_STYLE } from "../../../flows/stages/authenticator_static/AuthenticatorStaticStage"; import { STATIC_TOKEN_STYLE } from "../../../flows/stages/authenticator_static/AuthenticatorStaticStage";
import { BaseUserSettings } from "./BaseUserSettings"; import { BaseUserSettings } from "./BaseUserSettings";
import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-user-settings-authenticator-static") @customElement("ak-user-settings-authenticator-static")
export class UserSettingsAuthenticatorStatic extends BaseUserSettings { export class UserSettingsAuthenticatorStatic extends BaseUserSettings {
@ -42,7 +43,12 @@ export class UserSettingsAuthenticatorStatic extends BaseUserSettings {
return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsStaticDestroy({ return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsStaticDestroy({
id: devices.results[0].pk || 0 id: devices.results[0].pk || 0
}).then(() => { }).then(() => {
this.requestUpdate(); this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
})
);
}); });
}); });
}}> }}>

View File

@ -4,6 +4,7 @@ import { customElement, html, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config"; import { DEFAULT_CONFIG } from "../../../api/Config";
import { BaseUserSettings } from "./BaseUserSettings"; import { BaseUserSettings } from "./BaseUserSettings";
import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-user-settings-authenticator-totp") @customElement("ak-user-settings-authenticator-totp")
export class UserSettingsAuthenticatorTOTP extends BaseUserSettings { export class UserSettingsAuthenticatorTOTP extends BaseUserSettings {
@ -27,7 +28,12 @@ export class UserSettingsAuthenticatorTOTP extends BaseUserSettings {
return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsTotpDestroy({ return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsTotpDestroy({
id: devices.results[0].pk || 0 id: devices.results[0].pk || 0
}).then(() => { }).then(() => {
this.requestUpdate(); this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
})
);
}); });
}); });
}}> }}>

View File

@ -12,6 +12,7 @@ import "../../../elements/forms/Form";
import "../../../elements/forms/ModalForm"; import "../../../elements/forms/ModalForm";
import "../../../elements/forms/HorizontalFormElement"; import "../../../elements/forms/HorizontalFormElement";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-user-settings-authenticator-webauthn") @customElement("ak-user-settings-authenticator-webauthn")
export class UserSettingsAuthenticatorWebAuthn extends BaseUserSettings { export class UserSettingsAuthenticatorWebAuthn extends BaseUserSettings {
@ -28,7 +29,12 @@ export class UserSettingsAuthenticatorWebAuthn extends BaseUserSettings {
return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsWebauthnDestroy({ return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsWebauthnDestroy({
id: device.pk || 0 id: device.pk || 0
}).then(() => { }).then(() => {
this.requestUpdate(); this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
})
);
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -57,7 +57,7 @@ import TabItem from '@theme/TabItem';
location @akprox_signin { location @akprox_signin {
internal; internal;
add_header Set-Cookie $auth_cookie; add_header Set-Cookie $auth_cookie;
return 302 /akprox/start?rd=$escaped_request_uri; return 302 /akprox/start?rd=$request_uri;
} }
location / { location / {

View File

@ -116,6 +116,29 @@ slug: "2021.6"
- web/admin: handle elements in slot=form not being forms - web/admin: handle elements in slot=form not being forms
- web/admin: sort inputs on authenticator validation stage form - web/admin: sort inputs on authenticator validation stage form
## Fixed in 2021.6.3
- api: use partition instead of split for token
- core: fix flow background not correctly loading on initial draw
- events: add ability to create events via API
- events: ignore notification non-existent in transport
- events: only create SYSTEM_EXCEPTION event when error would've been sent to sentry
- expressions: fix regex_match result being inverted
- flows: add FlowStageBinding to flow plan instead of just stage
- flows: add invalid_response_action to configure how the FlowExecutor should handle invalid responses
- flows: handle possible errors with FlowPlans received from cache
- outposts: check docker container ports match
- outposts/ldap: fixed IsActive and IsSuperuser returning swapped incorrect values (#1078)
- providers/oauth2: fix exp of JWT when not using seconds
- sources/ldap: improve error handling when checking for password complexity on non-ad setups
- stages/authenticator_duo: fix component not being set in API
- stages/prompt: ensure hidden and static fields keep the value they had set
- stages/user_write: add flag to create new users as inactive
- tenants: include all default flows in current_tenant
- web/admin: fix deletion of authenticator not reloading the state correctly
- web/admin: fix only recovery flows being selectable for unenrollment flow in tenant form
- web/admin: fix text color on pf-c-card
## Upgrading ## Upgrading
This release does not introduce any new requirements. This release does not introduce any new requirements.

View File

@ -20,7 +20,7 @@
"react-toggle": "^4.1.2" "react-toggle": "^4.1.2"
}, },
"devDependencies": { "devDependencies": {
"prettier": "2.3.1" "prettier": "2.3.2"
} }
}, },
"node_modules/@algolia/autocomplete-core": { "node_modules/@algolia/autocomplete-core": {
@ -10376,9 +10376,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "2.3.1", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
"integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
"dev": true, "dev": true,
"bin": { "bin": {
"prettier": "bin-prettier.js" "prettier": "bin-prettier.js"
@ -22886,9 +22886,9 @@
"integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc="
}, },
"prettier": { "prettier": {
"version": "2.3.1", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
"integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
"dev": true "dev": true
}, },
"pretty-error": { "pretty-error": {

View File

@ -35,6 +35,6 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"prettier": "2.3.1" "prettier": "2.3.2"
} }
} }

View File

@ -145,7 +145,9 @@
"name": "default-enrollment-user-write" "name": "default-enrollment-user-write"
}, },
"model": "authentik_stages_user_write.userwritestage", "model": "authentik_stages_user_write.userwritestage",
"attrs": {} "attrs": {
"create_users_as_inactive": true
}
}, },
{ {
"identifiers": { "identifiers": {