ldap-rest 0.11.2 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/bin/index.js +1 -1
  2. package/dist/config/args.js +1 -1
  3. package/dist/lib/authz/composition.js +1 -1
  4. package/dist/lib/instanceProviders.js +1 -0
  5. package/dist/lib/ldapActions.js +1 -1
  6. package/dist/plugins/auth/openidconnect.js +1 -1
  7. package/dist/plugins/configApi.js +1 -1
  8. package/dist/plugins/scim/groups.js +1 -1
  9. package/dist/plugins/scim/mapping.js +1 -1
  10. package/dist/plugins/scim/patch.js +1 -1
  11. package/dist/plugins/scim/users.js +1 -1
  12. package/dist/plugins/twake/instances.js +1 -0
  13. package/dist/plugins/twake/lifecycleAttributes.js +1 -0
  14. package/dist/plugins/twake/lifecycleEvents.js +1 -0
  15. package/dist/plugins/twake/tombstone.js +1 -0
  16. package/dist/src/abstract/plugin.d.ts +7 -4
  17. package/dist/src/config/args.d.ts +48 -0
  18. package/dist/src/hooks.d.ts +2 -0
  19. package/dist/src/lib/authz/composition.d.ts +1 -1
  20. package/dist/src/lib/instanceProviders.d.ts +86 -0
  21. package/dist/src/lib/ldapActions.d.ts +10 -2
  22. package/dist/src/plugins/auth/openidconnect.d.ts +24 -0
  23. package/dist/src/plugins/scim/mapping.d.ts +4 -1
  24. package/dist/src/plugins/scim/patch.d.ts +7 -0
  25. package/dist/src/plugins/twake/instances.d.ts +88 -0
  26. package/dist/src/plugins/twake/lifecycleAttributes.d.ts +29 -0
  27. package/dist/src/plugins/twake/lifecycleEvents.d.ts +45 -0
  28. package/dist/src/plugins/twake/tombstone.d.ts +86 -0
  29. package/docs/client-development/api/rest-api.md +2 -0
  30. package/docs/plugin-development/hooks.md +24 -0
  31. package/docs/usage/configuration.md +75 -0
  32. package/docs/usage/plugins/README.md +9 -6
  33. package/docs/usage/plugins/auth/oidc.md +27 -0
  34. package/docs/usage/plugins/integrations/README.md +11 -4
  35. package/docs/usage/plugins/integrations/instances.md +122 -0
  36. package/docs/usage/plugins/integrations/lifecycle-events.md +200 -0
  37. package/docs/usage/plugins/integrations/scim.md +11 -0
  38. package/docs/usage/plugins/integrations/tombstone.md +115 -0
  39. package/docs/usage/upgrading.md +58 -0
  40. package/package.json +17 -1
@@ -53,12 +53,15 @@ Protection and rate limiting:
53
53
 
54
54
  Connect to external systems:
55
55
 
56
- | Plugin | Description |
57
- | -------------------------------------------------- | ---------------------------------- |
58
- | [james-mail](integrations/james-mail.md) | Apache James synchronization |
59
- | [james-mailboxes](integrations/james-mailboxes.md) | Apache James team mailboxes |
60
- | [calendar](integrations/calendar.md) | Twake Calendar resources and users |
61
- | [app-accounts](integrations/app-accounts.md) | Applicative accounts API |
56
+ | Plugin | Description |
57
+ | ---------------------------------------------------- | ------------------------------------ |
58
+ | [james-mail](integrations/james-mail.md) | Apache James synchronization |
59
+ | [james-mailboxes](integrations/james-mailboxes.md) | Apache James team mailboxes |
60
+ | [calendar](integrations/calendar.md) | Twake Calendar resources and users |
61
+ | [app-accounts](integrations/app-accounts.md) | Applicative accounts API |
62
+ | [lifecycle-events](integrations/lifecycle-events.md) | Account lifecycle events to RabbitMQ |
63
+ | [instances](integrations/instances.md) | Workplace instance of each account |
64
+ | [tombstone](integrations/tombstone.md) | Deleted accounts kept as tombstones |
62
65
 
63
66
  ## Utility Plugins
64
67
 
@@ -85,6 +85,33 @@ client (`Accept: application/json`) receives 401. Every other authentication
85
85
  plugin in this repository answers JSON 401 throughout — the difference is
86
86
  kept because it is what makes a login work at all.
87
87
 
88
+ ## Logout
89
+
90
+ `/logout` ends the session at the provider as well (RP-Initiated Logout): the
91
+ browser is sent to the provider's `end_session_endpoint` with the id token as
92
+ `id_token_hint`, and the provider closes its own session — and, with
93
+ [Back-Channel Logout](back-channel-logout.md), tells the other applications.
94
+ No `post_logout_redirect_uri` is sent: the caller ends on the provider's own
95
+ logout page, and nothing has to be registered there.
96
+
97
+ Ending the cookie alone would not log anyone out: every guarded path goes
98
+ through the provider, which still holds its session and logs the caller back
99
+ in without asking anything.
100
+
101
+ A provider whose discovery document names no `end_session_endpoint` gets a
102
+ local logout — the cookie is dropped, the browser goes to `--base-url` — and
103
+ a warning saying the provider may log the caller back in.
104
+
105
+ `GET /v1/config` (with `core/configApi`) publishes the route, so a front-end
106
+ knows it can offer a sign-out control. The key is `openidconnect` whatever
107
+ the instance is named:
108
+
109
+ ```json
110
+ "features": {
111
+ "openidconnect": { "enabled": true, "endpoints": { "logout": "/logout" } }
112
+ }
113
+ ```
114
+
88
115
  ## How It Works
89
116
 
90
117
  1. Uses `express-openid-connect` for OAuth2/OIDC flow
@@ -11,10 +11,13 @@ Plugins for connecting LDAP-Rest to external systems.
11
11
 
12
12
  ## Twake
13
13
 
14
- | Plugin | Description |
15
- | ------------------------------- | ---------------------------------------- |
16
- | [calendar](calendar.md) | Calendar resources and users sync |
17
- | [app-accounts](app-accounts.md) | Applicative accounts API (devices, apps) |
14
+ | Plugin | Description |
15
+ | --------------------------------------- | ---------------------------------------- |
16
+ | [calendar](calendar.md) | Calendar resources and users sync |
17
+ | [app-accounts](app-accounts.md) | Applicative accounts API (devices, apps) |
18
+ | [lifecycle-events](lifecycle-events.md) | Account lifecycle events to RabbitMQ |
19
+ | [instances](instances.md) | Workplace instance of each account |
20
+ | [tombstone](tombstone.md) | Deleted accounts kept as tombstones |
18
21
 
19
22
  ## Prerequisites
20
23
 
@@ -39,4 +42,8 @@ core/twake/james
39
42
 
40
43
  core/twake/calendar
41
44
  └─ requires: core/ldap/onChange
45
+
46
+ core/twake/lifecycleEvents
47
+ ├─ requires: core/ldap/onChange
48
+ └─ requires: core/rabbitmq
42
49
  ```
@@ -0,0 +1,122 @@
1
+ # Workplace Instances
2
+
3
+ `core/twake/instances` gives every account this server creates its workplace
4
+ instance. It replaces `core/twake/cozyProvision` and
5
+ `core/twake/clouderyProvision`, and refuses to start when either is loaded
6
+ too. What changes when moving to it is in the
7
+ [upgrade notes](../../upgrading.md#twakeinstances-replaces-cozyprovision-and-clouderyprovision).
8
+
9
+ Every account must be created through this server (REST, SCIM, the LSC
10
+ plugin, scripts through the API). An entry written straight to the directory
11
+ gets no instance.
12
+
13
+ ## Behaviour
14
+
15
+ - Once an add has written an entry whose DN matches `--twake-instance-dn`,
16
+ the instance is looked up, then asked for if missing, before the add
17
+ answers (through the
18
+ [`ldapaddafter`](../../../plugin-development/hooks.md#ldapaddafter) hook).
19
+ An entry holding `--twake-instance-skip-value` in
20
+ `--twake-instance-skip-attribute`, a technical account for instance, gets
21
+ none, and neither does a tombstone
22
+ (`--twake-lifecycle-deleted-attribute`) put back in place.
23
+ - The address (`--twake-instance-fqdn-attribute`) and the mark
24
+ (`--twake-instance-sent-attribute`) are this plugin's to write: an add that
25
+ carries them has them removed, and an address written by a modify is taken
26
+ only when it is the one the provider gives the account, or once the
27
+ provider confirms it.
28
+ - A refused, unreachable or slow provider leaves the entry written and
29
+ without an address: it is pending, and the error is logged. The add itself
30
+ succeeds, whatever the door (REST, SCIM, bulk import).
31
+ `--twake-instance-timeout` bounds each call, so a lookup, a creation and,
32
+ with cozy-stack, the onboarding can hold an add up to three times as long.
33
+ - Once the instance exists, its address is written, `user.created` is
34
+ published on the auth exchange, without a password hash, and the entry is
35
+ marked with the date. The event is sent at least once: a failure between
36
+ the publication and the mark sends it again on the next attempt, and two
37
+ replicas handling the same account at once may both send it. Within one
38
+ replica, the work on one account is done one step at a time.
39
+ - The server does not start without a reachable broker, and an event that
40
+ cannot be published fails its attempt rather than being marked as sent.
41
+ - With cozy-stack, deleting an account destroys its instance, when that
42
+ instance's OIDC id is this account's.
43
+
44
+ ## Making sure an account has its instance
45
+
46
+ `POST /api/v1/twake/instances/ensure` with `{ "dn": … }` or `{ "mail": … }`
47
+ (or `ensureInstance(dn)` from another plugin) writes a missing address and
48
+ announces the account when its instance exists, and asks for the instance
49
+ when it does not. It answers `ready` or `pending`, and `400` when the body
50
+ names no account.
51
+
52
+ The route reads and writes the account with the caller's rights: an account
53
+ the caller cannot read answers `404`, as a missing one does, and one it
54
+ cannot write is refused before the provider or the broker hear of it.
55
+
56
+ Pending accounts are the entries under the `--twake-instance-dn` branches
57
+ with a mail and no address, for instance
58
+ `(&(mail=*)(!(twakeWorkspaceUrl=*)))`, technical accounts aside.
59
+
60
+ ## Organizations
61
+
62
+ `ensureOrganization({ id, name, domain })` asks for an organization's own
63
+ instance, for the application that creates organizations to call once it
64
+ has written the organization entry. With the Cloudery it creates the
65
+ Cloudery organization first. The organization entry, found under
66
+ `--twake-instance-organization-base` by
67
+ `--twake-instance-organization-id-attribute`, gets the address when
68
+ `--twake-instance-organization-fqdn-attribute` names where, then
69
+ `organization.created` is published on the b2b exchange, and the entry is
70
+ marked. The mark, not the address, says the organization is done.
71
+
72
+ ## Providers
73
+
74
+ - `cloudery`: the Cloudery accepts the request and builds the instance
75
+ afterwards. An instance already at the account's address is taken when it
76
+ is this account's, so a re-created account keeps its instance. The address
77
+ arrives with `workplace.created`, consumed on `--twake-instance-queue`,
78
+ with a dead-letter queue. The message goes to an organization when its
79
+ `twakeId` is an organization id and its `internalEmail` that
80
+ organization's own (`<id>@<domain>`); otherwise to the account with that
81
+ `internalEmail`.
82
+ - `cozy-stack`: the admin API builds the instance before answering, and the
83
+ address is written in the request. The instance's onboarding is marked as
84
+ finished, so its first login does not open the setup wizard. An instance
85
+ already there is taken only when its email and OIDC id are this account's.
86
+
87
+ The instance id is `--twake-instance-id`, a template of the account's `uid`
88
+ and the named groups of the matching DN expression, for example
89
+ `{uid}{org}`; dots are dropped. A name no expression captures stops the
90
+ server at startup. It is the slug of the instance's address; the OIDC id is
91
+ this slug with the Cloudery, and the `uid` with cozy-stack. `user.created`
92
+ names the account by its `uid` (`twakeId`). A group named `org` is the organization id sent to the provider
93
+ and in `user.created`, with the mail's domain as its domain; with cozy-stack
94
+ and no such group, `--twake-instance-cozy-org-id` and
95
+ `--twake-instance-cozy-org-domain` stand for them.
96
+
97
+ ## Configuration
98
+
99
+ ```bash
100
+ --plugin core/twake/instances \
101
+ --twake-instance-dn '^uid=(?<uid>[^,]+),ou=users,ou=(?<org>[^,]+),ou=organizations,dc=example,dc=com$' \
102
+ --twake-instance-id '{uid}{org}' \
103
+ --twake-instance-skip-attribute employeeType \
104
+ --twake-instance-skip-value technical \
105
+ --twake-instance-provider cloudery \
106
+ --twake-instance-cloudery-url https://manager.example.com \
107
+ --twake-instance-cloudery-token … \
108
+ --twake-instance-cloudery-domain example.com \
109
+ --twake-instance-cloudery-organization-offer organization \
110
+ --twake-instance-organization-base ou=organizations,dc=example,dc=com
111
+ ```
112
+
113
+ A DN holds commas, so in `DM_TWAKE_INSTANCE_DN` end each expression with `;`.
114
+ Every option and its default is listed in the
115
+ [configuration reference](../../configuration.md). The plugin needs
116
+ `--rabbitmq-url`.
117
+
118
+ The directory schema must define the address and mark attributes, and with
119
+ `--twake-instance-organization-base` the organization domain (and address)
120
+ attributes: the defaults `twakeWorkspaceUrl`, `twakeCreatedEventAt` and
121
+ `twakeDomain` come from the Twake schema, which this repository does not
122
+ ship. The server refuses to start when one is missing.
@@ -0,0 +1,200 @@
1
+ # Lifecycle Events
2
+
3
+ `core/twake/lifecycleEvents` publishes an account's lifecycle to RabbitMQ from
4
+ the directory write itself. REST, SCIM and any other plugin writing through
5
+ LDAP-Rest announce the same events, with the same payloads.
6
+
7
+ Which entries are accounts, which attributes carry the role, the lock and the
8
+ deletion, and where each event goes are configuration.
9
+
10
+ ## Events
11
+
12
+ Each event is a change of state, read from the entry before and after a write
13
+ (`onLdapEntryChange`, from `core/ldap/onChange`). A write that changes nothing
14
+ publishes nothing.
15
+
16
+ - `created`: an entry is added. An entry added locked, as a SCIM create with
17
+ `active: false` does, publishes `created` then `disabled`.
18
+ - `roleChanged`: the role attribute gets different values. Case and order are
19
+ ignored: `Admin` and `admin` are one role. A multi-valued role attribute is
20
+ compared as a set, but `$title` carries its first value only, so
21
+ `[x, y]` to `[x, z]` publishes `x` as both role and previous role: keep the
22
+ role in a single-valued attribute.
23
+ - `disabled`: the lock attribute takes the lock value.
24
+ - `enabled`: the lock attribute no longer holds the lock value.
25
+ - `deleted`: the entry becomes a tombstone (the deleted attribute takes the
26
+ deleted value), or an entry that is not a tombstone is removed.
27
+
28
+ An account is locked when its lock attribute holds the lock value, by default
29
+ the ppolicy administrative lock `000001010000Z`. Any other value, such as the
30
+ timestamp of a ppolicy lockout after failed binds, is not a lock here.
31
+
32
+ A tombstone announces nothing else: the write that makes it does not also
33
+ publish `disabled`, later changes to it (a lock written afterwards included)
34
+ publish nothing, and removing it publishes nothing, since its deletion was
35
+ already announced.
36
+
37
+ A publish that fails is logged as an error, and the next targets are still
38
+ published. It never fails the write that caused it, and the event is not
39
+ replayed: writing the same value again changes nothing, so it publishes
40
+ nothing. With `core/twake/tombstone`, a lost `deleted` is published again by
41
+ deleting the tombstone once more (see below); a lost `disabled`, `enabled`,
42
+ `created` or `roleChanged` has no such path.
43
+
44
+ The lock attribute is requested by name when the entry is read, so an
45
+ operational one such as `pwdAccountLockedTime` is seen on both sides.
46
+
47
+ ## What is not published
48
+
49
+ - A rename or a move publishes nothing. The `$dn.*` groups of the new DN may
50
+ then differ from the ones consumers know, and an entry moved into or out of
51
+ a rule's pattern publishes neither `created` nor `deleted`.
52
+ - A tombstone cannot be restored. Clearing its deleted attribute is not a
53
+ supported operation: it publishes nothing, whatever else the same write
54
+ changes, and later changes are published again as for any account, to
55
+ consumers that consider it deleted.
56
+ - A change made outside LDAP-Rest is not seen. A ppolicy lockout after failed
57
+ binds is written by the directory itself, and holds a timestamp rather than
58
+ the lock value: it publishes nothing, and neither does clearing it.
59
+
60
+ ## Deployments with other plugins
61
+
62
+ - `core/twake/tombstone` answers a second delete of a tombstone without
63
+ writing anything, and fires `twakedeletionreplay`; this plugin then
64
+ publishes `deleted` again, with the tombstone as it is (same deletion date).
65
+ This explicit delete is how a `deleted` event a broker outage lost is
66
+ replayed.
67
+ - `core/twake/cozyProvision` and `core/twake/clouderyProvision` publish their
68
+ own deletion event on `--cozy-user-deleted-routing-key`. When a `deleted`
69
+ rule here publishes it, set that option to `""`. Turning it off without a
70
+ `deleted` rule leaves no deletion event at all; keeping both publishes it
71
+ twice.
72
+ - `core/ldap/trash` moves a deleted entry instead of removing it, and a move
73
+ made by the trash is not seen by `core/ldap/onChange`. A deletion in a
74
+ branch the trash watches publishes nothing.
75
+
76
+ ## Configuration
77
+
78
+ ```bash
79
+ --plugin core/twake/lifecycleEvents \
80
+ --twake-lifecycle-role-attribute title \
81
+ --twake-lifecycle-lock-attribute pwdAccountLockedTime \
82
+ --twake-lifecycle-lock-value 000001010000Z \
83
+ --twake-lifecycle-deleted-attribute employeeType \
84
+ --twake-lifecycle-deleted-value deleted \
85
+ --twake-lifecycle-deleted-at-attribute roomNumber \
86
+ --twake-lifecycle-deleted-at-format iso8601 \
87
+ --twake-lifecycle-rules /etc/ldap-rest/lifecycle-rules.json
88
+ ```
89
+
90
+ - `--twake-lifecycle-role-attribute`: empty means no `roleChanged`.
91
+ - `--twake-lifecycle-lock-attribute`, `--twake-lifecycle-lock-value`: default
92
+ to `--scim-user-lock-attribute` and `--scim-user-lock-value`, so SCIM
93
+ `active` and the events agree; SCIM's value only goes with SCIM's
94
+ attribute. `pwdAccountLockedTime` defaults to `000001010000Z`; any other
95
+ attribute needs its value, or the server does not start, as for
96
+ [SCIM](scim.md).
97
+ Only changes written through LDAP-Rest are seen: a lockout set by the
98
+ directory publishes nothing.
99
+ - `--twake-lifecycle-deleted-attribute`, `--twake-lifecycle-deleted-value`
100
+ (default `TRUE`, compared case-insensitively): what marks a tombstone. Empty
101
+ means entries are never tombstones, and only a removal publishes `deleted`.
102
+ - `--twake-lifecycle-deleted-at-attribute`,
103
+ `--twake-lifecycle-deleted-at-format` (`iso8601`, the default, or
104
+ `generalizedTime`): the deletion date. It is always published as ISO 8601.
105
+ - `--twake-lifecycle-rules`: a JSON file, or the JSON itself.
106
+
107
+ Each option has a `DM_` environment variable, for example
108
+ `DM_TWAKE_LIFECYCLE_RULES`. The events go through `core/rabbitmq`. With rules
109
+ configured, the server does not start without `--rabbitmq-url`, nor when
110
+ that broker cannot be reached. An event that finds no broker later on is
111
+ lost, and logged as an error.
112
+
113
+ ## Rules
114
+
115
+ The rules are an array. The first rule whose `dn` regular expression matches
116
+ the entry's DN (case-insensitively) decides what is published; an entry no
117
+ rule matches publishes nothing.
118
+
119
+ The DN is matched without spaces around its separators, and with escaped
120
+ commas written `\2C`: `uid=a\, b, ou=users,…` is read as `uid=a\2C b,ou=users,…`,
121
+ so `(?<id>[^,]+)` takes the whole value. A `$dn.name` group is unescaped:
122
+ `a, b`.
123
+
124
+ ```json
125
+ [
126
+ {
127
+ "dn": "^uid=(?<id>[^,]+),ou=users,dc=example,dc=com$",
128
+ "exchange": "accounts",
129
+ "payload": {
130
+ "id": "$dn.id",
131
+ "email": "$mail",
132
+ "domain": "$mail|domain",
133
+ "role": "$title",
134
+ "timestamp": "$now"
135
+ },
136
+ "events": {
137
+ "created": "account.created",
138
+ "roleChanged": {
139
+ "routingKey": "account.role.changed",
140
+ "payload": {
141
+ "id": "$dn.id",
142
+ "role": "$title",
143
+ "previousRole": "$previous.title"
144
+ }
145
+ },
146
+ "disabled": "account.disabled",
147
+ "enabled": "account.enabled",
148
+ "deleted": [
149
+ "account.deleted",
150
+ {
151
+ "exchange": "notifications",
152
+ "routingKey": "account.deleted",
153
+ "when": { "$businessCategory": "user_request" },
154
+ "payload": { "id": "$dn.id", "mobile": "$previous.mobile" }
155
+ }
156
+ ]
157
+ }
158
+ }
159
+ ]
160
+ ```
161
+
162
+ An event takes one target or a list of them, published in order. A target is
163
+ a routing key, or an object with:
164
+
165
+ - `routingKey`
166
+ - `exchange`: defaults to the rule's
167
+ - `payload`: replaces the rule's `payload`
168
+ - `when`: publish only if every source equals its value; a value starting with
169
+ `!` means "differs from"
170
+
171
+ Payload and `when` values are sources:
172
+
173
+ - `$attr`: the attribute after the write; for a removed entry, which has no
174
+ "after", the attribute before it
175
+ - `$previous.attr`: the attribute before the write
176
+ - `$dn.name`: a named group of the rule's `dn` expression
177
+ - `$context.actor`, `$context.requestId`, `$context.source`: who made the
178
+ write, the request it belongs to, and the API it came through (`rest`,
179
+ `scim`); left out for a write no request made, such as a scheduled task
180
+ - `$attr|domain`, `$previous.attr|domain`: what follows the `@`
181
+ - `$now`: the current time, ISO 8601
182
+ - anything else: the value itself
183
+
184
+ `$attr` and `$previous.attr` carry the first value of a multi-valued
185
+ attribute. A source with no value leaves its field out. Every message carries
186
+ a random AMQP `messageId`.
187
+
188
+ A source can read any attribute of the entry: `$userPassword` would put the
189
+ password hash in the message.
190
+
191
+ The rules are checked at startup: a malformed one, such as a payload value
192
+ that is not a string, stops the server with an error naming its `dn`.
193
+
194
+ ## Dependencies
195
+
196
+ ```
197
+ core/twake/lifecycleEvents
198
+ ├─ requires: core/ldap/onChange
199
+ └─ requires: core/rabbitmq
200
+ ```
@@ -305,6 +305,17 @@ mapping contains an entry with `scim: "department"`; otherwise the server return
305
305
  `400 invalidFilter`. Same for PATCH: operations on unmapped paths produce
306
306
  `400 invalidPath`.
307
307
 
308
+ A PATCH `add`, or a `remove` naming values, changes only the values it names:
309
+ values the caller's read did not show, such as group members an
310
+ `ldapsearchfilter` hides, stay. Two PATCHes changing different values of
311
+ the same attribute both apply. Adding a value already there succeeds where
312
+ the directory honours the permissive modify control; removing one another
313
+ write just removed replays the PATCH on a fresh read, then answers `409`.
314
+ A `replace`, or a `remove` of a whole attribute, sets the attribute to what
315
+ the caller sent, and so does a group `PUT` for its members, hidden ones
316
+ included. A group left with no member the caller saw gets the
317
+ `--group-dummy-user` placeholder.
318
+
308
319
  ### Schema advertisement
309
320
 
310
321
  Schemas listed under `GET /scim/v2/Schemas` are read from
@@ -0,0 +1,115 @@
1
+ # Tombstone
2
+
3
+ `core/twake/tombstone` turns the delete of an account into a tombstone. The
4
+ entry stays, so its identifiers cannot be taken by a new account, but it is
5
+ flagged as deleted, dated, locked, carries the reason, and loses the
6
+ attributes the deployment names.
7
+
8
+ A tombstone is erased later, from an operator's decision: once the deletion
9
+ is old enough, or right away when forced.
10
+
11
+ ## Behaviour
12
+
13
+ - A delete of an entry whose DN matches `--twake-tombstone-dn` writes, in one
14
+ modify: the deleted attribute, the deletion date (now), the reason, the
15
+ lock, and removes `--twake-tombstone-clear-attributes`. Other entries are
16
+ deleted as usual.
17
+ - A delete of an existing tombstone writes nothing and answers as a delete.
18
+ It fires the `twakedeletionreplay` hook with the tombstone, so
19
+ [lifecycle events](lifecycle-events.md) publishes the deletion again, with
20
+ the date of the first one. This is the way to replay a `deleted` event that
21
+ was lost, for instance while the broker was down.
22
+ - The tombstone is written by the server, not as the caller: the caller's
23
+ delete right is what was judged, and a write right is not required on top.
24
+ So the `deleted` event of that write has no `$context.actor`,
25
+ `$context.requestId` or `$context.source`. A replay has them.
26
+ - The tombstone keeps its group memberships until it is erased.
27
+ - SCIM treats a tombstone as gone: reads answer 404, lists leave it out, and
28
+ a create with the same identity erases the tombstone and creates a new
29
+ entry. If the directory then refuses the new entry, the tombstone and its
30
+ memberships are put back once the response is sent.
31
+ - Erasing removes the entry, then its group memberships. It publishes
32
+ nothing.
33
+ - A tombstone cannot be restored. Clearing its deleted attribute by hand is
34
+ not a supported operation.
35
+
36
+ The tombstone is written after every other plugin has judged the delete, so
37
+ a delete that an authorization plugin refuses writes nothing.
38
+
39
+ `core/ldap/trash` moves a deleted entry before this plugin sees the delete,
40
+ so no tombstone would be written in a branch it watches. The server refuses
41
+ to start when `--trash-watched-bases` covers a `--twake-tombstone-dn`
42
+ pattern, read the way the trash reads it today, and an empty list watches
43
+ every branch.
44
+
45
+ The trash splits `--trash-watched-bases` on every comma, a bug
46
+ ([#226](https://github.com/linagora/ldap-rest/issues/226)): a base such as
47
+ `ou=users,dc=example,dc=com` becomes the suffixes `ou=users`, `dc=example`
48
+ and `dc=com`, and `dc=com` watches every branch below it. The check follows
49
+ the trash as it behaves, so such a value refuses to start next to any
50
+ tombstone pattern under `dc=com`.
51
+
52
+ ## Reason
53
+
54
+ The reason is, in order:
55
+
56
+ - the one given to `tombstone(dn, reason, req)` by another plugin;
57
+ - the `--twake-tombstone-reason-header` request header;
58
+ - `--twake-tombstone-default-reason`.
59
+
60
+ When `--twake-tombstone-reasons` is set, any other reason is refused with a 400. Otherwise a reason is letters, digits, `_`, `.` or `-`.
61
+
62
+ ## Erase
63
+
64
+ ```http
65
+ POST /api/v1/twake/tombstones/erase
66
+ Content-Type: application/json
67
+
68
+ { "dn": "uid=jdoe,ou=users,dc=example,dc=com", "force": false }
69
+ ```
70
+
71
+ - `200`: erased.
72
+ - `404`: no tombstone at this DN.
73
+ - `409`: the deletion is younger than `--twake-tombstone-erase-min-age` and
74
+ `force` is not true.
75
+
76
+ Other plugins call `erase(dn, { force, req })`.
77
+
78
+ ## Configuration
79
+
80
+ The tombstone attributes are the lifecycle ones, shared with
81
+ [lifecycle events](lifecycle-events.md):
82
+
83
+ ```bash
84
+ --plugin core/twake/tombstone \
85
+ --twake-lifecycle-lock-attribute pwdAccountLockedTime \
86
+ --twake-lifecycle-lock-value 000001010000Z \
87
+ --twake-lifecycle-deleted-attribute employeeType \
88
+ --twake-lifecycle-deleted-value deleted \
89
+ --twake-lifecycle-deleted-at-attribute roomNumber \
90
+ --twake-lifecycle-deleted-at-format iso8601 \
91
+ --twake-lifecycle-reason-attribute businessCategory \
92
+ --twake-tombstone-dn '^uid=[^,]+,ou=users,dc=example,dc=com$' \
93
+ --twake-tombstone-dn '^uid=[^,]+,ou=[^,]+,ou=organizations,dc=example,dc=com$' \
94
+ --twake-tombstone-reasons deleted,user_request \
95
+ --twake-tombstone-clear-attributes mobile,telephoneNumber \
96
+ --twake-tombstone-erase-min-age 2592000 \
97
+ --twake-tombstone-group-bases ou=groups,dc=example,dc=com
98
+ ```
99
+
100
+ - `--twake-lifecycle-deleted-attribute`: required.
101
+ - `--twake-lifecycle-lock-attribute`, `--twake-lifecycle-lock-value`: the
102
+ lock written on a tombstone, read as
103
+ [lifecycle events](lifecycle-events.md) reads it. A lock attribute other
104
+ than `pwdAccountLockedTime` needs its value (`TRUE` for `nsAccountLock` on
105
+ 389-ds), or the server does not start: `000001010000Z` means nothing there.
106
+ - `--twake-lifecycle-reason-attribute`: empty means the reason is not
107
+ recorded.
108
+ - `--twake-tombstone-dn`: regular expressions, matched case-insensitively. A
109
+ DN holds commas, so in `DM_TWAKE_TOMBSTONE_DN` end each one with `;`.
110
+ - `--twake-tombstone-default-reason`: default `deleted`.
111
+ - `--twake-tombstone-reason-header`: default `x-deletion-reason`.
112
+ - `--twake-tombstone-erase-min-age`: seconds, default 30 days.
113
+ - `--twake-tombstone-group-bases`: where memberships are looked for at erase,
114
+ by `member`. Defaults to `--ldap-group-base`, then `--ldap-base`. In the
115
+ environment variable, end each base with `;`.
@@ -4,6 +4,64 @@ What to check before deploying, newest first. Only releases that need a
4
4
  decision or a configuration change appear here; see the
5
5
  [CHANGELOG](../../CHANGELOG.md) for everything else.
6
6
 
7
+ ## To 0.12.0
8
+
9
+ ### twake/instances replaces cozyProvision and clouderyProvision
10
+
11
+ **Who is affected:** a deployment loading `core/twake/cozyProvision` or
12
+ `core/twake/clouderyProvision`.
13
+
14
+ Replace the plugin with `core/twake/instances`
15
+ ([doc](plugins/integrations/instances.md)); the server refuses to start with
16
+ both. Its options are its own. From cozyProvision, with
17
+ `--twake-instance-provider cozy-stack` and `--twake-instance-dn` set to the
18
+ accounts' branch:
19
+
20
+ - `--cozy-admin-url` → `--twake-instance-cozy-url`
21
+ - `--cozy-admin-user` → `--twake-instance-cozy-user`
22
+ - `--cozy-admin-passphrase` → `--twake-instance-cozy-passphrase`
23
+ - `--cozy-org-domain` → `--twake-instance-cozy-domain` (the instances'
24
+ domain) and `--twake-instance-cozy-org-domain`
25
+ - `--cozy-org-id` → `--twake-instance-cozy-org-id`
26
+ - `--cozy-context-name` → `--twake-instance-cozy-context`
27
+ - `--cozy-apps` → `--twake-instance-cozy-apps`
28
+ - `--cozy-default-locale` → `--twake-instance-locale`
29
+ - `--cozy-auth-exchange` → `--twake-instance-auth-exchange`
30
+ - `--cozy-b2b-exchange` → `--twake-instance-b2b-exchange`
31
+ - `--cozy-user-created-routing-key` → `--twake-instance-user-created-key`
32
+
33
+ From clouderyProvision, with `--twake-instance-provider cloudery`:
34
+
35
+ - `--cloudery-manager-url` → `--twake-instance-cloudery-url`
36
+ - `--cloudery-manager-token` → `--twake-instance-cloudery-token`
37
+ - `--cloudery-offer` → `--twake-instance-cloudery-offer`
38
+ - `--cloudery-domain` → `--twake-instance-cloudery-domain`
39
+ - `--cloudery-default-locale` → `--twake-instance-locale`
40
+ - `--cloudery-fqdn-attribute` → `--twake-instance-fqdn-attribute`
41
+ - `--cloudery-user-branch` → `--twake-instance-dn`, with an `org` group for
42
+ the organization id; `--twake-instance-id '{uid}{org}'` keeps the slugs
43
+
44
+ What no longer happens:
45
+
46
+ - An account gets its instance on any add under `--twake-instance-dn`, not
47
+ only a SCIM create.
48
+ - `domain.user.deleted` is not published, and `--cozy-user-deleted-routing-key`
49
+ has no plugin to read it. With the Cloudery, deleting an account no longer
50
+ deletes its instance; cozy-stack instances still are.
51
+ - clouderyProvision's writes on a SCIM create are gone:
52
+ `twakeOrganizationId`, `twakeOrganizationRole`, `twakeInvited`,
53
+ `twakePhones`, `cn` set to the userName, and the lower-casing of the SCIM
54
+ `userName`. The `x-cloudery-org-*` headers are not read; the organization
55
+ comes from the DN. `--cloudery-org-*`, `--cloudery-phones-attribute`,
56
+ `--cloudery-invited-attribute` and the workflow polling options are gone:
57
+ the address arrives with `workplace.created` instead.
58
+ - With cozy-stack, the instance address drops the uid's dots and lower-cases
59
+ it, as the Cloudery's slugs do: an account whose uid holds a dot or a
60
+ capital gets a different address than cozyProvision gave it. Its OIDC id
61
+ is still the uid.
62
+
63
+ `user.created` keeps its fields, `twakeId` still being the account's uid.
64
+
7
65
  ## To 0.11.0
8
66
 
9
67
  ### Group reads are judged
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ldap-rest",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
4
4
  "description": "Light directory manager",
5
5
  "keywords": [
6
6
  "Identity-Manager"
@@ -268,10 +268,26 @@
268
268
  "types": "./dist/src/plugins/twake/drive.d.ts",
269
269
  "import": "./dist/plugins/twake/drive.js"
270
270
  },
271
+ "./plugin-twake-instances": {
272
+ "types": "./dist/src/plugins/twake/instances.d.ts",
273
+ "import": "./dist/plugins/twake/instances.js"
274
+ },
271
275
  "./plugin-twake-james": {
272
276
  "types": "./dist/src/plugins/twake/james.d.ts",
273
277
  "import": "./dist/plugins/twake/james.js"
274
278
  },
279
+ "./plugin-twake-lifecycleattributes": {
280
+ "types": "./dist/src/plugins/twake/lifecycleAttributes.d.ts",
281
+ "import": "./dist/plugins/twake/lifecycleAttributes.js"
282
+ },
283
+ "./plugin-twake-lifecycleevents": {
284
+ "types": "./dist/src/plugins/twake/lifecycleEvents.d.ts",
285
+ "import": "./dist/plugins/twake/lifecycleEvents.js"
286
+ },
287
+ "./plugin-twake-tombstone": {
288
+ "types": "./dist/src/plugins/twake/tombstone.d.ts",
289
+ "import": "./dist/plugins/twake/tombstone.js"
290
+ },
275
291
  "./plugin-weblogs": {
276
292
  "types": "./dist/src/plugins/weblogs.d.ts",
277
293
  "import": "./dist/plugins/weblogs.js"