ldap-rest 0.11.0 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abstract/ldapFlat.js +1 -1
- package/dist/bin/index.js +1 -1
- package/dist/config/args.js +1 -1
- package/dist/lib/authz/base.js +1 -1
- package/dist/lib/ldapActions.js +1 -1
- package/dist/lib/ldapSchema.js +1 -1
- package/dist/lib/storage/store.js +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/plugins/auth/authzScope.js +1 -1
- package/dist/plugins/ldap/groups.js +1 -1
- package/dist/plugins/ldap/organizations.js +1 -1
- package/dist/plugins/ldap/raw.js +1 -1
- package/dist/plugins/storage/ldap.js +1 -1
- package/dist/plugins/storage/postgres.js +1 -0
- package/dist/plugins/storage/valkey.js +1 -0
- package/dist/plugins/storage.js +1 -1
- package/dist/src/config/args.d.ts +14 -0
- package/dist/src/lib/authz/base.d.ts +25 -0
- package/dist/src/lib/ldapActions.d.ts +69 -0
- package/dist/src/lib/ldapSchema.d.ts +14 -0
- package/dist/src/lib/storage/store.d.ts +15 -0
- package/dist/src/lib/utils.d.ts +16 -0
- package/dist/src/plugins/ldap/raw.d.ts +3 -6
- package/dist/src/plugins/storage/ldap.d.ts +0 -3
- package/dist/src/plugins/storage/postgres.d.ts +54 -0
- package/dist/src/plugins/storage/valkey.d.ts +45 -0
- package/docs/usage/configuration.md +28 -22
- package/docs/usage/plugins/README.md +5 -5
- package/docs/usage/plugins/auth/authz-linid1.md +34 -1
- package/docs/usage/plugins/ldap/flat-generic.md +26 -0
- package/docs/usage/plugins/ldap/raw.md +1 -1
- package/docs/usage/plugins/utilities/storage.md +61 -10
- package/package.json +12 -1
|
@@ -62,11 +62,11 @@ Connect to external systems:
|
|
|
62
62
|
|
|
63
63
|
## Utility Plugins
|
|
64
64
|
|
|
65
|
-
| Plugin | Description
|
|
66
|
-
| ------------------------------- |
|
|
67
|
-
| [static](utilities/static.md) | Static file server
|
|
68
|
-
| [weblogs](utilities/weblogs.md) | HTTP logging
|
|
69
|
-
| [storage](utilities/storage.md) | Keyed storage with a deadline |
|
|
65
|
+
| Plugin | Description |
|
|
66
|
+
| ------------------------------- | ------------------------------------------------------------------- |
|
|
67
|
+
| [static](utilities/static.md) | Static file server |
|
|
68
|
+
| [weblogs](utilities/weblogs.md) | HTTP logging |
|
|
69
|
+
| [storage](utilities/storage.md) | Keyed storage with a deadline, in LDAP, files, PostgreSQL or Valkey |
|
|
70
70
|
|
|
71
71
|
## Plugin Dependencies
|
|
72
72
|
|
|
@@ -66,6 +66,36 @@ belongs to nobody's branch, and no administrator could list or change one.
|
|
|
66
66
|
- A caller named in no `twakeLocalAdminLink` is refused every read (`403`)
|
|
67
67
|
but the top organization's own entry, which `organizations/top` answers.
|
|
68
68
|
|
|
69
|
+
### Transit: handing an entry over
|
|
70
|
+
|
|
71
|
+
An administrator can only move an entry between organizations they write, and
|
|
72
|
+
an account created by another tool often carries no `twakeDepartmentLink` at
|
|
73
|
+
all. The transit branch covers both cases: `--authz-transit-branch` names an
|
|
74
|
+
organization through which entries change hands.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
--authz-transit-branch "ou=Transit,ou=organization,dc=example,dc=com"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
An entry is **in transit** when it is attached to that organization, or to
|
|
81
|
+
none (with or without the option).
|
|
82
|
+
|
|
83
|
+
- Every administrator sees the entries in transit.
|
|
84
|
+
- **Claiming** one (`POST /api/v1/ldap/users/:id/move` towards an
|
|
85
|
+
organization) asks for write on that organization only.
|
|
86
|
+
- **Putting** one in transit asks for write on the organization it leaves,
|
|
87
|
+
and nothing on the transit branch: an administrator only lets go of what
|
|
88
|
+
they manage. Handing an account over to an organization one does not manage
|
|
89
|
+
is two steps: put it in transit, then its new administrator claims it.
|
|
90
|
+
- Any other change to an entry in transit (attributes, password, status,
|
|
91
|
+
deletion) is judged as before, so the entry is claimed first.
|
|
92
|
+
|
|
93
|
+
`GET /api/v1/authz/scope` returns the transit branch as `transit` (`null`
|
|
94
|
+
when there is none), so that a client can offer these moves. A group attached
|
|
95
|
+
to the transit branch follows the same rules through
|
|
96
|
+
`POST /api/v1/ldap/groups/:cn/move`; that endpoint does not move a group
|
|
97
|
+
attached to no organization.
|
|
98
|
+
|
|
69
99
|
## Configuration
|
|
70
100
|
|
|
71
101
|
No configuration file needed - permissions are read from LDAP. Simply load the plugin:
|
|
@@ -440,7 +470,10 @@ User exists in `ou=users,dc=example,dc=com` but local admin cannot manage them.
|
|
|
440
470
|
twakeDepartmentLink: ou=HR,ou=organization,dc=example,dc=com
|
|
441
471
|
```
|
|
442
472
|
|
|
443
|
-
2. Or
|
|
473
|
+
2. Or let the local admin claim it: an account with no `twakeDepartmentLink`
|
|
474
|
+
is in transit, see [Transit](#transit-handing-an-entry-over).
|
|
475
|
+
|
|
476
|
+
3. Or move user to organization branch:
|
|
444
477
|
```
|
|
445
478
|
From: uid=user,ou=users,dc=example,dc=com
|
|
446
479
|
To: uid=user,ou=users,ou=HR,ou=organization,dc=example,dc=com
|
|
@@ -383,6 +383,32 @@ Content-Type: application/json
|
|
|
383
383
|
}
|
|
384
384
|
```
|
|
385
385
|
|
|
386
|
+
#### Entries created by other tools
|
|
387
|
+
|
|
388
|
+
An entry written by another tool may lack an object class the schema
|
|
389
|
+
declares, and the directory then refuses an attribute that class carries: a
|
|
390
|
+
bare `inetOrgPerson` in the users branch cannot take `twakeDepartmentLink`,
|
|
391
|
+
which `twakeWhitePages` brings. When a modify through LDAP-Rest (this route,
|
|
392
|
+
`/move`, a state change…) writes such an attribute, the **auxiliary** class
|
|
393
|
+
declared for the branch that allows it is added in the same write, provided
|
|
394
|
+
the entry holds, or receives, that class's mandatory attributes. Only what
|
|
395
|
+
the write needs is added, and a write the directory would accept is never
|
|
396
|
+
changed: several entities may share a branch (`users`, `posixAccounts` and
|
|
397
|
+
`sshPublicKeys` all live in `ou=users`) without every entry being given all
|
|
398
|
+
their classes.
|
|
399
|
+
|
|
400
|
+
A missing **structural** class, such as `twakeAccount`, cannot be added to an
|
|
401
|
+
existing entry: the write answers `409`, and the reason is logged once per
|
|
402
|
+
entry. The same applies to groups under `--ldap-group-base` (when it is set)
|
|
403
|
+
with `--group-class`, and to the organization tree with
|
|
404
|
+
`--ldap-organization-class`. Nothing is added when the change names
|
|
405
|
+
`objectClass` itself.
|
|
406
|
+
|
|
407
|
+
The repair reads the directory schema, kept for
|
|
408
|
+
`--ldap-raw-schema-cache-ttl`, and the entry's classes when a written
|
|
409
|
+
attribute belongs to a declared auxiliary class. `DM_LDAP_FLAT_AUTO_REPAIR=false`
|
|
410
|
+
turns it off.
|
|
411
|
+
|
|
386
412
|
### Delete Entry
|
|
387
413
|
|
|
388
414
|
```bash
|
|
@@ -197,7 +197,7 @@ Combine with an authentication plugin ([token](../auth/token.md), [oidc](../auth
|
|
|
197
197
|
|
|
198
198
|
## How It Works
|
|
199
199
|
|
|
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.
|
|
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
202
|
- **Missing entries**: the LDAP "no such object" result (code 32) is mapped to a 404.
|
|
203
203
|
|
|
@@ -26,13 +26,17 @@ which is invisible in a configuration.
|
|
|
26
26
|
--storage-ldap-base ou=Records,dc=example,dc=com
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
| Option | Environment | Default | Description
|
|
30
|
-
| ----------------------------- | ------------------------------ | -------------------- |
|
|
31
|
-
| `--storage-backend` | `DM_STORAGE_BACKEND` | _(none)_ | `ldap` or `
|
|
32
|
-
| `--storage-sweep-interval` | `DM_STORAGE_SWEEP_INTERVAL` | `600` | Seconds between two passes dropping expired records
|
|
33
|
-
| `--storage-ldap-base` | `DM_STORAGE_LDAP_BASE` | _(none)_ | Branch the `ldap` backend writes to
|
|
34
|
-
| `--storage-ldap-object-class` | `DM_STORAGE_LDAP_OBJECT_CLASS` | `applicationProcess` | Object class of the entries it writes
|
|
35
|
-
| `--storage-file-directory` | `DM_STORAGE_FILE_DIRECTORY` | _(none)_ | Directory the `file` backend writes to
|
|
29
|
+
| Option | Environment | Default | Description |
|
|
30
|
+
| ----------------------------- | ------------------------------ | -------------------- | ------------------------------------------------------------------------------- |
|
|
31
|
+
| `--storage-backend` | `DM_STORAGE_BACKEND` | _(none)_ | `ldap`, `file`, `postgres` or `valkey`. Empty means the plugin refuses to start |
|
|
32
|
+
| `--storage-sweep-interval` | `DM_STORAGE_SWEEP_INTERVAL` | `600` | Seconds between two passes dropping expired records |
|
|
33
|
+
| `--storage-ldap-base` | `DM_STORAGE_LDAP_BASE` | _(none)_ | Branch the `ldap` backend writes to |
|
|
34
|
+
| `--storage-ldap-object-class` | `DM_STORAGE_LDAP_OBJECT_CLASS` | `applicationProcess` | Object class of the entries it writes |
|
|
35
|
+
| `--storage-file-directory` | `DM_STORAGE_FILE_DIRECTORY` | _(none)_ | Directory the `file` backend writes to |
|
|
36
|
+
| `--storage-postgres-url` | `DM_STORAGE_POSTGRES_URL` | _(none)_ | Connection string of the `postgres` backend |
|
|
37
|
+
| `--storage-postgres-table` | `DM_STORAGE_POSTGRES_TABLE` | `ldap_rest_storage` | Table it writes to, `schema.table` accepted |
|
|
38
|
+
| `--storage-valkey-url` | `DM_STORAGE_VALKEY_URL` | _(none)_ | URL of the `valkey` backend (`redis://…` or `rediss://…`) |
|
|
39
|
+
| `--storage-valkey-prefix` | `DM_STORAGE_VALKEY_PREFIX` | `ldap-rest:` | Prefix of the keys it writes |
|
|
36
40
|
|
|
37
41
|
`core/storage` is in the priority list, so it is registered before the
|
|
38
42
|
plugins that consume it whatever order they are written in. A consumer that
|
|
@@ -61,18 +65,65 @@ The directory is the plugin's own: nothing else should be writing there, and
|
|
|
61
65
|
the sweeper removes what it finds expired. It is local to one process, so it
|
|
62
66
|
suits a single instance, not a cluster sharing one verdict.
|
|
63
67
|
|
|
68
|
+
### `postgres`
|
|
69
|
+
|
|
70
|
+
One row per record in `--storage-postgres-table`, with the consumer's
|
|
71
|
+
namespace and key kept verbatim beside the value and an `expires_at` column,
|
|
72
|
+
so a `SELECT` tells what a record is about. Needs the optional `pg` package.
|
|
73
|
+
|
|
74
|
+
The table is created at the first connection when it is not there. A role
|
|
75
|
+
that may not create tables works once it exists:
|
|
76
|
+
|
|
77
|
+
```sql
|
|
78
|
+
CREATE TABLE ldap_rest_storage (
|
|
79
|
+
namespace text NOT NULL,
|
|
80
|
+
key text NOT NULL,
|
|
81
|
+
value text NOT NULL,
|
|
82
|
+
expires_at timestamptz NOT NULL,
|
|
83
|
+
PRIMARY KEY (namespace, key)
|
|
84
|
+
);
|
|
85
|
+
CREATE INDEX ldap_rest_storage_expires_at ON ldap_rest_storage (expires_at);
|
|
86
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON ldap_rest_storage TO ldap_rest;
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Several instances can share the table and sweep it at once. PostgreSQL
|
|
90
|
+
refuses a NUL in `text`, so a key holding one cannot be kept: the write
|
|
91
|
+
fails.
|
|
92
|
+
|
|
93
|
+
### `valkey`
|
|
94
|
+
|
|
95
|
+
One key per record, `--storage-valkey-prefix` followed by the namespace and
|
|
96
|
+
the key, expiring with the record: nothing is left to sweep. Works with Redis
|
|
97
|
+
too. Needs the optional `iovalkey` package.
|
|
98
|
+
|
|
99
|
+
**The instance must not evict.** An instance configured as a cache
|
|
100
|
+
(`allkeys-lru`, `volatile-lru` and the like) drops keys under memory pressure,
|
|
101
|
+
and every key this backend writes has a TTL: a Back-Channel Logout mark
|
|
102
|
+
evicted early reopens the session it closed. Use an instance with
|
|
103
|
+
`maxmemory-policy noeviction`, which answers a full memory with a failed
|
|
104
|
+
write — reported to the identity provider — rather than a silent loss.
|
|
105
|
+
|
|
106
|
+
A connection lost while running is retried in the background; meanwhile, reads
|
|
107
|
+
answer "absent" with a warning and writes fail at once instead of waiting.
|
|
108
|
+
|
|
109
|
+
`postgres` and `valkey` connect at startup, and report there an address that
|
|
110
|
+
does not answer or a package that is not installed; the server starts anyway,
|
|
111
|
+
and every operation tries to connect again.
|
|
112
|
+
|
|
64
113
|
## Expiry
|
|
65
114
|
|
|
66
115
|
Two rules the backends do not get to reinterpret:
|
|
67
116
|
|
|
68
117
|
- **Expiry is enforced on read.** A record past its deadline is answered as
|
|
69
118
|
absent whatever the sweeper has done, so a sweeper running late is a
|
|
70
|
-
storage cost, never a wrong answer.
|
|
119
|
+
storage cost, never a wrong answer. Expiry is judged by this server's clock,
|
|
120
|
+
the sweep and the Valkey TTL included, so a database clock running ahead
|
|
121
|
+
cannot drop a record early.
|
|
71
122
|
- **The deadline belongs to the consumer.** How long a record lives is the
|
|
72
123
|
policy of whoever wrote it. The store is told an instant and respects it.
|
|
73
124
|
|
|
74
|
-
Keys are namespaced per consumer, so two of them cannot collide in one branch
|
|
75
|
-
or
|
|
125
|
+
Keys are namespaced per consumer, so two of them cannot collide in one branch,
|
|
126
|
+
directory, table or instance.
|
|
76
127
|
|
|
77
128
|
## For Plugin Authors
|
|
78
129
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ldap-rest",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "Light directory manager",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Identity-Manager"
|
|
@@ -232,6 +232,14 @@
|
|
|
232
232
|
"types": "./dist/src/plugins/storage/ldap.d.ts",
|
|
233
233
|
"import": "./dist/plugins/storage/ldap.js"
|
|
234
234
|
},
|
|
235
|
+
"./plugin-storage-postgres": {
|
|
236
|
+
"types": "./dist/src/plugins/storage/postgres.d.ts",
|
|
237
|
+
"import": "./dist/plugins/storage/postgres.js"
|
|
238
|
+
},
|
|
239
|
+
"./plugin-storage-valkey": {
|
|
240
|
+
"types": "./dist/src/plugins/storage/valkey.d.ts",
|
|
241
|
+
"import": "./dist/plugins/storage/valkey.js"
|
|
242
|
+
},
|
|
235
243
|
"./plugin-twake-appaccountsapi": {
|
|
236
244
|
"types": "./dist/src/plugins/twake/appAccountsApi.d.ts",
|
|
237
245
|
"import": "./dist/plugins/twake/appAccountsApi.js"
|
|
@@ -727,6 +735,7 @@
|
|
|
727
735
|
"@types/mocha": "^10.0.10",
|
|
728
736
|
"@types/multer": "^2.0.0",
|
|
729
737
|
"@types/node": "^24.5.1",
|
|
738
|
+
"@types/pg": "^8.23.1",
|
|
730
739
|
"@types/supertest": "^6.0.3",
|
|
731
740
|
"@typescript-eslint/eslint-plugin": "^8.44.0",
|
|
732
741
|
"@typescript-eslint/parser": "^8.44.0",
|
|
@@ -755,7 +764,9 @@
|
|
|
755
764
|
"optionalDependencies": {
|
|
756
765
|
"@linagora/rabbitmq-client": "^0.1.2",
|
|
757
766
|
"express-openid-connect": "^2.19.2",
|
|
767
|
+
"iovalkey": "^0.4.0",
|
|
758
768
|
"lemonldap-ng-handler": "^0.7.3",
|
|
769
|
+
"pg": "^8.23.0",
|
|
759
770
|
"re2": "^1.22.1"
|
|
760
771
|
},
|
|
761
772
|
"engines": {
|