stages/invitation: add single_use flag to delete invitation after use

closes #821

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-05-03 09:52:38 +02:00
parent 6ae660aea4
commit 4523550422
9 changed files with 66 additions and 2 deletions

View file

@ -39,6 +39,7 @@ class InvitationSerializer(ModelSerializer):
"expires", "expires",
"fixed_data", "fixed_data",
"created_by", "created_by",
"single_use",
] ]
depth = 2 depth = 2

View file

@ -0,0 +1,21 @@
# Generated by Django 3.2 on 2021-05-03 07:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_invitation", "0003_auto_20201227_1210"),
]
operations = [
migrations.AddField(
model_name="invitation",
name="single_use",
field=models.BooleanField(
default=False,
help_text="When enabled, the invitation will be deleted after usage.",
),
),
]

View file

@ -53,6 +53,11 @@ class Invitation(models.Model):
invite_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4) invite_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
single_use = models.BooleanField(
default=False,
help_text=_("When enabled, the invitation will be deleted after usage."),
)
created_by = models.ForeignKey(User, on_delete=models.CASCADE) created_by = models.ForeignKey(User, on_delete=models.CASCADE)
expires = models.DateTimeField(default=None, blank=True, null=True) expires = models.DateTimeField(default=None, blank=True, null=True)
fixed_data = models.JSONField( fixed_data = models.JSONField(

View file

@ -1,4 +1,5 @@
"""invitation stage logic""" """invitation stage logic"""
from copy import deepcopy
from typing import Optional from typing import Optional
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
@ -38,7 +39,9 @@ class InvitationStageView(StageView):
return self.executor.stage_invalid() return self.executor.stage_invalid()
invite: Invitation = get_object_or_404(Invitation, pk=token) invite: Invitation = get_object_or_404(Invitation, pk=token)
self.executor.plan.context[PLAN_CONTEXT_PROMPT] = invite.fixed_data self.executor.plan.context[PLAN_CONTEXT_PROMPT] = deepcopy(invite.fixed_data)
self.executor.plan.context[INVITATION_IN_EFFECT] = True self.executor.plan.context[INVITATION_IN_EFFECT] = True
invitation_used.send(sender=self, request=request, invitation=invite) invitation_used.send(sender=self, request=request, invitation=invite)
if invite.single_use:
invite.delete()
return self.executor.stage_ok() return self.executor.stage_ok()

View file

@ -130,7 +130,9 @@ class TestUserLoginStage(TestCase):
"""Test with invitation, check data in session""" """Test with invitation, check data in session"""
data = {"foo": "bar"} data = {"foo": "bar"}
invite = Invitation.objects.create( invite = Invitation.objects.create(
created_by=get_anonymous_user(), fixed_data=data created_by=get_anonymous_user(),
fixed_data=data,
single_use=True
) )
plan = FlowPlan( plan = FlowPlan(
@ -156,6 +158,7 @@ class TestUserLoginStage(TestCase):
force_str(response.content), force_str(response.content),
{"to": reverse("authentik_core:root-redirect"), "type": "redirect"}, {"to": reverse("authentik_core:root-redirect"), "type": "redirect"},
) )
self.assertFalse(Invitation.objects.filter(pk=invite.pk))
class TestInvitationsAPI(APITestCase): class TestInvitationsAPI(APITestCase):

View file

@ -18248,6 +18248,10 @@ definitions:
x-nullable: true x-nullable: true
readOnly: true readOnly: true
readOnly: true readOnly: true
single_use:
title: Single use
description: When enabled, the invitation will be deleted after usage.
type: boolean
InvitationStage: InvitationStage:
required: required:
- name - name

View file

@ -2841,6 +2841,10 @@ msgstr "Signing keypair"
msgid "Single Prompts that can be used for Prompt Stages." msgid "Single Prompts that can be used for Prompt Stages."
msgstr "Single Prompts that can be used for Prompt Stages." msgstr "Single Prompts that can be used for Prompt Stages."
#: src/pages/stages/invitation/InvitationForm.ts:62
msgid "Single use"
msgstr "Single use"
#: src/pages/providers/proxy/ProxyProviderForm.ts:173 #: src/pages/providers/proxy/ProxyProviderForm.ts:173
msgid "Skip path regex" msgid "Skip path regex"
msgstr "Skip path regex" msgstr "Skip path regex"
@ -3877,6 +3881,10 @@ msgstr "When a valid username/email has been entered, and this option is enabled
msgid "When enabled, global Email connection settings will be used and connection settings below will be ignored." msgid "When enabled, global Email connection settings will be used and connection settings below will be ignored."
msgstr "When enabled, global Email connection settings will be used and connection settings below will be ignored." msgstr "When enabled, global Email connection settings will be used and connection settings below will be ignored."
#: src/pages/stages/invitation/InvitationForm.ts:66
msgid "When enabled, the invitation will be deleted after usage."
msgstr "When enabled, the invitation will be deleted after usage."
#: src/pages/stages/identification/IdentificationStageForm.ts:94 #: src/pages/stages/identification/IdentificationStageForm.ts:94
msgid "When enabled, user fields are matched regardless of their casing." msgid "When enabled, user fields are matched regardless of their casing."
msgstr "When enabled, user fields are matched regardless of their casing." msgstr "When enabled, user fields are matched regardless of their casing."

View file

@ -2833,6 +2833,10 @@ msgstr ""
msgid "Single Prompts that can be used for Prompt Stages." msgid "Single Prompts that can be used for Prompt Stages."
msgstr "" msgstr ""
#: src/pages/stages/invitation/InvitationForm.ts:62
msgid "Single use"
msgstr ""
#: src/pages/providers/proxy/ProxyProviderForm.ts:173 #: src/pages/providers/proxy/ProxyProviderForm.ts:173
msgid "Skip path regex" msgid "Skip path regex"
msgstr "" msgstr ""
@ -3865,6 +3869,10 @@ msgstr ""
msgid "When enabled, global Email connection settings will be used and connection settings below will be ignored." msgid "When enabled, global Email connection settings will be used and connection settings below will be ignored."
msgstr "" msgstr ""
#: src/pages/stages/invitation/InvitationForm.ts:66
msgid "When enabled, the invitation will be deleted after usage."
msgstr ""
#: src/pages/stages/identification/IdentificationStageForm.ts:94 #: src/pages/stages/identification/IdentificationStageForm.ts:94
msgid "When enabled, user fields are matched regardless of their casing." msgid "When enabled, user fields are matched regardless of their casing."
msgstr "" msgstr ""

View file

@ -51,6 +51,17 @@ export class InvitationForm extends Form<Invitation> {
</ak-codemirror> </ak-codemirror>
<p class="pf-c-form__helper-text">${t`Optional data which is loaded into the flow's 'prompt_data' context variable. YAML or JSON.`}</p> <p class="pf-c-form__helper-text">${t`Optional data which is loaded into the flow's 'prompt_data' context variable. YAML or JSON.`}</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal name="singleUse">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.invitation?.singleUse, true)}>
<label class="pf-c-check__label">
${t`Single use`}
</label>
</div>
<p class="pf-c-form__helper-text">
${t`When enabled, the invitation will be deleted after usage.`}
</p>
</ak-form-element-horizontal>
</form>`; </form>`;
} }