clean up files

This commit is contained in:
Jonathan Griffin 2025-04-18 15:59:41 +02:00
parent 464b9b1b47
commit 62736ff29b
9 changed files with 1239 additions and 925 deletions

View file

@ -0,0 +1,70 @@
{
"id": "urn:ietf:params:scim:schemas:core:2.0:Group",
"name": "Group",
"description": "Group",
"attributes": [
{
"name": "displayName",
"type": "string",
"multiValued": false,
"description": "Human readable name for the Group. REQUIRED.",
"required": false,
"caseExact": false,
"mutability": "readWrite",
"returned": "default",
"uniqueness": "none"
},
{
"name": "members",
"type": "complex",
"multiValued": true,
"description": "A list of members of the Group.",
"required": false,
"subAttributes": [
{
"name": "value",
"type": "string",
"multiValued": false,
"description": "Identifier of the member of this Group.",
"required": false,
"caseExact": false,
"mutability": "immutable",
"returned": "default",
"uniqueness": "none"
},
{
"name": "$ref",
"type": "reference",
"referenceTypes": ["User", "Group"],
"multiValued": false,
"description": "The URI of the corresponding member resource of this Group.",
"required": false,
"caseExact": false,
"mutability": "immutable",
"returned": "default",
"uniqueness": "none"
},
{
"name": "type",
"type": "string",
"multiValued": false,
"description": "A label indicating the type of resource; e.g., 'User' or 'Group'.",
"required": false,
"caseExact": false,
"canonicalValues": ["User", "Group"],
"mutability": "immutable",
"returned": "default",
"uniqueness": "none"
}
],
"mutability": "readWrite",
"returned": "default"
}
],
"meta": {
"resourceType": "Schema",
"location": "/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:Group",
"created": "2025-04-17T15:48:00",
"lastModified": "2025-04-17T15:48:00"
}
}

View file

@ -0,0 +1,19 @@
[
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:ResourceType"
],
"id": "User",
"name": "User",
"endpoint": "/Users",
"description": "User account",
"schema": "urn:ietf:params:scim:schemas:core:2.0:User",
"meta": {
"resourceType": "ResourceType",
"created": "2025-04-16T08:37:00Z",
"lastModified": "2025-04-16T08:37:00Z",
"location": "ResourceType/User"
},
"schemaExtensions": []
}
]

View file

@ -0,0 +1,102 @@
{
"id": "urn:ietf:params:scim:schemas:core:2.0:ResourceType",
"name": "ResourceType",
"description": "Specifies the schema that describes a SCIM Resource Type",
"attributes": [
{
"name": "id",
"type": "string",
"multiValued": false,
"description": "The resource type's server unique id. May be the same as the 'name' attribute.",
"required": false,
"caseExact": false,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "name",
"type": "string",
"multiValued": false,
"description": "The resource type name.",
"required": true,
"caseExact": false,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "description",
"type": "string",
"multiValued": false,
"description": "The resource type's human readable description.",
"required": false,
"caseExact": false,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "endpoint",
"type": "reference",
"referenceTypes": ["uri"],
"multiValued": false,
"description": "The resource type's HTTP addressable endpoint relative to the Base URL; e.g., /Users.",
"required": true,
"caseExact": false,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "schema",
"type": "reference",
"referenceTypes": ["uri"],
"multiValued": false,
"description": "The resource types primary/base schema URI.",
"required": true,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "schemaExtensions",
"type": "complex",
"multiValued": true,
"description": "A list of URIs of the resource type's schema extensions",
"required": false,
"mutability": "readOnly",
"returned": "default",
"subAttributes": [
{
"name": "schema",
"type": "reference",
"referenceTypes": ["uri"],
"multiValued": false,
"description": "The URI of a schema extension.",
"required": true,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "required",
"type": "boolean",
"multiValued": false,
"description": "Specifies whether the schema extension is required for the resource type.",
"required": true,
"mutability": "readOnly",
"returned": "default"
}
]
}
],
"meta": {
"resourceType": "Schema",
"location": "/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:ResourceType",
"created": "2025-04-17T15:48:00",
"lastModified": "2025-04-17T15:48:00"
}
}

View file

@ -0,0 +1,304 @@
{
"id": "urn:ietf:params:scim:schemas:core:2.0:Schema",
"name": "Schema",
"description": "Specifies the schema that describes a SCIM Schema",
"attributes": [
{
"name": "id",
"type": "string",
"multiValued": false,
"description": "The unique URI of the schema.",
"required": true,
"caseExact": false,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "name",
"type": "string",
"multiValued": false,
"description": "The schema's human readable name.",
"required": true,
"caseExact": false,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "description",
"type": "string",
"multiValued": false,
"description": "The schema's human readable description.",
"required": false,
"caseExact": false,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "attributes",
"type": "complex",
"multiValued": true,
"description": "A complex attribute that includes the attributes of a schema",
"required": true,
"mutability": "readOnly",
"returned": "default",
"subAttributes": [
{
"name": "name",
"type": "string",
"multiValued": false,
"description": "The attribute's name",
"required": true,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "type",
"type": "string",
"multiValued": false,
"description": "The attribute's data type.",
"required": true,
"canonicalValues": ["string","complex","boolean","decimal","integer","dateTime","reference"],
"caseExact": false,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "multiValued",
"type": "boolean",
"multiValued": false,
"description": "Boolean indicating an attribute's plurality.",
"required": true,
"mutability": "readOnly",
"returned": "default"
},
{
"name": "description",
"type": "string",
"multiValued": false,
"description": "A human readable description of the attribute.",
"required": false,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "required",
"type": "boolean",
"multiValued": false,
"description": "A boolean indicating if the attribute is required.",
"required": false,
"mutability": "readOnly",
"returned": "default"
},
{
"name": "canonicalValues",
"type": "string",
"multiValued": true,
"description": "A collection of canonical values.",
"required": false,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "caseExact",
"type": "boolean",
"multiValued": false,
"description": "Indicates if a string attribute is case-sensitive.",
"required": false,
"mutability": "readOnly",
"returned": "default"
},
{
"name": "mutability",
"type": "string",
"multiValued": false,
"description": "Indicates if an attribute is modifiable.",
"required": false,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none",
"canonicalValues": ["readOnly","readWrite","immutable","writeOnly"]
},
{
"name": "returned",
"type": "string",
"multiValued": false,
"description": "Indicates when an attribute is returned in a response.",
"required": false,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none",
"canonicalValues": ["always","never","default","request"]
},
{
"name": "uniqueness",
"type": "string",
"multiValued": false,
"description": "Indicates how unique a value must be.",
"required": false,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none",
"canonicalValues": ["none","server","global"]
},
{
"name": "referenceTypes",
"type": "string",
"multiValued": true,
"description": "Specifies a resourceType that a reference attribute may refer to.",
"required": false,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "subAttributes",
"type": "complex",
"multiValued": true,
"description": "Used to define the sub-attributes of a complex attribute",
"required": false,
"mutability": "readOnly",
"returned": "default",
"subAttributes": [
{
"name": "name",
"type": "string",
"multiValued": false,
"description": "The sub-attribute's name",
"required": true,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "type",
"type": "string",
"multiValued": false,
"description": "The sub-attribute's data type.",
"required": true,
"canonicalValues": ["string","complex","boolean","decimal","integer","dateTime","reference"],
"caseExact": false,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "multiValued",
"type": "boolean",
"multiValued": false,
"description": "Boolean indicating sub-attribute plurality.",
"required": true,
"mutability": "readOnly",
"returned": "default"
},
{
"name": "description",
"type": "string",
"multiValued": false,
"description": "Human readable description of the sub-attribute.",
"required": false,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "required",
"type": "boolean",
"multiValued": false,
"description": "Whether the sub-attribute is required.",
"required": false,
"mutability": "readOnly",
"returned": "default"
},
{
"name": "canonicalValues",
"type": "string",
"multiValued": true,
"description": "A collection of canonical values for the sub-attribute.",
"required": false,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "caseExact",
"type": "boolean",
"multiValued": false,
"description": "Case sensitivity of the sub-attribute.",
"required": false,
"mutability": "readOnly",
"returned": "default"
},
{
"name": "mutability",
"type": "string",
"multiValued": false,
"description": "Modifiability of the sub-attribute.",
"required": false,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"canonicalValues": ["readOnly","readWrite","immutable","writeOnly"]
},
{
"name": "returned",
"type": "string",
"multiValued": false,
"description": "When the sub-attribute is returned.",
"required": false,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"canonicalValues": ["always","never","default","request"]
},
{
"name": "uniqueness",
"type": "string",
"multiValued": false,
"description": "Uniqueness constraint of the sub-attribute.",
"required": false,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none",
"canonicalValues": ["none","server","global"]
},
{
"name": "referenceTypes",
"type": "string",
"multiValued": true,
"description": "ResourceTypes that the sub-attribute may reference.",
"required": false,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
}
]
}
]
}
],
"meta": {
"resourceType": "Schema",
"location": "/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:Schema",
"created": "2025-04-17T15:48:00",
"lastModified": "2025-04-17T15:48:00"
}
}

View file

@ -0,0 +1,41 @@
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"
],
"patch": {
"supported": false
},
"bulk": {
"supported": false,
"maxOperations": 0,
"maxPayloadSize": 0
},
"filter": {
"supported": false,
"maxResults": 0
},
"changePassword": {
"supported": false
},
"sort": {
"supported": false
},
"etag": {
"supported": false
},
"authenticationSchemes": [
{
"type": "oauthbearertoken",
"name": "OAuth Bearer Token",
"description": "Authentication scheme using the OAuth Bearer Token Standard. The access token should be sent in the 'Authorization' header using the Bearer schema.",
"specUri": "https://tools.ietf.org/html/rfc6750",
"primary": true
}
],
"meta": {
"resourceType": "ServiceProviderConfig",
"created": "2025-04-15T15:45:00Z",
"lastModified": "2025-04-15T15:45:00Z",
"location": "/ServiceProviderConfig"
}
}

View file

@ -0,0 +1,212 @@
{
"id": "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig",
"name": "Service Provider Configuration",
"description": "Schema for representing the service provider's configuration",
"attributes": [
{
"name": "documentationUri",
"type": "reference",
"referenceTypes": ["external"],
"multiValued": false,
"description": "An HTTP addressable URL pointing to the service provider's human consumable help documentation.",
"required": false,
"caseExact": false,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "patch",
"type": "complex",
"multiValued": false,
"description": "A complex type that specifies PATCH configuration options.",
"required": true,
"returned": "default",
"mutability": "readOnly",
"subAttributes": [
{
"name": "supported",
"type": "boolean",
"multiValued": false,
"description": "Boolean value specifying whether the operation is supported.",
"required": true,
"mutability": "readOnly",
"returned": "default"
}
]
},
{
"name": "bulk",
"type": "complex",
"multiValued": false,
"description": "A complex type that specifies BULK configuration options.",
"required": true,
"returned": "default",
"mutability": "readOnly",
"subAttributes": [
{
"name": "supported",
"type": "boolean",
"multiValued": false,
"description": "Boolean value specifying whether the operation is supported.",
"required": true,
"mutability": "readOnly",
"returned": "default"
},
{
"name": "maxOperations",
"type": "integer",
"multiValued": false,
"description": "An integer value specifying the maximum number of operations.",
"required": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "maxPayloadSize",
"type": "integer",
"multiValued": false,
"description": "An integer value specifying the maximum payload size in bytes.",
"required": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
}
]
},
{
"name": "filter",
"type": "complex",
"multiValued": false,
"description": "A complex type that specifies FILTER options.",
"required": true,
"returned": "default",
"mutability": "readOnly",
"subAttributes": [
{
"name": "supported",
"type": "boolean",
"multiValued": false,
"description": "Boolean value specifying whether the operation is supported.",
"required": true,
"mutability": "readOnly",
"returned": "default"
},
{
"name": "maxResults",
"type": "integer",
"multiValued": false,
"description": "Integer value specifying the maximum number of resources returned in a response.",
"required": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
}
]
},
{
"name": "changePassword",
"type": "complex",
"multiValued": false,
"description": "A complex type that specifies change password options.",
"required": true,
"returned": "default",
"mutability": "readOnly",
"subAttributes": [
{
"name": "supported",
"type": "boolean",
"multiValued": false,
"description": "Boolean value specifying whether the operation is supported.",
"required": true,
"mutability": "readOnly",
"returned": "default"
}
]
},
{
"name": "sort",
"type": "complex",
"multiValued": false,
"description": "A complex type that specifies sort result options.",
"required": true,
"returned": "default",
"mutability": "readOnly",
"subAttributes": [
{
"name": "supported",
"type": "boolean",
"multiValued": false,
"description": "Boolean value specifying whether the operation is supported.",
"required": true,
"mutability": "readOnly",
"returned": "default"
}
]
},
{
"name": "authenticationSchemes",
"type": "complex",
"multiValued": true,
"description": "A complex type that specifies supported Authentication Scheme properties.",
"required": true,
"returned": "default",
"mutability": "readOnly",
"subAttributes": [
{
"name": "name",
"type": "string",
"multiValued": false,
"description": "The common authentication scheme name; e.g., HTTP Basic.",
"required": true,
"caseExact": false,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "description",
"type": "string",
"multiValued": false,
"description": "A description of the authentication scheme.",
"required": true,
"caseExact": false,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "specUri",
"type": "reference",
"referenceTypes": ["external"],
"multiValued": false,
"description": "An HTTP addressable URL pointing to the Authentication Scheme's specification.",
"required": false,
"caseExact": false,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "documentationUri",
"type": "reference",
"referenceTypes": ["external"],
"multiValued": false,
"description": "An HTTP addressable URL pointing to the Authentication Scheme's usage documentation.",
"required": false,
"caseExact": false,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
}
]
}
],
"meta": {
"resourceType": "Schema",
"location": "/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig",
"created": "2025-04-17T15:48:00",
"lastModified": "2025-04-17T15:48:00"
}
}

View file

@ -0,0 +1,386 @@
{
"id": "urn:ietf:params:scim:schemas:core:2.0:User",
"name": "User",
"description": "User Account",
"attributes": [
{
"name": "schemas",
"type": "string",
"multiValued": true,
"description": "An array of Strings containing URI that are used to indicate the namespaces of the SCIM schemas that define the attributes present in the current JSON structure.",
"required": true,
"caseExact": false,
"mutability": "immutable",
"returned": "always",
"uniqueness": "none"
},
{
"name": "id",
"type": "string",
"multiValued": false,
"description": "Unique identifier for the resource, assigned by the service provider. MUST be non-empty, unique, stable, and non-reassignable. Clients MUST NOT specify this value.",
"required": true,
"caseExact": true,
"mutability": "readOnly",
"returned": "always",
"uniqueness": "server"
},
{
"name": "externalId",
"type": "string",
"multiValued": false,
"description": "Identifier for the resource as defined by the provisioning client. OPTIONAL; clients MAY include a non-empty value.",
"required": false,
"caseExact": true,
"mutability": "readWrite",
"returned": "default",
"uniqueness": "none"
},
{
"name": "meta",
"type": "complex",
"multiValued": false,
"description": "Resource metadata. MUST be ignored when provided by clients.",
"required": false,
"mutability": "readOnly",
"returned": "default",
"subAttributes": [
{
"name": "resourceType",
"type": "string",
"multiValued": false,
"description": "The resource type name.",
"required": false,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "created",
"type": "dateTime",
"multiValued": false,
"description": "The date and time the resource was added.",
"required": false,
"mutability": "readOnly",
"returned": "default"
},
{
"name": "lastModified",
"type": "dateTime",
"multiValued": false,
"description": "The most recent date and time the resource was modified.",
"required": false,
"mutability": "readOnly",
"returned": "default"
},
{
"name": "location",
"type": "reference",
"referenceTypes": ["external"],
"multiValued": false,
"description": "The URI of the resource being returned.",
"required": false,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
},
{
"name": "version",
"type": "string",
"multiValued": false,
"description": "The version (ETag) of the resource being returned.",
"required": false,
"caseExact": true,
"mutability": "readOnly",
"returned": "default",
"uniqueness": "none"
}
]
},
{
"name": "userName",
"type": "string",
"multiValued": false,
"description": "Unique identifier for the User, used to authenticate. REQUIRED.",
"required": true,
"caseExact": false,
"mutability": "readWrite",
"returned": "default",
"uniqueness": "server"
},
{
"name": "name",
"type": "complex",
"multiValued": false,
"description": "Components of the user's real name.",
"required": false,
"mutability": "readWrite",
"returned": "default",
"uniqueness": "none",
"subAttributes": [
{ "name": "formatted", "type": "string", "multiValued": false, "description": "Complete name, formatted for display.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "familyName", "type": "string", "multiValued": false, "description": "Family name.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "givenName", "type": "string", "multiValued": false, "description": "Given name.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "middleName", "type": "string", "multiValued": false, "description": "Middle name(s).", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "honorificPrefix","type": "string", "multiValued": false, "description": "Honorific prefix.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "honorificSuffix","type": "string", "multiValued": false, "description": "Honorific suffix.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" }
]
},
{
"name": "displayName",
"type": "string",
"multiValued": false,
"description": "Full name, suitable for display.",
"required": false,
"caseExact": false,
"mutability": "readWrite",
"returned": "default",
"uniqueness": "none"
},
{
"name": "nickName",
"type": "string",
"multiValued": false,
"description": "Casual name.",
"required": false,
"caseExact": false,
"mutability": "readWrite",
"returned": "default",
"uniqueness": "none"
},
{
"name": "profileUrl",
"type": "reference",
"referenceTypes": ["external"],
"multiValued": false,
"description": "URL of the user's profile.",
"required": false,
"caseExact": false,
"mutability": "readWrite",
"returned": "default",
"uniqueness": "none"
},
{
"name": "title",
"type": "string",
"multiValued": false,
"description": "User's title (e.g., 'Vice President').",
"required": false,
"caseExact": false,
"mutability": "readWrite",
"returned": "default",
"uniqueness": "none"
},
{
"name": "userType",
"type": "string",
"multiValued": false,
"description": "Relationship between organization and user.",
"required": false,
"caseExact": false,
"mutability": "readWrite",
"returned": "default",
"uniqueness": "none"
},
{
"name": "preferredLanguage",
"type": "string",
"multiValued": false,
"description": "Preferred language, e.g., 'en_US'.",
"required": false,
"caseExact": false,
"mutability": "readWrite",
"returned": "default",
"uniqueness": "none"
},
{
"name": "locale",
"type": "string",
"multiValued": false,
"description": "Locale for formatting, e.g., 'en-US'.",
"required": false,
"caseExact": false,
"mutability": "readWrite",
"returned": "default",
"uniqueness": "none"
},
{
"name": "timezone",
"type": "string",
"multiValued": false,
"description": "Time zone in Olson format, e.g., 'America/Los_Angeles'.",
"required": false,
"caseExact": false,
"mutability": "readWrite",
"returned": "default",
"uniqueness": "none"
},
{
"name": "active",
"type": "boolean",
"multiValued": false,
"description": "Administrative status.",
"required": false,
"mutability": "readWrite",
"returned": "default"
},
{
"name": "password",
"type": "string",
"multiValued": false,
"description": "Cleartext password for create/reset operations.",
"required": false,
"caseExact": false,
"mutability": "writeOnly",
"returned": "never",
"uniqueness": "none"
},
{
"name": "emails",
"type": "complex",
"multiValued": true,
"description": "Email addresses for the user.",
"required": false,
"mutability": "readWrite",
"returned": "default",
"uniqueness": "none",
"subAttributes": [
{ "name": "value", "type": "string", "multiValued": false, "description": "Email address.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "display", "type": "string", "multiValued": false, "description": "Display name.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "type", "type": "string", "multiValued": false, "description": "Type: 'work','home','other'.", "required": false, "caseExact": false, "canonicalValues": ["work","home","other"], "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "primary", "type": "boolean", "multiValued": false, "description": "Primary flag; one per list.", "required": false, "mutability": "readWrite", "returned": "default" }
]
},
{
"name": "phoneNumbers",
"type": "complex",
"multiValued": true,
"description": "Phone numbers for the user.",
"required": false,
"mutability": "readWrite",
"returned": "default",
"subAttributes": [
{ "name": "value", "type": "string", "multiValued": false, "description": "Phone number (tel URI).", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "display", "type": "string", "multiValued": false, "description": "Display name.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "type", "type": "string", "multiValued": false, "description": "Type: 'work','home','mobile','fax','pager','other'.", "required": false, "caseExact": false, "canonicalValues": ["work","home","mobile","fax","pager","other"], "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "primary", "type": "boolean", "multiValued": false, "description": "Primary flag; one per list.", "required": false, "mutability": "readWrite", "returned": "default" }
]
},
{
"name": "ims",
"type": "complex",
"multiValued": true,
"description": "Instant messaging addresses.",
"required": false,
"mutability": "readWrite",
"returned": "default",
"subAttributes": [
{ "name": "value", "type": "string", "multiValued": false, "description": "IM address.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "display", "type": "string", "multiValued": false, "description": "Display name.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "type", "type": "string", "multiValued": false, "description": "Type: 'aim','gtalk','icq','xmpp','msn','skype','qq','yahoo'.", "required": false, "caseExact": false, "canonicalValues": ["aim","gtalk","icq","xmpp","msn","skype","qq","yahoo"], "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "primary", "type": "boolean", "multiValued": false, "description": "Primary flag; one per list.", "required": false, "mutability": "readWrite", "returned": "default" }
]
},
{
"name": "photos",
"type": "complex",
"multiValued": true,
"description": "URLs of photos of the user.",
"required": false,
"mutability": "readWrite",
"returned": "default",
"subAttributes": [
{ "name": "value", "type": "reference", "referenceTypes": ["external"], "multiValued": false, "description": "Photo URL.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "display", "type": "string", "multiValued": false, "description": "Display name.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "type", "type": "string", "multiValued": false, "description": "Type: 'photo','thumbnail'.", "required": false, "caseExact": false, "canonicalValues": ["photo","thumbnail"], "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "primary", "type": "boolean", "multiValued": false, "description": "Primary flag; one per list.", "required": false, "mutability": "readWrite", "returned": "default" }
]
},
{
"name": "addresses",
"type": "complex",
"multiValued": true,
"description": "Physical mailing addresses.",
"required": false,
"mutability": "readWrite",
"returned": "default",
"uniqueness": "none",
"subAttributes": [
{ "name": "formatted", "type": "string", "multiValued": false, "description": "Full address, may contain newlines.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "streetAddress", "type": "string", "multiValued": false, "description": "Street address.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "locality", "type": "string", "multiValued": false, "description": "City or locality.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "region", "type": "string", "multiValued": false, "description": "State or region.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "postalCode", "type": "string", "multiValued": false, "description": "Zip or postal code.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "country", "type": "string", "multiValued": false, "description": "Country name.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "type", "type": "string", "multiValued": false, "description": "Type: 'work','home','other'.", "required": false, "caseExact": false, "canonicalValues": ["work","home","other"], "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "primary", "type": "boolean","multiValued": false, "description": "Primary flag; one per list.", "required": false, "mutability": "readWrite", "returned": "default" }
]
},
{
"name": "groups",
"type": "complex",
"multiValued": true,
"description": "Groups to which the user belongs.",
"required": false,
"mutability": "readOnly",
"returned": "default",
"subAttributes": [
{ "name": "value", "type": "string", "multiValued": false, "description": "Group identifier.", "required": false, "caseExact": false, "mutability": "readOnly", "returned": "default", "uniqueness": "none" },
{ "name": "$ref", "type": "reference", "referenceTypes": ["User","Group"], "multiValued": false, "description": "URI of the Group resource.", "required": false, "caseExact": false, "mutability": "readOnly", "returned": "default", "uniqueness": "none" },
{ "name": "display", "type": "string", "multiValued": false, "description": "Display name.", "required": false, "caseExact": false, "mutability": "readOnly", "returned": "default", "uniqueness": "none" }
]
},
{
"name": "entitlements",
"type": "complex",
"multiValued": true,
"description": "Entitlements granted to the user.",
"required": false,
"mutability": "readWrite",
"returned": "default",
"subAttributes": [
{ "name": "value", "type": "string", "multiValued": false, "description": "Entitlement value.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "display", "type": "string", "multiValued": false, "description": "Display name.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "type", "type": "string", "multiValued": false, "description": "Type label.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "primary", "type": "boolean", "multiValued": false, "description": "Primary flag; one per list.", "required": false, "mutability": "readWrite", "returned": "default" }
]
},
{
"name": "roles",
"type": "complex",
"multiValued": true,
"description": "Roles granted to the user.",
"required": false,
"mutability": "readWrite",
"returned": "default",
"subAttributes": [
{ "name": "value", "type": "string", "multiValued": false, "description": "Role value.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "display", "type": "string", "multiValued": false, "description": "Display name.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "type", "type": "string", "multiValued": false, "description": "Type label.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "primary", "type": "boolean", "multiValued": false, "description": "Primary flag; one per list.", "required": false, "mutability": "readWrite", "returned": "default" }
]
},
{
"name": "x509Certificates",
"type": "complex",
"multiValued": true,
"description": "X.509 certificates issued to the user.",
"required": false,
"mutability": "readWrite",
"returned": "default",
"subAttributes": [
{ "name": "value", "type": "binary", "multiValued": false, "description": "Certificate value.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "display", "type": "string", "multiValued": false, "description": "Display name.", "required": false, "caseExact": false, "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "type", "type": "string", "multiValued": false, "description": "Type label.", "required": false, "caseExact": false, "canonicalValues": [], "mutability": "readWrite", "returned": "default", "uniqueness": "none" },
{ "name": "primary", "type": "boolean", "multiValued": false, "description": "Primary flag; one per list.", "required": false, "mutability": "readWrite", "returned": "default" }
]
}
],
"meta": {
"resourceType": "Schema",
"location": "/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:User",
"created": "2025-04-17T15:48:00",
"lastModified": "2025-04-17T15:48:00"
}
}

View file

@ -1,13 +1,11 @@
import logging
from typing import Any, Literal
import copy
from datetime import datetime
from typing import Any
from decouple import config
from fastapi import Depends, HTTPException, Header, Query, Response, Request
from fastapi.responses import JSONResponse
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel, field_serializer
from fastapi.security import OAuth2PasswordRequestForm
from pydantic import BaseModel
from chalicelib.core import users, tenants
from chalicelib.utils.scim_auth import (
@ -26,19 +24,8 @@ logger = logging.getLogger(__name__)
public_app, app, app_apikey = get_routers(prefix="/sso/scim/v2")
"""Authentication endpoints"""
class RefreshRequest(BaseModel):
refresh_token: str
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Login endpoint to generate tokens
@public_app.post("/token")
async def login(
async def post_token(
host: str = Header(..., alias="Host"),
form_data: OAuth2PasswordRequestForm = Depends(),
):
@ -56,17 +43,19 @@ async def login(
return {
"access_token": access_token,
"refresh_token": refresh_token,
"token_type": "bearer",
"token_type": "Bearer",
}
# Refresh token endpoint
class RefreshRequest(BaseModel):
refresh_token: str
@public_app.post("/refresh")
async def refresh_token(r: RefreshRequest):
async def post_refresh(r: RefreshRequest):
payload = verify_refresh_token(r.refresh_token)
new_access_token, _ = create_tokens(tenant_id=payload["tenant_id"])
return {"access_token": new_access_token, "token_type": "bearer"}
return {"access_token": new_access_token, "token_type": "Bearer"}
RESOURCE_TYPE_IDS_TO_RESOURCE_TYPE_DETAILS = {
@ -110,17 +99,33 @@ def _mutability_error_response():
)
def _operation_not_permitted_error_response():
return JSONResponse(
status_code=403,
content={
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"detail": "Operation is not permitted based on the supplied authorization",
"status": "403",
},
)
def _invalid_value_error_response():
return JSONResponse(
status_code=400,
content={
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"detail": "A required value was missing, or the value specified was not compatible with the operation or attribtue type, or resource schema.",
"status": "400",
"scimType": "invalidValue",
},
)
@public_app.get("/ResourceTypes", dependencies=[Depends(auth_required)])
async def get_resource_types(filter_param: str | None = Query(None, alias="filter")):
if filter_param is not None:
return JSONResponse(
status_code=403,
content={
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"detail": "Operation is not permitted based on the supplied authorization",
"status": "403",
},
)
return _operation_not_permitted_error_response()
return JSONResponse(
status_code=200,
content={
@ -151,14 +156,7 @@ SCHEMA_IDS_TO_SCHEMA_DETAILS = {
@public_app.get("/Schemas", dependencies=[Depends(auth_required)])
async def get_schemas(filter_param: str | None = Query(None, alias="filter")):
if filter_param is not None:
return JSONResponse(
status_code=403,
content={
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"detail": "Operation is not permitted based on the supplied authorization",
"status": "403",
},
)
return _operation_not_permitted_error_response()
return JSONResponse(
status_code=200,
content={
@ -189,81 +187,19 @@ async def get_schema(schema_id: str):
async def get_service_provider_config(
r: Request, tenant_id: str | None = Depends(auth_optional)
):
content = copy.deepcopy(SERVICE_PROVIDER_CONFIG)
content["meta"]["location"] = str(r.url)
is_authenticated = tenant_id is not None
if not is_authenticated:
content = {
"schemas": content["schemas"],
"authenticationSchemes": content["authenticationSchemes"],
"meta": content["meta"],
}
return JSONResponse(
status_code=200,
content=content,
)
"""
User endpoints
"""
class UserRequest(BaseModel):
userName: str
class PatchUserRequest(BaseModel):
schemas: list[str]
Operations: list[dict]
class ResourceMetaResponse(BaseModel):
resourceType: (
Literal["ServiceProviderConfig", "ResourceType", "Schema", "User"] | None
) = None
created: datetime | None = None
lastModified: datetime | None = None
location: str | None = None
version: str | None = None
@field_serializer("created", "lastModified")
def serialize_datetime(self, dt: datetime) -> str | None:
if not dt:
return None
return dt.strftime("%Y-%m-%dT%H:%M:%SZ")
class CommonResourceResponse(BaseModel):
id: str
externalId: str | None = None
schemas: list[
Literal[
"urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig",
"urn:ietf:params:scim:schemas:core:2.0:ResourceType",
"urn:ietf:params:scim:schemas:core:2.0:Schema",
"urn:ietf:params:scim:schemas:core:2.0:User",
]
]
meta: ResourceMetaResponse | None = None
class UserResponse(CommonResourceResponse):
schemas: list[Literal["urn:ietf:params:scim:schemas:core:2.0:User"]] = [
"urn:ietf:params:scim:schemas:core:2.0:User"
]
userName: str | None = None
class QueryResourceResponse(BaseModel):
schemas: list[Literal["urn:ietf:params:scim:api:messages:2.0:ListResponse"]] = [
"urn:ietf:params:scim:api:messages:2.0:ListResponse"
]
totalResults: int
# todo(jon): add the other schemas
Resources: list[UserResponse]
startIndex: int
itemsPerPage: int
return JSONResponse(
status_code=200,
content={
"schemas": SERVICE_PROVIDER_CONFIG["schemas"],
"authenticationSchemes": SERVICE_PROVIDER_CONFIG[
"authenticationSchemes"
],
"meta": SERVICE_PROVIDER_CONFIG["meta"],
},
)
return JSONResponse(status_code=200, content=SERVICE_PROVIDER_CONFIG)
MAX_USERS_PER_PAGE = 10
@ -273,7 +209,7 @@ def _convert_db_user_to_scim_user(
db_user: dict[str, Any],
attributes: list[str] | None = None,
excluded_attributes: list[str] | None = None,
) -> UserResponse:
) -> dict[str, Any]:
user_schema = SCHEMA_IDS_TO_SCHEMA_DETAILS[
"urn:ietf:params:scim:schemas:core:2.0:User"
]
@ -289,19 +225,19 @@ def _convert_db_user_to_scim_user(
)
scim_user = {
"id": str(db_user["userId"]),
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"meta": {
"resourceType": "User",
"created": db_user["createdAt"],
"lastModified": db_user[
"createdAt"
], # todo(jon): we currently don't keep track of this in the db
"created": db_user["createdAt"].strftime("%Y-%m-%dT%H:%M:%SZ"),
# todo(jon): we currently don't keep track of this in the db
"lastModified": db_user["createdAt"].strftime("%Y-%m-%dT%H:%M:%SZ"),
"location": f"Users/{db_user['userId']}",
},
"userName": db_user["email"],
}
scim_user = scim_helpers.filter_attributes(scim_user, included_attributes)
scim_user = scim_helpers.exclude_attributes(scim_user, excluded_attributes)
return UserResponse(**scim_user)
return scim_user
@public_app.get("/Users")
@ -318,79 +254,81 @@ async def get_users(
)
# todo(jon): this might not be the most efficient thing to do. could be better to just do a count.
# but this is the fastest thing at the moment just to test that it's working
total_users = users.get_users_paginated(1, tenant_id)
db_users = users.get_users_paginated(start_index, tenant_id, count=items_per_page)
scim_users = [
_convert_db_user_to_scim_user(user, attributes, excluded_attributes)
for user in db_users
total_resources = users.get_users_paginated(1, tenant_id)
db_resources = users.get_users_paginated(
start_index, tenant_id, count=items_per_page
)
scim_resources = [
_convert_db_user_to_scim_user(resource, attributes, excluded_attributes)
for resource in db_resources
]
return JSONResponse(
status_code=200,
content=QueryResourceResponse(
totalResults=len(total_users),
startIndex=start_index,
itemsPerPage=len(scim_users),
Resources=scim_users,
).model_dump(mode="json", exclude_none=True),
content={
"totalResults": len(total_resources),
"startIndex": start_index,
"itemsPerPage": len(scim_resources),
"Resources": scim_resources,
},
)
@public_app.get("/Users/{user_id}")
def get_user(
async def get_user(
user_id: str,
tenant_id=Depends(auth_required),
attributes: list[str] | None = Query(None),
excluded_attributes: list[str] | None = Query(None, alias="excludedAttributes"),
):
db_user = users.get_scim_user_by_id(user_id, tenant_id)
if not db_user:
db_resource = users.get_scim_user_by_id(user_id, tenant_id)
if not db_resource:
return _not_found_error_response(user_id)
scim_user = _convert_db_user_to_scim_user(db_user, attributes, excluded_attributes)
return JSONResponse(
status_code=200, content=scim_user.model_dump(mode="json", exclude_none=True)
scim_resource = _convert_db_user_to_scim_user(
db_resource, attributes, excluded_attributes
)
return JSONResponse(status_code=200, content=scim_resource)
@public_app.post("/Users")
async def create_user(r: UserRequest, tenant_id=Depends(auth_required)):
async def create_user(r: Request, tenant_id=Depends(auth_required)):
payload = await r.json()
if "userName" not in payload:
return _invalid_value_error_response()
# note(jon): this method will return soft deleted users as well
existing_db_user = users.get_existing_scim_user_by_unique_values(r.userName)
if existing_db_user and existing_db_user["deletedAt"] is None:
existing_db_resource = users.get_existing_scim_user_by_unique_values(
payload["userName"]
)
if existing_db_resource and existing_db_resource["deletedAt"] is None:
return _uniqueness_error_response()
if existing_db_user and existing_db_user["deletedAt"] is not None:
db_user = users.restore_scim_user(existing_db_user["userId"], tenant_id)
if existing_db_resource and existing_db_resource["deletedAt"] is not None:
db_resource = users.restore_scim_user(existing_db_resource["userId"], tenant_id)
else:
db_user = users.create_scim_user(
email=r.userName,
db_resource = users.create_scim_user(
email=payload["userName"],
# note(jon): scim schema does not require the `name.formatted` attribute, but we require `name`.
# so, we have to define the value ourselves here
name="",
tenant_id=tenant_id,
)
scim_user = _convert_db_user_to_scim_user(db_user)
response = JSONResponse(
status_code=201, content=scim_user.model_dump(mode="json", exclude_none=True)
)
response.headers["Location"] = scim_user.meta.location
scim_resource = _convert_db_user_to_scim_user(db_resource)
response = JSONResponse(status_code=201, content=scim_resource)
response.headers["Location"] = scim_resource["meta"]["location"]
return response
@public_app.put("/Users/{user_id}")
def update_user(user_id: str, r: UserRequest, tenant_id=Depends(auth_required)):
async def update_user(user_id: str, r: Request, tenant_id=Depends(auth_required)):
db_resource = users.get_scim_user_by_id(user_id, tenant_id)
if not db_resource:
return _not_found_error_response(user_id)
current_scim_resource = _convert_db_user_to_scim_user(db_resource).model_dump(
mode="json", exclude_none=True
)
changes = r.model_dump(mode="json")
current_scim_resource = _convert_db_user_to_scim_user(db_resource)
changes = await r.json()
schema = SCHEMA_IDS_TO_SCHEMA_DETAILS["urn:ietf:params:scim:schemas:core:2.0:User"]
try:
valid_mutable_changes = scim_helpers.filter_mutable_attributes(
schema, changes, current_scim_resource
)
except ValueError:
# todo(jon): will need to add a test for this once we have an immutable field
return _mutability_error_response()
try:
updated_db_resource = users.update_scim_user(
@ -399,19 +337,16 @@ def update_user(user_id: str, r: UserRequest, tenant_id=Depends(auth_required)):
email=valid_mutable_changes["userName"],
)
updated_scim_resource = _convert_db_user_to_scim_user(updated_db_resource)
return JSONResponse(
status_code=200,
content=updated_scim_resource.model_dump(mode="json", exclude_none=True),
)
return JSONResponse(status_code=200, content=updated_scim_resource)
except Exception:
# note(jon): for now, this is the only error that would happen when updating the scim user
return _uniqueness_error_response()
@public_app.delete("/Users/{user_id}")
def delete_user(user_id: str, tenant_id=Depends(auth_required)):
user = users.get_scim_user_by_id(user_id, tenant_id)
if not user:
async def delete_user(user_id: str, tenant_id=Depends(auth_required)):
db_resource = users.get_scim_user_by_id(user_id, tenant_id)
if not db_resource:
return _not_found_error_response(user_id)
users.soft_delete_scim_user_by_id(user_id, tenant_id)
return Response(status_code=204, content="")

View file

@ -1,778 +1,23 @@
# note(jon): please see https://datatracker.ietf.org/doc/html/rfc7643 for details on these constants
from typing import Any
def _attribute_characteristics(
name: str,
description: str,
type: str = "string",
sub_attributes: dict[str, Any] | None = None,
# note(jon): no default for multiValued is defined in the docs and it is marked as optional.
# from our side, we'll default it to False.
multi_valued: bool = False,
required: bool = False,
canonical_values: list[str] | None = None,
case_exact: bool = False,
mutability: str = "readWrite",
returned: str = "default",
uniqueness: str = "none",
reference_types: list[str] | None = None,
):
characteristics = {
"name": name,
"type": type,
"subAttributes": sub_attributes,
"multiValued": multi_valued,
"description": description,
"required": required,
"canonicalValues": canonical_values,
"caseExact": case_exact,
"mutability": mutability,
"returned": returned,
"uniqueness": uniqueness,
"referenceTypes": reference_types,
}
characteristics_without_none = {
key: value for key, value in characteristics.items() if value is not None
}
return characteristics_without_none
def _multi_valued_attributes(
type_canonical_values: list[str],
type_required: bool = False,
type_mutability="readWrite",
):
return [
_attribute_characteristics(
name="type",
description="A label indicating the attribute's function.",
canonical_values=type_canonical_values,
case_exact=True,
required=type_required,
mutability=type_mutability,
),
_attribute_characteristics(
name="primary",
type="boolean",
description="A Boolean value indicating the 'primary' or preferred attribute value for this attribute.",
),
_attribute_characteristics(
name="display",
description="A human-readable name.",
mutability="immutable",
),
_attribute_characteristics(
name="value",
description="The attribute's significant value.",
),
_attribute_characteristics(
name="$ref",
type="reference",
reference_types=["uri"],
description="The reference URI of a target resource.",
),
]
# note(jon): the docs are a little confusing regarding this, but
# in section 3.1 of RFC7643, it is specified that ResourceType and
# ServiceProviderConfig are not included in the common attributes. but
# in other references, they treat them as a resource.
def _common_resource_attributes(id_required: bool = True, id_uniqueness: str = "none"):
return [
_attribute_characteristics(
name="id",
description="A unique identifier for the SCIM resource.",
case_exact=True,
mutability="readOnly",
returned="always",
required=id_required,
uniqueness=id_uniqueness,
),
_attribute_characteristics(
name="externalId",
description="A String that is an identifier for the resource as defined by the provisioning client.",
case_exact=True,
),
_attribute_characteristics(
name="schemas",
type="reference",
reference_types=["uri"],
description="An array of Strings containing URI that are used to indicate the namespaces of the SCIM schemas that define the attributes present in the current JSON structure.",
multi_valued=True,
canonical_values=[
"urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig",
"urn:ietf:params:scim:schemas:core:2.0:ResourceType",
"urn:ietf:params:scim:schemas:core:2.0:Schema",
"urn:ietf:params:scim:schemas:core:2.0:User",
],
case_exact=True,
mutability="readOnly",
required=True,
returned="always",
),
_attribute_characteristics(
name="meta",
type="complex",
description="A complex attribute containing resource metadata.",
required=True,
sub_attributes=[
_attribute_characteristics(
name="resourceType",
description="The name of the resource type of the resource.",
mutability="readOnly",
case_exact=True,
required=True,
),
_attribute_characteristics(
name="created",
type="dateTime",
description="The 'DateTime' that the resource was added to the service provider.",
mutability="readOnly",
required=True,
),
_attribute_characteristics(
name="lastModified",
type="dateTime",
description="The most recent DateTime that the details of this resource were updated at the service provider.",
mutability="readOnly",
required=True,
),
_attribute_characteristics(
name="location",
type="reference",
reference_types=["uri"],
description="The URI of the resource being returned.",
mutability="readOnly",
required=True,
),
# todo(jon): decide if we'll handle versioning. for now, we won't do it.
],
),
]
SERVICE_PROVIDER_CONFIG_SCHEMA = {
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Schema"],
"id": "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig",
"name": "Service Provider Configuration",
"description": "Schema for representing the service provider's configuration.",
"meta": {
"resourceType": "Schema",
"created": "2025-04-16T14:48:00Z",
# note(jon): we might want to think about adding this resource as part of our db
# and then updating these timestamps from an api and such. for now, if we update
# the configuration, we should update the timestamp here.
"lastModified": "2025-04-16T14:48:00Z",
"location": "Schemas/urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig",
},
"attributes": [
*_common_resource_attributes(id_required=False),
_attribute_characteristics(
name="documentationUri",
type="reference",
reference_types=["external"],
description="An HTTP-addressable URL pointing to the service provider's human-consumable help documentation.",
mutability="readOnly",
),
_attribute_characteristics(
name="patch",
type="complex",
description="A complex type that specifies PATCH configuration options.",
required=True,
mutability="readOnly",
sub_attributes=[
_attribute_characteristics(
name="supported",
type="boolean",
description="A Boolean value specifying whether or not the operation is supported.",
required=True,
mutability="readOnly",
),
],
),
_attribute_characteristics(
name="bulk",
type="complex",
description="A complex type that specifies bulk configuration options.",
required=True,
mutability="readOnly",
sub_attributes=[
_attribute_characteristics(
name="supported",
type="boolean",
description="A Boolean value specifying whether or not the operation is supported.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="maxOperations",
type="integer",
description="An integer value specifying the maximum number of operations.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="maxPayloadSize",
type="integer",
description="An integer value specifying the maximum payload size in bytes.",
required=True,
mutability="readOnly",
),
],
),
_attribute_characteristics(
name="filter",
type="complex",
description="A complex type that specifies FILTER options.",
required=True,
mutability="readOnly",
sub_attributes=[
_attribute_characteristics(
name="supported",
type="boolean",
description="A Boolean value specifying whether or not the operation is supported.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="maxResults",
type="integer",
description="The integer value specifying the maximum number of resources returned in a response.",
required=True,
mutability="readOnly",
),
],
),
_attribute_characteristics(
name="changePassword",
type="complex",
description="A complex type that specifies configuration options related to changing a password.",
required=True,
mutability="readOnly",
sub_attributes=[
_attribute_characteristics(
name="supported",
type="boolean",
description="A Boolean value specifying whether or not the operation is supported.",
required=True,
mutability="readOnly",
),
],
),
_attribute_characteristics(
name="sort",
type="complex",
description="A complex type that specifies sort result options.",
required=True,
mutability="readOnly",
sub_attributes=[
_attribute_characteristics(
name="supported",
type="boolean",
description="A Boolean value specifying whether or not the operation is supported.",
required=True,
mutability="readOnly",
),
],
),
_attribute_characteristics(
name="etag",
type="complex",
description="A complex type that specifies ETag configuration options.",
required=True,
mutability="readOnly",
sub_attributes=[
_attribute_characteristics(
name="supported",
type="boolean",
description="A Boolean value specifying whether or not the operation is supported.",
required=True,
mutability="readOnly",
),
],
),
_attribute_characteristics(
name="authenticationSchemes",
type="complex",
multi_valued=True,
description="A complex type that specifies supported authentication scheme properties.",
required=True,
mutability="readOnly",
sub_attributes=[
*_multi_valued_attributes(
type_canonical_values=[
"oauth",
"oauth2",
"oauthbearertoken",
"httpbasic",
"httpdigest",
],
type_required=True,
type_mutability="readOnly",
),
_attribute_characteristics(
name="name",
description="The common authentication scheme name, e.g., HTTP Basic.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="description",
description="A description of the authentication scheme.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="specUri",
type="reference",
reference_types=["external"],
description="An HTTP-addressable URL pointing to the authentication scheme's specification.",
mutability="readOnly",
),
_attribute_characteristics(
name="documentationUri",
type="reference",
reference_types=["external"],
description="An HTTP-addressable URL pointing to the authentication scheme's usage documentation.",
mutability="readOnly",
),
],
),
],
}
RESOURCE_TYPE_SCHEMA = {
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Schema"],
"id": "urn:ietf:params:scim:schemas:core:2.0:ResourceType",
"name": "Resource Type",
"description": "Specifies the schema that describes a SCIM resource type.",
"meta": {
"resourceType": "Schema",
"created": "2025-04-16T14:48:00Z",
# note(jon): we might want to think about adding this resource as part of our db
# and then updating these timestamps from an api and such. for now, if we update
# the configuration, we should update the timestamp here.
"lastModified": "2025-04-16T14:48:00Z",
"location": "Schemas/urn:ietf:params:scim:schemas:core:2.0:ResourceType",
},
"attributes": [
*_common_resource_attributes(id_required=False, id_uniqueness="global"),
_attribute_characteristics(
name="name",
description="The resource type name.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="description",
description="The resource type's human-readable description.",
mutability="readOnly",
),
# todo(jon): figure out what the correct type/reference_type is here
_attribute_characteristics(
name="endpoint",
type="reference",
reference_types=["uri"],
description="The resource type's HTTP-addressable endpoint relative to the Base URL of the service provider.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="schema",
type="reference",
reference_types=["uri"],
description="The resource type's primary/base schema URI.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="schemaExtensions",
type="complex",
multi_valued=True,
description="A list of URIs of the resource type's schema extensions.",
mutability="readOnly",
sub_attributes=[
_attribute_characteristics(
name="schema",
type="reference",
reference_types=["uri"],
description="The URI of an extended schema.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="required",
type="boolean",
description="A Boolean value that specifies whether or not the schema extension is required for the resource type.",
required=True,
mutability="readOnly",
),
],
),
],
}
SCHEMA_SCHEMA = {
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Schema"],
"id": "urn:ietf:params:scim:schemas:core:2.0:Schema",
"name": "Schema",
"description": "Specifies the schema that describes a SCIM schema.",
"meta": {
"resourceType": "Schema",
"created": "2025-04-16T14:48:00Z",
# note(jon): we might want to think about adding this resource as part of our db
# and then updating these timestamps from an api and such. for now, if we update
# the configuration, we should update the timestamp here.
"lastModified": "2025-04-16T14:48:00Z",
"location": "Schemas/urn:ietf:params:scim:schemas:core:2.0:Schema",
},
"attributes": [
*_common_resource_attributes(id_uniqueness="global"),
_attribute_characteristics(
name="name",
description="The schema's humanreadable name.",
mutability="readOnly",
),
_attribute_characteristics(
name="description",
description="The schema's human-readable name.",
mutability="readOnly",
),
_attribute_characteristics(
name="attributes",
type="complex",
multi_valued=True,
description="A complex attribute that defines service provider attributes and their qualities.",
required=True,
mutability="readOnly",
sub_attributes=[
_attribute_characteristics(
name="name",
description="The attribute's name.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="type",
description="The attribute's data type.",
required=True,
canonical_values=[
"string",
"complex",
"boolean",
"decimal",
"integer",
"dateTime",
"reference",
],
case_exact=True,
mutability="readOnly",
),
_attribute_characteristics(
name="multiValued",
type="boolean",
description="A Boolean value indicating an attribute's plurality.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="description",
description="The attribute's human-readable description.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="required",
type="boolean",
description="A Boolean value indicating whether or not the attribute is required.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="canonicalValues",
multi_valued=True,
description="A collection of canonical values.",
mutability="readOnly",
),
_attribute_characteristics(
name="caseExact",
type="boolean",
description="A Boolean that specifies whether or not a string attribute is case sensitive.",
mutability="readOnly",
),
_attribute_characteristics(
name="mutability",
description="A single keyword indicating the circumstances under which the value of the attribute can be (re)defined.",
required=True,
mutability="readOnly",
canonical_values=[
"readOnly",
"readWrite",
"immutable",
"writeOnly",
],
case_exact=True,
),
_attribute_characteristics(
name="returned",
description="A single keyword that indicates when an attribute and associated values are returned in response to a GET request or in response to a PUT, POST, or PATCH request.",
required=True,
mutability="readOnly",
canonical_values=[
"always",
"never",
"default",
"request",
],
case_exact=True,
),
_attribute_characteristics(
name="uniqueness",
description="A single keyword value that specifies how the service provider enforces uniqueness of attribute values.",
required=True,
mutability="readOnly",
canonical_values=[
"none",
"server",
"global",
],
case_exact=True,
),
_attribute_characteristics(
name="referenceTypes",
multi_valued=True,
description="A multi-valued array of JSON strings that indicate the SCIM resource types that may be referenced.",
mutability="readOnly",
canonical_values=[
# todo(jon): add "User" and "Group" once those are done.
"external",
"uri",
],
case_exact=True,
),
_attribute_characteristics(
name="subAttributes",
type="complex",
multi_valued=True,
description="When an attribute is of type 'complex', this defines a set of sub-attributes.",
mutability="readOnly",
sub_attributes=[
_attribute_characteristics(
name="name",
description="The attribute's name.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="type",
description="The attribute's data type.",
required=True,
canonical_values=[
"string",
"complex",
"boolean",
"decimal",
"integer",
"dateTime",
"reference",
],
case_exact=True,
mutability="readOnly",
),
_attribute_characteristics(
name="multiValued",
type="boolean",
description="A Boolean value indicating an attribute's plurality.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="description",
description="The attribute's human-readable description.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="required",
type="boolean",
description="A Boolean value indicating whether or not the attribute is required.",
required=True,
mutability="readOnly",
),
_attribute_characteristics(
name="canonicalValues",
multi_valued=True,
description="A collection of canonical values.",
mutability="readOnly",
),
_attribute_characteristics(
name="caseExact",
type="boolean",
description="A Boolean that specifies whether or not a string attribute is case sensitive.",
mutability="readOnly",
),
_attribute_characteristics(
name="mutability",
description="A single keyword indicating the circumstances under which the value of the attribute can be (re)defined.",
required=True,
mutability="readOnly",
canonical_values=[
"readOnly",
"readWrite",
"immutable",
"writeOnly",
],
case_exact=True,
),
_attribute_characteristics(
name="returned",
description="A single keyword that indicates when an attribute and associated values are returned in response to a GET request or in response to a PUT, POST, or PATCH request.",
required=True,
mutability="readOnly",
canonical_values=[
"always",
"never",
"default",
"request",
],
case_exact=True,
),
_attribute_characteristics(
name="uniqueness",
description="A single keyword value that specifies how the service provider enforces uniqueness of attribute values.",
required=True,
mutability="readOnly",
canonical_values=[
"none",
"server",
"global",
],
case_exact=True,
),
_attribute_characteristics(
name="referenceTypes",
multi_valued=True,
description="A multi-valued array of JSON strings that indicate the SCIM resource types that may be referenced.",
mutability="readOnly",
canonical_values=[
# todo(jon): add "User" and "Group" once those are done.
"external",
"uri",
],
case_exact=True,
),
],
),
],
),
],
}
USER_SCHEMA = {
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Schema"],
"id": "urn:ietf:params:scim:schemas:core:2.0:User",
"name": "User",
"description": "User account.",
"meta": {
"resourceType": "Schema",
"created": "2025-04-16T14:48:00Z",
# note(jon): we might want to think about adding this resource as part of our db
# and then updating these timestamps from an api and such. for now, if we update
# the configuration, we should update the timestamp here.
"lastModified": "2025-04-16T14:48:00Z",
"location": "Schemas/urn:ietf:params:scim:schemas:core:2.0:User",
},
"attributes": [
*_common_resource_attributes(),
_attribute_characteristics(
name="userName",
description="A service provider's unique identifier for the user.",
required=True,
),
],
}
import json
SCHEMAS = sorted(
[
SERVICE_PROVIDER_CONFIG_SCHEMA,
RESOURCE_TYPE_SCHEMA,
SCHEMA_SCHEMA,
USER_SCHEMA,
json.load(open("routers/fixtures/service_provider_config_schema.json", "r")),
json.load(open("routers/fixtures/resource_type_schema.json", "r")),
json.load(open("routers/fixtures/schema_schema.json", "r")),
json.load(open("routers/fixtures/user_schema.json", "r")),
# todo(jon): add this when we have groups
# json.load(open("routers/schemas/group_schema.json", "r")),
],
key=lambda x: x["id"],
)
SERVICE_PROVIDER_CONFIG = {
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
"patch": {
# todo(jon): this needs to be updated to True once we properly implement patching for users and groups
"supported": False,
},
"bulk": {
"supported": False,
"maxOperations": 0,
"maxPayloadSize": 0,
},
"filter": {
# todo(jon): this needs to be updated to True once we properly implement filtering for users and groups
"supported": False,
"maxResults": 0,
},
"changePassword": {
"supported": False,
},
"sort": {
"supported": False,
},
"etag": {
"supported": False,
},
"authenticationSchemes": [
{
"type": "oauthbearertoken",
"name": "OAuth Bearer Token",
"description": "Authentication scheme using the OAuth Bearer Token Standard. The access token should be sent in the 'Authorization' header using the Bearer schema.",
"specUri": "https://tools.ietf.org/html/rfc6750",
# todo(jon): see if we have our own documentation for this
# "documentationUri": "", # optional
"primary": True,
},
],
"meta": {
"resourceType": "ServiceProviderConfig",
"created": "2025-04-15T15:45:00Z",
# note(jon): we might want to think about adding this resource as part of our db
# and then updating these timestamps from an api and such. for now, if we update
# the configuration, we should update the timestamp here.
"lastModified": "2025-04-15T15:45:00Z",
"location": "", # note(jon): this field will be computed in the /ServiceProviderConfig endpoint
},
}
SERVICE_PROVIDER_CONFIG = json.load(
open("routers/fixtures/service_provider_config.json", "r")
)
RESOURCE_TYPES = sorted(
[
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:ResourceType"],
"id": "User",
"name": "User",
"endpoint": "/Users",
"description": "User account",
"schema": "urn:ietf:params:scim:schemas:core:2.0:User",
"meta": {
"resourceType": "ResourceType",
"created": "2025-04-16T08:37:00Z",
# note(jon): we might want to think about adding this resource as part of our db
# and then updating these timestamps from an api and such. for now, if we update
# the configuration, we should update the timestamp here.
"lastModified": "2025-04-16T08:37:00Z",
"location": "ResourceType/User",
},
}
],
json.load(open("routers/fixtures/resource_type.json", "r")),
key=lambda x: x["id"],
)