ldap-rest 0.6.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abstract/ldapFlat.js +1 -1
- package/dist/abstract/plugin.js +1 -1
- package/dist/bin/index.js +1 -1
- package/dist/config/args.js +1 -1
- package/dist/config/schema.js +1 -0
- package/dist/lib/expressFormatedResponses.js +1 -1
- package/dist/lib/ldapActions.js +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/plugins/auth/authzScope.js +1 -0
- package/dist/plugins/auth/llng.js +1 -1
- package/dist/plugins/ldap/accountLifecycle.js +1 -0
- package/dist/plugins/ldap/bulkImport.js +1 -1
- package/dist/plugins/ldap/departmentSync.js +1 -1
- package/dist/plugins/ldap/enterpriseRules.js +1 -0
- package/dist/plugins/ldap/externalUsersInGroups.js +1 -1
- package/dist/plugins/ldap/flatGeneric.js +1 -1
- package/dist/plugins/ldap/groups.js +1 -1
- package/dist/plugins/ldap/organizations.js +1 -1
- package/dist/plugins/scim/bulk.js +1 -1
- package/dist/plugins/scim/errors.js +1 -1
- package/dist/plugins/scim/filter.js +1 -1
- package/dist/plugins/scim/groups.js +1 -1
- package/dist/plugins/scim/list.js +1 -1
- package/dist/plugins/scim/mapping.js +1 -1
- package/dist/plugins/scim/patch.js +1 -1
- package/dist/plugins/scim/projection.js +1 -0
- package/dist/plugins/scim/scim.js +1 -1
- package/dist/plugins/scim/users.js +1 -1
- package/dist/plugins/twake/calendar.js +1 -0
- package/dist/plugins/twake/calendarResources.js +1 -1
- package/dist/plugins/twake/james.js +1 -1
- package/dist/src/abstract/ldapFlat.d.ts +159 -1
- package/dist/src/abstract/plugin.d.ts +13 -0
- package/dist/src/config/args.d.ts +5 -0
- package/dist/src/config/schema.d.ts +382 -12
- package/dist/src/lib/expressFormatedResponses.d.ts +14 -0
- package/dist/src/lib/ldapActions.d.ts +59 -6
- package/dist/src/lib/utils.d.ts +65 -0
- package/dist/src/plugins/auth/authzScope.d.ts +53 -0
- package/dist/src/plugins/auth/llng.d.ts +30 -1
- package/dist/src/plugins/ldap/accountLifecycle.d.ts +56 -0
- package/dist/src/plugins/ldap/departmentSync.d.ts +43 -0
- package/dist/src/plugins/ldap/enterpriseRules.d.ts +436 -0
- package/dist/src/plugins/ldap/flatGeneric.d.ts +29 -0
- package/dist/src/plugins/ldap/groups.d.ts +22 -0
- package/dist/src/plugins/ldap/organizations.d.ts +25 -4
- package/dist/src/plugins/scim/errors.d.ts +17 -4
- package/dist/src/plugins/scim/filter.d.ts +11 -1
- package/dist/src/plugins/scim/groups.d.ts +2 -0
- package/dist/src/plugins/scim/list.d.ts +3 -5
- package/dist/src/plugins/scim/mapping.d.ts +90 -9
- package/dist/src/plugins/scim/patch.d.ts +16 -1
- package/dist/src/plugins/scim/projection.d.ts +15 -0
- package/dist/src/plugins/scim/scim.d.ts +5 -0
- package/dist/src/plugins/scim/users.d.ts +21 -0
- package/dist/src/plugins/twake/calendar.d.ts +113 -0
- package/dist/src/plugins/twake/calendarResources.d.ts +9 -58
- package/docs/README.md +2 -0
- package/docs/client-development/api/openapi.md +1 -1
- package/docs/client-development/browser/libraries.md +1 -1
- package/docs/plugin-development/README.md +22 -0
- package/docs/plugin-development/dependencies.md +10 -7
- package/docs/presentations/en.md +1 -1
- package/docs/presentations/fr.md +1 -1
- package/docs/usage/README.md +2 -0
- package/docs/usage/configuration.md +35 -15
- package/docs/usage/directory-audit.md +105 -0
- package/docs/usage/plugins/README.md +6 -6
- package/docs/usage/plugins/auth/README.md +6 -5
- package/docs/usage/plugins/auth/authz-scope.md +103 -0
- package/docs/usage/plugins/auth/llng.md +17 -0
- package/docs/usage/plugins/integrations/README.md +5 -5
- package/docs/usage/plugins/integrations/{calendar-resources.md → calendar.md} +70 -13
- package/docs/usage/plugins/integrations/scim.md +147 -38
- package/docs/usage/plugins/ldap/README.md +13 -11
- package/docs/usage/plugins/ldap/account-lifecycle.md +129 -0
- package/docs/usage/plugins/ldap/bulk-import.md +8 -1
- package/docs/usage/plugins/ldap/enterprise-rules.md +167 -0
- package/docs/usage/plugins/ldap/flat-generic.md +172 -0
- package/docs/usage/plugins/ldap/groups.md +10 -4
- package/docs/usage/plugins/ldap/organizations.md +59 -33
- package/docs/usage/upgrading.md +444 -0
- package/package.json +54 -5
- package/static/schemas/example/README.md +36 -0
- package/static/schemas/example/groups.json +94 -0
- package/static/schemas/example/nomenclature/domains.json +49 -0
- package/static/schemas/example/organizations.json +106 -0
- package/static/schemas/example/positions.json +35 -0
- package/static/schemas/example/users.json +246 -0
- package/static/schemas/twake/groups.json +81 -15
- package/static/schemas/twake/nomenclature/twakeAccountStatus.json +26 -0
- package/static/schemas/twake/nomenclature/twakeDeliveryMode.json +22 -0
- package/static/schemas/twake/nomenclature/twakeDomain.json +60 -0
- package/static/schemas/twake/nomenclature/twakeListType.json +22 -0
- package/static/schemas/twake/nomenclature/twakeMailboxType.json +22 -0
- package/static/schemas/twake/nomenclature/twakeTitle.json +42 -0
- package/static/schemas/twake/organizations.json +92 -12
- package/static/schemas/twake/positions.json +22 -4
- package/static/schemas/twake/users.json +269 -32
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Auditing a directory before a migration
|
|
2
|
+
|
|
3
|
+
Tightening a validation pattern changes what the server accepts **from now
|
|
4
|
+
on**. It says nothing about the entries already stored: a directory whose
|
|
5
|
+
addresses were never anchored, or whose organization names predate the rule
|
|
6
|
+
that now governs them, loads fine and then refuses the first update of every
|
|
7
|
+
offending entry — which is discovered one support ticket at a time.
|
|
8
|
+
|
|
9
|
+
`npm run audit:directory` reads a branch as it is and reports what the schema
|
|
10
|
+
would refuse, so the work is known before the switch rather than after it.
|
|
11
|
+
|
|
12
|
+
## Running it
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
export DM_LDAP_URL=ldap://directory.example.org
|
|
16
|
+
export DM_LDAP_DN='cn=admin,dc=example,dc=com'
|
|
17
|
+
export DM_LDAP_PWD='…'
|
|
18
|
+
export DM_LDAP_BASE='dc=example,dc=com'
|
|
19
|
+
|
|
20
|
+
npm run audit:directory -- --schema static/schemas/twake/users.json \
|
|
21
|
+
--plugin core/ldap/enterpriseRules
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
| Option | Default | Purpose |
|
|
25
|
+
| ----------------- | ---------------------------- | ----------------------------------- |
|
|
26
|
+
| `--schema` | _(required)_ | Schema to check against |
|
|
27
|
+
| `--base` | the schema's own entity base | Branch to read |
|
|
28
|
+
| `--filter` | `(objectClass=*)` | Narrows what is audited |
|
|
29
|
+
| `--url` | `DM_LDAP_URL` | Directory to read |
|
|
30
|
+
| `--bind-dn` | `DM_LDAP_DN` | Identity to read with |
|
|
31
|
+
| `--bind-password` | `DM_LDAP_PWD` | Its password |
|
|
32
|
+
| `--samples` | `5` | Offending entries named per finding |
|
|
33
|
+
| `--plugin` | _(none)_ | A plugin the server loads, repeated |
|
|
34
|
+
|
|
35
|
+
`--plugin` matters for the attributes a schema marks both `required` and
|
|
36
|
+
`generated`. The server exempts one from its check only when something fills
|
|
37
|
+
it: the entity itself (`generatedFrom`), or a loaded plugin. Name the plugins
|
|
38
|
+
the server loads and the audit exempts what they fill; without them it reports
|
|
39
|
+
those attributes as `missing, and no --plugin fills it` — which is what every
|
|
40
|
+
creation would be refused for on a server that does not load them.
|
|
41
|
+
|
|
42
|
+
It exits `0` when the branch is clean, `1` when the schema would refuse
|
|
43
|
+
something, and `2` when the audit itself could not run — an unreadable schema,
|
|
44
|
+
a directory that would not answer — so a pipeline can tell "dirty" from
|
|
45
|
+
"broken" rather than gating on both alike.
|
|
46
|
+
|
|
47
|
+
`--bind-password` puts the password in `ps` and in the shell history of
|
|
48
|
+
whoever runs it. `DM_LDAP_PWD` is the path to prefer, and the flag is there
|
|
49
|
+
for parity with the server's own `--ldap-pwd`.
|
|
50
|
+
|
|
51
|
+
## What it checks, and what only the server can
|
|
52
|
+
|
|
53
|
+
The audit reads a schema and an entry, so it answers what an entry alone can
|
|
54
|
+
answer: required attributes, the `test` pattern of a value and of an array's
|
|
55
|
+
elements, and the branch a DN must land in. It replaces `__KEY__` placeholders
|
|
56
|
+
and `{config_key}` in `entity.base` from the `DM_*` environment exactly as the
|
|
57
|
+
server does, leaving a placeholder the configuration does not name.
|
|
58
|
+
|
|
59
|
+
What it does not answer needs the rest of the directory, and belongs to a dry
|
|
60
|
+
run rather than here: uniqueness across a namespace, the mail domains an
|
|
61
|
+
organization owns, and anything a plugin computes after validation. A clean
|
|
62
|
+
audit is not a promise that every entry will survive every rule — it is a
|
|
63
|
+
promise that none of them fails on its own contents.
|
|
64
|
+
|
|
65
|
+
## Reading the report
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
12632 entries read, 47 value(s) the schema would refuse:
|
|
69
|
+
|
|
70
|
+
mail: does not match — 41
|
|
71
|
+
expected: Expected an address in the example.org domain, at most 64 characters before the @
|
|
72
|
+
uid=jean.dupont,ou=users,dc=example,dc=com → "jean.dupont@old-domain.example"
|
|
73
|
+
…and 36 more
|
|
74
|
+
|
|
75
|
+
postalCode: does not match — 6
|
|
76
|
+
expected: Expected pattern 9999, A9999 or R9999
|
|
77
|
+
uid=marie.martin,ou=users,dc=example,dc=com → "1234"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Each finding names the attribute, why it was refused, and — from the schema's
|
|
81
|
+
`hint` — what a valid value looks like. The same wording reaches the person
|
|
82
|
+
filling the form, so the fix and the rule are described identically.
|
|
83
|
+
|
|
84
|
+
## What it checks, and what it cannot
|
|
85
|
+
|
|
86
|
+
Checked, because an entry answers for itself:
|
|
87
|
+
|
|
88
|
+
- `test` patterns, on single and multi-valued attributes
|
|
89
|
+
- `required` attributes that are absent — except the ones the server computes
|
|
90
|
+
(`generated`), which an entry is not expected to carry yet
|
|
91
|
+
- the `branch` a `pointer` must land in
|
|
92
|
+
|
|
93
|
+
Not checked, because it takes the whole directory or a live server:
|
|
94
|
+
|
|
95
|
+
- uniqueness across a shared namespace
|
|
96
|
+
- mail addresses against the domains an organization owns
|
|
97
|
+
- anything a hook computes rather than validates
|
|
98
|
+
|
|
99
|
+
For those, run the target configuration against a copy of the directory and
|
|
100
|
+
replay the writes.
|
|
101
|
+
|
|
102
|
+
## See also
|
|
103
|
+
|
|
104
|
+
- [flat-generic](plugins/ldap/flat-generic.md) — where `test` and `hint` live
|
|
105
|
+
- [enterprise-rules](plugins/ldap/enterprise-rules.md) — the rules a dry run covers
|
|
@@ -52,12 +52,12 @@ Protection and rate limiting:
|
|
|
52
52
|
|
|
53
53
|
Connect to external systems:
|
|
54
54
|
|
|
55
|
-
| Plugin
|
|
56
|
-
|
|
|
57
|
-
| [james-mail](integrations/james-mail.md)
|
|
58
|
-
| [james-mailboxes](integrations/james-mailboxes.md)
|
|
59
|
-
| [calendar
|
|
60
|
-
| [app-accounts](integrations/app-accounts.md)
|
|
55
|
+
| Plugin | Description |
|
|
56
|
+
| -------------------------------------------------- | ---------------------------------- |
|
|
57
|
+
| [james-mail](integrations/james-mail.md) | Apache James synchronization |
|
|
58
|
+
| [james-mailboxes](integrations/james-mailboxes.md) | Apache James team mailboxes |
|
|
59
|
+
| [calendar](integrations/calendar.md) | Twake Calendar resources and users |
|
|
60
|
+
| [app-accounts](integrations/app-accounts.md) | Applicative accounts API |
|
|
61
61
|
|
|
62
62
|
## Utility Plugins
|
|
63
63
|
|
|
@@ -14,11 +14,12 @@ LDAP-Rest provides multiple authentication plugins to secure API access. These p
|
|
|
14
14
|
|
|
15
15
|
## Authorization Plugins
|
|
16
16
|
|
|
17
|
-
| Method | Plugin | Description
|
|
18
|
-
| ----------------------------------------------- | -------------------------- |
|
|
19
|
-
| [Authorization Per Branch](authz-per-branch.md) | `core/auth/authzPerBranch` | Branch-level access control
|
|
20
|
-
| [Authorization Per Route](authz-per-route.md) | `core/auth/authzPerRoute` | HTTP route-level ACL
|
|
21
|
-
| [Authorization LinID 1.x](authz-linid1.md) | `core/auth/authzLinid1` | LinID 1.x integration
|
|
17
|
+
| Method | Plugin | Description |
|
|
18
|
+
| ----------------------------------------------- | -------------------------- | ---------------------------- |
|
|
19
|
+
| [Authorization Per Branch](authz-per-branch.md) | `core/auth/authzPerBranch` | Branch-level access control |
|
|
20
|
+
| [Authorization Per Route](authz-per-route.md) | `core/auth/authzPerRoute` | HTTP route-level ACL |
|
|
21
|
+
| [Authorization LinID 1.x](authz-linid1.md) | `core/auth/authzLinid1` | LinID 1.x integration |
|
|
22
|
+
| [Authorization Scope](authz-scope.md) | `core/auth/authzScope` | What the current user may do |
|
|
22
23
|
|
|
23
24
|
## Security Plugins
|
|
24
25
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Authorization Scope Plugin
|
|
2
|
+
|
|
3
|
+
Tells a client what the signed-in administrator may actually do, before it
|
|
4
|
+
offers them a button that will fail.
|
|
5
|
+
|
|
6
|
+
In a local-administration model the scope _is_ the interface: a manager
|
|
7
|
+
administers a few branches of the tree and nothing else, and an application
|
|
8
|
+
that cannot name those branches leaves them guessing. This endpoint answers
|
|
9
|
+
both halves — which branches, and which entities can be created — by asking
|
|
10
|
+
whichever authorization plugin is in force.
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
--plugin core/auth/authzScope
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
It has no options. It answers through whichever loaded plugin carries the
|
|
19
|
+
`authz` role _and_ can say who may do what where — one implementing
|
|
20
|
+
`resolveUser`, `getAuthorizedBranches` and `getUserPermissions`. Both
|
|
21
|
+
[authz-linid1](authz-linid1.md) and [authz-per-branch](authz-per-branch.md)
|
|
22
|
+
do, and this endpoint works with either without knowing which.
|
|
23
|
+
|
|
24
|
+
The role alone is not enough. [authz-per-route](authz-per-route.md) gates URLs
|
|
25
|
+
and [authz-dynamic](authz-dynamic.md) reads a token: both carry the `authz`
|
|
26
|
+
role and implement none of those three methods, so this endpoint passes them
|
|
27
|
+
over — loading one beside a branch-level plugin changes the answer not at all.
|
|
28
|
+
A server whose only authorization plugin is one of those has nothing to
|
|
29
|
+
describe a scope with, and the answer is the unrestricted one shown below: a
|
|
30
|
+
client built on it shows every button, and a refusal, when it comes, comes from
|
|
31
|
+
the route or the token rather than from the scope.
|
|
32
|
+
|
|
33
|
+
## Endpoint
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
GET /api/v1/authz/scope
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"user": "uid=alice,ou=users,dc=example,dc=com",
|
|
42
|
+
"unrestricted": false,
|
|
43
|
+
"branches": [
|
|
44
|
+
{
|
|
45
|
+
"dn": "ou=Sales,ou=organization,dc=example,dc=com",
|
|
46
|
+
"name": "Sales",
|
|
47
|
+
"path": "Acme / Sales",
|
|
48
|
+
"read": true,
|
|
49
|
+
"write": true,
|
|
50
|
+
"delete": true
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"entities": [
|
|
54
|
+
{ "name": "users", "base": "ou=users,dc=example,dc=com", "create": true },
|
|
55
|
+
{ "name": "groups", "base": "ou=groups,dc=example,dc=com", "create": true },
|
|
56
|
+
{
|
|
57
|
+
"name": "organizations",
|
|
58
|
+
"base": "ou=organization,dc=example,dc=com",
|
|
59
|
+
"create": false
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`name` and `path` come from the branch entry itself, so an interface can show
|
|
66
|
+
the scope in the directory's own words rather than as a raw DN.
|
|
67
|
+
|
|
68
|
+
`create` answers "may I add one of these?", on the rule the add hook enforces.
|
|
69
|
+
An entry that can carry the organization link (its schema declares the
|
|
70
|
+
configured link attribute) is scoped by the organization it is attached to, so
|
|
71
|
+
the answer is yes as soon as the caller may write in a branch they administer.
|
|
72
|
+
One that cannot — a position, a nomenclature row — is checked against the
|
|
73
|
+
branch it lands in, so the answer is whether the caller may write there.
|
|
74
|
+
Organizations are the other exception, since they live in the tree itself and
|
|
75
|
+
need write permission on the node they hang from — the top of the tree, which
|
|
76
|
+
is where a new organization goes when the client names no parent. A local administrator of one branch therefore reads
|
|
77
|
+
`create: false` for organizations, and creates sub-organizations under their own
|
|
78
|
+
node by naming it as `parentDn`.
|
|
79
|
+
|
|
80
|
+
With no authorization plugin loaded the server grants everything, and the
|
|
81
|
+
answer says so:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"user": "alice",
|
|
86
|
+
"unrestricted": true,
|
|
87
|
+
"branches": [],
|
|
88
|
+
"entities": [
|
|
89
|
+
{ "name": "users", "base": "ou=users,dc=example,dc=com", "create": true }
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Errors
|
|
95
|
+
|
|
96
|
+
| Status | When |
|
|
97
|
+
| ------ | ------------------------------------------------------------- |
|
|
98
|
+
| `401` | No authenticated user, or a user the directory cannot resolve |
|
|
99
|
+
|
|
100
|
+
## See also
|
|
101
|
+
|
|
102
|
+
- [authz-linid1](authz-linid1.md) — the local-administrator model this serves
|
|
103
|
+
- [authz-per-branch](authz-per-branch.md) — static per-branch permissions
|
|
@@ -21,6 +21,23 @@ DM_LLNG_INI="/etc/lemonldap-ng/lemonldap-ng.ini"
|
|
|
21
21
|
2. **LLNG Configuration**: Valid `lemonldap-ng.ini` file
|
|
22
22
|
3. **Virtual Host Configuration**: LDAP-Rest must be configured as a protected application in LLNG
|
|
23
23
|
|
|
24
|
+
The handler runs inside LDAP-Rest and reads `lemonldap-ng.ini` when the server
|
|
25
|
+
starts. The file tells it where the LLNG configuration is, and which virtual
|
|
26
|
+
hosts it protects — a Node.js handler serves only those listed in
|
|
27
|
+
`nodeVhosts`:
|
|
28
|
+
|
|
29
|
+
```ini
|
|
30
|
+
[configuration]
|
|
31
|
+
type = REST
|
|
32
|
+
baseConfigUrl = https://auth.example.com/index.fcgi/config
|
|
33
|
+
|
|
34
|
+
[node-handler]
|
|
35
|
+
nodeVhosts = api.example.com
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
A file that cannot be read, a configuration that cannot be reached or a
|
|
39
|
+
missing `nodeVhosts` stops the server at startup, with the reason.
|
|
40
|
+
|
|
24
41
|
## How It Works
|
|
25
42
|
|
|
26
43
|
1. Uses the LemonLDAP::NG Handler to validate requests
|
|
@@ -11,10 +11,10 @@ Plugins for connecting LDAP-Rest to external systems.
|
|
|
11
11
|
|
|
12
12
|
## Twake
|
|
13
13
|
|
|
14
|
-
| Plugin
|
|
15
|
-
|
|
|
16
|
-
| [calendar
|
|
17
|
-
| [app-accounts](app-accounts.md)
|
|
14
|
+
| Plugin | Description |
|
|
15
|
+
| ------------------------------- | ---------------------------------------- |
|
|
16
|
+
| [calendar](calendar.md) | Calendar resources and users sync |
|
|
17
|
+
| [app-accounts](app-accounts.md) | Applicative accounts API (devices, apps) |
|
|
18
18
|
|
|
19
19
|
## Prerequisites
|
|
20
20
|
|
|
@@ -37,6 +37,6 @@ James plugins require:
|
|
|
37
37
|
core/twake/james
|
|
38
38
|
└─ requires: core/ldap/onChange
|
|
39
39
|
|
|
40
|
-
core/twake/
|
|
40
|
+
core/twake/calendar
|
|
41
41
|
└─ requires: core/ldap/onChange
|
|
42
42
|
```
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
# Twake Calendar
|
|
1
|
+
# Twake Calendar Plugin
|
|
2
2
|
|
|
3
|
-
Plugin to
|
|
3
|
+
Plugin to keep Twake Calendar in sync with LDAP via its WebAdmin API:
|
|
4
|
+
|
|
5
|
+
- resources (meeting rooms, equipment, etc.) stored in an LDAP branch
|
|
6
|
+
- registered users, whose email, first name and last name follow LDAP
|
|
7
|
+
|
|
8
|
+
> Formerly `core/twake/calendarResources`. That name still loads the plugin,
|
|
9
|
+
> registered as `calendarResources`, but logs a deprecation warning and will
|
|
10
|
+
> be removed in a future major release.
|
|
4
11
|
|
|
5
12
|
## Features
|
|
6
13
|
|
|
@@ -8,6 +15,7 @@ Plugin to automatically synchronize LDAP resources (meeting rooms, equipment, et
|
|
|
8
15
|
- **Automatic Updates**: When a resource is modified in LDAP, changes are synced to Twake Calendar
|
|
9
16
|
- **Automatic Deletion**: When a resource is removed from LDAP, it's deleted from Twake Calendar
|
|
10
17
|
- **Flexible Configuration**: Configure which LDAP branch and objectClass to monitor
|
|
18
|
+
- **User Identity Propagation**: When a user's email, first name or last name changes in LDAP, the matching Calendar registered user is updated
|
|
11
19
|
|
|
12
20
|
## Configuration
|
|
13
21
|
|
|
@@ -15,10 +23,15 @@ Plugin to automatically synchronize LDAP resources (meeting rooms, equipment, et
|
|
|
15
23
|
|
|
16
24
|
- `DM_CALENDAR_WEBADMIN_URL`: URL of the Twake Calendar WebAdmin API (default: `http://localhost:8080`)
|
|
17
25
|
- `DM_CALENDAR_WEBADMIN_TOKEN`: Bearer token for WebAdmin API authentication
|
|
18
|
-
- `DM_CALENDAR_RESOURCE_BASE`: LDAP branch to monitor for resources (e.g., `ou=resources,dc=example,dc=com`)
|
|
19
|
-
- `DM_CALENDAR_RESOURCE_OBJECTCLASS`: Optional objectClass filter
|
|
26
|
+
- `DM_CALENDAR_RESOURCE_BASE`: LDAP branch to monitor for resources, as a full DN (e.g., `ou=resources,dc=example,dc=com`). It is compared to an entry's DN part by part, so a partial value such as `ou=resources` names no branch and matches nothing (the plugin warns at startup when the value is not a DN of the directory).
|
|
27
|
+
- `DM_CALENDAR_RESOURCE_OBJECTCLASS`: Optional objectClass filter, applied when a resource is **created** (see [Resource identity](#resource-identity))
|
|
20
28
|
- `DM_CALENDAR_RESOURCE_CREATOR`: Default creator email for resources (default: `admin@example.com`)
|
|
21
29
|
- `DM_CALENDAR_RESOURCE_DOMAIN`: Default domain for resources (extracted from DN if not specified)
|
|
30
|
+
- `DM_CALENDAR_FIRSTNAME_ATTRIBUTE`: LDAP attribute holding a user's first name (default: `givenName`)
|
|
31
|
+
- `DM_CALENDAR_LASTNAME_ATTRIBUTE`: LDAP attribute holding a user's last name (default: `sn`)
|
|
32
|
+
|
|
33
|
+
The resource variables are only needed for resource synchronization; user
|
|
34
|
+
identity propagation only needs the WebAdmin URL and token.
|
|
22
35
|
|
|
23
36
|
### Required Dependencies
|
|
24
37
|
|
|
@@ -66,6 +79,49 @@ The plugin uses the following WebAdmin API endpoints:
|
|
|
66
79
|
- `POST /resources` - Create a new resource
|
|
67
80
|
- `PATCH /resources/{id}` - Update an existing resource
|
|
68
81
|
- `DELETE /resources/{id}` - Delete a resource
|
|
82
|
+
- `GET /registeredUsers?email={mail}` - Find the registered user to update
|
|
83
|
+
- `PATCH /registeredUsers?id={id}` - Update a registered user's email, first and last name
|
|
84
|
+
- `POST /users/{mail}?action=deleteData` - Delete a user's data (see `deleteUserData`)
|
|
85
|
+
|
|
86
|
+
The resource id goes into the path percent-encoded, so a `/` or a `#` in it
|
|
87
|
+
cannot build a path naming something else. The user's address is left as it is
|
|
88
|
+
written — `user@example.com`, `@` being legal in a path segment — which is what
|
|
89
|
+
`plugins/twake/james` does with the same WebAdmin; whether the two should
|
|
90
|
+
encode it is issue #175.
|
|
91
|
+
|
|
92
|
+
### Registered Users
|
|
93
|
+
|
|
94
|
+
A change of the configured mail attribute updates the registered user found by
|
|
95
|
+
its **previous** address; a change of the first or last name attribute updates
|
|
96
|
+
the one found by its current address. In both cases the email, first name and
|
|
97
|
+
last name are re-read from LDAP and sent together. Adding or removing the mail
|
|
98
|
+
attribute, and users not registered in Calendar, are skipped.
|
|
99
|
+
|
|
100
|
+
Calendar lower-cases the address before looking it up, which is also how it
|
|
101
|
+
stores addresses, so a case difference between LDAP and Calendar does not
|
|
102
|
+
matter. The exception is a legacy record whose stored address kept upper case
|
|
103
|
+
(written without Calendar's normalisation, e.g. migrated data): it is not
|
|
104
|
+
found, and its user is logged as not registered. Calendar releases before
|
|
105
|
+
1.0.0.1 ignore the `email` parameter and answer the full list of registered
|
|
106
|
+
users; the plugin then picks the user from it, which works but costs a full
|
|
107
|
+
listing per change.
|
|
108
|
+
|
|
109
|
+
### Resource identity
|
|
110
|
+
|
|
111
|
+
A resource is known to Calendar by an `id`, which is the value of its LDAP
|
|
112
|
+
entry's own RDN with the escapes removed: `cn=Meeting Room 1,ou=resources,…`
|
|
113
|
+
gives `Meeting Room 1`, and `o=Room 12,ou=resources,…` gives `Room 12`. The
|
|
114
|
+
creation, the update and the deletion hooks all read it that way, so the three
|
|
115
|
+
name the same resource; an entry whose DN yields no value is not synchronised.
|
|
116
|
+
|
|
117
|
+
Only entries in `DM_CALENDAR_RESOURCE_BASE` are synchronised, on all three
|
|
118
|
+
hooks. `DM_CALENDAR_RESOURCE_OBJECTCLASS` filters creations only: a
|
|
119
|
+
modification carries the attributes that changed, so an untouched objectClass
|
|
120
|
+
is not there to compare, and a deleted entry cannot be read any more. The
|
|
121
|
+
branch is the guard that remains on those two paths.
|
|
122
|
+
|
|
123
|
+
Renaming an entry changes its RDN, hence its id: Calendar is not told, and the
|
|
124
|
+
resource keeps its former id there.
|
|
69
125
|
|
|
70
126
|
### Resource Data Format
|
|
71
127
|
|
|
@@ -103,7 +159,7 @@ DM_LDAP_FLAT_SCHEMA="/path/to/calendar-resources-schema.json"
|
|
|
103
159
|
3. Add the plugin to `DM_PLUGINS`:
|
|
104
160
|
|
|
105
161
|
```bash
|
|
106
|
-
DM_PLUGINS="core/ldap/onChange,core/ldap/flatGeneric,twake/
|
|
162
|
+
DM_PLUGINS="core/ldap/onChange,core/ldap/flatGeneric,twake/calendar"
|
|
107
163
|
```
|
|
108
164
|
|
|
109
165
|
4. Start ldap-rest - resources will be automatically synced
|
|
@@ -121,7 +177,7 @@ Example log:
|
|
|
121
177
|
|
|
122
178
|
```json
|
|
123
179
|
{
|
|
124
|
-
"plugin": "
|
|
180
|
+
"plugin": "calendar",
|
|
125
181
|
"event": "ldapcalendarResourceadddone",
|
|
126
182
|
"result": "success",
|
|
127
183
|
"http_status": 201,
|
|
@@ -138,6 +194,7 @@ Example log:
|
|
|
138
194
|
2. Verify that `DM_CALENDAR_WEBADMIN_TOKEN` is valid
|
|
139
195
|
3. Check logs for API errors
|
|
140
196
|
4. Ensure the ldapFlat schema has `entity.name` set to `calendarResource`
|
|
197
|
+
5. Ensure `DM_CALENDAR_RESOURCE_BASE` is the full DN of the branch the entries are in: a partial value matches no entry, and the plugin says so at startup
|
|
141
198
|
|
|
142
199
|
### Authentication errors
|
|
143
200
|
|
|
@@ -145,22 +202,21 @@ Ensure `DM_CALENDAR_WEBADMIN_TOKEN` is set and valid. The token is sent as a Bea
|
|
|
145
202
|
|
|
146
203
|
### Wrong resources being synced
|
|
147
204
|
|
|
148
|
-
Use `DM_CALENDAR_RESOURCE_BASE` and `DM_CALENDAR_RESOURCE_OBJECTCLASS` to filter which LDAP entries are considered resources.
|
|
205
|
+
Use `DM_CALENDAR_RESOURCE_BASE` and `DM_CALENDAR_RESOURCE_OBJECTCLASS` to filter which LDAP entries are considered resources. Note that the objectClass filter only applies to creations, so an entry of the resource entity that must never reach Calendar has to sit outside the branch.
|
|
149
206
|
|
|
150
207
|
## Public Methods
|
|
151
208
|
|
|
152
|
-
Public methods available on the
|
|
209
|
+
Public methods available on the Calendar plugin instance.
|
|
153
210
|
|
|
154
211
|
### Usage
|
|
155
212
|
|
|
156
213
|
```typescript
|
|
157
214
|
import type { DM } from 'ldap-rest';
|
|
158
|
-
import type
|
|
215
|
+
import type Calendar from 'ldap-rest/plugin-twake-calendar';
|
|
159
216
|
|
|
160
217
|
// Get the plugin instance from another plugin
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
) as CalendarResources;
|
|
218
|
+
// (declare `dependencies = { calendar: 'core/twake/calendar' }` so it loads first)
|
|
219
|
+
const calendar = this.requirePlugin<Calendar>('calendar');
|
|
164
220
|
```
|
|
165
221
|
|
|
166
222
|
### deleteUserData(mail)
|
|
@@ -182,7 +238,8 @@ async deleteUserData(mail: string): Promise<{ taskId: string } | null>
|
|
|
182
238
|
**Example:**
|
|
183
239
|
|
|
184
240
|
```typescript
|
|
185
|
-
|
|
241
|
+
// requirePlugin() returns null when the plugin is not loaded
|
|
242
|
+
const result = await calendar?.deleteUserData('user@example.com');
|
|
186
243
|
if (result) {
|
|
187
244
|
console.log(`Deletion task started: ${result.taskId}`);
|
|
188
245
|
}
|