sources/ldap: fix mapping check, fix debug endpoint (#11442) * run connectivity check always * don't run sync if either sync_ option is enabled and no mappings are set * misc label fix * misc writing changse * add api validation * fix debug endpoint --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: Jens L. <jens@goauthentik.io>
This commit is contained in:
		![98988430+gcp-cherry-pick-bot[bot]@users.noreply.github.com](/assets/img/avatar_default.png) gcp-cherry-pick-bot[bot]
					gcp-cherry-pick-bot[bot]
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							4fa71d995d
						
					
				
				
					commit
					18078fd68f
				
			| @ -3,6 +3,7 @@ | ||||
| from typing import Any | ||||
|  | ||||
| from django.core.cache import cache | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from drf_spectacular.utils import extend_schema, inline_serializer | ||||
| from guardian.shortcuts import get_objects_for_user | ||||
| from rest_framework.decorators import action | ||||
| @ -39,9 +40,8 @@ class LDAPSourceSerializer(SourceSerializer): | ||||
|         """Get cached source connectivity""" | ||||
|         return cache.get(CACHE_KEY_STATUS + source.slug, None) | ||||
|  | ||||
|     def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: | ||||
|     def validate_sync_users_password(self, sync_users_password: bool) -> bool: | ||||
|         """Check that only a single source has password_sync on""" | ||||
|         sync_users_password = attrs.get("sync_users_password", True) | ||||
|         if sync_users_password: | ||||
|             sources = LDAPSource.objects.filter(sync_users_password=True) | ||||
|             if self.instance: | ||||
| @ -49,11 +49,31 @@ class LDAPSourceSerializer(SourceSerializer): | ||||
|             if sources.exists(): | ||||
|                 raise ValidationError( | ||||
|                     { | ||||
|                         "sync_users_password": ( | ||||
|                         "sync_users_password": _( | ||||
|                             "Only a single LDAP Source with password synchronization is allowed" | ||||
|                         ) | ||||
|                     } | ||||
|                 ) | ||||
|         return sync_users_password | ||||
|  | ||||
|     def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: | ||||
|         """Validate property mappings with sync_ flags""" | ||||
|         types = ["user", "group"] | ||||
|         for type in types: | ||||
|             toggle_value = attrs.get(f"sync_{type}s", False) | ||||
|             mappings_field = f"{type}_property_mappings" | ||||
|             mappings_value = attrs.get(mappings_field, []) | ||||
|             if toggle_value and len(mappings_value) == 0: | ||||
|                 raise ValidationError( | ||||
|                     { | ||||
|                         mappings_field: _( | ||||
|                             ( | ||||
|                                 "When 'Sync {type}s' is enabled, '{type}s property " | ||||
|                                 "mappings' cannot be empty." | ||||
|                             ).format(type=type) | ||||
|                         ) | ||||
|                     } | ||||
|                 ) | ||||
|         return super().validate(attrs) | ||||
|  | ||||
|     class Meta: | ||||
| @ -166,11 +186,12 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet): | ||||
|         for sync_class in SYNC_CLASSES: | ||||
|             class_name = sync_class.name() | ||||
|             all_objects.setdefault(class_name, []) | ||||
|             for obj in sync_class(source).get_objects(size_limit=10): | ||||
|                 obj: dict | ||||
|                 obj.pop("raw_attributes", None) | ||||
|                 obj.pop("raw_dn", None) | ||||
|                 all_objects[class_name].append(obj) | ||||
|             for page in sync_class(source).get_objects(size_limit=10): | ||||
|                 for obj in page: | ||||
|                     obj: dict | ||||
|                     obj.pop("raw_attributes", None) | ||||
|                     obj.pop("raw_dn", None) | ||||
|                     all_objects[class_name].append(obj) | ||||
|         return Response(data=all_objects) | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -26,17 +26,16 @@ def sync_ldap_source_on_save(sender, instance: LDAPSource, **_): | ||||
|     """Ensure that source is synced on save (if enabled)""" | ||||
|     if not instance.enabled: | ||||
|         return | ||||
|     ldap_connectivity_check.delay(instance.pk) | ||||
|     # Don't sync sources when they don't have any property mappings. This will only happen if: | ||||
|     # - the user forgets to set them or | ||||
|     # - the source is newly created, this is the first save event | ||||
|     #   and the mappings are created with an m2m event | ||||
|     if ( | ||||
|         not instance.user_property_mappings.exists() | ||||
|         or not instance.group_property_mappings.exists() | ||||
|     ): | ||||
|     if instance.sync_users and not instance.user_property_mappings.exists(): | ||||
|         return | ||||
|     if instance.sync_groups and not instance.group_property_mappings.exists(): | ||||
|         return | ||||
|     ldap_sync_single.delay(instance.pk) | ||||
|     ldap_connectivity_check.delay(instance.pk) | ||||
|  | ||||
|  | ||||
| @receiver(password_validate) | ||||
|  | ||||
| @ -50,3 +50,35 @@ class LDAPAPITests(APITestCase): | ||||
|             } | ||||
|         ) | ||||
|         self.assertFalse(serializer.is_valid()) | ||||
|  | ||||
|     def test_sync_users_mapping_empty(self): | ||||
|         """Check that when sync_users is enabled, property mappings must be set""" | ||||
|         serializer = LDAPSourceSerializer( | ||||
|             data={ | ||||
|                 "name": "foo", | ||||
|                 "slug": " foo", | ||||
|                 "server_uri": "ldaps://1.2.3.4", | ||||
|                 "bind_cn": "", | ||||
|                 "bind_password": LDAP_PASSWORD, | ||||
|                 "base_dn": "dc=foo", | ||||
|                 "sync_users": True, | ||||
|                 "user_property_mappings": [], | ||||
|             } | ||||
|         ) | ||||
|         self.assertFalse(serializer.is_valid()) | ||||
|  | ||||
|     def test_sync_groups_mapping_empty(self): | ||||
|         """Check that when sync_groups is enabled, property mappings must be set""" | ||||
|         serializer = LDAPSourceSerializer( | ||||
|             data={ | ||||
|                 "name": "foo", | ||||
|                 "slug": " foo", | ||||
|                 "server_uri": "ldaps://1.2.3.4", | ||||
|                 "bind_cn": "", | ||||
|                 "bind_password": LDAP_PASSWORD, | ||||
|                 "base_dn": "dc=foo", | ||||
|                 "sync_groups": True, | ||||
|                 "group_property_mappings": [], | ||||
|             } | ||||
|         ) | ||||
|         self.assertFalse(serializer.is_valid()) | ||||
|  | ||||
| @ -327,7 +327,7 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> { | ||||
|             <ak-form-group> | ||||
|                 <span slot="header"> ${msg("Additional settings")} </span> | ||||
|                 <div slot="body" class="pf-c-form"> | ||||
|                     <ak-form-element-horizontal label=${msg("Group")} name="syncParentGroup"> | ||||
|                     <ak-form-element-horizontal label=${msg("Parent Group")} name="syncParentGroup"> | ||||
|                         <ak-search-select | ||||
|                             .fetchObjects=${async (query?: string): Promise<Group[]> => { | ||||
|                                 const args: CoreGroupsListRequest = { | ||||
|  | ||||
| @ -18,13 +18,13 @@ To create or edit a source in authentik, open the Admin interface and navigate t | ||||
|  | ||||
| -   **Update internal password on login**: When the user logs in to authentik using the LDAP password backend, the password is stored as a hashed value in authentik. Toggle off (default setting) if you do not want to store the hashed passwords in authentik. | ||||
|  | ||||
| -   **Synch User**: Enable or disable user synchronization between authentik and the LDAP source. | ||||
| -   **Sync users**: Enable or disable user synchronization between authentik and the LDAP source. | ||||
|  | ||||
| -   **User password writeback**: Enable this option if you want to write password changes that are made in authentik back to LDAP. | ||||
|  | ||||
| -   **Synch groups**: Enable/disable group synchronization. Groups are synced in the background every 5 minutes. | ||||
| -   **Sync groups**: Enable/disable group synchronization. Groups are synced in the background every 5 minutes. | ||||
|  | ||||
| -   **Sync parent group**: Optionally set this group as the parent group for all synced groups. An example use case of this would be to import Active Directory groups under a root `imported-from-ad` group. | ||||
| -   **Parent group**: Optionally set this group as the parent group for all synced groups. An example use case of this would be to import Active Directory groups under a root `imported-from-ad` group. | ||||
|  | ||||
| #### Connection settings | ||||
|  | ||||
| @ -45,7 +45,11 @@ To create or edit a source in authentik, open the Admin interface and navigate t | ||||
|  | ||||
| #### LDAP Attribute mapping | ||||
|  | ||||
| -   **User Property mappings** and **Group Property Mappings**: Define which LDAP properties map to which authentik properties. The default set of property mappings is generated for Active Directory. See also our documentation on [property mappings](#ldap-source-property-mappings). | ||||
| -   **User Property Mappings** and **Group Property Mappings**: Define which LDAP properties map to which authentik properties. The default set of property mappings is generated for Active Directory. See also our documentation on [property mappings](#ldap-source-property-mappings). | ||||
|  | ||||
|     :::warning | ||||
|     When the **Sync users** and or the **Sync groups** options are enabled, their respective property mapping options must have at least one mapping selected, otherwise the sync will not start. | ||||
|     ::: | ||||
|  | ||||
| #### Additional Settings | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user