Repository: goauthentik/authentik

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



Discussion:

Resolving patch:
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 @@ def _build_object_properties(
             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."):


Test patch:
diff --git a/authentik/sources/ldap/tests/test_sync.py b/authentik/sources/ldap/tests/test_sync.py
--- a/authentik/sources/ldap/tests/test_sync.py
+++ b/authentik/sources/ldap/tests/test_sync.py
@@ -9,7 +9,7 @@
 from authentik.core.tests.utils import create_test_admin_user
 from authentik.events.models import Event, EventAction
 from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus
-from authentik.lib.generators import generate_key
+from authentik.lib.generators import generate_id, generate_key
 from authentik.lib.utils.reflection import class_to_path
 from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
 from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
@@ -71,6 +71,28 @@ def test_sync_error(self):
         )
         self.assertTrue(events.exists())
 
+    def test_sync_mapping(self):
+        """Test property mappings"""
+        none = LDAPPropertyMapping.objects.create(
+            name=generate_id(), object_field="none", expression="return None"
+        )
+        byte_mapping = LDAPPropertyMapping.objects.create(
+            name=generate_id(), object_field="bytes", expression="return b''"
+        )
+        self.source.property_mappings.set(
+            LDAPPropertyMapping.objects.filter(
+                Q(managed__startswith="goauthentik.io/sources/ldap/default")
+                | Q(managed__startswith="goauthentik.io/sources/ldap/ms")
+            )
+        )
+        self.source.property_mappings.add(none, byte_mapping)
+        connection = MagicMock(return_value=mock_ad_connection(LDAP_PASSWORD))
+
+        # we basically just test that the mappings don't throw errors
+        with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
+            user_sync = UserLDAPSynchronizer(self.source)
+            user_sync.sync_full()
+
     def test_sync_users_ad(self):
         """Test user sync"""
         self.source.property_mappings.set(
@@ -79,7 +101,6 @@ def test_sync_users_ad(self):
                 | Q(managed__startswith="goauthentik.io/sources/ldap/ms")
             )
         )
-        self.source.save()
         connection = MagicMock(return_value=mock_ad_connection(LDAP_PASSWORD))
 
         # Create the user beforehand so we can set attributes and check they aren't removed
@@ -113,7 +134,6 @@ def test_sync_users_openldap(self):
                 | Q(managed__startswith="goauthentik.io/sources/ldap/openldap")
             )
         )
-        self.source.save()
         connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
         with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
             user_sync = UserLDAPSynchronizer(self.source)
@@ -130,7 +150,6 @@ def test_sync_users_freeipa_ish(self):
                 | Q(managed__startswith="goauthentik.io/sources/ldap/openldap")
             )
         )
-        self.source.save()
         connection = MagicMock(return_value=mock_freeipa_connection(LDAP_PASSWORD))
         with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
             user_sync = UserLDAPSynchronizer(self.source)