Changelog#

[0.8.0] - Unreleased#

Added#

  • Cursor pagination of RFC 9865. A configuration that announces it in pagination gets nextCursor and previousCursor in the search responses. The clients cannot read or forge the cursors. The server refuses a cursor of another query, another count, or older than cursorTimeout. The cursors give stable pages: a resource that exists during the whole paging is returned once, even when others are created or deleted. Cursor pagination needs the new secret parameter of ScimService, the cursor extra, and a storage that sets the new supports_cursors, such as InMemoryStorage. The server refuses to start otherwise.

  • The scim2-server command pages with cursors when its --service-provider-config announces them. Its container image includes the cursor extra.

  • WSGIApplication and ASGIApplication give their default service the secret of the SCIM2_SERVER_SECRET environment variable.

  • The maxPageSize of pagination bounds the count of every search.

  • ScimStorageContract and AsyncScimStorageContract check that the cursors of the storages that support them give stable pages.

Changed#

  • ScimStorage.search returns a SearchPage instead of a tuple, and takes the position of the page when a cursor pages the resources. This also applies to code that calls it directly, such as the search method of InMemoryStorage.

  • InMemoryStorage returns the resources in the order of their identifiers when the search has no sortBy, instead of the order of creation. The same goes for the resources with the same sort value.

  • For the subclasses of ScimService: read_search(), read_search_query and read_search_body return the search request with the position of the page its cursor asks for, and search_response() takes the searched resource types and a SearchPage instead of the number of resources and the page.

  • The WSGI and ASGI applications keep the query parameters without a value, as scim2-flask does. An empty filter, sortBy or count gets a 400 response instead of being ignored.

  • With pagination in the configuration, a search without count gets defaultPageSize resources, by index as by cursor, instead of every resource up to maxResults.

  • A search with a cursor gets a 501 response when the configuration does not announce cursor pagination, instead of ignoring the cursor.

Deprecated#

  • A storage whose search method does not accept position raises a DeprecationWarning. Such a storage pages by index only. scim2-server 0.9 will require position.

[0.7.1] - 2026-10-07#

Added#

  • ScimStorage.get receives the attributes and excludedAttributes of a GET request in response_parameters. A storage can skip loading the attributes the response leaves out, such as the members of a group.

Changed#

  • Requires scim2-models 0.12.1.

Deprecated#

  • A storage whose get method does not accept response_parameters raises a DeprecationWarning. scim2-server 0.9 will require it.

[0.7.0] - 2026-10-06#

Added#

  • The --precis option of the scim2-server command compares the usernames and the passwords with the PRECIS profiles of RFC 8265, and refuses the values they do not allow. It needs the precis extra.

Changed#

  • Requires scim2-models 0.12. Responses leave out empty values, and list in schemas only the extensions they hold. A bulk request without operations answers 400.

  • Filters, sorting and uniqueness checks compare strings as the comparison_key of the ScimPolicy of the provider says. By default, the case of strings is mapped to lowercase rather than folded: Straße no longer matches STRASSE.

  • The default service provider configuration declares no authentication scheme. Declare the schemes of the application in the config of the provider.

Fixed#

  • The in-memory storage compares unique values as filters do. A userName written with a combining accent clashes with the same userName written with a precomposed one.

  • A stored value that the policy cannot compare no longer fails the next writes of the in-memory storage with a 500. It clashes with no other value.

[0.6.2] - 2026-10-06#

Added#

  • A storage can return references to other resources relative to the SCIM root, such as Users/2819c223 in members.$ref. The server returns them as the URLs of the resources, with reference_location().

[0.6.1] - 2026-10-05#

Changed#

Fixed#

  • Every failed bulk operation has a location, except a failed POST, which has none. scim2-client no longer rejects these bulk responses.

  • A bulk operation fails with the status of the same request on its own, such as 404 for an unknown endpoint or 405 for DELETE /Users, instead of 400.

  • A request body without Content-Type is read as JSON, instead of answering 415.

  • Each response carrying a resource has a Content-Location header with meta.location.

  • A provider without config announces PATCH, and no other capability. It announced every capability, even those its storage does not implement. A filter or a sort now answers 501, until the config announces it.

[0.6.0] - 2026-10-05#

Added#

  • authorize() accepts or refuses each operation of a client, bulk operations included.

Changed#

  • run_bulk_step() takes the bulk request instead of its base URL.

  • The storage contract accepts a version that only changes with the content of the resource.

[0.5.0] - 2026-10-05#

scim2-server becomes a library to build SCIM servers upon, and no longer depends on Werkzeug.

Added#

Changed#

  • Werkzeug is no longer a dependency. The scim2-server command serves its requests with the WSGI server of the standard library. --debug logs the WSGI environment of each request, without the debugger and the reloader of Werkzeug.

  • scim2_server.provider.SCIMApplication becomes scim2_server.applications.wsgi.WSGIApplication, and takes a storage instead of a backend. scim2_server.tenants.TenantDispatcher becomes scim2_server.applications.wsgi.TenantDispatcher. Its factory now decides which tenants exist, and returns None for an unknown tenant: the tenants and dynamic parameters are removed.

  • A concurrent write of a resource between its read and its write answers 412, instead of overwriting the other write. Two writes within the same microsecond get distinct versions.

  • A method that an endpoint does not support answers 405 with its Allow header. HEAD is no longer accepted.

  • The meta.location of the discovery resources starts with /v2, like the location of the other resources, even for a request without this prefix.

  • An exception other than a SCIMException, such as a Pydantic ValidationError raised by a storage, answers 500.

Removed#

  • scim2_server.backend.Backend and InMemoryBackend. Use a storage instead. A storage no longer fills meta.location: the server does.

  • SCIMApplication.register_bearer_token. The --bearer-token option of the scim2-server command still accepts static bearer tokens. In Python, override check_auth().

  • SCIMApplication.error_from, SCIMApplication.make_error and the call_* methods.

Fixed#

  • A PATCH operation that misses a member RFC 7644 §3.5.2 requires, such as a remove without path or an add without value, answers 400 and leaves the resource unchanged.

  • A bulk request with a long chain of bulkId references no longer exhausts the Python stack.

  • The errors of the clients, such as a 404, are logged at the INFO level without traceback. Only the unexpected errors keep their traceback.

[0.4.0] - 2026-10-01#

Added#

  • Multi-tenancy: the --tenant option of the scim2-server command serves one set of resources per URL prefix, such as /a/v2/Users. With --dynamic-tenants, the first request to an unknown tenant creates it.

Fixed#

  • The location of the resources keeps the prefix the application is mounted under.

[0.3.3] - 2026-10-01#

Fixed#

  • An unsupported method on a discovery endpoint answers 405, and every 405 response lists the supported methods in its Allow header.

[0.3.2] - 2026-10-01#

Added#

  • The scim2-server command serves several requests at once.

  • The package ships its type hints.

Fixed#

  • A bulk request larger than maxPayloadSize is refused before it is read whole.

[0.3.1] - 2026-09-30#

Fixed#

  • A PUT that changes nothing keeps the ETag of the resource.

[0.3.0] - 2026-09-28#

Python 3.10 is no longer supported.

Added#

  • Bulk requests, with bulkId references between operations and failOnErrors.

  • The service is described with a ScimProvider. The scim2-server command takes a --service-provider-config file, and serves the configuration it describes.

  • Features that the configuration does not support, such as PATCH, sorting or filtering, answer 501. Searches return at most filter.maxResults resources. Resources carry versions only when the configuration supports ETags.

  • The payloads are read under the ScimPolicy of the provider. By default, an attribute that no schema declares answers 400.

Changed#

  • SCIMProvider is renamed SCIMApplication, to avoid any confusion with the ScimProvider of scim2-models.

  • The backend takes the provider, and only stores the resources. Its methods take a ResourceType instead of its id. Its methods to register and read schemas and resource types are removed.

  • PATCH requests and filters are applied by scim2-models. The PATCH operators and the filter evaluation of scim2-server are removed.

Fixed#

  • A PATCH that changes nothing keeps the ETag and lastModified of the resource.

  • Conditional headers are evaluated in the order of RFC 7232.

  • Uniqueness is checked among the resources that share a schema, whatever their resource type.

  • totalResults counts every matching resource, and itemsPerPage counts the returned ones.

  • Validation errors carry a scimType.

  • An internal error no longer discloses its traceback to the client.

  • A search request body that is not a JSON object answers 400.

  • The default schemas follow the RFC errata: the password is case-exact, and the addresses of a user have a primary attribute. The enterprise user extension is optional in the default resource types.

[0.2.0] - 2026-09-25#

Added#

  • A container image is published on the GitHub container registry for each release.

  • The --debug option of the scim2-server command.

[0.1.9] - 2026-03-27#

Fixed#

  • PATCH requests on multi-valued attributes.

[0.1.8] - 2026-01-25#

Python 3.14 is supported.

Fixed#

  • A PATCH on the root of an extension no longer answers invalidPath.

[0.1.7] - 2025-07-25#

Fixed#

  • A PUT that adds an extension to a resource without one no longer fails.

[0.1.6] - 2025-07-23#

Fixed#

  • Compatibility with scim2-models 0.4.

[0.1.5] - 2025-03-28#

Fixed#

  • A Pydantic warning.

[0.1.4] - 2025-01-27#

No change for the users.

[0.1.3] - 2025-01-21#

No change for the users.

[0.1.2] - 2024-11-07#

Python 3.10 and 3.13 are supported.

Fixed#

  • The location of a resource created with POST or replaced with PUT.

[0.1.1] - 2024-09-22#

Added#

  • The /v2 prefix of the endpoints is optional.

Fixed#

  • meta.resourceType holds the name of the resource type, not its id.

[0.1.0] - 2024-08-30#

Added#

  • Initial release: an in-memory SCIM server, with the discovery endpoints, the creation, read, replacement, PATCH and deletion of resources, searches with filters and sorting, ETags and uniqueness constraints.