Map fields and install-editable values
Building a connector, step 7 (SCIM track, 3 of 3). Previous: Describe the provider's deviations.
Owlie keeps each user and group as a flat record, such as { "givenName": "Maya" }, while SCIM nests
attributes (name.givenName) and keeps some in lists (emails). The field lists translate between the
two: users.fields and groups.fields name each field Owlie shows and bind it to a SCIM path. The record schemas of the connector's entities, which
Sync, provisioning, attribute mappings and policies all read, are derived from these lists.
The field lists
users.fields and groups.fields are objects keyed by field name. A field name is letters, digits
and _, starting with a letter or _. Each field binds one SCIM path:
{
"users": {
"fields": {
"userName": {
"path": "userName",
"title": "Username",
"identifier": true,
"required": true,
"baseline": "identity.email"
},
"givenName": { "path": "name.givenName", "title": "Given name", "baseline": "identity.first_name" },
"email": {
"path": "emails[type eq \"work\"].value",
"title": "Work email",
"baseline": "identity.email",
"elementExtras": { "primary": true }
},
"active": { "path": "active", "title": "Active", "type": "boolean", "readOnly": true },
"department": {
"path": "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department",
"title": "Department"
}
}
}
}
| Property | Meaning |
|---|---|
path | Required. The SCIM path the field reads and writes (see Paths). |
type | string (the default), boolean, number or datetime. A datetime is a string in ISO 8601 date-time format. |
title | The label Owlie shows for the field. |
identifier | The field identifies a record, and Owlie may look a record up by it. |
required | The field is required in the record. A required user field needs a baseline unless it is readOnly. |
sensitive | The value is a secret or personal data to mask in logs. An identifier cannot be sensitive. |
readOnly | The server sets the value. Owlie reads it and never writes it. |
writeOnly | Owlie writes the value but never reads it back, such as a password. An identifier cannot be write-only. |
createOnly | The value can be set when the account is created, and not changed afterwards. |
reference | The value points at another user, such as a manager. { "attribute": "externalId" } names the user attribute the value holds. |
elementExtras | Constants added to the list element Owlie creates for a path with a filter, such as { "primary": true }. |
baseline | For a user field: an OXL expression over the identity that gives a new account its value, such as identity.email. |
Every record also has an id field: the read-only, server-assigned id, read from users.idPath or
groups.idPath. It is always the record's first identifier, and you do not declare it. A field named
id is refused.
Paths
A path is one of:
- A dotted attribute path:
userName,name.givenName,meta.lastModified. - An extension attribute, prefixed with its schema URN:
urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department, orurn:ietf:params:scim:schemas:extension:enterprise:2.0:User:manager.value. - A path with a filter that selects one element of a list:
emails[type eq "work"].value. A path has at most one filter, the only operator iseq, and the value is a double-quoted string. - A list path without a filter, for vendors that do not use filtered paths:
emails.value. Owlie reads the element markedprimary, or else the first one.
When Owlie creates a record, a path with a filter becomes a one-element list whose element carries the
filtered value and any elementExtras: emails[type eq "work"].value with { "primary": true } writes
"emails": [{ "type": "work", "value": "...", "primary": true }]. A list path without a filter on a
standard multi-valued attribute (emails, phoneNumbers, ims, photos, addresses,
entitlements, roles, groups, members, x509Certificates) also writes a one-element list.
The default lists
A definition without users.fields gets these user fields, and one without groups.fields gets one
group field, displayName, as an identifier. The starter definition writes both lists out.
| Field | Path | Notes |
|---|---|---|
userName | userName | Identifier, required, baseline identity.email |
externalId | externalId | Identifier |
givenName | name.givenName | Baseline identity.first_name |
familyName | name.familyName | Baseline identity.last_name |
displayName | displayName | Baseline identity.display_name |
email | emails[type eq "work"].value | Baseline identity.email |
active | active | boolean, read-only |
title | title | |
department | enterprise extension department | |
employeeNumber | enterprise extension employeeNumber | |
manager | enterprise extension manager.value | Reference to a user's externalId |
Replace the defaults whole
A field list you write replaces the default list entirely; Owlie does not merge the two. Start from the default rows (the builder shows them), delete the fields the vendor does not store, and add the ones it does. Owlie refuses a list that breaks these rules:
- The user list keeps an identifier field whose path is
userName, and the group list, when groups are enabled, keeps one whose path isdisplayName. SCIM requires both, and Owlie looks records up by them. - Every identifier field's path is filterable (see Filtering).
- A required user field that is not read-only has a
baseline, or new accounts could not be provisioned. - With
groups.membership.memberValueset touserNameorexternalId, the user list has a readable field of that name.
Two field names carry meaning beyond the schema. Sync reads each account's state from the record's
active field (an account is active unless active is false), so keep a field named active bound
to the attribute that carries the account's state. Sync does not use users.activeAttribute: only a
lookup of one user compares the attribute with its disabledValue
(Users). And the account's default matching
rule compares an identity's email with the record's email field, or its userName when there is no
email. Admins can change the matching rule on the integration; keeping these names makes the default
work.
What a new account gets
A user field's baseline is an OXL expression over the identity, evaluated when Owlie provisions a
new account. It reads the identity's attributes, such as identity.email, identity.first_name,
identity.last_name, identity.display_name or identity.id. Owlie's GitHub connector, for example,
provisions externalId from identity.id. Never hard-code a domain or a tenant name: build every
value from the identity. Every identity field an expression can read is in
Expressions.
The baselines become the starting provisioning profile of each integration, next to Owlie's defaults for the other operations: a revoked account is disabled and its managed Grants are removed. Admins can change the profile after installing. Read-only fields never get a baseline.
Let installs edit values
Customers of the same vendor store different attributes: one tenant has a cost centre in an extension,
another does not. Three values of a SCIM definition can be install-editable, so each install adapts the
connector without a new version: users.fields, groups.fields and filtering.attributes. Every
other value, apart from the baseUrl expression, must be written literally in the definition.
To make one install-editable, replace the value with { "$oxl": "config.<name>" } and declare that
config field with the list as its default, which is the seed every install starts from:
{
"config": {
"properties": {
"userFields": {
"type": "object",
"title": "User fields",
"description": "The user attributes this install syncs and provisions, keyed by field name, each bound to a SCIM path.",
"default": {
"userName": {
"path": "userName",
"title": "Username",
"identifier": true,
"required": true,
"baseline": "identity.email"
},
"externalId": { "path": "externalId", "title": "External ID", "identifier": true },
"email": { "path": "emails[type eq \"work\"].value", "title": "Work email", "baseline": "identity.email" },
"active": { "path": "active", "title": "Active", "type": "boolean", "readOnly": true }
}
},
"filterAttributes": {
"type": "array",
"items": { "type": "string" },
"title": "Filterable attributes",
"default": ["userName", "externalId", "displayName"]
}
}
},
"filtering": { "attributes": { "$oxl": "config.filterAttributes" } },
"users": { "fields": { "$oxl": "config.userFields" } }
}
The expression must be exactly config.<name>, and the config field must be declared with the
matching type: object for a field list, array of strings for filtering.attributes.
Identifiers stay as the author seeded them. An install can add, change and remove ordinary fields, but not identifiers. If its list leaves out an identifier field from the seed, Owlie puts it back, in the seed's order. If its list changes an identifier's path, turns it into an ordinary field, or marks a new field as an identifier, Owlie refuses the change. Identifiers decide which record is which, and an install that changed them would lose track of its accounts.
Each install owns its copy. Installing writes a copy of every seed into the integration's configuration, and the integration's admins edit that copy. When a later version of your connector changes a seed, the integration keeps its own value: upgrading never overwrites it. Instead, the integration's page shows a notice, Owlie updated followed by the connector's name, listing each changed value with Your value, Owlie's previous and Owlie's new. An admin chooses Use Owlie's value or Keep mine for each one.
In the Connector Builder
On the Install values tab, What installs may edit has a switch for User fields, Group
fields and Filterable attributes. Turning one on moves the current value into a config property
(userFields, groupFields or filterAttributes) as its default, and points the definition at it. A
field list you never wrote moves the default list. A definition without filtering.attributes has no
value to move, so the new filterAttributes property has no default: give it one in Raw JSON, the
paths the vendor filters on, or the definition stops deriving.
The Users and Groups tabs still show the rows, which are now the seed installs start from.
What the engine does with the fields
- Users are always fully writable. Every SCIM connector can create, update, enable, disable and delete accounts. An update writes the fields it is given, skipping read-only ones.
- Group writes follow
groups.manage. Creating, updating and deleting groups is available only where you allowed it. - Membership writes are one member at a time. Each Grant add or remove changes one user's membership of one group.
- Lookups use identifiers only. When Owlie has a record's
id, it reads the record directly. When it has only another identifier, it filters on that identifier's path; a filter on any other field could match someone else. - Values are escaped. Every value in a filter is sent as a quoted, escaped string, so a value containing quotes cannot change the filter. Every id placed in a URL path is URL-encoded.
- The connection test lists one user, and one group when groups are enabled.
Next: Test your connector