Skip to content

Commit

Permalink
sources: add property mappings for all oauth and saml sources (#8771)
Browse files Browse the repository at this point in the history
Co-authored-by: Jens L. <[email protected]>
  • Loading branch information
rissson and BeryJu authored Aug 7, 2024
1 parent 78bae55 commit 83b02a1
Show file tree
Hide file tree
Showing 64 changed files with 3,856 additions and 539 deletions.
2 changes: 2 additions & 0 deletions authentik/blueprints/v1/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from authentik.blueprints.v1.meta.registry import BaseMetaModel, registry
from authentik.core.models import (
AuthenticatedSession,
GroupSourceConnection,
PropertyMapping,
Provider,
Source,
Expand Down Expand Up @@ -91,6 +92,7 @@ def excluded_models() -> list[type[Model]]:
Source,
PropertyMapping,
UserSourceConnection,
GroupSourceConnection,
Stage,
OutpostServiceConnection,
Policy,
Expand Down
42 changes: 41 additions & 1 deletion authentik/core/api/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from authentik.core.api.object_types import TypesMixin
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import MetaNameSerializer, ModelSerializer
from authentik.core.models import Source, UserSourceConnection
from authentik.core.models import GroupSourceConnection, Source, UserSourceConnection
from authentik.core.types import UserSettingSerializer
from authentik.lib.utils.file import (
FilePathSerializer,
Expand Down Expand Up @@ -194,3 +194,43 @@ class UserSourceConnectionViewSet(
search_fields = ["source__slug"]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
ordering = ["source__slug", "pk"]


class GroupSourceConnectionSerializer(SourceSerializer):
"""Group Source Connection Serializer"""

source = SourceSerializer(read_only=True)

class Meta:
model = GroupSourceConnection
fields = [
"pk",
"group",
"source",
"identifier",
"created",
]
extra_kwargs = {
"group": {"read_only": True},
"identifier": {"read_only": True},
"created": {"read_only": True},
}


class GroupSourceConnectionViewSet(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):
"""Group-source connection Viewset"""

queryset = GroupSourceConnection.objects.all()
serializer_class = GroupSourceConnectionSerializer
permission_classes = [OwnerSuperuserPermissions]
filterset_fields = ["group", "source__slug"]
search_fields = ["source__slug"]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
ordering = ["source__slug", "pk"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Generated by Django 5.0.7 on 2024-08-01 18:52

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("authentik_core", "0038_source_authentik_c_enabled_d72365_idx"),
]

operations = [
migrations.AddField(
model_name="source",
name="group_matching_mode",
field=models.TextField(
choices=[
("identifier", "Use the source-specific identifier"),
(
"name_link",
"Link to a group with identical name. Can have security implications when a group name is used with another source.",
),
(
"name_deny",
"Use the group name, but deny enrollment when the name already exists.",
),
],
default="identifier",
help_text="How the source determines if an existing group should be used or a new group created.",
),
),
migrations.AlterField(
model_name="group",
name="name",
field=models.TextField(verbose_name="name"),
),
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)),
("identifier", models.TextField()),
(
"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")},
},
),
]
44 changes: 43 additions & 1 deletion authentik/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ class Group(SerializerModel, AttributesMixin):

group_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)

name = models.CharField(_("name"), max_length=80)
name = models.TextField(_("name"))
is_superuser = models.BooleanField(
default=False, help_text=_("Users added to this group will be superusers.")
)
Expand Down Expand Up @@ -583,6 +583,19 @@ class SourceUserMatchingModes(models.TextChoices):
)


class SourceGroupMatchingModes(models.TextChoices):
"""Different modes a source can handle new/returning groups"""

IDENTIFIER = "identifier", _("Use the source-specific identifier")
NAME_LINK = "name_link", _(
"Link to a group with identical name. Can have security implications "
"when a group name is used with another source."
)
NAME_DENY = "name_deny", _(
"Use the group name, but deny enrollment when the name already exists."
)


class Source(ManagedModel, SerializerModel, PolicyBindingModel):
"""Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server"""

Expand Down Expand Up @@ -632,6 +645,14 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
"a new user enrolled."
),
)
group_matching_mode = models.TextField(
choices=SourceGroupMatchingModes.choices,
default=SourceGroupMatchingModes.IDENTIFIER,
help_text=_(
"How the source determines if an existing group should be used or "
"a new group created."
),
)

objects = InheritanceManager()

Expand Down Expand Up @@ -727,6 +748,27 @@ class Meta:
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)
identifier = models.TextField()

objects = InheritanceManager()

@property
def serializer(self) -> type[Serializer]:
"""Get serializer for this model"""
raise NotImplementedError

def __str__(self) -> str:
return f"Group-source connection (group={self.group_id}, source={self.source_id})"

class Meta:
unique_together = (("group", "source"),)


class ExpiringModel(models.Model):
"""Base Model which can expire, and is automatically cleaned up."""

Expand Down
Loading

0 comments on commit 83b02a1

Please sign in to comment.