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:
Jens L 2022-07-02 14:17:41 +02:00 committed by GitHub
parent a9636b5727
commit c39a5933e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 150 additions and 59 deletions

View File

@ -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}"

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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(

View File

@ -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)

View File

@ -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
} }
} }
] ]