diff --git a/Makefile b/Makefile index 15049c040..b9628f180 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ gen-outpost: --additional-properties=packageName=api,enumClassPrefix=true,useOneOfDiscriminatorLookup=true,disallowAdditionalPropertiesIfNotPresent=false rm -f api/go.mod api/go.sum -gen: gen-build gen-clean gen-web gen-outpost +gen: gen-build gen-clean gen-web migrate: python -m lifecycle.migrate diff --git a/authentik/stages/user_write/api.py b/authentik/stages/user_write/api.py index bda945b0f..fd7b7dee5 100644 --- a/authentik/stages/user_write/api.py +++ b/authentik/stages/user_write/api.py @@ -12,7 +12,7 @@ class UserWriteStageSerializer(StageSerializer): class Meta: model = UserWriteStage - fields = StageSerializer.Meta.fields + ["create_users_as_inactive"] + fields = StageSerializer.Meta.fields + ["create_users_as_inactive", "create_users_group"] class UserWriteStageViewSet(UsedByMixin, ModelViewSet): diff --git a/authentik/stages/user_write/migrations/0004_userwritestage_create_users_group.py b/authentik/stages/user_write/migrations/0004_userwritestage_create_users_group.py new file mode 100644 index 000000000..3ce5ba63b --- /dev/null +++ b/authentik/stages/user_write/migrations/0004_userwritestage_create_users_group.py @@ -0,0 +1,26 @@ +# Generated by Django 3.2.7 on 2021-09-14 19:27 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_core", "0028_alter_token_intent"), + ("authentik_stages_user_write", "0003_userwritestage_create_users_as_inactive"), + ] + + operations = [ + migrations.AddField( + model_name="userwritestage", + name="create_users_group", + field=models.ForeignKey( + default=None, + help_text="Optionally add newly created users to this group.", + null=True, + on_delete=django.db.models.deletion.SET_DEFAULT, + to="authentik_core.group", + ), + ), + ] diff --git a/authentik/stages/user_write/models.py b/authentik/stages/user_write/models.py index be5b8d1d0..428c2f505 100644 --- a/authentik/stages/user_write/models.py +++ b/authentik/stages/user_write/models.py @@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _ from django.views import View from rest_framework.serializers import BaseSerializer +from authentik.core.models import Group from authentik.flows.models import Stage @@ -18,6 +19,14 @@ class UserWriteStage(Stage): help_text=_("When set, newly created users are inactive and cannot login."), ) + create_users_group = models.ForeignKey( + Group, + null=True, + default=None, + on_delete=models.SET_DEFAULT, + help_text=_("Optionally add newly created users to this group."), + ) + @property def serializer(self) -> BaseSerializer: from authentik.stages.user_write.api import UserWriteStageSerializer diff --git a/authentik/stages/user_write/stage.py b/authentik/stages/user_write/stage.py index 39945ba95..30abfe25a 100644 --- a/authentik/stages/user_write/stage.py +++ b/authentik/stages/user_write/stage.py @@ -92,6 +92,8 @@ class UserWriteStageView(StageView): try: with transaction.atomic(): user.save() + if self.executor.current_stage.create_users_group: + user.ak_groups.add(self.executor.current_stage.create_users_group) except IntegrityError as exc: LOGGER.warning("Failed to save user", exc=exc) return self.executor.stage_invalid() diff --git a/authentik/stages/user_write/tests.py b/authentik/stages/user_write/tests.py index 75ba7243f..0f078f73a 100644 --- a/authentik/stages/user_write/tests.py +++ b/authentik/stages/user_write/tests.py @@ -7,7 +7,7 @@ from django.urls import reverse from django.utils.encoding import force_str from rest_framework.test import APITestCase -from authentik.core.models import USER_ATTRIBUTE_SOURCES, Source, User, UserSourceConnection +from authentik.core.models import USER_ATTRIBUTE_SOURCES, Group, Source, User, UserSourceConnection from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION from authentik.flows.challenge import ChallengeTypes from authentik.flows.markers import StageMarker @@ -29,7 +29,10 @@ class TestUserWriteStage(APITestCase): slug="test-write", designation=FlowDesignation.AUTHENTICATION, ) - self.stage = UserWriteStage.objects.create(name="write") + self.group = Group.objects.create(name="test-group") + self.stage = UserWriteStage.objects.create( + name="write", create_users_as_inactive=True, create_users_group=self.group + ) self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) self.source = Source.objects.create(name="fake_source") @@ -67,6 +70,7 @@ class TestUserWriteStage(APITestCase): user_qs = User.objects.filter(username=plan.context[PLAN_CONTEXT_PROMPT]["username"]) self.assertTrue(user_qs.exists()) self.assertTrue(user_qs.first().check_password(password)) + self.assertEqual(list(user_qs.first().ak_groups.all()), [self.group]) self.assertEqual(user_qs.first().attributes, {USER_ATTRIBUTE_SOURCES: [self.source.name]}) def test_user_update(self): diff --git a/schema.yml b/schema.yml index 553743e34..6dd0a2efa 100644 --- a/schema.yml +++ b/schema.yml @@ -17949,6 +17949,11 @@ paths: name: create_users_as_inactive schema: type: boolean + - in: query + name: create_users_group + schema: + type: string + format: uuid - in: query name: name schema: @@ -26628,6 +26633,11 @@ components: create_users_as_inactive: type: boolean description: When set, newly created users are inactive and cannot login. + create_users_group: + type: string + format: uuid + nullable: true + description: Optionally add newly created users to this group. PatchedWebAuthnDeviceRequest: type: object description: Serializer for WebAuthn authenticator devices @@ -29283,6 +29293,11 @@ components: create_users_as_inactive: type: boolean description: When set, newly created users are inactive and cannot login. + create_users_group: + type: string + format: uuid + nullable: true + description: Optionally add newly created users to this group. required: - component - name @@ -29302,6 +29317,11 @@ components: create_users_as_inactive: type: boolean description: When set, newly created users are inactive and cannot login. + create_users_group: + type: string + format: uuid + nullable: true + description: Optionally add newly created users to this group. required: - name ValidationError: diff --git a/web/src/locales/en.po b/web/src/locales/en.po index 62eea79bc..f4f59d101 100644 --- a/web/src/locales/en.po +++ b/web/src/locales/en.po @@ -1888,6 +1888,7 @@ msgstr "Go to previous page" #: src/pages/policies/PolicyBindingForm.ts #: src/pages/policies/PolicyBindingForm.ts #: src/pages/providers/ldap/LDAPProviderForm.ts +#: src/pages/stages/user_write/UserWriteStageForm.ts msgid "Group" msgstr "Group" @@ -2371,6 +2372,7 @@ msgstr "Loading" #: src/pages/stages/password/PasswordStageForm.ts #: src/pages/stages/prompt/PromptStageForm.ts #: src/pages/stages/prompt/PromptStageForm.ts +#: src/pages/stages/user_write/UserWriteStageForm.ts #: src/pages/tenants/TenantForm.ts #: src/pages/tenants/TenantForm.ts #: src/pages/tenants/TenantForm.ts @@ -2644,6 +2646,10 @@ msgstr "Negates the outcome of the binding. Messages are unaffected." msgid "New version available!" msgstr "New version available!" +#: src/pages/stages/user_write/UserWriteStageForm.ts +msgid "Newly created users are added to this group, if a group is selected." +msgstr "Newly created users are added to this group, if a group is selected." + #: src/elements/oauth/UserRefreshList.ts #: src/pages/applications/ApplicationCheckAccessForm.ts #: src/pages/crypto/CertificateKeyPairListPage.ts diff --git a/web/src/locales/pseudo-LOCALE.po b/web/src/locales/pseudo-LOCALE.po index 8de31ae49..05dd00a6b 100644 --- a/web/src/locales/pseudo-LOCALE.po +++ b/web/src/locales/pseudo-LOCALE.po @@ -1880,6 +1880,7 @@ msgstr "" #: src/pages/policies/PolicyBindingForm.ts #: src/pages/policies/PolicyBindingForm.ts #: src/pages/providers/ldap/LDAPProviderForm.ts +#: src/pages/stages/user_write/UserWriteStageForm.ts msgid "Group" msgstr "" @@ -2363,6 +2364,7 @@ msgstr "" #: src/pages/stages/password/PasswordStageForm.ts #: src/pages/stages/prompt/PromptStageForm.ts #: src/pages/stages/prompt/PromptStageForm.ts +#: src/pages/stages/user_write/UserWriteStageForm.ts #: src/pages/tenants/TenantForm.ts #: src/pages/tenants/TenantForm.ts #: src/pages/tenants/TenantForm.ts @@ -2636,6 +2638,10 @@ msgstr "" msgid "New version available!" msgstr "" +#: src/pages/stages/user_write/UserWriteStageForm.ts +msgid "Newly created users are added to this group, if a group is selected." +msgstr "" + #: src/elements/oauth/UserRefreshList.ts #: src/pages/applications/ApplicationCheckAccessForm.ts #: src/pages/crypto/CertificateKeyPairListPage.ts diff --git a/web/src/pages/stages/user_write/UserWriteStageForm.ts b/web/src/pages/stages/user_write/UserWriteStageForm.ts index 402b05bb5..55191a852 100644 --- a/web/src/pages/stages/user_write/UserWriteStageForm.ts +++ b/web/src/pages/stages/user_write/UserWriteStageForm.ts @@ -1,4 +1,4 @@ -import { UserWriteStage, StagesApi } from "@goauthentik/api"; +import { UserWriteStage, StagesApi, CoreApi } from "@goauthentik/api"; import { t } from "@lingui/macro"; import { customElement } from "lit-element"; import { html, TemplateResult } from "lit-html"; @@ -8,6 +8,7 @@ import "../../../elements/forms/HorizontalFormElement"; import "../../../elements/forms/FormGroup"; import { ModelForm } from "../../../elements/forms/ModelForm"; import { first } from "../../../utils"; +import { until } from "lit-html/directives/until"; @customElement("ak-stage-user-write-form") export class UserWriteStageForm extends ModelForm { @@ -70,6 +71,33 @@ export class UserWriteStageForm extends ModelForm { ${t`Mark newly created users as inactive.`}

+ + +

+ ${t`Newly created users are added to this group, if a group is selected.`} +

+
`;