CX · Reference guidescim
PlaygroundDownloadsAboutv0.18.0-pre.1

scim

SCIM 2.0 provisioning semantics — the PROVISIONING half of enterprise SSO, counterpart to cx-stdlib/oidc's login half. It is a pure semantics module, not a mounted service: a feature routes /Users and /Groups itself, with its own bearer check and its own tenancy, and calls these verbs for the part that is identical everywhere and hard everywhere. apply-patch is RFC 7644 section 3.5.2 in full — add appends to a multi-valued attribute and replaces a single-valued one, replace with no path merges, a path may carry a value filter, attribute names and op verbs fold case, removing what is absent is a no-op. parse-filter covers the whole section 3.4.2.2 grammar and returns a walkable element tree rather than a predicate, so a deployment can push a filter into its own store. validate returns the validated resource rather than a boolean beside it. Resources are ordinary CX maps and multi-valued attributes are arrays — exactly what cx-stdlib/json parse yields — so nothing is translated between the wire and these verbs. project narrows a response per RFC 7644 section 3.9, and each attribute's schema returned characteristic outranks the request in both directions — always survives any exclusion, and never leaves no response whatever was asked for, which is why the core password attribute cannot ride a patched echo back to the IdP; list-response projects every member of a page for the same reason. Schemas are VALUES: supplying a custom extension schema in opts is the entire cost of supporting it, projection included. Every verb is pure, so a whole provisioning exchange replays in a fixture.

scim:parse-filter

[$scim:parse-filter] -> element — Parse an RFC 7644 section 3.4.2.2 filter into a walkable element tree; raises CXER5601 on a malformed filter.

      [?lib 'cx-stdlib/scim']
[?let [= $f [$scim:parse-filter 'userName eq "bjensen"']]
  [$concat $f/compare/@op "|" $f/compare/path/@name "|" $f/compare/value]]
    
      'eq|userName|bjensen'
    

scim:match-filter

[$scim:match-filter] -> bool — Answer whether a resource satisfies a parsed filter, honouring each attribute's schema caseExact.

      [?lib 'cx-stdlib/scim']
[?lib 'cx-stdlib/json']
[$scim:match-filter [$json:parse '{"userName":"BJensen"}']
                    [$scim:parse-filter 'userName eq "bjensen"'] {}]
    
      true
    

scim:parse-path

[$scim:parse-path] -> element — Parse an RFC 7644 section 3.5.2 PATCH path — bare, dotted, or value-filtered — into the same element vocabulary filters use.

      [?lib 'cx-stdlib/scim']
[$concat [$scim:parse-path 'userName']/path/@name "|"
         [$scim:parse-path 'name.familyName']/path/@name "|"
         [$scim:parse-path 'name.familyName']/path/@sub]
    
      'userName|name|familyName'
    

scim:apply-patch

[$scim:apply-patch] -> map — Apply a whole RFC 7644 PatchOp request to a resource, purely — add appends to a multi-valued attribute and replaces a single-valued one.

      [?lib 'cx-stdlib/scim']
[?lib 'cx-stdlib/json']
[?let [= $r [$json:parse '{"userName":"bj","emails":[{"value":"a@x.com","type":"work"}]}']]
      [= $p [$json:parse '{"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],"Operations":[{"op":"add","path":"emails","value":[{"value":"b@x.com","type":"home"}]}]}']]
  [$json:emit [$scim:apply-patch $r $p]]]
    
      '{"emails":[{"type":"work","value":"a@x.com"},{"type":"home","value":"b@x.com"}],"userName":"bj"}'
    

scim:validate

[$scim:validate] -> map — Check a resource against the schemas it declares and return the VALIDATED resource — never a boolean beside an unchecked one.

      [?lib 'cx-stdlib/scim']
[?lib 'cx-stdlib/json']
[$json:emit [$scim:validate
  [$json:parse '{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User"],"userName":"bj","active":true}'] {}]]
    
      '{"active":true,"schemas":["urn:ietf:params:scim:schemas:core:2.0:User"],"userName":"bj"}'
    

scim:schemas

[$scim:schemas] -> [sequence map] — The RFC 7643 schema resources this module ships — core User, core Group and the enterprise-user extension — as the values everything schema-driven reads.

      [?lib 'cx-stdlib/scim']
[?lib 'cx-stdlib/map']
[?lib 'cx-stdlib/strings']
[$strings:join [?for [in $s [$scim:schemas {}]] [yield [$map:get $s 'id']]] " "]
    
      'urn:ietf:params:scim:schemas:core:2.0:User urn:ietf:params:scim:schemas:core:2.0:Group urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
    

scim:resource-types

[$scim:resource-types] -> [sequence map] — The /ResourceTypes discovery documents for User and Group, naming each endpoint and its schema extensions.

      [?lib 'cx-stdlib/scim']
[?lib 'cx-stdlib/map']
[?lib 'cx-stdlib/strings']
[$strings:join [?for [in $t [$scim:resource-types {}]]
  [yield [$concat [$map:get $t 'id'] "=" [$map:get $t 'endpoint']]]] " "]
    
      'User=/Users Group=/Groups'
    

scim:service-provider-config

[$scim:service-provider-config] -> map — The /ServiceProviderConfig document, telling the truth about this module: patch and filter supported, bulk and sort not.

      [?lib 'cx-stdlib/scim']
[?lib 'cx-stdlib/map']
[?let [= $c [$scim:service-provider-config {}]]
  ([$map:get [$map:get $c 'patch'] 'supported'],
   [$map:get [$map:get $c 'filter'] 'supported'],
   [$map:get [$map:get $c 'bulk'] 'supported'],
   [$map:get [$map:get $c 'sort'] 'supported'])]
    
      (true, true, false, false)
    

scim:project

[$scim:project] -> map — Narrow a response per RFC 7644 section 3.9 — attributes: or excluded-attributes: in opts — with each attribute's schema returned outranking the request: always survives any exclusion and never leaves no response, whatever was asked for.

      [?lib 'cx-stdlib/scim']
[?lib 'cx-stdlib/json']
[?let [= $p [$json:parse '{"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],"Operations":[{"op":"replace","path":"password","value":"t1meMa$heen"}]}']]
      [= $r [$json:parse '{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User"],"id":"2819c223","userName":"bjensen"}']]
  [$json:emit [$scim:project [$scim:apply-patch $r $p] {}]]]
    
      '{"id":"2819c223","schemas":["urn:ietf:params:scim:schemas:core:2.0:User"],"userName":"bjensen"}'
    

scim:list-response

[$scim:list-response] -> map — Wrap a page of resources in the SCIM ListResponse envelope, reporting the store's own total rather than the page length.

      [?lib 'cx-stdlib/scim']
[?lib 'cx-stdlib/json']
[$json:emit [$scim:list-response ([$json:parse '{"id":"1"}'], [$json:parse '{"id":"2"}']) {}]]
    
      '{"Resources":[{"id":"1"},{"id":"2"}],"itemsPerPage":2,"schemas":["urn:ietf:params:scim:api:messages:2.0:ListResponse"],"startIndex":1,"totalResults":2}'
    

scim:version

[$scim:version] -> string — The resource's strong entity-tag per RFC 7232 — the canonical content's hash, quoted — so equal content is one version whatever order the keys arrived in and a changed attribute is a different one.

      [?lib 'cx-stdlib/scim']
[?lib 'cx-stdlib/json']
[?let [= $a [$json:parse '{"userName":"bj","active":true}']]
      [= $b [$json:parse '{"active":true,"userName":"bj"}']]
      [= $c [$json:parse '{"userName":"bj","active":false}']]
  ([= [$scim:version $a] [$scim:version $b]], [= [$scim:version $a] [$scim:version $c]])]
    
      (true, false)
    

scim:stamp

[$scim:stamp] -> map — Complete a resource's meta — resourceType, location, created/lastModified FROM THE STORE RECORD (this module has no clock), and version as the strong ETag.

      [?lib 'cx-stdlib/scim']
[?lib 'cx-stdlib/json']
[?lib 'cx-stdlib/map']
[?let [= $r [$json:parse '{"id":"2819c223","userName":"bjensen"}']]
      [= $s [$scim:stamp $r {'resource-type': 'User', 'base-url': 'https://x.example/scim/v2',
                             'created': '2026-01-01T00:00:00Z', 'last-modified': '2026-01-02T00:00:00Z'}]]
      [= $m [$map:get $s 'meta']]
  ([= [$map:get $m 'resourceType'] 'User'],
   [= [$map:get $m 'location'] 'https://x.example/scim/v2/Users/2819c223'],
   [= [$map:get $m 'created'] '2026-01-01T00:00:00Z'],
   [= [$map:get $m 'lastModified'] '2026-01-02T00:00:00Z'],
   [= [$map:get $m 'version'] [$scim:version $s]])]
    
      (true, true, true, true, true)
    

scim:precondition

[$scim:precondition] -> map — Answer the RFC 7232 preconditions over a resource: If-Match refuses a mismatch with CXER5608, If-None-Match answers unchanged, and a weak validator is declined — the 412/304 status is the mount's.

      [?lib 'cx-stdlib/scim']
[?lib 'cx-stdlib/json']
[?lib 'cx-stdlib/map']
[?let [= $r [$json:parse '{"id":"1","userName":"bj"}']]
      [= $v [$scim:version $r]]
      [= $p [$scim:precondition $r {'If-Match': $v}]]
  ([$map:get $p 'unchanged'], [= [$map:get $p 'version'] $v])]
    
      (false, true)
    

scim:delete

[$scim:delete] -> map — The module's answer to DELETE: a refusal naming active: false, because hard delete is not offered in v0.18 and mapping DELETE onto the soft one would make a later GET of a supposedly deleted resource a lie.

      [?lib 'cx-stdlib/scim']
[?lib 'cx-stdlib/json']
[?let [= $e [$scim:delete {}]]
  [$json:emit [$scim:error $e@code $e@message]]]
    
      '{"detail":"scim/delete: DELETE is not offered - deprovision by setting active: false (RFC 7644; RULED: 1406-a)","schemas":["urn:ietf:params:scim:api:messages:2.0:Error"],"status":"501"}'
    

scim:error

[$scim:error] -> map — Project one of this module's errs into the RFC 7644 error object, with the scimType and the STRING status the RFC assigns.

      [?lib 'cx-stdlib/scim']
[?lib 'cx-stdlib/json']
[$json:emit [$scim:error 'cx-err:CXER5602' 'no such attribute']]
    
      '{"detail":"no such attribute","schemas":["urn:ietf:params:scim:api:messages:2.0:Error"],"scimType":"invalidPath","status":"400"}'