Serve the /Me endpoint#

Use this guide to serve /Me, the alias of the resource of the authenticated client (RFC 7644 §3.11). It assumes an integration built with Integrate a web framework, and clients authenticated with Authenticate and authorize the clients.

By default, /Me answers 501. A request on /Me acts on a resource that depends on the client: a User for a person, or another type, such as a device. The application tells which one.

Pass the subject#

Pass the authenticated client in the subject of the ScimRequest, as in Pass the client to the service. The service passes it to me_target() and me_creation_type(). The subject can be the user of the framework, the claims of a token, or any other object.

Find the resource of the subject#

The service needs the resource that /Me stands for. Subclass ScimService, and override me_target(). It returns the resource type and the identifier of this resource. get_resource_type() gives a resource type from its name. Raise NotFoundException when the subject has no resource. An application that accepts anonymous requests raises UnauthorizedException when the request has no subject:

>>> from scim2_models import NotFoundException
>>> from scim2_models import UnauthorizedException
>>> from scim2_server.service import ScimService

>>> class MeService(ScimService):
...     def me_target(self, request):
...         if request.subject is None:
...             raise UnauthorizedException
...         if request.subject.get("user_id") is None:
...             raise NotFoundException(detail="No user for this client")
...         return self.get_resource_type("User"), request.subject["user_id"]

The handler then serves GET, PUT, PATCH and DELETE on /Me as on the URL of the resource. authorize() checks them as operations on this resource, after me_target() finds it (Authenticate and authorize the clients). Each response carries the URL of the resource in its Location header:

>>> from scim2_server.handler import ScimHandler
>>> from scim2_server.memory import InMemoryStorage
>>> from scim2_server.requests import ScimRequest
>>> from scim2_server.utils import load_default_provider

>>> handler = ScimHandler(MeService(load_default_provider()), InMemoryStorage())
>>> user = handler.handle(
...     ScimRequest(
...         "POST",
...         "https://scim.example/v2",
...         "/Users",
...         headers={"Content-Type": "application/scim+json"},
...         body=b'{"userName": "bjensen"}',
...     )
... ).body

>>> response = handler.handle(
...     ScimRequest(
...         "GET", "https://scim.example/v2", "/Me", subject={"user_id": user["id"]}
...     )
... )
>>> response.body["userName"]
'bjensen'
>>> response.headers["Location"] == user["meta"]["location"]
True

Let the clients register themselves#

A POST on /Me creates the resource of a client that has none yet. Such requests have risks (RFC 7644 §7.6). A POST on /Me answers 501 by default.

To accept it, override me_creation_type(). It returns the resource type of the resource to create, for instance from the subject or from the schemas of the body. The following service lets a client without user create one:

>>> from scim2_models import UniquenessException

>>> class RegisteringService(MeService):
...     def me_creation_type(self, request):
...         if request.subject is None:
...             raise UnauthorizedException
...         if request.subject.get("user_id") is not None:
...             raise UniquenessException(detail="This client already has a user")
...         return self.get_resource_type("User")

>>> handler = ScimHandler(RegisteringService(load_default_provider()), InMemoryStorage())
>>> client = {"user_id": None}
>>> response = handler.handle(
...     ScimRequest(
...         "POST",
...         "https://scim.example/v2",
...         "/Me",
...         headers={"Content-Type": "application/scim+json"},
...         body=b'{"userName": "jdoe"}',
...         subject=client,
...     )
... )
>>> response.status
<HTTPStatus.CREATED: 201>

After the creation, link the new resource to the subject in the application, so that me_target() finds it:

>>> client["user_id"] = response.body["id"]
>>> response = handler.handle(
...     ScimRequest("GET", "https://scim.example/v2", "/Me", subject=client)
... )
>>> response.body["userName"]
'jdoe'