From f4b0d6e85c3eb3ef37bcf8807611c1b5dc7aa099 Mon Sep 17 00:00:00 2001 From: Jens L Date: Wed, 17 May 2023 00:25:28 +0200 Subject: [PATCH] providers/scim: default to None for fields instead of empty list (#5642) * providers/scim: default to None for fields instead of empty list Signed-off-by: Jens Langhammer * make name of delete_none_keys clearer Signed-off-by: Jens Langhammer * fix Signed-off-by: Jens Langhammer --------- Signed-off-by: Jens Langhammer --- authentik/core/sources/flow_manager.py | 4 ++-- authentik/policies/utils.py | 2 +- authentik/providers/scim/clients/group.py | 4 ++-- authentik/providers/scim/clients/user.py | 4 ++-- authentik/providers/scim/tests/test_membership.py | 2 -- authentik/providers/scim/tests/test_user.py | 4 ---- authentik/sources/saml/processors/response.py | 6 +++--- blueprints/system/providers-scim.yaml | 6 +++--- 8 files changed, 13 insertions(+), 19 deletions(-) diff --git a/authentik/core/sources/flow_manager.py b/authentik/core/sources/flow_manager.py index 317e3062c..0339fee2f 100644 --- a/authentik/core/sources/flow_manager.py +++ b/authentik/core/sources/flow_manager.py @@ -28,7 +28,7 @@ from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSI from authentik.lib.utils.urls import redirect_with_qs from authentik.lib.views import bad_request_message from authentik.policies.denied import AccessDeniedResponse -from authentik.policies.utils import delete_none_keys +from authentik.policies.utils import delete_none_values from authentik.stages.password import BACKEND_INBUILT from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT @@ -329,7 +329,7 @@ class SourceFlowManager: ) ], **{ - PLAN_CONTEXT_PROMPT: delete_none_keys(self.enroll_info), + PLAN_CONTEXT_PROMPT: delete_none_values(self.enroll_info), PLAN_CONTEXT_USER_PATH: self.source.get_user_path(), }, ) diff --git a/authentik/policies/utils.py b/authentik/policies/utils.py index c9a1f56d6..8ac49024e 100644 --- a/authentik/policies/utils.py +++ b/authentik/policies/utils.py @@ -2,7 +2,7 @@ from typing import Any -def delete_none_keys(dict_: dict[Any, Any]) -> dict[Any, Any]: +def delete_none_values(dict_: dict[Any, Any]) -> dict[Any, Any]: """Remove any keys from `dict_` that are None.""" new_dict = {} for key, value in dict_.items(): diff --git a/authentik/providers/scim/clients/group.py b/authentik/providers/scim/clients/group.py index a2c0cad3b..9af35dd22 100644 --- a/authentik/providers/scim/clients/group.py +++ b/authentik/providers/scim/clients/group.py @@ -8,7 +8,7 @@ from authentik.core.exceptions import PropertyMappingExpressionException from authentik.core.models import Group from authentik.events.models import Event, EventAction from authentik.lib.utils.errors import exception_to_string -from authentik.policies.utils import delete_none_keys +from authentik.policies.utils import delete_none_values from authentik.providers.scim.clients.base import SCIMClient from authentik.providers.scim.clients.exceptions import ( ResourceMissing, @@ -74,7 +74,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroupSchema]): if not raw_scim_group: raise StopSync(ValueError("No group mappings configured"), obj) try: - scim_group = SCIMGroupSchema.parse_obj(delete_none_keys(raw_scim_group)) + scim_group = SCIMGroupSchema.parse_obj(delete_none_values(raw_scim_group)) except ValidationError as exc: raise StopSync(exc, obj) from exc if not scim_group.externalId: diff --git a/authentik/providers/scim/clients/user.py b/authentik/providers/scim/clients/user.py index ad5f7552e..f68f2f91d 100644 --- a/authentik/providers/scim/clients/user.py +++ b/authentik/providers/scim/clients/user.py @@ -6,7 +6,7 @@ from authentik.core.exceptions import PropertyMappingExpressionException from authentik.core.models import User from authentik.events.models import Event, EventAction from authentik.lib.utils.errors import exception_to_string -from authentik.policies.utils import delete_none_keys +from authentik.policies.utils import delete_none_values from authentik.providers.scim.clients.base import SCIMClient from authentik.providers.scim.clients.exceptions import ResourceMissing, StopSync from authentik.providers.scim.clients.schema import User as SCIMUserSchema @@ -64,7 +64,7 @@ class SCIMUserClient(SCIMClient[User, SCIMUserSchema]): if not raw_scim_user: raise StopSync(ValueError("No user mappings configured"), obj) try: - scim_user = SCIMUserSchema.parse_obj(delete_none_keys(raw_scim_user)) + scim_user = SCIMUserSchema.parse_obj(delete_none_values(raw_scim_user)) except ValidationError as exc: raise StopSync(exc, obj) from exc if not scim_user.externalId: diff --git a/authentik/providers/scim/tests/test_membership.py b/authentik/providers/scim/tests/test_membership.py index 184b91892..18b3faa67 100644 --- a/authentik/providers/scim/tests/test_membership.py +++ b/authentik/providers/scim/tests/test_membership.py @@ -91,7 +91,6 @@ class SCIMMembershipTests(TestCase): "active": True, "externalId": user.uid, "name": {"familyName": "", "formatted": "", "givenName": ""}, - "photos": [], "displayName": "", "userName": user.username, }, @@ -177,7 +176,6 @@ class SCIMMembershipTests(TestCase): "emails": [], "externalId": user.uid, "name": {"familyName": "", "formatted": "", "givenName": ""}, - "photos": [], "userName": user.username, }, ) diff --git a/authentik/providers/scim/tests/test_user.py b/authentik/providers/scim/tests/test_user.py index dae05f268..842674b10 100644 --- a/authentik/providers/scim/tests/test_user.py +++ b/authentik/providers/scim/tests/test_user.py @@ -81,7 +81,6 @@ class SCIMUserTests(TestCase): "givenName": uid, }, "displayName": uid, - "photos": [], "userName": uid, }, ) @@ -137,7 +136,6 @@ class SCIMUserTests(TestCase): "formatted": uid, "givenName": uid, }, - "photos": [], "userName": uid, }, ) @@ -190,7 +188,6 @@ class SCIMUserTests(TestCase): "givenName": uid, }, "displayName": uid, - "photos": [], "userName": uid, }, ) @@ -258,7 +255,6 @@ class SCIMUserTests(TestCase): "givenName": uid, }, "displayName": uid, - "photos": [], "userName": uid, }, ) diff --git a/authentik/sources/saml/processors/response.py b/authentik/sources/saml/processors/response.py index 2c809858e..9ff460a38 100644 --- a/authentik/sources/saml/processors/response.py +++ b/authentik/sources/saml/processors/response.py @@ -21,7 +21,7 @@ from authentik.core.models import ( from authentik.core.sources.flow_manager import SourceFlowManager from authentik.lib.expression.evaluator import BaseEvaluator from authentik.lib.utils.time import timedelta_from_string -from authentik.policies.utils import delete_none_keys +from authentik.policies.utils import delete_none_values from authentik.sources.saml.exceptions import ( InvalidSignature, MismatchedRequestID, @@ -160,7 +160,7 @@ class ResponseProcessor: self._source, self._http_request, name_id, - delete_none_keys(self.get_attributes()), + delete_none_values(self.get_attributes()), ) def _get_name_id(self) -> "Element": @@ -237,7 +237,7 @@ class ResponseProcessor: self._source, self._http_request, name_id.text, - delete_none_keys(self.get_attributes()), + delete_none_values(self.get_attributes()), ) diff --git a/blueprints/system/providers-scim.yaml b/blueprints/system/providers-scim.yaml index 3c19145ac..9fdac0376 100644 --- a/blueprints/system/providers-scim.yaml +++ b/blueprints/system/providers-scim.yaml @@ -21,7 +21,7 @@ entries: # photos supports URLs to images, however authentik might return data URIs avatar = request.user.avatar - photos = [] + photos = None if "://" in avatar: photos = [{"value": avatar, "type": "photo"}] @@ -31,11 +31,11 @@ entries: emails = [] if request.user.email != "": - emails.append({ + emails = [{ "value": request.user.email, "type": "other", "primary": True, - }) + }] return { "userName": request.user.username, "name": {