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):
|
||||
"""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()
|
||||
clear_failed_blueprints.delay()
|
||||
|
||||
def import_models(self):
|
||||
super().import_models()
|
||||
|
|
|
@ -9,4 +9,9 @@ CELERY_BEAT_SCHEDULE = {
|
|||
"schedule": crontab(minute=fqdn_rand("blueprints_v1_discover"), hour="*"),
|
||||
"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
|
||||
model: authentik_stages_prompt.prompt
|
||||
attrs:
|
||||
name: qwerweqrq
|
||||
field_key: username
|
||||
label: Username
|
||||
type: username
|
||||
|
|
|
@ -13,7 +13,7 @@ from authentik.tenants.models import Tenant
|
|||
class TestPackaged(TransactionTestCase):
|
||||
"""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):
|
||||
"""Test @apply_blueprint decorator"""
|
||||
self.assertTrue(Tenant.objects.filter(domain="authentik-default").exists())
|
||||
|
|
|
@ -262,15 +262,21 @@ class TestBlueprintsV1(TransactionTestCase):
|
|||
with transaction_rollback():
|
||||
# First stage fields
|
||||
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(
|
||||
name=generate_id(),
|
||||
field_key="password",
|
||||
label="Password",
|
||||
order=1,
|
||||
type=FieldTypes.PASSWORD,
|
||||
)
|
||||
password_repeat = Prompt.objects.create(
|
||||
name=generate_id(),
|
||||
field_key="password_repeat",
|
||||
label="Password (repeat)",
|
||||
order=2,
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
LABEL_AUTHENTIK_SYSTEM = "blueprints.goauthentik.io/system"
|
||||
LABEL_AUTHENTIK_INSTANTIATE = "blueprints.goauthentik.io/instantiate"
|
||||
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:
|
||||
if instance:
|
||||
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.flows.models import FlowDesignation, FlowStageBinding
|
||||
from authentik.flows.tests import FlowTestCase
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.policies.password.models import PasswordPolicy
|
||||
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
|
||||
|
||||
|
@ -16,6 +17,7 @@ class TestPasswordPolicyFlow(FlowTestCase):
|
|||
self.flow = create_test_flow(FlowDesignation.AUTHENTICATION)
|
||||
|
||||
password_prompt = Prompt.objects.create(
|
||||
name=generate_id(),
|
||||
field_key="password",
|
||||
label="PASSWORD_LABEL",
|
||||
type=FieldTypes.PASSWORD,
|
||||
|
|
|
@ -42,6 +42,7 @@ class PromptSerializer(ModelSerializer):
|
|||
model = Prompt
|
||||
fields = [
|
||||
"pk",
|
||||
"name",
|
||||
"field_key",
|
||||
"label",
|
||||
"type",
|
||||
|
@ -59,5 +60,5 @@ class PromptViewSet(UsedByMixin, ModelViewSet):
|
|||
|
||||
queryset = Prompt.objects.all().prefetch_related("promptstage_set")
|
||||
serializer_class = PromptSerializer
|
||||
filterset_fields = ["field_key", "label", "type", "placeholder"]
|
||||
search_fields = ["field_key", "label", "type", "placeholder"]
|
||||
filterset_fields = ["field_key", "name", "label", "type", "placeholder"]
|
||||
search_fields = ["field_key", "name", "label", "type", "placeholder"]
|
||||
|
|
|
@ -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."""
|
||||
|
||||
prompt_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
||||
name = models.TextField(unique=True, blank=False)
|
||||
|
||||
field_key = models.TextField(
|
||||
help_text=_("Name of the form field, also used to store the value")
|
||||
|
|
|
@ -30,6 +30,7 @@ class TestPromptStage(FlowTestCase):
|
|||
self.factory = RequestFactory()
|
||||
self.flow = create_test_flow()
|
||||
username_prompt = Prompt.objects.create(
|
||||
name=generate_id(),
|
||||
field_key="username_prompt",
|
||||
label="USERNAME_LABEL",
|
||||
type=FieldTypes.USERNAME,
|
||||
|
@ -37,6 +38,7 @@ class TestPromptStage(FlowTestCase):
|
|||
placeholder="USERNAME_PLACEHOLDER",
|
||||
)
|
||||
text_prompt = Prompt.objects.create(
|
||||
name=generate_id(),
|
||||
field_key="text_prompt",
|
||||
label="TEXT_LABEL",
|
||||
type=FieldTypes.TEXT,
|
||||
|
@ -44,6 +46,7 @@ class TestPromptStage(FlowTestCase):
|
|||
placeholder="TEXT_PLACEHOLDER",
|
||||
)
|
||||
email_prompt = Prompt.objects.create(
|
||||
name=generate_id(),
|
||||
field_key="email_prompt",
|
||||
label="EMAIL_LABEL",
|
||||
type=FieldTypes.EMAIL,
|
||||
|
@ -51,6 +54,7 @@ class TestPromptStage(FlowTestCase):
|
|||
placeholder="EMAIL_PLACEHOLDER",
|
||||
)
|
||||
password_prompt = Prompt.objects.create(
|
||||
name=generate_id(),
|
||||
field_key="password_prompt",
|
||||
label="PASSWORD_LABEL",
|
||||
type=FieldTypes.PASSWORD,
|
||||
|
@ -58,6 +62,7 @@ class TestPromptStage(FlowTestCase):
|
|||
placeholder="PASSWORD_PLACEHOLDER",
|
||||
)
|
||||
password2_prompt = Prompt.objects.create(
|
||||
name=generate_id(),
|
||||
field_key="password2_prompt",
|
||||
label="PASSWORD_LABEL",
|
||||
type=FieldTypes.PASSWORD,
|
||||
|
@ -65,6 +70,7 @@ class TestPromptStage(FlowTestCase):
|
|||
placeholder="PASSWORD_PLACEHOLDER",
|
||||
)
|
||||
number_prompt = Prompt.objects.create(
|
||||
name=generate_id(),
|
||||
field_key="number_prompt",
|
||||
label="NUMBER_LABEL",
|
||||
type=FieldTypes.NUMBER,
|
||||
|
@ -72,12 +78,14 @@ class TestPromptStage(FlowTestCase):
|
|||
placeholder="NUMBER_PLACEHOLDER",
|
||||
)
|
||||
hidden_prompt = Prompt.objects.create(
|
||||
name=generate_id(),
|
||||
field_key="hidden_prompt",
|
||||
type=FieldTypes.HIDDEN,
|
||||
required=True,
|
||||
placeholder="HIDDEN_PLACEHOLDER",
|
||||
)
|
||||
static_prompt = Prompt.objects.create(
|
||||
name=generate_id(),
|
||||
field_key="static_prompt",
|
||||
type=FieldTypes.STATIC,
|
||||
required=True,
|
||||
|
|
|
@ -17,9 +17,10 @@ entries:
|
|||
placeholder_expression: false
|
||||
required: true
|
||||
type: text
|
||||
identifiers:
|
||||
field_key: username
|
||||
label: Username
|
||||
identifiers:
|
||||
name: default-source-enrollment-field-username
|
||||
id: prompt-field-username
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
|
@ -21,9 +21,10 @@ entries:
|
|||
placeholder_expression: true
|
||||
required: true
|
||||
type: text
|
||||
identifiers:
|
||||
field_key: username
|
||||
label: Username
|
||||
identifiers:
|
||||
name: default-user-settings-field-username
|
||||
id: prompt-field-username
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
||||
|
@ -36,9 +37,10 @@ entries:
|
|||
placeholder_expression: true
|
||||
required: true
|
||||
type: text
|
||||
identifiers:
|
||||
field_key: name
|
||||
label: Name
|
||||
identifiers:
|
||||
name: default-user-settings-field-name
|
||||
id: prompt-field-name
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
||||
|
@ -51,9 +53,10 @@ entries:
|
|||
placeholder_expression: true
|
||||
required: true
|
||||
type: email
|
||||
identifiers:
|
||||
field_key: email
|
||||
label: Email
|
||||
identifiers:
|
||||
name: default-user-settings-field-email
|
||||
id: prompt-field-email
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
||||
|
@ -66,9 +69,10 @@ entries:
|
|||
placeholder_expression: true
|
||||
required: true
|
||||
type: ak-locale
|
||||
identifiers:
|
||||
field_key: attributes.settings.locale
|
||||
label: Locale
|
||||
identifiers:
|
||||
name: default-user-settings-field-locale
|
||||
id: prompt-field-locale
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
|
@ -19,10 +19,11 @@ entries:
|
|||
required: true
|
||||
sub_text: ''
|
||||
type: static
|
||||
id: prompt-field-header
|
||||
identifiers:
|
||||
field_key: oobe-header-text
|
||||
label: oobe-header-text
|
||||
id: prompt-field-header
|
||||
identifiers:
|
||||
name: initial-setup-field-header
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
||||
order: 101
|
||||
|
@ -31,10 +32,11 @@ entries:
|
|||
required: true
|
||||
sub_text: ''
|
||||
type: email
|
||||
field_key: email
|
||||
label: Email
|
||||
id: prompt-field-email
|
||||
identifiers:
|
||||
field_key: admin_email
|
||||
label: Email
|
||||
name: initial-setup-field-email
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
||||
order: 300
|
||||
|
@ -43,10 +45,11 @@ entries:
|
|||
required: true
|
||||
sub_text: ''
|
||||
type: password
|
||||
id: prompt-field-password
|
||||
identifiers:
|
||||
field_key: password
|
||||
label: Password
|
||||
id: prompt-field-password
|
||||
identifiers:
|
||||
name: initial-setup-field-password
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
||||
order: 301
|
||||
|
@ -55,10 +58,11 @@ entries:
|
|||
required: true
|
||||
sub_text: ''
|
||||
type: password
|
||||
id: prompt-field-password-repeat
|
||||
identifiers:
|
||||
field_key: password_repeat
|
||||
label: Password (repeat)
|
||||
id: prompt-field-password-repeat
|
||||
identifiers:
|
||||
name: initial-setup-field-password-repeat
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
||||
expression: |
|
||||
|
@ -66,8 +70,6 @@ entries:
|
|||
# by injecting "pending_user"
|
||||
akadmin = ak_user_by(username="akadmin")
|
||||
context["flow_plan"].context["pending_user"] = akadmin
|
||||
# Remap the email value
|
||||
context["prompt_data"]["email"] = context["prompt_data"]["admin_email"]
|
||||
return True
|
||||
id: policy-default-oobe-prefill-user
|
||||
identifiers:
|
|
@ -17,9 +17,10 @@ entries:
|
|||
placeholder_expression: false
|
||||
required: true
|
||||
type: password
|
||||
identifiers:
|
||||
field_key: password
|
||||
label: Password
|
||||
identifiers:
|
||||
name: default-password-change-field-password
|
||||
id: prompt-field-password
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
||||
|
@ -28,9 +29,10 @@ entries:
|
|||
placeholder_expression: false
|
||||
required: true
|
||||
type: password
|
||||
identifiers:
|
||||
field_key: password_repeat
|
||||
label: Password (repeat)
|
||||
identifiers:
|
||||
name: default-password-change-field-password-repeat
|
||||
id: prompt-field-password-repeat
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
|
@ -13,56 +13,61 @@ entries:
|
|||
title: Welcome to authentik!
|
||||
designation: enrollment
|
||||
authentication: require_unauthenticated
|
||||
- identifiers:
|
||||
- id: prompt-field-username
|
||||
model: authentik_stages_prompt.prompt
|
||||
identifiers:
|
||||
name: default-enrollment-field-username
|
||||
attrs:
|
||||
field_key: username
|
||||
label: Username
|
||||
id: prompt-field-username
|
||||
model: authentik_stages_prompt.prompt
|
||||
attrs:
|
||||
type: username
|
||||
required: true
|
||||
placeholder: Username
|
||||
placeholder_expression: false
|
||||
order: 0
|
||||
- identifiers:
|
||||
field_key: password
|
||||
label: Password
|
||||
name: default-enrollment-field-password
|
||||
id: prompt-field-password
|
||||
model: authentik_stages_prompt.prompt
|
||||
attrs:
|
||||
field_key: password
|
||||
label: Password
|
||||
type: password
|
||||
required: true
|
||||
placeholder: Password
|
||||
placeholder_expression: false
|
||||
order: 0
|
||||
- identifiers:
|
||||
field_key: password_repeat
|
||||
label: Password (repeat)
|
||||
name: default-enrollment-field-password-repeat
|
||||
id: prompt-field-password-repeat
|
||||
model: authentik_stages_prompt.prompt
|
||||
attrs:
|
||||
field_key: password_repeat
|
||||
label: Password (repeat)
|
||||
type: password
|
||||
required: true
|
||||
placeholder: Password (repeat)
|
||||
placeholder_expression: false
|
||||
order: 1
|
||||
- identifiers:
|
||||
field_key: name
|
||||
label: Name
|
||||
name: default-enrollment-field-name
|
||||
id: prompt-field-name
|
||||
model: authentik_stages_prompt.prompt
|
||||
attrs:
|
||||
field_key: name
|
||||
label: Name
|
||||
type: text
|
||||
required: true
|
||||
placeholder: Name
|
||||
placeholder_expression: false
|
||||
order: 0
|
||||
- identifiers:
|
||||
field_key: email
|
||||
label: Email
|
||||
name: default-enrollment-field-email
|
||||
id: prompt-field-email
|
||||
model: authentik_stages_prompt.prompt
|
||||
attrs:
|
||||
field_key: email
|
||||
label: Email
|
||||
type: email
|
||||
required: true
|
||||
placeholder: Email
|
||||
|
|
|
@ -14,55 +14,60 @@ entries:
|
|||
designation: enrollment
|
||||
authentication: require_unauthenticated
|
||||
- identifiers:
|
||||
field_key: username
|
||||
label: Username
|
||||
name: default-enrollment-field-username
|
||||
id: prompt-field-username
|
||||
model: authentik_stages_prompt.prompt
|
||||
attrs:
|
||||
field_key: username
|
||||
label: Username
|
||||
type: username
|
||||
required: true
|
||||
placeholder: Username
|
||||
placeholder_expression: false
|
||||
order: 0
|
||||
- identifiers:
|
||||
field_key: password
|
||||
label: Password
|
||||
name: default-enrollment-field-password
|
||||
id: prompt-field-password
|
||||
model: authentik_stages_prompt.prompt
|
||||
attrs:
|
||||
field_key: password
|
||||
label: Password
|
||||
type: password
|
||||
required: true
|
||||
placeholder: Password
|
||||
placeholder_expression: false
|
||||
order: 0
|
||||
- identifiers:
|
||||
field_key: password_repeat
|
||||
label: Password (repeat)
|
||||
name: default-enrollment-field-password-repeat
|
||||
id: prompt-field-password-repeat
|
||||
model: authentik_stages_prompt.prompt
|
||||
attrs:
|
||||
field_key: password_repeat
|
||||
label: Password (repeat)
|
||||
type: password
|
||||
required: true
|
||||
placeholder: Password (repeat)
|
||||
placeholder_expression: false
|
||||
order: 1
|
||||
- identifiers:
|
||||
field_key: name
|
||||
label: Name
|
||||
name: default-enrollment-field-name
|
||||
id: prompt-field-name
|
||||
model: authentik_stages_prompt.prompt
|
||||
attrs:
|
||||
field_key: name
|
||||
label: Name
|
||||
type: text
|
||||
required: true
|
||||
placeholder: Name
|
||||
placeholder_expression: false
|
||||
order: 0
|
||||
- identifiers:
|
||||
field_key: email
|
||||
label: Email
|
||||
name: default-enrollment-field-email
|
||||
id: prompt-field-email
|
||||
model: authentik_stages_prompt.prompt
|
||||
attrs:
|
||||
field_key: email
|
||||
label: Email
|
||||
type: email
|
||||
required: true
|
||||
placeholder: Email
|
||||
|
|
|
@ -14,22 +14,24 @@ entries:
|
|||
designation: recovery
|
||||
authentication: require_unauthenticated
|
||||
- identifiers:
|
||||
field_key: password
|
||||
label: Password
|
||||
name: default-recovery-field-password
|
||||
id: prompt-field-password
|
||||
model: authentik_stages_prompt.prompt
|
||||
attrs:
|
||||
field_key: password
|
||||
label: Password
|
||||
type: password
|
||||
required: true
|
||||
placeholder: Password
|
||||
order: 0
|
||||
placeholder_expression: false
|
||||
- identifiers:
|
||||
field_key: password_repeat
|
||||
label: Password (repeat)
|
||||
name: default-recovery-field-password-repeat
|
||||
id: prompt-field-password-repeat
|
||||
model: authentik_stages_prompt.prompt
|
||||
attrs:
|
||||
field_key: password_repeat
|
||||
label: Password (repeat)
|
||||
type: password
|
||||
required: true
|
||||
placeholder: Password (repeat)
|
||||
|
|
|
@ -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
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: name
|
||||
schema:
|
||||
type: string
|
||||
- name: ordering
|
||||
required: false
|
||||
in: query
|
||||
|
@ -34263,6 +34267,9 @@ components:
|
|||
type: object
|
||||
description: Prompt Serializer
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
field_key:
|
||||
type: string
|
||||
minLength: 1
|
||||
|
@ -35278,6 +35285,8 @@ components:
|
|||
format: uuid
|
||||
readOnly: true
|
||||
title: Prompt uuid
|
||||
name:
|
||||
type: string
|
||||
field_key:
|
||||
type: string
|
||||
description: Name of the form field, also used to store the value
|
||||
|
@ -35304,6 +35313,7 @@ components:
|
|||
required:
|
||||
- field_key
|
||||
- label
|
||||
- name
|
||||
- pk
|
||||
- type
|
||||
PromptChallenge:
|
||||
|
@ -35345,6 +35355,9 @@ components:
|
|||
type: object
|
||||
description: Prompt Serializer
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
field_key:
|
||||
type: string
|
||||
minLength: 1
|
||||
|
@ -35373,6 +35386,7 @@ components:
|
|||
required:
|
||||
- field_key
|
||||
- label
|
||||
- name
|
||||
- type
|
||||
PromptStage:
|
||||
type: object
|
||||
|
|
|
@ -26,8 +26,8 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_totp_validate(self):
|
||||
"""test flow with otp stages"""
|
||||
|
@ -52,10 +52,10 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-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):
|
||||
"""test TOTP Setup stage"""
|
||||
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
||||
|
@ -98,10 +98,10 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-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):
|
||||
"""test Static OTP Setup stage"""
|
||||
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
||||
|
|
|
@ -41,19 +41,28 @@ class TestFlowsEnroll(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_enroll_2_step(self):
|
||||
"""Test 2-step enroll flow"""
|
||||
# First stage fields
|
||||
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(
|
||||
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(
|
||||
name=generate_id(),
|
||||
field_key="password_repeat",
|
||||
label="Password (repeat)",
|
||||
order=2,
|
||||
|
@ -62,10 +71,10 @@ class TestFlowsEnroll(SeleniumTestCase):
|
|||
|
||||
# Second stage fields
|
||||
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(
|
||||
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
|
||||
|
@ -107,19 +116,28 @@ class TestFlowsEnroll(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_enroll_email(self):
|
||||
"""Test enroll with Email verification"""
|
||||
# First stage fields
|
||||
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(
|
||||
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(
|
||||
name=generate_id(),
|
||||
field_key="password_repeat",
|
||||
label="Password (repeat)",
|
||||
order=2,
|
||||
|
@ -128,10 +146,10 @@ class TestFlowsEnroll(SeleniumTestCase):
|
|||
|
||||
# Second stage fields
|
||||
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(
|
||||
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
|
||||
|
|
|
@ -12,8 +12,8 @@ class TestFlowsLogin(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_login(self):
|
||||
"""test default login flow"""
|
||||
|
|
|
@ -18,10 +18,10 @@ class TestFlowsStageSetup(SeleniumTestCase):
|
|||
"""test stage setup flows"""
|
||||
|
||||
@retry()
|
||||
@apply_blueprint("default/0-flow-password-change.yaml")
|
||||
@apply_blueprint("default/flow-password-change.yaml")
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_password_change(self):
|
||||
"""test password change flow"""
|
||||
|
|
|
@ -81,8 +81,8 @@ class TestProviderLDAP(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_ldap_bind_success(self):
|
||||
"""Test simple bind"""
|
||||
|
@ -108,8 +108,8 @@ class TestProviderLDAP(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_ldap_bind_success_ssl(self):
|
||||
"""Test simple bind with ssl"""
|
||||
|
@ -135,8 +135,8 @@ class TestProviderLDAP(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_ldap_bind_fail(self):
|
||||
"""Test simple bind (failed)"""
|
||||
|
@ -160,8 +160,8 @@ class TestProviderLDAP(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_outposts")
|
||||
def test_ldap_bind_search(self):
|
||||
|
|
|
@ -58,12 +58,12 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-oauth2.yaml",
|
||||
|
@ -114,12 +114,12 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-oauth2.yaml",
|
||||
|
@ -189,12 +189,12 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_denied(self):
|
||||
|
|
|
@ -67,12 +67,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-oauth2.yaml",
|
||||
|
@ -116,12 +116,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-oauth2.yaml",
|
||||
|
@ -178,12 +178,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-oauth2.yaml",
|
||||
|
@ -249,12 +249,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-oauth2.yaml",
|
||||
|
@ -329,12 +329,12 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-oauth2.yaml",
|
||||
|
|
|
@ -65,12 +65,12 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_redirect_uri_error(self):
|
||||
|
@ -111,12 +111,12 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
@apply_blueprint("system/providers-oauth2.yaml")
|
||||
|
@ -166,12 +166,12 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
@apply_blueprint("system/providers-oauth2.yaml")
|
||||
|
@ -236,12 +236,12 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_authorization_denied(self):
|
||||
|
|
|
@ -65,12 +65,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_redirect_uri_error(self):
|
||||
|
@ -111,12 +111,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
@apply_blueprint("system/providers-oauth2.yaml")
|
||||
|
@ -161,12 +161,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
@apply_blueprint("system/providers-oauth2.yaml")
|
||||
|
@ -227,12 +227,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_authorization_denied(self):
|
||||
|
|
|
@ -57,12 +57,12 @@ class TestProviderProxy(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-oauth2.yaml",
|
||||
|
@ -123,12 +123,12 @@ class TestProviderProxy(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-oauth2.yaml",
|
||||
|
@ -200,12 +200,12 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_proxy_connectivity(self):
|
||||
|
|
|
@ -64,12 +64,12 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-saml.yaml",
|
||||
|
@ -133,12 +133,12 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-saml.yaml",
|
||||
|
@ -217,12 +217,12 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-saml.yaml",
|
||||
|
@ -301,12 +301,12 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-saml.yaml",
|
||||
|
@ -376,12 +376,12 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-saml.yaml",
|
||||
|
@ -425,12 +425,12 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-saml.yaml",
|
||||
|
|
|
@ -143,17 +143,17 @@ class TestSourceOAuth2(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-source-authentication.yaml",
|
||||
"default/20-flow-default-source-enrollment.yaml",
|
||||
"default/20-flow-default-source-pre-authentication.yaml",
|
||||
"default/flow-default-source-authentication.yaml",
|
||||
"default/flow-default-source-enrollment.yaml",
|
||||
"default/flow-default-source-pre-authentication.yaml",
|
||||
)
|
||||
def test_oauth_enroll(self):
|
||||
"""test OAuth Source With With OIDC"""
|
||||
|
@ -200,12 +200,12 @@ class TestSourceOAuth2(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
def test_oauth_enroll_auth(self):
|
||||
"""test OAuth Source With With OIDC (enroll and authenticate again)"""
|
||||
|
@ -292,13 +292,13 @@ class TestSourceOAuth1(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-source-authentication.yaml",
|
||||
"default/20-flow-default-source-enrollment.yaml",
|
||||
"default/20-flow-default-source-pre-authentication.yaml",
|
||||
"default/flow-default-source-authentication.yaml",
|
||||
"default/flow-default-source-enrollment.yaml",
|
||||
"default/flow-default-source-pre-authentication.yaml",
|
||||
)
|
||||
def test_oauth_enroll(self):
|
||||
"""test OAuth Source With With OIDC"""
|
||||
|
|
|
@ -96,13 +96,13 @@ class TestSourceSAML(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-source-authentication.yaml",
|
||||
"default/20-flow-default-source-enrollment.yaml",
|
||||
"default/20-flow-default-source-pre-authentication.yaml",
|
||||
"default/flow-default-source-authentication.yaml",
|
||||
"default/flow-default-source-enrollment.yaml",
|
||||
"default/flow-default-source-pre-authentication.yaml",
|
||||
)
|
||||
def test_idp_redirect(self):
|
||||
"""test SAML Source With redirect binding"""
|
||||
|
@ -166,13 +166,13 @@ class TestSourceSAML(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-source-authentication.yaml",
|
||||
"default/20-flow-default-source-enrollment.yaml",
|
||||
"default/20-flow-default-source-pre-authentication.yaml",
|
||||
"default/flow-default-source-authentication.yaml",
|
||||
"default/flow-default-source-enrollment.yaml",
|
||||
"default/flow-default-source-pre-authentication.yaml",
|
||||
)
|
||||
def test_idp_post(self):
|
||||
"""test SAML Source With post binding"""
|
||||
|
@ -249,13 +249,13 @@ class TestSourceSAML(SeleniumTestCase):
|
|||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/10-flow-default-authentication-flow.yaml",
|
||||
"default/10-flow-default-invalidation-flow.yaml",
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/20-flow-default-source-authentication.yaml",
|
||||
"default/20-flow-default-source-enrollment.yaml",
|
||||
"default/20-flow-default-source-pre-authentication.yaml",
|
||||
"default/flow-default-source-authentication.yaml",
|
||||
"default/flow-default-source-enrollment.yaml",
|
||||
"default/flow-default-source-pre-authentication.yaml",
|
||||
)
|
||||
def test_idp_post_auto(self):
|
||||
"""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 { TemplateResult, html } from "lit";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
|
||||
import { BlueprintInstance, BlueprintInstanceStatusEnum, ManagedApi } from "@goauthentik/api";
|
||||
|
||||
export function BlueprintStatus(blueprint?: BlueprintInstance): string {
|
||||
|
@ -32,6 +34,7 @@ export function BlueprintStatus(blueprint?: BlueprintInstance): string {
|
|||
}
|
||||
return t`Unknown`;
|
||||
}
|
||||
|
||||
@customElement("ak-blueprint-list")
|
||||
export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
||||
searchEnabled(): boolean {
|
||||
|
@ -47,11 +50,16 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
|||
return "pf-icon pf-icon-blueprint";
|
||||
}
|
||||
|
||||
expandable = true;
|
||||
checkbox = true;
|
||||
|
||||
@property()
|
||||
order = "name";
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(PFDescriptionList);
|
||||
}
|
||||
|
||||
async apiEndpoint(page: number): Promise<PaginatedResponse<BlueprintInstance>> {
|
||||
return new ManagedApi(DEFAULT_CONFIG).managedBlueprintsList({
|
||||
ordering: this.order,
|
||||
|
@ -96,9 +104,34 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
|||
</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[] {
|
||||
let description = undefined;
|
||||
const descKey = "blueprints.goauthentik.io/description";
|
||||
if (Object.hasOwn(item.metadata.labels, descKey)) {
|
||||
description = item.metadata.labels[descKey];
|
||||
}
|
||||
return [
|
||||
html`${item.name}`,
|
||||
html`<div>${item.name}</div>
|
||||
${description ? html`<small>${description}</small>` : html``}`,
|
||||
html`${BlueprintStatus(item)}`,
|
||||
html`${item.lastApplied.toLocaleString()}`,
|
||||
html`<ak-label color=${item.enabled ? PFColor.Green : PFColor.Red}>
|
||||
|
|
|
@ -132,6 +132,17 @@ export class PromptForm extends ModelForm<Prompt, string> {
|
|||
|
||||
renderForm(): TemplateResult {
|
||||
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">
|
||||
<input
|
||||
type="text"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import "@goauthentik/admin/stages/prompt/PromptForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { uiConfig } from "@goauthentik/common/ui/config";
|
||||
import { truncate } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/buttons/ModalButton";
|
||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||
|
@ -35,7 +34,7 @@ export class PromptListPage extends TablePage<Prompt> {
|
|||
checkbox = true;
|
||||
|
||||
@property()
|
||||
order = "order";
|
||||
order = "name";
|
||||
|
||||
async apiEndpoint(page: number): Promise<PaginatedResponse<Prompt>> {
|
||||
return new StagesApi(DEFAULT_CONFIG).stagesPromptPromptsList({
|
||||
|
@ -48,8 +47,8 @@ export class PromptListPage extends TablePage<Prompt> {
|
|||
|
||||
columns(): TableColumn[] {
|
||||
return [
|
||||
new TableColumn(t`Name`, "name"),
|
||||
new TableColumn(t`Field`, "field_key"),
|
||||
new TableColumn(t`Label`, "label"),
|
||||
new TableColumn(t`Type`, "type"),
|
||||
new TableColumn(t`Order`, "order"),
|
||||
new TableColumn(t`Stages`),
|
||||
|
@ -81,8 +80,8 @@ export class PromptListPage extends TablePage<Prompt> {
|
|||
|
||||
row(item: Prompt): TemplateResult[] {
|
||||
return [
|
||||
html`${item.fieldKey}`,
|
||||
html`${truncate(item.label, 20)}`,
|
||||
html`${item.name}`,
|
||||
html`<code>${item.fieldKey}</code>`,
|
||||
html`${item.type}`,
|
||||
html`${item.order}`,
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
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`:
|
||||
|
||||
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 New Issue