Compare commits
	
		
			4 Commits
		
	
	
		
			version/20
			...
			sources/ld
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2a6479062f | |||
| 52463b8f96 | |||
| 330f639a7e | |||
| 85ea4651e4 | 
							
								
								
									
										41
									
								
								authentik/core/migrations/0032_groupsourceconnection.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								authentik/core/migrations/0032_groupsourceconnection.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| # Generated by Django 4.2.5 on 2023-09-27 10:44 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|     dependencies = [ | ||||
|         ("authentik_core", "0031_alter_user_type"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name="GroupSourceConnection", | ||||
|             fields=[ | ||||
|                 ( | ||||
|                     "id", | ||||
|                     models.AutoField( | ||||
|                         auto_created=True, primary_key=True, serialize=False, verbose_name="ID" | ||||
|                     ), | ||||
|                 ), | ||||
|                 ("created", models.DateTimeField(auto_now_add=True)), | ||||
|                 ("last_updated", models.DateTimeField(auto_now=True)), | ||||
|                 ( | ||||
|                     "group", | ||||
|                     models.ForeignKey( | ||||
|                         on_delete=django.db.models.deletion.CASCADE, to="authentik_core.group" | ||||
|                     ), | ||||
|                 ), | ||||
|                 ( | ||||
|                     "source", | ||||
|                     models.ForeignKey( | ||||
|                         on_delete=django.db.models.deletion.CASCADE, to="authentik_core.source" | ||||
|                     ), | ||||
|                 ), | ||||
|             ], | ||||
|             options={ | ||||
|                 "unique_together": {("group", "source")}, | ||||
|             }, | ||||
|         ), | ||||
|     ] | ||||
| @ -575,6 +575,23 @@ class UserSourceConnection(SerializerModel, CreatedUpdatedModel): | ||||
|         unique_together = (("user", "source"),) | ||||
|  | ||||
|  | ||||
| class GroupSourceConnection(SerializerModel, CreatedUpdatedModel): | ||||
|     """Connection between Group and Source.""" | ||||
|  | ||||
|     group = models.ForeignKey(Group, on_delete=models.CASCADE) | ||||
|     source = models.ForeignKey(Source, on_delete=models.CASCADE) | ||||
|  | ||||
|     objects = InheritanceManager() | ||||
|  | ||||
|     @property | ||||
|     def serializer(self) -> type[Serializer]: | ||||
|         """Get serializer for this model""" | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     class Meta: | ||||
|         unique_together = (("group", "source"),) | ||||
|  | ||||
|  | ||||
| class ExpiringModel(models.Model): | ||||
|     """Base Model which can expire, and is automatically cleaned up.""" | ||||
|  | ||||
|  | ||||
							
								
								
									
										0
									
								
								authentik/sources/ldap/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								authentik/sources/ldap/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										40
									
								
								authentik/sources/ldap/api/property_mappings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								authentik/sources/ldap/api/property_mappings.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| """Property mapping API Views""" | ||||
| from django_filters.filters import AllValuesMultipleFilter | ||||
| from django_filters.filterset import FilterSet | ||||
| from drf_spectacular.types import OpenApiTypes | ||||
| from drf_spectacular.utils import extend_schema_field | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
|  | ||||
| from authentik.core.api.propertymappings import PropertyMappingSerializer | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.sources.ldap.models import LDAPPropertyMapping | ||||
|  | ||||
|  | ||||
| class LDAPPropertyMappingSerializer(PropertyMappingSerializer): | ||||
|     """LDAP PropertyMapping Serializer""" | ||||
|  | ||||
|     class Meta: | ||||
|         model = LDAPPropertyMapping | ||||
|         fields = PropertyMappingSerializer.Meta.fields + [ | ||||
|             "object_field", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class LDAPPropertyMappingFilter(FilterSet): | ||||
|     """Filter for LDAPPropertyMapping""" | ||||
|  | ||||
|     managed = extend_schema_field(OpenApiTypes.STR)(AllValuesMultipleFilter(field_name="managed")) | ||||
|  | ||||
|     class Meta: | ||||
|         model = LDAPPropertyMapping | ||||
|         fields = "__all__" | ||||
|  | ||||
|  | ||||
| class LDAPPropertyMappingViewSet(UsedByMixin, ModelViewSet): | ||||
|     """LDAP PropertyMapping Viewset""" | ||||
|  | ||||
|     queryset = LDAPPropertyMapping.objects.all() | ||||
|     serializer_class = LDAPPropertyMappingSerializer | ||||
|     filterset_class = LDAPPropertyMappingFilter | ||||
|     search_fields = ["name"] | ||||
|     ordering = ["name"] | ||||
							
								
								
									
										32
									
								
								authentik/sources/ldap/api/source_connections.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								authentik/sources/ldap/api/source_connections.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| """LDAP Source Serializer""" | ||||
| from django_filters.rest_framework import DjangoFilterBackend | ||||
| from rest_framework.filters import OrderingFilter, SearchFilter | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
|  | ||||
| from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions | ||||
| from authentik.core.api.sources import UserSourceConnectionSerializer | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.sources.ldap.models import LDAPUserSourceConnection | ||||
|  | ||||
|  | ||||
| class LDAPUserSourceConnectionSerializer(UserSourceConnectionSerializer): | ||||
|     """LDAP Source Serializer""" | ||||
|  | ||||
|     class Meta: | ||||
|         model = LDAPUserSourceConnection | ||||
|         fields = ["pk", "user", "source", "unique_identifier"] | ||||
|         extra_kwargs = { | ||||
|             "access_token": {"write_only": True}, | ||||
|         } | ||||
|  | ||||
|  | ||||
| class LDAPUserSourceConnectionViewSet(UsedByMixin, ModelViewSet): | ||||
|     """Source Viewset""" | ||||
|  | ||||
|     queryset = LDAPUserSourceConnection.objects.all() | ||||
|     serializer_class = LDAPUserSourceConnectionSerializer | ||||
|     filterset_fields = ["source__slug"] | ||||
|     search_fields = ["source__slug"] | ||||
|     permission_classes = [OwnerSuperuserPermissions] | ||||
|     filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter] | ||||
|     ordering = ["source__slug"] | ||||
| @ -1,10 +1,7 @@ | ||||
| """Source API Views""" | ||||
| from typing import Any | ||||
| 
 | ||||
| from django_filters.filters import AllValuesMultipleFilter | ||||
| from django_filters.filterset import FilterSet | ||||
| from drf_spectacular.types import OpenApiTypes | ||||
| from drf_spectacular.utils import extend_schema, extend_schema_field, inline_serializer | ||||
| from drf_spectacular.utils import extend_schema, inline_serializer | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.exceptions import ValidationError | ||||
| from rest_framework.fields import DictField, ListField | ||||
| @ -14,12 +11,11 @@ from rest_framework.response import Response | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
| 
 | ||||
| from authentik.admin.api.tasks import TaskSerializer | ||||
| from authentik.core.api.propertymappings import PropertyMappingSerializer | ||||
| from authentik.core.api.sources import SourceSerializer | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.crypto.models import CertificateKeyPair | ||||
| from authentik.events.monitored_tasks import TaskInfo | ||||
| from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource | ||||
| from authentik.sources.ldap.models import LDAPSource | ||||
| from authentik.sources.ldap.tasks import SYNC_CLASSES | ||||
| 
 | ||||
| 
 | ||||
| @ -154,33 +150,3 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet): | ||||
|                 obj.pop("raw_dn", None) | ||||
|                 all_objects[class_name].append(obj) | ||||
|         return Response(data=all_objects) | ||||
| 
 | ||||
| 
 | ||||
| class LDAPPropertyMappingSerializer(PropertyMappingSerializer): | ||||
|     """LDAP PropertyMapping Serializer""" | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = LDAPPropertyMapping | ||||
|         fields = PropertyMappingSerializer.Meta.fields + [ | ||||
|             "object_field", | ||||
|         ] | ||||
| 
 | ||||
| 
 | ||||
| class LDAPPropertyMappingFilter(FilterSet): | ||||
|     """Filter for LDAPPropertyMapping""" | ||||
| 
 | ||||
|     managed = extend_schema_field(OpenApiTypes.STR)(AllValuesMultipleFilter(field_name="managed")) | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = LDAPPropertyMapping | ||||
|         fields = "__all__" | ||||
| 
 | ||||
| 
 | ||||
| class LDAPPropertyMappingViewSet(UsedByMixin, ModelViewSet): | ||||
|     """LDAP PropertyMapping Viewset""" | ||||
| 
 | ||||
|     queryset = LDAPPropertyMapping.objects.all() | ||||
|     serializer_class = LDAPPropertyMappingSerializer | ||||
|     filterset_class = LDAPPropertyMappingFilter | ||||
|     search_fields = ["name"] | ||||
|     ordering = ["name"] | ||||
| @ -0,0 +1,58 @@ | ||||
| # Generated by Django 4.2.5 on 2023-09-27 10:44 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|     dependencies = [ | ||||
|         ("authentik_core", "0032_groupsourceconnection"), | ||||
|         ("authentik_sources_ldap", "0003_ldapsource_client_certificate_ldapsource_sni_and_more"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name="LDAPGroupSourceConnection", | ||||
|             fields=[ | ||||
|                 ( | ||||
|                     "groupsourceconnection_ptr", | ||||
|                     models.OneToOneField( | ||||
|                         auto_created=True, | ||||
|                         on_delete=django.db.models.deletion.CASCADE, | ||||
|                         parent_link=True, | ||||
|                         primary_key=True, | ||||
|                         serialize=False, | ||||
|                         to="authentik_core.groupsourceconnection", | ||||
|                     ), | ||||
|                 ), | ||||
|                 ("unique_identifier", models.TextField(unique=True)), | ||||
|             ], | ||||
|             options={ | ||||
|                 "verbose_name": "LDAP Group Source Connection", | ||||
|                 "verbose_name_plural": "LDAP Group Source Connections", | ||||
|             }, | ||||
|             bases=("authentik_core.groupsourceconnection",), | ||||
|         ), | ||||
|         migrations.CreateModel( | ||||
|             name="LDAPUserSourceConnection", | ||||
|             fields=[ | ||||
|                 ( | ||||
|                     "usersourceconnection_ptr", | ||||
|                     models.OneToOneField( | ||||
|                         auto_created=True, | ||||
|                         on_delete=django.db.models.deletion.CASCADE, | ||||
|                         parent_link=True, | ||||
|                         primary_key=True, | ||||
|                         serialize=False, | ||||
|                         to="authentik_core.usersourceconnection", | ||||
|                     ), | ||||
|                 ), | ||||
|                 ("unique_identifier", models.TextField(unique=True)), | ||||
|             ], | ||||
|             options={ | ||||
|                 "verbose_name": "LDAP User Source Connection", | ||||
|                 "verbose_name_plural": "LDAP User Source Connections", | ||||
|             }, | ||||
|             bases=("authentik_core.usersourceconnection",), | ||||
|         ), | ||||
|     ] | ||||
| @ -10,7 +10,13 @@ from ldap3 import ALL, NONE, RANDOM, Connection, Server, ServerPool, Tls | ||||
| from ldap3.core.exceptions import LDAPInsufficientAccessRightsResult, LDAPSchemaError | ||||
| from rest_framework.serializers import Serializer | ||||
|  | ||||
| from authentik.core.models import Group, PropertyMapping, Source | ||||
| from authentik.core.models import ( | ||||
|     Group, | ||||
|     GroupSourceConnection, | ||||
|     PropertyMapping, | ||||
|     Source, | ||||
|     UserSourceConnection, | ||||
| ) | ||||
| from authentik.crypto.models import CertificateKeyPair | ||||
| from authentik.lib.config import CONFIG | ||||
| from authentik.lib.models import DomainlessURLValidator | ||||
| @ -113,7 +119,7 @@ class LDAPSource(Source): | ||||
|  | ||||
|     @property | ||||
|     def serializer(self) -> type[Serializer]: | ||||
|         from authentik.sources.ldap.api import LDAPSourceSerializer | ||||
|         from authentik.sources.ldap.api.sources import LDAPSourceSerializer | ||||
|  | ||||
|         return LDAPSourceSerializer | ||||
|  | ||||
| @ -202,7 +208,7 @@ class LDAPPropertyMapping(PropertyMapping): | ||||
|  | ||||
|     @property | ||||
|     def serializer(self) -> type[Serializer]: | ||||
|         from authentik.sources.ldap.api import LDAPPropertyMappingSerializer | ||||
|         from authentik.sources.ldap.api.property_mappings import LDAPPropertyMappingSerializer | ||||
|  | ||||
|         return LDAPPropertyMappingSerializer | ||||
|  | ||||
| @ -212,3 +218,35 @@ class LDAPPropertyMapping(PropertyMapping): | ||||
|     class Meta: | ||||
|         verbose_name = _("LDAP Property Mapping") | ||||
|         verbose_name_plural = _("LDAP Property Mappings") | ||||
|  | ||||
|  | ||||
| class LDAPUserSourceConnection(UserSourceConnection): | ||||
|     """Connection between an authentik user and an LDAP source.""" | ||||
|  | ||||
|     unique_identifier = models.TextField(unique=True) | ||||
|  | ||||
|     @property | ||||
|     def serializer(self) -> Serializer: | ||||
|         from authentik.sources.ldap.api.source_connections import LDAPUserSourceConnectionSerializer | ||||
|  | ||||
|         return LDAPUserSourceConnectionSerializer | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("LDAP User Source Connection") | ||||
|         verbose_name_plural = _("LDAP User Source Connections") | ||||
|  | ||||
|  | ||||
| class LDAPGroupSourceConnection(GroupSourceConnection): | ||||
|     """Connection between an authentik group and an LDAP source.""" | ||||
|  | ||||
|     unique_identifier = models.TextField(unique=True) | ||||
|  | ||||
|     @property | ||||
|     def serializer(self) -> Serializer: | ||||
|         from authentik.sources.ldap.api.source_connections import LDAPUserSourceConnectionSerializer | ||||
|  | ||||
|         return LDAPUserSourceConnectionSerializer | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("LDAP Group Source Connection") | ||||
|         verbose_name_plural = _("LDAP Group Source Connections") | ||||
|  | ||||
| @ -7,6 +7,7 @@ from ldap3 import ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, SUBTREE | ||||
|  | ||||
| from authentik.core.models import Group | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.sources.ldap.models import LDAPGroupSourceConnection | ||||
| from authentik.sources.ldap.sync.base import LDAP_UNIQUENESS, BaseLDAPSynchronizer | ||||
|  | ||||
|  | ||||
| @ -63,7 +64,13 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer): | ||||
|                     }, | ||||
|                     defaults, | ||||
|                 ) | ||||
|                 self._logger.debug("Created group with attributes", **defaults) | ||||
|                 LDAPGroupSourceConnection.objects.update_or_create( | ||||
|                     defaults={ | ||||
|                         "unique_identifier": uniq, | ||||
|                     }, | ||||
|                     source=self._source, | ||||
|                     group=ak_group, | ||||
|                 ) | ||||
|             except (IntegrityError, FieldError, TypeError, AttributeError) as exc: | ||||
|                 Event.new( | ||||
|                     EventAction.CONFIGURATION_ERROR, | ||||
|  | ||||
| @ -7,6 +7,7 @@ from ldap3 import ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, SUBTREE | ||||
|  | ||||
| from authentik.core.models import User | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.sources.ldap.models import LDAPUserSourceConnection | ||||
| from authentik.sources.ldap.sync.base import LDAP_UNIQUENESS, BaseLDAPSynchronizer | ||||
| from authentik.sources.ldap.sync.vendor.freeipa import FreeIPA | ||||
| from authentik.sources.ldap.sync.vendor.ms_ad import MicrosoftActiveDirectory | ||||
| @ -58,6 +59,13 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer): | ||||
|                 ak_user, created = self.update_or_create_attributes( | ||||
|                     User, {f"attributes__{LDAP_UNIQUENESS}": uniq}, defaults | ||||
|                 ) | ||||
|                 LDAPUserSourceConnection.objects.update_or_create( | ||||
|                     defaults={ | ||||
|                         "unique_identifier": uniq, | ||||
|                     }, | ||||
|                     source=self._source, | ||||
|                     user=ak_user, | ||||
|                 ) | ||||
|             except (IntegrityError, FieldError, TypeError, AttributeError) as exc: | ||||
|                 Event.new( | ||||
|                     EventAction.CONFIGURATION_ERROR, | ||||
| @ -72,6 +80,7 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer): | ||||
|             else: | ||||
|                 self._logger.debug("Synced User", user=ak_user.username, created=created) | ||||
|                 user_count += 1 | ||||
|                 # TODO: Optimise vendor sync to not create a new connection | ||||
|                 MicrosoftActiveDirectory(self._source).sync(attributes, ak_user, created) | ||||
|                 FreeIPA(self._source).sync(attributes, ak_user, created) | ||||
|         return user_count | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
| from rest_framework.test import APITestCase | ||||
|  | ||||
| from authentik.lib.generators import generate_key | ||||
| from authentik.sources.ldap.api import LDAPSourceSerializer | ||||
| from authentik.sources.ldap.api.sources import LDAPSourceSerializer | ||||
| from authentik.sources.ldap.models import LDAPSource | ||||
|  | ||||
| LDAP_PASSWORD = generate_key() | ||||
|  | ||||
| @ -1,7 +1,10 @@ | ||||
| """API URLs""" | ||||
| from authentik.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet | ||||
| from authentik.sources.ldap.api.property_mappings import LDAPPropertyMappingViewSet | ||||
| from authentik.sources.ldap.api.source_connections import LDAPUserSourceConnectionViewSet | ||||
| from authentik.sources.ldap.api.sources import LDAPSourceViewSet | ||||
|  | ||||
| api_urlpatterns = [ | ||||
|     ("propertymappings/ldap", LDAPPropertyMappingViewSet), | ||||
|     ("sources/user_connections/ldap", LDAPUserSourceConnectionViewSet), | ||||
|     ("sources/ldap", LDAPSourceViewSet), | ||||
| ] | ||||
|  | ||||
| @ -68,7 +68,7 @@ class OAuthSource(Source): | ||||
|     # we're using Type[] instead of type[] here since type[] interferes with the property above | ||||
|     @property | ||||
|     def serializer(self) -> Type[Serializer]: | ||||
|         from authentik.sources.oauth.api.source import OAuthSourceSerializer | ||||
|         from authentik.sources.oauth.api.sources import OAuthSourceSerializer | ||||
|  | ||||
|         return OAuthSourceSerializer | ||||
|  | ||||
| @ -234,7 +234,7 @@ class UserOAuthSourceConnection(UserSourceConnection): | ||||
|  | ||||
|     @property | ||||
|     def serializer(self) -> Serializer: | ||||
|         from authentik.sources.oauth.api.source_connection import ( | ||||
|         from authentik.sources.oauth.api.source_connections import ( | ||||
|             UserOAuthSourceConnectionSerializer, | ||||
|         ) | ||||
|  | ||||
|  | ||||
| @ -3,7 +3,7 @@ from django.test import TestCase | ||||
| from django.urls import reverse | ||||
| from requests_mock import Mocker | ||||
|  | ||||
| from authentik.sources.oauth.api.source import OAuthSourceSerializer | ||||
| from authentik.sources.oauth.api.sources import OAuthSourceSerializer | ||||
| from authentik.sources.oauth.models import OAuthSource | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -2,8 +2,8 @@ | ||||
|  | ||||
| from django.urls import path | ||||
|  | ||||
| from authentik.sources.oauth.api.source import OAuthSourceViewSet | ||||
| from authentik.sources.oauth.api.source_connection import UserOAuthSourceConnectionViewSet | ||||
| from authentik.sources.oauth.api.source_connections import UserOAuthSourceConnectionViewSet | ||||
| from authentik.sources.oauth.api.sources import OAuthSourceViewSet | ||||
| from authentik.sources.oauth.types.registry import RequestKind | ||||
| from authentik.sources.oauth.views.dispatcher import DispatcherView | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	