ldap-rest 0.9.0 → 0.11.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/bin/index.mjs +1 -1
- package/dist/abstract/ldapFlat.js +1 -1
- package/dist/config/args.js +1 -1
- package/dist/lib/authz/base.js +1 -1
- package/dist/lib/changeContext.js +1 -0
- package/dist/lib/errors.js +1 -1
- package/dist/lib/ldapActions.js +1 -1
- package/dist/plugins/auth/authzLinid1.js +1 -1
- package/dist/plugins/auth/authzPerBranch.js +1 -1
- package/dist/plugins/auth/fake.js +1 -0
- package/dist/plugins/ldap/departmentSync.js +1 -1
- package/dist/plugins/ldap/groups.js +1 -1
- package/dist/plugins/ldap/onChange.js +1 -1
- package/dist/plugins/ldap/organizations.js +1 -1
- package/dist/plugins/ldap/trash.js +1 -1
- package/dist/plugins/priority.json.js +1 -1
- package/dist/plugins/scim/scim.js +1 -1
- package/dist/plugins/twake/appAccountsApi.js +1 -1
- package/dist/plugins/twake/appAccountsConsistency.js +1 -1
- package/dist/plugins/twake/clouderyProvision.js +1 -1
- package/dist/plugins/twake/cozyProvision.js +1 -1
- package/dist/src/abstract/plugin.d.ts +6 -0
- package/dist/src/config/args.d.ts +5 -3
- package/dist/src/hooks.d.ts +15 -6
- package/dist/src/lib/authz/base.d.ts +32 -0
- package/dist/src/lib/changeContext.d.ts +24 -0
- package/dist/src/lib/errors.d.ts +15 -0
- package/dist/src/lib/ldapActions.d.ts +2 -1
- package/dist/src/plugins/auth/authzLinid1.d.ts +14 -0
- package/dist/src/plugins/auth/authzPerBranch.d.ts +35 -0
- package/dist/src/plugins/auth/fake.d.ts +21 -0
- package/dist/src/plugins/ldap/groups.d.ts +13 -8
- package/dist/src/plugins/ldap/onChange.d.ts +17 -8
- package/dist/src/plugins/ldap/organizations.d.ts +2 -2
- package/docs/plugin-development/README.md +1 -18
- package/docs/plugin-development/hooks.md +6 -0
- package/docs/plugin-development/testing.md +1 -17
- package/docs/usage/configuration.md +15 -6
- package/docs/usage/plugins/auth/README.md +2 -0
- package/docs/usage/plugins/auth/authz-linid1.md +29 -34
- package/docs/usage/plugins/auth/authz-per-branch.md +33 -11
- package/docs/usage/plugins/auth/authz-scope.md +5 -0
- package/docs/usage/plugins/auth/fake.md +28 -0
- package/docs/usage/plugins/ldap/on-change.md +51 -284
- package/docs/usage/troubleshooting.md +1 -9
- package/docs/usage/upgrading.md +93 -0
- package/package.json +5 -1
|
@@ -1,33 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module core/onLdapChange
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Publish the state of an entry before and after each write
|
|
4
|
+
* (onLdapEntryChange), and the hooks derived from it: onLdapChange,
|
|
5
|
+
* onLdapMailChange…
|
|
6
6
|
* @author Xavier Guimard <xguimard@linagora.com>
|
|
7
7
|
*/
|
|
8
8
|
import { Entry } from 'ldapts';
|
|
9
9
|
import DmPlugin, { type Role } from '../../abstract/plugin';
|
|
10
10
|
import type { Hooks } from '../../hooks';
|
|
11
11
|
import type { AttributeValue } from '../../lib/ldapActions';
|
|
12
|
+
import type { ChangeContext } from '../../lib/changeContext';
|
|
12
13
|
export type ChangesToNotify = Record<string, [
|
|
13
14
|
AttributeValue | null,
|
|
14
15
|
AttributeValue | null
|
|
15
16
|
]>;
|
|
17
|
+
/**
|
|
18
|
+
* The attributes whose values differ between two states of an entry. Values
|
|
19
|
+
* are compared as sets, so a single value and a one-element array are equal,
|
|
20
|
+
* and so are two orderings of the same values.
|
|
21
|
+
*/
|
|
22
|
+
export declare function diffEntries(before: Entry | null, after: Entry | null): ChangesToNotify;
|
|
16
23
|
declare class OnLdapChange extends DmPlugin {
|
|
17
24
|
name: string;
|
|
18
25
|
roles: Role[];
|
|
19
26
|
stack: Record<number, Entry>;
|
|
20
27
|
pendingDeletions: Map<string, Entry>;
|
|
28
|
+
pendingRenames: Map<string, Entry>;
|
|
21
29
|
hooks: Hooks;
|
|
22
|
-
|
|
30
|
+
read(dn: string): Promise<Entry | undefined>;
|
|
31
|
+
publish(dn: string, before: Entry | null, after: Entry | null, context?: ChangeContext): void;
|
|
32
|
+
notify(dn: string, changes: ChangesToNotify, before: Entry | null, after: Entry | null): void;
|
|
23
33
|
notifyAttributeChange(attribute: string, hookName: keyof Hooks, dn: string, changes: ChangesToNotify, stringOnly?: boolean): void;
|
|
24
|
-
notifyAttributeChangeWithMail(attribute: string, hookName: keyof Hooks, dn: string, changes: ChangesToNotify):
|
|
34
|
+
notifyAttributeChangeWithMail(attribute: string, hookName: keyof Hooks, dn: string, changes: ChangesToNotify, after: Entry | null): void;
|
|
25
35
|
/**
|
|
26
36
|
* Reconstruct display name from cn, givenName, and sn attributes
|
|
27
|
-
* @param
|
|
28
|
-
* @param index - 0 for old value, 1 for new value
|
|
37
|
+
* @param entry - The entry, before or after the change
|
|
29
38
|
* @returns The reconstructed display name or null
|
|
30
39
|
*/
|
|
31
|
-
reconstructDisplayName(
|
|
40
|
+
reconstructDisplayName(entry: Entry | null): string | null;
|
|
32
41
|
}
|
|
33
42
|
export default OnLdapChange;
|
|
@@ -214,7 +214,7 @@ export default class LdapOrganizations extends DmPlugin {
|
|
|
214
214
|
*/
|
|
215
215
|
private linkedEntities;
|
|
216
216
|
addOrganization(dn: string, entry: AttributesList, req?: Request): Promise<boolean>;
|
|
217
|
-
modifyOrganization(dn: string, changes: ModifyRequest): Promise<boolean>;
|
|
217
|
+
modifyOrganization(dn: string, changes: ModifyRequest, req?: Request): Promise<boolean>;
|
|
218
218
|
validateNewOrganization(dn: string, entry: AttributesList): Promise<boolean>;
|
|
219
219
|
/**
|
|
220
220
|
* Build the rejection message for a value that failed its schema `test`,
|
|
@@ -241,7 +241,7 @@ export default class LdapOrganizations extends DmPlugin {
|
|
|
241
241
|
moveOrganization(dn: string, targetOrgDn: string, req?: Request): Promise<{
|
|
242
242
|
newDn: string;
|
|
243
243
|
}>;
|
|
244
|
-
deleteOrganization(dn: string): Promise<boolean>;
|
|
244
|
+
deleteOrganization(dn: string, req?: Request): Promise<boolean>;
|
|
245
245
|
/**
|
|
246
246
|
* The same two searches as `getOrganisationSubnodes`, narrowed by a query.
|
|
247
247
|
*
|
|
@@ -1283,24 +1283,7 @@ describe('NotificationPlugin', function () {
|
|
|
1283
1283
|
|
|
1284
1284
|
### Running Tests
|
|
1285
1285
|
|
|
1286
|
-
|
|
1287
|
-
# Set up test environment
|
|
1288
|
-
cat > ~/.test-env <<EOF
|
|
1289
|
-
export DM_LDAP_URL="ldap://localhost:389"
|
|
1290
|
-
export DM_LDAP_DN="cn=admin,dc=example,dc=com"
|
|
1291
|
-
export DM_LDAP_PWD="admin"
|
|
1292
|
-
export DM_LDAP_BASE="dc=example,dc=com"
|
|
1293
|
-
EOF
|
|
1294
|
-
|
|
1295
|
-
# Run all tests
|
|
1296
|
-
source ~/.test-env && npm test
|
|
1297
|
-
|
|
1298
|
-
# Run specific test file
|
|
1299
|
-
source ~/.test-env && npm run test:one test/plugins/notification/webhook.test.ts
|
|
1300
|
-
|
|
1301
|
-
# Run tests in watch mode
|
|
1302
|
-
source ~/.test-env && npm run test:dev
|
|
1303
|
-
```
|
|
1286
|
+
See [Tests and checks](../../CONTRIBUTING.md#tests-and-checks).
|
|
1304
1287
|
|
|
1305
1288
|
### Test Best Practices
|
|
1306
1289
|
|
|
@@ -16,3 +16,9 @@ Typescript definitions into [hooks.ts](./src/hooks.ts)
|
|
|
16
16
|
- **ldapmodifyrequest**: called before any ldapmodify
|
|
17
17
|
- **ldapdeleterequest**: called before any ldapdelete
|
|
18
18
|
- **ldaprenamerequest**: called before any ldap rename/modifyDN operation
|
|
19
|
+
|
|
20
|
+
## [onChange](../usage/plugins/ldap/on-change.md) hooks
|
|
21
|
+
|
|
22
|
+
- **onLdapEntryChange**: called after any write, with the entry before and
|
|
23
|
+
after it, and who made it
|
|
24
|
+
- **onLdapChange**, **onLdapMailChange**…: derived from it
|
|
@@ -76,23 +76,7 @@ Loading initial LDIF from .../base-structure.ldif...
|
|
|
76
76
|
|
|
77
77
|
### Running Tests
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
npm test
|
|
83
|
-
npm run test:one test/plugins/ldap/flatGeneric.test.ts
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
**Using external LDAP:**
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
export DM_LDAP_URL=ldap://localhost:389
|
|
90
|
-
export DM_LDAP_DN=cn=admin,dc=example,dc=com
|
|
91
|
-
export DM_LDAP_PWD=secret
|
|
92
|
-
export DM_LDAP_BASE=dc=example,dc=com
|
|
93
|
-
|
|
94
|
-
npm test
|
|
95
|
-
```
|
|
79
|
+
See [Tests and checks](../../CONTRIBUTING.md#tests-and-checks).
|
|
96
80
|
|
|
97
81
|
### In CI/CD
|
|
98
82
|
|
|
@@ -25,6 +25,7 @@ All LDAP-Rest configuration options.
|
|
|
25
25
|
- [core/auth/hmac](#coreauthhmac)
|
|
26
26
|
- [core/auth/llng](#coreauthllng)
|
|
27
27
|
- [core/auth/openidconnect](#coreauthopenidconnect)
|
|
28
|
+
- [core/auth/fake](#coreauthfake)
|
|
28
29
|
- [core/bcl](#corebcl)
|
|
29
30
|
- [Authorization Plugins](#authorization-plugins)
|
|
30
31
|
- [Common to every authorization plugin](#common-to-every-authorization-plugin)
|
|
@@ -101,7 +102,7 @@ export DM_PLUGINS="core/auth/token,core/ldap/flatGeneric,core/ldap/groups"
|
|
|
101
102
|
| `--ldap-url` | `--ldap-urls` | `DM_LDAP_URL` | `ldap://localhost` | LDAP server URL(s) |
|
|
102
103
|
| `--ldap-dn` | | `DM_LDAP_DN` | `cn=admin,dc=example,dc=com` | Bind DN |
|
|
103
104
|
| `--ldap-pwd` | | `DM_LDAP_PWD` | `admin` | Password |
|
|
104
|
-
| `--ldap-base` | | `DM_LDAP_BASE` | | Base DN for searches
|
|
105
|
+
| `--ldap-base` | | `DM_LDAP_BASE` | | Base DN for searches (required) |
|
|
105
106
|
| `--ldap-user-main-attribute` | | `DM_LDAP_USER_ATTRIBUTE` | `uid` | User identifier attribute |
|
|
106
107
|
| `--ldap-cache-max` | | `DM_LDAP_CACHE_MAX` | `1000` | Max cache entries |
|
|
107
108
|
| `--ldap-cache-ttl` | | `DM_LDAP_CACHE_TTL` | `0` | Search cache TTL (seconds), `0` disables it |
|
|
@@ -238,6 +239,14 @@ Empty means the plugin guards every path. Scoping several instances to different
|
|
|
238
239
|
| -------------- | --------------- | ---------------- | ------- | --------------------- |
|
|
239
240
|
| `--auth-token` | `--auth-tokens` | `DM_AUTH_TOKENS` | `[]` | Authentication tokens |
|
|
240
241
|
|
|
242
|
+
#### `core/auth/fake`
|
|
243
|
+
|
|
244
|
+
| CLI | Env | Default | Description |
|
|
245
|
+
| ------------------ | ------------------- | ------- | ---------------------------------------------- |
|
|
246
|
+
| `--auth-fake-user` | `DM_AUTH_FAKE_USER` | | Identity every request is served as (required) |
|
|
247
|
+
|
|
248
|
+
Development only, refused with `NODE_ENV=production` — see [Fake Authentication](plugins/auth/fake.md).
|
|
249
|
+
|
|
241
250
|
#### `core/auth/totp`
|
|
242
251
|
|
|
243
252
|
| CLI | Plural | Env | Default | Description |
|
|
@@ -324,11 +333,11 @@ whichever of the two names it selects. A value other than `req.user` or
|
|
|
324
333
|
|
|
325
334
|
#### `core/auth/authzPerBranch`
|
|
326
335
|
|
|
327
|
-
| CLI | Env | Default | Description
|
|
328
|
-
| --------------------------------- | ---------------------------------- | ------------------------------------------------ |
|
|
329
|
-
| `--authz-per-branch-config` | `DM_AUTHZ_PER_BRANCH_CONFIG` | `{default:{read:true,write:false,delete:false}}` | Authorization config (JSON)
|
|
330
|
-
| `--authz-per-branch-cache-ttl` | `DM_AUTHZ_PER_BRANCH_CACHE_TTL` | `60` | Cache TTL (seconds)
|
|
331
|
-
| `--authz-filter-attached-entries` | `DM_AUTHZ_FILTER_ATTACHED_ENTRIES` | `false` | Judge an attached entry by the organization it hangs off, and filter listings accordingly. Honoured by `authzPerBranch
|
|
336
|
+
| CLI | Env | Default | Description |
|
|
337
|
+
| --------------------------------- | ---------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
338
|
+
| `--authz-per-branch-config` | `DM_AUTHZ_PER_BRANCH_CONFIG` | `{default:{read:true,write:false,delete:false}}` | Authorization config (JSON) |
|
|
339
|
+
| `--authz-per-branch-cache-ttl` | `DM_AUTHZ_PER_BRANCH_CACHE_TTL` | `60` | Cache TTL (seconds) |
|
|
340
|
+
| `--authz-filter-attached-entries` | `DM_AUTHZ_FILTER_ATTACHED_ENTRIES` | `false` | Judge an attached entry by the organization it hangs off, and filter listings accordingly. Honoured by `authzPerBranch`; always on under `authzLinid1`; does nothing under `authzDynamic`. |
|
|
332
341
|
|
|
333
342
|
#### `core/auth/authzScope`
|
|
334
343
|
|
|
@@ -11,6 +11,7 @@ LDAP-Rest provides multiple authentication plugins to secure API access. These p
|
|
|
11
11
|
| [HMAC](hmac.md) | `core/auth/hmac` | HMAC-SHA256 request signing for backend services |
|
|
12
12
|
| [LemonLDAP::NG](llng.md) | `core/auth/llng` | Integration with LemonLDAP::NG SSO |
|
|
13
13
|
| [OpenID Connect](oidc.md) | `core/auth/openidconnect` | OAuth 2.0 / OpenID Connect authentication |
|
|
14
|
+
| [Fake](fake.md) | `core/auth/fake` | Development only: a fixed identity, no login |
|
|
14
15
|
|
|
15
16
|
## What a rule is keyed on
|
|
16
17
|
|
|
@@ -25,6 +26,7 @@ of what a caller is called. Every one of them publishes the caller twice:
|
|
|
25
26
|
| `core/auth/llng` | LLNG's `whatToTrace` | `--llng-username-header`, or the same |
|
|
26
27
|
| `core/auth/openidconnect` | the OIDC `sub` | `--oidc-username-claim` (default: `sub`) |
|
|
27
28
|
| `core/auth/authzDynamic` | the token's tenant | the same |
|
|
29
|
+
| `core/auth/fake` | `--auth-fake-user` | the same |
|
|
28
30
|
|
|
29
31
|
`req.user` is this server's identifier for the caller; `req.userName` is the
|
|
30
32
|
caller under a name a person would use. `--authz-identity` says which one is
|
|
@@ -48,6 +48,24 @@ twakeDepartmentLink: ou=HR,ou=organization,dc=example,dc=com
|
|
|
48
48
|
|
|
49
49
|
The HR admin (listed in `ou=HR`'s `twakeLocalAdminLink`) can manage John even though he's in `ou=users` because his `twakeDepartmentLink` points to the HR organization.
|
|
50
50
|
|
|
51
|
+
### What an administrator reads
|
|
52
|
+
|
|
53
|
+
`authzLinid1` always judges an attached entry this way, as
|
|
54
|
+
`--authz-filter-attached-entries` does for `authzPerBranch`, whatever that
|
|
55
|
+
option says: judged by its parent instead, an account in a flat `ou=users`
|
|
56
|
+
belongs to nobody's branch, and no administrator could list or change one.
|
|
57
|
+
|
|
58
|
+
- Listings of accounts and groups drop the entries attached to an
|
|
59
|
+
organization outside the caller's branches. A single read of one answers
|
|
60
|
+
`404`.
|
|
61
|
+
- Entries attached to nothing — the organizations, the nomenclature values, a
|
|
62
|
+
group or an account with no `twakeDepartmentLink` — are readable by every
|
|
63
|
+
administrator: the organization tree is reference data. Writes to them are
|
|
64
|
+
judged on their parent. The tree a console draws still starts at the
|
|
65
|
+
caller's own branches: see [getOrganisationTop Hook](#getorganisationtop-hook).
|
|
66
|
+
- A caller named in no `twakeLocalAdminLink` is refused every read (`403`)
|
|
67
|
+
but the top organization's own entry, which `organizations/top` answers.
|
|
68
|
+
|
|
51
69
|
## Configuration
|
|
52
70
|
|
|
53
71
|
No configuration file needed - permissions are read from LDAP. Simply load the plugin:
|
|
@@ -75,19 +93,20 @@ works behind an authenticator that publishes what the directory calls a user:
|
|
|
75
93
|
| `core/auth/openidconnect` | the OIDC `sub` | **no** — an opaque provider identifier |
|
|
76
94
|
|
|
77
95
|
An identity that does not resolve is **refused** (403), and the refusal is
|
|
78
|
-
logged with the plugin's name
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
and
|
|
96
|
+
logged with the plugin's name; `--authz-unresolved-user` sets it otherwise
|
|
97
|
+
([notes](../../upgrading.md#an-identity-that-does-not-resolve-is-refused)).
|
|
98
|
+
An identity naming **several** entries is refused (403) whatever that
|
|
99
|
+
option says, and a warning gives the number of entries. A lookup the
|
|
100
|
+
directory fails fails the request and is not cached.
|
|
82
101
|
|
|
83
102
|
Both answers are cached for the plugin's TTL (5 minutes), negatives
|
|
84
103
|
included, so a mismatch does not turn every request into a directory search.
|
|
85
104
|
The TTL cuts both ways: an identity added to the directory after being
|
|
86
105
|
refused waits it out, and an administrator whose **own entry is renamed or
|
|
87
106
|
moved** keeps resolving to the former DN — where no organization names them,
|
|
88
|
-
so their own operations are refused. A rename
|
|
89
|
-
this server drops what was resolved; a change made
|
|
90
|
-
waits out the TTL.
|
|
107
|
+
so their own operations are refused. A rename, a move to the trash or a
|
|
108
|
+
delete performed through this server drops what was resolved; a change made
|
|
109
|
+
directly in the directory waits out the TTL.
|
|
91
110
|
|
|
92
111
|
`GET /api/v1/authz/scope` resolves the identity outside that cache, so
|
|
93
112
|
during such a window it can describe a scope the hooks no longer grant.
|
|
@@ -178,8 +197,9 @@ Intercepts all LDAP search operations:
|
|
|
178
197
|
2. Resolves user's DN from username
|
|
179
198
|
3. Allows base scope searches on top organization (for navigation)
|
|
180
199
|
4. Allows searches for `twakeLocalAdminLink` (for permission refresh)
|
|
181
|
-
5.
|
|
182
|
-
|
|
200
|
+
5. Refuses a caller who administers no branch; lets the others search, and
|
|
201
|
+
drops from the answer the entries attached outside their branches
|
|
202
|
+
(`ldapsearchfilter`)
|
|
183
203
|
|
|
184
204
|
**Special allowances:**
|
|
185
205
|
|
|
@@ -460,31 +480,6 @@ User jdoe not found in LDAP
|
|
|
460
480
|
|
|
461
481
|
3. Verify authentication plugin sets `req.user` correctly
|
|
462
482
|
|
|
463
|
-
### Problem: Cannot Search Top Organization
|
|
464
|
-
|
|
465
|
-
**Symptoms:**
|
|
466
|
-
|
|
467
|
-
```json
|
|
468
|
-
{
|
|
469
|
-
"error": "User admin does not have read permission for branch ou=organization,dc=example,dc=com"
|
|
470
|
-
}
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
**Solutions:**
|
|
474
|
-
|
|
475
|
-
This is expected behavior - users can only see organizations they manage. To grant access to top organization:
|
|
476
|
-
|
|
477
|
-
```bash
|
|
478
|
-
ldapsearch -x -b "ou=organization,dc=example,dc=com" -s base "(objectClass=*)"
|
|
479
|
-
# Modify to add user:
|
|
480
|
-
ldapmodify <<EOF
|
|
481
|
-
dn: ou=organization,dc=example,dc=com
|
|
482
|
-
changetype: modify
|
|
483
|
-
add: twakeLocalAdminLink
|
|
484
|
-
twakeLocalAdminLink: uid=admin,ou=users,dc=example,dc=com
|
|
485
|
-
EOF
|
|
486
|
-
```
|
|
487
|
-
|
|
488
483
|
## Integration Examples
|
|
489
484
|
|
|
490
485
|
### With Token Authentication
|
|
@@ -174,10 +174,20 @@ Modifies the organization tree API:
|
|
|
174
174
|
|
|
175
175
|
Groups are resolved dynamically:
|
|
176
176
|
|
|
177
|
-
1.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
177
|
+
1. Resolves the caller to its DN, searching
|
|
178
|
+
`(<ldap_user_main_attribute>=<uid>)` under the LDAP base (`--ldap-base`)
|
|
179
|
+
2. Searches for all groups where that DN is a member
|
|
180
|
+
3. Matches each group found against the `groups` keys **as DNs**: case and
|
|
181
|
+
the spaces around `,` and `=` do not matter, so
|
|
182
|
+
`CN=Editors, OU=Groups,dc=example,dc=com` names the same group as
|
|
183
|
+
`cn=editors,ou=groups,dc=example,dc=com`
|
|
184
|
+
4. Caches results (see [Group Caching](#group-caching)); concurrent
|
|
185
|
+
requests for the same user share one lookup
|
|
186
|
+
|
|
187
|
+
A uid that names **more than one** entry gets **no group rule**, since its
|
|
188
|
+
groups cannot be told apart from its homonym's; a warning is logged, and the
|
|
189
|
+
uid's own `users` rule still applies. A lookup that fails answers `500` and
|
|
190
|
+
is not cached.
|
|
181
191
|
|
|
182
192
|
## Sub-branch Inheritance
|
|
183
193
|
|
|
@@ -379,6 +389,10 @@ Group memberships are cached to reduce LDAP queries:
|
|
|
379
389
|
- **Configurable**: `--authz-per-branch-cache-ttl`
|
|
380
390
|
- **Per-user cache**: Each user's groups cached separately
|
|
381
391
|
- **Automatic expiry**: Cache entries expire after TTL
|
|
392
|
+
- **Dropped on writes**: any add, delete, rename or move made through
|
|
393
|
+
ldap-rest empties the cache, and so does a modify touching the group member
|
|
394
|
+
attribute or `--ldap-user-main-attribute`. A change made directly in the
|
|
395
|
+
directory waits for the TTL
|
|
382
396
|
|
|
383
397
|
Cache hit/miss logging:
|
|
384
398
|
|
|
@@ -420,7 +434,6 @@ names, this plugin lets them read and refuses every write. Give it an
|
|
|
420
434
|
|
|
421
435
|
- Group DNs must be fully qualified
|
|
422
436
|
- Group resolution uses LDAP search - ensure proper indexing
|
|
423
|
-
- Cache can delay permission revocation (up to TTL duration)
|
|
424
437
|
|
|
425
438
|
### Authentication Required
|
|
426
439
|
|
|
@@ -531,7 +544,8 @@ Changed permissions don't take effect immediately.
|
|
|
531
544
|
|
|
532
545
|
**Solutions:**
|
|
533
546
|
|
|
534
|
-
1.
|
|
547
|
+
1. A membership changed directly in the directory waits for the
|
|
548
|
+
[group cache](#group-caching) TTL
|
|
535
549
|
2. Restart server to clear all caches
|
|
536
550
|
3. Reduce `--authz-per-branch-cache-ttl` for testing
|
|
537
551
|
|
|
@@ -542,17 +556,25 @@ User should inherit group permissions but doesn't.
|
|
|
542
556
|
|
|
543
557
|
**Solutions:**
|
|
544
558
|
|
|
545
|
-
1. Verify user is member of group:
|
|
559
|
+
1. Verify the user's DN is a member of the group:
|
|
546
560
|
|
|
547
561
|
```bash
|
|
548
|
-
ldapsearch -x -b "
|
|
562
|
+
ldapsearch -x -b "dc=example,dc=com" \
|
|
563
|
+
"(member=uid=jdoe,ou=users,dc=example,dc=com)"
|
|
549
564
|
```
|
|
550
565
|
|
|
551
|
-
2. Check group DN in config
|
|
566
|
+
2. Check the group DN in config names the group: it is compared as a DN
|
|
567
|
+
(case and spaces are ignored), but every RDN must be there
|
|
552
568
|
|
|
553
|
-
3.
|
|
569
|
+
3. Check the uid names a single entry:
|
|
554
570
|
|
|
555
|
-
|
|
571
|
+
```bash
|
|
572
|
+
ldapsearch -x -b "dc=example,dc=com" "(uid=jdoe)" dn
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
4. Ensure group membership uses correct attribute (default: `member`)
|
|
576
|
+
|
|
577
|
+
5. Configure custom member attribute if needed:
|
|
556
578
|
```bash
|
|
557
579
|
--ldap-group-member-attribute uniqueMember
|
|
558
580
|
```
|
|
@@ -150,6 +150,11 @@ identified caller of the same server is told. A server loading no
|
|
|
150
150
|
authorization plugin at all answers `unrestricted` to anyone, anonymous
|
|
151
151
|
included, which is what it does.
|
|
152
152
|
|
|
153
|
+
An identity that names **several** entries — the same `uid` in two branches of
|
|
154
|
+
a directory without the `unique` overlay, which `authzLinid1` refuses with
|
|
155
|
+
`AmbiguousIdentityError` — is answered `403`, not `401`: the caller cannot be
|
|
156
|
+
told apart from the homonym, so no scope can be described for them.
|
|
157
|
+
|
|
153
158
|
## See also
|
|
154
159
|
|
|
155
160
|
- [authz-linid1](authz-linid1.md) — the local-administrator model this serves
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Fake Authentication
|
|
2
|
+
|
|
3
|
+
For development only: every request is served as the identity given on the
|
|
4
|
+
command line, without asking for anything. Restarting with another
|
|
5
|
+
`--auth-fake-user` shows an interface as another administrator sees it.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx ldap-rest \
|
|
9
|
+
--plugin core/auth/fake --auth-fake-user alice \
|
|
10
|
+
--plugin core/auth/authzLinid1 \
|
|
11
|
+
...
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**Environment Variable:** `DM_AUTH_FAKE_USER=alice`
|
|
15
|
+
|
|
16
|
+
The identity goes to `req.user` and `req.userName` alike, so the
|
|
17
|
+
authorization plugins judge it as they would the same login coming from a
|
|
18
|
+
real authenticator.
|
|
19
|
+
|
|
20
|
+
## Safeguards
|
|
21
|
+
|
|
22
|
+
- The server refuses to start when `NODE_ENV` is `production`, as it is in
|
|
23
|
+
the Docker image.
|
|
24
|
+
- It refuses to start without `--auth-fake-user`.
|
|
25
|
+
- A warning naming the identity is logged at every start.
|
|
26
|
+
|
|
27
|
+
Loaded without an authorization plugin, it grants everything to anyone who
|
|
28
|
+
reaches the port. Listen on a local address only.
|