ldap-rest 0.26.0 → 1.0.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 (89) hide show
  1. package/dist/abstract/ldapFlat.js +1 -1
  2. package/dist/abstract/twakePlugin.js +1 -1
  3. package/dist/bin/index.js +1 -1
  4. package/dist/config/args.js +1 -1
  5. package/dist/lib/authz/base.js +1 -1
  6. package/dist/lib/authz/composition.js +1 -1
  7. package/dist/lib/ldapActions.js +1 -1
  8. package/dist/lib/mailDomains.js +1 -0
  9. package/dist/lib/requestContext.js +1 -0
  10. package/dist/lib/utils.js +1 -1
  11. package/dist/plugins/auth/authzDynamic.js +1 -1
  12. package/dist/plugins/auth/authzLinid1.js +1 -1
  13. package/dist/plugins/auth/authzPerBranch.js +1 -1
  14. package/dist/plugins/auth/authzScope.js +1 -1
  15. package/dist/plugins/configApi.js +1 -1
  16. package/dist/plugins/ldap/bulkImport.js +1 -1
  17. package/dist/plugins/ldap/departmentSync.js +1 -1
  18. package/dist/plugins/ldap/enterpriseRules.js +1 -1
  19. package/dist/plugins/ldap/externalUsersInGroups.js +1 -1
  20. package/dist/plugins/ldap/groups.js +1 -1
  21. package/dist/plugins/ldap/onChange.js +1 -1
  22. package/dist/plugins/ldap/organizations.js +1 -1
  23. package/dist/plugins/ldap/passwordPolicy.js +1 -1
  24. package/dist/plugins/ldap/raw.js +1 -1
  25. package/dist/plugins/ldap/trash.js +1 -1
  26. package/dist/plugins/rabbitmq.js +1 -1
  27. package/dist/plugins/scim/groups.js +1 -1
  28. package/dist/plugins/scim/users.js +1 -1
  29. package/dist/plugins/storage/ldap.js +1 -1
  30. package/dist/plugins/storage/postgres.js +1 -1
  31. package/dist/plugins/storage/valkey.js +1 -1
  32. package/dist/plugins/twake/appAccountsApi.js +1 -1
  33. package/dist/plugins/twake/appAccountsConsistency.js +1 -1
  34. package/dist/plugins/twake/clouderyProvision.js +1 -1
  35. package/dist/plugins/twake/groups.js +1 -1
  36. package/dist/plugins/twake/instances.js +1 -1
  37. package/dist/plugins/twake/james.js +1 -1
  38. package/dist/plugins/twake/lifecycleEvents.js +1 -1
  39. package/dist/plugins/twake/spaces.js +1 -1
  40. package/dist/plugins/twake/tombstone.js +1 -1
  41. package/dist/src/abstract/ldapFlat.d.ts +25 -6
  42. package/dist/src/abstract/plugin.d.ts +1 -1
  43. package/dist/src/bin/index.d.ts +12 -0
  44. package/dist/src/lib/authz/base.d.ts +87 -4
  45. package/dist/src/lib/authz/composition.d.ts +21 -0
  46. package/dist/src/lib/ldapActions.d.ts +139 -18
  47. package/dist/src/lib/mailDomains.d.ts +41 -0
  48. package/dist/src/lib/requestContext.d.ts +23 -0
  49. package/dist/src/lib/utils.d.ts +41 -2
  50. package/dist/src/plugins/auth/authzDynamic.d.ts +5 -12
  51. package/dist/src/plugins/auth/authzLinid1.d.ts +3 -1
  52. package/dist/src/plugins/auth/authzPerBranch.d.ts +5 -9
  53. package/dist/src/plugins/ldap/accountLifecycle.d.ts +2 -2
  54. package/dist/src/plugins/ldap/bulkImport.d.ts +30 -5
  55. package/dist/src/plugins/ldap/enterpriseRules.d.ts +11 -1
  56. package/dist/src/plugins/ldap/externalUsersInGroups.d.ts +20 -3
  57. package/dist/src/plugins/ldap/groups.d.ts +48 -1
  58. package/dist/src/plugins/ldap/organizations.d.ts +4 -4
  59. package/dist/src/plugins/twake/appAccountsApi.d.ts +3 -67
  60. package/dist/src/plugins/twake/groups.d.ts +46 -2
  61. package/dist/src/plugins/twake/james.d.ts +0 -11
  62. package/docs/README.md +25 -0
  63. package/docs/plugin-development/README.md +61 -15
  64. package/docs/plugin-development/dependencies.md +8 -2
  65. package/docs/plugin-development/hooks.md +4 -5
  66. package/docs/usage/configuration.md +11 -8
  67. package/docs/usage/plugins/auth/README.md +22 -3
  68. package/docs/usage/plugins/auth/authz-linid1.md +14 -5
  69. package/docs/usage/plugins/auth/authz-per-branch.md +60 -9
  70. package/docs/usage/plugins/integrations/app-accounts.md +5 -0
  71. package/docs/usage/plugins/integrations/groups.md +36 -3
  72. package/docs/usage/plugins/integrations/scim.md +7 -0
  73. package/docs/usage/plugins/integrations/spaces.md +3 -1
  74. package/docs/usage/plugins/ldap/bulk-import.md +20 -0
  75. package/docs/usage/plugins/ldap/enterprise-rules.md +12 -2
  76. package/docs/usage/plugins/ldap/external-users.md +8 -3
  77. package/docs/usage/plugins/ldap/flat-generic.md +3 -0
  78. package/docs/usage/plugins/ldap/groups.md +6 -0
  79. package/docs/usage/plugins/ldap/organizations.md +17 -0
  80. package/docs/usage/plugins/ldap/raw.md +1 -1
  81. package/docs/usage/upgrading.md +228 -0
  82. package/package.json +1 -1
  83. package/static/schemas/example/groups.json +1 -0
  84. package/static/schemas/example/organizations.json +1 -0
  85. package/static/schemas/example/users.json +3 -0
  86. package/static/schemas/twake/groups.json +2 -0
  87. package/static/schemas/twake/organizations.json +1 -0
  88. package/static/schemas/twake/teamMailboxes.json +3 -1
  89. package/static/schemas/twake/users.json +3 -0
@@ -320,6 +320,13 @@ the caller sent, and so does a group `PUT` for its members, hidden ones
320
320
  included. A group left with no member the caller saw gets the
321
321
  `--group-dummy-user` placeholder.
322
322
 
323
+ A member is resolved as the caller sees the directory, whether it is given
324
+ by id or by DN: an account the caller cannot read is left out of the group,
325
+ as one that does not exist is. Under an authorization model judging by
326
+ branch, a member in a branch the caller may not read refuses the whole write
327
+ instead, whether the account exists or not. A DN outside the user base is
328
+ left out.
329
+
323
330
  ### Schema advertisement
324
331
 
325
332
  Schemas listed under `GET /scim/v2/Schemas` are read from
@@ -179,7 +179,9 @@ Member events compare each moved user's roles before and after a change:
179
179
  ## Routes
180
180
 
181
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
182
+ is missing or the caller cannot read it, and 410 `ORGANIZATION_DELETED` when
183
+ it is deleted. A user the caller cannot read is not found
184
+ (`USER_NOT_FOUND`). Errors are
183
185
  `{ "error": "...", "code": "..." }`. A request an authorization plugin refuses
184
186
  answers 403 `REFUSED`. A write answers `{ "success": true }` unless stated
185
187
  otherwise.
@@ -318,6 +318,26 @@ curl -X POST "http://localhost:8081/api/v1/ldap/bulk-import/users" \
318
318
  - Check organization DNs exist
319
319
  - Estimate import duration
320
320
 
321
+ A dry run is judged as the import is: a row the caller may not write fails,
322
+ and a row updating an existing entry counts as `updated`.
323
+
324
+ ### Authorization
325
+
326
+ Each row's write is judged by the authorization plugins before anything is
327
+ looked for in the directory: the entry, its organization, whether it exists.
328
+ A row is let through when the caller may create its entry, or change the
329
+ entry it names (an administrator updating the accounts of their organization
330
+ from a file naming none). Otherwise it fails with
331
+ `Token does not have permission on this branch` (`code: forbidden`), whether
332
+ its entry and its organization are there or not.
333
+
334
+ A row the caller may write tells, as a single creation does, whether its
335
+ identifier is taken: it is then skipped, or updated with `updateExisting`.
336
+
337
+ The organization link of a row names one organization: a row naming several,
338
+ or holding an empty value (`;` in a cell), fails as invalid, reported in
339
+ `errors` (a 400 answer with `continueOnError=false`).
340
+
321
341
  ### Update Existing Mode
322
342
 
323
343
  Update entries that already exist:
@@ -63,8 +63,18 @@ check that justified it.
63
63
  non-individual entries.
64
64
  - `filter` narrows the search, e.g. `(objectClass=twakeAccount)`.
65
65
 
66
- On an update the entry being modified is excluded from the search, so
67
- re-sending an unchanged value never conflicts with itself.
66
+ On an update, a value the entry already holds, as stored and spelled the
67
+ same, is not looked for again; only the values it does not hold yet are.
68
+ Re-sending an unchanged value conflicts neither with the entry itself nor
69
+ with the copies other plugins keep of it — the applicative accounts of
70
+ `appAccountsConsistency` carry the account's mail. A value held under
71
+ another attribute the constraint spans, an alias becoming the mail, is
72
+ skipped only when that attribute's own constraint looks for it everywhere
73
+ this one does; otherwise it is checked. A duplicate the directory already
74
+ holds, from before the rule or another tool, is therefore not reported on a
75
+ re-save. A value spelled differently is a new one, and looked for: changing
76
+ only the case of an account's mail is refused while an applicative copy
77
+ holds the old spelling, which the directory takes for the same address.
68
78
 
69
79
  ### Mail domains
70
80
 
@@ -37,7 +37,7 @@ This plugin intercepts group member validation and automatically creates missing
37
37
  the address escaped as any DN value (RFC 4514): `mail=a\+b@partner.com,…`
38
38
  creates an entry whose `mail` is `a+b@partner.com`. Only the direct children of the
39
39
  branch are external members.
40
- - **Domain Protection**: External users cannot be created if their mail are in managed domains (configured via `--mail-domain`, compared case-insensitively)
40
+ - **Domain Protection**: External users cannot be created if their mail is in a managed domain, compared case-insensitively. The managed domains are those of `--mail-domain` and those the directory declares in its domain branches: the entries of a flat schema declaring the `domainName` role, or else `ou=domains,ou=nomenclature,<base>` with `--enterprise-domain-name-attribute` (`associatedDomain`). A subdomain of a managed domain is not managed: `user@sub.example.com` is external unless `sub.example.com` is declared itself
41
41
  - **Automatic Creation**: Users are created automatically when added to a group if they don't exist
42
42
 
43
43
  ### Config API
@@ -56,7 +56,8 @@ where to send them:
56
56
 
57
57
  A client turns an address into a member with
58
58
  `rdnAttribute=<escaped address>,<branch>`, and can refuse a `managedDomains`
59
- address before sending it.
59
+ address before sending it. The list holds the domains declared in the
60
+ directory as well as those of `--mail-domain`.
60
61
 
61
62
  ## How It Works
62
63
 
@@ -225,6 +226,9 @@ Protect multiple domains from external user creation:
225
226
  --mail-domain example.com,company.org,subsidiary.net
226
227
  ```
227
228
 
229
+ The domains declared in the directory are protected without being listed
230
+ here.
231
+
228
232
  ### Custom Attributes via Hooks
229
233
 
230
234
  Add organization-specific attributes to external users:
@@ -261,7 +265,8 @@ hooks: {
261
265
  **Solutions:**
262
266
 
263
267
  1. Use the correct branch for internal users: `ou=users,dc=example,dc=com`
264
- 2. Remove domain from `--mail-domain` if it should allow external users
268
+ 2. Remove the domain from `--mail-domain`, or from the directory's domain
269
+ branches, if it should allow external users
265
270
  3. This is expected behavior for internal domains - create user in users branch instead
266
271
 
267
272
  ### Malformed Member DN
@@ -120,6 +120,9 @@ Each schema file must include an `entity` metadata section and attribute definit
120
120
  ```
121
121
 
122
122
  Pointer validation ensures the DN exists and is within allowed branches.
123
+ The DN is looked for as the server, whoever writes: a caller may point at
124
+ an entry it cannot read, and learns from the answer whether one is there,
125
+ never what it holds.
123
126
 
124
127
  #### Fixed Attributes
125
128
 
@@ -410,6 +410,12 @@ The plugin supports JSON schema validation for group attributes. Example schema:
410
410
  }
411
411
  ```
412
412
 
413
+ A `pointer` attribute is checked as in
414
+ [flat schemas](flat-generic.md#pointer-dn-reference): the DN must name an
415
+ entry in the allowed branches, looked for as the server, whoever writes.
416
+ A caller may point at an entry it cannot read, and learns from the answer
417
+ whether one is there, never what it holds.
418
+
413
419
  ## Hooks
414
420
 
415
421
  The plugin emits and listens to lifecycle hooks:
@@ -174,6 +174,14 @@ curl "http://localhost:8081/api/v1/ldap/organizations/ou%3DIT%2Co%3Dgov%2Cc%3Dmu
174
174
  ]
175
175
  ```
176
176
 
177
+ **Where the attached entries are looked for**
178
+
179
+ Below the parent of `--ldap-top-organization`, as the caller. A caller an
180
+ authorization plugin does not let search there — `authzPerBranch` judging
181
+ by DN, a token of `authzDynamic` — gets the entries attached within the
182
+ organization's own branch; those attached from elsewhere are not listed.
183
+ `/subnodes/search` looks the same way.
184
+
177
185
  **When the answer is partial**
178
186
 
179
187
  Two things bound what comes back, and both are announced in the list rather
@@ -715,6 +723,15 @@ The plugin validates organizations against configured object classes. Example sc
715
723
  }
716
724
  ```
717
725
 
726
+ A `pointer` attribute is checked as in
727
+ [flat schemas](flat-generic.md#pointer-dn-reference): the DN must name an
728
+ entry in the allowed branches, looked for as the server, whoever writes.
729
+ A caller may point at an entry it cannot read, and learns from the answer
730
+ whether one is there, never what it holds. A pointer that grants rights
731
+ follows the same rule: an organization administrator may name, in
732
+ `twakeLocalAdminLink`, an account they cannot read, and so hand the
733
+ administration of their organization to it.
734
+
718
735
  ## See Also
719
736
 
720
737
  - [LDAP Flat Plugin](./ldapFlat.md) - For managing flat LDAP entities (users, positions, nomenclature) through schema-driven approach
@@ -199,7 +199,7 @@ Combine with an authentication plugin ([token](../auth/token.md), [oidc](../auth
199
199
 
200
200
  - **Schema fetch**: the root DSE is read for `subschemaSubentry` (falling back to `cn=Subschema`), then the subschema entry is fetched and parsed once per `--ldap-raw-schema-cache-ttl`. Concurrent callers share a single fetch, and so does the [object class repair](flat-generic.md#entries-created-by-other-tools), which loads it even without this plugin.
201
201
  - **Operational attributes**: entries are read with `*` and `+`. When the server rejects `+`, the read is retried with `*` alone.
202
- - **Missing entries**: the LDAP "no such object" result (code 32) is mapped to a 404.
202
+ - **Missing entries**: the LDAP "no such object" result (code 32) is mapped to a 404. A search whose base the caller cannot read answers the same 404 as one whose base does not exist.
203
203
 
204
204
  ## Limitations
205
205
 
@@ -4,6 +4,234 @@ 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
+ ## From 0.26 to 1.0.0
8
+
9
+ Every breaking change since 0.26.0. Authorization fails closed in 1.0.0:
10
+ what used to pass because a call site forgot the request, or because a
11
+ name was spelled otherwise, is now judged, and may be refused.
12
+
13
+ ### Attribute names in a write: one name per attribute, no option
14
+
15
+ **Who is affected:** a client, a CSV of the bulk import or a plugin writing
16
+ an attribute with an option other than `;binary` (`title;lang-fr`,
17
+ `member;x-tag`), or naming one attribute twice in one part of a write
18
+ (`mail` and `MAIL`, `mail` and `rfc822Mailbox`).
19
+
20
+ Every add and modify now names its attributes as this server does before
21
+ it is judged: the name is resolved through the directory schema, so `MAIL`,
22
+ `rfc822Mailbox` and `0.9.2342.19200300.100.1.3` are all written `mail`.
23
+ The spelling kept is the configuration's or a loaded schema's, else the
24
+ first name the directory declares. An option other than `;binary` is
25
+ refused with a 400: a tagged value is one of its own, which the rules
26
+ guarding the attribute would not see. Two names for one attribute in one
27
+ `add`, `replace` or `delete` are refused with a 400 too. Values already
28
+ stored under an option are still read, but cannot be written or deleted
29
+ through ldap-rest: clean them up in the directory.
30
+
31
+ A rename gives the entry one value of the attribute it is already named by
32
+ (`cn=new` for a group named by `cn`): a new RDN holding several values, or
33
+ naming another attribute, is refused with a 400. The directory writes the
34
+ values of the new RDN on the entry, where no check of a write saw them.
35
+
36
+ When the directory schema cannot be read, the names, aliases and OIDs of
37
+ the standard types (`cn`, `mail`, `uid`, `ou`…) are still resolved, as are
38
+ the spellings of the configuration and the schemas. A name given as another
39
+ OID is refused, and another alias is written as given.
40
+
41
+ ### SCIM group members are looked up as the caller
42
+
43
+ **Who is affected:** a SCIM client writing group members by DN, and one
44
+ that tells an absent user from a hidden one by the message of
45
+ `PUT /Users/{id}`.
46
+
47
+ A member given by DN in `POST`, `PUT` or `PATCH /Groups` was taken as it
48
+ stood. It is now looked up as the caller, as a member given by id is: a DN
49
+ the caller cannot read, or that names no entry, is left out of the group,
50
+ and under a model judging by branch a member in a refused branch makes the
51
+ whole write refused. A DN outside the user base is left out, compared RDN by
52
+ RDN. A client that wrote members it may not read asks an administrator
53
+ whose rules cover them.
54
+
55
+ `PUT /Users/{id}` on a user that is not there answered `Resource not found`;
56
+ it now answers `User {id} not found`, as it did for a user the caller may
57
+ not read. The status, `404`, is unchanged.
58
+
59
+ ### The group routes take a DN for a group only
60
+
61
+ **Who is affected:** a client calling `/api/v1/ldap/groups/{cn}` with a
62
+ DN rather than a name.
63
+
64
+ A DN given to these routes, to read, write, delete, create or rename a
65
+ group, must name a group below the groups branch (`--ldap-group-base`),
66
+ named by the groups' main attribute (`--ldap-groups-main-attribute`, `cn`
67
+ by default) and of a group class (`--group-class`, or a standard one) the
68
+ caller reads; anything else answers `404 Group not found`, and a DN the
69
+ directory would refuse `400`. A client that managed the organization
70
+ groups of `core/twake/groups` by DN through these routes, when they lie
71
+ outside `--ldap-group-base`, uses `/api/v1/organizations/{id}/groups`.
72
+ A deployment whose groups carry a class of their own, and none of the
73
+ standard ones, declares it in `--group-class`.
74
+
75
+ ### Reference data still in use is not deleted
76
+
77
+ **Who is affected:** a deployment loading the shipped `twake` or `example`
78
+ schemas with `core/ldap/enterpriseRules`.
79
+
80
+ Deleting a mail domain an organization links, or a nomenclature value an
81
+ entry holds (account status, delivery mode, personal title, list type,
82
+ mailbox type), answers `409 still referenced` instead of leaving the
83
+ reference naming nothing. Change the referrers first. A deployment with
84
+ its own schemas marks its pointers `"referentialIntegrity": "restrict"` to
85
+ get the same. References are looked for under the base each entity is
86
+ configured with; a base the directory does not hold is logged once and
87
+ holds none.
88
+
89
+ ### Plugins: an operation naming no request is judged as the caller
90
+
91
+ **Who is affected:** a third-party plugin calling `this.server.ldap.search`,
92
+ `add`, `modify`, `rename` or `delete` without a request, from a route, while
93
+ an authorization plugin is loaded. `move` is the server's and stays unjudged:
94
+ move an entry for a caller with `rename`.
95
+
96
+ Such an operation used to pass unjudged, as the server's. While an
97
+ authenticated request is being served, it is now judged as that request's
98
+ caller, and refused where the caller may not. Hand the request down
99
+ (`ldap.forRequest(req)`), or, for the server's own work, go through
100
+ `ldap.system`. Hooks run outside the request and keep acting as the server.
101
+
102
+ Under `core/auth/authzDynamic`, a hook's operations that named no request
103
+ used to be judged against the token, through an async context of the
104
+ plugin's own; they now act as the server, as under the other authorization
105
+ plugins. A hook writing on the caller's behalf passes the request it is
106
+ handed. That context, `authzContext`, is no longer exported: read the token
107
+ off the request (`req.authzToken`).
108
+
109
+ ### An authorization plugin needs an authentication plugin
110
+
111
+ **Who is affected:** a deployment loading `core/auth/authzPerBranch`,
112
+ `core/auth/authzLinid1` or `core/auth/authzPerRoute` without any
113
+ authentication plugin.
114
+
115
+ Such a server no longer starts. No request carried an identity, so each of
116
+ these plugins stepped aside and every request passed, whatever the rules
117
+ said. Load the authentication plugin the rules were written for, or remove
118
+ the authorization plugins to serve the directory openly, as it already was.
119
+
120
+ ### Nested branch rules of `authzPerBranch`
121
+
122
+ **Who is affected:** a deployment of `core/auth/authzPerBranch` whose rules
123
+ for one user, or one group, name a branch and a sub-branch of it.
124
+
125
+ The rule of the sub-branch now applies to everything below it, whatever
126
+ order the two are written in. It used to apply only where it matched
127
+ exactly — the sub-branch entry, and writes on its direct children, judged
128
+ on their parent — and below that the first rule written won. A sub-branch rule that
129
+ was ignored, a restriction or a grant, now takes effect in single reads and
130
+ writes and in searches: a search on the parent no longer lists what the
131
+ sub-branch rule refuses, so a listing may now be shorter. Check such rules:
132
+ they now mean what they say.
133
+
134
+ `default` no longer adds to what the rules grant: it applies to a
135
+ permission none of the rules that apply sets. A rule's `read`, `write` or
136
+ `delete` set to `false` now refuses, where `"default": {"read": true}` used
137
+ to grant it anyway. A permission a rule leaves unset is still the
138
+ default's, not the broader rule's.
139
+
140
+ Renaming or moving an entry a rule names, or one holding such an entry, now
141
+ needs read and write permission at each of those rules, and deleting it
142
+ needs delete there; changing the entry itself needs that rule's write
143
+ permission, as does creating it. A write setting the organization link and
144
+ changing anything besides the link and its path needs write where the
145
+ entry stands — its branch, or under `--authz-filter-attached-entries` the
146
+ organization it leaves: it used to need only read there, so the rest of
147
+ the write — a password, say — went through. Moving an entry between
148
+ organizations is unchanged.
149
+
150
+ ### Organization groups need read access to the organization
151
+
152
+ **Who is affected:** a deployment loading an authorization plugin together
153
+ with `core/twake/groups` (and `core/twake/spaces`, which goes through it).
154
+
155
+ The `/organizations/{id}/groups` and `/organizations/{id}/spaces` routes read
156
+ as the caller. A client calling them must be granted read access to the
157
+ organization, its groups and its users, write access to change them and
158
+ delete access to delete one. An organization it cannot read answers `404`,
159
+ and a user it cannot read `404 USER_NOT_FOUND` where it used to get `403`.
160
+
161
+ Under `core/auth/authzLinid1` or `--authz-filter-attached-entries`, groups
162
+ written in an organization's branch now carry its link
163
+ (`--ldap-organization-link-attribute`): creating or changing a group needs
164
+ write on the organization, and a group without a link is read and changed by
165
+ every administrator. Link the groups written before; this script is an
166
+ example to adapt to your patterns, attribute names and organization names
167
+ (it expects plain ones, without spaces or commas):
168
+
169
+ ```sh
170
+ # Connection to the directory, as for any ldapsearch
171
+ OPTS="-H ldap://localhost -D cn=admin,dc=example,dc=com -w secret"
172
+ # --twake-group-organization-dn and --twake-group-base, {org} left out
173
+ ORGS="dc=example,dc=com"
174
+ for org in $(ldapsearch $OPTS -LLL -s one -b "$ORGS" '(ou=*)' ou |
175
+ sed -n 's/^ou: //p'); do
176
+ ldapsearch $OPTS -LLL -o ldif-wrap=no -s one \
177
+ -b "ou=groups,ou=$org,$ORGS" '(!(twakeDepartmentLink=*))' objectClass |
178
+ awk -v link="ou=$org,$ORGS" '
179
+ function flush() {
180
+ if (!dn) return
181
+ print "dn: " dn "\nchangetype: modify"
182
+ # The class is added only to a group lacking one allowing the link
183
+ if (!held) print "add: objectClass\nobjectClass: twakeGroup\n-"
184
+ print "add: twakeDepartmentLink\ntwakeDepartmentLink: " link "\n"
185
+ dn = ""
186
+ }
187
+ /^dn: / { flush(); dn = substr($0, 5); held = 0 }
188
+ tolower($0) ~ /^objectclass: (twakegroup|twakestaticgroup)$/ { held = 1 }
189
+ END { flush() }'
190
+ done | ldapmodify $OPTS
191
+ ```
192
+
193
+ ### The organization link holds one organization
194
+
195
+ **Who is affected:** a client or a CSV writing several organization links on
196
+ one entry, or an empty one.
197
+
198
+ A write giving the organization link (`--ldap-organization-link-attribute`)
199
+ several values, or an empty value, is refused with a 400: only one of them
200
+ was judged. Under any spelling of the attribute, adding a link to an entry
201
+ that had none is judged as a move: write where it goes and, judged by
202
+ branch, read where it stands.
203
+
204
+ ### Smaller changes in answers
205
+
206
+ - `GET /api/v1/ldap/raw/search` below a base the caller may not read
207
+ answers 404, as for a base that is not there; under `authzPerBranch`
208
+ judging by DN it answered 403.
209
+ - Changing only the case of an account's mail is refused (409) while an
210
+ applicative copy holds the old spelling, which the directory takes for
211
+ the same address.
212
+
213
+ ### External members in a domain the directory declares
214
+
215
+ **Who is affected:** a deployment of `core/ldap/externalUsersInGroups`
216
+ adding as external members addresses in a mail domain the directory
217
+ declares, without listing it in `--mail-domain`.
218
+
219
+ Such a member is refused, as one in a `--mail-domain` already was. The
220
+ managed domains are now also those of the directory's domain branches,
221
+ [docs](plugins/ldap/external-users.md#important-notes); `GET /config` lists
222
+ them in `managedDomains`.
223
+
224
+ ### Quota and app accounts need read access to the account
225
+
226
+ **Who is affected:** a deployment loading an authorization plugin
227
+ (`core/auth/authzPerBranch`, `authzLinid1`, `authzDynamic`) together with
228
+ `core/twake/james` or `core/twake/appAccountsApi`.
229
+
230
+ `GET /users/{user}/quota-usage` and the app account routes answer `404` for
231
+ an account the caller cannot read. A client calling them must be granted
232
+ read access to the accounts it asks about, as it already needed write
233
+ access to create or delete an app account.
234
+
7
235
  ## To 0.24.0
8
236
 
9
237
  ### Links to deleted accounts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ldap-rest",
3
- "version": "0.26.0",
3
+ "version": "1.0.0",
4
4
  "description": "Light directory manager",
5
5
  "keywords": [
6
6
  "Identity-Manager"
@@ -50,6 +50,7 @@
50
50
  "businessCategory": {
51
51
  "type": "pointer",
52
52
  "branch": ["ou=twakeListType,ou=nomenclature,__LDAP_BASE__"],
53
+ "referentialIntegrity": "restrict",
53
54
  "required": false,
54
55
  "role": "writePolicy",
55
56
  "label": "List type"
@@ -89,6 +89,7 @@
89
89
  "type": "pointer",
90
90
  "branch": ["ou=domains,ou=nomenclature,__LDAP_BASE__"]
91
91
  },
92
+ "referentialIntegrity": "restrict",
92
93
  "required": false,
93
94
  "role": "domainLink",
94
95
  "label": "Mail domains"
@@ -77,6 +77,7 @@
77
77
  "personalTitle": {
78
78
  "type": "pointer",
79
79
  "branch": ["ou=twakeTitle,ou=nomenclature,__LDAP_BASE__"],
80
+ "referentialIntegrity": "restrict",
80
81
  "required": false,
81
82
  "label": "Title"
82
83
  },
@@ -203,6 +204,7 @@
203
204
  "twakeAccountStatus": {
204
205
  "type": "pointer",
205
206
  "branch": ["ou=twakeAccountStatus,ou=nomenclature,__LDAP_BASE__"],
207
+ "referentialIntegrity": "restrict",
206
208
  "required": true,
207
209
  "generated": true,
208
210
  "role": "accountStatus",
@@ -220,6 +222,7 @@
220
222
  "type": "pointer",
221
223
  "branch": ["ou=twakeDeliveryMode,ou=nomenclature,__LDAP_BASE__"]
222
224
  },
225
+ "referentialIntegrity": "restrict",
223
226
  "required": true,
224
227
  "generated": true,
225
228
  "default": [
@@ -82,6 +82,7 @@
82
82
  "businessCategory": {
83
83
  "type": "pointer",
84
84
  "branch": ["ou=twakeListType,ou=nomenclature,__LDAP_BASE__"],
85
+ "referentialIntegrity": "restrict",
85
86
  "required": false,
86
87
  "role": "writePolicy",
87
88
  "label": {
@@ -98,6 +99,7 @@
98
99
  "twakeMailboxType": {
99
100
  "type": "pointer",
100
101
  "branch": ["ou=twakeMailboxType,ou=nomenclature,__LDAP_BASE__"],
102
+ "referentialIntegrity": "restrict",
101
103
  "group": {
102
104
  "en": "Mailbox Settings",
103
105
  "fr": "Paramètres de messagerie",
@@ -123,6 +123,7 @@
123
123
  "type": "pointer",
124
124
  "branch": ["ou=domains,ou=nomenclature,__LDAP_BASE__"]
125
125
  },
126
+ "referentialIntegrity": "restrict",
126
127
  "required": false,
127
128
  "role": "domainLink",
128
129
  "label": {
@@ -80,7 +80,9 @@
80
80
  }
81
81
  },
82
82
  "twakeMailboxType": {
83
- "type": "string",
83
+ "type": "pointer",
84
+ "branch": ["ou=twakeMailboxType,ou=nomenclature,__LDAP_BASE__"],
85
+ "referentialIntegrity": "restrict",
84
86
  "default": "cn=teamMailbox,ou=twakeMailboxType,ou=nomenclature,__LDAP_BASE__",
85
87
  "fixed": true,
86
88
  "required": true,
@@ -124,6 +124,7 @@
124
124
  "personalTitle": {
125
125
  "type": "pointer",
126
126
  "branch": ["ou=twakeTitle,ou=nomenclature,__LDAP_BASE__"],
127
+ "referentialIntegrity": "restrict",
127
128
  "required": false,
128
129
  "label": {
129
130
  "en": "Title",
@@ -324,6 +325,7 @@
324
325
  "twakeAccountStatus": {
325
326
  "type": "pointer",
326
327
  "branch": ["ou=twakeAccountStatus,ou=nomenclature,__LDAP_BASE__"],
328
+ "referentialIntegrity": "restrict",
327
329
  "required": true,
328
330
  "generated": true,
329
331
  "role": "accountStatus",
@@ -346,6 +348,7 @@
346
348
  "type": "pointer",
347
349
  "branch": ["ou=twakeDeliveryMode,ou=nomenclature,__LDAP_BASE__"]
348
350
  },
351
+ "referentialIntegrity": "restrict",
349
352
  "required": true,
350
353
  "generated": true,
351
354
  "default": [