# Configuration & Guides
## Configuration Reference
All options are set under `config :ex_scim`.
### Core
| Key | Default | Description |
|-----|---------|-------------|
| `:base_url` | `"http://localhost:4000"` | Base URL for SCIM endpoints. Falls back to `SCIM_BASE_URL` env var. |
| `:storage_strategy` | `ExScim.Storage.EtsStorage` | Module implementing `ExScim.Storage.Adapter` |
| `:auth_provider_adapter` | *required* | Module implementing `ExScim.Auth.AuthProvider.Adapter` |
### Resource Mapping
| Key | Default | Description |
|-----|---------|-------------|
| `:user_resource_mapper` | `ExScim.Users.Mapper.DefaultMapper` | Module implementing `ExScim.Users.Mapper.Adapter` |
| `:group_resource_mapper` | `ExScim.Groups.Mapper.DefaultMapper` | Module implementing `ExScim.Groups.Mapper.Adapter` |
### Ecto Storage (`ex_scim_ecto`)
| Key | Default | Description |
|-----|---------|-------------|
| `:storage_repo` | *required* | Your Ecto `Repo` module |
| `:user_model` | *required* | Ecto schema module or `{Schema, opts}` tuple |
| `:group_model` | *required* | Same format as `:user_model` |
When using the `{Schema, opts}` tuple form, available sub-options are:
- `:preload` - list of associations to preload (default `[]`)
- `:lookup_key` - primary key field (default `:id`)
- `:filter_mapping` - map of SCIM attribute paths to DB columns (default `%{}`)
- `:tenant_key` - column used for multi-tenant scoping (default `nil`)
- `:field_mapping` - map of domain fields to `{db_field, to_storage_fn, from_storage_fn}` tuples for value transformation (default `%{}`)
```elixir
config :ex_scim,
storage_strategy: ExScimEcto.StorageAdapter,
storage_repo: MyApp.Repo,
user_model:
{MyApp.Accounts.User,
preload: [:roles],
lookup_key: :uuid,
filter_mapping: %{"emails.value" => :email},
tenant_key: :organization_id,
field_mapping: %{
active: {:status,
fn true -> "active"; false -> "inactive" end,
fn "active" -> true; _ -> false end}
}},
group_model:
{MyApp.Groups.Group,
preload: [:members],
tenant_key: :organization_id}
```
### Capabilities
Reported in the ServiceProviderConfig discovery endpoint.
| Key | Default |
|-----|---------|
| `:patch_supported` | `false` |
| `:bulk_supported` | `true` |
| `:bulk_max_operations` | `1000` |
| `:bulk_max_payload_size` | `1_048_576` |
| `:filter_supported` | `false` |
| `:filter_max_results` | `200` |
| `:sort_supported` | `false` |
| `:change_password_supported` | `false` |
| `:etag_supported` | `false` |
### Discovery
| Key | Default | Description |
|-----|---------|-------------|
| `:documentation_uri` | `nil` | URI included in ServiceProviderConfig |
| `:authentication_schemes` | `[]` | List of scheme maps per RFC 7643 |
| `:resource_types` | User + Group | List of resource type maps |
| `:schema_modules` | Built-in User, EnterpriseUser, Group | Schema definition modules |
### Lifecycle & Tenancy
| Key | Default | Description |
|-----|---------|-------------|
| `:lifecycle_adapter` | `nil` | Module implementing `ExScim.Lifecycle.Adapter` |
| `:tenant_resolver` | `nil` | Module implementing `ExScim.Tenant.Resolver` |
## Authorization Scopes
Scopes are strings in the `ExScim.Scope` struct's `:scopes` list, populated by your `AuthProvider.Adapter` when validating a token or credentials.
### Standard scopes
| Scope | Endpoints | Actions |
|---|---|---|
| `scim:read` | `/Users`, `/Groups`, `/Schemas`, `/ResourceTypes`, `/ServiceProviderConfig` | GET (list, show, search) |
| `scim:create` | `/Users`, `/Groups`, `/Bulk` (POST operations) | POST |
| `scim:update` | `/Users`, `/Groups`, `/Bulk` (PUT/PATCH operations) | PUT, PATCH |
| `scim:delete` | `/Users`, `/Groups`, `/Bulk` (DELETE operations) | DELETE |
### `/Me` scopes
| Scope | Action |
|---|---|
| `scim:me:read` | GET `/Me` |
| `scim:me:create` | POST `/Me` |
| `scim:me:update` | PUT `/Me`, PATCH `/Me` |
| `scim:me:delete` | DELETE `/Me` |
### Bulk operations
For `/Bulk`, scope is enforced per individual operation rather than on the request as a whole. A caller with only `scim:create` may submit a bulk request containing POST operations; any PUT, PATCH, or DELETE operations in the same payload will return a `403` operation result (and count toward `failOnErrors`).
### Example scope lists
```elixir
# Read-only client
scopes: ["scim:read"]
# Provisioning client - can create and update users, but not delete them
scopes: ["scim:read", "scim:create", "scim:update"]
# Full-access admin client
scopes: ["scim:read", "scim:create", "scim:update", "scim:delete"]
# Self-service user (Me endpoint only)
scopes: ["scim:me:read", "scim:me:update"]
```
## Multi-Tenancy
Multi-tenancy is opt-in. When no `tenant_resolver` is configured (or `scope.tenant_id` is `nil`), the system operates in single-tenant mode with no isolation applied.
To enable it, wire up three pieces:
1. **Tenant resolver** - implement `ExScim.Tenant.Resolver` to extract a tenant identifier from the request (header, subdomain, path, etc.).
2. **Phoenix plug** - add `ExScimPhoenix.Plugs.ScimTenant` to your SCIM pipeline after `ScimAuth`.
3. **Ecto tenant key** - set `:tenant_key` on your model tuples so queries are scoped and creates inject the tenant ID.
```elixir
# 1. Resolver
defmodule MyApp.TenantResolver do
@behaviour ExScim.Tenant.Resolver
@impl true
def resolve_tenant(conn, _scope) do
case Plug.Conn.get_req_header(conn, "x-tenant-id") do
[tenant_id] -> {:ok, tenant_id}
_ -> {:error, :missing_tenant}
end
end
end
# 2. Config
config :ex_scim,
tenant_resolver: MyApp.TenantResolver,
user_model: {MyApp.Accounts.User, tenant_key: :organization_id},
group_model: {MyApp.Groups.Group, tenant_key: :organization_id}
# 3. Pipeline
# Add: plug ExScimPhoenix.Plugs.ScimTenant
```
The resolver can optionally implement `tenant_scim_base_url/1` to generate tenant-specific resource location URLs (e.g., `https://acme.example.com/scim/v2`).
## Custom Adapters
### Storage Adapter
Implement the `ExScim.Storage.Adapter` behaviour to use a custom data store:
```elixir
defmodule MyApp.CustomStorage do
@behaviour ExScim.Storage.Adapter
def get_user(id), do: # your implementation
def create_user(user_data), do: # your implementation
# ... other callbacks
end
```
### Resource Mapper
Implement `ExScim.Users.Mapper.Adapter` or `ExScim.Groups.Mapper.Adapter` to control how domain structs map to SCIM JSON:
```elixir
defmodule MyApp.UserMapper do
@behaviour ExScim.Users.Mapper.Adapter
def from_scim(scim_data) do
%MyApp.User{
username: scim_data["userName"],
email: get_primary_email(scim_data["emails"])
}
end
def to_scim(%MyApp.User{} = user, _opts) do
%{
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id" => user.id,
"userName" => user.username,
"emails" => format_emails(user.email)
}
end
end
```
## Endpoints
All endpoints are served under the scope you configure (typically `/scim/v2`).
### Users
| Method | Path | Description | RFC |
|--------|------|-------------|-----|
| `GET` | `/Users` | List with filtering, sorting, pagination | [§3.4.2](https://www.rfc-editor.org/rfc/rfc7644#section-3.4.2) |
| `POST` | `/Users` | Create | [§3.3](https://www.rfc-editor.org/rfc/rfc7644#section-3.3) |
| `GET` | `/Users/{id}` | Fetch by ID | [§3.4.1](https://www.rfc-editor.org/rfc/rfc7644#section-3.4.1) |
| `PUT` | `/Users/{id}` | Replace | [§3.5.1](https://www.rfc-editor.org/rfc/rfc7644#section-3.5.1) |
| `PATCH` | `/Users/{id}` | Partial update (JSON Patch) | [§3.5.2](https://www.rfc-editor.org/rfc/rfc7644#section-3.5.2) |
| `DELETE` | `/Users/{id}` | Delete | [§3.6](https://www.rfc-editor.org/rfc/rfc7644#section-3.6) |
Groups and Me follow the same pattern. See [RFC 7644 §3.11](https://www.rfc-editor.org/rfc/rfc7644#section-3.11) for Me endpoint details.
### Other
| Method | Path | Description | RFC |
|--------|------|-------------|-----|
| `POST` | `/.search` | Cross-resource search | [§3.4.3](https://www.rfc-editor.org/rfc/rfc7644#section-3.4.3) |
| `POST` | `/Bulk` | Bulk operations | [§3.7](https://www.rfc-editor.org/rfc/rfc7644#section-3.7) |
| `GET` | `/ServiceProviderConfig` | Server capabilities | [§4](https://www.rfc-editor.org/rfc/rfc7644#section-4) |
| `GET` | `/ResourceTypes` | Supported resource types | [§4](https://www.rfc-editor.org/rfc/rfc7644#section-4) |
| `GET` | `/Schemas` | Schema definitions | [§4](https://www.rfc-editor.org/rfc/rfc7644#section-4) |