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 typing import Any | ||||||
|  |  | ||||||
| from django.core.cache import cache | from django.core.cache import cache | ||||||
|  | from django.utils.translation import gettext_lazy as _ | ||||||
| from drf_spectacular.utils import extend_schema, inline_serializer | from drf_spectacular.utils import extend_schema, inline_serializer | ||||||
| from guardian.shortcuts import get_objects_for_user | from guardian.shortcuts import get_objects_for_user | ||||||
| from rest_framework.decorators import action | from rest_framework.decorators import action | ||||||
| @ -39,9 +40,8 @@ class LDAPSourceSerializer(SourceSerializer): | |||||||
|         """Get cached source connectivity""" |         """Get cached source connectivity""" | ||||||
|         return cache.get(CACHE_KEY_STATUS + source.slug, None) |         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""" |         """Check that only a single source has password_sync on""" | ||||||
|         sync_users_password = attrs.get("sync_users_password", True) |  | ||||||
|         if sync_users_password: |         if sync_users_password: | ||||||
|             sources = LDAPSource.objects.filter(sync_users_password=True) |             sources = LDAPSource.objects.filter(sync_users_password=True) | ||||||
|             if self.instance: |             if self.instance: | ||||||
| @ -49,11 +49,31 @@ class LDAPSourceSerializer(SourceSerializer): | |||||||
|             if sources.exists(): |             if sources.exists(): | ||||||
|                 raise ValidationError( |                 raise ValidationError( | ||||||
|                     { |                     { | ||||||
|                         "sync_users_password": ( |                         "sync_users_password": _( | ||||||
|                             "Only a single LDAP Source with password synchronization is allowed" |                             "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) |         return super().validate(attrs) | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
| @ -166,7 +186,8 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet): | |||||||
|         for sync_class in SYNC_CLASSES: |         for sync_class in SYNC_CLASSES: | ||||||
|             class_name = sync_class.name() |             class_name = sync_class.name() | ||||||
|             all_objects.setdefault(class_name, []) |             all_objects.setdefault(class_name, []) | ||||||
|             for obj in sync_class(source).get_objects(size_limit=10): |             for page in sync_class(source).get_objects(size_limit=10): | ||||||
|  |                 for obj in page: | ||||||
|                     obj: dict |                     obj: dict | ||||||
|                     obj.pop("raw_attributes", None) |                     obj.pop("raw_attributes", None) | ||||||
|                     obj.pop("raw_dn", None) |                     obj.pop("raw_dn", None) | ||||||
|  | |||||||
| @ -26,17 +26,16 @@ def sync_ldap_source_on_save(sender, instance: LDAPSource, **_): | |||||||
|     """Ensure that source is synced on save (if enabled)""" |     """Ensure that source is synced on save (if enabled)""" | ||||||
|     if not instance.enabled: |     if not instance.enabled: | ||||||
|         return |         return | ||||||
|  |     ldap_connectivity_check.delay(instance.pk) | ||||||
|     # Don't sync sources when they don't have any property mappings. This will only happen if: |     # 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 user forgets to set them or | ||||||
|     # - the source is newly created, this is the first save event |     # - the source is newly created, this is the first save event | ||||||
|     #   and the mappings are created with an m2m event |     #   and the mappings are created with an m2m event | ||||||
|     if ( |     if instance.sync_users and not instance.user_property_mappings.exists(): | ||||||
|         not instance.user_property_mappings.exists() |         return | ||||||
|         or not instance.group_property_mappings.exists() |     if instance.sync_groups and not instance.group_property_mappings.exists(): | ||||||
|     ): |  | ||||||
|         return |         return | ||||||
|     ldap_sync_single.delay(instance.pk) |     ldap_sync_single.delay(instance.pk) | ||||||
|     ldap_connectivity_check.delay(instance.pk) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @receiver(password_validate) | @receiver(password_validate) | ||||||
|  | |||||||
| @ -50,3 +50,35 @@ class LDAPAPITests(APITestCase): | |||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|         self.assertFalse(serializer.is_valid()) |         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> |             <ak-form-group> | ||||||
|                 <span slot="header"> ${msg("Additional settings")} </span> |                 <span slot="header"> ${msg("Additional settings")} </span> | ||||||
|                 <div slot="body" class="pf-c-form"> |                 <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 |                         <ak-search-select | ||||||
|                             .fetchObjects=${async (query?: string): Promise<Group[]> => { |                             .fetchObjects=${async (query?: string): Promise<Group[]> => { | ||||||
|                                 const args: CoreGroupsListRequest = { |                                 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. | -   **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. | -   **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 | #### Connection settings | ||||||
|  |  | ||||||
| @ -45,7 +45,11 @@ To create or edit a source in authentik, open the Admin interface and navigate t | |||||||
|  |  | ||||||
| #### LDAP Attribute mapping | #### 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 | #### Additional Settings | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user