stages/prompt: field name (#4497)
* add prompt field name Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove numerical prefix Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix missing name Signed-off-by: Jens Langhammer <jens@goauthentik.io> * use text field Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add description label Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add migrate blueprint to remove old stages Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add task to remove unretrievable blueprints Signed-off-by: Jens Langhammer <jens@goauthentik.io> * lint Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix blueprint test paths Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * actually fix tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests even more Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix fixtures Signed-off-by: Jens Langhammer <jens@goauthentik.io> Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
9437e2d3ab
commit
53b65a9d1a
|
@ -57,9 +57,10 @@ class AuthentikBlueprintsConfig(ManagedAppConfig):
|
||||||
|
|
||||||
def reconcile_blueprints_discover(self):
|
def reconcile_blueprints_discover(self):
|
||||||
"""Run blueprint discovery"""
|
"""Run blueprint discovery"""
|
||||||
from authentik.blueprints.v1.tasks import blueprints_discover
|
from authentik.blueprints.v1.tasks import blueprints_discover, clear_failed_blueprints
|
||||||
|
|
||||||
blueprints_discover.delay()
|
blueprints_discover.delay()
|
||||||
|
clear_failed_blueprints.delay()
|
||||||
|
|
||||||
def import_models(self):
|
def import_models(self):
|
||||||
super().import_models()
|
super().import_models()
|
||||||
|
|
|
@ -9,4 +9,9 @@ CELERY_BEAT_SCHEDULE = {
|
||||||
"schedule": crontab(minute=fqdn_rand("blueprints_v1_discover"), hour="*"),
|
"schedule": crontab(minute=fqdn_rand("blueprints_v1_discover"), hour="*"),
|
||||||
"options": {"queue": "authentik_scheduled"},
|
"options": {"queue": "authentik_scheduled"},
|
||||||
},
|
},
|
||||||
|
"blueprints_v1_cleanup": {
|
||||||
|
"task": "authentik.blueprints.v1.tasks.clear_failed_blueprints",
|
||||||
|
"schedule": crontab(minute=fqdn_rand("blueprints_v1_cleanup"), hour="*"),
|
||||||
|
"options": {"queue": "authentik_scheduled"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ entries:
|
||||||
pk: cb954fd4-65a5-4ad9-b1ee-180ee9559cf4
|
pk: cb954fd4-65a5-4ad9-b1ee-180ee9559cf4
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
attrs:
|
attrs:
|
||||||
|
name: qwerweqrq
|
||||||
field_key: username
|
field_key: username
|
||||||
label: Username
|
label: Username
|
||||||
type: username
|
type: username
|
||||||
|
|
|
@ -13,7 +13,7 @@ from authentik.tenants.models import Tenant
|
||||||
class TestPackaged(TransactionTestCase):
|
class TestPackaged(TransactionTestCase):
|
||||||
"""Empty class, test methods are added dynamically"""
|
"""Empty class, test methods are added dynamically"""
|
||||||
|
|
||||||
@apply_blueprint("default/90-default-tenant.yaml")
|
@apply_blueprint("default/default-tenant.yaml")
|
||||||
def test_decorator_static(self):
|
def test_decorator_static(self):
|
||||||
"""Test @apply_blueprint decorator"""
|
"""Test @apply_blueprint decorator"""
|
||||||
self.assertTrue(Tenant.objects.filter(domain="authentik-default").exists())
|
self.assertTrue(Tenant.objects.filter(domain="authentik-default").exists())
|
||||||
|
|
|
@ -262,15 +262,21 @@ class TestBlueprintsV1(TransactionTestCase):
|
||||||
with transaction_rollback():
|
with transaction_rollback():
|
||||||
# First stage fields
|
# First stage fields
|
||||||
username_prompt = Prompt.objects.create(
|
username_prompt = Prompt.objects.create(
|
||||||
field_key="username", label="Username", order=0, type=FieldTypes.TEXT
|
name=generate_id(),
|
||||||
|
field_key="username",
|
||||||
|
label="Username",
|
||||||
|
order=0,
|
||||||
|
type=FieldTypes.TEXT,
|
||||||
)
|
)
|
||||||
password = Prompt.objects.create(
|
password = Prompt.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
field_key="password",
|
field_key="password",
|
||||||
label="Password",
|
label="Password",
|
||||||
order=1,
|
order=1,
|
||||||
type=FieldTypes.PASSWORD,
|
type=FieldTypes.PASSWORD,
|
||||||
)
|
)
|
||||||
password_repeat = Prompt.objects.create(
|
password_repeat = Prompt.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
field_key="password_repeat",
|
field_key="password_repeat",
|
||||||
label="Password (repeat)",
|
label="Password (repeat)",
|
||||||
order=2,
|
order=2,
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
LABEL_AUTHENTIK_SYSTEM = "blueprints.goauthentik.io/system"
|
LABEL_AUTHENTIK_SYSTEM = "blueprints.goauthentik.io/system"
|
||||||
LABEL_AUTHENTIK_INSTANTIATE = "blueprints.goauthentik.io/instantiate"
|
LABEL_AUTHENTIK_INSTANTIATE = "blueprints.goauthentik.io/instantiate"
|
||||||
LABEL_AUTHENTIK_GENERATED = "blueprints.goauthentik.io/generated"
|
LABEL_AUTHENTIK_GENERATED = "blueprints.goauthentik.io/generated"
|
||||||
|
LABEL_AUTHENTIK_DESCRIPTION = "blueprints.goauthentik.io/description"
|
||||||
|
|
|
@ -219,3 +219,14 @@ def apply_blueprint(self: MonitoredTask, instance_pk: str):
|
||||||
finally:
|
finally:
|
||||||
if instance:
|
if instance:
|
||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
|
|
||||||
|
@CELERY_APP.task()
|
||||||
|
def clear_failed_blueprints():
|
||||||
|
"""Remove blueprints which couldn't be fetched"""
|
||||||
|
# Exclude OCI blueprints as those might be temporarily unavailable
|
||||||
|
for blueprint in BlueprintInstance.objects.exclude(path__startswith="oci://"):
|
||||||
|
try:
|
||||||
|
blueprint.retrieve()
|
||||||
|
except BlueprintRetrievalFailed:
|
||||||
|
blueprint.delete()
|
||||||
|
|
|
@ -4,6 +4,7 @@ from django.urls.base import reverse
|
||||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||||
from authentik.flows.models import FlowDesignation, FlowStageBinding
|
from authentik.flows.models import FlowDesignation, FlowStageBinding
|
||||||
from authentik.flows.tests import FlowTestCase
|
from authentik.flows.tests import FlowTestCase
|
||||||
|
from authentik.lib.generators import generate_id
|
||||||
from authentik.policies.password.models import PasswordPolicy
|
from authentik.policies.password.models import PasswordPolicy
|
||||||
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
|
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ class TestPasswordPolicyFlow(FlowTestCase):
|
||||||
self.flow = create_test_flow(FlowDesignation.AUTHENTICATION)
|
self.flow = create_test_flow(FlowDesignation.AUTHENTICATION)
|
||||||
|
|
||||||
password_prompt = Prompt.objects.create(
|
password_prompt = Prompt.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
field_key="password",
|
field_key="password",
|
||||||
label="PASSWORD_LABEL",
|
label="PASSWORD_LABEL",
|
||||||
type=FieldTypes.PASSWORD,
|
type=FieldTypes.PASSWORD,
|
||||||
|
|
|
@ -42,6 +42,7 @@ class PromptSerializer(ModelSerializer):
|
||||||
model = Prompt
|
model = Prompt
|
||||||
fields = [
|
fields = [
|
||||||
"pk",
|
"pk",
|
||||||
|
"name",
|
||||||
"field_key",
|
"field_key",
|
||||||
"label",
|
"label",
|
||||||
"type",
|
"type",
|
||||||
|
@ -59,5 +60,5 @@ class PromptViewSet(UsedByMixin, ModelViewSet):
|
||||||
|
|
||||||
queryset = Prompt.objects.all().prefetch_related("promptstage_set")
|
queryset = Prompt.objects.all().prefetch_related("promptstage_set")
|
||||||
serializer_class = PromptSerializer
|
serializer_class = PromptSerializer
|
||||||
filterset_fields = ["field_key", "label", "type", "placeholder"]
|
filterset_fields = ["field_key", "name", "label", "type", "placeholder"]
|
||||||
search_fields = ["field_key", "label", "type", "placeholder"]
|
search_fields = ["field_key", "name", "label", "type", "placeholder"]
|
||||||
|
|
40
authentik/stages/prompt/migrations/0009_prompt_name.py
Normal file
40
authentik/stages/prompt/migrations/0009_prompt_name.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# Generated by Django 4.1.5 on 2023-01-23 19:42
|
||||||
|
|
||||||
|
from django.apps.registry import Apps
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
|
||||||
|
|
||||||
|
def set_generated_name(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
Prompt = apps.get_model("authentik_stages_prompt", "prompt")
|
||||||
|
|
||||||
|
for prompt in Prompt.objects.using(db_alias).all():
|
||||||
|
name = prompt.field_key
|
||||||
|
stage = prompt.promptstage_set.order_by("name").first()
|
||||||
|
if stage:
|
||||||
|
name += "_" + stage.name
|
||||||
|
prompt.name = name
|
||||||
|
prompt.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_stages_prompt", "0008_alter_prompt_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="prompt",
|
||||||
|
name="name",
|
||||||
|
field=models.TextField(default="", unique=False, db_index=False, blank=False),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.RunPython(code=set_generated_name),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="prompt",
|
||||||
|
name="name",
|
||||||
|
field=models.TextField(unique=True, blank=False, db_index=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -96,6 +96,7 @@ class Prompt(SerializerModel):
|
||||||
"""Single Prompt, part of a prompt stage."""
|
"""Single Prompt, part of a prompt stage."""
|
||||||
|
|
||||||
prompt_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
prompt_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
||||||
|
name = models.TextField(unique=True, blank=False)
|
||||||
|
|
||||||
field_key = models.TextField(
|
field_key = models.TextField(
|
||||||
help_text=_("Name of the form field, also used to store the value")
|
help_text=_("Name of the form field, also used to store the value")
|
||||||
|
|
|
@ -30,6 +30,7 @@ class TestPromptStage(FlowTestCase):
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
self.flow = create_test_flow()
|
self.flow = create_test_flow()
|
||||||
username_prompt = Prompt.objects.create(
|
username_prompt = Prompt.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
field_key="username_prompt",
|
field_key="username_prompt",
|
||||||
label="USERNAME_LABEL",
|
label="USERNAME_LABEL",
|
||||||
type=FieldTypes.USERNAME,
|
type=FieldTypes.USERNAME,
|
||||||
|
@ -37,6 +38,7 @@ class TestPromptStage(FlowTestCase):
|
||||||
placeholder="USERNAME_PLACEHOLDER",
|
placeholder="USERNAME_PLACEHOLDER",
|
||||||
)
|
)
|
||||||
text_prompt = Prompt.objects.create(
|
text_prompt = Prompt.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
field_key="text_prompt",
|
field_key="text_prompt",
|
||||||
label="TEXT_LABEL",
|
label="TEXT_LABEL",
|
||||||
type=FieldTypes.TEXT,
|
type=FieldTypes.TEXT,
|
||||||
|
@ -44,6 +46,7 @@ class TestPromptStage(FlowTestCase):
|
||||||
placeholder="TEXT_PLACEHOLDER",
|
placeholder="TEXT_PLACEHOLDER",
|
||||||
)
|
)
|
||||||
email_prompt = Prompt.objects.create(
|
email_prompt = Prompt.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
field_key="email_prompt",
|
field_key="email_prompt",
|
||||||
label="EMAIL_LABEL",
|
label="EMAIL_LABEL",
|
||||||
type=FieldTypes.EMAIL,
|
type=FieldTypes.EMAIL,
|
||||||
|
@ -51,6 +54,7 @@ class TestPromptStage(FlowTestCase):
|
||||||
placeholder="EMAIL_PLACEHOLDER",
|
placeholder="EMAIL_PLACEHOLDER",
|
||||||
)
|
)
|
||||||
password_prompt = Prompt.objects.create(
|
password_prompt = Prompt.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
field_key="password_prompt",
|
field_key="password_prompt",
|
||||||
label="PASSWORD_LABEL",
|
label="PASSWORD_LABEL",
|
||||||
type=FieldTypes.PASSWORD,
|
type=FieldTypes.PASSWORD,
|
||||||
|
@ -58,6 +62,7 @@ class TestPromptStage(FlowTestCase):
|
||||||
placeholder="PASSWORD_PLACEHOLDER",
|
placeholder="PASSWORD_PLACEHOLDER",
|
||||||
)
|
)
|
||||||
password2_prompt = Prompt.objects.create(
|
password2_prompt = Prompt.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
field_key="password2_prompt",
|
field_key="password2_prompt",
|
||||||
label="PASSWORD_LABEL",
|
label="PASSWORD_LABEL",
|
||||||
type=FieldTypes.PASSWORD,
|
type=FieldTypes.PASSWORD,
|
||||||
|
@ -65,6 +70,7 @@ class TestPromptStage(FlowTestCase):
|
||||||
placeholder="PASSWORD_PLACEHOLDER",
|
placeholder="PASSWORD_PLACEHOLDER",
|
||||||
)
|
)
|
||||||
number_prompt = Prompt.objects.create(
|
number_prompt = Prompt.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
field_key="number_prompt",
|
field_key="number_prompt",
|
||||||
label="NUMBER_LABEL",
|
label="NUMBER_LABEL",
|
||||||
type=FieldTypes.NUMBER,
|
type=FieldTypes.NUMBER,
|
||||||
|
@ -72,12 +78,14 @@ class TestPromptStage(FlowTestCase):
|
||||||
placeholder="NUMBER_PLACEHOLDER",
|
placeholder="NUMBER_PLACEHOLDER",
|
||||||
)
|
)
|
||||||
hidden_prompt = Prompt.objects.create(
|
hidden_prompt = Prompt.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
field_key="hidden_prompt",
|
field_key="hidden_prompt",
|
||||||
type=FieldTypes.HIDDEN,
|
type=FieldTypes.HIDDEN,
|
||||||
required=True,
|
required=True,
|
||||||
placeholder="HIDDEN_PLACEHOLDER",
|
placeholder="HIDDEN_PLACEHOLDER",
|
||||||
)
|
)
|
||||||
static_prompt = Prompt.objects.create(
|
static_prompt = Prompt.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
field_key="static_prompt",
|
field_key="static_prompt",
|
||||||
type=FieldTypes.STATIC,
|
type=FieldTypes.STATIC,
|
||||||
required=True,
|
required=True,
|
||||||
|
|
|
@ -17,9 +17,10 @@ entries:
|
||||||
placeholder_expression: false
|
placeholder_expression: false
|
||||||
required: true
|
required: true
|
||||||
type: text
|
type: text
|
||||||
identifiers:
|
|
||||||
field_key: username
|
field_key: username
|
||||||
label: Username
|
label: Username
|
||||||
|
identifiers:
|
||||||
|
name: default-source-enrollment-field-username
|
||||||
id: prompt-field-username
|
id: prompt-field-username
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
- attrs:
|
- attrs:
|
|
@ -21,9 +21,10 @@ entries:
|
||||||
placeholder_expression: true
|
placeholder_expression: true
|
||||||
required: true
|
required: true
|
||||||
type: text
|
type: text
|
||||||
identifiers:
|
|
||||||
field_key: username
|
field_key: username
|
||||||
label: Username
|
label: Username
|
||||||
|
identifiers:
|
||||||
|
name: default-user-settings-field-username
|
||||||
id: prompt-field-username
|
id: prompt-field-username
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
- attrs:
|
- attrs:
|
||||||
|
@ -36,9 +37,10 @@ entries:
|
||||||
placeholder_expression: true
|
placeholder_expression: true
|
||||||
required: true
|
required: true
|
||||||
type: text
|
type: text
|
||||||
identifiers:
|
|
||||||
field_key: name
|
field_key: name
|
||||||
label: Name
|
label: Name
|
||||||
|
identifiers:
|
||||||
|
name: default-user-settings-field-name
|
||||||
id: prompt-field-name
|
id: prompt-field-name
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
- attrs:
|
- attrs:
|
||||||
|
@ -51,9 +53,10 @@ entries:
|
||||||
placeholder_expression: true
|
placeholder_expression: true
|
||||||
required: true
|
required: true
|
||||||
type: email
|
type: email
|
||||||
identifiers:
|
|
||||||
field_key: email
|
field_key: email
|
||||||
label: Email
|
label: Email
|
||||||
|
identifiers:
|
||||||
|
name: default-user-settings-field-email
|
||||||
id: prompt-field-email
|
id: prompt-field-email
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
- attrs:
|
- attrs:
|
||||||
|
@ -66,9 +69,10 @@ entries:
|
||||||
placeholder_expression: true
|
placeholder_expression: true
|
||||||
required: true
|
required: true
|
||||||
type: ak-locale
|
type: ak-locale
|
||||||
identifiers:
|
|
||||||
field_key: attributes.settings.locale
|
field_key: attributes.settings.locale
|
||||||
label: Locale
|
label: Locale
|
||||||
|
identifiers:
|
||||||
|
name: default-user-settings-field-locale
|
||||||
id: prompt-field-locale
|
id: prompt-field-locale
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
- attrs:
|
- attrs:
|
|
@ -19,10 +19,11 @@ entries:
|
||||||
required: true
|
required: true
|
||||||
sub_text: ''
|
sub_text: ''
|
||||||
type: static
|
type: static
|
||||||
id: prompt-field-header
|
|
||||||
identifiers:
|
|
||||||
field_key: oobe-header-text
|
field_key: oobe-header-text
|
||||||
label: oobe-header-text
|
label: oobe-header-text
|
||||||
|
id: prompt-field-header
|
||||||
|
identifiers:
|
||||||
|
name: initial-setup-field-header
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
- attrs:
|
- attrs:
|
||||||
order: 101
|
order: 101
|
||||||
|
@ -31,10 +32,11 @@ entries:
|
||||||
required: true
|
required: true
|
||||||
sub_text: ''
|
sub_text: ''
|
||||||
type: email
|
type: email
|
||||||
|
field_key: email
|
||||||
|
label: Email
|
||||||
id: prompt-field-email
|
id: prompt-field-email
|
||||||
identifiers:
|
identifiers:
|
||||||
field_key: admin_email
|
name: initial-setup-field-email
|
||||||
label: Email
|
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
- attrs:
|
- attrs:
|
||||||
order: 300
|
order: 300
|
||||||
|
@ -43,10 +45,11 @@ entries:
|
||||||
required: true
|
required: true
|
||||||
sub_text: ''
|
sub_text: ''
|
||||||
type: password
|
type: password
|
||||||
id: prompt-field-password
|
|
||||||
identifiers:
|
|
||||||
field_key: password
|
field_key: password
|
||||||
label: Password
|
label: Password
|
||||||
|
id: prompt-field-password
|
||||||
|
identifiers:
|
||||||
|
name: initial-setup-field-password
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
- attrs:
|
- attrs:
|
||||||
order: 301
|
order: 301
|
||||||
|
@ -55,10 +58,11 @@ entries:
|
||||||
required: true
|
required: true
|
||||||
sub_text: ''
|
sub_text: ''
|
||||||
type: password
|
type: password
|
||||||
id: prompt-field-password-repeat
|
|
||||||
identifiers:
|
|
||||||
field_key: password_repeat
|
field_key: password_repeat
|
||||||
label: Password (repeat)
|
label: Password (repeat)
|
||||||
|
id: prompt-field-password-repeat
|
||||||
|
identifiers:
|
||||||
|
name: initial-setup-field-password-repeat
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
- attrs:
|
- attrs:
|
||||||
expression: |
|
expression: |
|
||||||
|
@ -66,8 +70,6 @@ entries:
|
||||||
# by injecting "pending_user"
|
# by injecting "pending_user"
|
||||||
akadmin = ak_user_by(username="akadmin")
|
akadmin = ak_user_by(username="akadmin")
|
||||||
context["flow_plan"].context["pending_user"] = akadmin
|
context["flow_plan"].context["pending_user"] = akadmin
|
||||||
# Remap the email value
|
|
||||||
context["prompt_data"]["email"] = context["prompt_data"]["admin_email"]
|
|
||||||
return True
|
return True
|
||||||
id: policy-default-oobe-prefill-user
|
id: policy-default-oobe-prefill-user
|
||||||
identifiers:
|
identifiers:
|
|
@ -17,9 +17,10 @@ entries:
|
||||||
placeholder_expression: false
|
placeholder_expression: false
|
||||||
required: true
|
required: true
|
||||||
type: password
|
type: password
|
||||||
identifiers:
|
|
||||||
field_key: password
|
field_key: password
|
||||||
label: Password
|
label: Password
|
||||||
|
identifiers:
|
||||||
|
name: default-password-change-field-password
|
||||||
id: prompt-field-password
|
id: prompt-field-password
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
- attrs:
|
- attrs:
|
||||||
|
@ -28,9 +29,10 @@ entries:
|
||||||
placeholder_expression: false
|
placeholder_expression: false
|
||||||
required: true
|
required: true
|
||||||
type: password
|
type: password
|
||||||
identifiers:
|
|
||||||
field_key: password_repeat
|
field_key: password_repeat
|
||||||
label: Password (repeat)
|
label: Password (repeat)
|
||||||
|
identifiers:
|
||||||
|
name: default-password-change-field-password-repeat
|
||||||
id: prompt-field-password-repeat
|
id: prompt-field-password-repeat
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
- attrs:
|
- attrs:
|
|
@ -13,56 +13,61 @@ entries:
|
||||||
title: Welcome to authentik!
|
title: Welcome to authentik!
|
||||||
designation: enrollment
|
designation: enrollment
|
||||||
authentication: require_unauthenticated
|
authentication: require_unauthenticated
|
||||||
- identifiers:
|
- id: prompt-field-username
|
||||||
|
model: authentik_stages_prompt.prompt
|
||||||
|
identifiers:
|
||||||
|
name: default-enrollment-field-username
|
||||||
|
attrs:
|
||||||
field_key: username
|
field_key: username
|
||||||
label: Username
|
label: Username
|
||||||
id: prompt-field-username
|
|
||||||
model: authentik_stages_prompt.prompt
|
|
||||||
attrs:
|
|
||||||
type: username
|
type: username
|
||||||
required: true
|
required: true
|
||||||
placeholder: Username
|
placeholder: Username
|
||||||
placeholder_expression: false
|
placeholder_expression: false
|
||||||
order: 0
|
order: 0
|
||||||
- identifiers:
|
- identifiers:
|
||||||
field_key: password
|
name: default-enrollment-field-password
|
||||||
label: Password
|
|
||||||
id: prompt-field-password
|
id: prompt-field-password
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
attrs:
|
attrs:
|
||||||
|
field_key: password
|
||||||
|
label: Password
|
||||||
type: password
|
type: password
|
||||||
required: true
|
required: true
|
||||||
placeholder: Password
|
placeholder: Password
|
||||||
placeholder_expression: false
|
placeholder_expression: false
|
||||||
order: 0
|
order: 0
|
||||||
- identifiers:
|
- identifiers:
|
||||||
field_key: password_repeat
|
name: default-enrollment-field-password-repeat
|
||||||
label: Password (repeat)
|
|
||||||
id: prompt-field-password-repeat
|
id: prompt-field-password-repeat
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
attrs:
|
attrs:
|
||||||
|
field_key: password_repeat
|
||||||
|
label: Password (repeat)
|
||||||
type: password
|
type: password
|
||||||
required: true
|
required: true
|
||||||
placeholder: Password (repeat)
|
placeholder: Password (repeat)
|
||||||
placeholder_expression: false
|
placeholder_expression: false
|
||||||
order: 1
|
order: 1
|
||||||
- identifiers:
|
- identifiers:
|
||||||
field_key: name
|
name: default-enrollment-field-name
|
||||||
label: Name
|
|
||||||
id: prompt-field-name
|
id: prompt-field-name
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
attrs:
|
attrs:
|
||||||
|
field_key: name
|
||||||
|
label: Name
|
||||||
type: text
|
type: text
|
||||||
required: true
|
required: true
|
||||||
placeholder: Name
|
placeholder: Name
|
||||||
placeholder_expression: false
|
placeholder_expression: false
|
||||||
order: 0
|
order: 0
|
||||||
- identifiers:
|
- identifiers:
|
||||||
field_key: email
|
name: default-enrollment-field-email
|
||||||
label: Email
|
|
||||||
id: prompt-field-email
|
id: prompt-field-email
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
attrs:
|
attrs:
|
||||||
|
field_key: email
|
||||||
|
label: Email
|
||||||
type: email
|
type: email
|
||||||
required: true
|
required: true
|
||||||
placeholder: Email
|
placeholder: Email
|
||||||
|
|
|
@ -14,55 +14,60 @@ entries:
|
||||||
designation: enrollment
|
designation: enrollment
|
||||||
authentication: require_unauthenticated
|
authentication: require_unauthenticated
|
||||||
- identifiers:
|
- identifiers:
|
||||||
field_key: username
|
name: default-enrollment-field-username
|
||||||
label: Username
|
|
||||||
id: prompt-field-username
|
id: prompt-field-username
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
attrs:
|
attrs:
|
||||||
|
field_key: username
|
||||||
|
label: Username
|
||||||
type: username
|
type: username
|
||||||
required: true
|
required: true
|
||||||
placeholder: Username
|
placeholder: Username
|
||||||
placeholder_expression: false
|
placeholder_expression: false
|
||||||
order: 0
|
order: 0
|
||||||
- identifiers:
|
- identifiers:
|
||||||
field_key: password
|
name: default-enrollment-field-password
|
||||||
label: Password
|
|
||||||
id: prompt-field-password
|
id: prompt-field-password
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
attrs:
|
attrs:
|
||||||
|
field_key: password
|
||||||
|
label: Password
|
||||||
type: password
|
type: password
|
||||||
required: true
|
required: true
|
||||||
placeholder: Password
|
placeholder: Password
|
||||||
placeholder_expression: false
|
placeholder_expression: false
|
||||||
order: 0
|
order: 0
|
||||||
- identifiers:
|
- identifiers:
|
||||||
field_key: password_repeat
|
name: default-enrollment-field-password-repeat
|
||||||
label: Password (repeat)
|
|
||||||
id: prompt-field-password-repeat
|
id: prompt-field-password-repeat
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
attrs:
|
attrs:
|
||||||
|
field_key: password_repeat
|
||||||
|
label: Password (repeat)
|
||||||
type: password
|
type: password
|
||||||
required: true
|
required: true
|
||||||
placeholder: Password (repeat)
|
placeholder: Password (repeat)
|
||||||
placeholder_expression: false
|
placeholder_expression: false
|
||||||
order: 1
|
order: 1
|
||||||
- identifiers:
|
- identifiers:
|
||||||
field_key: name
|
name: default-enrollment-field-name
|
||||||
label: Name
|
|
||||||
id: prompt-field-name
|
id: prompt-field-name
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
attrs:
|
attrs:
|
||||||
|
field_key: name
|
||||||
|
label: Name
|
||||||
type: text
|
type: text
|
||||||
required: true
|
required: true
|
||||||
placeholder: Name
|
placeholder: Name
|
||||||
placeholder_expression: false
|
placeholder_expression: false
|
||||||
order: 0
|
order: 0
|
||||||
- identifiers:
|
- identifiers:
|
||||||
field_key: email
|
name: default-enrollment-field-email
|
||||||
label: Email
|
|
||||||
id: prompt-field-email
|
id: prompt-field-email
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
attrs:
|
attrs:
|
||||||
|
field_key: email
|
||||||
|
label: Email
|
||||||
type: email
|
type: email
|
||||||
required: true
|
required: true
|
||||||
placeholder: Email
|
placeholder: Email
|
||||||
|
|
|
@ -14,22 +14,24 @@ entries:
|
||||||
designation: recovery
|
designation: recovery
|
||||||
authentication: require_unauthenticated
|
authentication: require_unauthenticated
|
||||||
- identifiers:
|
- identifiers:
|
||||||
field_key: password
|
name: default-recovery-field-password
|
||||||
label: Password
|
|
||||||
id: prompt-field-password
|
id: prompt-field-password
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
attrs:
|
attrs:
|
||||||
|
field_key: password
|
||||||
|
label: Password
|
||||||
type: password
|
type: password
|
||||||
required: true
|
required: true
|
||||||
placeholder: Password
|
placeholder: Password
|
||||||
order: 0
|
order: 0
|
||||||
placeholder_expression: false
|
placeholder_expression: false
|
||||||
- identifiers:
|
- identifiers:
|
||||||
field_key: password_repeat
|
name: default-recovery-field-password-repeat
|
||||||
label: Password (repeat)
|
|
||||||
id: prompt-field-password-repeat
|
id: prompt-field-password-repeat
|
||||||
model: authentik_stages_prompt.prompt
|
model: authentik_stages_prompt.prompt
|
||||||
attrs:
|
attrs:
|
||||||
|
field_key: password_repeat
|
||||||
|
label: Password (repeat)
|
||||||
type: password
|
type: password
|
||||||
required: true
|
required: true
|
||||||
placeholder: Password (repeat)
|
placeholder: Password (repeat)
|
||||||
|
|
70
blueprints/migrations/migrate-prompt-name.yaml
Normal file
70
blueprints/migrations/migrate-prompt-name.yaml
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
version: 1
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
blueprints.goauthentik.io/description: Migrate to 2023.2, remove unused prompt fields
|
||||||
|
name: Migration - Remove old prompt fields
|
||||||
|
entries:
|
||||||
|
- model: authentik_stages_prompt.prompt
|
||||||
|
identifiers:
|
||||||
|
name: admin_email_stage-default-oobe-password
|
||||||
|
attrs:
|
||||||
|
field_key: foo
|
||||||
|
label: foo
|
||||||
|
type: text
|
||||||
|
state: absent
|
||||||
|
- model: authentik_stages_prompt.prompt
|
||||||
|
identifiers:
|
||||||
|
name: attributes.settings.locale_default-user-settings
|
||||||
|
attrs:
|
||||||
|
field_key: foo
|
||||||
|
label: foo
|
||||||
|
type: text
|
||||||
|
state: absent
|
||||||
|
- model: authentik_stages_prompt.prompt
|
||||||
|
identifiers:
|
||||||
|
name: email_default-user-settings
|
||||||
|
attrs:
|
||||||
|
field_key: foo
|
||||||
|
label: foo
|
||||||
|
type: text
|
||||||
|
state: absent
|
||||||
|
- model: authentik_stages_prompt.prompt
|
||||||
|
identifiers:
|
||||||
|
name: name_default-user-settings
|
||||||
|
attrs:
|
||||||
|
field_key: foo
|
||||||
|
label: foo
|
||||||
|
type: text
|
||||||
|
state: absent
|
||||||
|
- model: authentik_stages_prompt.prompt
|
||||||
|
identifiers:
|
||||||
|
name: oobe-header-text_stage-default-oobe-password
|
||||||
|
attrs:
|
||||||
|
field_key: foo
|
||||||
|
label: foo
|
||||||
|
type: text
|
||||||
|
state: absent
|
||||||
|
- model: authentik_stages_prompt.prompt
|
||||||
|
identifiers:
|
||||||
|
name: password_default-password-change-prompt
|
||||||
|
attrs:
|
||||||
|
field_key: foo
|
||||||
|
label: foo
|
||||||
|
type: text
|
||||||
|
state: absent
|
||||||
|
- model: authentik_stages_prompt.prompt
|
||||||
|
identifiers:
|
||||||
|
name: password_repeat_default-password-change-prompt
|
||||||
|
attrs:
|
||||||
|
field_key: foo
|
||||||
|
label: foo
|
||||||
|
type: text
|
||||||
|
state: absent
|
||||||
|
- model: authentik_stages_prompt.prompt
|
||||||
|
identifiers:
|
||||||
|
name: username_default-source-enrollment-prompt
|
||||||
|
attrs:
|
||||||
|
field_key: foo
|
||||||
|
label: foo
|
||||||
|
type: text
|
||||||
|
state: absent
|
14
schema.yml
14
schema.yml
|
@ -23008,6 +23008,10 @@ paths:
|
||||||
name: label
|
name: label
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: name
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
- name: ordering
|
- name: ordering
|
||||||
required: false
|
required: false
|
||||||
in: query
|
in: query
|
||||||
|
@ -34263,6 +34267,9 @@ components:
|
||||||
type: object
|
type: object
|
||||||
description: Prompt Serializer
|
description: Prompt Serializer
|
||||||
properties:
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
field_key:
|
field_key:
|
||||||
type: string
|
type: string
|
||||||
minLength: 1
|
minLength: 1
|
||||||
|
@ -35278,6 +35285,8 @@ components:
|
||||||
format: uuid
|
format: uuid
|
||||||
readOnly: true
|
readOnly: true
|
||||||
title: Prompt uuid
|
title: Prompt uuid
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
field_key:
|
field_key:
|
||||||
type: string
|
type: string
|
||||||
description: Name of the form field, also used to store the value
|
description: Name of the form field, also used to store the value
|
||||||
|
@ -35304,6 +35313,7 @@ components:
|
||||||
required:
|
required:
|
||||||
- field_key
|
- field_key
|
||||||
- label
|
- label
|
||||||
|
- name
|
||||||
- pk
|
- pk
|
||||||
- type
|
- type
|
||||||
PromptChallenge:
|
PromptChallenge:
|
||||||
|
@ -35345,6 +35355,9 @@ components:
|
||||||
type: object
|
type: object
|
||||||
description: Prompt Serializer
|
description: Prompt Serializer
|
||||||
properties:
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
field_key:
|
field_key:
|
||||||
type: string
|
type: string
|
||||||
minLength: 1
|
minLength: 1
|
||||||
|
@ -35373,6 +35386,7 @@ components:
|
||||||
required:
|
required:
|
||||||
- field_key
|
- field_key
|
||||||
- label
|
- label
|
||||||
|
- name
|
||||||
- type
|
- type
|
||||||
PromptStage:
|
PromptStage:
|
||||||
type: object
|
type: object
|
||||||
|
|
|
@ -26,8 +26,8 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
def test_totp_validate(self):
|
def test_totp_validate(self):
|
||||||
"""test flow with otp stages"""
|
"""test flow with otp stages"""
|
||||||
|
@ -52,10 +52,10 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint("default/20-flow-default-authenticator-totp-setup.yaml")
|
@apply_blueprint("default/flow-default-authenticator-totp-setup.yaml")
|
||||||
def test_totp_setup(self):
|
def test_totp_setup(self):
|
||||||
"""test TOTP Setup stage"""
|
"""test TOTP Setup stage"""
|
||||||
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
||||||
|
@ -98,10 +98,10 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint("default/20-flow-default-authenticator-static-setup.yaml")
|
@apply_blueprint("default/flow-default-authenticator-static-setup.yaml")
|
||||||
def test_static_setup(self):
|
def test_static_setup(self):
|
||||||
"""test Static OTP Setup stage"""
|
"""test Static OTP Setup stage"""
|
||||||
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
||||||
|
|
|
@ -41,19 +41,28 @@ class TestFlowsEnroll(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
def test_enroll_2_step(self):
|
def test_enroll_2_step(self):
|
||||||
"""Test 2-step enroll flow"""
|
"""Test 2-step enroll flow"""
|
||||||
# First stage fields
|
# First stage fields
|
||||||
username_prompt = Prompt.objects.create(
|
username_prompt = Prompt.objects.create(
|
||||||
field_key="username", label="Username", order=0, type=FieldTypes.TEXT
|
name=generate_id(),
|
||||||
|
field_key="username",
|
||||||
|
label="Username",
|
||||||
|
order=0,
|
||||||
|
type=FieldTypes.TEXT,
|
||||||
)
|
)
|
||||||
password = Prompt.objects.create(
|
password = Prompt.objects.create(
|
||||||
field_key="password", label="Password", order=1, type=FieldTypes.PASSWORD
|
name=generate_id(),
|
||||||
|
field_key="password",
|
||||||
|
label="Password",
|
||||||
|
order=1,
|
||||||
|
type=FieldTypes.PASSWORD,
|
||||||
)
|
)
|
||||||
password_repeat = Prompt.objects.create(
|
password_repeat = Prompt.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
field_key="password_repeat",
|
field_key="password_repeat",
|
||||||
label="Password (repeat)",
|
label="Password (repeat)",
|
||||||
order=2,
|
order=2,
|
||||||
|
@ -62,10 +71,10 @@ class TestFlowsEnroll(SeleniumTestCase):
|
||||||
|
|
||||||
# Second stage fields
|
# Second stage fields
|
||||||
name_field = Prompt.objects.create(
|
name_field = Prompt.objects.create(
|
||||||
field_key="name", label="Name", order=0, type=FieldTypes.TEXT
|
name=generate_id(), field_key="name", label="Name", order=0, type=FieldTypes.TEXT
|
||||||
)
|
)
|
||||||
email = Prompt.objects.create(
|
email = Prompt.objects.create(
|
||||||
field_key="email", label="E-Mail", order=1, type=FieldTypes.EMAIL
|
name=generate_id(), field_key="email", label="E-Mail", order=1, type=FieldTypes.EMAIL
|
||||||
)
|
)
|
||||||
|
|
||||||
# Stages
|
# Stages
|
||||||
|
@ -107,19 +116,28 @@ class TestFlowsEnroll(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
def test_enroll_email(self):
|
def test_enroll_email(self):
|
||||||
"""Test enroll with Email verification"""
|
"""Test enroll with Email verification"""
|
||||||
# First stage fields
|
# First stage fields
|
||||||
username_prompt = Prompt.objects.create(
|
username_prompt = Prompt.objects.create(
|
||||||
field_key="username", label="Username", order=0, type=FieldTypes.TEXT
|
name=generate_id(),
|
||||||
|
field_key="username",
|
||||||
|
label="Username",
|
||||||
|
order=0,
|
||||||
|
type=FieldTypes.TEXT,
|
||||||
)
|
)
|
||||||
password = Prompt.objects.create(
|
password = Prompt.objects.create(
|
||||||
field_key="password", label="Password", order=1, type=FieldTypes.PASSWORD
|
name=generate_id(),
|
||||||
|
field_key="password",
|
||||||
|
label="Password",
|
||||||
|
order=1,
|
||||||
|
type=FieldTypes.PASSWORD,
|
||||||
)
|
)
|
||||||
password_repeat = Prompt.objects.create(
|
password_repeat = Prompt.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
field_key="password_repeat",
|
field_key="password_repeat",
|
||||||
label="Password (repeat)",
|
label="Password (repeat)",
|
||||||
order=2,
|
order=2,
|
||||||
|
@ -128,10 +146,10 @@ class TestFlowsEnroll(SeleniumTestCase):
|
||||||
|
|
||||||
# Second stage fields
|
# Second stage fields
|
||||||
name_field = Prompt.objects.create(
|
name_field = Prompt.objects.create(
|
||||||
field_key="name", label="Name", order=0, type=FieldTypes.TEXT
|
name=generate_id(), field_key="name", label="Name", order=0, type=FieldTypes.TEXT
|
||||||
)
|
)
|
||||||
email = Prompt.objects.create(
|
email = Prompt.objects.create(
|
||||||
field_key="email", label="E-Mail", order=1, type=FieldTypes.EMAIL
|
name=generate_id(), field_key="email", label="E-Mail", order=1, type=FieldTypes.EMAIL
|
||||||
)
|
)
|
||||||
|
|
||||||
# Stages
|
# Stages
|
||||||
|
|
|
@ -12,8 +12,8 @@ class TestFlowsLogin(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
def test_login(self):
|
def test_login(self):
|
||||||
"""test default login flow"""
|
"""test default login flow"""
|
||||||
|
|
|
@ -18,10 +18,10 @@ class TestFlowsStageSetup(SeleniumTestCase):
|
||||||
"""test stage setup flows"""
|
"""test stage setup flows"""
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint("default/0-flow-password-change.yaml")
|
@apply_blueprint("default/flow-password-change.yaml")
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
def test_password_change(self):
|
def test_password_change(self):
|
||||||
"""test password change flow"""
|
"""test password change flow"""
|
||||||
|
|
|
@ -81,8 +81,8 @@ class TestProviderLDAP(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
def test_ldap_bind_success(self):
|
def test_ldap_bind_success(self):
|
||||||
"""Test simple bind"""
|
"""Test simple bind"""
|
||||||
|
@ -108,8 +108,8 @@ class TestProviderLDAP(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
def test_ldap_bind_success_ssl(self):
|
def test_ldap_bind_success_ssl(self):
|
||||||
"""Test simple bind with ssl"""
|
"""Test simple bind with ssl"""
|
||||||
|
@ -135,8 +135,8 @@ class TestProviderLDAP(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
def test_ldap_bind_fail(self):
|
def test_ldap_bind_fail(self):
|
||||||
"""Test simple bind (failed)"""
|
"""Test simple bind (failed)"""
|
||||||
|
@ -160,8 +160,8 @@ class TestProviderLDAP(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@reconcile_app("authentik_outposts")
|
@reconcile_app("authentik_outposts")
|
||||||
def test_ldap_bind_search(self):
|
def test_ldap_bind_search(self):
|
||||||
|
|
|
@ -58,12 +58,12 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"system/providers-oauth2.yaml",
|
"system/providers-oauth2.yaml",
|
||||||
|
@ -114,12 +114,12 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"system/providers-oauth2.yaml",
|
"system/providers-oauth2.yaml",
|
||||||
|
@ -189,12 +189,12 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@reconcile_app("authentik_crypto")
|
@reconcile_app("authentik_crypto")
|
||||||
def test_denied(self):
|
def test_denied(self):
|
||||||
|
|
|
@ -67,12 +67,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"system/providers-oauth2.yaml",
|
"system/providers-oauth2.yaml",
|
||||||
|
@ -116,12 +116,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"system/providers-oauth2.yaml",
|
"system/providers-oauth2.yaml",
|
||||||
|
@ -178,12 +178,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"system/providers-oauth2.yaml",
|
"system/providers-oauth2.yaml",
|
||||||
|
@ -249,12 +249,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"system/providers-oauth2.yaml",
|
"system/providers-oauth2.yaml",
|
||||||
|
@ -329,12 +329,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"system/providers-oauth2.yaml",
|
"system/providers-oauth2.yaml",
|
||||||
|
|
|
@ -65,12 +65,12 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@reconcile_app("authentik_crypto")
|
@reconcile_app("authentik_crypto")
|
||||||
def test_redirect_uri_error(self):
|
def test_redirect_uri_error(self):
|
||||||
|
@ -111,12 +111,12 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@reconcile_app("authentik_crypto")
|
@reconcile_app("authentik_crypto")
|
||||||
@apply_blueprint("system/providers-oauth2.yaml")
|
@apply_blueprint("system/providers-oauth2.yaml")
|
||||||
|
@ -166,12 +166,12 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@reconcile_app("authentik_crypto")
|
@reconcile_app("authentik_crypto")
|
||||||
@apply_blueprint("system/providers-oauth2.yaml")
|
@apply_blueprint("system/providers-oauth2.yaml")
|
||||||
|
@ -236,12 +236,12 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@reconcile_app("authentik_crypto")
|
@reconcile_app("authentik_crypto")
|
||||||
def test_authorization_denied(self):
|
def test_authorization_denied(self):
|
||||||
|
|
|
@ -65,12 +65,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@reconcile_app("authentik_crypto")
|
@reconcile_app("authentik_crypto")
|
||||||
def test_redirect_uri_error(self):
|
def test_redirect_uri_error(self):
|
||||||
|
@ -111,12 +111,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@reconcile_app("authentik_crypto")
|
@reconcile_app("authentik_crypto")
|
||||||
@apply_blueprint("system/providers-oauth2.yaml")
|
@apply_blueprint("system/providers-oauth2.yaml")
|
||||||
|
@ -161,12 +161,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@reconcile_app("authentik_crypto")
|
@reconcile_app("authentik_crypto")
|
||||||
@apply_blueprint("system/providers-oauth2.yaml")
|
@apply_blueprint("system/providers-oauth2.yaml")
|
||||||
|
@ -227,12 +227,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@reconcile_app("authentik_crypto")
|
@reconcile_app("authentik_crypto")
|
||||||
def test_authorization_denied(self):
|
def test_authorization_denied(self):
|
||||||
|
|
|
@ -57,12 +57,12 @@ class TestProviderProxy(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"system/providers-oauth2.yaml",
|
"system/providers-oauth2.yaml",
|
||||||
|
@ -123,12 +123,12 @@ class TestProviderProxy(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"system/providers-oauth2.yaml",
|
"system/providers-oauth2.yaml",
|
||||||
|
@ -200,12 +200,12 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@reconcile_app("authentik_crypto")
|
@reconcile_app("authentik_crypto")
|
||||||
def test_proxy_connectivity(self):
|
def test_proxy_connectivity(self):
|
||||||
|
|
|
@ -64,12 +64,12 @@ class TestProviderSAML(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"system/providers-saml.yaml",
|
"system/providers-saml.yaml",
|
||||||
|
@ -133,12 +133,12 @@ class TestProviderSAML(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"system/providers-saml.yaml",
|
"system/providers-saml.yaml",
|
||||||
|
@ -217,12 +217,12 @@ class TestProviderSAML(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"system/providers-saml.yaml",
|
"system/providers-saml.yaml",
|
||||||
|
@ -301,12 +301,12 @@ class TestProviderSAML(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"system/providers-saml.yaml",
|
"system/providers-saml.yaml",
|
||||||
|
@ -376,12 +376,12 @@ class TestProviderSAML(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"system/providers-saml.yaml",
|
"system/providers-saml.yaml",
|
||||||
|
@ -425,12 +425,12 @@ class TestProviderSAML(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"system/providers-saml.yaml",
|
"system/providers-saml.yaml",
|
||||||
|
|
|
@ -143,17 +143,17 @@ class TestSourceOAuth2(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-source-authentication.yaml",
|
"default/flow-default-source-authentication.yaml",
|
||||||
"default/20-flow-default-source-enrollment.yaml",
|
"default/flow-default-source-enrollment.yaml",
|
||||||
"default/20-flow-default-source-pre-authentication.yaml",
|
"default/flow-default-source-pre-authentication.yaml",
|
||||||
)
|
)
|
||||||
def test_oauth_enroll(self):
|
def test_oauth_enroll(self):
|
||||||
"""test OAuth Source With With OIDC"""
|
"""test OAuth Source With With OIDC"""
|
||||||
|
@ -200,12 +200,12 @@ class TestSourceOAuth2(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||||
)
|
)
|
||||||
def test_oauth_enroll_auth(self):
|
def test_oauth_enroll_auth(self):
|
||||||
"""test OAuth Source With With OIDC (enroll and authenticate again)"""
|
"""test OAuth Source With With OIDC (enroll and authenticate again)"""
|
||||||
|
@ -292,13 +292,13 @@ class TestSourceOAuth1(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-source-authentication.yaml",
|
"default/flow-default-source-authentication.yaml",
|
||||||
"default/20-flow-default-source-enrollment.yaml",
|
"default/flow-default-source-enrollment.yaml",
|
||||||
"default/20-flow-default-source-pre-authentication.yaml",
|
"default/flow-default-source-pre-authentication.yaml",
|
||||||
)
|
)
|
||||||
def test_oauth_enroll(self):
|
def test_oauth_enroll(self):
|
||||||
"""test OAuth Source With With OIDC"""
|
"""test OAuth Source With With OIDC"""
|
||||||
|
|
|
@ -96,13 +96,13 @@ class TestSourceSAML(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-source-authentication.yaml",
|
"default/flow-default-source-authentication.yaml",
|
||||||
"default/20-flow-default-source-enrollment.yaml",
|
"default/flow-default-source-enrollment.yaml",
|
||||||
"default/20-flow-default-source-pre-authentication.yaml",
|
"default/flow-default-source-pre-authentication.yaml",
|
||||||
)
|
)
|
||||||
def test_idp_redirect(self):
|
def test_idp_redirect(self):
|
||||||
"""test SAML Source With redirect binding"""
|
"""test SAML Source With redirect binding"""
|
||||||
|
@ -166,13 +166,13 @@ class TestSourceSAML(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-source-authentication.yaml",
|
"default/flow-default-source-authentication.yaml",
|
||||||
"default/20-flow-default-source-enrollment.yaml",
|
"default/flow-default-source-enrollment.yaml",
|
||||||
"default/20-flow-default-source-pre-authentication.yaml",
|
"default/flow-default-source-pre-authentication.yaml",
|
||||||
)
|
)
|
||||||
def test_idp_post(self):
|
def test_idp_post(self):
|
||||||
"""test SAML Source With post binding"""
|
"""test SAML Source With post binding"""
|
||||||
|
@ -249,13 +249,13 @@ class TestSourceSAML(SeleniumTestCase):
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/10-flow-default-authentication-flow.yaml",
|
"default/flow-default-authentication-flow.yaml",
|
||||||
"default/10-flow-default-invalidation-flow.yaml",
|
"default/flow-default-invalidation-flow.yaml",
|
||||||
)
|
)
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
"default/20-flow-default-source-authentication.yaml",
|
"default/flow-default-source-authentication.yaml",
|
||||||
"default/20-flow-default-source-enrollment.yaml",
|
"default/flow-default-source-enrollment.yaml",
|
||||||
"default/20-flow-default-source-pre-authentication.yaml",
|
"default/flow-default-source-pre-authentication.yaml",
|
||||||
)
|
)
|
||||||
def test_idp_post_auto(self):
|
def test_idp_post_auto(self):
|
||||||
"""test SAML Source With post binding (auto redirect)"""
|
"""test SAML Source With post binding (auto redirect)"""
|
||||||
|
|
|
@ -13,9 +13,11 @@ import { TablePage } from "@goauthentik/elements/table/TablePage";
|
||||||
|
|
||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
|
|
||||||
import { TemplateResult, html } from "lit";
|
import { CSSResult, TemplateResult, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
|
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||||
|
|
||||||
import { BlueprintInstance, BlueprintInstanceStatusEnum, ManagedApi } from "@goauthentik/api";
|
import { BlueprintInstance, BlueprintInstanceStatusEnum, ManagedApi } from "@goauthentik/api";
|
||||||
|
|
||||||
export function BlueprintStatus(blueprint?: BlueprintInstance): string {
|
export function BlueprintStatus(blueprint?: BlueprintInstance): string {
|
||||||
|
@ -32,6 +34,7 @@ export function BlueprintStatus(blueprint?: BlueprintInstance): string {
|
||||||
}
|
}
|
||||||
return t`Unknown`;
|
return t`Unknown`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement("ak-blueprint-list")
|
@customElement("ak-blueprint-list")
|
||||||
export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
||||||
searchEnabled(): boolean {
|
searchEnabled(): boolean {
|
||||||
|
@ -47,11 +50,16 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
||||||
return "pf-icon pf-icon-blueprint";
|
return "pf-icon pf-icon-blueprint";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expandable = true;
|
||||||
checkbox = true;
|
checkbox = true;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
order = "name";
|
order = "name";
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return super.styles.concat(PFDescriptionList);
|
||||||
|
}
|
||||||
|
|
||||||
async apiEndpoint(page: number): Promise<PaginatedResponse<BlueprintInstance>> {
|
async apiEndpoint(page: number): Promise<PaginatedResponse<BlueprintInstance>> {
|
||||||
return new ManagedApi(DEFAULT_CONFIG).managedBlueprintsList({
|
return new ManagedApi(DEFAULT_CONFIG).managedBlueprintsList({
|
||||||
ordering: this.order,
|
ordering: this.order,
|
||||||
|
@ -96,9 +104,34 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
||||||
</ak-forms-delete-bulk>`;
|
</ak-forms-delete-bulk>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderExpanded(item: BlueprintInstance): TemplateResult {
|
||||||
|
return html`<td role="cell" colspan="4">
|
||||||
|
<div class="pf-c-table__expandable-row-content">
|
||||||
|
<dl class="pf-c-description-list pf-m-horizontal">
|
||||||
|
<div class="pf-c-description-list__group">
|
||||||
|
<dt class="pf-c-description-list__term">
|
||||||
|
<span class="pf-c-description-list__text">${t`Path`}</span>
|
||||||
|
</dt>
|
||||||
|
<dd class="pf-c-description-list__description">
|
||||||
|
<div class="pf-c-description-list__text">
|
||||||
|
<pre>${item.path}</pre>
|
||||||
|
</div>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</td>`;
|
||||||
|
}
|
||||||
|
|
||||||
row(item: BlueprintInstance): TemplateResult[] {
|
row(item: BlueprintInstance): TemplateResult[] {
|
||||||
|
let description = undefined;
|
||||||
|
const descKey = "blueprints.goauthentik.io/description";
|
||||||
|
if (Object.hasOwn(item.metadata.labels, descKey)) {
|
||||||
|
description = item.metadata.labels[descKey];
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
html`${item.name}`,
|
html`<div>${item.name}</div>
|
||||||
|
${description ? html`<small>${description}</small>` : html``}`,
|
||||||
html`${BlueprintStatus(item)}`,
|
html`${BlueprintStatus(item)}`,
|
||||||
html`${item.lastApplied.toLocaleString()}`,
|
html`${item.lastApplied.toLocaleString()}`,
|
||||||
html`<ak-label color=${item.enabled ? PFColor.Green : PFColor.Red}>
|
html`<ak-label color=${item.enabled ? PFColor.Green : PFColor.Red}>
|
||||||
|
|
|
@ -132,6 +132,17 @@ export class PromptForm extends ModelForm<Prompt, string> {
|
||||||
|
|
||||||
renderForm(): TemplateResult {
|
renderForm(): TemplateResult {
|
||||||
return html`<form class="pf-c-form pf-m-horizontal">
|
return html`<form class="pf-c-form pf-m-horizontal">
|
||||||
|
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${ifDefined(this.instance?.name)}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${t`Unique name of this field, used for selecting fields in prompt stages.`}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal label=${t`Field Key`} ?required=${true} name="fieldKey">
|
<ak-form-element-horizontal label=${t`Field Key`} ?required=${true} name="fieldKey">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import "@goauthentik/admin/stages/prompt/PromptForm";
|
import "@goauthentik/admin/stages/prompt/PromptForm";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { uiConfig } from "@goauthentik/common/ui/config";
|
import { uiConfig } from "@goauthentik/common/ui/config";
|
||||||
import { truncate } from "@goauthentik/common/utils";
|
|
||||||
import "@goauthentik/elements/buttons/ModalButton";
|
import "@goauthentik/elements/buttons/ModalButton";
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||||
|
@ -35,7 +34,7 @@ export class PromptListPage extends TablePage<Prompt> {
|
||||||
checkbox = true;
|
checkbox = true;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
order = "order";
|
order = "name";
|
||||||
|
|
||||||
async apiEndpoint(page: number): Promise<PaginatedResponse<Prompt>> {
|
async apiEndpoint(page: number): Promise<PaginatedResponse<Prompt>> {
|
||||||
return new StagesApi(DEFAULT_CONFIG).stagesPromptPromptsList({
|
return new StagesApi(DEFAULT_CONFIG).stagesPromptPromptsList({
|
||||||
|
@ -48,8 +47,8 @@ export class PromptListPage extends TablePage<Prompt> {
|
||||||
|
|
||||||
columns(): TableColumn[] {
|
columns(): TableColumn[] {
|
||||||
return [
|
return [
|
||||||
|
new TableColumn(t`Name`, "name"),
|
||||||
new TableColumn(t`Field`, "field_key"),
|
new TableColumn(t`Field`, "field_key"),
|
||||||
new TableColumn(t`Label`, "label"),
|
|
||||||
new TableColumn(t`Type`, "type"),
|
new TableColumn(t`Type`, "type"),
|
||||||
new TableColumn(t`Order`, "order"),
|
new TableColumn(t`Order`, "order"),
|
||||||
new TableColumn(t`Stages`),
|
new TableColumn(t`Stages`),
|
||||||
|
@ -81,8 +80,8 @@ export class PromptListPage extends TablePage<Prompt> {
|
||||||
|
|
||||||
row(item: Prompt): TemplateResult[] {
|
row(item: Prompt): TemplateResult[] {
|
||||||
return [
|
return [
|
||||||
html`${item.fieldKey}`,
|
html`${item.name}`,
|
||||||
html`${truncate(item.label, 20)}`,
|
html`<code>${item.fieldKey}</code>`,
|
||||||
html`${item.type}`,
|
html`${item.type}`,
|
||||||
html`${item.order}`,
|
html`${item.order}`,
|
||||||
html`${item.promptstageSet?.map((stage) => {
|
html`${item.promptstageSet?.map((stage) => {
|
||||||
|
|
|
@ -34,6 +34,8 @@ Any additional `.yaml` file in `/blueprints` will be discovered and automaticall
|
||||||
|
|
||||||
To disable existing blueprints, an empty file can be mounted over the existing blueprint.
|
To disable existing blueprints, an empty file can be mounted over the existing blueprint.
|
||||||
|
|
||||||
|
File-based blueprints are automatically removed once they become unavailable, however none of the objects created by those blueprints afre affected by this.
|
||||||
|
|
||||||
## Storage - OCI
|
## Storage - OCI
|
||||||
|
|
||||||
Blueprints can also be stored in remote [OCI](https://opencontainers.org/) compliant registries. This includes GitHub Container Registry, Docker hub and many other registries.
|
Blueprints can also be stored in remote [OCI](https://opencontainers.org/) compliant registries. This includes GitHub Container Registry, Docker hub and many other registries.
|
||||||
|
|
|
@ -60,3 +60,7 @@ Used by authentik's packaged blueprints to keep globals up-to-date. Should only
|
||||||
#### `blueprints.goauthentik.io/instantiate`:
|
#### `blueprints.goauthentik.io/instantiate`:
|
||||||
|
|
||||||
Configure if this blueprint should automatically be instantiated (defaults to `"true"`). When set to `"false"`, blueprints are listed and available to be instantiated via API/Browser.
|
Configure if this blueprint should automatically be instantiated (defaults to `"true"`). When set to `"false"`, blueprints are listed and available to be instantiated via API/Browser.
|
||||||
|
|
||||||
|
#### `blueprints.goauthentik.io/description`:
|
||||||
|
|
||||||
|
Optionally set a description, which can be seen in the web interface.
|
||||||
|
|
Reference in a new issue