Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sources: add property mappings for all oauth and saml sources #8771

Merged
merged 95 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
0e1347e
sources: introduce new property mappings per-user and group
rissson Feb 29, 2024
a99b6ed
sources/ldap: migrate to new property mappings
rissson Feb 29, 2024
02fd08c
lint-fix and make gen
rissson Feb 29, 2024
5bbe030
web changes
rissson Feb 29, 2024
edfab70
wip
rissson Mar 1, 2024
3d30cd6
actually update groups
rissson Mar 1, 2024
b59a747
add property mappings to other sources
rissson Mar 1, 2024
bac96a2
plex: actually pass it another way
rissson Mar 1, 2024
c8f0a4e
Merge branch 'main' into refactor-sources
rissson Mar 1, 2024
0f92e37
fix tests
rissson Mar 1, 2024
1dbd5eb
update tests
rissson Mar 1, 2024
7132d27
Merge branch 'main' into refactor-sources
rissson Mar 1, 2024
3e42324
ldappropertymapping -> ldapsourcepropertymapping
rissson Mar 1, 2024
4bed58c
web stuff
rissson Mar 1, 2024
3df5c67
remove flatten for generic implem
rissson Mar 1, 2024
4927d2f
Merge branch 'main' into refactor-sources
rissson Mar 1, 2024
d276eb3
rework migration
rissson Mar 1, 2024
4323237
lint-fix
rissson Mar 1, 2024
924461b
wip
rissson Mar 1, 2024
99e2ce7
fix migrations
rissson Mar 1, 2024
bb823aa
Merge branch 'refactor-sources' into source-flow-manager-property-map…
rissson Mar 1, 2024
7884b31
re-add field migration to property mappings
rissson Mar 1, 2024
44c92c2
re-add field migration to property mappings
rissson Mar 1, 2024
20a625d
fix
rissson Mar 1, 2024
e95fdce
Merge branch 'refactor-sources' into source-flow-manager-property-map…
rissson Mar 1, 2024
20b18bc
Merge branch 'main' into refactor-sources
rissson Apr 24, 2024
cd66792
fix migrations
rissson Apr 24, 2024
bddfb7f
more migrations fixes
rissson Apr 25, 2024
442248a
Merge branch 'main' into refactor-sources
rissson Jun 6, 2024
193e4ed
easy fixes
rissson Jun 6, 2024
8083ab2
migrate to propertymappingmanager
rissson Jun 6, 2024
a82b5b7
ruff and small fixes
rissson Jun 6, 2024
c840c09
Merge branch 'refactor-sources' into source-flow-manager-property-map…
rissson Jun 6, 2024
9054557
Merge branch 'main' into source-flow-manager-property-mappings
rissson Jul 22, 2024
59cc0c0
wip
rissson Jul 22, 2024
8e2110d
fix merge
rissson Jul 22, 2024
dfa407d
gen-schema
rissson Jul 22, 2024
bca0ea4
Merge branch 'main' into source-flow-manager-property-mappings
rissson Jul 23, 2024
c21f63f
fix migrations
rissson Jul 23, 2024
dbc523d
add group source connection
rissson Jul 23, 2024
ffda25d
wip
rissson Jul 23, 2024
ebd7502
add group source connection models
rissson Jul 24, 2024
874aa05
refactor user source connection a bit
rissson Jul 24, 2024
bd2657c
also do it for plex
rissson Jul 24, 2024
2ba8bd0
lint
rissson Jul 24, 2024
81dbe65
Merge branch 'main' into source-flow-manager-property-mappings
rissson Jul 24, 2024
6edfcd9
Merge branch 'main' into source-flow-manager-property-mappings
rissson Jul 25, 2024
84acad7
fix migrations
rissson Jul 25, 2024
8c17efb
oauth types: reowkr
rissson Jul 25, 2024
cfefba9
fix saml
rissson Jul 25, 2024
ac12fbc
lint
rissson Jul 25, 2024
df2bb53
fix source flow manager tests
rissson Jul 25, 2024
68897a5
fix more tests
rissson Jul 25, 2024
ff05559
more tests
rissson Jul 25, 2024
16c5a00
Merge branch 'main' into source-flow-manager-property-mappings
rissson Jul 26, 2024
8ff188b
Merge branch 'main' into source-flow-manager-property-mappings
rissson Jul 29, 2024
4ba5716
deduplicate api
rissson Jul 29, 2024
ddfd7d4
lint-fix
rissson Jul 29, 2024
90ab89d
remove plex changes
rissson Jul 29, 2024
3692fdd
wip front
rissson Jul 29, 2024
0b8ef66
frontend
rissson Jul 29, 2024
20f7a93
revert plex front
rissson Jul 29, 2024
2be0550
saml: use group_id as name by default
rissson Jul 29, 2024
3678d0e
small fix
rissson Jul 29, 2024
399ccf1
fix saml tests
rissson Jul 29, 2024
a762e1e
fix oauth tests
rissson Jul 29, 2024
834d0d2
squash migrations
rissson Jul 29, 2024
ca69656
fix e2e tests
rissson Jul 29, 2024
a309930
Merge branch 'main' into source-flow-manager-property-mappings
rissson Jul 30, 2024
3c4b271
flow manager group sync
rissson Jul 31, 2024
bdf18db
small fix
rissson Jul 31, 2024
dcfa1cf
lint
rissson Jul 31, 2024
fd1e0db
add oauth pm tests
rissson Aug 1, 2024
d819217
obviously lint
rissson Aug 1, 2024
9ba38a2
add saml pm tests (groups failing for now)
rissson Aug 1, 2024
cc17edf
make web
rissson Aug 1, 2024
18ade2e
wip
rissson Aug 1, 2024
a131baf
remove groupsclaim from frontend
rissson Aug 1, 2024
1aa08a1
fix flow planner key
rissson Aug 1, 2024
a419245
saml groups
rissson Aug 1, 2024
c8f337a
refactor saml attrs
rissson Aug 1, 2024
3eae3d2
add sfm group stage tests
rissson Aug 1, 2024
71634b8
add e2e
rissson Aug 1, 2024
17bccbb
lint-fix
rissson Aug 1, 2024
27713d9
fix e2e?
rissson Aug 1, 2024
10d6a4e
wip
rissson Aug 1, 2024
26e7cc6
trigger
rissson Aug 1, 2024
3d1a3b2
Merge branch 'main' into source-flow-manager-property-mappings
rissson Aug 1, 2024
7fe1a83
redo migrations
rissson Aug 1, 2024
c938ff7
Merge branch 'main' into source-flow-manager-property-mappings
rissson Aug 5, 2024
cac572a
Merge branch 'main' into source-flow-manager-property-mappings
rissson Aug 5, 2024
703393a
Update authentik/core/models.py
rissson Aug 7, 2024
ad6db70
Update authentik/core/api/sources.py
rissson Aug 7, 2024
43416c4
Merge branch 'main' into source-flow-manager-property-mappings
rissson Aug 7, 2024
ab96d84
lint
rissson Aug 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions authentik/core/api/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class Meta:
"enabled",
"authentication_flow",
"enrollment_flow",
"user_property_mappings",
"group_property_mappings",
"component",
"verbose_name",
"verbose_name_plural",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Generated by Django 5.0.2 on 2024-02-29 11:05

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("authentik_core", "0033_alter_user_options"),
]

operations = [
migrations.AddField(
model_name="source",
name="group_property_mappings",
field=models.ManyToManyField(
blank=True,
default=None,
related_name="source_grouppropertymappings_set",
to="authentik_core.propertymapping",
),
),
migrations.AddField(
model_name="source",
name="user_property_mappings",
field=models.ManyToManyField(
blank=True,
default=None,
related_name="source_userpropertymappings_set",
to="authentik_core.propertymapping",
),
),
migrations.AlterField(
model_name="source",
name="property_mappings",
field=models.ManyToManyField(
blank=True,
default=None,
related_name="source_set",
to="authentik_core.propertymapping",
),
),
]
18 changes: 18 additions & 0 deletions authentik/core/migrations/0035_remove_source_property_mappings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2024-02-29 11:21

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("authentik_sources_ldap", "0004_remove_ldappropertymapping_object_field_and_more"),
("authentik_core", "0034_source_group_property_mappings_and_more"),
]

operations = [
migrations.RemoveField(
model_name="source",
name="property_mappings",
),
]
18 changes: 18 additions & 0 deletions authentik/core/migrations/0036_alter_group_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2024-03-01 12:22

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("authentik_core", "0035_remove_source_property_mappings"),
]

operations = [
migrations.AlterField(
model_name="group",
name="name",
field=models.TextField(verbose_name="name"),
),
]
121 changes: 114 additions & 7 deletions authentik/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@
from authentik.lib.avatars import get_avatar
from authentik.lib.config import CONFIG
from authentik.lib.generators import generate_id
from authentik.lib.merge import MERGE_LIST_UNIQUE
from authentik.lib.models import (
CreatedUpdatedModel,
DomainlessFormattedURLValidator,
SerializerModel,
)
from authentik.policies.models import PolicyBindingModel
from authentik.policies.utils import delete_none_values
from authentik.root.install_id import get_install_id

LOGGER = get_logger()
Expand Down Expand Up @@ -86,12 +88,40 @@ class UserTypes(models.TextChoices):
INTERNAL_SERVICE_ACCOUNT = "internal_service_account"


class Group(SerializerModel):
class AttributesMixin(models.Model):
"""Adds an attributes property to a model"""

attributes = models.JSONField(default=dict, blank=True)

class Meta:
abstract = True

@classmethod
def update_or_create_attributes(
cls, query: dict[str, Any], data: dict[str, Any]
) -> tuple[models.Model, bool]:
"""Same as django's update_or_create but correctly updates attributes by merging dicts"""
instance = cls.objects.filter(**query).first()
if not instance:
return cls.objects.create(**data), True
for key, value in data.items():
if key == "attributes":
continue
setattr(instance, key, value)
final_attributes = {}
MERGE_LIST_UNIQUE.merge(final_attributes, instance.attributes)
MERGE_LIST_UNIQUE.merge(final_attributes, data.get("attributes", {}))
instance.attributes = final_attributes
instance.save()
return instance, False


class Group(SerializerModel, AttributesMixin):
"""Group model which supports a basic hierarchy and has attributes"""

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 All @@ -106,7 +136,6 @@ class Group(SerializerModel):
on_delete=models.SET_NULL,
related_name="children",
)
attributes = models.JSONField(default=dict, blank=True)

@property
def serializer(self) -> Serializer:
Expand Down Expand Up @@ -195,7 +224,7 @@ def exclude_anonymous(self) -> QuerySet:
return self.get_queryset().exclude_anonymous()


class User(SerializerModel, GuardianUserMixin, AbstractUser):
class User(SerializerModel, GuardianUserMixin, AbstractUser, AttributesMixin):
"""authentik User model, based on django's contrib auth user model."""

uuid = models.UUIDField(default=uuid4, editable=False, unique=True)
Expand All @@ -207,8 +236,6 @@ class User(SerializerModel, GuardianUserMixin, AbstractUser):
ak_groups = models.ManyToManyField("Group", related_name="users")
password_change_date = models.DateTimeField(auto_now_add=True)

attributes = models.JSONField(default=dict, blank=True)

objects = UserManager()

@staticmethod
Expand Down Expand Up @@ -512,7 +539,12 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
user_path_template = models.TextField(default="goauthentik.io/sources/%(slug)s")

enabled = models.BooleanField(default=True)
property_mappings = models.ManyToManyField("PropertyMapping", default=None, blank=True)
user_property_mappings = models.ManyToManyField(
"PropertyMapping", default=None, blank=True, related_name="source_userpropertymappings_set"
)
group_property_mappings = models.ManyToManyField(
"PropertyMapping", default=None, blank=True, related_name="source_grouppropertymappings_set"
)
icon = models.FileField(
upload_to="source-icons/",
default=None,
Expand Down Expand Up @@ -576,6 +608,11 @@ def component(self) -> str:
"""Return component used to edit this object"""
raise NotImplementedError

@property
def property_mapping_type(self) -> "type[PropertyMapping]":
"""Return property mapping type used by this object"""
raise NotImplementedError

def ui_login_button(self, request: HttpRequest) -> UILoginButton | None:
"""If source uses a http-based flow, return UI Information about the login
button. If source doesn't use http-based flow, return None."""
Expand All @@ -586,6 +623,76 @@ def ui_user_settings(self) -> UserSettingSerializer | None:
user settings are available, or UserSettingSerializer."""
return None

def get_base_user_properties(self, **kwargs) -> dict[str, Any | dict[str, Any]]:
"""Get base properties for a user to build final properties upon."""
raise NotImplementedError

def get_base_group_properties(self, **kwargs) -> dict[str, Any | dict[str, Any]]:
"""Get base properties for a group to build final properties upon."""
raise NotImplementedError

def get_base_properties(
self, object_type: type[User | Group], **kwargs
) -> dict[str, Any | dict[str, Any]]:
"""Get base properties for a user or a group to build final properties upon."""
if object_type == User:
properties = self.get_base_user_properties(**kwargs)
properties.setdefault("path", self.get_user_path())
return properties
if object_type == Group:
return self.get_base_group_properties(**kwargs)
return {}

def build_object_properties(
self,
object_type: type[User | Group],
user: User | None = None,
request: HttpRequest | None = None,
**kwargs,
) -> dict[str, Any | dict[str, Any]]:
"""Build a user or group properties from the source configured property mappings."""
from authentik.events.models import Event, EventAction

properties = self.get_base_properties(object_type, **kwargs)
if "attributes" not in properties:
properties["attributes"] = {}
mappings = []
if object_type == User:
mappings = self.user_property_mappings.all().select_subclasses()
elif object_type == Group:
mappings = self.group_property_mappings.all().select_subclasses()
print(mappings)
for mapping in mappings:
if not isinstance(mapping, self.property_mapping_type):
continue
try:
value = mapping.evaluate(
user=user,
request=request,
source=self,
properties=properties,
**kwargs,
)
if not value or not isinstance(value, dict):
LOGGER.debug(
"Mapping evaluated to None or is not a dict. Skipping",
source=self,
mapping=mapping,
)
continue
except PropertyMappingExpressionException as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to evaluate property mapping: '{mapping.name}'",
source=self,
mapping=mapping,
).save()
LOGGER.warning("Mapping failed to evaluate", exc=exc, source=self, mapping=mapping)
continue
MERGE_LIST_UNIQUE.merge(properties, value)

return delete_none_values(properties)

def __str__(self):
return str(self.name)

Expand Down
Loading
Loading