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.
- package/dist/bin/index.js +1 -1
- package/dist/config/args.js +1 -1
- package/dist/lib/errors.js +1 -1
- package/dist/lib/expressFormatedResponses.js +1 -1
- package/dist/lib/ldapActions.js +1 -1
- package/dist/lib/parseConfig.js +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/plugins/auth/hmac.js +1 -1
- package/dist/plugins/auth/totp.js +1 -1
- package/dist/plugins/ldap/organizations.js +1 -1
- package/dist/plugins/scim/errors.js +1 -1
- package/dist/plugins/twake/groups.js +1 -1
- package/dist/plugins/twake/lifecycleEvents.js +1 -1
- package/dist/plugins/twake/spaces.js +1 -0
- package/dist/plugins/twake/tombstone.js +1 -1
- package/dist/src/config/args.d.ts +9 -0
- package/dist/src/lib/errors.d.ts +5 -0
- package/dist/src/lib/ldapActions.d.ts +1 -1
- package/dist/src/lib/parseConfig.d.ts +4 -1
- package/dist/src/lib/utils.d.ts +6 -0
- package/dist/src/plugins/twake/groups.d.ts +73 -13
- package/dist/src/plugins/twake/spaces.d.ts +212 -0
- package/dist/src/plugins/twake/tombstone.d.ts +5 -0
- package/docs/usage/configuration.md +85 -3
- package/docs/usage/plugins/integrations/README.md +6 -0
- package/docs/usage/plugins/integrations/groups.md +12 -4
- package/docs/usage/plugins/integrations/lifecycle-events.md +3 -1
- package/docs/usage/plugins/integrations/spaces.md +274 -0
- package/docs/usage/plugins/ldap/raw.md +1 -1
- package/docs/usage/upgrading.md +109 -0
- package/package.json +7 -2
|
@@ -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
|
|
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
|
-
|
|
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)
|
|
16
|
-
|
|
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
|
|
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`:
|
|
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`)
|
package/docs/usage/upgrading.md
CHANGED
|
@@ -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.
|
|
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"
|