canaille-globuzma/canaille/scim/models.py
Éloi Rivard 92214d932d
wip
2024-12-11 17:46:14 +01:00

291 lines
10 KiB
Python

from flask import url_for
from scim2_models import AuthenticationScheme
from scim2_models import Bulk
from scim2_models import ChangePassword
from scim2_models import EnterpriseUser
from scim2_models import ETag
from scim2_models import Filter
from scim2_models import Group
from scim2_models import Meta
from scim2_models import Mutability
from scim2_models import Patch
from scim2_models import Required
from scim2_models import Resource
from scim2_models import ResourceType
from scim2_models import Schema
from scim2_models import SchemaExtension
from scim2_models import ServiceProviderConfig
from scim2_models import Sort
from scim2_models import User
from canaille.app import models
from canaille.backends import Backend
# At the difference of SCIM User, Canaille User need a 'family_name'
# (because the LDAP 'sn' is mandatory) and the 'user_name'
# attribute is immutable (because it is part of the LDAP DN).
user_schema = User.to_schema()
user_schema["name"].required = Required.true
user_schema["name"]["familyName"].required = Required.true
user_schema["userName"].mutability = Mutability.immutable
User = Resource.from_schema(user_schema)
# At the difference of the SCIM Group, Canaille Group must have a display_name.
# and 'members' cannot be null.
group_schema = Group.to_schema()
group_schema["displayName"].required = Required.true
group_schema["displayName"].mutability = Mutability.immutable
group_schema["members"].required = Required.true
group_schema["members"]["value"].required = Required.true
group_schema["members"]["$ref"].required = Required.true
Group = Resource.from_schema(group_schema)
def get_service_provider_config():
return ServiceProviderConfig(
meta=Meta(
resource_type="ServiceProviderConfig",
location=url_for("scim.query_service_provider_config", _external=True),
),
documentation_uri="https://canaille.readthedocs.io",
patch=Patch(supported=False),
bulk=Bulk(supported=False, max_operations=0, max_payload_size=0),
change_password=ChangePassword(supported=True),
filter=Filter(supported=False, max_results=0),
sort=Sort(supported=False),
etag=ETag(supported=False),
authentication_schemes=[
AuthenticationScheme(
name="OAuth Bearer Token",
description="Authentication scheme using the OAuth Bearer Token Standard",
spec_uri="http://www.rfc-editor.org/info/rfc6750",
documentation_uri="https://canaille.readthedocs.io",
type="oauthbearertoken",
primary=True,
),
],
)
def get_resource_types():
"""The resource types implemented by Canaille."""
return {
"User": ResourceType(
id="User",
name="User",
endpoint=url_for("scim.query_users", _external=True),
description="User accounts",
schema_="urn:ietf:params:scim:schemas:core:2.0:User",
schema_extensions=[
SchemaExtension(
schema_="urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
required=True,
)
],
meta=Meta(
resource_type="ResourceType",
location=url_for(
"scim.query_resource_type",
resource_type_name="User",
_external=True,
),
),
),
"Group": ResourceType(
id="Group",
name="Group",
endpoint=url_for("scim.query_groups", _external=True),
description="Group management",
schema_="urn:ietf:params:scim:schemas:core:2.0:Group",
meta=Meta(
resource_type="ResourceType",
location=url_for(
"scim.query_resource_type",
resource_type_name="Group",
_external=True,
),
),
),
}
def get_schemas():
schemas = {
"urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig": ServiceProviderConfig.to_schema(),
"urn:ietf:params:scim:schemas:core:2.0:ResourceType": ResourceType.to_schema(),
"urn:ietf:params:scim:schemas:core:2.0:Schema": Schema.to_schema(),
"urn:ietf:params:scim:schemas:core:2.0:User": User.to_schema(),
"urn:ietf:params:scim:schemas:core:2.0:Group": Group.to_schema(),
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": EnterpriseUser.to_schema(),
}
for schema_id, schema in schemas.items():
schema.meta = Meta(
resource_type="Schema",
location=url_for("scim.query_schema", schema_id=schema_id, _external=True),
)
return schemas
def user_from_canaille_to_scim(user):
scim_user = User[EnterpriseUser](
meta=Meta(
resource_type="User",
created=user.created,
last_modified=user.last_modified,
location=url_for("scim.query_user", user=user, _external=True),
),
id=user.id,
user_name=user.user_name,
# password=user.password,
preferred_language=user.preferred_language,
name=User.Name(
formatted=user.formatted_name,
family_name=user.family_name,
given_name=user.given_name,
)
if (user.formatted_name or user.family_name or user.given_name)
else None,
display_name=user.display_name,
title=user.title,
profile_url=user.profile_url,
emails=[
User.Emails(
value=email,
primary=email == user.emails[0],
)
for email in user.emails or []
]
or None,
phone_numbers=[
User.PhoneNumbers(
value=phone_number, primary=phone_number == user.phone_numbers[0]
)
for phone_number in user.phone_numbers or []
]
or None,
addresses=[
User.Addresses(
formatted=user.formatted_address,
street_address=user.street,
postal_code=user.postal_code,
locality=user.locality,
region=user.region,
primary=True,
)
]
if (
user.formatted_address
or user.street
or user.postal_code
or user.locality
or user.region
)
else None,
photos=[
User.Photos(
value=url_for(
"core.account.photo", user=user, field="photo", _external=True
),
primary=True,
type=User.Photos.Type.photo,
)
]
if user.photo
else None,
groups=[
User.Groups(
value=group.id,
display=group.display_name,
ref=url_for("scim.query_group", group=group, _external=True),
)
for group in user.groups or []
]
or None,
)
scim_user[EnterpriseUser] = EnterpriseUser(
employee_number=user.employee_number,
organization=user.organization,
department=user.department,
)
return scim_user
def user_from_scim_to_canaille(scim_user: User, user):
user.user_name = scim_user.user_name
user.password = scim_user.password
user.preferred_language = scim_user.preferred_language
user.formatted_name = scim_user.name.formatted if scim_user.name else None
user.family_name = scim_user.name.family_name if scim_user.name else None
user.given_name = scim_user.name.given_name if scim_user.name else None
user.display_name = scim_user.display_name
user.title = scim_user.title
user.profile_url = scim_user.profile_url
user.emails = [email.value for email in scim_user.emails or []] or None
user.phone_numbers = [
phone_number.value for phone_number in scim_user.phone_numbers or []
] or None
user.formatted_address = (
scim_user.addresses[0].formatted if scim_user.addresses else None
)
user.street = scim_user.addresses[0].street_address if scim_user.addresses else None
user.postal_code = (
scim_user.addresses[0].postal_code if scim_user.addresses else None
)
user.locality = scim_user.addresses[0].locality if scim_user.addresses else None
user.region = scim_user.addresses[0].region if scim_user.addresses else None
# TODO: delete the photo
# if scim_user.photos and scim_user.photos[0].value:
# user.photo = scim_user.photos[0].value
user.employee_number = (
scim_user[EnterpriseUser].employee_number if scim_user[EnterpriseUser] else None
)
user.organization = (
scim_user[EnterpriseUser].organization if scim_user[EnterpriseUser] else None
)
user.department = (
scim_user[EnterpriseUser].department if scim_user[EnterpriseUser] else None
)
user.groups = [
Backend.instance.get(models.Group, group.value)
for group in scim_user.groups or []
if group.value
]
return user
def group_from_canaille_to_scim(group):
return Group(
id=group.id,
meta=Meta(
resource_type="Group",
created=group.created,
last_modified=group.last_modified,
location=url_for("scim.query_group", group=group, _external=True),
),
display_name=group.display_name,
members=[
Group.Members(
value=user.id,
type="User",
display=user.display_name,
ref=url_for("scim.query_user", user=user, _external=True),
)
for user in group.members or []
]
or None,
)
def group_from_scim_to_canaille(scim_group: Group, group):
group.display_name = scim_group.display_name
members = []
for member in scim_group.members or []:
# extract the user identifier from scim/v2/Users/<identifier>
identifier = member.ref.split("/")[-1]
members.append(Backend.instance.get(models.User, identifier))
group.members = members
return group