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.
Files changed (99) hide show
  1. package/dist/abstract/ldapFlat.js +1 -1
  2. package/dist/abstract/plugin.js +1 -1
  3. package/dist/bin/index.js +1 -1
  4. package/dist/config/args.js +1 -1
  5. package/dist/config/schema.js +1 -0
  6. package/dist/lib/expressFormatedResponses.js +1 -1
  7. package/dist/lib/ldapActions.js +1 -1
  8. package/dist/lib/utils.js +1 -1
  9. package/dist/plugins/auth/authzScope.js +1 -0
  10. package/dist/plugins/auth/llng.js +1 -1
  11. package/dist/plugins/ldap/accountLifecycle.js +1 -0
  12. package/dist/plugins/ldap/bulkImport.js +1 -1
  13. package/dist/plugins/ldap/departmentSync.js +1 -1
  14. package/dist/plugins/ldap/enterpriseRules.js +1 -0
  15. package/dist/plugins/ldap/externalUsersInGroups.js +1 -1
  16. package/dist/plugins/ldap/flatGeneric.js +1 -1
  17. package/dist/plugins/ldap/groups.js +1 -1
  18. package/dist/plugins/ldap/organizations.js +1 -1
  19. package/dist/plugins/scim/bulk.js +1 -1
  20. package/dist/plugins/scim/errors.js +1 -1
  21. package/dist/plugins/scim/filter.js +1 -1
  22. package/dist/plugins/scim/groups.js +1 -1
  23. package/dist/plugins/scim/list.js +1 -1
  24. package/dist/plugins/scim/mapping.js +1 -1
  25. package/dist/plugins/scim/patch.js +1 -1
  26. package/dist/plugins/scim/projection.js +1 -0
  27. package/dist/plugins/scim/scim.js +1 -1
  28. package/dist/plugins/scim/users.js +1 -1
  29. package/dist/plugins/twake/calendar.js +1 -0
  30. package/dist/plugins/twake/calendarResources.js +1 -1
  31. package/dist/plugins/twake/james.js +1 -1
  32. package/dist/src/abstract/ldapFlat.d.ts +159 -1
  33. package/dist/src/abstract/plugin.d.ts +13 -0
  34. package/dist/src/config/args.d.ts +5 -0
  35. package/dist/src/config/schema.d.ts +382 -12
  36. package/dist/src/lib/expressFormatedResponses.d.ts +14 -0
  37. package/dist/src/lib/ldapActions.d.ts +59 -6
  38. package/dist/src/lib/utils.d.ts +65 -0
  39. package/dist/src/plugins/auth/authzScope.d.ts +53 -0
  40. package/dist/src/plugins/auth/llng.d.ts +30 -1
  41. package/dist/src/plugins/ldap/accountLifecycle.d.ts +56 -0
  42. package/dist/src/plugins/ldap/departmentSync.d.ts +43 -0
  43. package/dist/src/plugins/ldap/enterpriseRules.d.ts +436 -0
  44. package/dist/src/plugins/ldap/flatGeneric.d.ts +29 -0
  45. package/dist/src/plugins/ldap/groups.d.ts +22 -0
  46. package/dist/src/plugins/ldap/organizations.d.ts +25 -4
  47. package/dist/src/plugins/scim/errors.d.ts +17 -4
  48. package/dist/src/plugins/scim/filter.d.ts +11 -1
  49. package/dist/src/plugins/scim/groups.d.ts +2 -0
  50. package/dist/src/plugins/scim/list.d.ts +3 -5
  51. package/dist/src/plugins/scim/mapping.d.ts +90 -9
  52. package/dist/src/plugins/scim/patch.d.ts +16 -1
  53. package/dist/src/plugins/scim/projection.d.ts +15 -0
  54. package/dist/src/plugins/scim/scim.d.ts +5 -0
  55. package/dist/src/plugins/scim/users.d.ts +21 -0
  56. package/dist/src/plugins/twake/calendar.d.ts +113 -0
  57. package/dist/src/plugins/twake/calendarResources.d.ts +9 -58
  58. package/docs/README.md +2 -0
  59. package/docs/client-development/api/openapi.md +1 -1
  60. package/docs/client-development/browser/libraries.md +1 -1
  61. package/docs/plugin-development/README.md +22 -0
  62. package/docs/plugin-development/dependencies.md +10 -7
  63. package/docs/presentations/en.md +1 -1
  64. package/docs/presentations/fr.md +1 -1
  65. package/docs/usage/README.md +2 -0
  66. package/docs/usage/configuration.md +35 -15
  67. package/docs/usage/directory-audit.md +105 -0
  68. package/docs/usage/plugins/README.md +6 -6
  69. package/docs/usage/plugins/auth/README.md +6 -5
  70. package/docs/usage/plugins/auth/authz-scope.md +103 -0
  71. package/docs/usage/plugins/auth/llng.md +17 -0
  72. package/docs/usage/plugins/integrations/README.md +5 -5
  73. package/docs/usage/plugins/integrations/{calendar-resources.md → calendar.md} +70 -13
  74. package/docs/usage/plugins/integrations/scim.md +147 -38
  75. package/docs/usage/plugins/ldap/README.md +13 -11
  76. package/docs/usage/plugins/ldap/account-lifecycle.md +129 -0
  77. package/docs/usage/plugins/ldap/bulk-import.md +8 -1
  78. package/docs/usage/plugins/ldap/enterprise-rules.md +167 -0
  79. package/docs/usage/plugins/ldap/flat-generic.md +172 -0
  80. package/docs/usage/plugins/ldap/groups.md +10 -4
  81. package/docs/usage/plugins/ldap/organizations.md +59 -33
  82. package/docs/usage/upgrading.md +444 -0
  83. package/package.json +54 -5
  84. package/static/schemas/example/README.md +36 -0
  85. package/static/schemas/example/groups.json +94 -0
  86. package/static/schemas/example/nomenclature/domains.json +49 -0
  87. package/static/schemas/example/organizations.json +106 -0
  88. package/static/schemas/example/positions.json +35 -0
  89. package/static/schemas/example/users.json +246 -0
  90. package/static/schemas/twake/groups.json +81 -15
  91. package/static/schemas/twake/nomenclature/twakeAccountStatus.json +26 -0
  92. package/static/schemas/twake/nomenclature/twakeDeliveryMode.json +22 -0
  93. package/static/schemas/twake/nomenclature/twakeDomain.json +60 -0
  94. package/static/schemas/twake/nomenclature/twakeListType.json +22 -0
  95. package/static/schemas/twake/nomenclature/twakeMailboxType.json +22 -0
  96. package/static/schemas/twake/nomenclature/twakeTitle.json +42 -0
  97. package/static/schemas/twake/organizations.json +92 -12
  98. package/static/schemas/twake/positions.json +22 -4
  99. 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.6.2",
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": "^6.1.0",
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": "^10.1.3",
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": "^11.7.2",
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
+ ```