We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Do not silently ignore byte and none properties in _build_object_properties
**Is your feature request related to a problem? Please describe.**
It took me quite some time to understand why my custom ldap mapping had no effect. 

**Describe the solution you'd like**
In the byte-case I think an exception should be raised, in case of none maybe a warning should be logged ? 
Maybe allow custom ldap mapping to be marked as required ?

**Describe alternatives you've considered**
It would also help if this behavior would be highlighted in the documentaion

**Additional context**
The source is in authentik/sources/ldap/base.py in _build_object_properties line 154 - 158


--- 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/base.py`
Content:
```
1 """Sync LDAP Users and groups into authentik"""
2 from typing import Any, Generator
3 
4 from django.conf import settings
5 from django.db.models.base import Model
6 from django.db.models.query import QuerySet
7 from ldap3 import DEREF_ALWAYS, SUBTREE, Connection
8 from structlog.stdlib import BoundLogger, get_logger
9 
10 from authentik.core.exceptions import PropertyMappingExpressionException
11 from authentik.events.models import Event, EventAction
12 from authentik.lib.config import CONFIG
13 from authentik.lib.merge import MERGE_LIST_UNIQUE
14 from authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME
15 from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
16 
17 LDAP_UNIQUENESS = "ldap_uniq"
18 
19 
20 class BaseLDAPSynchronizer:
21     """Sync LDAP Users and groups into authentik"""
22 
23     _source: LDAPSource
24     _logger: BoundLogger
25     _connection: Connection
26     _messages: list[str]
27 
28     def __init__(self, source: LDAPSource):
29         self._source = source
30         self._connection = source.connection()
31         self._messages = []
32         self._logger = get_logger().bind(source=source, syncer=self.__class__.__name__)
33 
34     @staticmethod
35     def name() -> str:
36         """UI name for the type of object this class synchronizes"""
37         raise NotImplementedError
38 
39     def sync_full(self):
40         """Run full sync, this function should only be used in tests"""
41         if not settings.TEST:  # noqa
42             raise RuntimeError(
43                 f"{self.__class__.__name__}.sync_full() should only be used in tests"
44             )
45         for page in self.get_objects():
46             self.sync(page)
47 
48     def sync(self, page_data: list) -> int:
49         """Sync function, implemented in subclass"""
50         raise NotImplementedError()
51 
52     @property
53     def messages(self) -> list[str]:
54         """Get all UI messages"""
55         return self._messages
56 
57     @property
58     def base_dn_users(self) -> str:
59         """Shortcut to get full base_dn for user lookups"""
60         if self._source.additional_user_dn:
61             return f"{self._source.additional_user_dn},{self._source.base_dn}"
62         return self._source.base_dn
63 
64     @property
65     def base_dn_groups(self) -> str:
66         """Shortcut to get full base_dn for group lookups"""
67         if self._source.additional_group_dn:
68             return f"{self._source.additional_group_dn},{self._source.base_dn}"
69         return self._source.base_dn
70 
71     def message(self, *args, **kwargs):
72         """Add message that is later added to the System Task and shown to the user"""
73         formatted_message = " ".join(args)
74         if "dn" in kwargs:
75             formatted_message += f"; DN: {kwargs['dn']}"
76         self._messages.append(formatted_message)
77         self._logger.warning(*args, **kwargs)
78 
79     def get_objects(self, **kwargs) -> Generator:
80         """Get objects from LDAP, implemented in subclass"""
81         raise NotImplementedError()
82 
83     # pylint: disable=too-many-arguments
84     def search_paginator(
85         self,
86         search_base,
87         search_filter,
88         search_scope=SUBTREE,
89         dereference_aliases=DEREF_ALWAYS,
90         attributes=None,
91         size_limit=0,
92         time_limit=0,
93         types_only=False,
94         get_operational_attributes=False,
95         controls=None,
96         paged_size=CONFIG.get_int("ldap.page_size", 50),
97         paged_criticality=False,
98     ):
99         """Search in pages, returns each page"""
100         cookie = True
101         while cookie:
102             self._connection.search(
103                 search_base,
104                 search_filter,
105                 search_scope,
106                 dereference_aliases,
107                 attributes,
108                 size_limit,
109                 time_limit,
110                 types_only,
111                 get_operational_attributes,
112                 controls,
113                 paged_size,
114                 paged_criticality,
115                 None if cookie is True else cookie,
116             )
117             try:
118                 cookie = self._connection.result["controls"]["1.2.840.113556.1.4.319"]["value"][
119                     "cookie"
120                 ]
121             except KeyError:
122                 cookie = None
123             yield self._connection.response
124 
125     def _flatten(self, value: Any) -> Any:
126         """Flatten `value` if its a list"""
127         if isinstance(value, list):
128             if len(value) < 1:
129                 return None
130             return value[0]
131         return value
132 
133     def build_user_properties(self, user_dn: str, **kwargs) -> dict[str, Any]:
134         """Build attributes for User object based on property mappings."""
135         props = self._build_object_properties(user_dn, self._source.property_mappings, **kwargs)
136         props["path"] = self._source.get_user_path()
137         return props
138 
139     def build_group_properties(self, group_dn: str, **kwargs) -> dict[str, Any]:
140         """Build attributes for Group object based on property mappings."""
141         return self._build_object_properties(
142             group_dn, self._source.property_mappings_group, **kwargs
143         )
144 
145     def _build_object_properties(
146         self, object_dn: str, mappings: QuerySet, **kwargs
147     ) -> dict[str, dict[Any, Any]]:
148         properties = {"attributes": {}}
149         for mapping in mappings.all().select_subclasses():
150             if not isinstance(mapping, LDAPPropertyMapping):
151                 continue
152             mapping: LDAPPropertyMapping
153             try:
154                 value = mapping.evaluate(user=None, request=None, ldap=kwargs, dn=object_dn)
155                 if value is None:
156                     continue
157                 if isinstance(value, (bytes)):
158                     continue
159                 object_field = mapping.object_field
160                 if object_field.startswith("attributes."):
161                     # Because returning a list might desired, we can't
162                     # rely on self._flatten here. Instead, just save the result as-is
163                     properties["attributes"][object_field.replace("attributes.", "")] = value
164                 else:
165                     properties[object_field] = self._flatten(value)
166             except PropertyMappingExpressionException as exc:
167                 Event.new(
168                     EventAction.CONFIGURATION_ERROR,
169                     message=f"Failed to evaluate property-mapping: '{mapping.name}'",
170                     source=self._source,
171                     mapping=mapping,
172                 ).save()
173                 self._logger.warning("Mapping failed to evaluate", exc=exc, mapping=mapping)
174                 continue
175         if self._source.object_uniqueness_field in kwargs:
176             properties["attributes"][LDAP_UNIQUENESS] = self._flatten(
177                 kwargs.get(self._source.object_uniqueness_field)
178             )
179         properties["attributes"][LDAP_DISTINGUISHED_NAME] = object_dn
180         return properties
181 
182     def update_or_create_attributes(
183         self,
184         obj: type[Model],
185         query: dict[str, Any],
186         data: dict[str, Any],
187     ) -> tuple[Model, bool]:
188         """Same as django's update_or_create but correctly update attributes by merging dicts"""
189         instance = obj.objects.filter(**query).first()
190         if not instance:
191             return (obj.objects.create(**data), True)
192         for key, value in data.items():
193             if key == "attributes":
194                 continue
195             setattr(instance, key, value)
196         final_attributes = {}
197         MERGE_LIST_UNIQUE.merge(final_attributes, instance.attributes)
198         MERGE_LIST_UNIQUE.merge(final_attributes, data.get("attributes", {}))
199         instance.attributes = final_attributes
200         instance.save()
201         return (instance, False)
202
```
--- 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/base.py b/authentik/sources/ldap/sync/base.py
--- a/authentik/sources/ldap/sync/base.py
+++ b/authentik/sources/ldap/sync/base.py
@@ -153,8 +153,10 @@
             try:
                 value = mapping.evaluate(user=None, request=None, ldap=kwargs, dn=object_dn)
                 if value is None:
+                    self._logger.warning("property mapping returned None", mapping=mapping)
                     continue
                 if isinstance(value, (bytes)):
+                    self._logger.warning("property mapping returned bytes", mapping=mapping)
                     continue
                 object_field = mapping.object_field
                 if object_field.startswith("attributes."):

{"golden_diff": "diff --git a/authentik/sources/ldap/sync/base.py b/authentik/sources/ldap/sync/base.py\n--- a/authentik/sources/ldap/sync/base.py\n+++ b/authentik/sources/ldap/sync/base.py\n@@ -153,8 +153,10 @@\n             try:\n                 value = mapping.evaluate(user=None, request=None, ldap=kwargs, dn=object_dn)\n                 if value is None:\n+                    self._logger.warning(\"property mapping returned None\", mapping=mapping)\n                     continue\n                 if isinstance(value, (bytes)):\n+                    self._logger.warning(\"property mapping returned bytes\", mapping=mapping)\n                     continue\n                 object_field = mapping.object_field\n                 if object_field.startswith(\"attributes.\"):\n", "issue": "Do not silently ignore byte and none properties in _build_object_properties\n**Is your feature request related to a problem? Please describe.**\r\nIt took me quite some time to understand why my custom ldap mapping had no effect. \r\n\r\n**Describe the solution you'd like**\r\nIn the byte-case I think an exception should be raised, in case of none maybe a warning should be logged ? \r\nMaybe allow custom ldap mapping to be marked as required ?\r\n\r\n**Describe alternatives you've considered**\r\nIt would also help if this behavior would be highlighted in the documentaion\r\n\r\n**Additional context**\r\nThe source is in authentik/sources/ldap/base.py in _build_object_properties line 154 - 158\r\n\n", "before_files": [{"content": "\"\"\"Sync LDAP Users and groups into authentik\"\"\"\nfrom typing import Any, Generator\n\nfrom django.conf import settings\nfrom django.db.models.base import Model\nfrom django.db.models.query import QuerySet\nfrom ldap3 import DEREF_ALWAYS, SUBTREE, Connection\nfrom structlog.stdlib import BoundLogger, get_logger\n\nfrom authentik.core.exceptions import PropertyMappingExpressionException\nfrom authentik.events.models import Event, EventAction\nfrom authentik.lib.config import CONFIG\nfrom authentik.lib.merge import MERGE_LIST_UNIQUE\nfrom authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME\nfrom authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource\n\nLDAP_UNIQUENESS = \"ldap_uniq\"\n\n\nclass BaseLDAPSynchronizer:\n    \"\"\"Sync LDAP Users and groups into authentik\"\"\"\n\n    _source: LDAPSource\n    _logger: BoundLogger\n    _connection: Connection\n    _messages: list[str]\n\n    def __init__(self, source: LDAPSource):\n        self._source = source\n        self._connection = source.connection()\n        self._messages = []\n        self._logger = get_logger().bind(source=source, syncer=self.__class__.__name__)\n\n    @staticmethod\n    def name() -> str:\n        \"\"\"UI name for the type of object this class synchronizes\"\"\"\n        raise NotImplementedError\n\n    def sync_full(self):\n        \"\"\"Run full sync, this function should only be used in tests\"\"\"\n        if not settings.TEST:  # noqa\n            raise RuntimeError(\n                f\"{self.__class__.__name__}.sync_full() should only be used in tests\"\n            )\n        for page in self.get_objects():\n            self.sync(page)\n\n    def sync(self, page_data: list) -> int:\n        \"\"\"Sync function, implemented in subclass\"\"\"\n        raise NotImplementedError()\n\n    @property\n    def messages(self) -> list[str]:\n        \"\"\"Get all UI messages\"\"\"\n        return self._messages\n\n    @property\n    def base_dn_users(self) -> str:\n        \"\"\"Shortcut to get full base_dn for user lookups\"\"\"\n        if self._source.additional_user_dn:\n            return f\"{self._source.additional_user_dn},{self._source.base_dn}\"\n        return self._source.base_dn\n\n    @property\n    def base_dn_groups(self) -> str:\n        \"\"\"Shortcut to get full base_dn for group lookups\"\"\"\n        if self._source.additional_group_dn:\n            return f\"{self._source.additional_group_dn},{self._source.base_dn}\"\n        return self._source.base_dn\n\n    def message(self, *args, **kwargs):\n        \"\"\"Add message that is later added to the System Task and shown to the user\"\"\"\n        formatted_message = \" \".join(args)\n        if \"dn\" in kwargs:\n            formatted_message += f\"; DN: {kwargs['dn']}\"\n        self._messages.append(formatted_message)\n        self._logger.warning(*args, **kwargs)\n\n    def get_objects(self, **kwargs) -> Generator:\n        \"\"\"Get objects from LDAP, implemented in subclass\"\"\"\n        raise NotImplementedError()\n\n    # pylint: disable=too-many-arguments\n    def search_paginator(\n        self,\n        search_base,\n        search_filter,\n        search_scope=SUBTREE,\n        dereference_aliases=DEREF_ALWAYS,\n        attributes=None,\n        size_limit=0,\n        time_limit=0,\n        types_only=False,\n        get_operational_attributes=False,\n        controls=None,\n        paged_size=CONFIG.get_int(\"ldap.page_size\", 50),\n        paged_criticality=False,\n    ):\n        \"\"\"Search in pages, returns each page\"\"\"\n        cookie = True\n        while cookie:\n            self._connection.search(\n                search_base,\n                search_filter,\n                search_scope,\n                dereference_aliases,\n                attributes,\n                size_limit,\n                time_limit,\n                types_only,\n                get_operational_attributes,\n                controls,\n                paged_size,\n                paged_criticality,\n                None if cookie is True else cookie,\n            )\n            try:\n                cookie = self._connection.result[\"controls\"][\"1.2.840.113556.1.4.319\"][\"value\"][\n                    \"cookie\"\n                ]\n            except KeyError:\n                cookie = None\n            yield self._connection.response\n\n    def _flatten(self, value: Any) -> Any:\n        \"\"\"Flatten `value` if its a list\"\"\"\n        if isinstance(value, list):\n            if len(value) < 1:\n                return None\n            return value[0]\n        return value\n\n    def build_user_properties(self, user_dn: str, **kwargs) -> dict[str, Any]:\n        \"\"\"Build attributes for User object based on property mappings.\"\"\"\n        props = self._build_object_properties(user_dn, self._source.property_mappings, **kwargs)\n        props[\"path\"] = self._source.get_user_path()\n        return props\n\n    def build_group_properties(self, group_dn: str, **kwargs) -> dict[str, Any]:\n        \"\"\"Build attributes for Group object based on property mappings.\"\"\"\n        return self._build_object_properties(\n            group_dn, self._source.property_mappings_group, **kwargs\n        )\n\n    def _build_object_properties(\n        self, object_dn: str, mappings: QuerySet, **kwargs\n    ) -> dict[str, dict[Any, Any]]:\n        properties = {\"attributes\": {}}\n        for mapping in mappings.all().select_subclasses():\n            if not isinstance(mapping, LDAPPropertyMapping):\n                continue\n            mapping: LDAPPropertyMapping\n            try:\n                value = mapping.evaluate(user=None, request=None, ldap=kwargs, dn=object_dn)\n                if value is None:\n                    continue\n                if isinstance(value, (bytes)):\n                    continue\n                object_field = mapping.object_field\n                if object_field.startswith(\"attributes.\"):\n                    # Because returning a list might desired, we can't\n                    # rely on self._flatten here. Instead, just save the result as-is\n                    properties[\"attributes\"][object_field.replace(\"attributes.\", \"\")] = value\n                else:\n                    properties[object_field] = self._flatten(value)\n            except PropertyMappingExpressionException as exc:\n                Event.new(\n                    EventAction.CONFIGURATION_ERROR,\n                    message=f\"Failed to evaluate property-mapping: '{mapping.name}'\",\n                    source=self._source,\n                    mapping=mapping,\n                ).save()\n                self._logger.warning(\"Mapping failed to evaluate\", exc=exc, mapping=mapping)\n                continue\n        if self._source.object_uniqueness_field in kwargs:\n            properties[\"attributes\"][LDAP_UNIQUENESS] = self._flatten(\n                kwargs.get(self._source.object_uniqueness_field)\n            )\n        properties[\"attributes\"][LDAP_DISTINGUISHED_NAME] = object_dn\n        return properties\n\n    def update_or_create_attributes(\n        self,\n        obj: type[Model],\n        query: dict[str, Any],\n        data: dict[str, Any],\n    ) -> tuple[Model, bool]:\n        \"\"\"Same as django's update_or_create but correctly update attributes by merging dicts\"\"\"\n        instance = obj.objects.filter(**query).first()\n        if not instance:\n            return (obj.objects.create(**data), True)\n        for key, value in data.items():\n            if key == \"attributes\":\n                continue\n            setattr(instance, key, value)\n        final_attributes = {}\n        MERGE_LIST_UNIQUE.merge(final_attributes, instance.attributes)\n        MERGE_LIST_UNIQUE.merge(final_attributes, data.get(\"attributes\", {}))\n        instance.attributes = final_attributes\n        instance.save()\n        return (instance, False)\n", "path": "authentik/sources/ldap/sync/base.py"}], "after_files": [{"content": "\"\"\"Sync LDAP Users and groups into authentik\"\"\"\nfrom typing import Any, Generator\n\nfrom django.conf import settings\nfrom django.db.models.base import Model\nfrom django.db.models.query import QuerySet\nfrom ldap3 import DEREF_ALWAYS, SUBTREE, Connection\nfrom structlog.stdlib import BoundLogger, get_logger\n\nfrom authentik.core.exceptions import PropertyMappingExpressionException\nfrom authentik.events.models import Event, EventAction\nfrom authentik.lib.config import CONFIG\nfrom authentik.lib.merge import MERGE_LIST_UNIQUE\nfrom authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME\nfrom authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource\n\nLDAP_UNIQUENESS = \"ldap_uniq\"\n\n\nclass BaseLDAPSynchronizer:\n    \"\"\"Sync LDAP Users and groups into authentik\"\"\"\n\n    _source: LDAPSource\n    _logger: BoundLogger\n    _connection: Connection\n    _messages: list[str]\n\n    def __init__(self, source: LDAPSource):\n        self._source = source\n        self._connection = source.connection()\n        self._messages = []\n        self._logger = get_logger().bind(source=source, syncer=self.__class__.__name__)\n\n    @staticmethod\n    def name() -> str:\n        \"\"\"UI name for the type of object this class synchronizes\"\"\"\n        raise NotImplementedError\n\n    def sync_full(self):\n        \"\"\"Run full sync, this function should only be used in tests\"\"\"\n        if not settings.TEST:  # noqa\n            raise RuntimeError(\n                f\"{self.__class__.__name__}.sync_full() should only be used in tests\"\n            )\n        for page in self.get_objects():\n            self.sync(page)\n\n    def sync(self, page_data: list) -> int:\n        \"\"\"Sync function, implemented in subclass\"\"\"\n        raise NotImplementedError()\n\n    @property\n    def messages(self) -> list[str]:\n        \"\"\"Get all UI messages\"\"\"\n        return self._messages\n\n    @property\n    def base_dn_users(self) -> str:\n        \"\"\"Shortcut to get full base_dn for user lookups\"\"\"\n        if self._source.additional_user_dn:\n            return f\"{self._source.additional_user_dn},{self._source.base_dn}\"\n        return self._source.base_dn\n\n    @property\n    def base_dn_groups(self) -> str:\n        \"\"\"Shortcut to get full base_dn for group lookups\"\"\"\n        if self._source.additional_group_dn:\n            return f\"{self._source.additional_group_dn},{self._source.base_dn}\"\n        return self._source.base_dn\n\n    def message(self, *args, **kwargs):\n        \"\"\"Add message that is later added to the System Task and shown to the user\"\"\"\n        formatted_message = \" \".join(args)\n        if \"dn\" in kwargs:\n            formatted_message += f\"; DN: {kwargs['dn']}\"\n        self._messages.append(formatted_message)\n        self._logger.warning(*args, **kwargs)\n\n    def get_objects(self, **kwargs) -> Generator:\n        \"\"\"Get objects from LDAP, implemented in subclass\"\"\"\n        raise NotImplementedError()\n\n    # pylint: disable=too-many-arguments\n    def search_paginator(\n        self,\n        search_base,\n        search_filter,\n        search_scope=SUBTREE,\n        dereference_aliases=DEREF_ALWAYS,\n        attributes=None,\n        size_limit=0,\n        time_limit=0,\n        types_only=False,\n        get_operational_attributes=False,\n        controls=None,\n        paged_size=CONFIG.get_int(\"ldap.page_size\", 50),\n        paged_criticality=False,\n    ):\n        \"\"\"Search in pages, returns each page\"\"\"\n        cookie = True\n        while cookie:\n            self._connection.search(\n                search_base,\n                search_filter,\n                search_scope,\n                dereference_aliases,\n                attributes,\n                size_limit,\n                time_limit,\n                types_only,\n                get_operational_attributes,\n                controls,\n                paged_size,\n                paged_criticality,\n                None if cookie is True else cookie,\n            )\n            try:\n                cookie = self._connection.result[\"controls\"][\"1.2.840.113556.1.4.319\"][\"value\"][\n                    \"cookie\"\n                ]\n            except KeyError:\n                cookie = None\n            yield self._connection.response\n\n    def _flatten(self, value: Any) -> Any:\n        \"\"\"Flatten `value` if its a list\"\"\"\n        if isinstance(value, list):\n            if len(value) < 1:\n                return None\n            return value[0]\n        return value\n\n    def build_user_properties(self, user_dn: str, **kwargs) -> dict[str, Any]:\n        \"\"\"Build attributes for User object based on property mappings.\"\"\"\n        props = self._build_object_properties(user_dn, self._source.property_mappings, **kwargs)\n        props[\"path\"] = self._source.get_user_path()\n        return props\n\n    def build_group_properties(self, group_dn: str, **kwargs) -> dict[str, Any]:\n        \"\"\"Build attributes for Group object based on property mappings.\"\"\"\n        return self._build_object_properties(\n            group_dn, self._source.property_mappings_group, **kwargs\n        )\n\n    def _build_object_properties(\n        self, object_dn: str, mappings: QuerySet, **kwargs\n    ) -> dict[str, dict[Any, Any]]:\n        properties = {\"attributes\": {}}\n        for mapping in mappings.all().select_subclasses():\n            if not isinstance(mapping, LDAPPropertyMapping):\n                continue\n            mapping: LDAPPropertyMapping\n            try:\n                value = mapping.evaluate(user=None, request=None, ldap=kwargs, dn=object_dn)\n                if value is None:\n                    self._logger.warning(\"property mapping returned None\", mapping=mapping)\n                    continue\n                if isinstance(value, (bytes)):\n                    self._logger.warning(\"property mapping returned bytes\", mapping=mapping)\n                    continue\n                object_field = mapping.object_field\n                if object_field.startswith(\"attributes.\"):\n                    # Because returning a list might desired, we can't\n                    # rely on self._flatten here. Instead, just save the result as-is\n                    properties[\"attributes\"][object_field.replace(\"attributes.\", \"\")] = value\n                else:\n                    properties[object_field] = self._flatten(value)\n            except PropertyMappingExpressionException as exc:\n                Event.new(\n                    EventAction.CONFIGURATION_ERROR,\n                    message=f\"Failed to evaluate property-mapping: '{mapping.name}'\",\n                    source=self._source,\n                    mapping=mapping,\n                ).save()\n                self._logger.warning(\"Mapping failed to evaluate\", exc=exc, mapping=mapping)\n                continue\n        if self._source.object_uniqueness_field in kwargs:\n            properties[\"attributes\"][LDAP_UNIQUENESS] = self._flatten(\n                kwargs.get(self._source.object_uniqueness_field)\n            )\n        properties[\"attributes\"][LDAP_DISTINGUISHED_NAME] = object_dn\n        return properties\n\n    def update_or_create_attributes(\n        self,\n        obj: type[Model],\n        query: dict[str, Any],\n        data: dict[str, Any],\n    ) -> tuple[Model, bool]:\n        \"\"\"Same as django's update_or_create but correctly update attributes by merging dicts\"\"\"\n        instance = obj.objects.filter(**query).first()\n        if not instance:\n            return (obj.objects.create(**data), True)\n        for key, value in data.items():\n            if key == \"attributes\":\n                continue\n            setattr(instance, key, value)\n        final_attributes = {}\n        MERGE_LIST_UNIQUE.merge(final_attributes, instance.attributes)\n        MERGE_LIST_UNIQUE.merge(final_attributes, data.get(\"attributes\", {}))\n        instance.attributes = final_attributes\n        instance.save()\n        return (instance, False)\n", "path": "authentik/sources/ldap/sync/base.py"}]}