-
Notifications
You must be signed in to change notification settings - Fork 2
docs: correct the update_profile action contract #171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,19 +54,27 @@ Connector actions are custom capabilities that extend C1 automations with app-sp | |
|
|
||
| | Action name | Additional fields | Description | | ||
| | ----------- | ----------------- | ----------- | | ||
| | `update_profile` | `user_id` (resource ID, required), `first_name` (string), `last_name` (string), `display_name` (string), `email` (string), `custom_attributes` (string map) | Set core profile fields (first name, last name, display name, email) and/or arbitrary custom LDAP attributes on an existing user. This action is scoped to the `user` resource type, which is what makes it discoverable and usable from C1's attribute-push-rule feature. It also backs C1's [push profile](/product/admin/account-provisioning) flow for LDAP users. | | ||
| | `update_profile` | `user_id` (account resource ID, required), `first_name` (string), `last_name` (string), `display_name` (string), `email` (string), `custom_attributes` (string map) | Set core profile fields (first name, last name, display name, email) and/or arbitrary custom LDAP attributes on an existing user. This action is scoped to the `user` resource type, which is what makes it discoverable and usable from C1's attribute-push-rule feature. It also backs C1's [attribute push rule](/product/admin/push-rules) flow for LDAP users. | | ||
|
|
||
| The `update_profile` action returns `success` (bool), `updated_user` (the user resource after the update, best-effort re-fetched; absent if the read-back failed, though the write itself still succeeded), `applied` (the number of attributes changed), and `skipped` (named fields or `custom_attributes` entries that were not written). | ||
| The `update_profile` action returns `success` (bool), `updated_user` (the modified user resource, re-fetched after the write; absent if the read-back failed, though the write itself still succeeded), `applied` (the number of attributes changed), and `skipped` (named fields or `custom_attributes` entries that were not written). | ||
|
|
||
| `updated_user` carries the resource identity, `displayName`, and the user trait — not the entry's full attribute set. A value the action just wrote appears there only when it also feeds one of those: `display_name` through `displayName`, `email` through the trait's email list, and a `custom_attributes` key only when it maps to a trait field (`mail`, `displayName`, `sAMAccountName`, `userPrincipalName`, a non-RDN `uid` or `cn`, `lastLogonTimestamp`, `authTimestamp`). `first_name`, `last_name`, and every other `custom_attributes` key reach the directory but do not appear in `updated_user`. Use `applied` to confirm those. | ||
|
|
||
| Two requirements come from the C1 side rather than from LDAP: | ||
|
|
||
| - **`user_id` takes the C1 account identifier, not the LDAP DN.** C1 resolves the account to the connector's resource before dispatching the action. A DN fails inside C1 with `resource <dn> with type user was not found` and never reaches the connector, so it produces no connector log line and leaves the directory untouched. | ||
| - **An attribute push rule must map from a single-valued attribute.** The connector's user profile carries only attributes that hold exactly one value on the entry, so a multi-valued source resolves to nothing: the rule saves and enables, and each push reports zero attributes applied. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: "holds exactly one value" is necessary but not sufficient.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: The README's new third C1-side note — that |
||
|
|
||
| <Note> | ||
| `update_profile` is intended for generic LDAP directories (Active Directory and FreeIPA have their own connectors). It applies the following rules: | ||
|
|
||
| - **Named fields:** `first_name` → `givenName`, `last_name` → `sn`, `display_name` → `displayName`, `email` → `mail`. A named field is applied only when present and non-empty; a present-but-empty named field cannot clear the attribute and is instead reported in `skipped`. | ||
| - **`inetOrgPerson` requirement:** only `last_name` (`sn`) is universal — it's a MUST attribute of the base `person` object class. `first_name` (`givenName`), `display_name` (`displayName`), and `email` (`mail`) are only defined by RFC 2798's `inetOrgPerson` object class, so writing one of them to an entry that doesn't carry `inetOrgPerson` fails loudly with LDAP result code 65 ("Object Class Violation") — an atomic, clearly-signaled failure with no partial write, not a silent no-op. | ||
| - **Custom attributes:** `custom_attributes` maps arbitrary raw LDAP attribute names to values; an empty value clears the attribute (unlike the named fields above, where empty just means "not supplied"). Keys are used **verbatim** as attribute names — the named-field mapping above applies only to the named arguments, never to `custom_attributes`. `{"user_id": "x"}` therefore writes an attribute literally named `user_id` (failing with LDAP result code 17, "Undefined Attribute Type", if the directory has no such attribute); it does not write `uid`. The same goes for baton profile field names such as `login` and `path`: they are attempted as literal attribute names rather than skipped. | ||
| - **`inetOrgPerson` requirement:** only `last_name` (`sn`) is universal — it's a MUST attribute of the base `person` object class. `first_name` (`givenName`, defined in RFC 4519), `display_name` (`displayName`, RFC 2798), and `email` (`mail`, RFC 4524) are permitted on an entry only by RFC 2798's `inetOrgPerson` object class, so writing one of them to an entry that doesn't carry `inetOrgPerson` fails loudly with LDAP result code 65 ("Object Class Violation") — an atomic, clearly-signaled failure with no partial write, not a silent no-op. | ||
| - **Custom attributes:** `custom_attributes` maps arbitrary raw LDAP attribute names to values; an empty value clears the attribute (unlike the named fields above, where empty just means "not supplied"). Keys are used **verbatim** as attribute names — the named-field mapping above applies only to the named arguments, never to `custom_attributes`. `{"user_id": "x"}` therefore writes an attribute literally named `user_id`; it does not write `uid`. A name the directory does not define is refused by the server, and the result code depends on the implementation: OpenLDAP returns 17 ("Undefined Attribute Type"), ApacheDS returns 16 ("No Such Attribute"). The same goes for baton profile field names such as `login` and `path`: they are attempted as literal attribute names rather than skipped. | ||
| - **Collisions:** a `custom_attributes` key is dropped (never merged with, or overwriting, a named field's slot) and reported once in `skipped` when it case-insensitively matches either one of the four named field names, or the LDAP attribute a supplied named field is writing (`givenName`, `sn`, `displayName`, `mail`). The latter only applies when that named field was actually supplied and non-empty; otherwise `{"givenName": "Jane"}` is an ordinary raw write. | ||
| - **Not modifiable:** password attributes (`userPassword`, or any name containing `password` — use credential rotation instead) and `objectClass` are rejected; the user's RDN attribute (for example `cn` when the DN is `cn=jdoe,...`) is skipped, since renaming requires a different operation. | ||
| - **Multi-valued attributes:** setting (not clearing) a value on an attribute that currently holds more than one value returns an error instead of silently discarding the extra values; clearing (an empty value) still removes all values. | ||
| - **Value types:** `custom_attributes` carries one string per attribute, so binary attributes (`jpegPhoto`, `userCertificate;binary`) and option-tagged attributes (`;lang-xx`) cannot be set through this action. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: The option-tagged half of this claim doesn't hold. |
||
| - **Scope:** only entries within the configured user search scope (`user-search-dn`, falling back to `base-dn`) can be modified; out-of-scope or non-user DNs are rejected. | ||
|
|
||
| The connector's bind account must have permission to modify the target entry. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Suggestion:
displayNameis listed inside the "maps to a trait field" parenthetical, but it isn't a trait field — it feedsResource.DisplayName(pkg/connector/user.go:263), which the sentence already accounts for separately as one of the three carriers. Also,parseUserLogin(user.go:126) sources the login/alias set fromobjectGUIDtoo, so the trait-field list is one short. Both are small, but this paragraph is the deliverable of the change.