Tutorial

Initialization

scim2-client depends on request engines such as httpx to perform network requests. This tutorial demonstrate how to use scim2-client with httpx, and suppose you have installed the httpx extra for example with pip install scim2-client[httpx].

As a start you will need to instantiate a httpx Client object that you can parameter as your will, and then pass it to a SCIMClient object. In addition to your SCIM server root endpoint, you will probably want to provide some authorization headers through the httpx Client headers parameter:

from httpx import Client
from scim2_client.engines.httpx import SyncSCIMClient

client = Client(
    base_url="https://auth.example/scim/v2",
    headers={"Authorization": "Bearer foobar"},
)
scim = SyncSCIMClient(client)

You need to give to indicate to SCIMClient all the different Resource models that you will need to manipulate, and the matching ResourceType objects to let the client know where to look for resources on the server.

You can either provision those objects manually or automatically.

Automatic provisioning

The easiest way is to let the client discover the server’s configuration and available resources. The discover() method looks for the server ServiceProviderConfig, Schema and ResourceType endpoints, and dynamically generate local Python models based on those schemas. They are then available to use with get_resource_model().

Dynamically discover models from the server
scim.discover()
User = scim.get_resource_model("User")
EnterpriseUser = User.get_extension_model("EnterpriseUser")

Manual provisioning

To manually register models and resource types, you can simply use the resource_models and resource_types arguments.

Manually registering models and resource types
from scim2_models import User, EnterpriseUserUser, Group, ResourceType
scim = SyncSCIMClient(
    client,
    resource_models=[User[EnterpriseUser], Group],
    resource_types=[ResourceType(id="User", ...), ResourceType(id="Group", ...)],
)

Tip

If you know that all the resources are hosted at regular server endpoints (for instance /Users for User etc.), you can skip passing the ResourceType objects by hand, and simply call register_naive_resource_types().

Manually registering models and resource types
from scim2_models import User, EnterpriseUserUser, Group, ResourceType
scim = SyncSCIMClient(
    client,
    resource_models=[User[EnterpriseUser], Group],
)
scim.register_naive_resource_types()

Performing actions

scim2-client allows your application to interact with a SCIM server as described in RFC7644 §3, so you can read and manage the resources. The following actions are available:

Have a look at the Reference to see usage examples and the exhaustive set of parameters, but generally it looks like this:

request = User(user_name="bjensen@example.com")
response = scim.create(request)
print(f"User {response.id} has been created!")

By default, if the server returns an error, a SCIMResponseErrorObject exception is raised. The to_error() method gives access to the Error object:

from scim2_client import SCIMResponseErrorObject

try:
    response = scim.create(request)
except SCIMResponseErrorObject as exc:
    error = exc.to_error()
    print(f"SCIM error [{error.status}] {error.scim_type}: {error.detail}")

PATCH modifications

The modify() method allows you to perform partial updates on resources using PATCH operations as defined in RFC7644 §3.5.2.

from scim2_models import PatchOp, PatchOperation

# Create a patch operation to update the display name
operation = PatchOperation(
    op=PatchOperation.Op.replace_,
    path="displayName",
    value="New Display Name"
)
patch_op = PatchOp[User](operations=[operation])

# Apply the patch
response = scim.modify(User, user_id, patch_op)
if response:  # Server returned 200 with updated resource
    print(f"User updated: {response.display_name}")
else:  # Server returned 204 (no content)
    print("User updated successfully")

Multiple Operations

You can include multiple operations in a single PATCH request:

operations = [
    PatchOperation(
        op=PatchOperation.Op.replace_,
        path="displayName",
        value="Updated Name"
    ),
    PatchOperation(
        op=PatchOperation.Op.replace_,
        path="active",
        value=False
    ),
    PatchOperation(
        op=PatchOperation.Op.add,
        path="emails",
        value=[{"value": "new@example.com", "primary": True}]
    )
]
patch_op = PatchOp[User](operations=operations)
response = scim.modify(User, user_id, patch_op)

Patch Operation Types

SCIM supports three types of patch operations:

  • add: Add new attribute values

  • remove: Remove attribute values

  • replace_: Replace existing attribute values

Bulk operations

Note

Bulk operation requests are not yet implemented, but any help is welcome!

Request and response validation

By default, scim2-client validates both request payloads and server responses against the SCIM specifications, raising an error on non-compliance. However sometimes you want to accept invalid inputs and outputs. To achieve this, all the methods provide the following parameters, all are True by default:

  • check_request_payload: If True (the default) a ValidationError will be raised if the input does not respect the SCIM standard. If False, input is expected to be a dict that will be passed as-is in the request.

  • check_response_payload: If True (the default) a ValidationError will be raised if the server response does not respect the SCIM standard. If False the server response is returned as-is.

  • expected_status_codes: The list of expected status codes in the response. If None any status code is accepted. If an unexpected status code is returned, a UnexpectedStatusCode exception is raised.

  • raise_scim_errors: If True (the default) and the server returned an Error object, a SCIMResponseErrorObject exception will be raised. The to_error() method gives access to the Error object. If False the error object is returned directly.

Tip

Check the request Contexts to understand which value will excluded from the request payload, and which values are expected in the response payload.

Engines

scim2-client comes with a light abstraction layers that allows for different requests engines. Currently those engines are shipped:

  • SyncSCIMClient: A synchronous engine using httpx to perform the HTTP requests.

  • AsyncSCIMClient: An asynchronous engine using httpx to perform the HTTP requests. It has the very same API than its synchronous version, except it is asynchronous.

  • TestSCIMClient: A test engine for development purposes. It takes a WSGI app and directly execute the server code instead of performing real HTTP requests. This is faster in unit test suites, and helpful to catch the server exceptions.

You can easily implement your own engine by inheriting from SCIMClient.

Additional request parameters

Pass additional parameters directly to the underlying engine methods. This can be useful if you need to explicitly pass a certain URL for example:

scim.query(url="/User/i-know-what-im-doing")