diff --git a/authentik/blueprints/v1/importer.py b/authentik/blueprints/v1/importer.py index 7ea645c467..92f008901f 100644 --- a/authentik/blueprints/v1/importer.py +++ b/authentik/blueprints/v1/importer.py @@ -58,7 +58,7 @@ from authentik.outposts.models import OutpostServiceConnection from authentik.policies.models import Policy, PolicyBindingModel from authentik.policies.reputation.models import Reputation 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.stages.authenticator_webauthn.models import WebAuthnDeviceType 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 FlowToken, LicenseUsage, - SCIMGroup, - SCIMUser, + SCIMProviderGroup, + SCIMProviderUser, Tenant, SystemTask, ConnectionToken, diff --git a/authentik/core/api/property_mappings.py b/authentik/core/api/property_mappings.py index fb7d6c0778..0b2f04ab1e 100644 --- a/authentik/core/api/property_mappings.py +++ b/authentik/core/api/property_mappings.py @@ -80,8 +80,10 @@ class PropertyMappingViewSet( class PropertyMappingTestSerializer(PolicyTestSerializer): """Test property mapping execution for a user/group with context""" - user = PrimaryKeyRelatedField(queryset=User.objects.all(), required=False) - group = PrimaryKeyRelatedField(queryset=Group.objects.all(), required=False) + user = PrimaryKeyRelatedField(queryset=User.objects.all(), required=False, allow_null=True) + group = PrimaryKeyRelatedField( + queryset=Group.objects.all(), required=False, allow_null=True + ) queryset = PropertyMapping.objects.select_subclasses() serializer_class = PropertyMappingSerializer diff --git a/authentik/enterprise/providers/google_workspace/api/groups.py b/authentik/enterprise/providers/google_workspace/api/groups.py index 06a2449dd8..db54b4820f 100644 --- a/authentik/enterprise/providers/google_workspace/api/groups.py +++ b/authentik/enterprise/providers/google_workspace/api/groups.py @@ -19,6 +19,7 @@ class GoogleWorkspaceProviderGroupSerializer(ModelSerializer): model = GoogleWorkspaceProviderGroup fields = [ "id", + "google_id", "group", "group_obj", "provider", diff --git a/authentik/enterprise/providers/google_workspace/api/users.py b/authentik/enterprise/providers/google_workspace/api/users.py index 3826598eea..de794e135b 100644 --- a/authentik/enterprise/providers/google_workspace/api/users.py +++ b/authentik/enterprise/providers/google_workspace/api/users.py @@ -19,6 +19,7 @@ class GoogleWorkspaceProviderUserSerializer(ModelSerializer): model = GoogleWorkspaceProviderUser fields = [ "id", + "google_id", "user", "user_obj", "provider", diff --git a/authentik/enterprise/providers/microsoft_entra/api/groups.py b/authentik/enterprise/providers/microsoft_entra/api/groups.py index afb7606b1c..6be035e4c2 100644 --- a/authentik/enterprise/providers/microsoft_entra/api/groups.py +++ b/authentik/enterprise/providers/microsoft_entra/api/groups.py @@ -19,6 +19,7 @@ class MicrosoftEntraProviderGroupSerializer(ModelSerializer): model = MicrosoftEntraProviderGroup fields = [ "id", + "microsoft_id", "group", "group_obj", "provider", diff --git a/authentik/enterprise/providers/microsoft_entra/api/users.py b/authentik/enterprise/providers/microsoft_entra/api/users.py index 65251a11fb..da0203ed94 100644 --- a/authentik/enterprise/providers/microsoft_entra/api/users.py +++ b/authentik/enterprise/providers/microsoft_entra/api/users.py @@ -19,6 +19,7 @@ class MicrosoftEntraProviderUserSerializer(ModelSerializer): model = MicrosoftEntraProviderUser fields = [ "id", + "microsoft_id", "user", "user_obj", "provider", diff --git a/authentik/lib/logging.py b/authentik/lib/logging.py index 60309658c4..0ffb74530e 100644 --- a/authentik/lib/logging.py +++ b/authentik/lib/logging.py @@ -102,6 +102,8 @@ def get_logger_config(): "gunicorn": "INFO", "requests_mock": "WARNING", "hpack": "WARNING", + "httpx": "WARNING", + "azure": "WARNING", } for handler_name, level in handler_level_map.items(): base_config["loggers"][handler_name] = { diff --git a/authentik/lib/sync/mapper.py b/authentik/lib/sync/mapper.py index 18211a1104..806dd8cd63 100644 --- a/authentik/lib/sync/mapper.py +++ b/authentik/lib/sync/mapper.py @@ -57,6 +57,8 @@ class PropertyMappingManager: mapping.set_context(user, request, **kwargs) try: value = mapping.evaluate(mapping.model.expression) + except PropertyMappingExpressionException as exc: + raise exc from exc except Exception as exc: raise PropertyMappingExpressionException(exc, mapping.model) from exc if value is None: diff --git a/authentik/lib/sync/outgoing/base.py b/authentik/lib/sync/outgoing/base.py index f6a1ca3738..52e7da145b 100644 --- a/authentik/lib/sync/outgoing/base.py +++ b/authentik/lib/sync/outgoing/base.py @@ -91,10 +91,9 @@ class BaseOutgoingSyncClient[ } eval_kwargs.setdefault("user", None) for value in self.mapper.iter_eval(**eval_kwargs): - try: - always_merger.merge(raw_final_object, value) - except SkipObjectException as exc: - raise exc from exc + always_merger.merge(raw_final_object, value) + except SkipObjectException as exc: + raise exc from exc except PropertyMappingExpressionException as exc: # Value error can be raised when assigning invalid data to an attribute Event.new( @@ -104,7 +103,7 @@ class BaseOutgoingSyncClient[ ).save() raise StopSync(exc, obj, exc.mapping) from exc 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(): raw_final_object.setdefault(key, value) return raw_final_object diff --git a/authentik/lib/sync/outgoing/tasks.py b/authentik/lib/sync/outgoing/tasks.py index 80f26c4883..f6083f9b15 100644 --- a/authentik/lib/sync/outgoing/tasks.py +++ b/authentik/lib/sync/outgoing/tasks.py @@ -125,6 +125,7 @@ class SyncTasks: try: client.write(obj) except SkipObjectException: + self.logger.debug("skipping object due to SkipObject", obj=obj) continue except BadRequestSyncException as exc: self.logger.warning("failed to sync object", exc=exc, obj=obj) diff --git a/authentik/policies/event_matcher/models.py b/authentik/policies/event_matcher/models.py index 78af38d5cf..30735386df 100644 --- a/authentik/policies/event_matcher/models.py +++ b/authentik/policies/event_matcher/models.py @@ -102,7 +102,7 @@ class EventMatcherPolicy(Policy): result = checker(request, event) if result is None: continue - LOGGER.info( + LOGGER.debug( "Event matcher check result", checker=checker.__name__, result=result, diff --git a/authentik/providers/scim/api/groups.py b/authentik/providers/scim/api/groups.py new file mode 100644 index 0000000000..f9fd697e47 --- /dev/null +++ b/authentik/providers/scim/api/groups.py @@ -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"] diff --git a/authentik/providers/scim/api/users.py b/authentik/providers/scim/api/users.py new file mode 100644 index 0000000000..632421f024 --- /dev/null +++ b/authentik/providers/scim/api/users.py @@ -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"] diff --git a/authentik/providers/scim/clients/groups.py b/authentik/providers/scim/clients/groups.py index 363945f5cc..b1dc657dcb 100644 --- a/authentik/providers/scim/clients/groups.py +++ b/authentik/providers/scim/clients/groups.py @@ -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 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""" - connection_type = SCIMGroup + connection_type = SCIMProviderGroup connection_type_query = "group" mapper: PropertyMappingManager @@ -37,7 +42,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroup, SCIMGroupSchema]): ["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""" raw_scim_group = super().to_schema( obj, @@ -52,7 +57,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroup, SCIMGroupSchema]): scim_group.externalId = str(obj.pk) 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 = [] for user in connections: members.append( @@ -66,7 +71,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroup, SCIMGroupSchema]): def delete(self, obj: 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: self.logger.debug("Group does not exist in SCIM, skipping") return None @@ -88,9 +93,11 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroup, SCIMGroupSchema]): scim_id = response.get("id") if not scim_id or scim_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""" scim_group = self.to_schema(group, connection) scim_group.id = connection.scim_id @@ -158,16 +165,16 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroup, SCIMGroupSchema]): """Add users in users_set to group""" if len(users_set) < 1: 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: self.logger.warning( "could not sync group membership, group does not exist", group=group ) return user_ids = list( - SCIMUser.objects.filter(user__pk__in=users_set, provider=self.provider).values_list( - "scim_id", flat=True - ) + SCIMProviderUser.objects.filter( + user__pk__in=users_set, provider=self.provider + ).values_list("scim_id", flat=True) ) if len(user_ids) < 1: return @@ -184,16 +191,16 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroup, SCIMGroupSchema]): """Remove users in users_set from group""" if len(users_set) < 1: 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: self.logger.warning( "could not sync group membership, group does not exist", group=group ) return user_ids = list( - SCIMUser.objects.filter(user__pk__in=users_set, provider=self.provider).values_list( - "scim_id", flat=True - ) + SCIMProviderUser.objects.filter( + user__pk__in=users_set, provider=self.provider + ).values_list("scim_id", flat=True) ) if len(user_ids) < 1: return diff --git a/authentik/providers/scim/clients/users.py b/authentik/providers/scim/clients/users.py index f85c19bcd6..90ffe76abb 100644 --- a/authentik/providers/scim/clients/users.py +++ b/authentik/providers/scim/clients/users.py @@ -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.schema import SCIM_USER_SCHEMA 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""" - connection_type = SCIMUser + connection_type = SCIMProviderUser connection_type_query = "user" mapper: PropertyMappingManager @@ -27,7 +27,7 @@ class SCIMUserClient(SCIMClient[User, SCIMUser, SCIMUserSchema]): ["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""" raw_scim_user = super().to_schema( obj, @@ -44,7 +44,7 @@ class SCIMUserClient(SCIMClient[User, SCIMUser, SCIMUserSchema]): def delete(self, obj: 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: self.logger.debug("User does not exist in SCIM, skipping") return None @@ -66,9 +66,9 @@ class SCIMUserClient(SCIMClient[User, SCIMUser, SCIMUserSchema]): scim_id = response.get("id") if not scim_id or scim_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""" scim_user = self.to_schema(user, connection) scim_user.id = connection.scim_id diff --git a/authentik/providers/scim/migrations/0008_rename_scimgroup_scimprovidergroup_and_more.py b/authentik/providers/scim/migrations/0008_rename_scimgroup_scimprovidergroup_and_more.py new file mode 100644 index 0000000000..c5b299a30c --- /dev/null +++ b/authentik/providers/scim/migrations/0008_rename_scimgroup_scimprovidergroup_and_more.py @@ -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", + ), + ] diff --git a/authentik/providers/scim/models.py b/authentik/providers/scim/models.py index e7e8a0987c..735d3a1ca3 100644 --- a/authentik/providers/scim/models.py +++ b/authentik/providers/scim/models.py @@ -10,6 +10,7 @@ from django.utils.translation import gettext_lazy as _ from rest_framework.serializers import Serializer 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.models import OutgoingSyncProvider @@ -106,7 +107,7 @@ class SCIMMapping(PropertyMapping): verbose_name_plural = _("SCIM Mappings") -class SCIMUser(models.Model): +class SCIMProviderUser(SerializerModel): """Mapping of a user and provider to a SCIM user ID""" 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) 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: unique_together = (("scim_id", "user", "provider"),) 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""" 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) 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: unique_together = (("scim_id", "group", "provider"),) 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}" diff --git a/authentik/providers/scim/urls.py b/authentik/providers/scim/urls.py index 50ef596965..b692ae44ea 100644 --- a/authentik/providers/scim/urls.py +++ b/authentik/providers/scim/urls.py @@ -1,9 +1,17 @@ """API URLs""" +from authentik.providers.scim.api.groups import ( + SCIMProviderGroupViewSet, +) from authentik.providers.scim.api.property_mappings import SCIMMappingViewSet from authentik.providers.scim.api.providers import SCIMProviderViewSet +from authentik.providers.scim.api.users import ( + SCIMProviderUserViewSet, +) api_urlpatterns = [ ("providers/scim", SCIMProviderViewSet), + ("providers/scim_users", SCIMProviderUserViewSet), + ("providers/scim_groups", SCIMProviderGroupViewSet), ("propertymappings/scim", SCIMMappingViewSet), ] diff --git a/authentik/sources/ldap/sync/base.py b/authentik/sources/ldap/sync/base.py index 8435b80d5c..cde8fbe5b3 100644 --- a/authentik/sources/ldap/sync/base.py +++ b/authentik/sources/ldap/sync/base.py @@ -161,19 +161,18 @@ class BaseLDAPSynchronizer: dn=object_dn, source=self._source, ): - try: - if isinstance(value, (bytes)): - self._logger.warning("property mapping returned bytes", mapping=mapping) - continue - object_field = mapping.object_field - if object_field.startswith("attributes."): - # Because returning a list might desired, we can't - # rely on flatten here. Instead, just save the result as-is - set_path_in_dict(properties, object_field, value) - else: - properties[object_field] = flatten(value) - except SkipObjectException as exc: - raise exc from exc + if isinstance(value, (bytes)): + self._logger.warning("property mapping returned bytes", mapping=mapping) + continue + object_field = mapping.object_field + if object_field.startswith("attributes."): + # Because returning a list might desired, we can't + # rely on flatten here. Instead, just save the result as-is + set_path_in_dict(properties, object_field, value) + else: + properties[object_field] = flatten(value) + except SkipObjectException as exc: + raise exc from exc except PropertyMappingExpressionException as exc: # Value error can be raised when assigning invalid data to an attribute Event.new( diff --git a/schema.yml b/schema.yml index 1f47eccfcf..822318ad11 100644 --- a/schema.yml +++ b/schema.yml @@ -19726,6 +19726,403 @@ paths: schema: $ref: '#/components/schemas/GenericError' 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/: get: operationId: rac_connection_tokens_list @@ -36335,6 +36732,8 @@ components: type: string format: uuid readOnly: true + google_id: + type: string group: type: string format: uuid @@ -36348,6 +36747,7 @@ components: readOnly: true required: - attributes + - google_id - group - group_obj - id @@ -36356,12 +36756,16 @@ components: type: object description: GoogleWorkspaceProviderGroup Serializer properties: + google_id: + type: string + minLength: 1 group: type: string format: uuid provider: type: integer required: + - google_id - group - provider GoogleWorkspaceProviderMapping: @@ -36484,6 +36888,8 @@ components: type: string format: uuid readOnly: true + google_id: + type: string user: type: integer user_obj: @@ -36496,6 +36902,7 @@ components: readOnly: true required: - attributes + - google_id - id - provider - user @@ -36504,11 +36911,15 @@ components: type: object description: GoogleWorkspaceProviderUser Serializer properties: + google_id: + type: string + minLength: 1 user: type: integer provider: type: integer required: + - google_id - provider - user Group: @@ -38003,6 +38414,8 @@ components: type: string format: uuid readOnly: true + microsoft_id: + type: string group: type: string format: uuid @@ -38019,11 +38432,15 @@ components: - group - group_obj - id + - microsoft_id - provider MicrosoftEntraProviderGroupRequest: type: object description: MicrosoftEntraProviderGroup Serializer properties: + microsoft_id: + type: string + minLength: 1 group: type: string format: uuid @@ -38031,6 +38448,7 @@ components: type: integer required: - group + - microsoft_id - provider MicrosoftEntraProviderMapping: type: object @@ -38149,6 +38567,8 @@ components: type: string format: uuid readOnly: true + microsoft_id: + type: string user: type: integer user_obj: @@ -38162,6 +38582,7 @@ components: required: - attributes - id + - microsoft_id - provider - user - user_obj @@ -38169,11 +38590,15 @@ components: type: object description: MicrosoftEntraProviderUser Serializer properties: + microsoft_id: + type: string + minLength: 1 user: type: integer provider: type: integer required: + - microsoft_id - provider - user ModelEnum: @@ -40166,6 +40591,18 @@ components: required: - pagination - results + PaginatedSCIMProviderGroupList: + type: object + properties: + pagination: + $ref: '#/components/schemas/Pagination' + results: + type: array + items: + $ref: '#/components/schemas/SCIMProviderGroup' + required: + - pagination + - results PaginatedSCIMProviderList: type: object properties: @@ -40178,6 +40615,18 @@ components: required: - pagination - results + PaginatedSCIMProviderUserList: + type: object + properties: + pagination: + $ref: '#/components/schemas/Pagination' + results: + type: array + items: + $ref: '#/components/schemas/SCIMProviderUser' + required: + - pagination + - results PaginatedSCIMSourceGroupList: type: object properties: @@ -44147,12 +44596,14 @@ components: properties: user: type: integer + nullable: true context: type: object additionalProperties: {} group: type: string format: uuid + nullable: true PropertyMappingTestResult: type: object description: Result of a Property-mapping test @@ -45906,6 +46357,47 @@ components: - url - verbose_name - 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: type: object description: SCIMProvider Serializer @@ -45942,6 +46434,45 @@ components: - name - token - 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: type: object description: SCIMSource Serializer diff --git a/web/src/admin/property-mappings/PropertyMappingTestForm.ts b/web/src/admin/property-mappings/PropertyMappingTestForm.ts index ff348f5530..dff9865ad4 100644 --- a/web/src/admin/property-mappings/PropertyMappingTestForm.ts +++ b/web/src/admin/property-mappings/PropertyMappingTestForm.ts @@ -126,6 +126,7 @@ export class PolicyTestForm extends Form { renderForm(): TemplateResult { return html` => { const args: CoreUsersListRequest = { ordering: "username", @@ -153,6 +154,7 @@ export class PolicyTestForm extends Form { => { const args: CoreGroupsListRequest = { ordering: "name", diff --git a/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderGroupList.ts b/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderGroupList.ts index cd3509ec3b..cb6ba6794d 100644 --- a/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderGroupList.ts +++ b/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderGroupList.ts @@ -1,5 +1,6 @@ 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"; @@ -19,6 +20,26 @@ export class GoogleWorkspaceProviderGroupList extends Table { + return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceGroupsDestroy({ + id: item.id, + }); + }} + > + + `; + } + async apiEndpoint(page: number): Promise> { return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceGroupsList({ page: page, diff --git a/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderUserList.ts b/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderUserList.ts index 99ad822784..7312f7ea16 100644 --- a/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderUserList.ts +++ b/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderUserList.ts @@ -1,5 +1,6 @@ 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"; @@ -19,6 +20,26 @@ export class GoogleWorkspaceProviderUserList extends Table { + return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceUsersDestroy({ + id: item.id, + }); + }} + > + + `; + } + async apiEndpoint(page: number): Promise> { return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceUsersList({ page: page, diff --git a/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderGroupList.ts b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderGroupList.ts index 809d69914c..a77298b8ef 100644 --- a/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderGroupList.ts +++ b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderGroupList.ts @@ -1,5 +1,6 @@ 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"; @@ -19,6 +20,23 @@ export class MicrosoftEntraProviderGroupList extends Table { + return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraGroupsDestroy({ + id: item.id, + }); + }} + > + + `; + } + async apiEndpoint(page: number): Promise> { return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraGroupsList({ page: page, diff --git a/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderUserList.ts b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderUserList.ts index 7c7aa70b41..f65da7c522 100644 --- a/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderUserList.ts +++ b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderUserList.ts @@ -1,5 +1,6 @@ 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"; @@ -19,6 +20,26 @@ export class MicrosoftEntraProviderUserList extends Table { + return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraUsersDestroy({ + id: item.id, + }); + }} + > + + `; + } + async apiEndpoint(page: number): Promise> { return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraUsersList({ page: page, diff --git a/web/src/admin/providers/scim/SCIMProviderGroupList.ts b/web/src/admin/providers/scim/SCIMProviderGroupList.ts new file mode 100644 index 0000000000..a7effbc387 --- /dev/null +++ b/web/src/admin/providers/scim/SCIMProviderGroupList.ts @@ -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 { + @property({ type: Number }) + providerId?: number; + + searchEnabled(): boolean { + return true; + } + + checkbox = true; + clearOnRefresh = true; + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return new ProvidersApi(DEFAULT_CONFIG).providersScimGroupsDestroy({ + id: item.id, + }); + }} + > + + `; + } + + async apiEndpoint(page: number): Promise> { + 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` +
${item.groupObj.name}
+
`, + html`${item.id}`, + ]; + } +} diff --git a/web/src/admin/providers/scim/SCIMProviderUserList.ts b/web/src/admin/providers/scim/SCIMProviderUserList.ts new file mode 100644 index 0000000000..eee86c7ce0 --- /dev/null +++ b/web/src/admin/providers/scim/SCIMProviderUserList.ts @@ -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 { + @property({ type: Number }) + providerId?: number; + + searchEnabled(): boolean { + return true; + } + + checkbox = true; + clearOnRefresh = true; + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return new ProvidersApi(DEFAULT_CONFIG).providersScimUsersDestroy({ + id: item.id, + }); + }} + > + + `; + } + + async apiEndpoint(page: number): Promise> { + 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` +
${item.userObj.username}
+ ${item.userObj.name} +
`, + html`${item.id}`, + ]; + } +} diff --git a/web/src/admin/providers/scim/SCIMProviderViewPage.ts b/web/src/admin/providers/scim/SCIMProviderViewPage.ts index 5e852946b2..ddc4fe97e3 100644 --- a/web/src/admin/providers/scim/SCIMProviderViewPage.ts +++ b/web/src/admin/providers/scim/SCIMProviderViewPage.ts @@ -1,4 +1,6 @@ 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 { EVENT_REFRESH } from "@goauthentik/common/constants"; import "@goauthentik/components/events/ObjectChangelog"; @@ -102,6 +104,28 @@ export class SCIMProviderViewPage extends AKElement { +
+
+ +
+
+
+
+ +
+