diff --git a/authentik/providers/scim/clients/base.py b/authentik/providers/scim/clients/base.py index 127d3dc40..8399b862f 100644 --- a/authentik/providers/scim/clients/base.py +++ b/authentik/providers/scim/clients/base.py @@ -14,7 +14,7 @@ from requests import RequestException, Session from structlog.stdlib import get_logger from authentik.lib.utils.http import get_http_session -from authentik.providers.scim.clients.exceptions import SCIMRequestException +from authentik.providers.scim.clients.exceptions import ResourceMissing, SCIMRequestException from authentik.providers.scim.models import SCIMProvider T = TypeVar("T") @@ -73,6 +73,8 @@ class SCIMClient(Generic[T, SchemaType]): raise SCIMRequestException(None) from exc self.logger.debug("scim request", path=path, method=method, **kwargs) if response.status_code >= 400: + if response.status_code == 404: + raise ResourceMissing(response) self.logger.warning( "Failed to send SCIM request", path=path, method=method, response=response.text ) diff --git a/authentik/providers/scim/clients/exceptions.py b/authentik/providers/scim/clients/exceptions.py index 20c3a5f70..926cd95fb 100644 --- a/authentik/providers/scim/clients/exceptions.py +++ b/authentik/providers/scim/clients/exceptions.py @@ -41,3 +41,8 @@ class SCIMRequestException(SentryIgnoredException): except ValidationError: pass return super().__str__() + + +class ResourceMissing(SCIMRequestException): + """Error raised when the provider raises a 404, meaning that we + should delete our internal ID and re-create the object""" diff --git a/authentik/providers/scim/clients/group.py b/authentik/providers/scim/clients/group.py index 63eb74af2..656d9edd6 100644 --- a/authentik/providers/scim/clients/group.py +++ b/authentik/providers/scim/clients/group.py @@ -10,7 +10,7 @@ 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.providers.scim.clients.base import SCIMClient -from authentik.providers.scim.clients.exceptions import StopSync +from authentik.providers.scim.clients.exceptions import ResourceMissing, StopSync from authentik.providers.scim.clients.schema import Group as SCIMGroupSchema from authentik.providers.scim.models import SCIMGroup, SCIMMapping, SCIMUser @@ -23,15 +23,11 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroupSchema]): scim_group = SCIMGroup.objects.filter(provider=self.provider, group=obj).first() if not scim_group: return self._create(obj) - scim_group = self.to_scim(obj) - scim_group.id = scim_group.id - return self._request( - "PUT", - f"/Groups/{scim_group.id}", - data=scim_group.json( - exclude_unset=True, - ), - ) + try: + return self._update(obj, scim_group) + except ResourceMissing: + scim_group.delete() + return self._create(obj) def delete(self, obj: Group): """Delete group""" @@ -104,6 +100,18 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroupSchema]): ) SCIMGroup.objects.create(provider=self.provider, group=group, id=response["id"]) + def _update(self, group: Group, connection: SCIMGroup): + """Update existing group""" + scim_group = self.to_scim(group) + scim_group.id = connection.id + return self._request( + "PUT", + f"/Groups/{scim_group.id}", + data=scim_group.json( + exclude_unset=True, + ), + ) + def _patch( self, group_id: str, diff --git a/authentik/providers/scim/clients/user.py b/authentik/providers/scim/clients/user.py index 4f79be7e0..ad5f7552e 100644 --- a/authentik/providers/scim/clients/user.py +++ b/authentik/providers/scim/clients/user.py @@ -8,7 +8,7 @@ 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.providers.scim.clients.base import SCIMClient -from authentik.providers.scim.clients.exceptions import StopSync +from authentik.providers.scim.clients.exceptions import ResourceMissing, StopSync from authentik.providers.scim.clients.schema import User as SCIMUserSchema from authentik.providers.scim.models import SCIMMapping, SCIMUser @@ -21,7 +21,11 @@ class SCIMUserClient(SCIMClient[User, SCIMUserSchema]): scim_user = SCIMUser.objects.filter(provider=self.provider, user=obj).first() if not scim_user: return self._create(obj) - return self._update(obj, scim_user) + try: + return self._update(obj, scim_user) + except ResourceMissing: + scim_user.delete() + return self._create(obj) def delete(self, obj: User): """Delete user"""