SCIM 2.0¶
NeoZen implements SCIM 2.0 (System for Cross-domain Identity Management) for enterprise identity provisioning. This lets external identity providers (Okta, Azure AD, OneLogin, etc.) automatically create, update, and deprovision users in your application.
Quick Start¶
from neozen.core.scim import SCIMUserService, SCIMGroupService
from neozen.web.scim import (
UsersEndpoint,
GroupsEndpoint,
DiscoveryEndpoints,
)
# Implement adapters that bridge your User/Group models to SCIM
class MyUserAdapter:
async def create(self, user): ...
async def get(self, user_id): ...
async def get_by_username(self, user_name): ...
async def update(self, user_id, user): ...
async def delete(self, user_id): ...
async def list_all(self): ...
# Wire it up
user_service = SCIMUserService(
adapter=MyUserAdapter(),
base_url="https://api.example.com/scim/v2",
)
users_endpoint = UsersEndpoint(user_service)
discovery = DiscoveryEndpoints()
# Use in your framework's routes:
# GET /Users -> users_endpoint.list(query_params)
# GET /Users/{id} -> users_endpoint.get(id)
# POST /Users -> users_endpoint.create(json_body)
# PUT /Users/{id} -> users_endpoint.replace(id, json_body)
# DELETE /Users/{id} -> users_endpoint.delete(id)
# GET /ServiceProviderConfig -> discovery.service_provider_config_handler()
Resources¶
NeoZen ships with the standard SCIM 2.0 User and Group schemas:
from neozen.core.scim import User, Group, Email, Name, GroupMember
user = User(
user_name="alice",
display_name="Alice Smith",
name=Name(given_name="Alice", family_name="Smith"),
emails=[Email(value="alice@example.com", primary=True)],
active=True,
)
group = Group(
display_name="Engineers",
members=[GroupMember(value="user_1", display="Alice", type="User")],
)
Filtering¶
NeoZen includes a SCIM filter parser that supports:
| Operator | Meaning | Example |
|---|---|---|
eq |
equals (case-insensitive for strings) | userName eq "alice" |
ne |
not equals | active ne false |
co |
contains | userName co "ali" |
sw |
starts with | userName sw "ali" |
ew |
ends with | userName ew "ice" |
gt, ge, lt, le |
comparison | count gt 5 |
pr |
present | displayName pr |
and, or, not |
logical | active eq true and userName sw "a" |
result = await user_service.list_users(
filter_expr='userName sw "alice" and active eq true',
start_index=1,
count=10,
)
Discovery¶
The discovery endpoints expose:
/ServiceProviderConfig-- features (filter, patch, sort, bulk, ...)/ResourceTypes-- User and Group resource type descriptors/Schemas-- attribute schemas for each resource type
from neozen.web.scim import DiscoveryEndpoints, ServiceProviderConfig
config = ServiceProviderConfig(
patch_supported=False,
bulk_supported=False,
filter_supported=True,
change_password_supported=True,
)
discovery = DiscoveryEndpoints(service_provider_config=config)
Errors¶
SCIM errors follow RFC 7644 Section 3.12:
from neozen.core.scim import SCIMNotFoundError, SCIMUniquenessError
try:
user = await users_endpoint.get("missing")
except SCIMNotFoundError as e:
body = e.to_response() # SCIM error JSON
status = e.status # 404
Authentication¶
SCIM endpoints should be protected with OAuth 2.0 bearer token
authentication. Use OAuthTokenService.introspect_access_token to
verify tokens before passing requests to the SCIM endpoints.