diff --git a/authentik/sources/ldap/api/__init__.py b/authentik/sources/ldap/api/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/authentik/sources/ldap/api/connections.py b/authentik/sources/ldap/api/connections.py new file mode 100644 index 0000000000..b3ffca7d51 --- /dev/null +++ b/authentik/sources/ldap/api/connections.py @@ -0,0 +1,42 @@ +"""Source API Views""" + +from rest_framework.viewsets import ModelViewSet + +from authentik.core.api.groups import PartialUserSerializer +from authentik.core.api.sources import ( + GroupSourceConnectionSerializer, + GroupSourceConnectionViewSet, + UserSourceConnectionSerializer, + UserSourceConnectionViewSet, +) +from authentik.core.api.users import PartialGroupSerializer +from authentik.sources.ldap.models import ( + GroupLDAPSourceConnection, + UserLDAPSourceConnection, +) + + +class UserLDAPSourceConnectionSerializer(UserSourceConnectionSerializer): + user_obj = PartialUserSerializer(source="user", read_only=True) + + class Meta(UserSourceConnectionSerializer.Meta): + model = UserLDAPSourceConnection + fields = UserSourceConnectionSerializer.Meta.fields + ["user_obj"] + + +class UserLDAPSourceConnectionViewSet(UserSourceConnectionViewSet, ModelViewSet): + queryset = UserLDAPSourceConnection.objects.all() + serializer_class = UserLDAPSourceConnectionSerializer + + +class GroupLDAPSourceConnectionSerializer(GroupSourceConnectionSerializer): + group_obj = PartialGroupSerializer(source="group", read_only=True) + + class Meta(GroupSourceConnectionSerializer.Meta): + model = GroupLDAPSourceConnection + fields = GroupSourceConnectionSerializer.Meta.fields + ["group_obj"] + + +class GroupLDAPSourceConnectionViewSet(GroupSourceConnectionViewSet, ModelViewSet): + queryset = GroupLDAPSourceConnection.objects.all() + serializer_class = GroupLDAPSourceConnectionSerializer diff --git a/authentik/sources/ldap/api/property_mappings.py b/authentik/sources/ldap/api/property_mappings.py new file mode 100644 index 0000000000..62bf578fcc --- /dev/null +++ b/authentik/sources/ldap/api/property_mappings.py @@ -0,0 +1,32 @@ +from rest_framework.viewsets import ModelViewSet + +from authentik.core.api.property_mappings import PropertyMappingFilterSet, PropertyMappingSerializer +from authentik.core.api.used_by import UsedByMixin +from authentik.sources.ldap.models import ( + LDAPSourcePropertyMapping, +) + + +class LDAPSourcePropertyMappingSerializer(PropertyMappingSerializer): + """LDAP PropertyMapping Serializer""" + + class Meta: + model = LDAPSourcePropertyMapping + fields = PropertyMappingSerializer.Meta.fields + + +class LDAPSourcePropertyMappingFilter(PropertyMappingFilterSet): + """Filter for LDAPSourcePropertyMapping""" + + class Meta(PropertyMappingFilterSet.Meta): + model = LDAPSourcePropertyMapping + + +class LDAPSourcePropertyMappingViewSet(UsedByMixin, ModelViewSet): + """LDAP PropertyMapping Viewset""" + + queryset = LDAPSourcePropertyMapping.objects.all() + serializer_class = LDAPSourcePropertyMappingSerializer + filterset_class = LDAPSourcePropertyMappingFilter + search_fields = ["name"] + ordering = ["name"] diff --git a/authentik/sources/ldap/api.py b/authentik/sources/ldap/api/sources.py similarity index 80% rename from authentik/sources/ldap/api.py rename to authentik/sources/ldap/api/sources.py index 8d39c8c306..443bd2ee14 100644 --- a/authentik/sources/ldap/api.py +++ b/authentik/sources/ldap/api/sources.py @@ -13,23 +13,15 @@ from rest_framework.request import Request from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet -from authentik.core.api.property_mappings import PropertyMappingFilterSet, PropertyMappingSerializer from authentik.core.api.sources import ( - GroupSourceConnectionSerializer, - GroupSourceConnectionViewSet, SourceSerializer, - UserSourceConnectionSerializer, - UserSourceConnectionViewSet, ) from authentik.core.api.used_by import UsedByMixin from authentik.crypto.models import CertificateKeyPair from authentik.lib.sync.api import SyncStatusSerializer from authentik.rbac.filters import ObjectFilter from authentik.sources.ldap.models import ( - GroupLDAPSourceConnection, LDAPSource, - LDAPSourcePropertyMapping, - UserLDAPSourceConnection, ) from authentik.sources.ldap.tasks import CACHE_KEY_STATUS, SYNC_CLASSES, ldap_sync from authentik.tasks.models import Task, TaskStatus @@ -224,48 +216,3 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet): obj.pop("raw_dn", None) all_objects[class_name].append(obj) return Response(data=all_objects) - - -class LDAPSourcePropertyMappingSerializer(PropertyMappingSerializer): - """LDAP PropertyMapping Serializer""" - - class Meta: - model = LDAPSourcePropertyMapping - fields = PropertyMappingSerializer.Meta.fields - - -class LDAPSourcePropertyMappingFilter(PropertyMappingFilterSet): - """Filter for LDAPSourcePropertyMapping""" - - class Meta(PropertyMappingFilterSet.Meta): - model = LDAPSourcePropertyMapping - - -class LDAPSourcePropertyMappingViewSet(UsedByMixin, ModelViewSet): - """LDAP PropertyMapping Viewset""" - - queryset = LDAPSourcePropertyMapping.objects.all() - serializer_class = LDAPSourcePropertyMappingSerializer - filterset_class = LDAPSourcePropertyMappingFilter - search_fields = ["name"] - ordering = ["name"] - - -class UserLDAPSourceConnectionSerializer(UserSourceConnectionSerializer): - class Meta(UserSourceConnectionSerializer.Meta): - model = UserLDAPSourceConnection - - -class UserLDAPSourceConnectionViewSet(UserSourceConnectionViewSet, ModelViewSet): - queryset = UserLDAPSourceConnection.objects.all() - serializer_class = UserLDAPSourceConnectionSerializer - - -class GroupLDAPSourceConnectionSerializer(GroupSourceConnectionSerializer): - class Meta(GroupSourceConnectionSerializer.Meta): - model = GroupLDAPSourceConnection - - -class GroupLDAPSourceConnectionViewSet(GroupSourceConnectionViewSet, ModelViewSet): - queryset = GroupLDAPSourceConnection.objects.all() - serializer_class = GroupLDAPSourceConnectionSerializer diff --git a/authentik/sources/ldap/models.py b/authentik/sources/ldap/models.py index 1e83982eb4..f7fe889a97 100644 --- a/authentik/sources/ldap/models.py +++ b/authentik/sources/ldap/models.py @@ -31,6 +31,7 @@ from authentik.tasks.schedules.common import ScheduleSpec LDAP_TIMEOUT = 15 LDAP_UNIQUENESS = "ldap_uniq" +"""Deprecated, don't use""" LDAP_DISTINGUISHED_NAME = "distinguishedName" LOGGER = get_logger() @@ -159,7 +160,7 @@ class LDAPSource(IncomingSyncSource): @property def serializer(self) -> type[Serializer]: - from authentik.sources.ldap.api import LDAPSourceSerializer + from authentik.sources.ldap.api.sources import LDAPSourceSerializer return LDAPSourceSerializer @@ -192,6 +193,7 @@ class LDAPSource(IncomingSyncSource): def update_properties_with_uniqueness_field(self, properties, dn, ldap, **kwargs): properties.setdefault("attributes", {})[LDAP_DISTINGUISHED_NAME] = dn + # TODO: Remove after 2026.5, still stored for legacy if self.object_uniqueness_field in ldap: properties["attributes"][LDAP_UNIQUENESS] = flatten( ldap.get(self.object_uniqueness_field) @@ -356,7 +358,7 @@ class LDAPSourcePropertyMapping(PropertyMapping): @property def serializer(self) -> type[Serializer]: - from authentik.sources.ldap.api import LDAPSourcePropertyMappingSerializer + from authentik.sources.ldap.api.property_mappings import LDAPSourcePropertyMappingSerializer return LDAPSourcePropertyMappingSerializer @@ -377,7 +379,7 @@ class UserLDAPSourceConnection(UserSourceConnection): @property def serializer(self) -> type[Serializer]: - from authentik.sources.ldap.api import ( + from authentik.sources.ldap.api.connections import ( UserLDAPSourceConnectionSerializer, ) @@ -400,7 +402,7 @@ class GroupLDAPSourceConnection(GroupSourceConnection): @property def serializer(self) -> type[Serializer]: - from authentik.sources.ldap.api import ( + from authentik.sources.ldap.api.connections import ( GroupLDAPSourceConnectionSerializer, ) diff --git a/authentik/sources/ldap/sync/base.py b/authentik/sources/ldap/sync/base.py index 931313caed..650b711edc 100644 --- a/authentik/sources/ldap/sync/base.py +++ b/authentik/sources/ldap/sync/base.py @@ -7,9 +7,15 @@ from ldap3 import DEREF_ALWAYS, SUBTREE, Connection from structlog.stdlib import BoundLogger, get_logger from authentik.core.sources.mapper import SourceMapper +from authentik.core.sources.matcher import SourceMatcher from authentik.lib.config import CONFIG from authentik.lib.sync.mapper import PropertyMappingManager -from authentik.sources.ldap.models import LDAPSource, flatten +from authentik.sources.ldap.models import ( + GroupLDAPSourceConnection, + LDAPSource, + UserLDAPSourceConnection, + flatten, +) from authentik.tasks.models import Task @@ -28,6 +34,9 @@ class BaseLDAPSynchronizer: self._task = task self._connection = source.connection() self._logger = get_logger().bind(source=source, syncer=self.__class__.__name__) + self.matcher = SourceMatcher( + self._source, UserLDAPSourceConnection, GroupLDAPSourceConnection + ) @staticmethod def name() -> str: diff --git a/authentik/sources/ldap/sync/groups.py b/authentik/sources/ldap/sync/groups.py index 9382cd4d38..c250817fdd 100644 --- a/authentik/sources/ldap/sync/groups.py +++ b/authentik/sources/ldap/sync/groups.py @@ -12,8 +12,10 @@ from authentik.core.expression.exceptions import ( ) from authentik.core.models import Group from authentik.core.sources.mapper import SourceMapper +from authentik.core.sources.matcher import Action from authentik.events.models import Event, EventAction from authentik.lib.sync.outgoing.exceptions import StopSync +from authentik.lib.utils.errors import exception_to_dict from authentik.sources.ldap.models import ( LDAP_UNIQUENESS, GroupLDAPSourceConnection, @@ -88,33 +90,55 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer): if "users" in defaults: del defaults["users"] parent = defaults.pop("parent", None) - group, created = Group.update_or_create_attributes( - { - f"attributes__{LDAP_UNIQUENESS}": uniq, - }, - defaults, - ) + action, connection = self.matcher.get_group_action(uniq, defaults) + + created = False + if action == Action.ENROLL: + # Legacy fallback, in case the group only has an `ldap_uniq` attribute set, but + # no source connection exists yet + legacy_group = Group.objects.filter( + **{ + f"attributes__{LDAP_UNIQUENESS}": uniq, + } + ).first() + if legacy_group and LDAP_UNIQUENESS in legacy_group.attributes: + connection = GroupLDAPSourceConnection( + source=self._source, + group=legacy_group, + identifier=legacy_group.attributes.get(LDAP_UNIQUENESS), + ) + group = legacy_group + # Switch the action to update the attributes + action = Action.AUTH + else: + group = Group.objects.create(**defaults) + created = True + connection.group = group + connection.save() + + if action in (Action.AUTH, Action.LINK): + group = connection.group + group.update_attributes(defaults) + elif action == Action.DENY: + continue + if parent: group.parents.add(parent) self._logger.debug("Created group with attributes", **defaults) - if not GroupLDAPSourceConnection.objects.filter( - source=self._source, identifier=uniq - ): - GroupLDAPSourceConnection.objects.create( - source=self._source, group=group, identifier=uniq - ) except SkipObjectException: continue except PropertyMappingExpressionException as exc: raise StopSync(exc, None, exc.mapping) from exc except (IntegrityError, FieldError, TypeError, AttributeError) as exc: + self._logger.debug("failed to create group", exc=exc) Event.new( EventAction.CONFIGURATION_ERROR, message=( - f"Failed to create group: {str(exc)} " - "To merge new group with existing group, set the groups's " - f"Attribute '{LDAP_UNIQUENESS}' to '{uniq}'" + "Failed to create group; " + "To merge new group with existing group, connect it via the LDAP Source's " + "'Synced Groups' tab." ), + exception=exception_to_dict(exc), source=self._source, dn=group_dn, ).save() diff --git a/authentik/sources/ldap/sync/membership.py b/authentik/sources/ldap/sync/membership.py index 9fcb3cae6e..7b4fd8df12 100644 --- a/authentik/sources/ldap/sync/membership.py +++ b/authentik/sources/ldap/sync/membership.py @@ -8,7 +8,11 @@ from ldap3 import SUBTREE from ldap3.utils.conv import escape_filter_chars from authentik.core.models import Group, User -from authentik.sources.ldap.models import LDAP_DISTINGUISHED_NAME, LDAP_UNIQUENESS, LDAPSource +from authentik.sources.ldap.models import ( + LDAP_DISTINGUISHED_NAME, + GroupLDAPSourceConnection, + LDAPSource, +) from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer from authentik.tasks.models import Task @@ -104,7 +108,9 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer): return None group_uniq = group_uniq[0] if group_uniq not in self.group_cache: - groups = Group.objects.filter(**{f"attributes__{LDAP_UNIQUENESS}": group_uniq}) + groups = GroupLDAPSourceConnection.objects.filter(identifier=group_uniq).select_related( + "group" + ) if not groups.exists(): if self._source.sync_groups: self._task.info( @@ -112,5 +118,5 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer): group=group_dn, ) return None - self.group_cache[group_uniq] = groups.first() + self.group_cache[group_uniq] = groups.first().group return self.group_cache[group_uniq] diff --git a/authentik/sources/ldap/sync/users.py b/authentik/sources/ldap/sync/users.py index a23e456f1a..6c6b3ac439 100644 --- a/authentik/sources/ldap/sync/users.py +++ b/authentik/sources/ldap/sync/users.py @@ -12,8 +12,10 @@ from authentik.core.expression.exceptions import ( ) from authentik.core.models import User from authentik.core.sources.mapper import SourceMapper +from authentik.core.sources.matcher import Action from authentik.events.models import Event, EventAction from authentik.lib.sync.outgoing.exceptions import StopSync +from authentik.lib.utils.errors import exception_to_dict from authentik.sources.ldap.models import ( LDAP_UNIQUENESS, LDAPSource, @@ -86,27 +88,50 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer): self._logger.debug("Writing user with attributes", **defaults) if "username" not in defaults: raise IntegrityError("Username was not set by propertymappings") - ak_user, created = User.update_or_create_attributes( - {f"attributes__{LDAP_UNIQUENESS}": uniq}, defaults - ) - if not UserLDAPSourceConnection.objects.filter( - source=self._source, identifier=uniq - ): - UserLDAPSourceConnection.objects.create( - source=self._source, user=ak_user, identifier=uniq - ) + action, connection = self.matcher.get_user_action(uniq, defaults) + created = False + if action == Action.ENROLL: + # Legacy fallback, in case the user only has an `ldap_uniq` attribute set, but + # no source connection exists yet + legacy_user = User.objects.filter( + **{ + f"attributes__{LDAP_UNIQUENESS}": uniq, + } + ).first() + if legacy_user and LDAP_UNIQUENESS in legacy_user.attributes: + connection = UserLDAPSourceConnection( + source=self._source, + user=legacy_user, + identifier=legacy_user.attributes.get(LDAP_UNIQUENESS), + ) + ak_user = legacy_user + # Switch the action to update the attributes + action = Action.AUTH + else: + ak_user = User.objects.create(**defaults) + created = True + connection.user = ak_user + connection.save() + + if action in (Action.AUTH, Action.LINK): + ak_user = connection.user + ak_user.update_attributes(defaults) + elif action == Action.DENY: + continue except PropertyMappingExpressionException as exc: raise StopSync(exc, None, exc.mapping) from exc except SkipObjectException: continue except (IntegrityError, FieldError, TypeError, AttributeError) as exc: + self._logger.debug("failed to create user", exc=exc) Event.new( EventAction.CONFIGURATION_ERROR, message=( - f"Failed to create user: {str(exc)} " - "To merge new user with existing user, set the user's " - f"Attribute '{LDAP_UNIQUENESS}' to '{uniq}'" + "Failed to create user; " + "To merge new user with existing user, connect it via the LDAP Source's " + "'Synced Users' tab." ), + exception=exception_to_dict(exc), source=self._source, dn=user_dn, ).save() diff --git a/authentik/sources/ldap/tests/test_api.py b/authentik/sources/ldap/tests/test_api.py index 34f327344a..a6fdf3c373 100644 --- a/authentik/sources/ldap/tests/test_api.py +++ b/authentik/sources/ldap/tests/test_api.py @@ -11,7 +11,7 @@ from rest_framework.test import APITestCase from authentik.blueprints.tests import apply_blueprint from authentik.core.tests.utils import create_test_admin_user from authentik.lib.generators import generate_id -from authentik.sources.ldap.api import LDAPSourceSerializer +from authentik.sources.ldap.api.sources import LDAPSourceSerializer from authentik.sources.ldap.models import LDAPSource, LDAPSourcePropertyMapping from authentik.sources.ldap.tests.mock_ad import mock_ad_connection diff --git a/authentik/sources/ldap/tests/test_sync.py b/authentik/sources/ldap/tests/test_sync.py index 8864abb854..83e10cff55 100644 --- a/authentik/sources/ldap/tests/test_sync.py +++ b/authentik/sources/ldap/tests/test_sync.py @@ -130,10 +130,14 @@ class LDAPSyncTests(TestCase): user = User.objects.create( username="erin.h", attributes={ - "ldap_uniq": "S-1-5-21-1955698215-2946288202-2760262721-1114", "foo": "bar", }, ) + UserLDAPSourceConnection.objects.create( + user=user, + source=self.source, + identifier="S-1-5-21-1955698215-2946288202-2760262721-1114", + ) with patch("authentik.sources.ldap.models.LDAPSource.connection", connection): user_sync = UserLDAPSynchronizer(self.source, Task()) @@ -149,6 +153,70 @@ class LDAPSyncTests(TestCase): self.assertIsNotNone(deactivated) self.assertFalse(deactivated.is_active) + def test_sync_ad_legacy(self): + """Test user sync""" + self.source.base_dn = "dc=t,dc=goauthentik,dc=io" + self.source.additional_user_dn = "" + self.source.additional_group_dn = "" + self.source.save() + self.source.user_property_mappings.set( + LDAPSourcePropertyMapping.objects.filter( + Q(managed__startswith="goauthentik.io/sources/ldap/default") + | Q(managed__startswith="goauthentik.io/sources/ldap/ms") + ) + ) + self.source.group_property_mappings.set( + LDAPSourcePropertyMapping.objects.filter( + managed="goauthentik.io/sources/ldap/default-name" + ) + ) + connection = MagicMock(return_value=mock_ad_connection()) + + # Create the user beforehand so we can set attributes and check they aren't removed + user = User.objects.create( + username="erin.h", + attributes={ + "ldap_uniq": "S-1-5-21-1955698215-2946288202-2760262721-1114", + "foo": "bar", + }, + ) + group = Group.objects.create( + name="Administrators", attributes={"ldap_uniq": "S-1-5-32-544", "foo": "bar"} + ) + + with patch("authentik.sources.ldap.models.LDAPSource.connection", connection): + user_sync = UserLDAPSynchronizer(self.source, Task()) + user_sync.sync_full() + group_sync = GroupLDAPSynchronizer(self.source, Task()) + group_sync.sync_full() + + user.refresh_from_db() + group.refresh_from_db() + + self.assertEqual(user.name, "Erin M. Hagens") + self.assertEqual(user.attributes["foo"], "bar") + self.assertTrue(user.is_active) + self.assertEqual(user.path, "goauthentik.io/sources/ldap/ak-test") + self.assertTrue( + UserLDAPSourceConnection.objects.filter( + source=self.source, + user=user, + identifier="S-1-5-21-1955698215-2946288202-2760262721-1114", + ).exists() + ) + + deactivated = User.objects.filter(username="deactivated.a").first() + self.assertIsNotNone(deactivated) + self.assertFalse(deactivated.is_active) + + self.assertEqual(group.name, "Administrators") + self.assertTrue( + GroupLDAPSourceConnection.objects.filter( + source=self.source, group=group, identifier="S-1-5-32-544" + ).exists() + ) + self.assertEqual(group.attributes["foo"], "bar") + def test_sync_users_openldap(self): """Test user sync""" self.source.object_uniqueness_field = "uid" diff --git a/authentik/sources/ldap/urls.py b/authentik/sources/ldap/urls.py index bd5cbcfd3f..e35f07e32f 100644 --- a/authentik/sources/ldap/urls.py +++ b/authentik/sources/ldap/urls.py @@ -1,11 +1,11 @@ """API URLs""" -from authentik.sources.ldap.api import ( +from authentik.sources.ldap.api.connections import ( GroupLDAPSourceConnectionViewSet, - LDAPSourcePropertyMappingViewSet, - LDAPSourceViewSet, UserLDAPSourceConnectionViewSet, ) +from authentik.sources.ldap.api.property_mappings import LDAPSourcePropertyMappingViewSet +from authentik.sources.ldap.api.sources import LDAPSourceViewSet api_urlpatterns = [ ("propertymappings/source/ldap", LDAPSourcePropertyMappingViewSet), diff --git a/packages/client-go/model_group_ldap_source_connection.go b/packages/client-go/model_group_ldap_source_connection.go index 4fd80ca628..d2e63eb84f 100644 --- a/packages/client-go/model_group_ldap_source_connection.go +++ b/packages/client-go/model_group_ldap_source_connection.go @@ -22,13 +22,14 @@ var _ MappedNullable = &GroupLDAPSourceConnection{} // GroupLDAPSourceConnection Group Source Connection type GroupLDAPSourceConnection struct { - Pk int32 `json:"pk"` - Group string `json:"group"` - Source string `json:"source"` - SourceObj Source `json:"source_obj"` - Identifier string `json:"identifier"` - Created time.Time `json:"created"` - LastUpdated time.Time `json:"last_updated"` + Pk int32 `json:"pk"` + Group string `json:"group"` + Source string `json:"source"` + SourceObj Source `json:"source_obj"` + Identifier string `json:"identifier"` + Created time.Time `json:"created"` + LastUpdated time.Time `json:"last_updated"` + GroupObj PartialGroup `json:"group_obj"` AdditionalProperties map[string]interface{} } @@ -38,7 +39,7 @@ type _GroupLDAPSourceConnection GroupLDAPSourceConnection // This constructor will assign default values to properties that have it defined, // and makes sure properties required by API are set, but the set of arguments // will change when the set of required properties is changed -func NewGroupLDAPSourceConnection(pk int32, group string, source string, sourceObj Source, identifier string, created time.Time, lastUpdated time.Time) *GroupLDAPSourceConnection { +func NewGroupLDAPSourceConnection(pk int32, group string, source string, sourceObj Source, identifier string, created time.Time, lastUpdated time.Time, groupObj PartialGroup) *GroupLDAPSourceConnection { this := GroupLDAPSourceConnection{} this.Pk = pk this.Group = group @@ -47,6 +48,7 @@ func NewGroupLDAPSourceConnection(pk int32, group string, source string, sourceO this.Identifier = identifier this.Created = created this.LastUpdated = lastUpdated + this.GroupObj = groupObj return &this } @@ -226,6 +228,30 @@ func (o *GroupLDAPSourceConnection) SetLastUpdated(v time.Time) { o.LastUpdated = v } +// GetGroupObj returns the GroupObj field value +func (o *GroupLDAPSourceConnection) GetGroupObj() PartialGroup { + if o == nil { + var ret PartialGroup + return ret + } + + return o.GroupObj +} + +// GetGroupObjOk returns a tuple with the GroupObj field value +// and a boolean to check if the value has been set. +func (o *GroupLDAPSourceConnection) GetGroupObjOk() (*PartialGroup, bool) { + if o == nil { + return nil, false + } + return &o.GroupObj, true +} + +// SetGroupObj sets field value +func (o *GroupLDAPSourceConnection) SetGroupObj(v PartialGroup) { + o.GroupObj = v +} + func (o GroupLDAPSourceConnection) MarshalJSON() ([]byte, error) { toSerialize, err := o.ToMap() if err != nil { @@ -243,6 +269,7 @@ func (o GroupLDAPSourceConnection) ToMap() (map[string]interface{}, error) { toSerialize["identifier"] = o.Identifier toSerialize["created"] = o.Created toSerialize["last_updated"] = o.LastUpdated + toSerialize["group_obj"] = o.GroupObj for key, value := range o.AdditionalProperties { toSerialize[key] = value @@ -263,6 +290,7 @@ func (o *GroupLDAPSourceConnection) UnmarshalJSON(data []byte) (err error) { "identifier", "created", "last_updated", + "group_obj", } allProperties := make(map[string]interface{}) @@ -299,6 +327,7 @@ func (o *GroupLDAPSourceConnection) UnmarshalJSON(data []byte) (err error) { delete(additionalProperties, "identifier") delete(additionalProperties, "created") delete(additionalProperties, "last_updated") + delete(additionalProperties, "group_obj") o.AdditionalProperties = additionalProperties } diff --git a/packages/client-go/model_user_ldap_source_connection.go b/packages/client-go/model_user_ldap_source_connection.go index 1dd7efa6b1..d6a243420a 100644 --- a/packages/client-go/model_user_ldap_source_connection.go +++ b/packages/client-go/model_user_ldap_source_connection.go @@ -22,13 +22,14 @@ var _ MappedNullable = &UserLDAPSourceConnection{} // UserLDAPSourceConnection User source connection type UserLDAPSourceConnection struct { - Pk int32 `json:"pk"` - User int32 `json:"user"` - Source string `json:"source"` - SourceObj Source `json:"source_obj"` - Identifier string `json:"identifier"` - Created time.Time `json:"created"` - LastUpdated time.Time `json:"last_updated"` + Pk int32 `json:"pk"` + User int32 `json:"user"` + Source string `json:"source"` + SourceObj Source `json:"source_obj"` + Identifier string `json:"identifier"` + Created time.Time `json:"created"` + LastUpdated time.Time `json:"last_updated"` + UserObj PartialUser `json:"user_obj"` AdditionalProperties map[string]interface{} } @@ -38,7 +39,7 @@ type _UserLDAPSourceConnection UserLDAPSourceConnection // This constructor will assign default values to properties that have it defined, // and makes sure properties required by API are set, but the set of arguments // will change when the set of required properties is changed -func NewUserLDAPSourceConnection(pk int32, user int32, source string, sourceObj Source, identifier string, created time.Time, lastUpdated time.Time) *UserLDAPSourceConnection { +func NewUserLDAPSourceConnection(pk int32, user int32, source string, sourceObj Source, identifier string, created time.Time, lastUpdated time.Time, userObj PartialUser) *UserLDAPSourceConnection { this := UserLDAPSourceConnection{} this.Pk = pk this.User = user @@ -47,6 +48,7 @@ func NewUserLDAPSourceConnection(pk int32, user int32, source string, sourceObj this.Identifier = identifier this.Created = created this.LastUpdated = lastUpdated + this.UserObj = userObj return &this } @@ -226,6 +228,30 @@ func (o *UserLDAPSourceConnection) SetLastUpdated(v time.Time) { o.LastUpdated = v } +// GetUserObj returns the UserObj field value +func (o *UserLDAPSourceConnection) GetUserObj() PartialUser { + if o == nil { + var ret PartialUser + return ret + } + + return o.UserObj +} + +// GetUserObjOk returns a tuple with the UserObj field value +// and a boolean to check if the value has been set. +func (o *UserLDAPSourceConnection) GetUserObjOk() (*PartialUser, bool) { + if o == nil { + return nil, false + } + return &o.UserObj, true +} + +// SetUserObj sets field value +func (o *UserLDAPSourceConnection) SetUserObj(v PartialUser) { + o.UserObj = v +} + func (o UserLDAPSourceConnection) MarshalJSON() ([]byte, error) { toSerialize, err := o.ToMap() if err != nil { @@ -243,6 +269,7 @@ func (o UserLDAPSourceConnection) ToMap() (map[string]interface{}, error) { toSerialize["identifier"] = o.Identifier toSerialize["created"] = o.Created toSerialize["last_updated"] = o.LastUpdated + toSerialize["user_obj"] = o.UserObj for key, value := range o.AdditionalProperties { toSerialize[key] = value @@ -263,6 +290,7 @@ func (o *UserLDAPSourceConnection) UnmarshalJSON(data []byte) (err error) { "identifier", "created", "last_updated", + "user_obj", } allProperties := make(map[string]interface{}) @@ -299,6 +327,7 @@ func (o *UserLDAPSourceConnection) UnmarshalJSON(data []byte) (err error) { delete(additionalProperties, "identifier") delete(additionalProperties, "created") delete(additionalProperties, "last_updated") + delete(additionalProperties, "user_obj") o.AdditionalProperties = additionalProperties } diff --git a/packages/client-rust/src/models/group_ldap_source_connection.rs b/packages/client-rust/src/models/group_ldap_source_connection.rs index fe324eb0ff..060798a6fa 100644 --- a/packages/client-rust/src/models/group_ldap_source_connection.rs +++ b/packages/client-rust/src/models/group_ldap_source_connection.rs @@ -27,6 +27,8 @@ pub struct GroupLdapSourceConnection { pub created: String, #[serde(rename = "last_updated")] pub last_updated: String, + #[serde(rename = "group_obj")] + pub group_obj: models::PartialGroup, } impl GroupLdapSourceConnection { @@ -39,6 +41,7 @@ impl GroupLdapSourceConnection { identifier: String, created: String, last_updated: String, + group_obj: models::PartialGroup, ) -> GroupLdapSourceConnection { GroupLdapSourceConnection { pk, @@ -48,6 +51,7 @@ impl GroupLdapSourceConnection { identifier, created, last_updated, + group_obj, } } } diff --git a/packages/client-rust/src/models/user_ldap_source_connection.rs b/packages/client-rust/src/models/user_ldap_source_connection.rs index 9a8ff08a2a..6e7139309d 100644 --- a/packages/client-rust/src/models/user_ldap_source_connection.rs +++ b/packages/client-rust/src/models/user_ldap_source_connection.rs @@ -27,6 +27,8 @@ pub struct UserLdapSourceConnection { pub created: String, #[serde(rename = "last_updated")] pub last_updated: String, + #[serde(rename = "user_obj")] + pub user_obj: models::PartialUser, } impl UserLdapSourceConnection { @@ -39,6 +41,7 @@ impl UserLdapSourceConnection { identifier: String, created: String, last_updated: String, + user_obj: models::PartialUser, ) -> UserLdapSourceConnection { UserLdapSourceConnection { pk, @@ -48,6 +51,7 @@ impl UserLdapSourceConnection { identifier, created, last_updated, + user_obj, } } } diff --git a/packages/client-ts/src/models/GroupLDAPSourceConnection.ts b/packages/client-ts/src/models/GroupLDAPSourceConnection.ts index c48805f377..19f8b5bd18 100644 --- a/packages/client-ts/src/models/GroupLDAPSourceConnection.ts +++ b/packages/client-ts/src/models/GroupLDAPSourceConnection.ts @@ -12,6 +12,8 @@ * Do not edit the class manually. */ +import type { PartialGroup } from "./PartialGroup"; +import { PartialGroupFromJSON } from "./PartialGroup"; import type { Source } from "./Source"; import { SourceFromJSON } from "./Source"; @@ -63,6 +65,12 @@ export interface GroupLDAPSourceConnection { * @memberof GroupLDAPSourceConnection */ readonly lastUpdated: Date; + /** + * + * @type {PartialGroup} + * @memberof GroupLDAPSourceConnection + */ + readonly groupObj: PartialGroup; } /** @@ -78,6 +86,7 @@ export function instanceOfGroupLDAPSourceConnection( if (!("identifier" in value) || value["identifier"] === undefined) return false; if (!("created" in value) || value["created"] === undefined) return false; if (!("lastUpdated" in value) || value["lastUpdated"] === undefined) return false; + if (!("groupObj" in value) || value["groupObj"] === undefined) return false; return true; } @@ -100,6 +109,7 @@ export function GroupLDAPSourceConnectionFromJSONTyped( identifier: json["identifier"], created: new Date(json["created"]), lastUpdated: new Date(json["last_updated"]), + groupObj: PartialGroupFromJSON(json["group_obj"]), }; } @@ -110,7 +120,7 @@ export function GroupLDAPSourceConnectionToJSON(json: any): GroupLDAPSourceConne export function GroupLDAPSourceConnectionToJSONTyped( value?: Omit< GroupLDAPSourceConnection, - "pk" | "source_obj" | "created" | "last_updated" + "pk" | "source_obj" | "created" | "last_updated" | "group_obj" > | null, ignoreDiscriminator: boolean = false, ): any { diff --git a/packages/client-ts/src/models/UserLDAPSourceConnection.ts b/packages/client-ts/src/models/UserLDAPSourceConnection.ts index b8c5d61664..ecad9ef54d 100644 --- a/packages/client-ts/src/models/UserLDAPSourceConnection.ts +++ b/packages/client-ts/src/models/UserLDAPSourceConnection.ts @@ -12,6 +12,8 @@ * Do not edit the class manually. */ +import type { PartialUser } from "./PartialUser"; +import { PartialUserFromJSON } from "./PartialUser"; import type { Source } from "./Source"; import { SourceFromJSON } from "./Source"; @@ -63,6 +65,12 @@ export interface UserLDAPSourceConnection { * @memberof UserLDAPSourceConnection */ readonly lastUpdated: Date; + /** + * + * @type {PartialUser} + * @memberof UserLDAPSourceConnection + */ + readonly userObj: PartialUser; } /** @@ -78,6 +86,7 @@ export function instanceOfUserLDAPSourceConnection( if (!("identifier" in value) || value["identifier"] === undefined) return false; if (!("created" in value) || value["created"] === undefined) return false; if (!("lastUpdated" in value) || value["lastUpdated"] === undefined) return false; + if (!("userObj" in value) || value["userObj"] === undefined) return false; return true; } @@ -100,6 +109,7 @@ export function UserLDAPSourceConnectionFromJSONTyped( identifier: json["identifier"], created: new Date(json["created"]), lastUpdated: new Date(json["last_updated"]), + userObj: PartialUserFromJSON(json["user_obj"]), }; } @@ -108,7 +118,10 @@ export function UserLDAPSourceConnectionToJSON(json: any): UserLDAPSourceConnect } export function UserLDAPSourceConnectionToJSONTyped( - value?: Omit | null, + value?: Omit< + UserLDAPSourceConnection, + "pk" | "source_obj" | "created" | "last_updated" | "user_obj" + > | null, ignoreDiscriminator: boolean = false, ): any { if (value == null) { diff --git a/schema.yml b/schema.yml index da79798e0f..a0908c45e4 100644 --- a/schema.yml +++ b/schema.yml @@ -39876,9 +39876,14 @@ components: type: string format: date-time readOnly: true + group_obj: + allOf: + - $ref: '#/components/schemas/PartialGroup' + readOnly: true required: - created - group + - group_obj - identifier - last_updated - pk @@ -56960,6 +56965,10 @@ components: type: string format: date-time readOnly: true + user_obj: + allOf: + - $ref: '#/components/schemas/PartialUser' + readOnly: true required: - created - identifier @@ -56968,6 +56977,7 @@ components: - source - source_obj - user + - user_obj UserLDAPSourceConnectionRequest: type: object description: User source connection diff --git a/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderGroupList.ts b/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderGroupList.ts index d389982a3e..5767916ec7 100644 --- a/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderGroupList.ts +++ b/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderGroupList.ts @@ -1,7 +1,6 @@ import "#elements/forms/DeleteBulkForm"; import "#elements/forms/ModalForm"; import "#elements/sync/SyncObjectForm"; -import "#admin/common/ak-flow-search/ak-flow-search-no-default"; import { DEFAULT_CONFIG } from "#common/api/config"; diff --git a/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderUserList.ts b/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderUserList.ts index 4982fca631..c9071e7d1b 100644 --- a/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderUserList.ts +++ b/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderUserList.ts @@ -1,7 +1,6 @@ import "#elements/forms/DeleteBulkForm"; import "#elements/forms/ModalForm"; import "#elements/sync/SyncObjectForm"; -import "#admin/common/ak-flow-search/ak-flow-search-no-default"; import { DEFAULT_CONFIG } from "#common/api/config"; diff --git a/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderGroupList.ts b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderGroupList.ts index 6618f38d6e..903d2be696 100644 --- a/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderGroupList.ts +++ b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderGroupList.ts @@ -1,7 +1,6 @@ import "#elements/forms/DeleteBulkForm"; import "#elements/forms/ModalForm"; import "#elements/sync/SyncObjectForm"; -import "#admin/common/ak-flow-search/ak-flow-search-no-default"; import { DEFAULT_CONFIG } from "#common/api/config"; diff --git a/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderUserList.ts b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderUserList.ts index 5253f0535d..b486c291b4 100644 --- a/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderUserList.ts +++ b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderUserList.ts @@ -1,7 +1,6 @@ import "#elements/forms/DeleteBulkForm"; import "#elements/forms/ModalForm"; import "#elements/sync/SyncObjectForm"; -import "#admin/common/ak-flow-search/ak-flow-search-no-default"; import { DEFAULT_CONFIG } from "#common/api/config"; diff --git a/web/src/admin/sources/ldap/LDAPSourceConnectivity.ts b/web/src/admin/sources/ldap/LDAPSourceConnectivity.ts index ed5f80c99b..16f894a37d 100644 --- a/web/src/admin/sources/ldap/LDAPSourceConnectivity.ts +++ b/web/src/admin/sources/ldap/LDAPSourceConnectivity.ts @@ -4,7 +4,7 @@ import { AKElement } from "#elements/Base"; import { SlottedTemplateResult } from "#elements/types"; import { msg } from "@lit/localize"; -import { CSSResult, html, nothing } from "lit"; +import { CSSResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; import PFList from "@patternfly/patternfly/components/List/list.css"; @@ -12,17 +12,17 @@ import PFList from "@patternfly/patternfly/components/List/list.css"; @customElement("ak-source-ldap-connectivity") export class LDAPSourceConnectivity extends AKElement { @property() - connectivity?: { + connectivity: { [key: string]: { [key: string]: string; }; - }; + } | null = null; static styles: CSSResult[] = [PFList]; render(): SlottedTemplateResult { if (!this.connectivity) { - return nothing; + return html`${msg("No connectivity status available.")}`; } return html`