LDAP definition
Every field of an LDAP connector's definition.json, what Owlie adds to each install's configuration,
and what it derives from the definition. The machine-readable form is
ldap.schema.json, a JSON Schema
(draft-07). The schema checks shapes only. Owlie also applies the rules on this page when it derives
the declaration, such as a lookup key's form, a single-bit disabledFlag, password only with
write, and valid attribute names, so a definition the schema accepts can still be refused.
In the tables, Editable marks a field that accepts an install-editable value: instead of a literal,
the bare expression { "$oxl": "config.<name>" } (an object whose only key is $oxl), where <name>
is a field declared under config.properties with the same JSON type. Each install then reads its own
copy of that field, seeded from the field's default. Any other expression, and an expression in a
field not marked Editable, is refused.
Top level
| Field | Type | Required | Default | Editable | What it does |
|---|---|---|---|---|---|
engine | integer | Yes | No | Always 1. | |
description | string | No | No | The catalog description. | |
icon | string | No | No | The catalog icon: @ plus a domain, such as @openldap.org. | |
config | object | No | No | Holds properties. | |
config.properties | object | No | No | The install fields the definition reads, keyed by name. Each is a JSON Schema object: type, title, description, default (the seed), and for lists items, for objects additionalProperties. A field without a default, or with any seed other than "" or null, is required at install. The names url, baseDn and tls are refused. | |
pageSize | integer | No | 500 | Yes | Entries per search page, 1 to 1000. |
entities | object | Yes | No | The entities: user, optional group, and lookup entities under any other key. | |
incremental | object | No | No | Incremental sync of the account entity. | |
custom_ops | object | No | No | Custom operations, keyed by operation key: engine-run LDAP writes or PowerShell scripts. | |
handlers | object | No | No | PowerShell scripts that replace engine handlers, keyed by handler key. |
No other top-level field is accepted.
Entity fields
entities.user is required and is the account. entities.group is optional. Any other key under
entities is a lookup entity: a lowercase letter followed by lowercase letters, digits and _, not
connection, grant, grants or notification, read and never written. All three share these
fields:
| Field | Type | Required | Default | Editable | What it does |
|---|---|---|---|---|---|
name | string | No | Account, Group, or the lookup key | No | What Owlie calls the records. |
bases | list of strings | No | The install's base DN | Yes | Whole DNs to search under, in order. An install's empty list stops the connector for user and group, and means the whole base DN for a lookup entity. Only bases may reference an install field that has no seed. |
scope | string | No | sub | No | base, one or sub. |
objectClasses | list of strings | No | [] | Yes | Object class names or OIDs. Each becomes an (objectClass=...) condition; a new entry gets exactly these classes. Required for a create. |
filter | string | No | Yes | A parenthesized LDAP filter, ANDed with the object classes. With neither, the entity matches (objectClass=*). | |
identifier | object | Yes | No | The entry's immutable unique id. | |
identifier.attribute | string | Yes | No | An attribute name, such as entryUUID or objectGUID. | |
identifier.type | string | No | string | No | string, binary (read as base64) or guid (read as the GUID's standard text form). |
rdnAttribute | string | Yes | No | The attribute that names an entry in its DN, such as uid or cn. Always read. | |
attributes | object | No | {} | Yes | The attributes to read, keyed by attribute name (options such as cn;lang-en allowed). dn and account_disabled are refused. |
write | object | No | No | user and group only. Enables create and update. |
Attribute fields
Each entry of attributes:
| Field | Type | Required | Default | Allowed values | What it does |
|---|---|---|---|---|---|
type | string | No | string | string, number, boolean, binary, guid, sid | How the value is read. boolean reads TRUE; binary reads as base64; sid reads as S-1-5-.... |
multi | boolean | No | false | Read every value as a list. | |
title | string | No | The label Owlie shows. | ||
sensitive | boolean | No | false | Mark the value as secret or personal data. | |
readOnly | boolean | No | false | Never written by provisioning. | |
baseline | string or object | No | The value a new account gets: an OXL expression over the identity, such as "identity.email", or { "usernameGenerator": { "steps": [...], "maxAttempts": 20 } }. An expression may also read generated, the generator's value. Refused on a readOnly attribute. Used only on user attributes. | ||
usernameGenerator | object | Yes, in the object form | Inside baseline: steps, a non-empty list whose entries each have a non-empty blocks list, and maxAttempts, a positive integer. At most one attribute may have one. See Username generators. |
Username generators
A usernameGenerator produces candidate values for one account attribute. Owlie uses the first
candidate when it creates an account, and the next one each time the directory answers that a value is
taken, up to maxAttempts candidates in all. A retry changes only that attribute and any attribute
whose baseline expression reads generated. Only one attribute may have a generator.
| Field | What it is |
|---|---|
steps | The candidate patterns, in order. Each step has blocks, whose values are joined. A step without a counter yields one candidate. The last step, when it has a counter, repeats with the counter going up until maxAttempts is reached. |
maxAttempts | The most candidates to try, a positive integer. |
A block's type is one of:
type | Fields | Value |
|---|---|---|
field | field, modifiers | The identity's value for field, changed by each modifier in order. |
literal | value | The text in value. |
increment | startFrom (default 1), padding (default 1) | A counter: startFrom plus the attempt within the step, padded with leading zeros to padding digits. |
alphabetIncrement | A letter: a on the step's first attempt, then b, and back to a after z. |
field is one of first_name, last_name, middle_name, nickname, employee_id, email,
display_name, department, title and username. Each reads the identity's field of that name; a
field the identity does not have reads as empty. A step whose field blocks all read empty yields no
candidate, whatever literals or counter it has.
A modifier's type is one of:
type | Fields | What it does |
|---|---|---|
firstN | n (default 1) | Keeps the first n characters. |
lastN | n (default 1) | Keeps the last n characters. |
lowercase, uppercase | Changes the case. | |
capitalize | Upper-cases the first character and lower-cases the rest. | |
ascii | Removes accents, then every character that is not a letter or digit: Núñez-O'Brien becomes NunezOBrien. | |
splitAt | delimiter, keep (before, the default, or after), includeDelimiter (default false) | Keeps the text before or after the first delimiter. A value without the delimiter is unchanged. |
Put ascii before firstN or lastN, so accented letters are folded before characters are counted.
Active Directory's sAMAccountName holds at most 20 characters, so shorten long names with firstN.
write
The fields of an entity's write object:
| Field | Type | Required | Default | Editable | What it does |
|---|---|---|---|---|---|
base | string | No | The entity's first search base | Yes | The DN new entries are created under. |
dn | string | No | Yes | A DN template with {attribute} placeholders, each replaced by the new entry's escaped value. Empty means no template. | |
delete | boolean | No | false | No | Also enables delete. |
Account fields
entities.user also takes lifecycle, password and matching.
lifecycle
One of two forms. The form is fixed. With either, every account record carries a read-only boolean
account_disabled, true when the lifecycle attribute marks the account disabled, and Sync reads it
(see What Owlie derives). The lifecycle attribute is read on every account read,
whether or not attributes lists it.
A value lifecycle:
| Field | Type | Required | Editable | What it does |
|---|---|---|---|---|
attribute | string | Yes | Yes | The attribute that records the state. |
disabledValue | string | Yes | Yes | Written on disable; an account holding it reads as disabled. |
enabledValue | string | No | Yes | Written on enable and create. Absent or empty: enable removes the attribute. Must differ from disabledValue. |
A flag lifecycle:
| Field | Type | Required | Editable | What it does |
|---|---|---|---|---|
attribute | string | Yes | No | An integer attribute, such as userAccountControl. |
disabledFlag | integer | Yes | No | A single bit, such as 2: set on disable, cleared on enable. |
newAccountValue | integer | Yes | No | 0 or more. A new account is created with this value plus the disabled bit, given its password, then enabled unless the create input has enabled: false. |
password
Requires write on the account.
| Field | Type | Required | Default | Allowed values | What it does |
|---|---|---|---|---|---|
attribute | string | Yes | The attribute the password is written to, on create and update, only when the request carries a password. Never read. | ||
encoding | string | No | text | text, unicodePwd | unicodePwd writes Active Directory's format: the quoted password in UTF-16LE. |
on_change | object | No | Attribute names to strings | Attributes every password change writes in the same directory modify, right after the password, such as "pwdLastSet": "-1" to clear Active Directory's "must change password at next logon". Each must be one of the account's writable attributes, spelled exactly, and not its RDN attribute. |
matching
Adds a rule that matches accounts to identities by employee ID, ahead of the email rule.
| Field | Type | Required | Editable | What it does |
|---|---|---|---|---|
employeeId | string | No | Yes | The attribute holding each account's employee ID. Empty, or an install-editable value left empty, adds no employee-ID rule. |
employeeId must be a plain attribute name (letters, digits and _, not starting with a digit), and
must be an attribute the account reads: one listed in attributes, or the identifier. Otherwise Owlie
refuses it, naming the install field it came from when the value is install-editable. When an
install's own attribute list drops it, the attribute is still read on every account read, as the
lifecycle attribute is. An account that matches a terminated identity by this rule relinks to it and
reactivates it, so a rehire does not become a new identity. The email rule on mail is always added
after it.
Group fields
entities.group also takes membership, which is required on a group. The fields of membership:
| Field | Type | Required | Default | Editable | What it does |
|---|---|---|---|---|---|
attribute | string | Yes | No | The attribute that holds membership, such as member, uniqueMember or memberOf. | |
direction | string | No | member-on-group | No | member-on-group (the group lists member DNs) or memberOf-on-account (the account lists group DNs). |
writable | boolean | No | false | No | Enables adding and removing members. Only with member-on-group. |
nested | boolean | No | false | Yes | Groups can be members of groups: Sync reports each group's direct members, accounts and groups, found by memberOf. Owlie resolves membership through the nested groups. Needs a directory that keeps memberOf on accounts and groups. |
incremental
| Field | Type | Required | Default | Allowed values | What it does |
|---|---|---|---|---|---|
attribute | string | No | modifyTimestamp | The attribute that changes when an entry changes. | |
kind | string | No | timestamp | timestamp, counter | A GeneralizedTime, or an integer that only grows. |
overlapSeconds | integer | No | 300 | 0 to 86400 | For timestamp: how far before the last marker the next run reads. |
source | string | No | invocationId | For counter only: ties the marker to the domain controller that issued it; a marker from another controller restarts from zero. |
None of these fields is install-editable.
custom_ops
Keyed by operation key: a letter, then letters, digits, _ and -. Each entry is either an engine-run
LDAP write or a script binding. The operation's handler is <account entity>.op.<key>: custom operations run on the account
entity only.
An engine-run LDAP write:
| Field | Type | Required | What it does |
|---|---|---|---|
ldap | object | Yes | Exactly one of replace and move, below. |
replace | object | One of the two | Attribute name to value, set on the entry the operation runs on. null removes the attribute. |
move | object | One of the two | Moves the entry, keeping its RDN, under a container DN. |
param | string | Yes, with move | The parameter that carries the container DN. Without a params_schema, one requiring it is derived. |
entity | string | No | user (the default) or group. |
title, description | string | No | What admins and workflows see. |
params_schema | object | No | JSON Schema the parameters are checked against before the write. |
A script binding:
| Field | Type | Required | What it does |
|---|---|---|---|
script | string | Yes | The name of scripts/<name>.ps1: a letter or digit, then letters, digits, _ and -, up to 64 characters. The connector must contain it. |
entity | string | No | The entity the operation runs on: user (the default) or group. |
title, description | string | No | What admins and workflows see. |
params_schema | object | No | JSON Schema the parameters are checked against before the script runs. |
config | list of strings | No | Install field names passed to the script as $Params.config. |
secrets | object | No | Parameter name to gateway secret name (letters, digits, _, . and -). The gateway resolves the secret itself. identifiers and config cannot be bound, and params_schema cannot declare a property with either name: Owlie fills both itself. |
timeoutMs | integer | No | 1 to 900000. How long the script may run. The default is 60000. The gateway caps it at its powershellTimeoutMs for the connector and its customConnectorLimits.timeoutMs, both 60000 unless the operator raises them. |
handlers
Keyed by a handler key the definition enables, such as user.disable. Each entry replaces the engine's
handler with a script and takes script, config, secrets and timeoutMs, as for a script binding
above. A key the definition does not enable, or a custom operation key, is refused, and so is a
read-only key (connection.discover): there is no PowerShell broker in that context, so a script
bound there would fail every time it ran.
What the script reads as $Params depends on the key. fetchOne, create, fetchAll,
fetchAllSince, delete, enable, disable, a Grant kind's add and remove, and
connection.testConnection/connection.validateConfig take one value: the script reads it as
$Params.payload, beside $Params.identifiers for the entry the operation runs on: null when the
operation has none, such as create, fetchAll, fetchAllSince, a Grant write, or either connection
operation, which has no entry at all. update takes the entry and the change, so the script reads
$Params.identifiers and $Params.input. fetchRelationships takes the external id of the entry
whose relationships are read (the group, for a membership grant) and the page request, so the script
reads $Params.externalId and $Params.options, with no $Params.identifiers.
A script replacing fetchOne, fetchAll, fetchAllSince or fetchRelationships stands in for a
read, so its output is checked before Owlie uses it: a page must be { records, nextCursor, hasMore },
with nextCursor set whenever hasMore is true, and fetchOne must be { record } (the entry, or
null when there is none). A script that returns something else fails the operation with a message
that says so. With a lifecycle, a script replacing user.fetchOne, user.fetchAll or
user.fetchAllSince must give each account record account_disabled (true for a disabled account):
Sync reads an account's state from it, and a record without it reads as active.
A handlers entry's secrets cannot name a $Params field the key itself fills: config, always,
plus payload and identifiers for a one-argument key, identifiers and input for update, or
externalId and options for fetchRelationships.
What the engine adds to each install
Every LDAP install has these fields besides the ones in config.properties:
| Field | Type | Required | What it is |
|---|---|---|---|
url | string | Yes | The directory URL, ldaps:// (TLS) or ldap:// (upgraded with StartTLS). |
baseDn | string | Yes | The install's base DN. |
tls | object | No | caCert, a CA certificate in PEM, and rejectUnauthorized, whether to verify the directory's certificate (default true). A caCert, or rejectUnauthorized: false, needs the gateway operator's allowTlsOverrides. |
The one authentication option, bind ("Bind DN and password"), has bindDn and bindPassword, both
required. bindPassword is a gateway secret: an install gives the secret's name, which the gateway
resolves on your network. A draft test in the Connector Builder takes the password itself instead.
What Owlie derives
From the definition, Owlie derives the connector's declaration:
| Part | Derived value |
|---|---|
kind | ldap |
provides | ["sync"], plus provision when the definition has any write: write on an entity, a lifecycle, a writable membership or an engine-run LDAP custom operation |
protocols | ["ldap"], plus powershell when a script is bound |
execution_targets | ["gateway"] |
| Entities | user, group and each lookup entity. Each record has dn and the identifier, and identifier_paths is ["dn", "<identifier>"]; the identifier is marked as the provider-stable id. Attributes become schema properties, read-only unless the entity has write and the attribute is not readOnly. A declared password adds a write-only password property. |
| Account | sync_support.supports_incremental follows incremental, and supports_deleted is false. The sync profile matches the account's mail to the identity's email; with matching.employeeId, it first matches that attribute to the identity's employee ID. With a lifecycle, the schema has a read-only boolean account_disabled and is_active is account_disabled != true, so Sync and user.fetchOne read the same state; without one, is_active is true. With writes, a provisioning profile whose new-account baseline is each attribute's baseline. With write on the account, revoke disables the account, or deletes it when it has write.delete and no lifecycle. With other writes but no write on the account, create_semantics is external: the account is Grants-only, so provisioning never creates, updates, enables, disables or deletes it, even with a lifecycle, and a revoke removes the memberships Owlie manages and leaves the account alone. |
| Grant kinds | With a group: member_of, source user, target group, discovered through group per entity, a membership, writable when membership.writable is true. When membership.nested is true or install-editable, principal_types is ["user", "group"] and via_membership is transitive, whatever an install sets. |
| Custom operations | One per custom_ops entry, with its title, description and params_schema. |
The handlers the engine runs:
| Handlers | When |
|---|---|
connection.validateConfig, connection.testConnection, connection.discover | Always |
user.fetchOne, user.fetchAll | Always |
user.fetchAllSince | With incremental |
<entity>.create, <entity>.update | With write on user or group |
<entity>.delete | With write.delete |
user.enable, user.disable | With lifecycle |
group.fetchOne, group.fetchAll, group.fetchRelationships | With a group |
grant.member_of.add, grant.member_of.remove | With membership.writable |
<key>.fetchOne, <key>.fetchAll | For each lookup entity |
<entity>.op.<key> | For each custom operation |
connection.testConnection reads the first account search base. connection.discover reads the root
DSE and the schema, and never writes. What the membership writes treat as already present or already
absent is in the Grant write contract; the handler
context is in the Handler API.