Test server#
The scim2-server command runs a SCIM server for the tests or the demo of a SCIM client,
whatever the language of the client. For the tests of a Python project,
pytest-scim2-server provides the same
server as a pytest fixture.
The command serves a WSGIApplication over an
InMemoryStorage, with the WSGI server of wsgiref.simple_server. The
server keeps the resources in memory. They are lost when it stops. Options lists every
option of the command.
Start the server#
Install scim2-server:
$ pip install scim2-serverStart the server. It listens on
127.0.0.1:8080by default:$ scim2-server --hostname 0.0.0.0 --port 8080Point the client at
http://<HOST>:8080/v2, where<HOST>is the address of the machine.
Serve other resource types#
By default, the server serves the users and the groups of RFC 7643, with the
enterprise user extension EnterpriseUser. To serve other resources, pass JSON files in the format of the
discovery endpoints:
--schema: a list ofSchemaobjects;--resource-type: a list ofResourceTypeobjects;--service-provider-config: aServiceProviderConfigobject, to turn a feature such as the bulk requests or the sorting on or off. The cursor pagination is off by default, and needs thecursorextra. The server encrypts its cursors with the secret of theSCIM2_SERVER_SECRETenvironment variable, or else with a secret drawn at start, so that its cursors do not survive a restart.
$ scim2-server --schema schemas.json --resource-type resource-types.json
Require credentials#
Without credentials, every endpoint is open. The service provider configuration still announces
the bearer token scheme, because RFC 7643 §5 requires the
authenticationSchemes attribute. A configuration passed with --service-provider-config
keeps its own schemes.
Pass each accepted bearer token with --bearer-token, and each accepted user and password of
HTTP Basic with --basic-auth. Both options can be repeated, and can be combined. Each one announces its scheme
in the service provider configuration:
$ scim2-server --bearer-token s3cret --basic-auth alice:s3cret
A request without valid credentials gets a 401 response. Its WWW-Authenticate header holds a
Bearer and a Basic challenge, one for each scheme in use. The /ServiceProviderConfig
endpoint stays open.
Accept OAuth access tokens#
The server can act as an OAuth resource server. It then accepts the JWT access tokens of
RFC 9068 that an authorization server issues. Pass the issuer of the tokens and the audience of
the server. The options need the oauth extra:
$ pip install 'scim2-server[oauth]'
$ scim2-server \
--oauth-issuer https://auth.example.com/realms/apps \
--oauth-audience https://scim.example.com
The server finds the public keys of the issuer with the jwks_uri of its metadata. It reads
the metadata of RFC 8414 §3 first, then the ones of
OpenID Connect Discovery. The
issuer of the metadata must be the issuer of the tokens.
When the server cannot reach the issuer URL, pass the URL of the keys with --oauth-jwks-uri.
For instance, a server in a container may reach the authorization server under another host
name than the one of the issuer:
$ scim2-server \
--oauth-issuer http://localhost:8080/realms/apps \
--oauth-audience https://scim.example.com \
--oauth-jwks-uri http://keycloak:8080/realms/apps/protocol/openid-connect/certs
The service provider configuration announces the oauth2 scheme. An access token is accepted
when:
its
typheader isat+jwt;one of the keys of the JWK set signs it;
its
issclaim is the issuer, and itsaudclaim holds the audience;it is not expired;
it has the
sub,client_id,iatandjticlaims.
Pass --oauth-scope to also require a scope. Several scopes separated by spaces are all
required.
The server downloads the metadata and the keys with the first token, then keeps them. A token signed with an unknown key downloads the keys again, at most once a minute. The server can thus start before the authorization server.
The responses follow RFC 6750 §3:
A request without access token gets a 401 response, with a
Bearerchallenge without error code.An invalid or expired access token gets a 401 response, with the
invalid_tokenerror code. The client can then request a new token. Thedetailof the SCIM error tells why the token is refused, such asInvalid access token: Invalid claim: 'aud'.An access token without the scope gets a 403 response, with the
insufficient_scopeerror code.When the metadata or the keys cannot be downloaded, the server answers 503, with a
Retry-Afterheader of 5 seconds. The client can send the request again later, with the same token.
The OAuth options can be combined with --bearer-token and --basic-auth.
With Keycloak, set the access.token.header.type.rfc9068 attribute of the client to true.
Otherwise the typ header of the tokens is JWT, and they are refused. Add an Audience
mapper, so that the aud claim holds the audience of the server.
Serve several tenants#
A tenant is an independent set of resources. The first segment of the URL path selects it, with
the URL prefix method of RFC 7644 §6.1: /a/v2/Users
and /b/v2/Users hold separate users. The schemas, the resource types, the configuration
and the credentials are shared.
Pass each tenant with --tenant:
$ scim2-server --tenant a --tenant b
A request to another tenant gets a 404 response. v2 cannot be a tenant name.
To create the tenants on demand, pass --dynamic-tenants. The first request to an unknown
tenant creates it, with no resources:
$ scim2-server --dynamic-tenants
Each test of a test suite can then pick a random tenant, and get an empty server. Any client can create tenants, even without valid credentials, and every tenant stays in memory until the server stops. Serve this option to trusted clients only.
Compare the usernames with PRECIS#
By default, the server compares the usernames without the PRECIS rules that
RFC 7644 §5 requires. Pass --precis to compare the usernames and the
passwords with the PRECIS profiles of RFC 8265. The server then refuses with invalidValue
a username that PRECIS does not allow, such as one holding a space. The option needs the
precis extra:
$ pip install 'scim2-server[precis]'
$ scim2-server --precis
Keep the resources of a run#
Pass --dump-resources with a file name. When the server stops normally, with Ctrl+C, it
writes every resource to the file, as a JSON list. With tenants, the file holds a JSON object,
with the list of the resources of each tenant. The server empties the file when it starts:
$ scim2-server --dump-resources resources.json
Run the server in a container#
Each release publishes an image on the GitHub container registry. The server listens on
0.0.0.0:8080 in the container, and the arguments are passed to scim2-server:
$ docker run --publish 8080:8080 ghcr.io/python-scim/scim2-server --bearer-token s3cret
To build the image, run docker build --file Containerfile . or podman build . in the
repository.
Run the server behind a reverse proxy#
Pass --reverse-proxy. The server then reads the X-Forwarded-For, X-Forwarded-Proto,
X-Forwarded-Host, X-Forwarded-Port and X-Forwarded-Prefix headers, and builds the
meta.location of the resources from the URL the client used.
Debug the server#
Pass --debug to log the WSGI environment of each request, with its headers.
Warning
The logged environment holds the credentials. Never use --debug on a server reachable by
others.
Options#
usage: scim2-server [-h] [--schema FILE] [--resource-type FILE]
[--service-provider-config FILE] [--bearer-token TOKEN]
[--basic-auth USER:PASSWORD] [--oauth-issuer URL]
[--oauth-audience VALUE] [--oauth-jwks-uri URL]
[--oauth-scope SCOPE] [--precis] [--hostname HOST]
[--port PORT] [--reverse-proxy] [--dump-resources FILE]
[--tenant TENANT] [--dynamic-tenants] [--debug]
Named Arguments#
- --schema
Serve the schemas of a JSON file holding a list of schemas. Defaults to the schemas of RFC 7643.
- --resource-type
Serve the resource types of a JSON file holding a list of resource types. Defaults to User and Group.
- --service-provider-config
Announce the service provider configuration of a JSON file. Defaults to every feature supported, except cursor pagination.
- --bearer-token
Accept a static bearer token, and announce the bearer token scheme. Can be repeated.
- --basic-auth
Accept HTTP Basic credentials, and announce the Basic scheme. Can be repeated. Without –bearer-token, –basic-auth nor –oauth-issuer, the server accepts every request.
- --oauth-issuer
Accept the OAuth JWT access tokens (RFC 9068) of this issuer, and announce the oauth2 scheme. Needs –oauth-audience and the oauth extra.
- --oauth-audience
Accept only the access tokens whose aud claim holds this value.
- --oauth-jwks-uri
Check the signature of the access tokens with the keys of this JWK set URL. Defaults to the jwks_uri of the metadata of the issuer.
- --oauth-scope
Accept only the access tokens with this scope. Several scopes separated by spaces are all required.
- --precis
Compare the usernames and the passwords with the PRECIS profiles of RFC 8265, and refuse the values they do not allow. Needs the precis extra.
- --hostname
Listen on this address. Defaults to 127.0.0.1.
- --port
Listen on this port. Defaults to 8080.
- --reverse-proxy
Read the X-Forwarded-* headers of a reverse proxy.
- --dump-resources
Write the resources to a JSON file when the server stops.
- --tenant
Serve a tenant under /TENANT, with its own resources. Can be repeated.
- --dynamic-tenants
Create a tenant on the first request to /TENANT. Any client can create tenants, and they stay in memory until the server stops.
- --debug
Log the WSGI environment of each request. Never use it on a server reachable by others.