diff --git a/authentik/stages/user_write/api.py b/authentik/stages/user_write/api.py index 4cf0f17d2..1abca9e9f 100644 --- a/authentik/stages/user_write/api.py +++ b/authentik/stages/user_write/api.py @@ -15,6 +15,7 @@ class UserWriteStageSerializer(StageSerializer): "user_creation_mode", "create_users_as_inactive", "create_users_group", + "user_type", "user_path_template", ] diff --git a/authentik/stages/user_write/migrations/0008_userwritestage_user_type.py b/authentik/stages/user_write/migrations/0008_userwritestage_user_type.py new file mode 100644 index 000000000..e0761b551 --- /dev/null +++ b/authentik/stages/user_write/migrations/0008_userwritestage_user_type.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.6 on 2023-10-25 15:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("authentik_stages_user_write", "0007_remove_userwritestage_can_create_users_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="userwritestage", + name="user_type", + field=models.TextField( + choices=[ + ("internal", "Internal"), + ("external", "External"), + ("service_account", "Service Account"), + ("internal_service_account", "Internal Service Account"), + ], + default="external", + ), + ), + ] diff --git a/authentik/stages/user_write/models.py b/authentik/stages/user_write/models.py index ca50951a3..eb1dfd2c7 100644 --- a/authentik/stages/user_write/models.py +++ b/authentik/stages/user_write/models.py @@ -5,7 +5,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.core.models import Group, UserTypes from authentik.flows.models import Stage @@ -39,6 +39,10 @@ class UserWriteStage(Stage): help_text=_("Optionally add newly created users to this group."), ) + user_type = models.TextField( + choices=UserTypes.choices, + default=UserTypes.EXTERNAL, + ) user_path_template = models.TextField( default="", blank=True, diff --git a/authentik/stages/user_write/stage.py b/authentik/stages/user_write/stage.py index 5a4c80974..5117f0358 100644 --- a/authentik/stages/user_write/stage.py +++ b/authentik/stages/user_write/stage.py @@ -9,7 +9,7 @@ from django.utils.translation import gettext as _ from rest_framework.exceptions import ValidationError from authentik.core.middleware import SESSION_KEY_IMPERSONATE_USER -from authentik.core.models import USER_ATTRIBUTE_SOURCES, User, UserSourceConnection +from authentik.core.models import USER_ATTRIBUTE_SOURCES, User, UserSourceConnection, UserTypes from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.stage import StageView @@ -22,6 +22,7 @@ from authentik.stages.user_write.models import UserCreationMode from authentik.stages.user_write.signals import user_write PLAN_CONTEXT_GROUPS = "groups" +PLAN_CONTEXT_USER_TYPE = "user_type" PLAN_CONTEXT_USER_PATH = "user_path" @@ -55,6 +56,19 @@ class UserWriteStageView(StageView): ) if path == "": path = User.default_path() + + try: + user_type = UserTypes( + self.executor.plan.context.get( + PLAN_CONTEXT_USER_TYPE, + self.executor.current_stage.user_type, + ) + ) + except ValueError: + user_type = self.executor.current_stage.user_type + if user_type == UserTypes.INTERNAL_SERVICE_ACCOUNT: + user_type = UserTypes.SERVICE_ACCOUNT + if not self.request.user.is_anonymous: self.executor.plan.context.setdefault(PLAN_CONTEXT_PENDING_USER, self.request.user) if ( @@ -66,6 +80,7 @@ class UserWriteStageView(StageView): self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User( is_active=not self.executor.current_stage.create_users_as_inactive, path=path, + type=user_type, ) self.executor.plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_INBUILT self.logger.debug( diff --git a/blueprints/schema.json b/blueprints/schema.json index deb177e4d..6fe11e20a 100644 --- a/blueprints/schema.json +++ b/blueprints/schema.json @@ -8368,6 +8368,16 @@ "title": "Create users group", "description": "Optionally add newly created users to this group." }, + "user_type": { + "type": "string", + "enum": [ + "internal", + "external", + "service_account", + "internal_service_account" + ], + "title": "User type" + }, "user_path_template": { "type": "string", "title": "User path template" diff --git a/schema.yml b/schema.yml index 5a6a347ab..f956fcabb 100644 --- a/schema.yml +++ b/schema.yml @@ -27494,6 +27494,20 @@ paths: name: user_path_template schema: type: string + - in: query + name: user_type + schema: + type: string + enum: + - external + - internal + - internal_service_account + - service_account + description: |- + * `internal` - Internal + * `external` - External + * `service_account` - Service Account + * `internal_service_account` - Internal Service Account tags: - stages security: @@ -38052,6 +38066,8 @@ components: format: uuid nullable: true description: Optionally add newly created users to this group. + user_type: + $ref: '#/components/schemas/UserTypeEnum' user_path_template: type: string PatchedWebAuthnDeviceRequest: @@ -42422,6 +42438,8 @@ components: format: uuid nullable: true description: Optionally add newly created users to this group. + user_type: + $ref: '#/components/schemas/UserTypeEnum' user_path_template: type: string required: @@ -42452,6 +42470,8 @@ components: format: uuid nullable: true description: Optionally add newly created users to this group. + user_type: + $ref: '#/components/schemas/UserTypeEnum' user_path_template: type: string required: diff --git a/web/src/admin/stages/user_write/UserWriteStageForm.ts b/web/src/admin/stages/user_write/UserWriteStageForm.ts index 0cfefc57c..3ef5185cd 100644 --- a/web/src/admin/stages/user_write/UserWriteStageForm.ts +++ b/web/src/admin/stages/user_write/UserWriteStageForm.ts @@ -12,7 +12,14 @@ import { TemplateResult, html } from "lit"; import { customElement } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; -import { CoreApi, CoreGroupsListRequest, Group, StagesApi, UserWriteStage } from "@goauthentik/api"; +import { + CoreApi, + CoreGroupsListRequest, + Group, + StagesApi, + UserTypeEnum, + UserWriteStage, +} from "@goauthentik/api"; @customElement("ak-stage-user-write-form") export class UserWriteStageForm extends ModelForm { @@ -111,6 +118,42 @@ export class UserWriteStageForm extends ModelForm { ${msg("Mark newly created users as inactive.")}

+ + + +

+ ${msg("User type used for newly created users.")} +

+
{ diff --git a/web/xliff/fr.xlf b/web/xliff/fr.xlf index cac520e96..222053997 100644 --- a/web/xliff/fr.xlf +++ b/web/xliff/fr.xlf @@ -7930,6 +7930,15 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti <No name set> <No name set> + + + Check the release notes + + + User Statistics + + + User type used for newly created users. diff --git a/website/docs/flow/context/index.md b/website/docs/flow/context/index.md index 13f6c5c5b..c092f3471 100644 --- a/website/docs/flow/context/index.md +++ b/website/docs/flow/context/index.md @@ -120,6 +120,14 @@ If set, this must be a list of group objects and not group names. Path the `pending_user` will be written to. If not set in the flow, falls back to the value set in the user_write stage, and otherwise to the `users` path. +##### `user_type` (string) + +:::info +Requires authentik 2023.10 +::: + +Type the `pending_user` will be created as. Must be one of `internal`, `external` or `service_account`. + #### Password stage ##### `user_backend` (string)