We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
ldap group membership not updated
**Is your feature request related to a problem? Please describe.**
The ldap syncing code assumes group membership is based on DN.
In our ldap tree this is unfortunately not the case. Instead the group membership is based on CN.
So while users and groups are synced, the group memberships are not established in authentik's database.

**Describe the solution you'd like**
I need the 'group membership field' to be matched against the CN of a user instead of it's fully qualified DN.

**Additional context**
Example pseudo ldif for groups:
```
dn: cn=group-1,ou=groups,o=example,c=ch
objectClass: posixGroup
cn: group-1
memberUid: user-1
memberUid: user-2
memberUid: user-3

dn: cn=group-2,ou=groups,o=example,c=ch
objectClass: posixGroup
cn: group-2
memberUid: user-1
memberUid: user-2
```
And for users:

```
dn: cn=user-1,ou=users,o=example,c=ch
objectClass: posixAccount
cn: user-1
ou: group-1
ou: group-2

dn: cn=user-2,ou=users,o=example,c=ch
objectClass: posixAccount
cn: user-2
ou: group-1
ou: group-2

dn: cn=user-3,ou=users,o=example,c=ch
objectClass: posixAccount
cn: user-3
ou: group-1
```

If think for the current authentik sync code to work the groups would have to look like this:
```
dn: cn=group-1,ou=groups,o=example,c=ch
objectClass: posixGroup
cn: group-1
memberUid: user-1,ou=users,o=example,c=ch
memberUid: user-2,ou=users,o=example,c=ch
memberUid: user-3,ou=users,o=example,c=ch

dn: cn=group-2,ou=groups,o=example,c=ch
objectClass: posixGroup
cn: group-2
memberUid: user-1,ou=users,o=example,c=ch
memberUid: user-2,ou=users,o=example,c=ch
```

But changing this is beyond my control.

This is the OpenLDAP directory tree of a large University. I'm not sure if this setup and therefor also the problem is common or unique to our deployment.

I guess for the membership mapping to work I'd need the code that authentik uses to lookup group memberships in ./authentik/sources/ldap/sync/membership.py to look something like this:

```
LDAP_UNIQUENESS = "ldap_uniq"

            users = User.objects.filter(
                Q(**{f"attributes__{LDAP_UNIQUENESS}__in": members})
                | Q(
                    **{
                        f"attributes__{LDAP_UNIQUENESS}__isnull": True,
                        "ak_groups__in": [ak_group],
                    }
                )
            )

```

Instead of the current:

```
LDAP_DISTINGUISHED_NAME = "distinguishedName"

            users = User.objects.filter(
                Q(**{f"attributes__{LDAP_DISTINGUISHED_NAME}__in": members})
                | Q(
                    **{
                        f"attributes__{LDAP_DISTINGUISHED_NAME}__isnull": True,
                        "ak_groups__in": [ak_group],
                    }
                )
            )

```

I'm happy to provide a PR after discussing the right approach on how to make this configurable.
ldap group membership not updated
**Is your feature request related to a problem? Please describe.**
The ldap syncing code assumes group membership is based on DN.
In our ldap tree this is unfortunately not the case. Instead the group membership is based on CN.
So while users and groups are synced, the group memberships are not established in authentik's database.

**Describe the solution you'd like**
I need the 'group membership field' to be matched against the CN of a user instead of it's fully qualified DN.

**Additional context**
Example pseudo ldif for groups:
```
dn: cn=group-1,ou=groups,o=example,c=ch
objectClass: posixGroup
cn: group-1
memberUid: user-1
memberUid: user-2
memberUid: user-3

dn: cn=group-2,ou=groups,o=example,c=ch
objectClass: posixGroup
cn: group-2
memberUid: user-1
memberUid: user-2
```
And for users:

```
dn: cn=user-1,ou=users,o=example,c=ch
objectClass: posixAccount
cn: user-1
ou: group-1
ou: group-2

dn: cn=user-2,ou=users,o=example,c=ch
objectClass: posixAccount
cn: user-2
ou: group-1
ou: group-2

dn: cn=user-3,ou=users,o=example,c=ch
objectClass: posixAccount
cn: user-3
ou: group-1
```

If think for the current authentik sync code to work the groups would have to look like this:
```
dn: cn=group-1,ou=groups,o=example,c=ch
objectClass: posixGroup
cn: group-1
memberUid: user-1,ou=users,o=example,c=ch
memberUid: user-2,ou=users,o=example,c=ch
memberUid: user-3,ou=users,o=example,c=ch

dn: cn=group-2,ou=groups,o=example,c=ch
objectClass: posixGroup
cn: group-2
memberUid: user-1,ou=users,o=example,c=ch
memberUid: user-2,ou=users,o=example,c=ch
```

But changing this is beyond my control.

This is the OpenLDAP directory tree of a large University. I'm not sure if this setup and therefor also the problem is common or unique to our deployment.

I guess for the membership mapping to work I'd need the code that authentik uses to lookup group memberships in ./authentik/sources/ldap/sync/membership.py to look something like this:

```
LDAP_UNIQUENESS = "ldap_uniq"

            users = User.objects.filter(
                Q(**{f"attributes__{LDAP_UNIQUENESS}__in": members})
                | Q(
                    **{
                        f"attributes__{LDAP_UNIQUENESS}__isnull": True,
                        "ak_groups__in": [ak_group],
                    }
                )
            )

```

Instead of the current:

```
LDAP_DISTINGUISHED_NAME = "distinguishedName"

            users = User.objects.filter(
                Q(**{f"attributes__{LDAP_DISTINGUISHED_NAME}__in": members})
                | Q(
                    **{
                        f"attributes__{LDAP_DISTINGUISHED_NAME}__isnull": True,
                        "ak_groups__in": [ak_group],
                    }
                )
            )

```

I'm happy to provide a PR after discussing the right approach on how to make this configurable.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `authentik/sources/ldap/sync/membership.py`
Content:
```
1 """Sync LDAP Users and groups into authentik"""
2 from typing import Any, Optional
3 
4 import ldap3
5 import ldap3.core.exceptions
6 from django.db.models import Q
7 
8 from authentik.core.models import Group, User
9 from authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME
10 from authentik.sources.ldap.models import LDAPSource
11 from authentik.sources.ldap.sync.base import LDAP_UNIQUENESS, BaseLDAPSynchronizer
12 
13 
14 class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
15     """Sync LDAP Users and groups into authentik"""
16 
17     group_cache: dict[str, Group]
18 
19     def __init__(self, source: LDAPSource):
20         super().__init__(source)
21         self.group_cache: dict[str, Group] = {}
22 
23     def sync(self) -> int:
24         """Iterate over all Users and assign Groups using memberOf Field"""
25         groups = self._source.connection.extend.standard.paged_search(
26             search_base=self.base_dn_groups,
27             search_filter=self._source.group_object_filter,
28             search_scope=ldap3.SUBTREE,
29             attributes=[
30                 self._source.group_membership_field,
31                 self._source.object_uniqueness_field,
32                 LDAP_DISTINGUISHED_NAME,
33             ],
34         )
35         membership_count = 0
36         for group in groups:
37             members = group.get("attributes", {}).get(self._source.group_membership_field, [])
38             ak_group = self.get_group(group)
39             if not ak_group:
40                 continue
41 
42             users = User.objects.filter(
43                 Q(**{f"attributes__{LDAP_DISTINGUISHED_NAME}__in": members})
44                 | Q(
45                     **{
46                         f"attributes__{LDAP_DISTINGUISHED_NAME}__isnull": True,
47                         "ak_groups__in": [ak_group],
48                     }
49                 )
50             )
51             membership_count += 1
52             membership_count += users.count()
53             ak_group.users.set(users)
54             ak_group.save()
55         self._logger.debug("Successfully updated group membership")
56         return membership_count
57 
58     def get_group(self, group_dict: dict[str, Any]) -> Optional[Group]:
59         """Check if we fetched the group already, and if not cache it for later"""
60         group_dn = group_dict.get("attributes", {}).get(LDAP_DISTINGUISHED_NAME, [])
61         group_uniq = group_dict.get("attributes", {}).get(self._source.object_uniqueness_field, [])
62         # group_uniq might be a single string or an array with (hopefully) a single string
63         if isinstance(group_uniq, list):
64             if len(group_uniq) < 1:
65                 self.message(
66                     f"Group does not have a uniqueness attribute: '{group_dn}'",
67                     group=group_dn,
68                 )
69                 return None
70             group_uniq = group_uniq[0]
71         if group_uniq not in self.group_cache:
72             groups = Group.objects.filter(**{f"attributes__{LDAP_UNIQUENESS}": group_uniq})
73             if not groups.exists():
74                 self.message(
75                     f"Group does not exist in our DB yet, run sync_groups first: '{group_dn}'",
76                     group=group_dn,
77                 )
78                 return None
79             self.group_cache[group_uniq] = groups.first()
80         return self.group_cache[group_uniq]
81
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/authentik/sources/ldap/sync/membership.py b/authentik/sources/ldap/sync/membership.py
--- a/authentik/sources/ldap/sync/membership.py
+++ b/authentik/sources/ldap/sync/membership.py
@@ -39,11 +39,17 @@
             if not ak_group:
                 continue
 
+            membership_mapping_attribute = LDAP_DISTINGUISHED_NAME
+            if self._source.group_membership_field == "memberUid":
+                # If memberships are based on the posixGroup's 'memberUid'
+                # attribute we use the RDN instead of the FDN to lookup members.
+                membership_mapping_attribute = LDAP_UNIQUENESS
+
             users = User.objects.filter(
-                Q(**{f"attributes__{LDAP_DISTINGUISHED_NAME}__in": members})
+                Q(**{f"attributes__{membership_mapping_attribute}__in": members})
                 | Q(
                     **{
-                        f"attributes__{LDAP_DISTINGUISHED_NAME}__isnull": True,
+                        f"attributes__{membership_mapping_attribute}__isnull": True,
                         "ak_groups__in": [ak_group],
                     }
                 )

{"golden_diff": "diff --git a/authentik/sources/ldap/sync/membership.py b/authentik/sources/ldap/sync/membership.py\n--- a/authentik/sources/ldap/sync/membership.py\n+++ b/authentik/sources/ldap/sync/membership.py\n@@ -39,11 +39,17 @@\n             if not ak_group:\n                 continue\n \n+            membership_mapping_attribute = LDAP_DISTINGUISHED_NAME\n+            if self._source.group_membership_field == \"memberUid\":\n+                # If memberships are based on the posixGroup's 'memberUid'\n+                # attribute we use the RDN instead of the FDN to lookup members.\n+                membership_mapping_attribute = LDAP_UNIQUENESS\n+\n             users = User.objects.filter(\n-                Q(**{f\"attributes__{LDAP_DISTINGUISHED_NAME}__in\": members})\n+                Q(**{f\"attributes__{membership_mapping_attribute}__in\": members})\n                 | Q(\n                     **{\n-                        f\"attributes__{LDAP_DISTINGUISHED_NAME}__isnull\": True,\n+                        f\"attributes__{membership_mapping_attribute}__isnull\": True,\n                         \"ak_groups__in\": [ak_group],\n                     }\n                 )\n", "issue": "ldap group membership not updated\n**Is your feature request related to a problem? Please describe.**\r\nThe ldap syncing code assumes group membership is based on DN.\r\nIn our ldap tree this is unfortunately not the case. Instead the group membership is based on CN.\r\nSo while users and groups are synced, the group memberships are not established in authentik's database.\r\n\r\n**Describe the solution you'd like**\r\nI need the 'group membership field' to be matched against the CN of a user instead of it's fully qualified DN.\r\n\r\n**Additional context**\r\nExample pseudo ldif for groups:\r\n```\r\ndn: cn=group-1,ou=groups,o=example,c=ch\r\nobjectClass: posixGroup\r\ncn: group-1\r\nmemberUid: user-1\r\nmemberUid: user-2\r\nmemberUid: user-3\r\n\r\ndn: cn=group-2,ou=groups,o=example,c=ch\r\nobjectClass: posixGroup\r\ncn: group-2\r\nmemberUid: user-1\r\nmemberUid: user-2\r\n```\r\nAnd for users:\r\n\r\n```\r\ndn: cn=user-1,ou=users,o=example,c=ch\r\nobjectClass: posixAccount\r\ncn: user-1\r\nou: group-1\r\nou: group-2\r\n\r\ndn: cn=user-2,ou=users,o=example,c=ch\r\nobjectClass: posixAccount\r\ncn: user-2\r\nou: group-1\r\nou: group-2\r\n\r\ndn: cn=user-3,ou=users,o=example,c=ch\r\nobjectClass: posixAccount\r\ncn: user-3\r\nou: group-1\r\n```\r\n\r\nIf think for the current authentik sync code to work the groups would have to look like this:\r\n```\r\ndn: cn=group-1,ou=groups,o=example,c=ch\r\nobjectClass: posixGroup\r\ncn: group-1\r\nmemberUid: user-1,ou=users,o=example,c=ch\r\nmemberUid: user-2,ou=users,o=example,c=ch\r\nmemberUid: user-3,ou=users,o=example,c=ch\r\n\r\ndn: cn=group-2,ou=groups,o=example,c=ch\r\nobjectClass: posixGroup\r\ncn: group-2\r\nmemberUid: user-1,ou=users,o=example,c=ch\r\nmemberUid: user-2,ou=users,o=example,c=ch\r\n```\r\n\r\nBut changing this is beyond my control.\r\n\r\nThis is the OpenLDAP directory tree of a large University. I'm not sure if this setup and therefor also the problem is common or unique to our deployment.\r\n\r\nI guess for the membership mapping to work I'd need the code that authentik uses to lookup group memberships in ./authentik/sources/ldap/sync/membership.py to look something like this:\r\n\r\n```\r\nLDAP_UNIQUENESS = \"ldap_uniq\"\r\n\r\n            users = User.objects.filter(\r\n                Q(**{f\"attributes__{LDAP_UNIQUENESS}__in\": members})\r\n                | Q(\r\n                    **{\r\n                        f\"attributes__{LDAP_UNIQUENESS}__isnull\": True,\r\n                        \"ak_groups__in\": [ak_group],\r\n                    }\r\n                )\r\n            )\r\n\r\n```\r\n\r\nInstead of the current:\r\n\r\n```\r\nLDAP_DISTINGUISHED_NAME = \"distinguishedName\"\r\n\r\n            users = User.objects.filter(\r\n                Q(**{f\"attributes__{LDAP_DISTINGUISHED_NAME}__in\": members})\r\n                | Q(\r\n                    **{\r\n                        f\"attributes__{LDAP_DISTINGUISHED_NAME}__isnull\": True,\r\n                        \"ak_groups__in\": [ak_group],\r\n                    }\r\n                )\r\n            )\r\n\r\n```\r\n\r\nI'm happy to provide a PR after discussing the right approach on how to make this configurable.\nldap group membership not updated\n**Is your feature request related to a problem? Please describe.**\r\nThe ldap syncing code assumes group membership is based on DN.\r\nIn our ldap tree this is unfortunately not the case. Instead the group membership is based on CN.\r\nSo while users and groups are synced, the group memberships are not established in authentik's database.\r\n\r\n**Describe the solution you'd like**\r\nI need the 'group membership field' to be matched against the CN of a user instead of it's fully qualified DN.\r\n\r\n**Additional context**\r\nExample pseudo ldif for groups:\r\n```\r\ndn: cn=group-1,ou=groups,o=example,c=ch\r\nobjectClass: posixGroup\r\ncn: group-1\r\nmemberUid: user-1\r\nmemberUid: user-2\r\nmemberUid: user-3\r\n\r\ndn: cn=group-2,ou=groups,o=example,c=ch\r\nobjectClass: posixGroup\r\ncn: group-2\r\nmemberUid: user-1\r\nmemberUid: user-2\r\n```\r\nAnd for users:\r\n\r\n```\r\ndn: cn=user-1,ou=users,o=example,c=ch\r\nobjectClass: posixAccount\r\ncn: user-1\r\nou: group-1\r\nou: group-2\r\n\r\ndn: cn=user-2,ou=users,o=example,c=ch\r\nobjectClass: posixAccount\r\ncn: user-2\r\nou: group-1\r\nou: group-2\r\n\r\ndn: cn=user-3,ou=users,o=example,c=ch\r\nobjectClass: posixAccount\r\ncn: user-3\r\nou: group-1\r\n```\r\n\r\nIf think for the current authentik sync code to work the groups would have to look like this:\r\n```\r\ndn: cn=group-1,ou=groups,o=example,c=ch\r\nobjectClass: posixGroup\r\ncn: group-1\r\nmemberUid: user-1,ou=users,o=example,c=ch\r\nmemberUid: user-2,ou=users,o=example,c=ch\r\nmemberUid: user-3,ou=users,o=example,c=ch\r\n\r\ndn: cn=group-2,ou=groups,o=example,c=ch\r\nobjectClass: posixGroup\r\ncn: group-2\r\nmemberUid: user-1,ou=users,o=example,c=ch\r\nmemberUid: user-2,ou=users,o=example,c=ch\r\n```\r\n\r\nBut changing this is beyond my control.\r\n\r\nThis is the OpenLDAP directory tree of a large University. I'm not sure if this setup and therefor also the problem is common or unique to our deployment.\r\n\r\nI guess for the membership mapping to work I'd need the code that authentik uses to lookup group memberships in ./authentik/sources/ldap/sync/membership.py to look something like this:\r\n\r\n```\r\nLDAP_UNIQUENESS = \"ldap_uniq\"\r\n\r\n            users = User.objects.filter(\r\n                Q(**{f\"attributes__{LDAP_UNIQUENESS}__in\": members})\r\n                | Q(\r\n                    **{\r\n                        f\"attributes__{LDAP_UNIQUENESS}__isnull\": True,\r\n                        \"ak_groups__in\": [ak_group],\r\n                    }\r\n                )\r\n            )\r\n\r\n```\r\n\r\nInstead of the current:\r\n\r\n```\r\nLDAP_DISTINGUISHED_NAME = \"distinguishedName\"\r\n\r\n            users = User.objects.filter(\r\n                Q(**{f\"attributes__{LDAP_DISTINGUISHED_NAME}__in\": members})\r\n                | Q(\r\n                    **{\r\n                        f\"attributes__{LDAP_DISTINGUISHED_NAME}__isnull\": True,\r\n                        \"ak_groups__in\": [ak_group],\r\n                    }\r\n                )\r\n            )\r\n\r\n```\r\n\r\nI'm happy to provide a PR after discussing the right approach on how to make this configurable.\n", "before_files": [{"content": "\"\"\"Sync LDAP Users and groups into authentik\"\"\"\nfrom typing import Any, Optional\n\nimport ldap3\nimport ldap3.core.exceptions\nfrom django.db.models import Q\n\nfrom authentik.core.models import Group, User\nfrom authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME\nfrom authentik.sources.ldap.models import LDAPSource\nfrom authentik.sources.ldap.sync.base import LDAP_UNIQUENESS, BaseLDAPSynchronizer\n\n\nclass MembershipLDAPSynchronizer(BaseLDAPSynchronizer):\n    \"\"\"Sync LDAP Users and groups into authentik\"\"\"\n\n    group_cache: dict[str, Group]\n\n    def __init__(self, source: LDAPSource):\n        super().__init__(source)\n        self.group_cache: dict[str, Group] = {}\n\n    def sync(self) -> int:\n        \"\"\"Iterate over all Users and assign Groups using memberOf Field\"\"\"\n        groups = self._source.connection.extend.standard.paged_search(\n            search_base=self.base_dn_groups,\n            search_filter=self._source.group_object_filter,\n            search_scope=ldap3.SUBTREE,\n            attributes=[\n                self._source.group_membership_field,\n                self._source.object_uniqueness_field,\n                LDAP_DISTINGUISHED_NAME,\n            ],\n        )\n        membership_count = 0\n        for group in groups:\n            members = group.get(\"attributes\", {}).get(self._source.group_membership_field, [])\n            ak_group = self.get_group(group)\n            if not ak_group:\n                continue\n\n            users = User.objects.filter(\n                Q(**{f\"attributes__{LDAP_DISTINGUISHED_NAME}__in\": members})\n                | Q(\n                    **{\n                        f\"attributes__{LDAP_DISTINGUISHED_NAME}__isnull\": True,\n                        \"ak_groups__in\": [ak_group],\n                    }\n                )\n            )\n            membership_count += 1\n            membership_count += users.count()\n            ak_group.users.set(users)\n            ak_group.save()\n        self._logger.debug(\"Successfully updated group membership\")\n        return membership_count\n\n    def get_group(self, group_dict: dict[str, Any]) -> Optional[Group]:\n        \"\"\"Check if we fetched the group already, and if not cache it for later\"\"\"\n        group_dn = group_dict.get(\"attributes\", {}).get(LDAP_DISTINGUISHED_NAME, [])\n        group_uniq = group_dict.get(\"attributes\", {}).get(self._source.object_uniqueness_field, [])\n        # group_uniq might be a single string or an array with (hopefully) a single string\n        if isinstance(group_uniq, list):\n            if len(group_uniq) < 1:\n                self.message(\n                    f\"Group does not have a uniqueness attribute: '{group_dn}'\",\n                    group=group_dn,\n                )\n                return None\n            group_uniq = group_uniq[0]\n        if group_uniq not in self.group_cache:\n            groups = Group.objects.filter(**{f\"attributes__{LDAP_UNIQUENESS}\": group_uniq})\n            if not groups.exists():\n                self.message(\n                    f\"Group does not exist in our DB yet, run sync_groups first: '{group_dn}'\",\n                    group=group_dn,\n                )\n                return None\n            self.group_cache[group_uniq] = groups.first()\n        return self.group_cache[group_uniq]\n", "path": "authentik/sources/ldap/sync/membership.py"}], "after_files": [{"content": "\"\"\"Sync LDAP Users and groups into authentik\"\"\"\nfrom typing import Any, Optional\n\nimport ldap3\nimport ldap3.core.exceptions\nfrom django.db.models import Q\n\nfrom authentik.core.models import Group, User\nfrom authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME\nfrom authentik.sources.ldap.models import LDAPSource\nfrom authentik.sources.ldap.sync.base import LDAP_UNIQUENESS, BaseLDAPSynchronizer\n\n\nclass MembershipLDAPSynchronizer(BaseLDAPSynchronizer):\n    \"\"\"Sync LDAP Users and groups into authentik\"\"\"\n\n    group_cache: dict[str, Group]\n\n    def __init__(self, source: LDAPSource):\n        super().__init__(source)\n        self.group_cache: dict[str, Group] = {}\n\n    def sync(self) -> int:\n        \"\"\"Iterate over all Users and assign Groups using memberOf Field\"\"\"\n        groups = self._source.connection.extend.standard.paged_search(\n            search_base=self.base_dn_groups,\n            search_filter=self._source.group_object_filter,\n            search_scope=ldap3.SUBTREE,\n            attributes=[\n                self._source.group_membership_field,\n                self._source.object_uniqueness_field,\n                LDAP_DISTINGUISHED_NAME,\n            ],\n        )\n        membership_count = 0\n        for group in groups:\n            members = group.get(\"attributes\", {}).get(self._source.group_membership_field, [])\n            ak_group = self.get_group(group)\n            if not ak_group:\n                continue\n\n            membership_mapping_attribute = LDAP_DISTINGUISHED_NAME\n            if self._source.group_membership_field == \"memberUid\":\n                # If memberships are based on the posixGroup's 'memberUid'\n                # attribute we use the RDN instead of the FDN to lookup members.\n                membership_mapping_attribute = LDAP_UNIQUENESS\n\n            users = User.objects.filter(\n                Q(**{f\"attributes__{membership_mapping_attribute}__in\": members})\n                | Q(\n                    **{\n                        f\"attributes__{membership_mapping_attribute}__isnull\": True,\n                        \"ak_groups__in\": [ak_group],\n                    }\n                )\n            )\n            membership_count += 1\n            membership_count += users.count()\n            ak_group.users.set(users)\n            ak_group.save()\n        self._logger.debug(\"Successfully updated group membership\")\n        return membership_count\n\n    def get_group(self, group_dict: dict[str, Any]) -> Optional[Group]:\n        \"\"\"Check if we fetched the group already, and if not cache it for later\"\"\"\n        group_dn = group_dict.get(\"attributes\", {}).get(LDAP_DISTINGUISHED_NAME, [])\n        group_uniq = group_dict.get(\"attributes\", {}).get(self._source.object_uniqueness_field, [])\n        # group_uniq might be a single string or an array with (hopefully) a single string\n        if isinstance(group_uniq, list):\n            if len(group_uniq) < 1:\n                self.message(\n                    f\"Group does not have a uniqueness attribute: '{group_dn}'\",\n                    group=group_dn,\n                )\n                return None\n            group_uniq = group_uniq[0]\n        if group_uniq not in self.group_cache:\n            groups = Group.objects.filter(**{f\"attributes__{LDAP_UNIQUENESS}\": group_uniq})\n            if not groups.exists():\n                self.message(\n                    f\"Group does not exist in our DB yet, run sync_groups first: '{group_dn}'\",\n                    group=group_dn,\n                )\n                return None\n            self.group_cache[group_uniq] = groups.first()\n        return self.group_cache[group_uniq]\n", "path": "authentik/sources/ldap/sync/membership.py"}]}