ldap-rest 0.6.2 → 0.8.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/abstract/ldapFlat.js +1 -1
- package/dist/abstract/plugin.js +1 -1
- package/dist/bin/index.js +1 -1
- package/dist/config/args.js +1 -1
- package/dist/config/schema.js +1 -0
- package/dist/lib/expressFormatedResponses.js +1 -1
- package/dist/lib/ldapActions.js +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/plugins/auth/authzScope.js +1 -0
- package/dist/plugins/auth/llng.js +1 -1
- package/dist/plugins/ldap/accountLifecycle.js +1 -0
- package/dist/plugins/ldap/bulkImport.js +1 -1
- package/dist/plugins/ldap/departmentSync.js +1 -1
- package/dist/plugins/ldap/enterpriseRules.js +1 -0
- package/dist/plugins/ldap/externalUsersInGroups.js +1 -1
- package/dist/plugins/ldap/flatGeneric.js +1 -1
- package/dist/plugins/ldap/groups.js +1 -1
- package/dist/plugins/ldap/organizations.js +1 -1
- package/dist/plugins/scim/bulk.js +1 -1
- package/dist/plugins/scim/errors.js +1 -1
- package/dist/plugins/scim/filter.js +1 -1
- package/dist/plugins/scim/groups.js +1 -1
- package/dist/plugins/scim/list.js +1 -1
- package/dist/plugins/scim/mapping.js +1 -1
- package/dist/plugins/scim/patch.js +1 -1
- package/dist/plugins/scim/projection.js +1 -0
- package/dist/plugins/scim/scim.js +1 -1
- package/dist/plugins/scim/users.js +1 -1
- package/dist/plugins/twake/calendar.js +1 -0
- package/dist/plugins/twake/calendarResources.js +1 -1
- package/dist/plugins/twake/james.js +1 -1
- package/dist/src/abstract/ldapFlat.d.ts +159 -1
- package/dist/src/abstract/plugin.d.ts +13 -0
- package/dist/src/config/args.d.ts +5 -0
- package/dist/src/config/schema.d.ts +382 -12
- package/dist/src/lib/expressFormatedResponses.d.ts +14 -0
- package/dist/src/lib/ldapActions.d.ts +59 -6
- package/dist/src/lib/utils.d.ts +65 -0
- package/dist/src/plugins/auth/authzScope.d.ts +53 -0
- package/dist/src/plugins/auth/llng.d.ts +30 -1
- package/dist/src/plugins/ldap/accountLifecycle.d.ts +56 -0
- package/dist/src/plugins/ldap/departmentSync.d.ts +43 -0
- package/dist/src/plugins/ldap/enterpriseRules.d.ts +436 -0
- package/dist/src/plugins/ldap/flatGeneric.d.ts +29 -0
- package/dist/src/plugins/ldap/groups.d.ts +22 -0
- package/dist/src/plugins/ldap/organizations.d.ts +25 -4
- package/dist/src/plugins/scim/errors.d.ts +17 -4
- package/dist/src/plugins/scim/filter.d.ts +11 -1
- package/dist/src/plugins/scim/groups.d.ts +2 -0
- package/dist/src/plugins/scim/list.d.ts +3 -5
- package/dist/src/plugins/scim/mapping.d.ts +90 -9
- package/dist/src/plugins/scim/patch.d.ts +16 -1
- package/dist/src/plugins/scim/projection.d.ts +15 -0
- package/dist/src/plugins/scim/scim.d.ts +5 -0
- package/dist/src/plugins/scim/users.d.ts +21 -0
- package/dist/src/plugins/twake/calendar.d.ts +113 -0
- package/dist/src/plugins/twake/calendarResources.d.ts +9 -58
- package/docs/README.md +2 -0
- package/docs/client-development/api/openapi.md +1 -1
- package/docs/client-development/browser/libraries.md +1 -1
- package/docs/plugin-development/README.md +22 -0
- package/docs/plugin-development/dependencies.md +10 -7
- package/docs/presentations/en.md +1 -1
- package/docs/presentations/fr.md +1 -1
- package/docs/usage/README.md +2 -0
- package/docs/usage/configuration.md +35 -15
- package/docs/usage/directory-audit.md +105 -0
- package/docs/usage/plugins/README.md +6 -6
- package/docs/usage/plugins/auth/README.md +6 -5
- package/docs/usage/plugins/auth/authz-scope.md +103 -0
- package/docs/usage/plugins/auth/llng.md +17 -0
- package/docs/usage/plugins/integrations/README.md +5 -5
- package/docs/usage/plugins/integrations/{calendar-resources.md → calendar.md} +70 -13
- package/docs/usage/plugins/integrations/scim.md +147 -38
- package/docs/usage/plugins/ldap/README.md +13 -11
- package/docs/usage/plugins/ldap/account-lifecycle.md +129 -0
- package/docs/usage/plugins/ldap/bulk-import.md +8 -1
- package/docs/usage/plugins/ldap/enterprise-rules.md +167 -0
- package/docs/usage/plugins/ldap/flat-generic.md +172 -0
- package/docs/usage/plugins/ldap/groups.md +10 -4
- package/docs/usage/plugins/ldap/organizations.md +59 -33
- package/docs/usage/upgrading.md +444 -0
- package/package.json +54 -5
- package/static/schemas/example/README.md +36 -0
- package/static/schemas/example/groups.json +94 -0
- package/static/schemas/example/nomenclature/domains.json +49 -0
- package/static/schemas/example/organizations.json +106 -0
- package/static/schemas/example/positions.json +35 -0
- package/static/schemas/example/users.json +246 -0
- package/static/schemas/twake/groups.json +81 -15
- package/static/schemas/twake/nomenclature/twakeAccountStatus.json +26 -0
- package/static/schemas/twake/nomenclature/twakeDeliveryMode.json +22 -0
- package/static/schemas/twake/nomenclature/twakeDomain.json +60 -0
- package/static/schemas/twake/nomenclature/twakeListType.json +22 -0
- package/static/schemas/twake/nomenclature/twakeMailboxType.json +22 -0
- package/static/schemas/twake/nomenclature/twakeTitle.json +42 -0
- package/static/schemas/twake/organizations.json +92 -12
- package/static/schemas/twake/positions.json +22 -4
- package/static/schemas/twake/users.json +269 -32
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# Upgrading
|
|
2
|
+
|
|
3
|
+
What to check before deploying, newest first. Only releases that need a
|
|
4
|
+
decision or a configuration change appear here; see the
|
|
5
|
+
[CHANGELOG](../../CHANGELOG.md) for everything else.
|
|
6
|
+
|
|
7
|
+
## To 0.8.0
|
|
8
|
+
|
|
9
|
+
### Node 20 is the floor
|
|
10
|
+
|
|
11
|
+
**Who is affected:** anyone installing on Node 18 or older.
|
|
12
|
+
|
|
13
|
+
`engines` declares `>=20` — the version Debian 13 ships — and the CI runs the
|
|
14
|
+
suite on 20, 22, 24 and 26. Older runtimes are neither tested nor supported.
|
|
15
|
+
|
|
16
|
+
**`core/auth/llng` is the one plugin that does not follow.** It needs
|
|
17
|
+
`lemonldap-ng-handler`, which depends on the native `re2`, and no single `re2`
|
|
18
|
+
release installs on every supported Node: up to `1.24.0` it builds on 20 but
|
|
19
|
+
not on 26, and from `1.24.1` it declares `engines: >=22`. npm answers an
|
|
20
|
+
unsatisfied dependency of this kind by leaving an _optional_ package out
|
|
21
|
+
without a word, so on some runtimes the handler is simply absent.
|
|
22
|
+
|
|
23
|
+
Everything else builds, tests and runs there regardless. A server that
|
|
24
|
+
configures `core/auth/llng` on a runtime where the handler could not be
|
|
25
|
+
installed now fails at startup, naming the plugin and the missing package,
|
|
26
|
+
rather than accepting requests an authentication plugin cannot check. Check
|
|
27
|
+
after upgrading that the runtime you deploy on carries it:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
node -e "require('lemonldap-ng-handler'); console.log('present')"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### The LLNG handler now needs a working configuration at startup
|
|
34
|
+
|
|
35
|
+
**Who is affected:** every deployment configuring `core/auth/llng`.
|
|
36
|
+
|
|
37
|
+
The handler used to do nothing at startup: `--llng-ini` was read into the
|
|
38
|
+
configuration but never handed to it, so every request failed with a `500`
|
|
39
|
+
regardless of what the file said. It is now initialized once, before the
|
|
40
|
+
server starts serving — which means the file finally matters, and two ways
|
|
41
|
+
it can be wrong now stop the server instead of answering `500`:
|
|
42
|
+
|
|
43
|
+
- `--llng-ini` defaults to `/etc/lemonldap-ng/lemonldap-ng.ini`, and a file at
|
|
44
|
+
that path with no `[node-handler] nodeVhosts` listing this server — the
|
|
45
|
+
normal state until now, since nothing read it — refuses to start where it
|
|
46
|
+
used to start and answer `500` to every request instead;
|
|
47
|
+
- an LLNG configuration store (`[configuration] baseConfigUrl` and the rest)
|
|
48
|
+
unreachable when the server boots crash-loops it under an orchestrator,
|
|
49
|
+
until the portal or config store it depends on comes up.
|
|
50
|
+
|
|
51
|
+
List this server before upgrading:
|
|
52
|
+
|
|
53
|
+
```ini
|
|
54
|
+
[node-handler]
|
|
55
|
+
nodeVhosts = api.example.com
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
and, if the LLNG configuration store starts after this server does, sequence
|
|
59
|
+
the two or give the container a restart policy that tolerates a few
|
|
60
|
+
failures at boot.
|
|
61
|
+
|
|
62
|
+
### The Twake schemas now need `core/ldap/enterpriseRules`
|
|
63
|
+
|
|
64
|
+
**Who is affected:** every deployment loading `static/schemas/twake/*`.
|
|
65
|
+
|
|
66
|
+
Those schemas mark `twakeDepartmentPath`, `twakeAccountStatus` and
|
|
67
|
+
`twakeDeliveryMode` both `required` and `generated` — a client may not send
|
|
68
|
+
them, and a plugin has to fill them. Load the one that does:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
--plugin core/ldap/enterpriseRules
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Without it, a creation answers `400` naming the attribute, on the flat routes
|
|
75
|
+
as on `POST /ldap/organizations` and `POST /ldap/groups`. That refusal is
|
|
76
|
+
deliberate: the previous release wrote the entry anyway, missing an attribute
|
|
77
|
+
its own schema called required and that no client could ever add.
|
|
78
|
+
|
|
79
|
+
**The nomenclature has to hold what the schema defaults name.** The user
|
|
80
|
+
schema points at `cn=normal,ou=twakeDeliveryMode,ou=nomenclature,<base>` and
|
|
81
|
+
`cn=active,ou=twakeAccountStatus,ou=nomenclature,<base>`. A directory never
|
|
82
|
+
seeded with those entries answers `400` on every creation, naming the DN that
|
|
83
|
+
does not resolve — rather than storing a dangling one on each account.
|
|
84
|
+
|
|
85
|
+
### Clients must stop sending the computed attributes
|
|
86
|
+
|
|
87
|
+
**Who is affected:** anything that creates or updates users, groups or
|
|
88
|
+
organizations against the Twake schemas.
|
|
89
|
+
|
|
90
|
+
`uid` (generated from the local part of `mail`), `twakeDepartmentPath`,
|
|
91
|
+
`twakeAccountStatus` and `twakeDeliveryMode` are refused in a request body
|
|
92
|
+
with a `400` naming the attribute. Drop them from the payload — the server
|
|
93
|
+
fills them.
|
|
94
|
+
|
|
95
|
+
To keep the old behaviour, copy the schema and remove the markers: each of
|
|
96
|
+
these is a `generated` or `readOnly` flag in the JSON, not code.
|
|
97
|
+
|
|
98
|
+
### Run the audit before switching
|
|
99
|
+
|
|
100
|
+
**Who is affected:** every directory holding entries written before this
|
|
101
|
+
release.
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npm run audit:directory -- --schema static/schemas/twake/users.json \
|
|
105
|
+
--plugin core/ldap/enterpriseRules
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
It reads the branch as it stands and reports what the schema would now refuse,
|
|
109
|
+
quoting each rule's own `hint`. Two rules tightened in ways that only show on
|
|
110
|
+
stored data:
|
|
111
|
+
|
|
112
|
+
- an array's `items.test` and `items.branch` are enforced on the flat,
|
|
113
|
+
organization and group routes, where they never were —
|
|
114
|
+
`mailAlternateAddress` has carried a pattern since 0.7.0 and accepted
|
|
115
|
+
anything — and an array of pointers must name existing entries;
|
|
116
|
+
- a pointer's `branch` is compared RDN by RDN. A DN that merely ended with the
|
|
117
|
+
branch as text, `uid=x,xou=users,dc=example,dc=com` against
|
|
118
|
+
`ou=users,dc=example,dc=com`, used to pass.
|
|
119
|
+
|
|
120
|
+
Stored values are left alone. The refusal comes at the next update of an
|
|
121
|
+
offending entry, which is why it is worth knowing beforehand.
|
|
122
|
+
|
|
123
|
+
### `employeeNumber` is unique, apart from the `UNIT` placeholder
|
|
124
|
+
|
|
125
|
+
**Who is affected:** directories where accounts share a placeholder employee
|
|
126
|
+
number other than `UNIT`.
|
|
127
|
+
|
|
128
|
+
`static/schemas/twake/users.json` declares `employeeNumber` unique, exempting
|
|
129
|
+
the value `UNIT` — the placeholder the interface these schemas replace uses
|
|
130
|
+
for an account standing for a unit rather than a person. Once
|
|
131
|
+
`core/ldap/enterpriseRules` is loaded, every creation or update carrying a
|
|
132
|
+
value another account already holds answers `409`; `UNIT` is let through
|
|
133
|
+
however many accounts carry it.
|
|
134
|
+
|
|
135
|
+
A deployment whose placeholder is spelled otherwise has to say so, or its
|
|
136
|
+
second holder of that value is refused, and every existing holder at its next
|
|
137
|
+
update of the attribute. The audit cannot warn: uniqueness spans the whole
|
|
138
|
+
directory, and it only checks what each entry says on its own.
|
|
139
|
+
|
|
140
|
+
Find the shared values before switching:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
ldapsearch -LLL -b "ou=users,<base>" "(employeeNumber=*)" employeeNumber \
|
|
144
|
+
| awk '/^employeeNumber:/ {print $2}' | sort | uniq -cd
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Then name yours in a copy of the schema, as
|
|
148
|
+
`static/schemas/example/users.json` does:
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
"unique": { "sentinel": "YOUR-PLACEHOLDER" }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Calendar resource ids, and the branch they are looked for in
|
|
155
|
+
|
|
156
|
+
**Who is affected:** deployments loading `core/twake/calendar` (the plugin
|
|
157
|
+
`calendarResources` was renamed to).
|
|
158
|
+
|
|
159
|
+
Three things changed in how an LDAP entry is matched to a Calendar resource.
|
|
160
|
+
A resource whose DN is `cn=…` or `uid=…` directly under the configured base,
|
|
161
|
+
with no escape in its value, keeps the identifier it had and needs nothing.
|
|
162
|
+
|
|
163
|
+
**`--calendar-resource-base` must be a full DN.** The branch was tested by
|
|
164
|
+
asking whether the DN _contained_ the configured value as text, so a partial
|
|
165
|
+
value like `ou=resources` matched — and matched a sibling branch such as
|
|
166
|
+
`ou=resourcesArchive` with it. It is compared by DN components now, which
|
|
167
|
+
also fixes the other direction: a DN written with spaces after its commas was
|
|
168
|
+
not recognised as a resource at all. Give the option the whole DN; the plugin
|
|
169
|
+
warns at startup when the value is not one under `--ldap-base`.
|
|
170
|
+
|
|
171
|
+
**The identifier is read from the entry's own RDN.** It used to come from an
|
|
172
|
+
unanchored search for `cn=` or `uid=` anywhere in the DN, so an entry whose
|
|
173
|
+
own RDN was neither borrowed the one of an ancestor; failing that, creation
|
|
174
|
+
fell back to a slug of the entry's name, under which modification and
|
|
175
|
+
deletion then found nothing. An RDN value carrying an escape — `cn=Salle\, 2`
|
|
176
|
+
— was cut at the escape.
|
|
177
|
+
|
|
178
|
+
So an entry of either of those shapes is now created, patched and deleted in
|
|
179
|
+
Calendar under a different identifier than before. **The resource Calendar
|
|
180
|
+
already holds under the old one is not migrated**: rename it there, or delete
|
|
181
|
+
it and let the next write recreate it. List what is affected before
|
|
182
|
+
upgrading — every resource whose RDN attribute is neither `cn` nor `uid`, and
|
|
183
|
+
every one whose RDN value contains a `\`.
|
|
184
|
+
|
|
185
|
+
**A modification or a deletion outside the base no longer reaches Calendar.**
|
|
186
|
+
Only creation checked the branch; the other two acted on any entry of the
|
|
187
|
+
entity whose DN yielded an identifier.
|
|
188
|
+
|
|
189
|
+
### Renaming an entry, and what the cascade reaches
|
|
190
|
+
|
|
191
|
+
**Who is affected:** anyone calling the new
|
|
192
|
+
`POST /v1/ldap/{resource}/{id}/rename`. Nothing changes for a deployment
|
|
193
|
+
that does not.
|
|
194
|
+
|
|
195
|
+
The identifier of a flat entry can be changed. The entry's DN changes with
|
|
196
|
+
it, so everything naming that DN has to be rewritten, and the endpoint waits
|
|
197
|
+
for that before it answers.
|
|
198
|
+
|
|
199
|
+
What it rewrites is read from the schemas, never from a list of attribute
|
|
200
|
+
names: every `pointer` — single or in an array — whose `branch` admits the
|
|
201
|
+
renamed entry, and every attribute carrying the `members` or `owners` role.
|
|
202
|
+
**Load `core/ldap/enterpriseRules`**: without it the entry is renamed and
|
|
203
|
+
nothing else is touched.
|
|
204
|
+
|
|
205
|
+
What it cannot reach is a DN held in a plain `string` attribute that carries
|
|
206
|
+
no role. Declare it a `pointer`, or give it its role, and it is covered.
|
|
207
|
+
|
|
208
|
+
A directory running OpenLDAP's `refint` overlay already fixes the attributes
|
|
209
|
+
it is configured for — usually `member`, `owner`, `uniqueMember` and
|
|
210
|
+
`memberOf` — in a task of its own, after the rename has answered. The server
|
|
211
|
+
converges with it rather than fighting it: what the overlay has already
|
|
212
|
+
fixed counts as done. `refint` is not a substitute, though. It never sees a
|
|
213
|
+
deployment's own pointers, `twakeManagerLink` and `twakeLocalAdminLink`
|
|
214
|
+
among them — and losing the second one silently costs an administrator every
|
|
215
|
+
branch they administer.
|
|
216
|
+
|
|
217
|
+
### A rename answers `207` when it could not finish
|
|
218
|
+
|
|
219
|
+
The rename of the entry and the rewrite of what points at it are separate
|
|
220
|
+
writes, and a directory has no transaction to hold them together.
|
|
221
|
+
|
|
222
|
+
If the rename itself fails, nothing else has happened and the directory's own
|
|
223
|
+
refusal is what you get. Once it succeeds the rename is a fact, and an error
|
|
224
|
+
would tell you the opposite — so a rewrite that fails answers `207` with the
|
|
225
|
+
attributes and the counts that could not be written. The referring DNs are
|
|
226
|
+
not in the body, where the caller has no business reading them; they are in
|
|
227
|
+
the log, at `error`, with the attribute and both DNs.
|
|
228
|
+
|
|
229
|
+
**Re-issue the identical request to finish it.** A rename whose source is
|
|
230
|
+
already gone and whose target is already there runs the rewrite alone. That
|
|
231
|
+
is also what to do if the server dies between the two writes.
|
|
232
|
+
|
|
233
|
+
### `unique` is not widened on a rename
|
|
234
|
+
|
|
235
|
+
A schema marking its identifier `unique: { "branches": [...] }` gets the
|
|
236
|
+
RDN's own branch checked on a rename, and nothing more: the wider check runs
|
|
237
|
+
from the add and modify hooks, which a rename does not go through. The
|
|
238
|
+
endpoint's own description says so too.
|
|
239
|
+
|
|
240
|
+
### Creating an entry that already exists answers `409`
|
|
241
|
+
|
|
242
|
+
**Who is affected:** any client that reads a failed creation by its status
|
|
243
|
+
code or by the text of its message — a bulk import recording per-line errors
|
|
244
|
+
most of all.
|
|
245
|
+
|
|
246
|
+
Two creations of the same entry racing past the existence checks answered
|
|
247
|
+
`500`, the add path having wrapped every LDAP error in a plain `Error`. The
|
|
248
|
+
directory's own `entryAlreadyExists` is now recognised once, in
|
|
249
|
+
`lib/ldapActions`, so every creation route answers `409` alike: the flat
|
|
250
|
+
routes, `POST /ldap/groups`, the organization routes, and the external
|
|
251
|
+
members `core/ldap/externalUsersInGroups` inserts.
|
|
252
|
+
|
|
253
|
+
The message is `Entry <dn> already exists`. The flat routes used to say
|
|
254
|
+
`<entity> <dn> already exists`, and `core/ldap/bulkImport` used to record
|
|
255
|
+
`LDAP add error: …` in its per-line `errors[]`; both read the same now.
|
|
256
|
+
SCIM still answers `uniqueness`, the numeric code being carried on the
|
|
257
|
+
error it is given.
|
|
258
|
+
|
|
259
|
+
### A flat schema may not claim a URL an LDAP plugin serves
|
|
260
|
+
|
|
261
|
+
**Who is affected:** anyone passing `--ldap-flat-schema` a schema whose
|
|
262
|
+
`entity.pluralName` is `groups`, `organizations`, `raw` or `bulk-import`
|
|
263
|
+
beside the plugin of the same name — `static/schemas/twake/groups.json` is
|
|
264
|
+
exactly that.
|
|
265
|
+
|
|
266
|
+
Both used to load, sharing a hook prefix and a URL; Express answered with
|
|
267
|
+
whichever registered first, and the loser stayed advertised by the
|
|
268
|
+
configuration API while being unreachable in fact. The schema is now dropped
|
|
269
|
+
with an error naming the holder. **Load one or the other, not both.**
|
|
270
|
+
|
|
271
|
+
### Organization paths: nothing to convert
|
|
272
|
+
|
|
273
|
+
**Who is affected:** directories written before the path order was settled.
|
|
274
|
+
|
|
275
|
+
`twakeDepartmentPath` reads from the root down, the entry's own name last.
|
|
276
|
+
What is already stored in the old order — the entry's own name first, the top
|
|
277
|
+
organization's last — is still accepted as it stands, so no organization
|
|
278
|
+
becomes unwritable on upgrade. Only paths the server computes from now on
|
|
279
|
+
follow the new order, and the two forms coexist until an entry is rewritten.
|
|
280
|
+
|
|
281
|
+
### The search cache works now, and is off by default
|
|
282
|
+
|
|
283
|
+
**Who is affected:** deployments that set `--ldap-cache-ttl` (or
|
|
284
|
+
`DM_LDAP_CACHE_TTL`) explicitly. Everyone else keeps exactly the behaviour
|
|
285
|
+
they had.
|
|
286
|
+
|
|
287
|
+
The base-scope search cache stored nothing since it was written: the branch
|
|
288
|
+
that would have kept a result could not be reached, so every read went to the
|
|
289
|
+
directory whatever the option said. It caches now — which means a deployment
|
|
290
|
+
that had set the option gets a working cache for the first time, where it used
|
|
291
|
+
to get none.
|
|
292
|
+
|
|
293
|
+
What a write through this service drops is what it changed, its subtree
|
|
294
|
+
included, whatever case the DN is written in. What this service cannot know
|
|
295
|
+
about, it cannot drop: another replica of it, an LSC synchronisation, a
|
|
296
|
+
hand-run `ldapmodify`. A cached read stays as it was until its TTL runs out.
|
|
297
|
+
|
|
298
|
+
So `--ldap-cache-ttl` now defaults to `0`, which means no caching at all. Set
|
|
299
|
+
it only where this service is the sole writer of the entries it reads, and
|
|
300
|
+
keep the value under the staleness you are willing to serve.
|
|
301
|
+
|
|
302
|
+
### `calendarResources` is renamed `calendar`
|
|
303
|
+
|
|
304
|
+
**Who is affected:** deployments loading `core/twake/calendarResources`, and
|
|
305
|
+
plugins importing `ldap-rest/plugin-twake-calendarresources` or looking the
|
|
306
|
+
plugin up by name.
|
|
307
|
+
|
|
308
|
+
Besides calendar resources, the plugin propagates user email and name changes
|
|
309
|
+
to the Twake Calendar registered users; the old name hid that. Nothing breaks
|
|
310
|
+
yet: `core/twake/calendarResources` still loads the same plugin, registered
|
|
311
|
+
under its old name, and logs a deprecation warning. It will be removed in a
|
|
312
|
+
future major release, so switch now by **replacing** the old name with the new
|
|
313
|
+
one:
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# before
|
|
317
|
+
DM_PLUGINS="…,core/twake/calendarResources"
|
|
318
|
+
# after
|
|
319
|
+
DM_PLUGINS="…,core/twake/calendar"
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Do not list both: they register under different names, so the loader does not
|
|
323
|
+
see a duplicate and runs two instances — every resource change and every user
|
|
324
|
+
email or name change is then sent to Calendar twice.
|
|
325
|
+
|
|
326
|
+
Plugin code should import `ldap-rest/plugin-twake-calendar` and look the
|
|
327
|
+
plugin up as `calendar`. Configuration flags (`--calendar-*`) are unchanged.
|
|
328
|
+
|
|
329
|
+
## To 0.7.0
|
|
330
|
+
|
|
331
|
+
### `externalId` on Groups is no longer served
|
|
332
|
+
|
|
333
|
+
**Who is affected:** anyone provisioning Groups from an identity provider and
|
|
334
|
+
reading `externalId` back, or filtering on it.
|
|
335
|
+
|
|
336
|
+
A Group's `externalId` used to answer its `entryUUID`. RFC 7643 section 3.1
|
|
337
|
+
makes `externalId` the _provisioning client's_ identifier, so the id Okta or
|
|
338
|
+
Entra ID sent was discarded on write, and `filter=externalId eq "<their id>"`
|
|
339
|
+
searched `entryUUID` and matched nothing.
|
|
340
|
+
|
|
341
|
+
Name an attribute to store it in:
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
--scim-group-external-id-attribute description
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Left unset, `externalId` is simply not supported on Groups — absent from
|
|
348
|
+
answers, and refused in filters. That is the honest behaviour; the previous
|
|
349
|
+
one looked like support and was not.
|
|
350
|
+
|
|
351
|
+
Users are unaffected: theirs was already stored in `employeeNumber`.
|
|
352
|
+
|
|
353
|
+
**Migrating existing Groups.** Values sent before this release were never
|
|
354
|
+
stored, so there is nothing to convert — re-send them from the provisioning
|
|
355
|
+
side once the flag is set.
|
|
356
|
+
|
|
357
|
+
### `active` is now writable, and needs an attribute your directory holds
|
|
358
|
+
|
|
359
|
+
**Who is affected:** every SCIM deployment. Deactivations that used to be
|
|
360
|
+
silently dropped now reach the directory.
|
|
361
|
+
|
|
362
|
+
`active` is modelled on the presence of one LDAP attribute:
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
--scim-user-lock-attribute pwdAccountLockedTime # default
|
|
366
|
+
--scim-user-lock-value 000001010000Z # default, "locked forever"
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
The default is the ppolicy overlay's convention, and **`pwdAccountLockedTime`
|
|
370
|
+
only exists where slapd loads that overlay**. On a directory without it —
|
|
371
|
+
plain OpenLDAP, 389-ds, AD — a deactivation is refused by the schema and
|
|
372
|
+
answers `400 invalidValue` naming the flags. Point them elsewhere:
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
--scim-user-lock-attribute nsAccountLock
|
|
376
|
+
--scim-user-lock-value TRUE
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Two configuration mistakes are now refused at startup rather than accepted
|
|
380
|
+
and silently ineffective: naming an attribute without a value, and a name
|
|
381
|
+
that is not an LDAP attribute description. Two more are warned about. See
|
|
382
|
+
[the SCIM plugin documentation](plugins/integrations/scim.md#deactivating-an-account-active) for the
|
|
383
|
+
detail.
|
|
384
|
+
|
|
385
|
+
**Verify once, by hand, that a deactivation actually prevents a bind.**
|
|
386
|
+
Nothing here can check that your directory honours the value you chose:
|
|
387
|
+
`active` is read back from the mere presence of the attribute, so a value the
|
|
388
|
+
directory stores and ignores still reads as `false`.
|
|
389
|
+
|
|
390
|
+
**One deliberate deviation from RFC 7644 section 3.5.1:** a `PUT` that omits
|
|
391
|
+
`active` leaves the lock as it stands rather than clearing it. Clearing it
|
|
392
|
+
would release locks SCIM never set — a ppolicy auto-lockout after failed
|
|
393
|
+
binds, or one an administrator placed — so a routine profile sync would
|
|
394
|
+
defeat the brute-force control. Send `"active": true` to reactivate
|
|
395
|
+
deliberately.
|
|
396
|
+
|
|
397
|
+
### `forRequest()` for plugin authors
|
|
398
|
+
|
|
399
|
+
No action needed to upgrade. If you maintain a plugin that serves HTTP
|
|
400
|
+
requests, `this.server.ldap.forRequest(req)` returns a directory whose methods
|
|
401
|
+
carry the request, so the authorization hooks always see it. The unbound
|
|
402
|
+
methods take the request as a trailing optional argument, and omitting it
|
|
403
|
+
skips every authorization check silently. See the
|
|
404
|
+
[plugin development guide](../plugin-development/README.md#using-ldap-operations).
|
|
405
|
+
|
|
406
|
+
## To 0.6.2
|
|
407
|
+
|
|
408
|
+
### Writes that a bypass was letting through are now refused
|
|
409
|
+
|
|
410
|
+
**Who is affected:** deployments using `core/auth/authzPerBranch` or
|
|
411
|
+
`core/auth/authzLinid1` together with `plugins/ldap/onChange` or
|
|
412
|
+
`plugins/ldap/organizations`.
|
|
413
|
+
|
|
414
|
+
Both plugins rebuilt the hook tuple without the request, which made every
|
|
415
|
+
authorization plugin registered after them skip its check. Every `modify`,
|
|
416
|
+
and every `rename` for the second, went through unchecked.
|
|
417
|
+
|
|
418
|
+
**Check your grants before rolling this out widely.** An identity whose
|
|
419
|
+
grants were never quite right may start seeing `403` where it saw `200`. That
|
|
420
|
+
is the fix, not a regression.
|
|
421
|
+
|
|
422
|
+
Whether a given deployment was exposed was decided by plugin load order,
|
|
423
|
+
which is a race — it could differ from one restart to the next.
|
|
424
|
+
`core/auth/authzDynamic` was never affected: it reads its token from an
|
|
425
|
+
`AsyncLocalStorage` rather than from the request.
|
|
426
|
+
|
|
427
|
+
## To 0.6.1
|
|
428
|
+
|
|
429
|
+
### SCIM reads are authorized
|
|
430
|
+
|
|
431
|
+
**Who is affected:** any SCIM identity granted `write` without `read` on the
|
|
432
|
+
same branch.
|
|
433
|
+
|
|
434
|
+
Every search the SCIM plugin issued omitted the request, so
|
|
435
|
+
`ldapsearchrequest` — the hook the authorization plugins use — skipped its
|
|
436
|
+
check, and any authenticated identity could read any branch the plugin was
|
|
437
|
+
pointed at.
|
|
438
|
+
|
|
439
|
+
**An identity that writes now needs `read` on the same branch.** A SCIM write
|
|
440
|
+
answers with the resource it just changed, so it reads the entry back; a
|
|
441
|
+
grant of `write` without `read` no longer serves a write.
|
|
442
|
+
|
|
443
|
+
`GET /Users`, `GET /Users/{id}` and their Group counterparts answer `403`
|
|
444
|
+
where read is denied.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ldap-rest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Light directory manager",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Identity-Manager"
|
|
@@ -56,6 +56,10 @@
|
|
|
56
56
|
"types": "./dist/src/plugins/auth/authzPerRoute.d.ts",
|
|
57
57
|
"import": "./dist/plugins/auth/authzPerRoute.js"
|
|
58
58
|
},
|
|
59
|
+
"./plugin-auth-authzscope": {
|
|
60
|
+
"types": "./dist/src/plugins/auth/authzScope.d.ts",
|
|
61
|
+
"import": "./dist/plugins/auth/authzScope.js"
|
|
62
|
+
},
|
|
59
63
|
"./plugin-auth-crowdsec": {
|
|
60
64
|
"types": "./dist/src/plugins/auth/crowdsec.d.ts",
|
|
61
65
|
"import": "./dist/plugins/auth/crowdsec.js"
|
|
@@ -96,6 +100,10 @@
|
|
|
96
100
|
"types": "./dist/src/plugins/demo/helloworld.d.ts",
|
|
97
101
|
"import": "./dist/plugins/demo/helloworld.js"
|
|
98
102
|
},
|
|
103
|
+
"./plugin-ldap-accountlifecycle": {
|
|
104
|
+
"types": "./dist/src/plugins/ldap/accountLifecycle.d.ts",
|
|
105
|
+
"import": "./dist/plugins/ldap/accountLifecycle.js"
|
|
106
|
+
},
|
|
99
107
|
"./plugin-ldap-bulkimport": {
|
|
100
108
|
"types": "./dist/src/plugins/ldap/bulkImport.d.ts",
|
|
101
109
|
"import": "./dist/plugins/ldap/bulkImport.js"
|
|
@@ -104,6 +112,10 @@
|
|
|
104
112
|
"types": "./dist/src/plugins/ldap/departmentSync.d.ts",
|
|
105
113
|
"import": "./dist/plugins/ldap/departmentSync.js"
|
|
106
114
|
},
|
|
115
|
+
"./plugin-ldap-enterpriserules": {
|
|
116
|
+
"types": "./dist/src/plugins/ldap/enterpriseRules.d.ts",
|
|
117
|
+
"import": "./dist/plugins/ldap/enterpriseRules.js"
|
|
118
|
+
},
|
|
107
119
|
"./plugin-ldap-externalusersingroups": {
|
|
108
120
|
"types": "./dist/src/plugins/ldap/externalUsersInGroups.d.ts",
|
|
109
121
|
"import": "./dist/plugins/ldap/externalUsersInGroups.js"
|
|
@@ -180,6 +192,10 @@
|
|
|
180
192
|
"types": "./dist/src/plugins/scim/patch.d.ts",
|
|
181
193
|
"import": "./dist/plugins/scim/patch.js"
|
|
182
194
|
},
|
|
195
|
+
"./plugin-scim-projection": {
|
|
196
|
+
"types": "./dist/src/plugins/scim/projection.d.ts",
|
|
197
|
+
"import": "./dist/plugins/scim/projection.js"
|
|
198
|
+
},
|
|
183
199
|
"./plugin-scim-scim": {
|
|
184
200
|
"types": "./dist/src/plugins/scim/scim.d.ts",
|
|
185
201
|
"import": "./dist/plugins/scim/scim.js"
|
|
@@ -204,6 +220,10 @@
|
|
|
204
220
|
"types": "./dist/src/plugins/twake/appAccountsConsistency.d.ts",
|
|
205
221
|
"import": "./dist/plugins/twake/appAccountsConsistency.js"
|
|
206
222
|
},
|
|
223
|
+
"./plugin-twake-calendar": {
|
|
224
|
+
"types": "./dist/src/plugins/twake/calendar.d.ts",
|
|
225
|
+
"import": "./dist/plugins/twake/calendar.js"
|
|
226
|
+
},
|
|
207
227
|
"./plugin-twake-calendarresources": {
|
|
208
228
|
"types": "./dist/src/plugins/twake/calendarResources.d.ts",
|
|
209
229
|
"import": "./dist/plugins/twake/calendarResources.js"
|
|
@@ -476,6 +496,26 @@
|
|
|
476
496
|
"import": "./static/schemas/ad/users.json",
|
|
477
497
|
"require": "./static/schemas/ad/users.json"
|
|
478
498
|
},
|
|
499
|
+
"./schema-example-groups": {
|
|
500
|
+
"import": "./static/schemas/example/groups.json",
|
|
501
|
+
"require": "./static/schemas/example/groups.json"
|
|
502
|
+
},
|
|
503
|
+
"./schema-example-nomenclature-domains": {
|
|
504
|
+
"import": "./static/schemas/example/nomenclature/domains.json",
|
|
505
|
+
"require": "./static/schemas/example/nomenclature/domains.json"
|
|
506
|
+
},
|
|
507
|
+
"./schema-example-organizations": {
|
|
508
|
+
"import": "./static/schemas/example/organizations.json",
|
|
509
|
+
"require": "./static/schemas/example/organizations.json"
|
|
510
|
+
},
|
|
511
|
+
"./schema-example-positions": {
|
|
512
|
+
"import": "./static/schemas/example/positions.json",
|
|
513
|
+
"require": "./static/schemas/example/positions.json"
|
|
514
|
+
},
|
|
515
|
+
"./schema-example-users": {
|
|
516
|
+
"import": "./static/schemas/example/users.json",
|
|
517
|
+
"require": "./static/schemas/example/users.json"
|
|
518
|
+
},
|
|
479
519
|
"./schema-obm-organizations": {
|
|
480
520
|
"import": "./static/schemas/obm/organizations.json",
|
|
481
521
|
"require": "./static/schemas/obm/organizations.json"
|
|
@@ -560,6 +600,10 @@
|
|
|
560
600
|
"import": "./static/schemas/twake/nomenclature/twakeDeliveryMode.json",
|
|
561
601
|
"require": "./static/schemas/twake/nomenclature/twakeDeliveryMode.json"
|
|
562
602
|
},
|
|
603
|
+
"./schema-twake-nomenclature-twakedomain": {
|
|
604
|
+
"import": "./static/schemas/twake/nomenclature/twakeDomain.json",
|
|
605
|
+
"require": "./static/schemas/twake/nomenclature/twakeDomain.json"
|
|
606
|
+
},
|
|
563
607
|
"./schema-twake-nomenclature-twakelisttype": {
|
|
564
608
|
"import": "./static/schemas/twake/nomenclature/twakeListType.json",
|
|
565
609
|
"require": "./static/schemas/twake/nomenclature/twakeListType.json"
|
|
@@ -603,6 +647,7 @@
|
|
|
603
647
|
"package.json"
|
|
604
648
|
],
|
|
605
649
|
"scripts": {
|
|
650
|
+
"audit:directory": "tsx scripts/audit-directory.ts",
|
|
606
651
|
"build": "npm run build:prod",
|
|
607
652
|
"build:browser": "rollup -c rollup.browser.config.mjs",
|
|
608
653
|
"build:dev": "./.dev.mk builddev",
|
|
@@ -611,7 +656,8 @@
|
|
|
611
656
|
"build:pages": "tsx scripts/build-pages.ts",
|
|
612
657
|
"build:prod": "npm run clean && NODE_ENV=production rollup -c && npm run build:browser && node scripts/moveBrowserLibs.mjs && npm run build:dockerfile",
|
|
613
658
|
"build:watch": "rollup -c --watch",
|
|
614
|
-
"check": "npm run check:ts && npm run lint && npm run format:check",
|
|
659
|
+
"check": "npm run check:ts && npm run lint && npm run format:check && npm run check:no-client-values",
|
|
660
|
+
"check:no-client-values": "sh scripts/check-no-client-values.sh",
|
|
615
661
|
"check:ts": "tsc --noEmit",
|
|
616
662
|
"clean": "rimraf dist/* static/browser/* coverage",
|
|
617
663
|
"coverage": "c8 --config .c8rc.json mocha",
|
|
@@ -639,7 +685,7 @@
|
|
|
639
685
|
},
|
|
640
686
|
"dependencies": {
|
|
641
687
|
"body-parser": "^2.2.0",
|
|
642
|
-
"csv-parse": "^
|
|
688
|
+
"csv-parse": "^7.0.2",
|
|
643
689
|
"express": "^5.1.0",
|
|
644
690
|
"express-rate-limit": "^8.1.0",
|
|
645
691
|
"ldapts": "^8.0.9",
|
|
@@ -664,7 +710,7 @@
|
|
|
664
710
|
"@types/supertest": "^6.0.3",
|
|
665
711
|
"@typescript-eslint/eslint-plugin": "^8.44.0",
|
|
666
712
|
"@typescript-eslint/parser": "^8.44.0",
|
|
667
|
-
"c8": "^
|
|
713
|
+
"c8": "^12.0.0",
|
|
668
714
|
"chai": "^6.0.1",
|
|
669
715
|
"eslint": "^9.35.0",
|
|
670
716
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -674,7 +720,7 @@
|
|
|
674
720
|
"fast-glob": "^3.3.3",
|
|
675
721
|
"globals": "^14.0.0",
|
|
676
722
|
"js-yaml": "^4.1.1",
|
|
677
|
-
"mocha": "^
|
|
723
|
+
"mocha": "^12.0.0",
|
|
678
724
|
"nock": "^14.0.10",
|
|
679
725
|
"prettier": "^3.6.2",
|
|
680
726
|
"rimraf": "^6.0.1",
|
|
@@ -691,5 +737,8 @@
|
|
|
691
737
|
"express-openid-connect": "^2.19.2",
|
|
692
738
|
"lemonldap-ng-handler": "^0.7.3",
|
|
693
739
|
"re2": "^1.22.1"
|
|
740
|
+
},
|
|
741
|
+
"engines": {
|
|
742
|
+
"node": ">=20"
|
|
694
743
|
}
|
|
695
744
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Example deployment configuration
|
|
2
|
+
|
|
3
|
+
These schemas show what a concrete deployment looks like once every
|
|
4
|
+
client-specific value has been moved out of the code: national phone and
|
|
5
|
+
postal-code formats, the mail domain accounts must belong to, the payroll
|
|
6
|
+
number format and the placeholder that exempts a value from its uniqueness
|
|
7
|
+
check, the mailbox quota default, and the naming rules the directory already
|
|
8
|
+
enforces on its existing data.
|
|
9
|
+
|
|
10
|
+
They are **not** part of the product. Nothing in `src/` refers to them, and the
|
|
11
|
+
`npm run check:no-client-values` guard makes sure nothing ever does. Copy the
|
|
12
|
+
directory, replace the patterns and the domains, and the core serves another
|
|
13
|
+
directory unchanged.
|
|
14
|
+
|
|
15
|
+
Each `test` carries the `hint` that explains it, so a client can show the
|
|
16
|
+
expected format under the field instead of only rejecting what was typed.
|
|
17
|
+
|
|
18
|
+
Point a server at them with:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
node bin/index.mjs \
|
|
22
|
+
--ldap-base dc=example,dc=org \
|
|
23
|
+
--ldap-top-organization ou=organization,dc=example,dc=org \
|
|
24
|
+
--ldap-group-base ou=lists,ou=groups,dc=example,dc=org \
|
|
25
|
+
--group-schema static/schemas/example/groups.json \
|
|
26
|
+
--organization-schema static/schemas/example/organizations.json \
|
|
27
|
+
--ldap-flat-schema static/schemas/example/users.json \
|
|
28
|
+
--ldap-flat-schema static/schemas/example/positions.json \
|
|
29
|
+
--ldap-flat-schema static/schemas/example/nomenclature/domains.json \
|
|
30
|
+
--plugin core/ldap/flatGeneric \
|
|
31
|
+
--plugin core/ldap/groups \
|
|
32
|
+
--plugin core/ldap/organizations \
|
|
33
|
+
--plugin core/ldap/enterpriseRules \
|
|
34
|
+
--plugin core/ldap/accountLifecycle \
|
|
35
|
+
--plugin core/auth/authzScope
|
|
36
|
+
```
|