providers/sync: improve v3 (#9966)

* make external id field externally visible

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* catch up scim provider

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add missing views to scim provider

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make neither user nor group required for mapping testing

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* improve SkipObject handling

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* allow deletion of connection objects

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make entra logs less noisy

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make event_matcher less noisy

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L
2024-06-06 12:47:48 +09:00
committed by GitHub
parent 0c652a210d
commit 88e9c9b669
28 changed files with 963 additions and 51 deletions

View File

@ -58,7 +58,7 @@ from authentik.outposts.models import OutpostServiceConnection
from authentik.policies.models import Policy, PolicyBindingModel from authentik.policies.models import Policy, PolicyBindingModel
from authentik.policies.reputation.models import Reputation from authentik.policies.reputation.models import Reputation
from authentik.providers.oauth2.models import AccessToken, AuthorizationCode, RefreshToken from authentik.providers.oauth2.models import AccessToken, AuthorizationCode, RefreshToken
from authentik.providers.scim.models import SCIMGroup, SCIMUser from authentik.providers.scim.models import SCIMProviderGroup, SCIMProviderUser
from authentik.sources.scim.models import SCIMSourceGroup, SCIMSourceUser from authentik.sources.scim.models import SCIMSourceGroup, SCIMSourceUser
from authentik.stages.authenticator_webauthn.models import WebAuthnDeviceType from authentik.stages.authenticator_webauthn.models import WebAuthnDeviceType
from authentik.tenants.models import Tenant from authentik.tenants.models import Tenant
@ -97,8 +97,8 @@ def excluded_models() -> list[type[Model]]:
# FIXME: these shouldn't need to be explicitly listed, but rather based off of a mixin # FIXME: these shouldn't need to be explicitly listed, but rather based off of a mixin
FlowToken, FlowToken,
LicenseUsage, LicenseUsage,
SCIMGroup, SCIMProviderGroup,
SCIMUser, SCIMProviderUser,
Tenant, Tenant,
SystemTask, SystemTask,
ConnectionToken, ConnectionToken,

View File

@ -80,8 +80,10 @@ class PropertyMappingViewSet(
class PropertyMappingTestSerializer(PolicyTestSerializer): class PropertyMappingTestSerializer(PolicyTestSerializer):
"""Test property mapping execution for a user/group with context""" """Test property mapping execution for a user/group with context"""
user = PrimaryKeyRelatedField(queryset=User.objects.all(), required=False) user = PrimaryKeyRelatedField(queryset=User.objects.all(), required=False, allow_null=True)
group = PrimaryKeyRelatedField(queryset=Group.objects.all(), required=False) group = PrimaryKeyRelatedField(
queryset=Group.objects.all(), required=False, allow_null=True
)
queryset = PropertyMapping.objects.select_subclasses() queryset = PropertyMapping.objects.select_subclasses()
serializer_class = PropertyMappingSerializer serializer_class = PropertyMappingSerializer

View File

@ -19,6 +19,7 @@ class GoogleWorkspaceProviderGroupSerializer(ModelSerializer):
model = GoogleWorkspaceProviderGroup model = GoogleWorkspaceProviderGroup
fields = [ fields = [
"id", "id",
"google_id",
"group", "group",
"group_obj", "group_obj",
"provider", "provider",

View File

@ -19,6 +19,7 @@ class GoogleWorkspaceProviderUserSerializer(ModelSerializer):
model = GoogleWorkspaceProviderUser model = GoogleWorkspaceProviderUser
fields = [ fields = [
"id", "id",
"google_id",
"user", "user",
"user_obj", "user_obj",
"provider", "provider",

View File

@ -19,6 +19,7 @@ class MicrosoftEntraProviderGroupSerializer(ModelSerializer):
model = MicrosoftEntraProviderGroup model = MicrosoftEntraProviderGroup
fields = [ fields = [
"id", "id",
"microsoft_id",
"group", "group",
"group_obj", "group_obj",
"provider", "provider",

View File

@ -19,6 +19,7 @@ class MicrosoftEntraProviderUserSerializer(ModelSerializer):
model = MicrosoftEntraProviderUser model = MicrosoftEntraProviderUser
fields = [ fields = [
"id", "id",
"microsoft_id",
"user", "user",
"user_obj", "user_obj",
"provider", "provider",

View File

@ -102,6 +102,8 @@ def get_logger_config():
"gunicorn": "INFO", "gunicorn": "INFO",
"requests_mock": "WARNING", "requests_mock": "WARNING",
"hpack": "WARNING", "hpack": "WARNING",
"httpx": "WARNING",
"azure": "WARNING",
} }
for handler_name, level in handler_level_map.items(): for handler_name, level in handler_level_map.items():
base_config["loggers"][handler_name] = { base_config["loggers"][handler_name] = {

View File

@ -57,6 +57,8 @@ class PropertyMappingManager:
mapping.set_context(user, request, **kwargs) mapping.set_context(user, request, **kwargs)
try: try:
value = mapping.evaluate(mapping.model.expression) value = mapping.evaluate(mapping.model.expression)
except PropertyMappingExpressionException as exc:
raise exc from exc
except Exception as exc: except Exception as exc:
raise PropertyMappingExpressionException(exc, mapping.model) from exc raise PropertyMappingExpressionException(exc, mapping.model) from exc
if value is None: if value is None:

View File

@ -91,7 +91,6 @@ class BaseOutgoingSyncClient[
} }
eval_kwargs.setdefault("user", None) eval_kwargs.setdefault("user", None)
for value in self.mapper.iter_eval(**eval_kwargs): for value in self.mapper.iter_eval(**eval_kwargs):
try:
always_merger.merge(raw_final_object, value) always_merger.merge(raw_final_object, value)
except SkipObjectException as exc: except SkipObjectException as exc:
raise exc from exc raise exc from exc
@ -104,7 +103,7 @@ class BaseOutgoingSyncClient[
).save() ).save()
raise StopSync(exc, obj, exc.mapping) from exc raise StopSync(exc, obj, exc.mapping) from exc
if not raw_final_object: if not raw_final_object:
raise StopSync(ValueError("No user mappings configured"), obj) raise StopSync(ValueError("No mappings configured"), obj)
for key, value in defaults.items(): for key, value in defaults.items():
raw_final_object.setdefault(key, value) raw_final_object.setdefault(key, value)
return raw_final_object return raw_final_object

View File

@ -125,6 +125,7 @@ class SyncTasks:
try: try:
client.write(obj) client.write(obj)
except SkipObjectException: except SkipObjectException:
self.logger.debug("skipping object due to SkipObject", obj=obj)
continue continue
except BadRequestSyncException as exc: except BadRequestSyncException as exc:
self.logger.warning("failed to sync object", exc=exc, obj=obj) self.logger.warning("failed to sync object", exc=exc, obj=obj)

View File

@ -102,7 +102,7 @@ class EventMatcherPolicy(Policy):
result = checker(request, event) result = checker(request, event)
if result is None: if result is None:
continue continue
LOGGER.info( LOGGER.debug(
"Event matcher check result", "Event matcher check result",
checker=checker.__name__, checker=checker.__name__,
result=result, result=result,

View File

@ -0,0 +1,43 @@
"""SCIMProviderGroup API Views"""
from rest_framework import mixins
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserGroupSerializer
from authentik.providers.scim.models import SCIMProviderGroup
class SCIMProviderGroupSerializer(ModelSerializer):
"""SCIMProviderGroup Serializer"""
group_obj = UserGroupSerializer(source="group", read_only=True)
class Meta:
model = SCIMProviderGroup
fields = [
"id",
"scim_id",
"group",
"group_obj",
"provider",
]
class SCIMProviderGroupViewSet(
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):
"""SCIMProviderGroup Viewset"""
queryset = SCIMProviderGroup.objects.all().select_related("group")
serializer_class = SCIMProviderGroupSerializer
filterset_fields = ["provider__id", "group__name", "group__group_uuid"]
search_fields = ["provider__name", "group__name"]
ordering = ["group__name"]

View File

@ -0,0 +1,43 @@
"""SCIMProviderUser API Views"""
from rest_framework import mixins
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet
from authentik.core.api.groups import GroupMemberSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.providers.scim.models import SCIMProviderUser
class SCIMProviderUserSerializer(ModelSerializer):
"""SCIMProviderUser Serializer"""
user_obj = GroupMemberSerializer(source="user", read_only=True)
class Meta:
model = SCIMProviderUser
fields = [
"id",
"scim_id",
"user",
"user_obj",
"provider",
]
class SCIMProviderUserViewSet(
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):
"""SCIMProviderUser Viewset"""
queryset = SCIMProviderUser.objects.all().select_related("user")
serializer_class = SCIMProviderUserSerializer
filterset_fields = ["provider__id", "user__username", "user__id"]
search_fields = ["provider__name", "user__username"]
ordering = ["user__username"]

View File

@ -19,13 +19,18 @@ from authentik.providers.scim.clients.exceptions import (
) )
from authentik.providers.scim.clients.schema import SCIM_GROUP_SCHEMA, PatchRequest from authentik.providers.scim.clients.schema import SCIM_GROUP_SCHEMA, PatchRequest
from authentik.providers.scim.clients.schema import Group as SCIMGroupSchema from authentik.providers.scim.clients.schema import Group as SCIMGroupSchema
from authentik.providers.scim.models import SCIMGroup, SCIMMapping, SCIMProvider, SCIMUser from authentik.providers.scim.models import (
SCIMMapping,
SCIMProvider,
SCIMProviderGroup,
SCIMProviderUser,
)
class SCIMGroupClient(SCIMClient[Group, SCIMGroup, SCIMGroupSchema]): class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
"""SCIM client for groups""" """SCIM client for groups"""
connection_type = SCIMGroup connection_type = SCIMProviderGroup
connection_type_query = "group" connection_type_query = "group"
mapper: PropertyMappingManager mapper: PropertyMappingManager
@ -37,7 +42,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroup, SCIMGroupSchema]):
["group", "provider", "connection"], ["group", "provider", "connection"],
) )
def to_schema(self, obj: Group, connection: SCIMGroup) -> SCIMGroupSchema: def to_schema(self, obj: Group, connection: SCIMProviderGroup) -> SCIMGroupSchema:
"""Convert authentik user into SCIM""" """Convert authentik user into SCIM"""
raw_scim_group = super().to_schema( raw_scim_group = super().to_schema(
obj, obj,
@ -52,7 +57,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroup, SCIMGroupSchema]):
scim_group.externalId = str(obj.pk) scim_group.externalId = str(obj.pk)
users = list(obj.users.order_by("id").values_list("id", flat=True)) users = list(obj.users.order_by("id").values_list("id", flat=True))
connections = SCIMUser.objects.filter(provider=self.provider, user__pk__in=users) connections = SCIMProviderUser.objects.filter(provider=self.provider, user__pk__in=users)
members = [] members = []
for user in connections: for user in connections:
members.append( members.append(
@ -66,7 +71,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroup, SCIMGroupSchema]):
def delete(self, obj: Group): def delete(self, obj: Group):
"""Delete group""" """Delete group"""
scim_group = SCIMGroup.objects.filter(provider=self.provider, group=obj).first() scim_group = SCIMProviderGroup.objects.filter(provider=self.provider, group=obj).first()
if not scim_group: if not scim_group:
self.logger.debug("Group does not exist in SCIM, skipping") self.logger.debug("Group does not exist in SCIM, skipping")
return None return None
@ -88,9 +93,11 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroup, SCIMGroupSchema]):
scim_id = response.get("id") scim_id = response.get("id")
if not scim_id or scim_id == "": if not scim_id or scim_id == "":
raise StopSync("SCIM Response with missing or invalid `id`") raise StopSync("SCIM Response with missing or invalid `id`")
return SCIMGroup.objects.create(provider=self.provider, group=group, scim_id=scim_id) return SCIMProviderGroup.objects.create(
provider=self.provider, group=group, scim_id=scim_id
)
def update(self, group: Group, connection: SCIMGroup): def update(self, group: Group, connection: SCIMProviderGroup):
"""Update existing group""" """Update existing group"""
scim_group = self.to_schema(group, connection) scim_group = self.to_schema(group, connection)
scim_group.id = connection.scim_id scim_group.id = connection.scim_id
@ -158,16 +165,16 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroup, SCIMGroupSchema]):
"""Add users in users_set to group""" """Add users in users_set to group"""
if len(users_set) < 1: if len(users_set) < 1:
return return
scim_group = SCIMGroup.objects.filter(provider=self.provider, group=group).first() scim_group = SCIMProviderGroup.objects.filter(provider=self.provider, group=group).first()
if not scim_group: if not scim_group:
self.logger.warning( self.logger.warning(
"could not sync group membership, group does not exist", group=group "could not sync group membership, group does not exist", group=group
) )
return return
user_ids = list( user_ids = list(
SCIMUser.objects.filter(user__pk__in=users_set, provider=self.provider).values_list( SCIMProviderUser.objects.filter(
"scim_id", flat=True user__pk__in=users_set, provider=self.provider
) ).values_list("scim_id", flat=True)
) )
if len(user_ids) < 1: if len(user_ids) < 1:
return return
@ -184,16 +191,16 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroup, SCIMGroupSchema]):
"""Remove users in users_set from group""" """Remove users in users_set from group"""
if len(users_set) < 1: if len(users_set) < 1:
return return
scim_group = SCIMGroup.objects.filter(provider=self.provider, group=group).first() scim_group = SCIMProviderGroup.objects.filter(provider=self.provider, group=group).first()
if not scim_group: if not scim_group:
self.logger.warning( self.logger.warning(
"could not sync group membership, group does not exist", group=group "could not sync group membership, group does not exist", group=group
) )
return return
user_ids = list( user_ids = list(
SCIMUser.objects.filter(user__pk__in=users_set, provider=self.provider).values_list( SCIMProviderUser.objects.filter(
"scim_id", flat=True user__pk__in=users_set, provider=self.provider
) ).values_list("scim_id", flat=True)
) )
if len(user_ids) < 1: if len(user_ids) < 1:
return return

View File

@ -9,13 +9,13 @@ from authentik.policies.utils import delete_none_values
from authentik.providers.scim.clients.base import SCIMClient from authentik.providers.scim.clients.base import SCIMClient
from authentik.providers.scim.clients.schema import SCIM_USER_SCHEMA from authentik.providers.scim.clients.schema import SCIM_USER_SCHEMA
from authentik.providers.scim.clients.schema import User as SCIMUserSchema from authentik.providers.scim.clients.schema import User as SCIMUserSchema
from authentik.providers.scim.models import SCIMMapping, SCIMProvider, SCIMUser from authentik.providers.scim.models import SCIMMapping, SCIMProvider, SCIMProviderUser
class SCIMUserClient(SCIMClient[User, SCIMUser, SCIMUserSchema]): class SCIMUserClient(SCIMClient[User, SCIMProviderUser, SCIMUserSchema]):
"""SCIM client for users""" """SCIM client for users"""
connection_type = SCIMUser connection_type = SCIMProviderUser
connection_type_query = "user" connection_type_query = "user"
mapper: PropertyMappingManager mapper: PropertyMappingManager
@ -27,7 +27,7 @@ class SCIMUserClient(SCIMClient[User, SCIMUser, SCIMUserSchema]):
["provider", "connection"], ["provider", "connection"],
) )
def to_schema(self, obj: User, connection: SCIMUser) -> SCIMUserSchema: def to_schema(self, obj: User, connection: SCIMProviderUser) -> SCIMUserSchema:
"""Convert authentik user into SCIM""" """Convert authentik user into SCIM"""
raw_scim_user = super().to_schema( raw_scim_user = super().to_schema(
obj, obj,
@ -44,7 +44,7 @@ class SCIMUserClient(SCIMClient[User, SCIMUser, SCIMUserSchema]):
def delete(self, obj: User): def delete(self, obj: User):
"""Delete user""" """Delete user"""
scim_user = SCIMUser.objects.filter(provider=self.provider, user=obj).first() scim_user = SCIMProviderUser.objects.filter(provider=self.provider, user=obj).first()
if not scim_user: if not scim_user:
self.logger.debug("User does not exist in SCIM, skipping") self.logger.debug("User does not exist in SCIM, skipping")
return None return None
@ -66,9 +66,9 @@ class SCIMUserClient(SCIMClient[User, SCIMUser, SCIMUserSchema]):
scim_id = response.get("id") scim_id = response.get("id")
if not scim_id or scim_id == "": if not scim_id or scim_id == "":
raise StopSync("SCIM Response with missing or invalid `id`") raise StopSync("SCIM Response with missing or invalid `id`")
return SCIMUser.objects.create(provider=self.provider, user=user, scim_id=scim_id) return SCIMProviderUser.objects.create(provider=self.provider, user=user, scim_id=scim_id)
def update(self, user: User, connection: SCIMUser): def update(self, user: User, connection: SCIMProviderUser):
"""Update existing user""" """Update existing user"""
scim_user = self.to_schema(user, connection) scim_user = self.to_schema(user, connection)
scim_user.id = connection.scim_id scim_user.id = connection.scim_id

View File

@ -0,0 +1,24 @@
# Generated by Django 5.0.6 on 2024-06-04 07:45
from django.conf import settings
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0035_alter_group_options_and_more"),
("authentik_providers_scim", "0007_scimgroup_scim_id_scimuser_scim_id_and_more"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RenameModel(
old_name="SCIMGroup",
new_name="SCIMProviderGroup",
),
migrations.RenameModel(
old_name="SCIMUser",
new_name="SCIMProviderUser",
),
]

View File

@ -10,6 +10,7 @@ from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer from rest_framework.serializers import Serializer
from authentik.core.models import BackchannelProvider, Group, PropertyMapping, User, UserTypes from authentik.core.models import BackchannelProvider, Group, PropertyMapping, User, UserTypes
from authentik.lib.models import SerializerModel
from authentik.lib.sync.outgoing.base import BaseOutgoingSyncClient from authentik.lib.sync.outgoing.base import BaseOutgoingSyncClient
from authentik.lib.sync.outgoing.models import OutgoingSyncProvider from authentik.lib.sync.outgoing.models import OutgoingSyncProvider
@ -106,7 +107,7 @@ class SCIMMapping(PropertyMapping):
verbose_name_plural = _("SCIM Mappings") verbose_name_plural = _("SCIM Mappings")
class SCIMUser(models.Model): class SCIMProviderUser(SerializerModel):
"""Mapping of a user and provider to a SCIM user ID""" """Mapping of a user and provider to a SCIM user ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4) id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@ -114,14 +115,20 @@ class SCIMUser(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
provider = models.ForeignKey(SCIMProvider, on_delete=models.CASCADE) provider = models.ForeignKey(SCIMProvider, on_delete=models.CASCADE)
@property
def serializer(self) -> type[Serializer]:
from authentik.providers.scim.api.users import SCIMProviderUserSerializer
return SCIMProviderUserSerializer
class Meta: class Meta:
unique_together = (("scim_id", "user", "provider"),) unique_together = (("scim_id", "user", "provider"),)
def __str__(self) -> str: def __str__(self) -> str:
return f"SCIM User {self.user_id} to {self.provider_id}" return f"SCIM Provider User {self.user_id} to {self.provider_id}"
class SCIMGroup(models.Model): class SCIMProviderGroup(SerializerModel):
"""Mapping of a group and provider to a SCIM user ID""" """Mapping of a group and provider to a SCIM user ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4) id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@ -129,8 +136,14 @@ class SCIMGroup(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE)
provider = models.ForeignKey(SCIMProvider, on_delete=models.CASCADE) provider = models.ForeignKey(SCIMProvider, on_delete=models.CASCADE)
@property
def serializer(self) -> type[Serializer]:
from authentik.providers.scim.api.groups import SCIMProviderGroupSerializer
return SCIMProviderGroupSerializer
class Meta: class Meta:
unique_together = (("scim_id", "group", "provider"),) unique_together = (("scim_id", "group", "provider"),)
def __str__(self) -> str: def __str__(self) -> str:
return f"SCIM Group {self.group_id} to {self.provider_id}" return f"SCIM Provider Group {self.group_id} to {self.provider_id}"

View File

@ -1,9 +1,17 @@
"""API URLs""" """API URLs"""
from authentik.providers.scim.api.groups import (
SCIMProviderGroupViewSet,
)
from authentik.providers.scim.api.property_mappings import SCIMMappingViewSet from authentik.providers.scim.api.property_mappings import SCIMMappingViewSet
from authentik.providers.scim.api.providers import SCIMProviderViewSet from authentik.providers.scim.api.providers import SCIMProviderViewSet
from authentik.providers.scim.api.users import (
SCIMProviderUserViewSet,
)
api_urlpatterns = [ api_urlpatterns = [
("providers/scim", SCIMProviderViewSet), ("providers/scim", SCIMProviderViewSet),
("providers/scim_users", SCIMProviderUserViewSet),
("providers/scim_groups", SCIMProviderGroupViewSet),
("propertymappings/scim", SCIMMappingViewSet), ("propertymappings/scim", SCIMMappingViewSet),
] ]

View File

@ -161,7 +161,6 @@ class BaseLDAPSynchronizer:
dn=object_dn, dn=object_dn,
source=self._source, source=self._source,
): ):
try:
if isinstance(value, (bytes)): if isinstance(value, (bytes)):
self._logger.warning("property mapping returned bytes", mapping=mapping) self._logger.warning("property mapping returned bytes", mapping=mapping)
continue continue

View File

@ -19726,6 +19726,403 @@ paths:
schema: schema:
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
description: '' description: ''
/providers/scim_groups/:
get:
operationId: providers_scim_groups_list
description: SCIMProviderGroup Viewset
parameters:
- in: query
name: group__group_uuid
schema:
type: string
format: uuid
- in: query
name: group__name
schema:
type: string
- name: ordering
required: false
in: query
description: Which field to use when ordering the results.
schema:
type: string
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
- in: query
name: provider__id
schema:
type: integer
- name: search
required: false
in: query
description: A search term.
schema:
type: string
tags:
- providers
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedSCIMProviderGroupList'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
post:
operationId: providers_scim_groups_create
description: SCIMProviderGroup Viewset
tags:
- providers
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SCIMProviderGroupRequest'
required: true
security:
- authentik: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/SCIMProviderGroup'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/providers/scim_groups/{id}/:
get:
operationId: providers_scim_groups_retrieve
description: SCIMProviderGroup Viewset
parameters:
- in: path
name: id
schema:
type: string
format: uuid
description: A UUID string identifying this scim provider group.
required: true
tags:
- providers
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/SCIMProviderGroup'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
delete:
operationId: providers_scim_groups_destroy
description: SCIMProviderGroup Viewset
parameters:
- in: path
name: id
schema:
type: string
format: uuid
description: A UUID string identifying this scim provider group.
required: true
tags:
- providers
security:
- authentik: []
responses:
'204':
description: No response body
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/providers/scim_groups/{id}/used_by/:
get:
operationId: providers_scim_groups_used_by_list
description: Get a list of all objects that use this object
parameters:
- in: path
name: id
schema:
type: string
format: uuid
description: A UUID string identifying this scim provider group.
required: true
tags:
- providers
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UsedBy'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/providers/scim_users/:
get:
operationId: providers_scim_users_list
description: SCIMProviderUser Viewset
parameters:
- name: ordering
required: false
in: query
description: Which field to use when ordering the results.
schema:
type: string
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
- in: query
name: provider__id
schema:
type: integer
- name: search
required: false
in: query
description: A search term.
schema:
type: string
- in: query
name: user__id
schema:
type: integer
- in: query
name: user__username
schema:
type: string
tags:
- providers
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedSCIMProviderUserList'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
post:
operationId: providers_scim_users_create
description: SCIMProviderUser Viewset
tags:
- providers
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SCIMProviderUserRequest'
required: true
security:
- authentik: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/SCIMProviderUser'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/providers/scim_users/{id}/:
get:
operationId: providers_scim_users_retrieve
description: SCIMProviderUser Viewset
parameters:
- in: path
name: id
schema:
type: string
format: uuid
description: A UUID string identifying this scim provider user.
required: true
tags:
- providers
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/SCIMProviderUser'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
delete:
operationId: providers_scim_users_destroy
description: SCIMProviderUser Viewset
parameters:
- in: path
name: id
schema:
type: string
format: uuid
description: A UUID string identifying this scim provider user.
required: true
tags:
- providers
security:
- authentik: []
responses:
'204':
description: No response body
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/providers/scim_users/{id}/used_by/:
get:
operationId: providers_scim_users_used_by_list
description: Get a list of all objects that use this object
parameters:
- in: path
name: id
schema:
type: string
format: uuid
description: A UUID string identifying this scim provider user.
required: true
tags:
- providers
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UsedBy'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/rac/connection_tokens/: /rac/connection_tokens/:
get: get:
operationId: rac_connection_tokens_list operationId: rac_connection_tokens_list
@ -36335,6 +36732,8 @@ components:
type: string type: string
format: uuid format: uuid
readOnly: true readOnly: true
google_id:
type: string
group: group:
type: string type: string
format: uuid format: uuid
@ -36348,6 +36747,7 @@ components:
readOnly: true readOnly: true
required: required:
- attributes - attributes
- google_id
- group - group
- group_obj - group_obj
- id - id
@ -36356,12 +36756,16 @@ components:
type: object type: object
description: GoogleWorkspaceProviderGroup Serializer description: GoogleWorkspaceProviderGroup Serializer
properties: properties:
google_id:
type: string
minLength: 1
group: group:
type: string type: string
format: uuid format: uuid
provider: provider:
type: integer type: integer
required: required:
- google_id
- group - group
- provider - provider
GoogleWorkspaceProviderMapping: GoogleWorkspaceProviderMapping:
@ -36484,6 +36888,8 @@ components:
type: string type: string
format: uuid format: uuid
readOnly: true readOnly: true
google_id:
type: string
user: user:
type: integer type: integer
user_obj: user_obj:
@ -36496,6 +36902,7 @@ components:
readOnly: true readOnly: true
required: required:
- attributes - attributes
- google_id
- id - id
- provider - provider
- user - user
@ -36504,11 +36911,15 @@ components:
type: object type: object
description: GoogleWorkspaceProviderUser Serializer description: GoogleWorkspaceProviderUser Serializer
properties: properties:
google_id:
type: string
minLength: 1
user: user:
type: integer type: integer
provider: provider:
type: integer type: integer
required: required:
- google_id
- provider - provider
- user - user
Group: Group:
@ -38003,6 +38414,8 @@ components:
type: string type: string
format: uuid format: uuid
readOnly: true readOnly: true
microsoft_id:
type: string
group: group:
type: string type: string
format: uuid format: uuid
@ -38019,11 +38432,15 @@ components:
- group - group
- group_obj - group_obj
- id - id
- microsoft_id
- provider - provider
MicrosoftEntraProviderGroupRequest: MicrosoftEntraProviderGroupRequest:
type: object type: object
description: MicrosoftEntraProviderGroup Serializer description: MicrosoftEntraProviderGroup Serializer
properties: properties:
microsoft_id:
type: string
minLength: 1
group: group:
type: string type: string
format: uuid format: uuid
@ -38031,6 +38448,7 @@ components:
type: integer type: integer
required: required:
- group - group
- microsoft_id
- provider - provider
MicrosoftEntraProviderMapping: MicrosoftEntraProviderMapping:
type: object type: object
@ -38149,6 +38567,8 @@ components:
type: string type: string
format: uuid format: uuid
readOnly: true readOnly: true
microsoft_id:
type: string
user: user:
type: integer type: integer
user_obj: user_obj:
@ -38162,6 +38582,7 @@ components:
required: required:
- attributes - attributes
- id - id
- microsoft_id
- provider - provider
- user - user
- user_obj - user_obj
@ -38169,11 +38590,15 @@ components:
type: object type: object
description: MicrosoftEntraProviderUser Serializer description: MicrosoftEntraProviderUser Serializer
properties: properties:
microsoft_id:
type: string
minLength: 1
user: user:
type: integer type: integer
provider: provider:
type: integer type: integer
required: required:
- microsoft_id
- provider - provider
- user - user
ModelEnum: ModelEnum:
@ -40166,6 +40591,18 @@ components:
required: required:
- pagination - pagination
- results - results
PaginatedSCIMProviderGroupList:
type: object
properties:
pagination:
$ref: '#/components/schemas/Pagination'
results:
type: array
items:
$ref: '#/components/schemas/SCIMProviderGroup'
required:
- pagination
- results
PaginatedSCIMProviderList: PaginatedSCIMProviderList:
type: object type: object
properties: properties:
@ -40178,6 +40615,18 @@ components:
required: required:
- pagination - pagination
- results - results
PaginatedSCIMProviderUserList:
type: object
properties:
pagination:
$ref: '#/components/schemas/Pagination'
results:
type: array
items:
$ref: '#/components/schemas/SCIMProviderUser'
required:
- pagination
- results
PaginatedSCIMSourceGroupList: PaginatedSCIMSourceGroupList:
type: object type: object
properties: properties:
@ -44147,12 +44596,14 @@ components:
properties: properties:
user: user:
type: integer type: integer
nullable: true
context: context:
type: object type: object
additionalProperties: {} additionalProperties: {}
group: group:
type: string type: string
format: uuid format: uuid
nullable: true
PropertyMappingTestResult: PropertyMappingTestResult:
type: object type: object
description: Result of a Property-mapping test description: Result of a Property-mapping test
@ -45906,6 +46357,47 @@ components:
- url - url
- verbose_name - verbose_name
- verbose_name_plural - verbose_name_plural
SCIMProviderGroup:
type: object
description: SCIMProviderGroup Serializer
properties:
id:
type: string
format: uuid
readOnly: true
scim_id:
type: string
group:
type: string
format: uuid
group_obj:
allOf:
- $ref: '#/components/schemas/UserGroup'
readOnly: true
provider:
type: integer
required:
- group
- group_obj
- id
- provider
- scim_id
SCIMProviderGroupRequest:
type: object
description: SCIMProviderGroup Serializer
properties:
scim_id:
type: string
minLength: 1
group:
type: string
format: uuid
provider:
type: integer
required:
- group
- provider
- scim_id
SCIMProviderRequest: SCIMProviderRequest:
type: object type: object
description: SCIMProvider Serializer description: SCIMProvider Serializer
@ -45942,6 +46434,45 @@ components:
- name - name
- token - token
- url - url
SCIMProviderUser:
type: object
description: SCIMProviderUser Serializer
properties:
id:
type: string
format: uuid
readOnly: true
scim_id:
type: string
user:
type: integer
user_obj:
allOf:
- $ref: '#/components/schemas/GroupMember'
readOnly: true
provider:
type: integer
required:
- id
- provider
- scim_id
- user
- user_obj
SCIMProviderUserRequest:
type: object
description: SCIMProviderUser Serializer
properties:
scim_id:
type: string
minLength: 1
user:
type: integer
provider:
type: integer
required:
- provider
- scim_id
- user
SCIMSource: SCIMSource:
type: object type: object
description: SCIMSource Serializer description: SCIMSource Serializer

View File

@ -126,6 +126,7 @@ export class PolicyTestForm extends Form<PropertyMappingTestRequest> {
renderForm(): TemplateResult { renderForm(): TemplateResult {
return html`<ak-form-element-horizontal label=${msg("User")} name="user"> return html`<ak-form-element-horizontal label=${msg("User")} name="user">
<ak-search-select <ak-search-select
blankable
.fetchObjects=${async (query?: string): Promise<User[]> => { .fetchObjects=${async (query?: string): Promise<User[]> => {
const args: CoreUsersListRequest = { const args: CoreUsersListRequest = {
ordering: "username", ordering: "username",
@ -153,6 +154,7 @@ export class PolicyTestForm extends Form<PropertyMappingTestRequest> {
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Group")} name="group"> <ak-form-element-horizontal label=${msg("Group")} name="group">
<ak-search-select <ak-search-select
blankable
.fetchObjects=${async (query?: string): Promise<Group[]> => { .fetchObjects=${async (query?: string): Promise<Group[]> => {
const args: CoreGroupsListRequest = { const args: CoreGroupsListRequest = {
ordering: "name", ordering: "name",

View File

@ -1,5 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { uiConfig } from "@goauthentik/common/ui/config"; import { uiConfig } from "@goauthentik/common/ui/config";
import "@goauthentik/elements/forms/DeleteBulkForm";
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
@ -19,6 +20,26 @@ export class GoogleWorkspaceProviderGroupList extends Table<GoogleWorkspaceProvi
return true; return true;
} }
checkbox = true;
clearOnRefresh = true;
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${msg("Google Workspace Group(s)")}
.objects=${this.selectedElements}
.delete=${(item: GoogleWorkspaceProviderGroup) => {
return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceGroupsDestroy({
id: item.id,
});
}}
>
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
${msg("Delete")}
</button>
</ak-forms-delete-bulk>`;
}
async apiEndpoint(page: number): Promise<PaginatedResponse<GoogleWorkspaceProviderGroup>> { async apiEndpoint(page: number): Promise<PaginatedResponse<GoogleWorkspaceProviderGroup>> {
return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceGroupsList({ return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceGroupsList({
page: page, page: page,

View File

@ -1,5 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { uiConfig } from "@goauthentik/common/ui/config"; import { uiConfig } from "@goauthentik/common/ui/config";
import "@goauthentik/elements/forms/DeleteBulkForm";
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
@ -19,6 +20,26 @@ export class GoogleWorkspaceProviderUserList extends Table<GoogleWorkspaceProvid
expandable = true; expandable = true;
checkbox = true;
clearOnRefresh = true;
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${msg("Google Workspace User(s)")}
.objects=${this.selectedElements}
.delete=${(item: GoogleWorkspaceProviderUser) => {
return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceUsersDestroy({
id: item.id,
});
}}
>
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
${msg("Delete")}
</button>
</ak-forms-delete-bulk>`;
}
async apiEndpoint(page: number): Promise<PaginatedResponse<GoogleWorkspaceProviderUser>> { async apiEndpoint(page: number): Promise<PaginatedResponse<GoogleWorkspaceProviderUser>> {
return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceUsersList({ return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceUsersList({
page: page, page: page,

View File

@ -1,5 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { uiConfig } from "@goauthentik/common/ui/config"; import { uiConfig } from "@goauthentik/common/ui/config";
import "@goauthentik/elements/forms/DeleteBulkForm";
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
@ -19,6 +20,23 @@ export class MicrosoftEntraProviderGroupList extends Table<MicrosoftEntraProvide
return true; return true;
} }
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${msg("Microsoft Entra Group(s)")}
.objects=${this.selectedElements}
.delete=${(item: MicrosoftEntraProviderGroup) => {
return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraGroupsDestroy({
id: item.id,
});
}}
>
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
${msg("Delete")}
</button>
</ak-forms-delete-bulk>`;
}
async apiEndpoint(page: number): Promise<PaginatedResponse<MicrosoftEntraProviderGroup>> { async apiEndpoint(page: number): Promise<PaginatedResponse<MicrosoftEntraProviderGroup>> {
return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraGroupsList({ return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraGroupsList({
page: page, page: page,

View File

@ -1,5 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { uiConfig } from "@goauthentik/common/ui/config"; import { uiConfig } from "@goauthentik/common/ui/config";
import "@goauthentik/elements/forms/DeleteBulkForm";
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
@ -19,6 +20,26 @@ export class MicrosoftEntraProviderUserList extends Table<MicrosoftEntraProvider
return true; return true;
} }
checkbox = true;
clearOnRefresh = true;
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${msg("Microsoft Entra User(s)")}
.objects=${this.selectedElements}
.delete=${(item: MicrosoftEntraProviderUser) => {
return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraUsersDestroy({
id: item.id,
});
}}
>
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
${msg("Delete")}
</button>
</ak-forms-delete-bulk>`;
}
async apiEndpoint(page: number): Promise<PaginatedResponse<MicrosoftEntraProviderUser>> { async apiEndpoint(page: number): Promise<PaginatedResponse<MicrosoftEntraProviderUser>> {
return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraUsersList({ return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraUsersList({
page: page, page: page,

View File

@ -0,0 +1,63 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { uiConfig } from "@goauthentik/common/ui/config";
import "@goauthentik/elements/forms/DeleteBulkForm";
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ProvidersApi, SCIMProviderGroup } from "@goauthentik/api";
@customElement("ak-provider-scim-groups-list")
export class SCIMProviderGroupList extends Table<SCIMProviderGroup> {
@property({ type: Number })
providerId?: number;
searchEnabled(): boolean {
return true;
}
checkbox = true;
clearOnRefresh = true;
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${msg("SCIM Group(s)")}
.objects=${this.selectedElements}
.delete=${(item: SCIMProviderGroup) => {
return new ProvidersApi(DEFAULT_CONFIG).providersScimGroupsDestroy({
id: item.id,
});
}}
>
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
${msg("Delete")}
</button>
</ak-forms-delete-bulk>`;
}
async apiEndpoint(page: number): Promise<PaginatedResponse<SCIMProviderGroup>> {
return new ProvidersApi(DEFAULT_CONFIG).providersScimGroupsList({
page: page,
pageSize: (await uiConfig()).pagination.perPage,
ordering: this.order,
search: this.search || "",
providerId: this.providerId,
});
}
columns(): TableColumn[] {
return [new TableColumn(msg("Name")), new TableColumn(msg("ID"))];
}
row(item: SCIMProviderGroup): TemplateResult[] {
return [
html`<a href="#/identity/groups/${item.groupObj.pk}">
<div>${item.groupObj.name}</div>
</a>`,
html`${item.id}`,
];
}
}

View File

@ -0,0 +1,64 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { uiConfig } from "@goauthentik/common/ui/config";
import "@goauthentik/elements/forms/DeleteBulkForm";
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ProvidersApi, SCIMProviderUser } from "@goauthentik/api";
@customElement("ak-provider-google-workspace-users-list")
export class SCIMProviderUserList extends Table<SCIMProviderUser> {
@property({ type: Number })
providerId?: number;
searchEnabled(): boolean {
return true;
}
checkbox = true;
clearOnRefresh = true;
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${msg("SCIM User(s)")}
.objects=${this.selectedElements}
.delete=${(item: SCIMProviderUser) => {
return new ProvidersApi(DEFAULT_CONFIG).providersScimUsersDestroy({
id: item.id,
});
}}
>
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
${msg("Delete")}
</button>
</ak-forms-delete-bulk>`;
}
async apiEndpoint(page: number): Promise<PaginatedResponse<SCIMProviderUser>> {
return new ProvidersApi(DEFAULT_CONFIG).providersScimUsersList({
page: page,
pageSize: (await uiConfig()).pagination.perPage,
ordering: this.order,
search: this.search || "",
providerId: this.providerId,
});
}
columns(): TableColumn[] {
return [new TableColumn(msg("Username")), new TableColumn(msg("ID"))];
}
row(item: SCIMProviderUser): TemplateResult[] {
return [
html`<a href="#/identity/users/${item.userObj.pk}">
<div>${item.userObj.username}</div>
<small>${item.userObj.name}</small>
</a>`,
html`${item.id}`,
];
}
}

View File

@ -1,4 +1,6 @@
import "@goauthentik/admin/providers/scim/SCIMProviderForm"; import "@goauthentik/admin/providers/scim/SCIMProviderForm";
import "@goauthentik/admin/providers/scim/SCIMProviderGroupList";
import "@goauthentik/admin/providers/scim/SCIMProviderUserList";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { EVENT_REFRESH } from "@goauthentik/common/constants";
import "@goauthentik/components/events/ObjectChangelog"; import "@goauthentik/components/events/ObjectChangelog";
@ -102,6 +104,28 @@ export class SCIMProviderViewPage extends AKElement {
</div> </div>
</div> </div>
</section> </section>
<section
slot="page-users"
data-tab-title="${msg("Provisioned Users")}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-l-grid pf-m-gutter">
<ak-provider-scim-users-list
providerId=${this.provider.pk}
></ak-provider-scim-users-list>
</div>
</section>
<section
slot="page-groups"
data-tab-title="${msg("Provisioned Groups")}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-l-grid pf-m-gutter">
<ak-provider-scim-groups-list
providerId=${this.provider.pk}
></ak-provider-scim-groups-list>
</div>
</section>
<ak-rbac-object-permission-page <ak-rbac-object-permission-page
slot="page-permissions" slot="page-permissions"
data-tab-title="${msg("Permissions")}" data-tab-title="${msg("Permissions")}"