core: create FlowToken instead of regular token for generated recovery links (#3193)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> #2749
This commit is contained in:
parent
a9636b5727
commit
c39a5933e1
|
@ -63,6 +63,9 @@ from authentik.core.models import (
|
||||||
User,
|
User,
|
||||||
)
|
)
|
||||||
from authentik.events.models import EventAction
|
from authentik.events.models import EventAction
|
||||||
|
from authentik.flows.models import FlowToken
|
||||||
|
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner
|
||||||
|
from authentik.flows.views.executor import QS_KEY_TOKEN
|
||||||
from authentik.stages.email.models import EmailStage
|
from authentik.stages.email.models import EmailStage
|
||||||
from authentik.stages.email.tasks import send_mails
|
from authentik.stages.email.tasks import send_mails
|
||||||
from authentik.stages.email.utils import TemplateEmailMessage
|
from authentik.stages.email.utils import TemplateEmailMessage
|
||||||
|
@ -294,12 +297,23 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||||
LOGGER.debug("No recovery flow set")
|
LOGGER.debug("No recovery flow set")
|
||||||
return None, None
|
return None, None
|
||||||
user: User = self.get_object()
|
user: User = self.get_object()
|
||||||
token, __ = Token.objects.get_or_create(
|
planner = FlowPlanner(flow)
|
||||||
identifier=f"{user.uid}-password-reset",
|
planner.allow_empty_flows = True
|
||||||
user=user,
|
plan = planner.plan(
|
||||||
intent=TokenIntents.INTENT_RECOVERY,
|
self.request._request,
|
||||||
|
{
|
||||||
|
PLAN_CONTEXT_PENDING_USER: user,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
querystring = urlencode({"token": token.key})
|
token, __ = FlowToken.objects.update_or_create(
|
||||||
|
identifier=f"{user.uid}-password-reset",
|
||||||
|
defaults={
|
||||||
|
"user": user,
|
||||||
|
"flow": flow,
|
||||||
|
"_plan": FlowToken.pickle(plan),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
querystring = urlencode({QS_KEY_TOKEN: token.key})
|
||||||
link = self.request.build_absolute_uri(
|
link = self.request.build_absolute_uri(
|
||||||
reverse_lazy("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
|
reverse_lazy("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
|
||||||
+ f"?{querystring}"
|
+ f"?{querystring}"
|
||||||
|
|
|
@ -16,6 +16,7 @@ from authentik.core.models import AuthenticatedSession, 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.flows.models import FlowToken
|
||||||
from authentik.lib.sentry import before_send
|
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
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ IGNORED_MODELS = [
|
||||||
AuthenticatedSession,
|
AuthenticatedSession,
|
||||||
StaticToken,
|
StaticToken,
|
||||||
Session,
|
Session,
|
||||||
|
FlowToken,
|
||||||
]
|
]
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
from silk.models import Request, Response, SQLQuery
|
from silk.models import Request, Response, SQLQuery
|
||||||
|
|
|
@ -27,6 +27,7 @@ def get_attrs(obj: SerializerModel) -> dict[str, Any]:
|
||||||
"promptstage_set",
|
"promptstage_set",
|
||||||
"policybindingmodel_ptr_id",
|
"policybindingmodel_ptr_id",
|
||||||
"export_url",
|
"export_url",
|
||||||
|
"meta_model_name",
|
||||||
)
|
)
|
||||||
for to_remove_name in to_remove:
|
for to_remove_name in to_remove:
|
||||||
if to_remove_name in data:
|
if to_remove_name in data:
|
||||||
|
|
|
@ -138,12 +138,11 @@ class FlowExecutorView(APIView):
|
||||||
message = exc.__doc__ if exc.__doc__ else str(exc)
|
message = exc.__doc__ if exc.__doc__ else str(exc)
|
||||||
return self.stage_invalid(error_message=message)
|
return self.stage_invalid(error_message=message)
|
||||||
|
|
||||||
def _check_flow_token(self, get_params: QueryDict):
|
def _check_flow_token(self, key: str) -> Optional[FlowPlan]:
|
||||||
"""Check if the user is using a flow token to restore a plan"""
|
"""Check if the user is using a flow token to restore a plan"""
|
||||||
tokens = FlowToken.filter_not_expired(key=get_params[QS_KEY_TOKEN])
|
token: Optional[FlowToken] = FlowToken.filter_not_expired(key=key).first()
|
||||||
if not tokens.exists():
|
if not token:
|
||||||
return False
|
return None
|
||||||
token: FlowToken = tokens.first()
|
|
||||||
try:
|
try:
|
||||||
plan = token.plan
|
plan = token.plan
|
||||||
except (AttributeError, EOFError, ImportError, IndexError) as exc:
|
except (AttributeError, EOFError, ImportError, IndexError) as exc:
|
||||||
|
@ -164,7 +163,7 @@ class FlowExecutorView(APIView):
|
||||||
span.set_data("authentik Flow", self.flow.slug)
|
span.set_data("authentik Flow", self.flow.slug)
|
||||||
get_params = QueryDict(request.GET.get("query", ""))
|
get_params = QueryDict(request.GET.get("query", ""))
|
||||||
if QS_KEY_TOKEN in get_params:
|
if QS_KEY_TOKEN in get_params:
|
||||||
plan = self._check_flow_token(get_params)
|
plan = self._check_flow_token(get_params[QS_KEY_TOKEN])
|
||||||
if plan:
|
if plan:
|
||||||
self.request.session[SESSION_KEY_PLAN] = plan
|
self.request.session[SESSION_KEY_PLAN] = plan
|
||||||
# Early check if there's an active Plan for the current session
|
# Early check if there's an active Plan for the current session
|
||||||
|
|
|
@ -122,6 +122,7 @@ class Migration(migrations.Migration):
|
||||||
default=list,
|
default=list,
|
||||||
help_text="Specify which sources should be shown.",
|
help_text="Specify which sources should be shown.",
|
||||||
to="authentik_core.Source",
|
to="authentik_core.Source",
|
||||||
|
blank=True,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
migrations.RunPython(
|
migrations.RunPython(
|
||||||
|
|
|
@ -87,7 +87,7 @@ class IdentificationStage(Stage):
|
||||||
)
|
)
|
||||||
|
|
||||||
sources = models.ManyToManyField(
|
sources = models.ManyToManyField(
|
||||||
Source, default=list, help_text=_("Specify which sources should be shown.")
|
Source, default=list, help_text=_("Specify which sources should be shown."), blank=True
|
||||||
)
|
)
|
||||||
show_source_labels = models.BooleanField(default=False)
|
show_source_labels = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
|
|
@ -10,21 +10,11 @@
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"name": "Default recovery flow",
|
"name": "Default recovery flow",
|
||||||
"title": "Reset your password",
|
"title": "Reset your password",
|
||||||
"designation": "recovery"
|
"designation": "recovery",
|
||||||
}
|
"cache_count": 0,
|
||||||
},
|
"policy_engine_mode": "any",
|
||||||
{
|
"compatibility_mode": false,
|
||||||
"identifiers": {
|
"layout": "stacked"
|
||||||
"pk": "1ff91927-e33d-4615-95b0-c258e5f0df62"
|
|
||||||
},
|
|
||||||
"model": "authentik_stages_prompt.prompt",
|
|
||||||
"attrs": {
|
|
||||||
"field_key": "email",
|
|
||||||
"label": "Email",
|
|
||||||
"type": "email",
|
|
||||||
"required": true,
|
|
||||||
"placeholder": "Email",
|
|
||||||
"order": 1
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -38,7 +28,9 @@
|
||||||
"type": "password",
|
"type": "password",
|
||||||
"required": true,
|
"required": true,
|
||||||
"placeholder": "Password",
|
"placeholder": "Password",
|
||||||
"order": 0
|
"order": 0,
|
||||||
|
"sub_text": "",
|
||||||
|
"placeholder_expression": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -52,33 +44,38 @@
|
||||||
"type": "password",
|
"type": "password",
|
||||||
"required": true,
|
"required": true,
|
||||||
"placeholder": "Password (repeat)",
|
"placeholder": "Password (repeat)",
|
||||||
"order": 1
|
"order": 1,
|
||||||
|
"sub_text": "",
|
||||||
|
"placeholder_expression": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
"pk": "e54045a7-6ecb-4ad9-ad37-28e72d8e565e",
|
"pk": "1c5709ae-1b3e-413a-a117-260ab509bf5c"
|
||||||
"name": "default-recovery-identification"
|
|
||||||
},
|
},
|
||||||
"model": "authentik_stages_identification.identificationstage",
|
"model": "authentik_policies_expression.expressionpolicy",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"user_fields": ["email", "username"],
|
"name": "default-recovery-skip-if-restored",
|
||||||
"template": "stages/identification/recovery.html",
|
"execution_logging": false,
|
||||||
"enrollment_flow": null,
|
"bound_to": 2,
|
||||||
"recovery_flow": null
|
"expression": "return request.context.get('is_restored', False)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
"pk": "3909fd60-b013-4668-8806-12e9507dab97",
|
"pk": "1c5709ae-1b3e-413a-a117-260ab509bf5c"
|
||||||
"name": "default-recovery-user-write"
|
|
||||||
},
|
},
|
||||||
"model": "authentik_stages_user_write.userwritestage",
|
"model": "authentik_policies_expression.expressionpolicy",
|
||||||
"attrs": {}
|
"attrs": {
|
||||||
|
"name": "default-recovery-skip-if-restored",
|
||||||
|
"execution_logging": false,
|
||||||
|
"bound_to": 2,
|
||||||
|
"expression": "return request.context.get('is_restored', False)"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
"pk": "66f948dc-3f74-42b2-b26b-b8b9df109efb",
|
"pk": "4ac5719f-32c0-441c-8a7e-33c5ea0db7da",
|
||||||
"name": "default-recovery-email"
|
"name": "default-recovery-email"
|
||||||
},
|
},
|
||||||
"model": "authentik_stages_email.emailstage",
|
"model": "authentik_stages_email.emailstage",
|
||||||
|
@ -99,20 +96,40 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
"pk": "975d5502-1e22-4d10-b560-fbc5bd70ff4d",
|
"pk": "68b25ad5-318a-496e-95a7-cf4d94247f0d",
|
||||||
"name": "Change your password"
|
"name": "default-recovery-user-write"
|
||||||
},
|
},
|
||||||
"model": "authentik_stages_prompt.promptstage",
|
"model": "authentik_stages_user_write.userwritestage",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"fields": [
|
"create_users_as_inactive": false,
|
||||||
"7db91ee8-4290-4e08-8d39-63f132402515",
|
"create_users_group": null,
|
||||||
"d30b5eb4-7787-4072-b1ba-65b46e928920"
|
"user_path_template": ""
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
"pk": "fcdd4206-0d35-4ad2-a59f-5a72422936bb",
|
"pk": "94843ef6-28fe-4939-bd61-cd46bb34f1de",
|
||||||
|
"name": "default-recovery-identification"
|
||||||
|
},
|
||||||
|
"model": "authentik_stages_identification.identificationstage",
|
||||||
|
"attrs": {
|
||||||
|
"user_fields": [
|
||||||
|
"email",
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"password_stage": null,
|
||||||
|
"case_insensitive_matching": true,
|
||||||
|
"show_matched_user": true,
|
||||||
|
"enrollment_flow": null,
|
||||||
|
"recovery_flow": null,
|
||||||
|
"passwordless_flow": null,
|
||||||
|
"sources": [],
|
||||||
|
"show_source_labels": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifiers": {
|
||||||
|
"pk": "e74230b2-82bc-4843-8b18-2c3a66a62d57",
|
||||||
"name": "default-recovery-user-login"
|
"name": "default-recovery-user-login"
|
||||||
},
|
},
|
||||||
"model": "authentik_stages_user_login.userloginstage",
|
"model": "authentik_stages_user_login.userloginstage",
|
||||||
|
@ -120,64 +137,121 @@
|
||||||
"session_duration": "seconds=0"
|
"session_duration": "seconds=0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identifiers": {
|
||||||
|
"pk": "fa2d8d65-1809-4dcc-bdc0-56266e0f7971",
|
||||||
|
"name": "Change your password"
|
||||||
|
},
|
||||||
|
"model": "authentik_stages_prompt.promptstage",
|
||||||
|
"attrs": {
|
||||||
|
"fields": [
|
||||||
|
"7db91ee8-4290-4e08-8d39-63f132402515",
|
||||||
|
"d30b5eb4-7787-4072-b1ba-65b46e928920"
|
||||||
|
],
|
||||||
|
"validation_policies": []
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
"pk": "7af7558e-2196-4b9f-a08e-d38420b7cfbb",
|
"pk": "7af7558e-2196-4b9f-a08e-d38420b7cfbb",
|
||||||
"target": "a5993183-89c0-43d2-a7f4-ddffb17baba7",
|
"target": "a5993183-89c0-43d2-a7f4-ddffb17baba7",
|
||||||
"stage": "e54045a7-6ecb-4ad9-ad37-28e72d8e565e",
|
"stage": "94843ef6-28fe-4939-bd61-cd46bb34f1de",
|
||||||
"order": 10
|
"order": 10
|
||||||
},
|
},
|
||||||
"model": "authentik_flows.flowstagebinding",
|
"model": "authentik_flows.flowstagebinding",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"re_evaluate_policies": false
|
"evaluate_on_plan": true,
|
||||||
|
"re_evaluate_policies": true,
|
||||||
|
"policy_engine_mode": "any",
|
||||||
|
"invalid_response_action": "retry"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
"pk": "29446fd6-dd93-4e92-9830-2d81debad5ae",
|
"pk": "29446fd6-dd93-4e92-9830-2d81debad5ae",
|
||||||
"target": "a5993183-89c0-43d2-a7f4-ddffb17baba7",
|
"target": "a5993183-89c0-43d2-a7f4-ddffb17baba7",
|
||||||
"stage": "66f948dc-3f74-42b2-b26b-b8b9df109efb",
|
"stage": "4ac5719f-32c0-441c-8a7e-33c5ea0db7da",
|
||||||
"order": 20
|
"order": 20
|
||||||
},
|
},
|
||||||
"model": "authentik_flows.flowstagebinding",
|
"model": "authentik_flows.flowstagebinding",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"re_evaluate_policies": false
|
"evaluate_on_plan": true,
|
||||||
|
"re_evaluate_policies": true,
|
||||||
|
"policy_engine_mode": "any",
|
||||||
|
"invalid_response_action": "retry"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
"pk": "1219d06e-2c06-4c5b-a162-78e3959c6cf0",
|
"pk": "1219d06e-2c06-4c5b-a162-78e3959c6cf0",
|
||||||
"target": "a5993183-89c0-43d2-a7f4-ddffb17baba7",
|
"target": "a5993183-89c0-43d2-a7f4-ddffb17baba7",
|
||||||
"stage": "975d5502-1e22-4d10-b560-fbc5bd70ff4d",
|
"stage": "fa2d8d65-1809-4dcc-bdc0-56266e0f7971",
|
||||||
"order": 30
|
"order": 30
|
||||||
},
|
},
|
||||||
"model": "authentik_flows.flowstagebinding",
|
"model": "authentik_flows.flowstagebinding",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"re_evaluate_policies": false
|
"evaluate_on_plan": true,
|
||||||
|
"re_evaluate_policies": false,
|
||||||
|
"policy_engine_mode": "any",
|
||||||
|
"invalid_response_action": "retry"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
"pk": "66de86ba-0707-46a0-8475-ff2e260d6935",
|
"pk": "66de86ba-0707-46a0-8475-ff2e260d6935",
|
||||||
"target": "a5993183-89c0-43d2-a7f4-ddffb17baba7",
|
"target": "a5993183-89c0-43d2-a7f4-ddffb17baba7",
|
||||||
"stage": "3909fd60-b013-4668-8806-12e9507dab97",
|
"stage": "68b25ad5-318a-496e-95a7-cf4d94247f0d",
|
||||||
"order": 40
|
"order": 40
|
||||||
},
|
},
|
||||||
"model": "authentik_flows.flowstagebinding",
|
"model": "authentik_flows.flowstagebinding",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"re_evaluate_policies": false
|
"evaluate_on_plan": true,
|
||||||
|
"re_evaluate_policies": false,
|
||||||
|
"policy_engine_mode": "any",
|
||||||
|
"invalid_response_action": "retry"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
"pk": "9cec2334-d4a2-4895-a2b2-bc5ae4e9639a",
|
"pk": "9cec2334-d4a2-4895-a2b2-bc5ae4e9639a",
|
||||||
"target": "a5993183-89c0-43d2-a7f4-ddffb17baba7",
|
"target": "a5993183-89c0-43d2-a7f4-ddffb17baba7",
|
||||||
"stage": "fcdd4206-0d35-4ad2-a59f-5a72422936bb",
|
"stage": "e74230b2-82bc-4843-8b18-2c3a66a62d57",
|
||||||
"order": 100
|
"order": 100
|
||||||
},
|
},
|
||||||
"model": "authentik_flows.flowstagebinding",
|
"model": "authentik_flows.flowstagebinding",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"re_evaluate_policies": false
|
"evaluate_on_plan": true,
|
||||||
|
"re_evaluate_policies": false,
|
||||||
|
"policy_engine_mode": "any",
|
||||||
|
"invalid_response_action": "retry"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifiers": {
|
||||||
|
"pk": "95aad215-8729-4177-953d-41ffbe86239e",
|
||||||
|
"policy": "1c5709ae-1b3e-413a-a117-260ab509bf5c",
|
||||||
|
"target": "7af7558e-2196-4b9f-a08e-d38420b7cfbb",
|
||||||
|
"order": 0
|
||||||
|
},
|
||||||
|
"model": "authentik_policies.policybinding",
|
||||||
|
"attrs": {
|
||||||
|
"negate": false,
|
||||||
|
"enabled": true,
|
||||||
|
"timeout": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifiers": {
|
||||||
|
"pk": "a5454cbc-d2e4-403a-84af-6af999990b12",
|
||||||
|
"policy": "1c5709ae-1b3e-413a-a117-260ab509bf5c",
|
||||||
|
"target": "29446fd6-dd93-4e92-9830-2d81debad5ae",
|
||||||
|
"order": 0
|
||||||
|
},
|
||||||
|
"model": "authentik_policies.policybinding",
|
||||||
|
"attrs": {
|
||||||
|
"negate": false,
|
||||||
|
"enabled": true,
|
||||||
|
"timeout": 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
Reference in a new issue