*: Squash Migrations (#1593)
* *: first squash pass Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * sources/saml: squash less Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * outposts: fix docker controller not correctly checking image Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * tests/e2e: fix old migration reference Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
35fa93d9aa
commit
e4f141c6c0
|
@ -0,0 +1,221 @@
|
|||
# Generated by Django 3.2.8 on 2021-10-10 16:16
|
||||
|
||||
from os import environ
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.apps.registry import Apps
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
import authentik.core.models
|
||||
|
||||
|
||||
def create_default_user(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
# We have to use a direct import here, otherwise we get an object manager error
|
||||
from authentik.core.models import User
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
akadmin, _ = User.objects.using(db_alias).get_or_create(
|
||||
username="akadmin", email="root@localhost", name="authentik Default Admin"
|
||||
)
|
||||
if "TF_BUILD" in environ or "AK_ADMIN_PASS" in environ or settings.TEST:
|
||||
akadmin.set_password(environ.get("AK_ADMIN_PASS", "akadmin"), signal=False) # noqa # nosec
|
||||
else:
|
||||
akadmin.set_unusable_password()
|
||||
akadmin.save()
|
||||
|
||||
|
||||
def create_default_admin_group(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
Group = apps.get_model("authentik_core", "Group")
|
||||
User = apps.get_model("authentik_core", "User")
|
||||
|
||||
# Creates a default admin group
|
||||
group, _ = Group.objects.using(db_alias).get_or_create(
|
||||
is_superuser=True,
|
||||
defaults={
|
||||
"name": "authentik Admins",
|
||||
},
|
||||
)
|
||||
group.users.set(User.objects.filter(username="akadmin"))
|
||||
group.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [
|
||||
("authentik_core", "0002_auto_20200523_1133"),
|
||||
("authentik_core", "0003_default_user"),
|
||||
("authentik_core", "0004_auto_20200703_2213"),
|
||||
("authentik_core", "0005_token_intent"),
|
||||
("authentik_core", "0006_auto_20200709_1608"),
|
||||
("authentik_core", "0007_auto_20200815_1841"),
|
||||
("authentik_core", "0008_auto_20200824_1532"),
|
||||
("authentik_core", "0009_group_is_superuser"),
|
||||
("authentik_core", "0010_auto_20200917_1021"),
|
||||
("authentik_core", "0011_provider_name_temp"),
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0001_initial"),
|
||||
("authentik_flows", "0003_auto_20200523_1133"),
|
||||
("auth", "0012_alter_user_first_name_max_length"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="application",
|
||||
name="skip_authorization",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="source",
|
||||
name="authentication_flow",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="Flow to use when authenticating existing users.",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="source_authentication",
|
||||
to="authentik_flows.flow",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="source",
|
||||
name="enrollment_flow",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="Flow to use when enrolling new users.",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="source_enrollment",
|
||||
to="authentik_flows.flow",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="provider",
|
||||
name="authorization_flow",
|
||||
field=models.ForeignKey(
|
||||
help_text="Flow used when authorizing this provider.",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="provider_authorization",
|
||||
to="authentik_flows.flow",
|
||||
),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="user",
|
||||
name="is_superuser",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="user",
|
||||
name="is_staff",
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=create_default_user,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="is_superuser",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="is_staff",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="application",
|
||||
options={"verbose_name": "Application", "verbose_name_plural": "Applications"},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="user",
|
||||
options={
|
||||
"permissions": (("reset_user_password", "Reset Password"),),
|
||||
"verbose_name": "User",
|
||||
"verbose_name_plural": "Users",
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="token",
|
||||
name="intent",
|
||||
field=models.TextField(
|
||||
choices=[("verification", "Intent Verification"), ("api", "Intent Api")],
|
||||
default="verification",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="source",
|
||||
name="slug",
|
||||
field=models.SlugField(help_text="Internal source name, used in URLs.", unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="first_name",
|
||||
field=models.CharField(blank=True, max_length=150, verbose_name="first name"),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="user",
|
||||
name="groups",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="groups",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.Group",
|
||||
verbose_name="groups",
|
||||
),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="user",
|
||||
name="is_superuser",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="user",
|
||||
name="is_staff",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="pb_groups",
|
||||
field=models.ManyToManyField(related_name="users", to="authentik_core.Group"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="group",
|
||||
name="is_superuser",
|
||||
field=models.BooleanField(
|
||||
default=False, help_text="Users added to this group will be superusers."
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=create_default_admin_group,
|
||||
),
|
||||
migrations.AlterModelManagers(
|
||||
name="user",
|
||||
managers=[
|
||||
("objects", authentik.core.models.UserManager()),
|
||||
],
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="user",
|
||||
options={
|
||||
"permissions": (
|
||||
("reset_user_password", "Reset Password"),
|
||||
("impersonate", "Can impersonate other users"),
|
||||
),
|
||||
"verbose_name": "User",
|
||||
"verbose_name_plural": "Users",
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="provider",
|
||||
name="name_temp",
|
||||
field=models.TextField(default=""),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
|
@ -0,0 +1,331 @@
|
|||
# Generated by Django 3.2.8 on 2021-10-10 16:12
|
||||
|
||||
import uuid
|
||||
from os import environ
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.apps.registry import Apps
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
from django.db.models import Count
|
||||
|
||||
import authentik.core.models
|
||||
|
||||
|
||||
def set_default_token_key(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
Token = apps.get_model("authentik_core", "Token")
|
||||
|
||||
for token in Token.objects.using(db_alias).all():
|
||||
token.key = token.pk.hex
|
||||
token.save()
|
||||
|
||||
|
||||
def migrate_sessions(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
||||
from django.core.cache import cache
|
||||
|
||||
session_keys = cache.keys(KEY_PREFIX + "*")
|
||||
cache.delete_many(session_keys)
|
||||
|
||||
|
||||
def fix_duplicates(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
Token = apps.get_model("authentik_core", "token")
|
||||
identifiers = (
|
||||
Token.objects.using(db_alias)
|
||||
.values("identifier")
|
||||
.annotate(identifier_count=Count("identifier"))
|
||||
.filter(identifier_count__gt=1)
|
||||
)
|
||||
for ident in identifiers:
|
||||
Token.objects.using(db_alias).filter(identifier=ident["identifier"]).delete()
|
||||
|
||||
|
||||
def create_default_user_token(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
# We have to use a direct import here, otherwise we get an object manager error
|
||||
from authentik.core.models import Token, TokenIntents, User
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
akadmin = User.objects.using(db_alias).filter(username="akadmin")
|
||||
if not akadmin.exists():
|
||||
return
|
||||
if "AK_ADMIN_TOKEN" not in environ:
|
||||
return
|
||||
Token.objects.using(db_alias).create(
|
||||
identifier="authentik-boostrap-token",
|
||||
user=akadmin.first(),
|
||||
intent=TokenIntents.INTENT_API,
|
||||
expiring=False,
|
||||
key=environ["AK_ADMIN_TOKEN"],
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [
|
||||
("authentik_core", "0012_auto_20201003_1737"),
|
||||
("authentik_core", "0013_auto_20201003_2132"),
|
||||
("authentik_core", "0014_auto_20201018_1158"),
|
||||
("authentik_core", "0015_application_icon"),
|
||||
("authentik_core", "0016_auto_20201202_2234"),
|
||||
("authentik_core", "0017_managed"),
|
||||
("authentik_core", "0018_auto_20210330_1345"),
|
||||
("authentik_core", "0019_source_managed"),
|
||||
("authentik_core", "0020_source_user_matching_mode"),
|
||||
("authentik_core", "0021_alter_application_slug"),
|
||||
("authentik_core", "0022_authenticatedsession"),
|
||||
("authentik_core", "0023_alter_application_meta_launch_url"),
|
||||
("authentik_core", "0024_alter_token_identifier"),
|
||||
("authentik_core", "0025_alter_application_meta_icon"),
|
||||
("authentik_core", "0026_alter_application_meta_icon"),
|
||||
("authentik_core", "0027_bootstrap_token"),
|
||||
("authentik_core", "0028_alter_token_intent"),
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
("authentik_providers_saml", "0006_remove_samlprovider_name"),
|
||||
("authentik_core", "0011_provider_name_temp"),
|
||||
("authentik_providers_oauth2", "0006_remove_oauth2provider_name"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="provider",
|
||||
old_name="name_temp",
|
||||
new_name="name",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="token",
|
||||
name="identifier",
|
||||
field=models.TextField(default=""),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="token",
|
||||
name="intent",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("verification", "Intent Verification"),
|
||||
("api", "Intent Api"),
|
||||
("recovery", "Intent Recovery"),
|
||||
],
|
||||
default="verification",
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="token",
|
||||
unique_together={("identifier", "user")},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="token",
|
||||
name="key",
|
||||
field=models.TextField(default=authentik.core.models.default_token_key),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="token",
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="token",
|
||||
name="identifier",
|
||||
field=models.SlugField(max_length=255),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="token",
|
||||
index=models.Index(fields=["key"], name="authentik_co_key_e45007_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="token",
|
||||
index=models.Index(fields=["identifier"], name="authentik_co_identif_1a34a8_idx"),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=set_default_token_key,
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="application",
|
||||
name="meta_icon_url",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="application",
|
||||
name="meta_icon",
|
||||
field=models.FileField(blank=True, default="", upload_to="application-icons/"),
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name="token",
|
||||
name="authentik_co_key_e45007_idx",
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name="token",
|
||||
name="authentik_co_identif_1a34a8_idx",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="user",
|
||||
old_name="pb_groups",
|
||||
new_name="ak_groups",
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="token",
|
||||
index=models.Index(fields=["identifier"], name="authentik_c_identif_d9d032_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="token",
|
||||
index=models.Index(fields=["key"], name="authentik_c_key_f71355_idx"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="propertymapping",
|
||||
name="managed",
|
||||
field=models.TextField(
|
||||
default=None,
|
||||
help_text="Objects which are managed by authentik. These objects are created and updated automatically. This is flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.",
|
||||
null=True,
|
||||
unique=True,
|
||||
verbose_name="Managed by authentik",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="token",
|
||||
name="managed",
|
||||
field=models.TextField(
|
||||
default=None,
|
||||
help_text="Objects which are managed by authentik. These objects are created and updated automatically. This is flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.",
|
||||
null=True,
|
||||
unique=True,
|
||||
verbose_name="Managed by authentik",
|
||||
),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="token",
|
||||
options={
|
||||
"permissions": (("view_token_key", "View token's key"),),
|
||||
"verbose_name": "Token",
|
||||
"verbose_name_plural": "Tokens",
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="source",
|
||||
name="managed",
|
||||
field=models.TextField(
|
||||
default=None,
|
||||
help_text="Objects which are managed by authentik. These objects are created and updated automatically. This is flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.",
|
||||
null=True,
|
||||
unique=True,
|
||||
verbose_name="Managed by authentik",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="source",
|
||||
name="user_matching_mode",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("identifier", "Use the source-specific identifier"),
|
||||
(
|
||||
"email_link",
|
||||
"Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses.",
|
||||
),
|
||||
(
|
||||
"email_deny",
|
||||
"Use the user's email address, but deny enrollment when the email address already exists.",
|
||||
),
|
||||
(
|
||||
"username_link",
|
||||
"Link to a user with identical username. Can have security implications when a username is used with another source.",
|
||||
),
|
||||
(
|
||||
"username_deny",
|
||||
"Use the user's username, but deny enrollment when the username already exists.",
|
||||
),
|
||||
],
|
||||
default="identifier",
|
||||
help_text="How the source determines if an existing user should be authenticated or a new user enrolled.",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="application",
|
||||
name="slug",
|
||||
field=models.SlugField(
|
||||
help_text="Internal application name, used in URLs.", unique=True
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="AuthenticatedSession",
|
||||
fields=[
|
||||
(
|
||||
"expires",
|
||||
models.DateTimeField(default=authentik.core.models.default_token_duration),
|
||||
),
|
||||
("expiring", models.BooleanField(default=True)),
|
||||
("uuid", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
("session_key", models.CharField(max_length=40)),
|
||||
("last_ip", models.TextField()),
|
||||
("last_user_agent", models.TextField(blank=True)),
|
||||
("last_used", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=migrate_sessions,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="application",
|
||||
name="meta_launch_url",
|
||||
field=models.TextField(
|
||||
blank=True, default="", validators=[django.core.validators.URLValidator()]
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=fix_duplicates,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="token",
|
||||
name="identifier",
|
||||
field=models.SlugField(max_length=255, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="application",
|
||||
name="meta_icon",
|
||||
field=models.FileField(default=None, null=True, upload_to="application-icons/"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="application",
|
||||
name="meta_icon",
|
||||
field=models.FileField(
|
||||
default=None, max_length=500, null=True, upload_to="application-icons/"
|
||||
),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="authenticatedsession",
|
||||
options={
|
||||
"verbose_name": "Authenticated Session",
|
||||
"verbose_name_plural": "Authenticated Sessions",
|
||||
},
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=create_default_user_token,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="token",
|
||||
name="intent",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("verification", "Intent Verification"),
|
||||
("api", "Intent Api"),
|
||||
("recovery", "Intent Recovery"),
|
||||
("app_password", "Intent App Password"),
|
||||
],
|
||||
default="verification",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,831 @@
|
|||
# Generated by Django 3.2.8 on 2021-10-10 16:01
|
||||
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
from typing import Iterable
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.apps.registry import Apps
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
import authentik.events.models
|
||||
from authentik.events.models import EventAction, NotificationSeverity, TransportMode
|
||||
|
||||
|
||||
def convert_user_to_json(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
Event = apps.get_model("authentik_events", "Event")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
for event in Event.objects.all():
|
||||
event.delete()
|
||||
# Because event objects cannot be updated, we have to re-create them
|
||||
event.pk = None
|
||||
event.user_json = authentik.events.models.get_user(event.user) if event.user else {}
|
||||
event._state.adding = True
|
||||
event.save()
|
||||
|
||||
|
||||
def notify_configuration_error(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
Group = apps.get_model("authentik_core", "Group")
|
||||
PolicyBinding = apps.get_model("authentik_policies", "PolicyBinding")
|
||||
EventMatcherPolicy = apps.get_model("authentik_policies_event_matcher", "EventMatcherPolicy")
|
||||
NotificationRule = apps.get_model("authentik_events", "NotificationRule")
|
||||
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
|
||||
|
||||
admin_group = (
|
||||
Group.objects.using(db_alias).filter(name="authentik Admins", is_superuser=True).first()
|
||||
)
|
||||
|
||||
policy, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
|
||||
name="default-match-configuration-error",
|
||||
defaults={"action": EventAction.CONFIGURATION_ERROR},
|
||||
)
|
||||
trigger, _ = NotificationRule.objects.using(db_alias).update_or_create(
|
||||
name="default-notify-configuration-error",
|
||||
defaults={"group": admin_group, "severity": NotificationSeverity.ALERT},
|
||||
)
|
||||
trigger.transports.set(
|
||||
NotificationTransport.objects.using(db_alias).filter(name="default-email-transport")
|
||||
)
|
||||
trigger.save()
|
||||
PolicyBinding.objects.using(db_alias).update_or_create(
|
||||
target=trigger,
|
||||
policy=policy,
|
||||
defaults={
|
||||
"order": 0,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def notify_update(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
Group = apps.get_model("authentik_core", "Group")
|
||||
PolicyBinding = apps.get_model("authentik_policies", "PolicyBinding")
|
||||
EventMatcherPolicy = apps.get_model("authentik_policies_event_matcher", "EventMatcherPolicy")
|
||||
NotificationRule = apps.get_model("authentik_events", "NotificationRule")
|
||||
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
|
||||
|
||||
admin_group = (
|
||||
Group.objects.using(db_alias).filter(name="authentik Admins", is_superuser=True).first()
|
||||
)
|
||||
|
||||
policy, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
|
||||
name="default-match-update",
|
||||
defaults={"action": EventAction.UPDATE_AVAILABLE},
|
||||
)
|
||||
trigger, _ = NotificationRule.objects.using(db_alias).update_or_create(
|
||||
name="default-notify-update",
|
||||
defaults={"group": admin_group, "severity": NotificationSeverity.ALERT},
|
||||
)
|
||||
trigger.transports.set(
|
||||
NotificationTransport.objects.using(db_alias).filter(name="default-email-transport")
|
||||
)
|
||||
trigger.save()
|
||||
PolicyBinding.objects.using(db_alias).update_or_create(
|
||||
target=trigger,
|
||||
policy=policy,
|
||||
defaults={
|
||||
"order": 0,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def notify_exception(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
Group = apps.get_model("authentik_core", "Group")
|
||||
PolicyBinding = apps.get_model("authentik_policies", "PolicyBinding")
|
||||
EventMatcherPolicy = apps.get_model("authentik_policies_event_matcher", "EventMatcherPolicy")
|
||||
NotificationRule = apps.get_model("authentik_events", "NotificationRule")
|
||||
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
|
||||
|
||||
admin_group = (
|
||||
Group.objects.using(db_alias).filter(name="authentik Admins", is_superuser=True).first()
|
||||
)
|
||||
|
||||
policy_policy_exc, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
|
||||
name="default-match-policy-exception",
|
||||
defaults={"action": EventAction.POLICY_EXCEPTION},
|
||||
)
|
||||
policy_pm_exc, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
|
||||
name="default-match-property-mapping-exception",
|
||||
defaults={"action": EventAction.PROPERTY_MAPPING_EXCEPTION},
|
||||
)
|
||||
trigger, _ = NotificationRule.objects.using(db_alias).update_or_create(
|
||||
name="default-notify-exception",
|
||||
defaults={"group": admin_group, "severity": NotificationSeverity.ALERT},
|
||||
)
|
||||
trigger.transports.set(
|
||||
NotificationTransport.objects.using(db_alias).filter(name="default-email-transport")
|
||||
)
|
||||
trigger.save()
|
||||
PolicyBinding.objects.using(db_alias).update_or_create(
|
||||
target=trigger,
|
||||
policy=policy_policy_exc,
|
||||
defaults={
|
||||
"order": 0,
|
||||
},
|
||||
)
|
||||
PolicyBinding.objects.using(db_alias).update_or_create(
|
||||
target=trigger,
|
||||
policy=policy_pm_exc,
|
||||
defaults={
|
||||
"order": 1,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def transport_email_global(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
|
||||
|
||||
NotificationTransport.objects.using(db_alias).update_or_create(
|
||||
name="default-email-transport",
|
||||
defaults={"mode": TransportMode.EMAIL},
|
||||
)
|
||||
|
||||
|
||||
def token_view_to_secret_view(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
from authentik.events.models import EventAction
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
Event = apps.get_model("authentik_events", "Event")
|
||||
|
||||
events = Event.objects.using(db_alias).filter(action="token_view")
|
||||
|
||||
for event in events:
|
||||
event.context["secret"] = event.context.pop("token")
|
||||
event.action = EventAction.SECRET_VIEW
|
||||
|
||||
Event.objects.using(db_alias).bulk_update(events, ["context", "action"])
|
||||
|
||||
|
||||
# Taken from https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console
|
||||
def progress_bar(
|
||||
iterable: Iterable,
|
||||
prefix="Writing: ",
|
||||
suffix=" finished",
|
||||
decimals=1,
|
||||
length=100,
|
||||
fill="█",
|
||||
print_end="\r",
|
||||
):
|
||||
"""
|
||||
Call in a loop to create terminal progress bar
|
||||
@params:
|
||||
iteration - Required : current iteration (Int)
|
||||
total - Required : total iterations (Int)
|
||||
prefix - Optional : prefix string (Str)
|
||||
suffix - Optional : suffix string (Str)
|
||||
decimals - Optional : positive number of decimals in percent complete (Int)
|
||||
length - Optional : character length of bar (Int)
|
||||
fill - Optional : bar fill character (Str)
|
||||
print_end - Optional : end character (e.g. "\r", "\r\n") (Str)
|
||||
"""
|
||||
total = len(iterable)
|
||||
if total < 1:
|
||||
return
|
||||
|
||||
def print_progress_bar(iteration):
|
||||
"""Progress Bar Printing Function"""
|
||||
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
|
||||
filledLength = int(length * iteration // total)
|
||||
bar = fill * filledLength + "-" * (length - filledLength)
|
||||
print(f"\r{prefix} |{bar}| {percent}% {suffix}", end=print_end)
|
||||
|
||||
# Initial Call
|
||||
print_progress_bar(0)
|
||||
# Update Progress Bar
|
||||
for i, item in enumerate(iterable):
|
||||
yield item
|
||||
print_progress_bar(i + 1)
|
||||
# Print New Line on Complete
|
||||
print()
|
||||
|
||||
|
||||
def update_expires(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
Event = apps.get_model("authentik_events", "event")
|
||||
all_events = Event.objects.using(db_alias).all()
|
||||
if all_events.count() < 1:
|
||||
return
|
||||
|
||||
print("\nAdding expiry to events, this might take a couple of minutes...")
|
||||
for event in progress_bar(all_events):
|
||||
event.expires = event.created + timedelta(days=365)
|
||||
event.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [
|
||||
("authentik_events", "0001_initial"),
|
||||
("authentik_events", "0002_auto_20200918_2116"),
|
||||
("authentik_events", "0003_auto_20200917_1155"),
|
||||
("authentik_events", "0004_auto_20200921_1829"),
|
||||
("authentik_events", "0005_auto_20201005_2139"),
|
||||
("authentik_events", "0006_auto_20201017_2024"),
|
||||
("authentik_events", "0007_auto_20201215_0939"),
|
||||
("authentik_events", "0008_auto_20201220_1651"),
|
||||
("authentik_events", "0009_auto_20201227_1210"),
|
||||
("authentik_events", "0010_notification_notificationtransport_notificationrule"),
|
||||
("authentik_events", "0011_notification_rules_default_v1"),
|
||||
("authentik_events", "0012_auto_20210202_1821"),
|
||||
("authentik_events", "0013_auto_20210209_1657"),
|
||||
("authentik_events", "0014_expiry"),
|
||||
("authentik_events", "0015_alter_event_action"),
|
||||
("authentik_events", "0016_add_tenant"),
|
||||
("authentik_events", "0017_alter_event_action"),
|
||||
("authentik_events", "0018_auto_20210911_2217"),
|
||||
("authentik_events", "0019_alter_notificationtransport_webhook_url"),
|
||||
]
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("authentik_policies", "0004_policy_execution_logging"),
|
||||
("authentik_core", "0016_auto_20201202_2234"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("authentik_policies_event_matcher", "0003_auto_20210110_1907"),
|
||||
("authentik_core", "0028_alter_token_intent"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Event",
|
||||
fields=[
|
||||
(
|
||||
"event_uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
(
|
||||
"action",
|
||||
models.TextField(
|
||||
choices=[
|
||||
("LOGIN", "login"),
|
||||
("LOGIN_FAILED", "login_failed"),
|
||||
("LOGOUT", "logout"),
|
||||
("AUTHORIZE_APPLICATION", "authorize_application"),
|
||||
("SUSPICIOUS_REQUEST", "suspicious_request"),
|
||||
("SIGN_UP", "sign_up"),
|
||||
("PASSWORD_RESET", "password_reset"),
|
||||
("INVITE_CREATED", "invitation_created"),
|
||||
("INVITE_USED", "invitation_used"),
|
||||
("IMPERSONATION_STARTED", "impersonation_started"),
|
||||
("IMPERSONATION_ENDED", "impersonation_ended"),
|
||||
("CUSTOM", "custom"),
|
||||
]
|
||||
),
|
||||
),
|
||||
("date", models.DateTimeField(auto_now_add=True)),
|
||||
("app", models.TextField()),
|
||||
("context", models.JSONField(blank=True, default=dict)),
|
||||
("client_ip", models.GenericIPAddressField(null=True)),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
("user_json", models.JSONField(default=dict)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Event",
|
||||
"verbose_name_plural": "Events",
|
||||
},
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=convert_user_to_json,
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="event",
|
||||
name="user",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="event",
|
||||
old_name="user_json",
|
||||
new_name="user",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("sign_up", "Sign Up"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("invitation_created", "Invite Created"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("invitation_created", "Invite Created"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="event",
|
||||
name="date",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("token_view", "Token View"),
|
||||
("invitation_created", "Invite Created"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("token_view", "Token View"),
|
||||
("invitation_created", "Invite Created"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("token_view", "Token View"),
|
||||
("invitation_created", "Invite Created"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("update_available", "Update Available"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("token_view", "Token View"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("configuration_error", "Configuration Error"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("update_available", "Update Available"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="NotificationTransport",
|
||||
fields=[
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
("name", models.TextField(unique=True)),
|
||||
(
|
||||
"mode",
|
||||
models.TextField(
|
||||
choices=[
|
||||
("webhook", "Generic Webhook"),
|
||||
("webhook_slack", "Slack Webhook (Slack/Discord)"),
|
||||
("email", "Email"),
|
||||
]
|
||||
),
|
||||
),
|
||||
("webhook_url", models.TextField(blank=True)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Notification Transport",
|
||||
"verbose_name_plural": "Notification Transports",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="NotificationRule",
|
||||
fields=[
|
||||
(
|
||||
"policybindingmodel_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="authentik_policies.policybindingmodel",
|
||||
),
|
||||
),
|
||||
("name", models.TextField(unique=True)),
|
||||
(
|
||||
"severity",
|
||||
models.TextField(
|
||||
choices=[("notice", "Notice"), ("warning", "Warning"), ("alert", "Alert")],
|
||||
default="notice",
|
||||
help_text="Controls which severity level the created notifications will have.",
|
||||
),
|
||||
),
|
||||
(
|
||||
"group",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Define which group of users this notification should be sent and shown to. If left empty, Notification won't ben sent.",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="authentik_core.group",
|
||||
),
|
||||
),
|
||||
(
|
||||
"transports",
|
||||
models.ManyToManyField(
|
||||
help_text="Select which transports should be used to notify the user. If none are selected, the notification will only be shown in the authentik UI.",
|
||||
to="authentik_events.NotificationTransport",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Notification Rule",
|
||||
"verbose_name_plural": "Notification Rules",
|
||||
},
|
||||
bases=("authentik_policies.policybindingmodel",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Notification",
|
||||
fields=[
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
(
|
||||
"severity",
|
||||
models.TextField(
|
||||
choices=[("notice", "Notice"), ("warning", "Warning"), ("alert", "Alert")]
|
||||
),
|
||||
),
|
||||
("body", models.TextField()),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("seen", models.BooleanField(default=False)),
|
||||
(
|
||||
"event",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="authentik_events.event",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Notification",
|
||||
"verbose_name_plural": "Notifications",
|
||||
},
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=transport_email_global,
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=notify_configuration_error,
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=notify_update,
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=notify_exception,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="notificationtransport",
|
||||
name="send_once",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="Only send notification once, for example when sending a webhook into a chat channel.",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("token_view", "Token View"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("system_task_execution", "System Task Execution"),
|
||||
("system_task_exception", "System Task Exception"),
|
||||
("configuration_error", "Configuration Error"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("update_available", "Update Available"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("secret_view", "Secret View"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("system_task_execution", "System Task Execution"),
|
||||
("system_task_exception", "System Task Exception"),
|
||||
("configuration_error", "Configuration Error"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("update_available", "Update Available"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=token_view_to_secret_view,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="event",
|
||||
name="expires",
|
||||
field=models.DateTimeField(default=authentik.events.models.default_event_duration),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="event",
|
||||
name="expiring",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=update_expires,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("secret_view", "Secret View"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("system_task_execution", "System Task Execution"),
|
||||
("system_task_exception", "System Task Exception"),
|
||||
("configuration_error", "Configuration Error"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("email_sent", "Email Sent"),
|
||||
("update_available", "Update Available"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="event",
|
||||
name="tenant",
|
||||
field=models.JSONField(blank=True, default=authentik.events.models.default_tenant),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("secret_view", "Secret View"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("system_task_execution", "System Task Execution"),
|
||||
("system_task_exception", "System Task Exception"),
|
||||
("system_exception", "System Exception"),
|
||||
("configuration_error", "Configuration Error"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("email_sent", "Email Sent"),
|
||||
("update_available", "Update Available"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("secret_view", "Secret View"),
|
||||
("secret_rotate", "Secret Rotate"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("system_task_execution", "System Task Execution"),
|
||||
("system_task_exception", "System Task Exception"),
|
||||
("system_exception", "System Exception"),
|
||||
("configuration_error", "Configuration Error"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("email_sent", "Email Sent"),
|
||||
("update_available", "Update Available"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="NotificationWebhookMapping",
|
||||
fields=[
|
||||
(
|
||||
"propertymapping_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="authentik_core.propertymapping",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Notification Webhook Mapping",
|
||||
"verbose_name_plural": "Notification Webhook Mappings",
|
||||
},
|
||||
bases=("authentik_core.propertymapping",),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="notificationtransport",
|
||||
name="webhook_mapping",
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||
to="authentik_events.notificationwebhookmapping",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notificationtransport",
|
||||
name="webhook_url",
|
||||
field=models.TextField(blank=True, validators=[django.core.validators.URLValidator()]),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,180 @@
|
|||
# Generated by Django 3.2.8 on 2021-10-10 16:08
|
||||
|
||||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [
|
||||
("authentik_flows", "0001_initial"),
|
||||
("authentik_flows", "0003_auto_20200523_1133"),
|
||||
("authentik_flows", "0006_auto_20200629_0857"),
|
||||
("authentik_flows", "0007_auto_20200703_2059"),
|
||||
]
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("authentik_policies", "0001_initial"),
|
||||
("authentik_policies", "0002_auto_20200528_1647"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Flow",
|
||||
fields=[
|
||||
(
|
||||
"flow_uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
("name", models.TextField()),
|
||||
("slug", models.SlugField(unique=True)),
|
||||
(
|
||||
"designation",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("authentication", "Authentication"),
|
||||
("invalidation", "Invalidation"),
|
||||
("enrollment", "Enrollment"),
|
||||
("unenrollment", "Unrenollment"),
|
||||
("recovery", "Recovery"),
|
||||
("password_change", "Password Change"),
|
||||
],
|
||||
max_length=100,
|
||||
),
|
||||
),
|
||||
(
|
||||
"pbm",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
related_name="+",
|
||||
to="authentik_policies.policybindingmodel",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Flow",
|
||||
"verbose_name_plural": "Flows",
|
||||
},
|
||||
bases=("authentik_policies.policybindingmodel",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Stage",
|
||||
fields=[
|
||||
(
|
||||
"stage_uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
("name", models.TextField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="FlowStageBinding",
|
||||
fields=[
|
||||
(
|
||||
"policybindingmodel_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
to="authentik_policies.policybindingmodel",
|
||||
),
|
||||
),
|
||||
(
|
||||
"fsb_uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
(
|
||||
"re_evaluate_policies",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="When this option is enabled, the planner will re-evaluate policies bound to this.",
|
||||
),
|
||||
),
|
||||
("order", models.IntegerField()),
|
||||
(
|
||||
"target",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="authentik_flows.flow"
|
||||
),
|
||||
),
|
||||
(
|
||||
"stage",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="authentik_flows.stage"
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Flow Stage Binding",
|
||||
"verbose_name_plural": "Flow Stage Bindings",
|
||||
"ordering": ["order", "target"],
|
||||
"unique_together": {("target", "stage", "order")},
|
||||
},
|
||||
bases=("authentik_policies.policybindingmodel",),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="flow",
|
||||
name="stages",
|
||||
field=models.ManyToManyField(
|
||||
blank=True, through="authentik_flows.FlowStageBinding", to="authentik_flows.Stage"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="flow",
|
||||
name="designation",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("authentication", "Authentication"),
|
||||
("authorization", "Authorization"),
|
||||
("invalidation", "Invalidation"),
|
||||
("enrollment", "Enrollment"),
|
||||
("unenrollment", "Unrenollment"),
|
||||
("recovery", "Recovery"),
|
||||
("password_change", "Password Change"),
|
||||
],
|
||||
max_length=100,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="flow",
|
||||
name="designation",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("authentication", "Authentication"),
|
||||
("authorization", "Authorization"),
|
||||
("invalidation", "Invalidation"),
|
||||
("enrollment", "Enrollment"),
|
||||
("unenrollment", "Unrenollment"),
|
||||
("recovery", "Recovery"),
|
||||
("stage_setup", "Stage Setup"),
|
||||
],
|
||||
max_length=100,
|
||||
),
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="flow",
|
||||
old_name="pbm",
|
||||
new_name="policybindingmodel_ptr",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="flow",
|
||||
name="policybindingmodel_ptr",
|
||||
field=models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
to="authentik_policies.policybindingmodel",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,171 @@
|
|||
# Generated by Django 3.2.8 on 2021-10-10 16:08
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
import authentik.lib.models
|
||||
from authentik.flows.models import FlowDesignation
|
||||
|
||||
|
||||
def update_flow_designation(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
for flow in Flow.objects.using(db_alias).all():
|
||||
if flow.designation == "stage_setup":
|
||||
flow.designation = FlowDesignation.STAGE_CONFIGURATION
|
||||
flow.save()
|
||||
|
||||
|
||||
# First stage for default-source-enrollment flow (prompt stage)
|
||||
# needs to have its policy re-evaluated
|
||||
def update_default_source_enrollment_flow_binding(
|
||||
apps: Apps, schema_editor: BaseDatabaseSchemaEditor
|
||||
):
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding")
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
flows = Flow.objects.using(db_alias).filter(slug="default-source-enrollment")
|
||||
if not flows.exists():
|
||||
return
|
||||
flow = flows.first()
|
||||
|
||||
binding = FlowStageBinding.objects.get(target=flow, order=0)
|
||||
binding.re_evaluate_policies = True
|
||||
binding.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [
|
||||
("authentik_flows", "0012_auto_20200908_1542"),
|
||||
("authentik_flows", "0013_auto_20200924_1605"),
|
||||
("authentik_flows", "0014_auto_20200925_2332"),
|
||||
("authentik_flows", "0015_flowstagebinding_evaluate_on_plan"),
|
||||
("authentik_flows", "0016_auto_20201202_1307"),
|
||||
("authentik_flows", "0017_auto_20210329_1334"),
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
("authentik_flows", "0011_flow_title"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="flowstagebinding",
|
||||
name="stage",
|
||||
field=authentik.lib.models.InheritanceForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="authentik_flows.stage"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="stage",
|
||||
name="name",
|
||||
field=models.TextField(unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="flow",
|
||||
name="designation",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("authentication", "Authentication"),
|
||||
("authorization", "Authorization"),
|
||||
("invalidation", "Invalidation"),
|
||||
("enrollment", "Enrollment"),
|
||||
("unenrollment", "Unrenollment"),
|
||||
("recovery", "Recovery"),
|
||||
("stage_configuration", "Stage Configuration"),
|
||||
],
|
||||
max_length=100,
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=update_flow_designation,
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="flowstagebinding",
|
||||
options={
|
||||
"ordering": ["target", "order"],
|
||||
"verbose_name": "Flow Stage Binding",
|
||||
"verbose_name_plural": "Flow Stage Bindings",
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="flowstagebinding",
|
||||
name="re_evaluate_policies",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="When this option is enabled, the planner will re-evaluate policies bound to this binding.",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=update_default_source_enrollment_flow_binding,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="flowstagebinding",
|
||||
name="re_evaluate_policies",
|
||||
field=models.BooleanField(
|
||||
default=False, help_text="Evaluate policies when the Stage is present to the user."
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="flowstagebinding",
|
||||
name="evaluate_on_plan",
|
||||
field=models.BooleanField(
|
||||
default=True,
|
||||
help_text="Evaluate policies during the Flow planning process. Disable this for input-based policies.",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="flow",
|
||||
name="background",
|
||||
field=models.FileField(
|
||||
blank=True,
|
||||
default="../static/dist/assets/images/flow_background.jpg",
|
||||
help_text="Background shown during execution",
|
||||
upload_to="flow-backgrounds/",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="flow",
|
||||
name="designation",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("authentication", "Authentication"),
|
||||
("authorization", "Authorization"),
|
||||
("invalidation", "Invalidation"),
|
||||
("enrollment", "Enrollment"),
|
||||
("unenrollment", "Unrenollment"),
|
||||
("recovery", "Recovery"),
|
||||
("stage_configuration", "Stage Configuration"),
|
||||
],
|
||||
help_text="Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik.",
|
||||
max_length=100,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="flow",
|
||||
name="slug",
|
||||
field=models.SlugField(help_text="Visible in the URL.", unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="flow",
|
||||
name="title",
|
||||
field=models.TextField(help_text="Shown as the Title in Flow pages."),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="flow",
|
||||
options={
|
||||
"permissions": [
|
||||
("export_flow", "Can export a Flow"),
|
||||
("view_flow_cache", "View Flow's cache metrics"),
|
||||
("clear_flow_cache", "Clear Flow's cache metrics"),
|
||||
],
|
||||
"verbose_name": "Flow",
|
||||
"verbose_name_plural": "Flows",
|
||||
},
|
||||
),
|
||||
]
|
|
@ -0,0 +1,64 @@
|
|||
# Generated by Django 3.2.8 on 2021-10-10 16:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [
|
||||
("authentik_flows", "0019_alter_flow_background"),
|
||||
("authentik_flows", "0020_flow_compatibility_mode"),
|
||||
("authentik_flows", "0021_flowstagebinding_invalid_response_action"),
|
||||
("authentik_flows", "0022_alter_flowstagebinding_invalid_response_action"),
|
||||
("authentik_flows", "0023_alter_flow_background"),
|
||||
("authentik_flows", "0024_alter_flow_compatibility_mode"),
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
("authentik_flows", "0018_oob_flows"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="flow",
|
||||
name="background",
|
||||
field=models.FileField(
|
||||
default=None,
|
||||
help_text="Background shown during execution",
|
||||
null=True,
|
||||
upload_to="flow-backgrounds/",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="flowstagebinding",
|
||||
name="invalid_response_action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("retry", "Retry"),
|
||||
("restart", "Restart"),
|
||||
("restart_with_context", "Restart With Context"),
|
||||
],
|
||||
default="retry",
|
||||
help_text="Configure how the flow executor should handle an invalid response to a challenge. RETRY returns the error message and a similar challenge to the executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT restarts the flow while keeping the current context.",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="flow",
|
||||
name="background",
|
||||
field=models.FileField(
|
||||
default=None,
|
||||
help_text="Background shown during execution",
|
||||
max_length=500,
|
||||
null=True,
|
||||
upload_to="flow-backgrounds/",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="flow",
|
||||
name="compatibility_mode",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="Enable compatibility mode, increases compatibility with password managers on mobile devices.",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -144,12 +144,11 @@ class DockerController(BaseController):
|
|||
return None
|
||||
# Check if the container is out of date, delete it and retry
|
||||
if len(container.image.tags) > 0:
|
||||
tag: str = container.image.tags[0]
|
||||
should_image = self.try_pull_image()
|
||||
if tag != should_image:
|
||||
if should_image not in container.image.tags:
|
||||
self.logger.info(
|
||||
"Container has mismatched image, re-creating...",
|
||||
has=tag,
|
||||
has=container.tags,
|
||||
should=should_image,
|
||||
)
|
||||
self.down()
|
||||
|
|
|
@ -0,0 +1,340 @@
|
|||
# Generated by Django 3.2.8 on 2021-10-10 16:18
|
||||
|
||||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.apps.registry import Apps
|
||||
from django.core.exceptions import FieldError
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
import authentik.lib.models
|
||||
import authentik.outposts.models
|
||||
|
||||
|
||||
def fix_missing_token_identifier(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
User = apps.get_model("authentik_core", "User")
|
||||
Token = apps.get_model("authentik_core", "Token")
|
||||
from authentik.outposts.models import Outpost
|
||||
|
||||
for outpost in Outpost.objects.using(schema_editor.connection.alias).all().only("pk"):
|
||||
user_identifier = outpost.user_identifier
|
||||
users = User.objects.filter(username=user_identifier)
|
||||
if not users.exists():
|
||||
continue
|
||||
tokens = Token.objects.filter(user=users.first())
|
||||
for token in tokens:
|
||||
if token.identifier != outpost.token_identifier:
|
||||
token.identifier = outpost.token_identifier
|
||||
token.save()
|
||||
|
||||
|
||||
def migrate_to_service_connection(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
Outpost = apps.get_model("authentik_outposts", "Outpost")
|
||||
DockerServiceConnection = apps.get_model("authentik_outposts", "DockerServiceConnection")
|
||||
KubernetesServiceConnection = apps.get_model(
|
||||
"authentik_outposts", "KubernetesServiceConnection"
|
||||
)
|
||||
|
||||
docker = DockerServiceConnection.objects.filter(local=True).first()
|
||||
k8s = KubernetesServiceConnection.objects.filter(local=True).first()
|
||||
|
||||
try:
|
||||
for outpost in Outpost.objects.using(db_alias).all().exclude(deployment_type="custom"):
|
||||
if outpost.deployment_type == "kubernetes":
|
||||
outpost.service_connection = k8s
|
||||
elif outpost.deployment_type == "docker":
|
||||
outpost.service_connection = docker
|
||||
outpost.save()
|
||||
except FieldError:
|
||||
# This is triggered during e2e tests when this function is called on an already-upgraded
|
||||
# schema
|
||||
pass
|
||||
|
||||
|
||||
def remove_pb_prefix_users(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
alias = schema_editor.connection.alias
|
||||
User = apps.get_model("authentik_core", "User")
|
||||
Outpost = apps.get_model("authentik_outposts", "Outpost")
|
||||
|
||||
for outpost in Outpost.objects.using(alias).all():
|
||||
matching = User.objects.using(alias).filter(username=f"pb-outpost-{outpost.uuid.hex}")
|
||||
if matching.exists():
|
||||
matching.delete()
|
||||
|
||||
|
||||
def update_config_prefix(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
alias = schema_editor.connection.alias
|
||||
Outpost = apps.get_model("authentik_outposts", "Outpost")
|
||||
|
||||
for outpost in Outpost.objects.using(alias).all():
|
||||
config = outpost._config
|
||||
for key in list(config):
|
||||
if "passbook" in key:
|
||||
new_key = key.replace("passbook", "authentik")
|
||||
config[new_key] = config[key]
|
||||
del config[key]
|
||||
outpost._config = config
|
||||
outpost.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [
|
||||
("authentik_outposts", "0001_initial"),
|
||||
("authentik_outposts", "0002_auto_20200826_1306"),
|
||||
("authentik_outposts", "0003_auto_20200827_2108"),
|
||||
("authentik_outposts", "0004_auto_20200830_1056"),
|
||||
("authentik_outposts", "0005_auto_20200909_1733"),
|
||||
("authentik_outposts", "0006_auto_20201003_2239"),
|
||||
("authentik_outposts", "0007_remove_outpost_channels"),
|
||||
("authentik_outposts", "0008_auto_20201014_1547"),
|
||||
("authentik_outposts", "0009_fix_missing_token_identifier"),
|
||||
("authentik_outposts", "0010_service_connection"),
|
||||
("authentik_outposts", "0011_docker_tls_auth"),
|
||||
("authentik_outposts", "0012_service_connection_non_unique"),
|
||||
("authentik_outposts", "0013_auto_20201203_2009"),
|
||||
("authentik_outposts", "0014_auto_20201213_1407"),
|
||||
("authentik_outposts", "0015_auto_20201224_1206"),
|
||||
("authentik_outposts", "0016_alter_outpost_type"),
|
||||
("authentik_outposts", "0017_outpost_managed"),
|
||||
]
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0014_auto_20201018_1158"),
|
||||
("authentik_core", "0016_auto_20201202_2234"),
|
||||
("authentik_crypto", "0002_create_self_signed_kp"),
|
||||
("authentik_core", "0008_auto_20200824_1532"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Outpost",
|
||||
fields=[
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
("name", models.TextField()),
|
||||
("providers", models.ManyToManyField(to="authentik_core.Provider")),
|
||||
(
|
||||
"_config",
|
||||
models.JSONField(default=authentik.outposts.models.default_outpost_config),
|
||||
),
|
||||
("type", models.TextField(choices=[("proxy", "Proxy")], default="proxy")),
|
||||
(
|
||||
"deployment_type",
|
||||
models.TextField(
|
||||
choices=[
|
||||
("kubernetes", "Kubernetes"),
|
||||
("docker", "Docker"),
|
||||
("custom", "Custom"),
|
||||
],
|
||||
default="custom",
|
||||
help_text="Select between authentik-managed deployment types or a custom deployment.",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=fix_missing_token_identifier,
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="OutpostServiceConnection",
|
||||
fields=[
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
("name", models.TextField()),
|
||||
(
|
||||
"local",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="If enabled, use the local connection. Required Docker socket/Kubernetes Integration",
|
||||
unique=True,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="DockerServiceConnection",
|
||||
fields=[
|
||||
(
|
||||
"outpostserviceconnection_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="authentik_outposts.outpostserviceconnection",
|
||||
),
|
||||
),
|
||||
("url", models.TextField()),
|
||||
("tls", models.BooleanField()),
|
||||
],
|
||||
bases=("authentik_outposts.outpostserviceconnection",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="KubernetesServiceConnection",
|
||||
fields=[
|
||||
(
|
||||
"outpostserviceconnection_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="authentik_outposts.outpostserviceconnection",
|
||||
),
|
||||
),
|
||||
("kubeconfig", models.JSONField()),
|
||||
],
|
||||
bases=("authentik_outposts.outpostserviceconnection",),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="outpost",
|
||||
name="service_connection",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="Select Service-Connection authentik should use to manage this outpost. Leave empty if authentik should not handle the deployment.",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||
to="authentik_outposts.outpostserviceconnection",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=migrate_to_service_connection,
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="outpost",
|
||||
name="deployment_type",
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="dockerserviceconnection",
|
||||
options={
|
||||
"verbose_name": "Docker Service-Connection",
|
||||
"verbose_name_plural": "Docker Service-Connections",
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="kubernetesserviceconnection",
|
||||
options={
|
||||
"verbose_name": "Kubernetes Service-Connection",
|
||||
"verbose_name_plural": "Kubernetes Service-Connections",
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="outpost",
|
||||
name="service_connection",
|
||||
field=authentik.lib.models.InheritanceForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="Select Service-Connection authentik should use to manage this outpost. Leave empty if authentik should not handle the deployment.",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||
to="authentik_outposts.outpostserviceconnection",
|
||||
),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="outpostserviceconnection",
|
||||
options={
|
||||
"verbose_name": "Outpost Service-Connection",
|
||||
"verbose_name_plural": "Outpost Service-Connections",
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="kubernetesserviceconnection",
|
||||
name="kubeconfig",
|
||||
field=models.JSONField(
|
||||
default=None,
|
||||
help_text="Paste your kubeconfig here. authentik will automatically use the currently selected context.",
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="dockerserviceconnection",
|
||||
name="tls",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="dockerserviceconnection",
|
||||
name="tls_authentication",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="Certificate/Key used for authentication. Can be left empty for no authentication.",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||
related_name="+",
|
||||
to="authentik_crypto.certificatekeypair",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="dockerserviceconnection",
|
||||
name="tls_verification",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="CA which the endpoint's Certificate is verified against. Can be left empty for no validation.",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||
related_name="+",
|
||||
to="authentik_crypto.certificatekeypair",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="outpostserviceconnection",
|
||||
name="local",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="If enabled, use the local connection. Required Docker socket/Kubernetes Integration",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=remove_pb_prefix_users,
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=update_config_prefix,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="dockerserviceconnection",
|
||||
name="url",
|
||||
field=models.TextField(
|
||||
help_text="Can be in the format of 'unix://<path>' when connecting to a local docker daemon, or 'https://<hostname>:2376' when connecting to a remote system."
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="kubernetesserviceconnection",
|
||||
name="kubeconfig",
|
||||
field=models.JSONField(
|
||||
blank=True,
|
||||
help_text="Paste your kubeconfig here. authentik will automatically use the currently selected context.",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="outpost",
|
||||
name="type",
|
||||
field=models.TextField(choices=[("proxy", "Proxy"), ("ldap", "Ldap")], default="proxy"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="outpost",
|
||||
name="managed",
|
||||
field=models.TextField(
|
||||
default=None,
|
||||
help_text="Objects which are managed by authentik. These objects are created and updated automatically. This is flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.",
|
||||
null=True,
|
||||
unique=True,
|
||||
verbose_name="Managed by authentik",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,173 @@
|
|||
# Generated by Django 3.2.8 on 2021-10-10 16:11
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [
|
||||
("authentik_policies_event_matcher", "0001_initial"),
|
||||
("authentik_policies_event_matcher", "0002_auto_20201230_2046"),
|
||||
("authentik_policies_event_matcher", "0003_auto_20210110_1907"),
|
||||
("authentik_policies_event_matcher", "0004_auto_20210112_2158"),
|
||||
("authentik_policies_event_matcher", "0005_auto_20210202_1821"),
|
||||
("authentik_policies_event_matcher", "0006_auto_20210203_1134"),
|
||||
("authentik_policies_event_matcher", "0007_auto_20210209_1657"),
|
||||
("authentik_policies_event_matcher", "0008_auto_20210213_1640"),
|
||||
("authentik_policies_event_matcher", "0009_auto_20210215_2159"),
|
||||
("authentik_policies_event_matcher", "0010_auto_20210222_1821"),
|
||||
("authentik_policies_event_matcher", "0011_auto_20210302_0856"),
|
||||
("authentik_policies_event_matcher", "0012_auto_20210323_1339"),
|
||||
("authentik_policies_event_matcher", "0013_alter_eventmatcherpolicy_app"),
|
||||
("authentik_policies_event_matcher", "0014_alter_eventmatcherpolicy_app"),
|
||||
("authentik_policies_event_matcher", "0015_alter_eventmatcherpolicy_app"),
|
||||
("authentik_policies_event_matcher", "0016_alter_eventmatcherpolicy_action"),
|
||||
("authentik_policies_event_matcher", "0017_alter_eventmatcherpolicy_action"),
|
||||
("authentik_policies_event_matcher", "0018_alter_eventmatcherpolicy_action"),
|
||||
]
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("authentik_policies", "0004_policy_execution_logging"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="EventMatcherPolicy",
|
||||
fields=[
|
||||
(
|
||||
"policy_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="authentik_policies.policy",
|
||||
),
|
||||
),
|
||||
(
|
||||
"action",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("secret_view", "Secret View"),
|
||||
("secret_rotate", "Secret Rotate"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("system_task_execution", "System Task Execution"),
|
||||
("system_task_exception", "System Task Exception"),
|
||||
("system_exception", "System Exception"),
|
||||
("configuration_error", "Configuration Error"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("email_sent", "Email Sent"),
|
||||
("update_available", "Update Available"),
|
||||
("custom_", "Custom Prefix"),
|
||||
],
|
||||
help_text="Match created events with this action type. When left empty, all action types will be matched.",
|
||||
),
|
||||
),
|
||||
(
|
||||
"client_ip",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
help_text="Matches Event's Client IP (strict matching, for network matching use an Expression Policy)",
|
||||
),
|
||||
),
|
||||
(
|
||||
"app",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("authentik.admin", "authentik Admin"),
|
||||
("authentik.api", "authentik API"),
|
||||
("authentik.events", "authentik Events"),
|
||||
("authentik.crypto", "authentik Crypto"),
|
||||
("authentik.flows", "authentik Flows"),
|
||||
("authentik.outposts", "authentik Outpost"),
|
||||
("authentik.lib", "authentik lib"),
|
||||
("authentik.policies", "authentik Policies"),
|
||||
("authentik.policies.dummy", "authentik Policies.Dummy"),
|
||||
(
|
||||
"authentik.policies.event_matcher",
|
||||
"authentik Policies.Event Matcher",
|
||||
),
|
||||
("authentik.policies.expiry", "authentik Policies.Expiry"),
|
||||
("authentik.policies.expression", "authentik Policies.Expression"),
|
||||
("authentik.policies.hibp", "authentik Policies.HaveIBeenPwned"),
|
||||
("authentik.policies.password", "authentik Policies.Password"),
|
||||
("authentik.policies.reputation", "authentik Policies.Reputation"),
|
||||
("authentik.providers.proxy", "authentik Providers.Proxy"),
|
||||
("authentik.providers.ldap", "authentik Providers.LDAP"),
|
||||
("authentik.providers.oauth2", "authentik Providers.OAuth2"),
|
||||
("authentik.providers.saml", "authentik Providers.SAML"),
|
||||
("authentik.recovery", "authentik Recovery"),
|
||||
("authentik.sources.ldap", "authentik Sources.LDAP"),
|
||||
("authentik.sources.oauth", "authentik Sources.OAuth"),
|
||||
("authentik.sources.plex", "authentik Sources.Plex"),
|
||||
("authentik.sources.saml", "authentik Sources.SAML"),
|
||||
(
|
||||
"authentik.stages.authenticator_duo",
|
||||
"authentik Stages.Authenticator.Duo",
|
||||
),
|
||||
(
|
||||
"authentik.stages.authenticator_static",
|
||||
"authentik Stages.Authenticator.Static",
|
||||
),
|
||||
(
|
||||
"authentik.stages.authenticator_totp",
|
||||
"authentik Stages.Authenticator.TOTP",
|
||||
),
|
||||
(
|
||||
"authentik.stages.authenticator_validate",
|
||||
"authentik Stages.Authenticator.Validate",
|
||||
),
|
||||
(
|
||||
"authentik.stages.authenticator_webauthn",
|
||||
"authentik Stages.Authenticator.WebAuthn",
|
||||
),
|
||||
("authentik.stages.captcha", "authentik Stages.Captcha"),
|
||||
("authentik.stages.consent", "authentik Stages.Consent"),
|
||||
("authentik.stages.deny", "authentik Stages.Deny"),
|
||||
("authentik.stages.dummy", "authentik Stages.Dummy"),
|
||||
("authentik.stages.email", "authentik Stages.Email"),
|
||||
("authentik.stages.identification", "authentik Stages.Identification"),
|
||||
("authentik.stages.invitation", "authentik Stages.User Invitation"),
|
||||
("authentik.stages.password", "authentik Stages.Password"),
|
||||
("authentik.stages.prompt", "authentik Stages.Prompt"),
|
||||
("authentik.stages.user_delete", "authentik Stages.User Delete"),
|
||||
("authentik.stages.user_login", "authentik Stages.User Login"),
|
||||
("authentik.stages.user_logout", "authentik Stages.User Logout"),
|
||||
("authentik.stages.user_write", "authentik Stages.User Write"),
|
||||
("authentik.tenants", "authentik Tenants"),
|
||||
("authentik.core", "authentik Core"),
|
||||
("authentik.managed", "authentik Managed"),
|
||||
],
|
||||
default="",
|
||||
help_text="Match events created by selected application. When left empty, all applications are matched.",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Event Matcher Policy",
|
||||
"verbose_name_plural": "Event Matcher Policies",
|
||||
},
|
||||
bases=("authentik_policies.policy",),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,128 @@
|
|||
# Generated by Django 3.2.8 on 2021-10-10 16:24
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
|
||||
import authentik.lib.utils.time
|
||||
|
||||
scope_uid_map = {
|
||||
"openid": "goauthentik.io/providers/oauth2/scope-openid",
|
||||
"email": "goauthentik.io/providers/oauth2/scope-email",
|
||||
"profile": "goauthentik.io/providers/oauth2/scope-profile",
|
||||
"ak_proxy": "goauthentik.io/providers/proxy/scope-proxy",
|
||||
}
|
||||
|
||||
|
||||
def set_managed_flag(apps: Apps, schema_editor):
|
||||
ScopeMapping = apps.get_model("authentik_providers_oauth2", "ScopeMapping")
|
||||
db_alias = schema_editor.connection.alias
|
||||
for mapping in ScopeMapping.objects.using(db_alias).filter(name__startswith="Autogenerated "):
|
||||
mapping.managed = scope_uid_map[mapping.scope_name]
|
||||
mapping.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [
|
||||
("authentik_providers_oauth2", "0007_auto_20201016_1107"),
|
||||
("authentik_providers_oauth2", "0008_oauth2provider_issuer_mode"),
|
||||
("authentik_providers_oauth2", "0009_remove_oauth2provider_response_type"),
|
||||
("authentik_providers_oauth2", "0010_auto_20201227_1804"),
|
||||
("authentik_providers_oauth2", "0011_managed"),
|
||||
("authentik_providers_oauth2", "0012_oauth2provider_access_code_validity"),
|
||||
("authentik_providers_oauth2", "0013_alter_authorizationcode_nonce"),
|
||||
("authentik_providers_oauth2", "0014_alter_oauth2provider_rsa_key"),
|
||||
("authentik_providers_oauth2", "0015_auto_20210703_1313"),
|
||||
("authentik_providers_oauth2", "0016_alter_authorizationcode_nonce"),
|
||||
("authentik_providers_oauth2", "0017_alter_oauth2provider_token_validity"),
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0017_managed"),
|
||||
("authentik_crypto", "0002_create_self_signed_kp"),
|
||||
("authentik_providers_oauth2", "0006_remove_oauth2provider_name"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="refreshtoken",
|
||||
options={"verbose_name": "OAuth2 Token", "verbose_name_plural": "OAuth2 Tokens"},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="oauth2provider",
|
||||
name="issuer_mode",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("global", "Same identifier is used for all providers"),
|
||||
(
|
||||
"per_provider",
|
||||
"Each provider has a different issuer, based on the application slug.",
|
||||
),
|
||||
],
|
||||
default="per_provider",
|
||||
help_text="Configure how the issuer field of the ID Token should be filled.",
|
||||
),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="oauth2provider",
|
||||
name="response_type",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="refreshtoken",
|
||||
name="access_token",
|
||||
field=models.TextField(verbose_name="Access Token"),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=set_managed_flag,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="oauth2provider",
|
||||
name="access_code_validity",
|
||||
field=models.TextField(
|
||||
default="minutes=1",
|
||||
help_text="Access codes not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).",
|
||||
validators=[authentik.lib.utils.time.timedelta_string_validator],
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="authorizationcode",
|
||||
name="nonce",
|
||||
field=models.TextField(blank=True, default="", verbose_name="Nonce"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="oauth2provider",
|
||||
name="rsa_key",
|
||||
field=models.ForeignKey(
|
||||
help_text="Key used to sign the tokens. Only required when JWT Algorithm is set to RS256.",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="authentik_crypto.certificatekeypair",
|
||||
verbose_name="RSA Key",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="authorizationcode",
|
||||
name="revoked",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="refreshtoken",
|
||||
name="revoked",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="authorizationcode",
|
||||
name="nonce",
|
||||
field=models.TextField(default=None, null=True, verbose_name="Nonce"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="oauth2provider",
|
||||
name="token_validity",
|
||||
field=models.TextField(
|
||||
default="days=30",
|
||||
help_text="Tokens not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).",
|
||||
validators=[authentik.lib.utils.time.timedelta_string_validator],
|
||||
),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,146 @@
|
|||
# Generated by Django 3.2.8 on 2021-10-10 16:16
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import authentik.lib.utils.time
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [
|
||||
("authentik_providers_saml", "0001_initial"),
|
||||
("authentik_providers_saml", "0002_default_saml_property_mappings"),
|
||||
("authentik_providers_saml", "0003_samlprovider_sp_binding"),
|
||||
("authentik_providers_saml", "0004_auto_20200620_1950"),
|
||||
("authentik_providers_saml", "0005_remove_samlprovider_processor_path"),
|
||||
]
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("authentik_crypto", "0001_initial"),
|
||||
("authentik_core", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="SAMLPropertyMapping",
|
||||
fields=[
|
||||
(
|
||||
"propertymapping_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="authentik_core.propertymapping",
|
||||
),
|
||||
),
|
||||
("saml_name", models.TextField(verbose_name="SAML Name")),
|
||||
("friendly_name", models.TextField(blank=True, default=None, null=True)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "SAML Property Mapping",
|
||||
"verbose_name_plural": "SAML Property Mappings",
|
||||
},
|
||||
bases=("authentik_core.propertymapping",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="SAMLProvider",
|
||||
fields=[
|
||||
(
|
||||
"provider_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="authentik_core.provider",
|
||||
),
|
||||
),
|
||||
("name", models.TextField()),
|
||||
("acs_url", models.URLField(verbose_name="ACS URL")),
|
||||
("audience", models.TextField(default="")),
|
||||
("issuer", models.TextField(help_text="Also known as EntityID")),
|
||||
(
|
||||
"assertion_valid_not_before",
|
||||
models.TextField(
|
||||
default="minutes=-5",
|
||||
help_text="Assertion valid not before current time + this value (Format: hours=-1;minutes=-2;seconds=-3).",
|
||||
validators=[authentik.lib.utils.time.timedelta_string_validator],
|
||||
),
|
||||
),
|
||||
(
|
||||
"assertion_valid_not_on_or_after",
|
||||
models.TextField(
|
||||
default="minutes=5",
|
||||
help_text="Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).",
|
||||
validators=[authentik.lib.utils.time.timedelta_string_validator],
|
||||
),
|
||||
),
|
||||
(
|
||||
"session_valid_not_on_or_after",
|
||||
models.TextField(
|
||||
default="minutes=86400",
|
||||
help_text="Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).",
|
||||
validators=[authentik.lib.utils.time.timedelta_string_validator],
|
||||
),
|
||||
),
|
||||
(
|
||||
"digest_algorithm",
|
||||
models.CharField(
|
||||
choices=[("sha1", "SHA1"), ("sha256", "SHA256")],
|
||||
default="sha256",
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
(
|
||||
"signature_algorithm",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("rsa-sha1", "RSA-SHA1"),
|
||||
("rsa-sha256", "RSA-SHA256"),
|
||||
("ecdsa-sha256", "ECDSA-SHA256"),
|
||||
("dsa-sha1", "DSA-SHA1"),
|
||||
],
|
||||
default="rsa-sha256",
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
(
|
||||
"require_signing",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Require Requests to be signed by an X509 Certificate. Must match the Certificate selected in `Singing Keypair`.",
|
||||
),
|
||||
),
|
||||
(
|
||||
"signing_kp",
|
||||
models.ForeignKey(
|
||||
default=None,
|
||||
help_text="Singing is enabled upon selection of a Key Pair.",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="authentik_crypto.certificatekeypair",
|
||||
verbose_name="Signing Keypair",
|
||||
),
|
||||
),
|
||||
(
|
||||
"sp_binding",
|
||||
models.TextField(
|
||||
choices=[("redirect", "Redirect"), ("post", "Post")],
|
||||
default="redirect",
|
||||
verbose_name="Service Prodier Binding",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "SAML Provider",
|
||||
"verbose_name_plural": "SAML Providers",
|
||||
},
|
||||
bases=("authentik_core.provider",),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,248 @@
|
|||
# Generated by Django 3.2.8 on 2021-10-10 16:26
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
|
||||
import authentik.lib.models
|
||||
|
||||
|
||||
def set_managed_flag(apps: Apps, schema_editor):
|
||||
LDAPPropertyMapping = apps.get_model("authentik_sources_ldap", "LDAPPropertyMapping")
|
||||
db_alias = schema_editor.connection.alias
|
||||
field_to_uid = {
|
||||
"name": "goauthentik.io/sources/ldap/default-name",
|
||||
"email": "goauthentik.io/sources/ldap/default-mail",
|
||||
"username": "goauthentik.io/sources/ldap/ms-samaccountname",
|
||||
"attributes.upn": "goauthentik.io/sources/ldap/ms-userprincipalname",
|
||||
"first_name": "goauthentik.io/sources/ldap/ms-givenName",
|
||||
"last_name": "goauthentik.io/sources/ldap/ms-sn",
|
||||
}
|
||||
for mapping in LDAPPropertyMapping.objects.using(db_alias).filter(
|
||||
name__startswith="Autogenerated "
|
||||
):
|
||||
mapping.managed = field_to_uid.get(mapping.object_field)
|
||||
mapping.save()
|
||||
|
||||
|
||||
def set_default_group_mappings(apps: Apps, schema_editor):
|
||||
LDAPPropertyMapping = apps.get_model("authentik_sources_ldap", "LDAPPropertyMapping")
|
||||
LDAPSource = apps.get_model("authentik_sources_ldap", "LDAPSource")
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
for source in LDAPSource.objects.using(db_alias).all():
|
||||
if source.property_mappings_group.exists():
|
||||
continue
|
||||
source.property_mappings_group.set(
|
||||
LDAPPropertyMapping.objects.using(db_alias).filter(
|
||||
managed="goauthentik.io/sources/ldap/default-name"
|
||||
)
|
||||
)
|
||||
source.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [
|
||||
("authentik_sources_ldap", "0001_initial"),
|
||||
("authentik_sources_ldap", "0002_ldapsource_sync_users"),
|
||||
("authentik_sources_ldap", "0003_default_ldap_property_mappings"),
|
||||
("authentik_sources_ldap", "0004_auto_20200524_1146"),
|
||||
("authentik_sources_ldap", "0005_auto_20200913_1947"),
|
||||
("authentik_sources_ldap", "0006_auto_20200915_1919"),
|
||||
("authentik_sources_ldap", "0007_ldapsource_sync_users_password"),
|
||||
("authentik_sources_ldap", "0008_managed"),
|
||||
("authentik_sources_ldap", "0009_auto_20210204_1834"),
|
||||
("authentik_sources_ldap", "0010_auto_20210205_1027"),
|
||||
("authentik_sources_ldap", "0011_ldapsource_property_mappings_group"),
|
||||
("authentik_sources_ldap", "0012_auto_20210812_1703"),
|
||||
]
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0001_initial"),
|
||||
("authentik_core", "0017_managed"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="LDAPPropertyMapping",
|
||||
fields=[
|
||||
(
|
||||
"propertymapping_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="authentik_core.propertymapping",
|
||||
),
|
||||
),
|
||||
("object_field", models.TextField()),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "LDAP Property Mapping",
|
||||
"verbose_name_plural": "LDAP Property Mappings",
|
||||
},
|
||||
bases=("authentik_core.propertymapping",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="LDAPSource",
|
||||
fields=[
|
||||
(
|
||||
"source_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="authentik_core.source",
|
||||
),
|
||||
),
|
||||
(
|
||||
"server_uri",
|
||||
models.TextField(
|
||||
validators=[
|
||||
authentik.lib.models.DomainlessURLValidator(schemes=["ldap", "ldaps"])
|
||||
],
|
||||
verbose_name="Server URI",
|
||||
),
|
||||
),
|
||||
("bind_cn", models.TextField(verbose_name="Bind CN")),
|
||||
("bind_password", models.TextField()),
|
||||
("start_tls", models.BooleanField(default=False, verbose_name="Enable Start TLS")),
|
||||
("base_dn", models.TextField(verbose_name="Base DN")),
|
||||
(
|
||||
"additional_user_dn",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
help_text="Prepended to Base DN for User-queries.",
|
||||
verbose_name="Addition User DN",
|
||||
),
|
||||
),
|
||||
(
|
||||
"additional_group_dn",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
help_text="Prepended to Base DN for Group-queries.",
|
||||
verbose_name="Addition Group DN",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_object_filter",
|
||||
models.TextField(
|
||||
default="(objectCategory=Person)",
|
||||
help_text="Consider Objects matching this filter to be Users.",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_group_membership_field",
|
||||
models.TextField(
|
||||
default="memberOf", help_text="Field which contains Groups of user."
|
||||
),
|
||||
),
|
||||
(
|
||||
"group_object_filter",
|
||||
models.TextField(
|
||||
default="(objectCategory=Group)",
|
||||
help_text="Consider Objects matching this filter to be Groups.",
|
||||
),
|
||||
),
|
||||
(
|
||||
"object_uniqueness_field",
|
||||
models.TextField(
|
||||
default="objectSid", help_text="Field which contains a unique Identifier."
|
||||
),
|
||||
),
|
||||
("sync_groups", models.BooleanField(default=True)),
|
||||
(
|
||||
"sync_parent_group",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||
to="authentik_core.group",
|
||||
),
|
||||
),
|
||||
("sync_users", models.BooleanField(default=True)),
|
||||
(
|
||||
"sync_users_password",
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text="When a user changes their password, sync it back to LDAP. This can only be enabled on a single LDAP source.",
|
||||
unique=True,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "LDAP Source",
|
||||
"verbose_name_plural": "LDAP Sources",
|
||||
},
|
||||
bases=("authentik_core.source",),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=set_managed_flag,
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="ldapsource",
|
||||
name="user_group_membership_field",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ldapsource",
|
||||
name="group_membership_field",
|
||||
field=models.TextField(
|
||||
default="member", help_text="Field which contains members of a group."
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="ldapsource",
|
||||
name="group_object_filter",
|
||||
field=models.TextField(
|
||||
default="(objectClass=group)",
|
||||
help_text="Consider Objects matching this filter to be Groups.",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="ldapsource",
|
||||
name="user_object_filter",
|
||||
field=models.TextField(
|
||||
default="(objectClass=person)",
|
||||
help_text="Consider Objects matching this filter to be Users.",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="ldapsource",
|
||||
name="property_mappings_group",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="Property mappings used for group creation/updating.",
|
||||
to="authentik_core.PropertyMapping",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=set_default_group_mappings,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="ldapsource",
|
||||
name="bind_cn",
|
||||
field=models.TextField(blank=True, verbose_name="Bind CN"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="ldapsource",
|
||||
name="bind_password",
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="ldapsource",
|
||||
name="sync_users_password",
|
||||
field=models.BooleanField(
|
||||
default=True,
|
||||
help_text="When a user changes their password, sync it back to LDAP. This can only be enabled on a single LDAP source.",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,210 @@
|
|||
# Generated by Django 3.2.8 on 2021-10-11 15:55
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
import authentik.lib.utils.time
|
||||
from authentik.sources.saml.processors import constants
|
||||
|
||||
|
||||
def update_algorithms(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
SAMLSource = apps.get_model("authentik_sources_saml", "SAMLSource")
|
||||
signature_translation_map = {
|
||||
"rsa-sha1": constants.RSA_SHA1,
|
||||
"rsa-sha256": constants.RSA_SHA256,
|
||||
"ecdsa-sha256": constants.RSA_SHA256,
|
||||
"dsa-sha1": constants.DSA_SHA1,
|
||||
}
|
||||
digest_translation_map = {
|
||||
"sha1": constants.SHA1,
|
||||
"sha256": constants.SHA256,
|
||||
}
|
||||
|
||||
for source in SAMLSource.objects.all():
|
||||
source.signature_algorithm = signature_translation_map.get(
|
||||
source.signature_algorithm, constants.RSA_SHA256
|
||||
)
|
||||
source.digest_algorithm = digest_translation_map.get(
|
||||
source.digest_algorithm, constants.SHA256
|
||||
)
|
||||
source.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [
|
||||
("authentik_sources_saml", "0001_initial"),
|
||||
("authentik_sources_saml", "0002_auto_20200523_2329"),
|
||||
("authentik_sources_saml", "0003_auto_20200624_1957"),
|
||||
("authentik_sources_saml", "0004_auto_20200708_1207"),
|
||||
("authentik_sources_saml", "0005_samlsource_name_id_policy"),
|
||||
("authentik_sources_saml", "0006_samlsource_allow_idp_initiated"),
|
||||
("authentik_sources_saml", "0007_auto_20201112_1055"),
|
||||
("authentik_sources_saml", "0008_auto_20201112_2016"),
|
||||
("authentik_sources_saml", "0009_auto_20210301_0949"),
|
||||
]
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0001_initial"),
|
||||
("authentik_crypto", "0002_create_self_signed_kp"),
|
||||
("authentik_crypto", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="SAMLSource",
|
||||
fields=[
|
||||
(
|
||||
"source_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="authentik_core.source",
|
||||
),
|
||||
),
|
||||
(
|
||||
"issuer",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="Also known as Entity ID. Defaults the Metadata URL.",
|
||||
verbose_name="Issuer",
|
||||
),
|
||||
),
|
||||
(
|
||||
"sso_url",
|
||||
models.URLField(
|
||||
help_text="URL that the initial Login request is sent to.",
|
||||
verbose_name="SSO URL",
|
||||
),
|
||||
),
|
||||
(
|
||||
"slo_url",
|
||||
models.URLField(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="Optional URL if your IDP supports Single-Logout.",
|
||||
null=True,
|
||||
verbose_name="SLO URL",
|
||||
),
|
||||
),
|
||||
(
|
||||
"signing_kp",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="Keypair which is used to sign outgoing requests. Leave empty to disable signing.",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||
to="authentik_crypto.certificatekeypair",
|
||||
verbose_name="Singing Keypair",
|
||||
),
|
||||
),
|
||||
(
|
||||
"binding_type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("REDIRECT", "Redirect Binding"),
|
||||
("POST", "POST Binding"),
|
||||
("POST_AUTO", "POST Binding with auto-confirmation"),
|
||||
],
|
||||
default="REDIRECT",
|
||||
max_length=100,
|
||||
),
|
||||
),
|
||||
(
|
||||
"temporary_user_delete_after",
|
||||
models.TextField(
|
||||
default="days=1",
|
||||
help_text="Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3).",
|
||||
validators=[authentik.lib.utils.time.timedelta_string_validator],
|
||||
verbose_name="Delete temporary users after",
|
||||
),
|
||||
),
|
||||
(
|
||||
"name_id_policy",
|
||||
models.TextField(
|
||||
choices=[
|
||||
("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "Email"),
|
||||
("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", "Persistent"),
|
||||
("urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName", "X509"),
|
||||
(
|
||||
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
|
||||
"Windows",
|
||||
),
|
||||
("urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "Transient"),
|
||||
],
|
||||
default="urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
|
||||
help_text="NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent.",
|
||||
),
|
||||
),
|
||||
(
|
||||
"allow_idp_initiated",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Allows authentication flows initiated by the IdP. This can be a security risk, as no validation of the request ID is done.",
|
||||
),
|
||||
),
|
||||
(
|
||||
"digest_algorithm",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("http://www.w3.org/2000/09/xmldsig#sha1", "SHA1"),
|
||||
("http://www.w3.org/2001/04/xmlenc#sha256", "SHA256"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#sha384", "SHA384"),
|
||||
("http://www.w3.org/2001/04/xmlenc#sha512", "SHA512"),
|
||||
],
|
||||
default="http://www.w3.org/2001/04/xmlenc#sha256",
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
(
|
||||
"signature_algorithm",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("http://www.w3.org/2000/09/xmldsig#rsa-sha1", "RSA-SHA1"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "RSA-SHA256"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha384", "RSA-SHA384"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512", "RSA-SHA512"),
|
||||
("http://www.w3.org/2000/09/xmldsig#dsa-sha1", "DSA-SHA1"),
|
||||
],
|
||||
default="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "SAML Source",
|
||||
"verbose_name_plural": "SAML Sources",
|
||||
},
|
||||
bases=("authentik_core.source",),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=update_algorithms,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="samlsource",
|
||||
name="name_id_policy",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "Email"),
|
||||
("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", "Persistent"),
|
||||
("urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName", "X509"),
|
||||
(
|
||||
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
|
||||
"Windows",
|
||||
),
|
||||
("urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "Transient"),
|
||||
],
|
||||
default="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
|
||||
help_text="NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent.",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -25,7 +25,7 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
|||
"""test flow with otp stages"""
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
def test_totp_validate(self):
|
||||
|
@ -56,7 +56,7 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
|||
self.assert_user(USER())
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_stages_authenticator_totp", "0006_default_setup_flow")
|
||||
|
@ -101,7 +101,7 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
|||
self.assertTrue(TOTPDevice.objects.filter(user=USER(), confirmed=True).exists())
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_stages_authenticator_static", "0005_default_setup_flow")
|
||||
|
|
|
@ -38,7 +38,7 @@ class TestFlowsEnroll(SeleniumTestCase):
|
|||
}
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
def test_enroll_2_step(self):
|
||||
|
@ -108,7 +108,7 @@ class TestFlowsEnroll(SeleniumTestCase):
|
|||
self.assertEqual(user.email, "foo@bar.baz")
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@override_settings(EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend")
|
||||
|
|
|
@ -10,7 +10,7 @@ class TestFlowsLogin(SeleniumTestCase):
|
|||
"""test default login flow"""
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
def test_login(self):
|
||||
|
|
|
@ -17,7 +17,7 @@ class TestFlowsStageSetup(SeleniumTestCase):
|
|||
"""test stage setup flows"""
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_stages_password", "0002_passwordstage_change_flow")
|
||||
|
|
|
@ -80,7 +80,7 @@ class TestProviderLDAP(SeleniumTestCase):
|
|||
return user
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@object_manager
|
||||
def test_ldap_bind_success(self):
|
||||
|
@ -106,7 +106,7 @@ class TestProviderLDAP(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@object_manager
|
||||
def test_ldap_bind_success_ssl(self):
|
||||
|
@ -132,7 +132,7 @@ class TestProviderLDAP(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@object_manager
|
||||
def test_ldap_bind_fail(self):
|
||||
|
@ -156,8 +156,7 @@ class TestProviderLDAP(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0009_group_is_superuser")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@object_manager
|
||||
def test_ldap_bind_search(self):
|
||||
|
|
|
@ -56,7 +56,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
|||
}
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -105,7 +105,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -173,7 +173,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
|
|
@ -68,7 +68,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
}
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -110,7 +110,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -167,7 +167,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -232,7 +232,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
self.driver.find_element(By.ID, "logout").click()
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -306,7 +306,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
|
|
@ -67,7 +67,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
sleep(1)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -109,7 +109,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -159,7 +159,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
self.assertEqual(body["UserInfo"]["email"], USER().email)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -224,7 +224,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
self.assertEqual(body["UserInfo"]["email"], USER().email)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
|
|
@ -67,7 +67,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
sleep(1)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -109,7 +109,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -156,7 +156,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
self.assertEqual(body["profile"]["email"], USER().email)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -218,7 +218,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
self.assertEqual(body["profile"]["email"], USER().email)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
|
|
@ -54,7 +54,7 @@ class TestProviderProxy(SeleniumTestCase):
|
|||
return container
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -119,7 +119,7 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
|
|||
"""Test Proxy connectivity over websockets"""
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
|
|
@ -62,7 +62,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
sleep(1)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -125,7 +125,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -203,7 +203,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
@ -272,7 +272,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
|
|
|
@ -130,7 +130,7 @@ class TestSourceOAuth2(SeleniumTestCase):
|
|||
ident_stage.save()
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0009_source_flows")
|
||||
|
@ -180,7 +180,7 @@ class TestSourceOAuth2(SeleniumTestCase):
|
|||
self.assert_user(User(username="foo", name="admin", email="admin@example.com"))
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0009_source_flows")
|
||||
|
@ -217,7 +217,7 @@ class TestSourceOAuth2(SeleniumTestCase):
|
|||
self.driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0009_source_flows")
|
||||
|
@ -307,7 +307,7 @@ class TestSourceOAuth1(SeleniumTestCase):
|
|||
ident_stage.save()
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0009_source_flows")
|
||||
|
|
|
@ -97,7 +97,7 @@ class TestSourceSAML(SeleniumTestCase):
|
|||
}
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0009_source_flows")
|
||||
|
@ -164,7 +164,7 @@ class TestSourceSAML(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0009_source_flows")
|
||||
|
@ -244,7 +244,7 @@ class TestSourceSAML(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_core", "0002_auto_20200523_1133_squashed_0011_provider_name_temp")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0009_source_flows")
|
||||
|
|
Reference in a new issue