ldap-rest 0.14.0 → 0.16.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.
@@ -59,18 +59,37 @@ All CLI options can be set via environment variables with the `DM_` prefix.
59
59
 
60
60
  Some options accept multiple values (e.g., `--plugin`, `--auth-token`, `--ldap-url`). These can be configured in several ways:
61
61
 
62
- **Via CLI - repeat the option:**
62
+ **Via CLI - repeat the option, or give it comma-separated values:**
63
63
 
64
64
  ```bash
65
- ldap-rest --plugin core/auth/token --plugin core/ldap/flatGeneric --plugin core/ldap/groups
65
+ ldap-rest --plugin core/auth/token --plugin core/ldap/flatGeneric,core/ldap/groups
66
66
  ```
67
67
 
68
+ Only options holding identifiers split on commas: `--plugin`, `--mail-domain`,
69
+ `--ldap-url`, the object class options (`--user-class`, `--group-class`,
70
+ `--ldap-organization-class`, `--external-branch-class`,
71
+ `--scim-user-object-class`, `--scim-group-object-class`,
72
+ `--twake-space-class`), the attribute
73
+ options (`--ldap-raw-hidden-attribute`, `--applicative-account-attribute`,
74
+ `--ldap-operational-attribute`, `--twake-tombstone-clear-attributes`),
75
+ `--twake-tombstone-reasons`, `--authz-for`, `--authz-dynamic-bypass` and
76
+ `--trusted-proxy`. Any other
77
+ option takes each value whole, so a DN or a secret keeps its commas.
78
+
68
79
  **Via CLI - use plural form with comma-separated values:**
69
80
 
70
81
  ```bash
71
82
  ldap-rest --plugins core/auth/token,core/ldap/flatGeneric,core/ldap/groups
72
83
  ```
73
84
 
85
+ The plural form splits like the environment variable, see below. Give a
86
+ DN with the repeated singular option, or separate DNs with `;` in the plural
87
+ form:
88
+
89
+ ```bash
90
+ ldap-rest --ldap-raw-base ou=users,dc=example,dc=com --ldap-raw-base ou=groups,dc=example,dc=com
91
+ ```
92
+
74
93
  **Via environment variable - use `;` or `,` as separator:**
75
94
 
76
95
  ```bash
@@ -81,7 +100,49 @@ export DM_PLUGINS="core/auth/token;core/ldap/flatGeneric;core/ldap/groups"
81
100
  export DM_PLUGINS="core/auth/token,core/ldap/flatGeneric,core/ldap/groups"
82
101
  ```
83
102
 
84
- > **Note:** If the value contains a semicolon, it will be used as the separator. Otherwise, commas are used. Whitespace around separators is ignored.
103
+ The split depends on what the option holds. The plural form splits the same
104
+ way:
105
+
106
+ - Options holding identifiers (the list above): a value containing `;` is
107
+ split on `;` and spaces, any other on `,` and spaces. A value starting with
108
+ `;` is split on `;` too.
109
+ - Options holding DNs or DN expressions (`--ldap-raw-base`,
110
+ `--james-mailing-list-branch`, `--twake-tombstone-dn`,
111
+ `--twake-tombstone-group-bases`, `--twake-instance-dn`): split on `;` only,
112
+ never on `,` nor spaces, as a DN holds both.
113
+ `DM_LDAP_RAW_BASE="ou=My Unit,dc=example,dc=com"` is one DN; separate
114
+ several DNs with `;`.
115
+ - `--auth-hmac` (`id:secret:name`) and `--auth-totp` (`secret:name[:digits]`):
116
+ split on `;` if the value contains one, else on `,`, and on newlines (one
117
+ entry per line), never on spaces, so a name keeps its spaces. A list
118
+ separated by spaces is read as one entry: for HMAC the services after the
119
+ first cannot authenticate, and for TOTP the entry is refused. Any entry that
120
+ cannot work stops the server: an HMAC one with an empty id, secret or name or
121
+ fewer than 3 fields, a TOTP one with an empty secret or name, too many
122
+ fields, a secret that is not Base32 or digits that are not an integer from 6
123
+ to 10.
124
+ - Any other option (tokens, paths, rules...): split on `;` if
125
+ the value contains one, else on `,`, and on spaces. `DM_AUTH_TOKENS="t1 t2"`
126
+ and `DM_AUTH_TOKENS="t1,t2"` are two tokens, and padded base64 secrets
127
+ (`YWJj=,ZGVm=`) split on the comma.
128
+
129
+ Empty items are dropped, and each item is trimmed. A variable that is empty,
130
+ only holds whitespace or only holds separators (`;`, `${A:-};${B:-}`), such as
131
+ `${VAR:-}` in a compose file, counts as unset: the default applies, so an
132
+ environment variable cannot give an empty list to an option whose default is
133
+ not empty. The exception is `DM_LDAP_URL`, whose default `ldap://localhost` is
134
+ a guess: an empty value stops the server. The same
135
+ goes for an empty number or JSON value such as `DM_PORT=`. A number option
136
+ takes an integer only: `DM_PORT=abc`, `DM_PORT=1e3`, `--port 80abc` and
137
+ `--port --log-level debug` stop the server, and so does an empty number on the
138
+ command line, as an empty array value does.
139
+ The plural form of an option takes `;` before `,` too: `--auth-tokens 'a;b,c'`
140
+ is `a` and `b,c`.
141
+
142
+ Values given on the command line replace the default, and are added to those
143
+ of the environment variable. An empty value on the command line, such as an
144
+ unset variable expanding to nothing, stops the server: leave the option out
145
+ to keep the default.
85
146
 
86
147
  ## General Options
87
148
 
@@ -541,6 +602,27 @@ Extends `core/ldap/groups`, whose options it reads too.
541
602
 
542
603
  See [groups](plugins/integrations/groups.md).
543
604
 
605
+ #### `core/twake/spaces`
606
+
607
+ Requires `core/twake/groups`, whose options it reads for users, groups and the
608
+ organization entry, and `core/ldap/onChange`; with `--rabbitmq-url`, it
609
+ requires `core/rabbitmq` and reads the organization domain from
610
+ `--twake-instance-organization-domain-attribute`.
611
+
612
+ | CLI | Env | Default | Description |
613
+ | -------------------------------------- | --------------------------------------- | -------------------------------------- | -------------------------------------------------- |
614
+ | `--twake-space-base` | `DM_TWAKE_SPACE_BASE` | | Space branch of an organization, with `{org}` |
615
+ | `--twake-space-class` | `DM_TWAKE_SPACE_CLASS` | `top,twakeSpace` | Object classes of a space |
616
+ | `--twake-space-display-name-attribute` | `DM_TWAKE_SPACE_DISPLAY_NAME_ATTRIBUTE` | `twakeDisplayName` | Space name attribute |
617
+ | `--twake-space-admin-attribute` | `DM_TWAKE_SPACE_ADMIN_ATTRIBUTE` | `twakeSpaceAdmin` | DNs holding the admin role |
618
+ | `--twake-space-editor-attribute` | `DM_TWAKE_SPACE_EDITOR_ATTRIBUTE` | `twakeSpaceEditor` | DNs holding the editor role |
619
+ | `--twake-space-viewer-attribute` | `DM_TWAKE_SPACE_VIEWER_ATTRIBUTE` | `twakeSpaceViewer` | DNs holding the viewer role |
620
+ | `--twake-space-user-role-attribute` | `DM_TWAKE_SPACE_USER_ROLE_ATTRIBUTE` | | User attribute holding `<space id>:<role>`, if set |
621
+ | `--twake-space-exchange` | `DM_TWAKE_SPACE_EXCHANGE` | `space` | Topic exchange of the space events |
622
+ | `--twake-space-sync-queue` | `DM_TWAKE_SPACE_SYNC_QUEUE` | `twake.space.sync.requested.ldap-rest` | Queue of the space sync requests |
623
+
624
+ See [spaces](plugins/integrations/spaces.md).
625
+
544
626
  ### Utility Plugins
545
627
 
546
628
  #### `core/static`
@@ -19,6 +19,7 @@ Plugins for connecting LDAP-Rest to external systems.
19
19
  | [instances](instances.md) | Workplace instance of each account |
20
20
  | [tombstone](tombstone.md) | Deleted accounts kept as tombstones |
21
21
  | [groups](groups.md) | Organization groups |
22
+ | [spaces](spaces.md) | Organization spaces, with member roles |
22
23
 
23
24
  ## Prerequisites
24
25
 
@@ -47,4 +48,9 @@ core/twake/calendar
47
48
  core/twake/lifecycleEvents
48
49
  ├─ requires: core/ldap/onChange
49
50
  └─ requires: core/rabbitmq
51
+
52
+ core/twake/spaces
53
+ ├─ requires: core/twake/groups
54
+ ├─ requires: core/ldap/onChange
55
+ └─ requires: core/rabbitmq (with RabbitMQ)
50
56
  ```
@@ -12,8 +12,14 @@ branch per organization. It serves the groups of an organization under
12
12
  display name attribute, an optional color and its creation date.
13
13
  - A group's members belong to its organization: adding a user of another
14
14
  organization, or of no organization, is refused with a 400, whichever API
15
- writes the group (REST, SCIM, another plugin). The placeholder
16
- (`--group-dummy-user`) is the one exception.
15
+ writes the group (REST, SCIM, another plugin), and so is moving a group
16
+ with its members, tombstones included, straight into another
17
+ organization's group branch: that refusal names none of them. The
18
+ placeholder (`--group-dummy-user`) is the one exception. Not checked:
19
+ moving a group out of the organizations' branches (to `ou=archive,…`, for
20
+ instance) or deeper than one level under another organization, which
21
+ takes its members along, and moving a user to another organization, whose
22
+ groups in the one it left keep it as a member.
17
23
  - An empty group holds the placeholder, because `groupOfNames` needs a member.
18
24
  The placeholder is never listed.
19
25
  - A tombstone (see [tombstone](tombstone.md)) keeps its memberships until it
@@ -33,7 +39,8 @@ branch per organization. It serves the groups of an organization under
33
39
 
34
40
  Every route answers 404 `ORGANIZATION_NOT_FOUND` when the organization entry
35
41
  is missing, and 410 `ORGANIZATION_DELETED` when it is deleted. Errors are
36
- `{ "error": "...", "code": "..." }`.
42
+ `{ "error": "...", "code": "..." }`. A request an authorization plugin refuses
43
+ answers 403 `REFUSED`.
37
44
 
38
45
  A group reads:
39
46
 
@@ -55,7 +62,8 @@ A group reads:
55
62
  display name reads its `cn`.
56
63
 
57
64
  - `GET /groups?page&limit&search&sortBy&sortOrder`: `search` (2 characters
58
- or more) matches the display name and the description; `sortBy` is
65
+ or more; an empty one lists everything) matches the display name — the
66
+ `cn` when the group has none — and the description; `sortBy` is
59
67
  `displayName`, `description` or `createdAt`. Answers
60
68
  `{ organizationId, groups, pagination: { page, limit, total, totalPages } }`.
61
69
  - `POST /groups` with `{ name, description?, color? }`: `name` is at most 256
@@ -195,7 +195,9 @@ Payload and `when` values are sources:
195
195
  - anything else: the value itself
196
196
 
197
197
  `$attr` and `$previous.attr` carry the first value of a multi-valued
198
- attribute. A source with no value leaves its field out.
198
+ attribute. A source with no value leaves its field out. Every attribute a
199
+ source names is requested by name when the entry is read, so an operational
200
+ one such as `entryUUID` works anywhere in a payload or a `when`.
199
201
 
200
202
  A payload field can also list members, as an object with one key:
201
203
 
@@ -0,0 +1,274 @@
1
+ # Organization Spaces
2
+
3
+ `core/twake/spaces` serves the spaces of an organization under
4
+ `/api/v1/organizations/:id/spaces`. A space gathers users and groups of the
5
+ organization, each with a role in it: `viewer`, `editor` or `admin`.
6
+
7
+ ## Behaviour
8
+
9
+ - A space lives in the organization's space branch, given by a DN pattern
10
+ where `{org}` stands for the organization id. Its `cn` is a generated UUID,
11
+ its name is in the display name attribute.
12
+ - A space holds DNs in three attributes, one per role. Each holds users of
13
+ the organization's user branch and groups of its group branch, the
14
+ branches [groups](groups.md) serves.
15
+ - A user or a group holds one role in a space. A user's role in a space is
16
+ the strongest of their own role and the roles of the linked groups they
17
+ belong to.
18
+ - The routes keep at least one admin among a space's users: a request that
19
+ would demote or remove the last one is refused with 409 `LAST_ADMIN`. A
20
+ linked group does not count as an admin. A tombstoned or erased admin is
21
+ not a request, and can leave a space with none.
22
+ - A space holds users and groups of its organization only: any other value is
23
+ refused with a 400, whichever API writes the space, or moves it straight
24
+ into another organization's space branch; a move refused names none of
25
+ them. Not checked: moving a space out of the organizations' branches (to
26
+ `ou=archive,…`, for instance) or deeper than one level under another
27
+ organization, which takes its holders along, and moving a user or group
28
+ to another organization, whose spaces in the one it left keep it. The
29
+ one role and the admin are kept by the routes below only; a user or group
30
+ a direct write, or two requests at once, left under two roles reads with
31
+ the strongest, and a role change or removal takes it out of all of them.
32
+ - A tombstone (see [tombstone](tombstone.md)) keeps its roles until it is
33
+ erased, and is hidden from the spaces read through these routes, whatever
34
+ the caller may read of the user branch: it never counts as an admin. It
35
+ cannot be added.
36
+ - An erased user or group leaves its spaces through the directory: enable the
37
+ `refint` overlay on the three role attributes.
38
+ - When a user who is erased or becomes a tombstone was the last admin among
39
+ a space's users, its editors become admins, or its viewers when it has no
40
+ editor. A space left with no user of its own is deleted, whatever groups
41
+ it links. Both happen shortly after the deletion, and publish their
42
+ events. Erasing a tombstone checks its spaces again, for a hand-over that
43
+ failed or was never made. The hand-over relies on refint: without it an erased admin's DN
44
+ stays in the space and still counts as an admin, so erasing two admins
45
+ one after the other hands nothing over. A write of the space made at the
46
+ same moment is retried twice; past that the hand-over is logged and left
47
+ undone.
48
+ - With `--twake-space-user-role-attribute`, each user entry holds one
49
+ `<space id>:<role>` value per space they are in, their resolved role. It
50
+ follows every write of a space, every change of a linked group's members,
51
+ and the deletion of a linked group, whichever API makes them, shortly after
52
+ the write. A change only tells which users to look at: each one's values
53
+ are then rewritten from the directory as it is, so changes made close
54
+ together end the same whatever order they are followed in. Values not
55
+ ending in `:viewer`, `:editor` or `:admin` are left alone. A tombstone keeps
56
+ the values it held when it was deleted.
57
+ - With `--rabbitmq-url`, every write of a space and every change of a
58
+ member's role is published as an event, see below.
59
+ - With either of the two, the plugin refuses to start when core/ldap/trash
60
+ watches the group or space branches: a group or space it moves away is
61
+ never followed.
62
+
63
+ ## Events
64
+
65
+ Each event goes to the `--twake-space-exchange` topic exchange with the
66
+ routing key `twake.space.<event>`. Like the user role attribute, events follow
67
+ every write, whichever API makes it, shortly after it.
68
+
69
+ ```json
70
+ {
71
+ "organizationId": "acme",
72
+ "organizationDomain": "acme.com",
73
+ "id": "3b9e2c71-5d4a-4f0e-9c8b-1a2d6e7f8091",
74
+ "members": [
75
+ {
76
+ "uuid": "6f1c0a52-8e3b-4d7f-a9c2-5b0e1d4f7a83",
77
+ "username": "jdoe",
78
+ "email": "jdoe@acme.example.org",
79
+ "firstName": "John",
80
+ "lastName": "Doe",
81
+ "role": "editor"
82
+ }
83
+ ],
84
+ "actor": "jsmith",
85
+ "timestamp": "2026-10-06T09:12:44.512Z"
86
+ }
87
+ ```
88
+
89
+ Every event carries `organizationId`, `id` (the space), `actor` (who made
90
+ the write, when known) and `timestamp`. A member is described as above, its
91
+ `uuid` the user's `entryUUID`; a group is `{ id, name, role }`.
92
+
93
+ `organizationDomain` is the organization's domain, read on its entry
94
+ (`--twake-group-organization-dn`) from
95
+ `--twake-instance-organization-domain-attribute` (default `twakeDomain`), the
96
+ option `twake/instances` reads too. It is kept until the entry is changed
97
+ through this server: another replica, or a write made straight to the
98
+ directory, keeps the old domain until restart. It is left out when
99
+ `--twake-group-organization-dn` is empty or the entry has no such value. The
100
+ server does not start when the directory schema does not define that
101
+ attribute.
102
+
103
+ Writes made close together blur the actor: the member events of two writes
104
+ of one space or group followed together may go out under the first one's.
105
+ With `--twake-space-user-role-attribute`, the follow of a write may find a
106
+ role another write moved in another space, not followed yet: it publishes
107
+ that event with no actor, as the later follow finds nothing left to
108
+ announce.
109
+
110
+ - `created`: `name`, `members` (every user in the space with their resolved
111
+ role, linked groups' members included) and `groups`.
112
+ - `updated`: the changed `name`.
113
+ - `deleted`.
114
+ - `member.added`, `member.role.changed`, `member.removed`: `members`, the one
115
+ user whose resolved role changed, whatever the cause: a member write, a
116
+ group linked, unlinked or given another role, a user joining or leaving a
117
+ linked group. `member.removed` gives the role the user held.
118
+ - `group.linked`, `group.role.changed`, `group.unlinked`: `groups`, the one
119
+ group, with its name when the event is published. A deleted group is
120
+ unlinked from each of its spaces once, whether refint or a later write
121
+ takes its DN out of them; one unlinked, then deleted before the unlink is
122
+ followed, is unlinked too, under its id for a name. Renaming a linked group
123
+ publishes nothing.
124
+
125
+ - `synced`: the whole space, as `created` gives it, published by a sync.
126
+ - `sync.completed`: `spaceIds`, the ids of every space of the organization,
127
+ after its `synced` events. It has no `id`.
128
+
129
+ A user who is deleted or becomes a tombstone publishes no member event.
130
+
131
+ ## Sync
132
+
133
+ An app repairs its copy of the spaces by publishing
134
+ `twake.space.sync.requested` on the exchange:
135
+
136
+ - `{ "organizationId", "id" }`: the space's `synced`, or `deleted` when it
137
+ is gone;
138
+ - `{ "organizationId" }`: a `synced` for each space of the organization,
139
+ then `sync.completed`;
140
+ - `{}`: a `twake.space.sync.requested` with `organizationId` for each
141
+ organization, the entries one level under the parent of `{org}` in
142
+ `--twake-space-base`, published on the exchange, so every organization is
143
+ synced as above.
144
+
145
+ A request reaches every queue bound to its key, `twake.space.#` included.
146
+ An app that follows the events binds `twake.space.*`, `twake.space.member.#`,
147
+ `twake.space.group.#` and `twake.space.sync.completed`, or ignores
148
+ `twake.space.sync.requested`.
149
+
150
+ The plugin consumes these requests from the `--twake-space-sync-queue`
151
+ quorum queue, with a dead letter queue `<queue>.dlq`. Every replica consumes
152
+ it, several requests at once, so the events of two syncs of one organization
153
+ may interleave: their timestamps order them. A request it cannot read goes
154
+ to the dead letter queue. A missing organization is skipped. A deleted one
155
+ has no space left: its `sync.completed` lists none, and a space asked is
156
+ `deleted`. A sync whose events the broker does not take fails, and its
157
+ request is retried, then goes to the dead letter queue.
158
+
159
+ The events of a sync carry no `actor`. They all carry the time just before
160
+ the spaces were read, so, with synchronized clocks, the event of a write
161
+ followed after that is newer.
162
+ An app that applies only events newer than the last one keeps the right
163
+ state, and keeps a space created after a `sync.completed` that does not list
164
+ it.
165
+
166
+ The server does not start when the broker cannot be reached. An event the
167
+ broker cannot take once the server runs is logged with `result: "error"`;
168
+ the event of a write is not sent again.
169
+
170
+ Member events compare each moved user's roles before and after a change:
171
+
172
+ - With `--twake-space-user-role-attribute`, the roles before are the values
173
+ on the user's entry, so changes made close together announce each role
174
+ once, in whatever order they are followed.
175
+ - Without it, the roles before are worked out from the directory with the
176
+ change undone. Two changes followed late may announce a role twice, or
177
+ not at all. Consumers that need an exact copy resync from the routes.
178
+
179
+ ## Routes
180
+
181
+ Every route answers 404 `ORGANIZATION_NOT_FOUND` when the organization entry
182
+ is missing, and 410 `ORGANIZATION_DELETED` when it is deleted. Errors are
183
+ `{ "error": "...", "code": "..." }`. A request an authorization plugin refuses
184
+ answers 403 `REFUSED`. A write answers `{ "success": true }` unless stated
185
+ otherwise.
186
+
187
+ A space reads, strongest role first:
188
+
189
+ ```json
190
+ {
191
+ "id": "3b9e2c71-5d4a-4f0e-9c8b-1a2d6e7f8091",
192
+ "name": "Design Sprint",
193
+ "organizationId": "acme",
194
+ "members": [
195
+ { "username": "jsmith", "role": "admin" },
196
+ { "username": "jdoe", "role": "editor" }
197
+ ],
198
+ "groups": [{ "id": "c2a8e1f0-7b3d-4e9a-8f61-2d5b9c0e4a17", "role": "viewer" }]
199
+ }
200
+ ```
201
+
202
+ `username` is the RDN value of a user, `id` the `cn` of a group.
203
+
204
+ - `GET /spaces?page&limit&search&sortBy&sortOrder&user`: `search` (2
205
+ characters or more) matches the name; `sortBy` is `name`. With `user`, the
206
+ spaces that user is in, directly or through a linked group, each with the
207
+ user's `role` (404 `USER_NOT_FOUND` for an unknown user). Answers
208
+ `{ organizationId, spaces, pagination: { page, limit, total, totalPages } }`.
209
+ - `POST /spaces` with `{ name, members: [{ username, role }], groups?: [{ id, role }] }`:
210
+ `name` follows the [groups](groups.md) rule, `members` holds at least one
211
+ admin (400 `ADMIN_REQUIRED`), every user and group must exist in the
212
+ organization (404 `USER_NOT_FOUND`, `GROUP_NOT_FOUND`). Answers 201 with the
213
+ space.
214
+ - `GET /spaces/:spaceId`: the space, or 404 `SPACE_NOT_FOUND`.
215
+ - `PATCH /spaces/:spaceId` with `{ name }`: renames the space; any other
216
+ field is a 400.
217
+ - `DELETE /spaces/:spaceId`.
218
+ - `GET /spaces/:spaceId/members?page&limit&search&sortBy&sortOrder`: the
219
+ members' public profiles, as [groups](groups.md) answers them, each with
220
+ its `role`; `sortBy` is `uid` (default), `displayName`, `mail`, `jobTitle`
221
+ or `role`.
222
+ - `POST /spaces/:spaceId/members` with `{ usernames: [...], role }`: every
223
+ user must exist in the organization (404 `USER_NOT_FOUND`, nothing added),
224
+ at most `--twake-group-max-page-limit` per request, as for every list of
225
+ users or groups below.
226
+ A member already holding that role is left as is; one holding another role
227
+ answers 409 `MEMBER_EXISTS`, nothing added.
228
+ - `PATCH /spaces/:spaceId/members/:userId` with `{ role }`, and
229
+ `DELETE /spaces/:spaceId/members/:userId`: 404 `MEMBER_NOT_FOUND` for a
230
+ user who is not a member.
231
+ - `GET /spaces/:spaceId/groups`: `{ organizationId, id, groups }`, each group
232
+ `{ id, name, role }`, by name.
233
+ - `POST /spaces/:spaceId/groups` with `{ groupIds: [...], role }`: every group
234
+ must exist in the organization (404 `GROUP_NOT_FOUND`, nothing linked). A
235
+ group already linked with another role answers 409 `GROUP_ALREADY_LINKED`.
236
+ - `PATCH /spaces/:spaceId/groups/:groupId` with `{ role }`, and
237
+ `DELETE /spaces/:spaceId/groups/:groupId`: 404 `GROUP_NOT_FOUND` for a group
238
+ that is not linked.
239
+
240
+ ## Configuration
241
+
242
+ ```bash
243
+ --plugin core/twake/spaces \
244
+ --twake-space-base 'ou=spaces,ou={org},dc=example,dc=com'
245
+ ```
246
+
247
+ The plugin loads [groups](groups.md), and reads its options for the
248
+ organization's users, groups and organization entry.
249
+
250
+ - `--twake-space-base`: required, with `{org}`.
251
+ - `--twake-space-class` (default `top,twakeSpace`): the classes of a space
252
+ created.
253
+ - `--twake-space-display-name-attribute` (default `twakeDisplayName`).
254
+ - `--twake-space-admin-attribute` (default `twakeSpaceAdmin`),
255
+ `--twake-space-editor-attribute` (default `twakeSpaceEditor`),
256
+ `--twake-space-viewer-attribute` (default `twakeSpaceViewer`): the role
257
+ attributes, holding DNs.
258
+ - `--twake-space-user-role-attribute` (no default): the multi-valued user
259
+ attribute holding the user's role in each space, such as `twakeSpaceRole`.
260
+ Unset, no user entry is written.
261
+ - `--twake-space-exchange` (default `space`): the topic exchange of the
262
+ events, published only when `--rabbitmq-url` is set.
263
+ - `--twake-space-sync-queue` (default
264
+ `twake.space.sync.requested.ldap-rest`): the queue of the sync requests,
265
+ consumed only when `--rabbitmq-url` is set.
266
+
267
+ ## Dependencies
268
+
269
+ ```
270
+ core/twake/spaces
271
+ ├─ requires: core/twake/groups
272
+ ├─ requires: core/ldap/onChange
273
+ └─ requires: core/rabbitmq (with RabbitMQ)
274
+ ```
@@ -17,7 +17,7 @@ The companion browser library is [`ldap-browser`](../../../client-development/br
17
17
 
18
18
  ### Environment Variables
19
19
 
20
- - `DM_LDAP_RAW_BASE`: comma-separated subtrees to expose (default: `--ldap-base`)
20
+ - `DM_LDAP_RAW_BASE`: subtrees to expose, separated by `;` (default: `--ldap-base`)
21
21
  - `DM_LDAP_RAW_HIDDEN_ATTRIBUTES`: comma-separated attributes never returned, on top of the credential ones (default: none)
22
22
  - `DM_LDAP_RAW_SHOW_SECRETS`: serve credential attributes instead of hiding them (default: `false`)
23
23
  - `DM_LDAP_RAW_MAX_RESULTS`: maximum entries returned by a search or a children listing (default: `200`)
@@ -4,6 +4,115 @@ 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.16.0
8
+
9
+ ### Space events need the organization domain attribute
10
+
11
+ **Who is affected:** a deployment of `core/twake/spaces` with `--rabbitmq-url`
12
+ and `--twake-group-organization-dn`.
13
+
14
+ The space events now carry the organization's domain, read from
15
+ `--twake-instance-organization-domain-attribute` (default `twakeDomain`). The
16
+ server does not start when the directory schema does not define that
17
+ attribute: load a schema that does, or point the option at one it defines.
18
+
19
+ ## To 0.15.0
20
+
21
+ ### A DN option read from the environment splits on `;` only
22
+
23
+ **Who is affected:** a deployment that sets `DM_LDAP_RAW_BASE`,
24
+ `DM_JAMES_MAILING_LIST_BRANCHES`, `DM_TWAKE_TOMBSTONE_DN`,
25
+ `DM_TWAKE_TOMBSTONE_GROUP_BASES` or `DM_TWAKE_INSTANCE_DN`, or gives
26
+ `--ldap-raw-bases` or `--james-mailing-list-branches`.
27
+
28
+ Their values are no longer cut on spaces or commas: a DN such as
29
+ `DM_LDAP_RAW_BASE="ou=My Unit,dc=example,dc=com"` stays whole. Separate
30
+ several DNs with `;`, as already documented.
31
+
32
+ ### HMAC and TOTP entries are no longer split on spaces
33
+
34
+ **Who is affected:** a deployment that sets `DM_AUTH_HMAC` or `DM_AUTH_TOTP`,
35
+ or gives `--auth-hmacs` or `--auth-totps`.
36
+
37
+ Separate entries with `,`, `;` or newlines. A name may now hold spaces, as in
38
+ `id:secret:Registration Service`. A list separated by spaces is read as one
39
+ entry: an HMAC list gives one service, the others cannot authenticate (their
40
+ requests get 401, and a warning is logged), and a TOTP list stops the server.
41
+
42
+ `core/auth/hmac` and `core/auth/totp` also stop the server on an entry that
43
+ cannot work, instead of skipping it with a warning: an HMAC entry with an
44
+ empty id, secret or name or fewer than 3 fields, a TOTP entry with an empty
45
+ secret or name, more than 3 fields, a secret that is not Base32 or digits that
46
+ are not an integer from 6 to 10. A TOTP entry such as `secret:name:abc` used
47
+ to register a user whose code was the string `NaN`.
48
+
49
+ ### The plural command-line form of an array option splits on `;` first
50
+
51
+ **Who is affected:** a deployment that gives a plural form such as
52
+ `--auth-tokens`, `--ldap-urls` or `--plugins`.
53
+
54
+ A value containing `;` is now split on `;` and not on `,`, as the environment
55
+ variable does: `--auth-tokens 'a;b;c'` was one token and is now three, and
56
+ `--auth-tokens 'a;b,c'` was `a;b` and `c` and is now `a` and `b,c`. A value
57
+ without `;` is split as before.
58
+
59
+ `--authz-dynamic-bypass` and `--twake-space-class` now split on commas in their
60
+ singular form, as the other options holding identifiers do.
61
+
62
+ ### An empty environment variable or a number that is not one
63
+
64
+ **Who is affected:** a deployment that sets an array option, a number or a JSON
65
+ option to an empty value, or a number option to something else than an integer.
66
+
67
+ - An array variable that is empty, only holds whitespace or only holds
68
+ separators (`DM_GROUP_CLASSES=`, `DM_GROUP_CLASSES=,`) keeps the default
69
+ instead of giving an empty list, which was a silent misconfiguration (a
70
+ server without object class, for instance). `DM_LDAP_OPERATIONAL_ATTRIBUTES=`
71
+ used to give `[]` and now gives the default; an option with a non-empty
72
+ default cannot be given an empty list from the environment.
73
+ - `DM_LDAP_URL=` is the exception: its default, `ldap://localhost`, is a guess,
74
+ not a fallback, so an empty value still stops the server. Set the variable
75
+ or leave it out.
76
+ - An empty number or JSON value (`DM_PORT=`) keeps its default instead of
77
+ giving `NaN` or stopping the server.
78
+ - A number option takes an integer only: `DM_PORT=abc`, `DM_PORT=1e3`,
79
+ `DM_PORT=8080abc`, `--port ''` or `--port --log-level debug` stop the server
80
+ instead of giving `NaN` or a truncated value.
81
+
82
+ Booleans and strings are unchanged.
83
+
84
+ ### An array option on the command line replaces its default
85
+
86
+ **Who is affected:** a deployment that gives, on the command line, an array
87
+ option that has a default: `--user-class`, `--group-class`,
88
+ `--ldap-organization-class`, `--external-branch-class`,
89
+ `--scim-user-object-class`, `--scim-group-object-class`,
90
+ `--applicative-account-attribute`, `--ldap-operational-attribute` or
91
+ `--ldap-url`, in their singular or plural form.
92
+
93
+ Their values used to be added to the default, and now replace it. Repeat the
94
+ default values you want to keep:
95
+
96
+ ```bash
97
+ # Until now: top, twakeAccount, twakeWhitePages, twakeExtra
98
+ ldap-rest --user-class twakeExtra
99
+
100
+ # Now
101
+ ldap-rest --user-class top,twakeAccount,twakeWhitePages,twakeExtra
102
+ ```
103
+
104
+ Without its default classes, an entry is created without its structural
105
+ class. Without the default operational attributes, `appAccountsConsistency`
106
+ recreates an applicative account with `userPassword` and the attributes the
107
+ directory generates. The defaults are listed in
108
+ [the configuration reference](configuration.md).
109
+
110
+ `--ldap-url ldap://ldap.example.com` no longer keeps `ldap://localhost` next
111
+ to it.
112
+
113
+ An environment variable already replaced the default, and values given on the
114
+ command line are still added to it.
115
+
7
116
  ## To 0.14.0
8
117
 
9
118
  ### `--twake-instance-organization-account` is removed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ldap-rest",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Light directory manager",
5
5
  "keywords": [
6
6
  "Identity-Manager"
@@ -288,6 +288,10 @@
288
288
  "types": "./dist/src/plugins/twake/lifecycleEvents.d.ts",
289
289
  "import": "./dist/plugins/twake/lifecycleEvents.js"
290
290
  },
291
+ "./plugin-twake-spaces": {
292
+ "types": "./dist/src/plugins/twake/spaces.d.ts",
293
+ "import": "./dist/plugins/twake/spaces.js"
294
+ },
291
295
  "./plugin-twake-tombstone": {
292
296
  "types": "./dist/src/plugins/twake/tombstone.d.ts",
293
297
  "import": "./dist/plugins/twake/tombstone.js"
@@ -733,6 +737,7 @@
733
737
  "fast-xml-parser": ">=5.3.6",
734
738
  "glob": ">=11.0.4",
735
739
  "minimatch": "^10.2.1",
740
+ "postcss-selector-parser": "^7.1.6",
736
741
  "serialize-javascript": ">=7.0.3"
737
742
  },
738
743
  "dependencies": {
@@ -770,7 +775,6 @@
770
775
  "eslint-plugin-import": "^2.32.0",
771
776
  "eslint-plugin-node": "^11.1.0",
772
777
  "eslint-plugin-prettier": "^5.5.4",
773
- "fast-glob": "^3.3.3",
774
778
  "globals": "^14.0.0",
775
779
  "js-yaml": "^4.1.1",
776
780
  "mocha": "^12.0.0",
@@ -781,6 +785,7 @@
781
785
  "rollup-plugin-postcss": "^4.0.2",
782
786
  "sort-package-json": "^3.4.0",
783
787
  "supertest": "^7.1.4",
788
+ "tinyglobby": "^0.2.17",
784
789
  "tslib": "^2.8.1",
785
790
  "tsx": "^4.20.5",
786
791
  "typescript": "^5.9.2"