From 28053059ff602c3f1af5e13cc657abf320a4f6f8 Mon Sep 17 00:00:00 2001
From: Jens L
Date: Thu, 26 Oct 2023 14:33:29 +0200
Subject: [PATCH] stages/user_write: allow setting user type when creating new
user (#7293)
Signed-off-by: Jens Langhammer
---
authentik/stages/user_write/api.py | 1 +
.../0008_userwritestage_user_type.py | 25 +++++++++++
authentik/stages/user_write/models.py | 6 ++-
authentik/stages/user_write/stage.py | 17 ++++++-
blueprints/schema.json | 10 +++++
schema.yml | 20 +++++++++
.../stages/user_write/UserWriteStageForm.ts | 45 ++++++++++++++++++-
web/src/admin/users/UserForm.ts | 3 +-
web/xliff/fr.xlf | 9 ++++
website/docs/flow/context/index.md | 8 ++++
10 files changed, 139 insertions(+), 5 deletions(-)
create mode 100644 authentik/stages/user_write/migrations/0008_userwritestage_user_type.py
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 Meta:
"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.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 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.signals import user_write
PLAN_CONTEXT_GROUPS = "groups"
+PLAN_CONTEXT_USER_TYPE = "user_type"
PLAN_CONTEXT_USER_PATH = "user_path"
@@ -55,6 +56,19 @@ def ensure_user(self) -> tuple[Optional[User], bool]:
)
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 @@ def ensure_user(self) -> tuple[Optional[User], bool]:
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.