ldap-rest 0.6.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/abstract/ldapFlat.js +1 -1
  2. package/dist/abstract/plugin.js +1 -1
  3. package/dist/bin/index.js +1 -1
  4. package/dist/config/args.js +1 -1
  5. package/dist/config/schema.js +1 -0
  6. package/dist/lib/expressFormatedResponses.js +1 -1
  7. package/dist/lib/ldapActions.js +1 -1
  8. package/dist/lib/utils.js +1 -1
  9. package/dist/plugins/auth/authzScope.js +1 -0
  10. package/dist/plugins/auth/llng.js +1 -1
  11. package/dist/plugins/ldap/accountLifecycle.js +1 -0
  12. package/dist/plugins/ldap/bulkImport.js +1 -1
  13. package/dist/plugins/ldap/departmentSync.js +1 -1
  14. package/dist/plugins/ldap/enterpriseRules.js +1 -0
  15. package/dist/plugins/ldap/externalUsersInGroups.js +1 -1
  16. package/dist/plugins/ldap/flatGeneric.js +1 -1
  17. package/dist/plugins/ldap/groups.js +1 -1
  18. package/dist/plugins/ldap/organizations.js +1 -1
  19. package/dist/plugins/scim/bulk.js +1 -1
  20. package/dist/plugins/scim/errors.js +1 -1
  21. package/dist/plugins/scim/filter.js +1 -1
  22. package/dist/plugins/scim/groups.js +1 -1
  23. package/dist/plugins/scim/list.js +1 -1
  24. package/dist/plugins/scim/mapping.js +1 -1
  25. package/dist/plugins/scim/patch.js +1 -1
  26. package/dist/plugins/scim/projection.js +1 -0
  27. package/dist/plugins/scim/scim.js +1 -1
  28. package/dist/plugins/scim/users.js +1 -1
  29. package/dist/plugins/twake/calendar.js +1 -0
  30. package/dist/plugins/twake/calendarResources.js +1 -1
  31. package/dist/plugins/twake/james.js +1 -1
  32. package/dist/src/abstract/ldapFlat.d.ts +159 -1
  33. package/dist/src/abstract/plugin.d.ts +13 -0
  34. package/dist/src/config/args.d.ts +5 -0
  35. package/dist/src/config/schema.d.ts +382 -12
  36. package/dist/src/lib/expressFormatedResponses.d.ts +14 -0
  37. package/dist/src/lib/ldapActions.d.ts +59 -6
  38. package/dist/src/lib/utils.d.ts +65 -0
  39. package/dist/src/plugins/auth/authzScope.d.ts +53 -0
  40. package/dist/src/plugins/auth/llng.d.ts +30 -1
  41. package/dist/src/plugins/ldap/accountLifecycle.d.ts +56 -0
  42. package/dist/src/plugins/ldap/departmentSync.d.ts +43 -0
  43. package/dist/src/plugins/ldap/enterpriseRules.d.ts +436 -0
  44. package/dist/src/plugins/ldap/flatGeneric.d.ts +29 -0
  45. package/dist/src/plugins/ldap/groups.d.ts +22 -0
  46. package/dist/src/plugins/ldap/organizations.d.ts +25 -4
  47. package/dist/src/plugins/scim/errors.d.ts +17 -4
  48. package/dist/src/plugins/scim/filter.d.ts +11 -1
  49. package/dist/src/plugins/scim/groups.d.ts +2 -0
  50. package/dist/src/plugins/scim/list.d.ts +3 -5
  51. package/dist/src/plugins/scim/mapping.d.ts +90 -9
  52. package/dist/src/plugins/scim/patch.d.ts +16 -1
  53. package/dist/src/plugins/scim/projection.d.ts +15 -0
  54. package/dist/src/plugins/scim/scim.d.ts +5 -0
  55. package/dist/src/plugins/scim/users.d.ts +21 -0
  56. package/dist/src/plugins/twake/calendar.d.ts +113 -0
  57. package/dist/src/plugins/twake/calendarResources.d.ts +9 -58
  58. package/docs/README.md +2 -0
  59. package/docs/client-development/api/openapi.md +1 -1
  60. package/docs/client-development/browser/libraries.md +1 -1
  61. package/docs/plugin-development/README.md +22 -0
  62. package/docs/plugin-development/dependencies.md +10 -7
  63. package/docs/presentations/en.md +1 -1
  64. package/docs/presentations/fr.md +1 -1
  65. package/docs/usage/README.md +2 -0
  66. package/docs/usage/configuration.md +35 -15
  67. package/docs/usage/directory-audit.md +105 -0
  68. package/docs/usage/plugins/README.md +6 -6
  69. package/docs/usage/plugins/auth/README.md +6 -5
  70. package/docs/usage/plugins/auth/authz-scope.md +103 -0
  71. package/docs/usage/plugins/auth/llng.md +17 -0
  72. package/docs/usage/plugins/integrations/README.md +5 -5
  73. package/docs/usage/plugins/integrations/{calendar-resources.md → calendar.md} +70 -13
  74. package/docs/usage/plugins/integrations/scim.md +147 -38
  75. package/docs/usage/plugins/ldap/README.md +13 -11
  76. package/docs/usage/plugins/ldap/account-lifecycle.md +129 -0
  77. package/docs/usage/plugins/ldap/bulk-import.md +8 -1
  78. package/docs/usage/plugins/ldap/enterprise-rules.md +167 -0
  79. package/docs/usage/plugins/ldap/flat-generic.md +172 -0
  80. package/docs/usage/plugins/ldap/groups.md +10 -4
  81. package/docs/usage/plugins/ldap/organizations.md +59 -33
  82. package/docs/usage/upgrading.md +444 -0
  83. package/package.json +54 -5
  84. package/static/schemas/example/README.md +36 -0
  85. package/static/schemas/example/groups.json +94 -0
  86. package/static/schemas/example/nomenclature/domains.json +49 -0
  87. package/static/schemas/example/organizations.json +106 -0
  88. package/static/schemas/example/positions.json +35 -0
  89. package/static/schemas/example/users.json +246 -0
  90. package/static/schemas/twake/groups.json +81 -15
  91. package/static/schemas/twake/nomenclature/twakeAccountStatus.json +26 -0
  92. package/static/schemas/twake/nomenclature/twakeDeliveryMode.json +22 -0
  93. package/static/schemas/twake/nomenclature/twakeDomain.json +60 -0
  94. package/static/schemas/twake/nomenclature/twakeListType.json +22 -0
  95. package/static/schemas/twake/nomenclature/twakeMailboxType.json +22 -0
  96. package/static/schemas/twake/nomenclature/twakeTitle.json +42 -0
  97. package/static/schemas/twake/organizations.json +92 -12
  98. package/static/schemas/twake/positions.json +22 -4
  99. package/static/schemas/twake/users.json +269 -32
@@ -1,7 +1,22 @@
1
1
  import type { AttributesList } from '../../lib/ldapActions';
2
2
  import { type ScimUser, type ScimGroup, type ResourceMapping, type MultiValued } from './types';
3
3
  export declare const DEFAULT_USER_MAPPING: ResourceMapping;
4
+ /**
5
+ * Group mapping.
6
+ *
7
+ * `externalId` is deliberately absent: RFC 7643 section 3.1 defines it as the
8
+ * *provisioning client's* identifier, so it cannot be served from a
9
+ * server-assigned value. groupOfNames has no attribute meant to hold one
10
+ * either, so the deployment names one with
11
+ * `--scim-group-external-id-attribute` and the entry is added at runtime by
12
+ * `withExternalId()`.
13
+ */
4
14
  export declare const DEFAULT_GROUP_MAPPING: ResourceMapping;
15
+ /**
16
+ * Add the `externalId` entry when the deployment named an attribute to store
17
+ * it in. An entry already present — from a mapping override — wins.
18
+ */
19
+ export declare function withExternalId(mapping: ResourceMapping, ldapAttribute: string): ResourceMapping;
5
20
  /**
6
21
  * LDAP GeneralizedTime (RFC 4517 §3.3.13) → SCIM DateTime (RFC 7643 §2.3.5,
7
22
  * an `xsd:dateTime`).
@@ -31,7 +46,81 @@ export interface MappingContext {
31
46
  resourceType: 'User' | 'Group';
32
47
  baseUrl?: string;
33
48
  scimPrefix: string;
49
+ /**
50
+ * LDAP attribute whose *presence* marks the account as locked, and the
51
+ * value written to lock it. Both come from `resolveLockConfig()`, which is
52
+ * the only place the defaults live: re-defaulting here would let a context
53
+ * built outside the plugin reach the directory with a pair the startup
54
+ * guard never saw. Groups have no `active`, so they pass empty strings.
55
+ */
56
+ lockAttribute: string;
57
+ lockValue: string;
34
58
  }
59
+ /**
60
+ * SCIM `active` (RFC 7643 section 4.1.1) has no direct LDAP equivalent. We
61
+ * model it on the presence of one attribute: present means locked, absent
62
+ * means active.
63
+ *
64
+ * The default is the ppolicy overlay's `pwdAccountLockedTime`, whose
65
+ * conventional "locked forever" value is a GeneralizedTime in the distant
66
+ * past. A directory without that overlay points
67
+ * `--scim-user-lock-attribute` / `--scim-user-lock-value` somewhere else
68
+ * (`nsAccountLock` / `TRUE`, for instance).
69
+ */
70
+ export declare const DEFAULT_LOCK_ATTRIBUTE = "pwdAccountLockedTime";
71
+ export declare const DEFAULT_LOCK_VALUE = "000001010000Z";
72
+ /**
73
+ * Where the configuration warnings go. A seam rather than a direct
74
+ * `console.warn` so the unit tests can read what was said instead of
75
+ * asserting on stderr.
76
+ */
77
+ export declare let warn: (message: string) => void;
78
+ /** Testing seam: replace the warning sink, and get the previous one back. */
79
+ export declare function setLockConfigWarn(sink: (message: string) => void): (message: string) => void;
80
+ /**
81
+ * Settle the lock configuration, or refuse to start.
82
+ *
83
+ * Ways to get a deployment that answers 200 to every deactivation while the
84
+ * directory locks nothing:
85
+ *
86
+ * - naming an attribute without its value. `000001010000Z` is the ppolicy
87
+ * convention and means nothing to `nsAccountLock`, which 389-ds reads as
88
+ * locked only when it says `true` — so every deactivation would be
89
+ * written, read back as `active: false`, and the account would keep
90
+ * binding.
91
+ * - naming an attribute and pinning that same ppolicy value on it, which a
92
+ * deployment template that always sets both flags does by default. The
93
+ * guard above sees a value and passes; the outcome is identical.
94
+ * - the mirror: a value the default attribute cannot hold.
95
+ * `pwdAccountLockedTime` is a GeneralizedTime, so `TRUE` is refused by the
96
+ * directory's schema — or, where the attribute was redefined locally,
97
+ * stored and ignored.
98
+ * - a name that is not an attribute description. It is interpolated into the
99
+ * emitted LDAP filter for `active eq …`, where a metacharacter makes
100
+ * every list either malformed or quietly wrong.
101
+ *
102
+ * All are operator mistakes rather than attacks, and all are silent. The
103
+ * first and the last are refused outright. The two in the middle cannot be
104
+ * told apart from a deliberate local schema, so they are warned about: what
105
+ * the directory does with a value is beyond anything this can check.
106
+ */
107
+ export declare function resolveLockConfig(attribute: string, value: string): {
108
+ attribute: string;
109
+ value: string;
110
+ };
111
+ /**
112
+ * Read a SCIM `active` value, wherever it came from.
113
+ *
114
+ * `undefined` means the body did not mention it — which is not the same as
115
+ * `true`. Only a boolean, or the two canonical strings some providers send
116
+ * instead, are accepted: guessing at `"0"`, `null` or `1` would let a
117
+ * deprovisioning request be executed as a re-activation, and answer 200
118
+ * doing it. The one rule lives here so POST, PUT, PATCH and the filter
119
+ * translator cannot drift apart.
120
+ */
121
+ export declare function readActive(value: unknown): boolean | undefined;
122
+ /** Is this entry locked, per the configured lock attribute? */
123
+ export declare function isLocked(entry: AttributesList, ctx: MappingContext): boolean;
35
124
  /** Resolve the SCIM id for an LDAP entry, per configuration. */
36
125
  export declare function resolveScimId(entry: AttributesList, ctx: MappingContext): string | undefined;
37
126
  export declare function buildLocation(id: string, ctx: MappingContext): string | undefined;
@@ -44,6 +133,7 @@ export declare function ldapToScimUser(entry: AttributesList, mapping: ResourceM
44
133
  export declare function scimUserToLdap(user: ScimUser, mapping: ResourceMapping, ctx: MappingContext, objectClass: string[]): {
45
134
  rdn: string;
46
135
  attributes: AttributesList;
136
+ active?: boolean;
47
137
  };
48
138
  export declare function ldapToScimGroup(entry: AttributesList, mapping: ResourceMapping, ctx: MappingContext, memberResolver?: (dn: string) => MultiValued | undefined): ScimGroup;
49
139
  /**
@@ -58,15 +148,6 @@ export declare function scimGroupToLdap(group: ScimGroup, mapping: ResourceMappi
58
148
  * List of LDAP attributes to request from the directory so all
59
149
  * mapped SCIM attributes can be populated.
60
150
  */
61
- /**
62
- * The ppolicy overlay's lock marker, whose presence means `active: false`.
63
- *
64
- * Operational, so a search that does not name it never answers it: the read
65
- * path has to ask for it explicitly.
66
- */
67
- export declare const LOCK_ATTRIBUTE = "pwdAccountLockedTime";
68
- /** Is this entry locked, per the ppolicy lock marker? */
69
- export declare function isLocked(entry: AttributesList): boolean;
70
151
  export declare function requiredLdapAttributes(mapping: ResourceMapping, extra?: string[]): string[];
71
152
  /**
72
153
  * Given a SCIM attribute path like "emails.value" or "name.familyName",
@@ -19,7 +19,7 @@
19
19
  * member operations for Groups are handled via the caller-provided
20
20
  * `resolveMemberRef` hook (SCIM id → LDAP DN lookup).
21
21
  */
22
- import type { ModifyRequest } from '../../lib/ldapActions';
22
+ import type { AttributesList, ModifyRequest } from '../../lib/ldapActions';
23
23
  import { type ResourceMapping, type PatchRequest } from './types';
24
24
  export interface PatchContext {
25
25
  mapping: ResourceMapping;
@@ -27,6 +27,21 @@ export interface PatchContext {
27
27
  resolveMemberRef?: (value: string) => Promise<string | undefined>;
28
28
  /** The LDAP attribute holding members, default 'member'. */
29
29
  memberAttribute?: string;
30
+ /**
31
+ * For Users: the attribute whose presence marks the account as locked, and
32
+ * the value to write when SCIM `active` is set to false.
33
+ */
34
+ lockAttribute?: string;
35
+ lockValue?: string;
36
+ /** True when this resource type carries SCIM `active` (Users only). */
37
+ supportsActive?: boolean;
38
+ /**
39
+ * The entry as it stands. RFC 7644 section 3.5.2 applies operations in
40
+ * order, so they are played against this and only the resulting difference
41
+ * is sent — without it, two operations touching one attribute would
42
+ * collapse into whichever the emitter happened to keep.
43
+ */
44
+ current: AttributesList;
30
45
  }
31
46
  export declare function patchToModifyRequest(patch: PatchRequest, ctx: PatchContext): Promise<ModifyRequest>;
32
47
  /**
@@ -0,0 +1,15 @@
1
+ export interface ProjectionQuery {
2
+ attributes?: string[];
3
+ excludedAttributes?: string[];
4
+ }
5
+ /**
6
+ * Raise when both parameters are given: RFC 7644 §3.9 makes them mutually
7
+ * exclusive, and honouring one silently would hide the mistake.
8
+ */
9
+ export declare function assertProjection(query: ProjectionQuery): void;
10
+ /** Apply `attributes` / `excludedAttributes` to one resource. */
11
+ export declare function projectResource<T extends Record<string, unknown>>(resource: T, query: ProjectionQuery): T;
12
+ /** Apply the projection to every resource of a ListResponse. */
13
+ export declare function projectList<T extends Record<string, unknown>, L extends {
14
+ Resources: T[];
15
+ }>(list: L, query: ProjectionQuery): L;
@@ -419,6 +419,11 @@ export default class Scim extends DmPlugin {
419
419
  * `--scim-base-url` to make it absolute and trustworthy too.
420
420
  */
421
421
  private scimResource;
422
+ /**
423
+ * `attributes` / `excludedAttributes` (RFC 7644 section 3.9). Accepted on
424
+ * every route that answers a resource, not only the list ones.
425
+ */
426
+ private parseProjection;
422
427
  private readScimBody;
423
428
  api(app: Express): void;
424
429
  private parseListQuery;
@@ -41,14 +41,35 @@ export declare class ScimUsers {
41
41
  private readonly maxResults;
42
42
  private readonly maxScanned;
43
43
  private readonly scimPrefix;
44
+ private readonly lockAttribute;
45
+ private readonly lockValue;
44
46
  constructor(opts: ScimUsersOptions);
45
47
  private ctx;
48
+ /**
49
+ * LDAP attributes to fetch. The lock attribute backing SCIM `active` is
50
+ * operational on most directories, so it is never returned unless asked
51
+ * for by name.
52
+ */
53
+ private ldapAttributes;
46
54
  private dnForId;
47
55
  get(req: DmRequest, id: string): Promise<ScimUser>;
48
56
  list(req: DmRequest, query: ListQuery): Promise<ListResponse<ScimUser>>;
49
57
  create(req: DmRequest, resource: ScimUser): Promise<ScimUser>;
50
58
  replace(req: DmRequest, id: string, resource: ScimUser): Promise<ScimUser>;
51
59
  patch(req: DmRequest, id: string, patch: PatchRequest): Promise<ScimUser>;
60
+ /**
61
+ * Re-raise a directory refusal that names the schema, pointing at the flag
62
+ * that caused it.
63
+ *
64
+ * The generic translation in `errors` cannot do this: it sees the error,
65
+ * not the configuration. Only this class knows which attribute was
66
+ * configured to back `active`, and the shipped default —
67
+ * `pwdAccountLockedTime` — exists only where slapd loads the ppolicy
68
+ * overlay, which is the common way to meet this.
69
+ */
70
+ private lockSchemaError;
71
+ /** Fetch the raw LDAP entry backing a SCIM id, or raise 404. */
72
+ private currentEntry;
52
73
  delete(req: DmRequest, id: string): Promise<void>;
53
74
  /**
54
75
  * Given a SCIM member value (typically an id), return the LDAP DN.
@@ -0,0 +1,113 @@
1
+ import TwakePlugin from '../../abstract/twakePlugin';
2
+ import { type Role } from '../../abstract/plugin';
3
+ import { Hooks } from '../../hooks';
4
+ /**
5
+ * Plugin to sync LDAP resources and users with Twake Calendar
6
+ *
7
+ * Monitors a LDAP branch for resources (meeting rooms, equipment, etc.)
8
+ * and automatically creates/updates/deletes them in Twake Calendar via WebAdmin API.
9
+ *
10
+ * Also propagates user identity changes (email, first name, last name) to the
11
+ * Twake Calendar "registered users" via the WebAdmin API.
12
+ */
13
+ export default class Calendar extends TwakePlugin {
14
+ name: string;
15
+ roles: Role[];
16
+ dependencies: {
17
+ onLdapChange: string;
18
+ };
19
+ private resourceBase;
20
+ private resourceObjectClass;
21
+ private resourceCreator;
22
+ private resourceDomain;
23
+ private firstnameAttr;
24
+ private lastnameAttr;
25
+ constructor(server: import('../../bin').DM);
26
+ hooks: Hooks;
27
+ /**
28
+ * Check if a LDAP entry is a calendar resource
29
+ */
30
+ private isResource;
31
+ /**
32
+ * Whether a DN is the configured resource branch or sits below it.
33
+ *
34
+ * Compared RDN by RDN ({@link isDnInBranch}), not as text: a substring test
35
+ * took `cn=Salle,ou=resourcesArchive,…` for a resource when the branch was
36
+ * given as `ou=resources`, and missed a DN written `cn=Salle, ou=resources,
37
+ * …` — the spaces a client puts after its commas are not part of the DN.
38
+ *
39
+ * No configured branch means no branch restriction, as before.
40
+ */
41
+ private isInResourceBranch;
42
+ /**
43
+ * URL of one resource in the WebAdmin API.
44
+ *
45
+ * The id is a raw RDN value, so it can carry anything a directory accepts:
46
+ * `cn=Salle A/B` interpolated as such builds `/resources/Salle A/B`, a path
47
+ * naming another resource or none. Percent-encoding keeps it one segment.
48
+ */
49
+ private resourceUrl;
50
+ /**
51
+ * Build resource data for Calendar API from LDAP attributes
52
+ */
53
+ private buildResourceData;
54
+ /**
55
+ * The name a resource is known by in Calendar: the value of the entry's own
56
+ * RDN, escapes removed.
57
+ *
58
+ * Taken from the first RDN, whatever its attribute type, so that an entity
59
+ * whose mainAttribute is neither `cn` nor `uid` gets an id of its own. The
60
+ * previous derivation looked for `cn=`/`uid=` anywhere in the DN, so
61
+ * `o=Room 12,cn=zone,ou=resources,…` answered `zone`, its parent's id — a
62
+ * modify on that entry then patched another resource. It also cut the value
63
+ * at the first comma, escaped or not, so `cn=Salle\, 2` answered `Salle\`,
64
+ * an id two rooms could share.
65
+ *
66
+ * A DN with `cn=` or `uid=` as its own first RDN and no escape in its value
67
+ * — the shape a deployment has — answers exactly what it answered before.
68
+ */
69
+ private getResourceId;
70
+ /**
71
+ * Find a Calendar registered user by email.
72
+ *
73
+ * `GET /registeredUsers?email=…` answers the one user, or 404. Calendar
74
+ * lower-cases the address before matching it (James's `Username` folds both
75
+ * parts), which is also how it stores every address, so a case difference
76
+ * between LDAP and Calendar does not matter. The one record this misses is
77
+ * a legacy one whose stored address kept upper case, written without going
78
+ * through `Username`; the full-list lookup used before did find it.
79
+ *
80
+ * Calendar releases before 1.0.0.1 ignore the `email` parameter and answer
81
+ * the full list; the user is then picked from it, case-insensitively.
82
+ *
83
+ * @param email Address Calendar holds for the user
84
+ * @param log Log context of the calling sync
85
+ * @returns The registered user, or null when it is not registered or the
86
+ * lookup failed (both logged)
87
+ */
88
+ private findRegisteredUser;
89
+ /**
90
+ * Synchronize an LDAP user's identity (email, first and last name) to the
91
+ * Twake Calendar registered users via the WebAdmin API.
92
+ *
93
+ * Registered users are keyed by an internal id: the user is looked up with
94
+ * `GET /registeredUsers?email=…` (see findRegisteredUser), then
95
+ * `PATCH /registeredUsers?id={id}` with the LDAP values.
96
+ *
97
+ * @param event Hook name, used for logging
98
+ * @param dn LDAP DN of the user
99
+ * @param lookupEmail Email used to locate the existing registered user.
100
+ * Defaults to the user's current mail; pass the OLD mail when the email
101
+ * itself is changing (Calendar still holds the previous address).
102
+ */
103
+ syncRegisteredUser(event: string, dn: string, lookupEmail?: string): Promise<void>;
104
+ /**
105
+ * Delete user data from Twake Calendar WebAdmin API
106
+ * Calls POST /users/{mail}?action=deleteData
107
+ * @param mail - The user's email address
108
+ * @returns Task information or null on error
109
+ */
110
+ deleteUserData(mail: string): Promise<{
111
+ taskId: string;
112
+ } | null>;
113
+ }
@@ -1,63 +1,14 @@
1
- import TwakePlugin from '../../abstract/twakePlugin';
2
- import { type Role } from '../../abstract/plugin';
3
- import { Hooks } from '../../hooks';
4
1
  /**
5
- * Plugin to sync LDAP resources and users with Twake Calendar
2
+ * @deprecated Use `core/twake/calendar` instead. This file is a
3
+ * backwards-compatibility shim for setups that still load the plugin under
4
+ * its historical name, which undersold it: besides resources, it propagates
5
+ * user identity changes (email, first and last name) to Twake Calendar. It
6
+ * will be removed in a future major release.
6
7
  *
7
- * Monitors a LDAP branch for resources (meeting rooms, equipment, etc.)
8
- * and automatically creates/updates/deletes them in Twake Calendar via WebAdmin API.
9
- *
10
- * Also propagates user identity changes (email, first name, last name) to the
11
- * Twake Calendar "registered users" via the WebAdmin API.
8
+ * The instance keeps registering as `calendarResources`, so code looking it
9
+ * up by that name keeps finding it.
12
10
  */
13
- export default class CalendarResources extends TwakePlugin {
11
+ import Calendar from './calendar';
12
+ export default class CalendarResources extends Calendar {
14
13
  name: string;
15
- roles: Role[];
16
- dependencies: {
17
- onLdapChange: string;
18
- };
19
- private resourceBase;
20
- private resourceObjectClass;
21
- private resourceCreator;
22
- private resourceDomain;
23
- private firstnameAttr;
24
- private lastnameAttr;
25
- constructor(server: import('../../bin').DM);
26
- hooks: Hooks;
27
- /**
28
- * Check if a LDAP entry is a calendar resource
29
- */
30
- private isResource;
31
- /**
32
- * Build resource data for Calendar API from LDAP attributes
33
- */
34
- private buildResourceData;
35
- /**
36
- * Extract resource ID from DN
37
- */
38
- private getResourceId;
39
- /**
40
- * Synchronize an LDAP user's identity (email, first and last name) to the
41
- * Twake Calendar registered users via the WebAdmin API.
42
- *
43
- * Registered users are keyed by an internal id, and `GET /registeredUsers`
44
- * exposes no filter, so we list all registered users, locate the entry by
45
- * email, then `PATCH /registeredUsers?id={id}` with the LDAP values.
46
- *
47
- * @param event Hook name, used for logging
48
- * @param dn LDAP DN of the user
49
- * @param lookupEmail Email used to locate the existing registered user.
50
- * Defaults to the user's current mail; pass the OLD mail when the email
51
- * itself is changing (Calendar still holds the previous address).
52
- */
53
- syncRegisteredUser(event: string, dn: string, lookupEmail?: string): Promise<void>;
54
- /**
55
- * Delete user data from Twake Calendar WebAdmin API
56
- * Calls POST /users/{mail}?action=deleteData
57
- * @param mail - The user's email address
58
- * @returns Task information or null on error
59
- */
60
- deleteUserData(mail: string): Promise<{
61
- taskId: string;
62
- } | null>;
63
14
  }
package/docs/README.md CHANGED
@@ -10,6 +10,8 @@ Installation, configuration and plugin usage.
10
10
 
11
11
  - **[Getting Started](usage/README.md)** - Quick start guide
12
12
  - **[Configuration](usage/configuration.md)** - CLI options and environment variables
13
+ - **[Upgrading](usage/upgrading.md)** - What to check before deploying a new version
14
+ - **[Directory audit](usage/directory-audit.md)** - What a schema would refuse in an existing directory
13
15
  - **[Plugins](usage/plugins/README.md)** - Documentation for all available plugins
14
16
 
15
17
  ### [Plugin Development](plugin-development/README.md)
@@ -220,7 +220,7 @@ private pluginTags: Map<string, string> = new Map([
220
220
  ['ldapFlatGeneric', 'Entities'],
221
221
  ['ldapBulkImport', 'Bulk Import'],
222
222
  ['james', 'Apache James Integration'],
223
- ['calendarResources', 'Calendar Resources'],
223
+ ['calendar', 'Twake Calendar'],
224
224
  ['configApi', 'Configuration'],
225
225
  ['static', 'Static Files'],
226
226
  ]);
@@ -34,7 +34,7 @@ This guide covers LDAP-Rest's browser libraries for building LDAP management int
34
34
 
35
35
  ## Overview
36
36
 
37
- LDAP-Rest provides four ready-to-use browser libraries for building LDAP management interfaces:
37
+ LDAP-Rest provides ready-to-use browser libraries for building LDAP management interfaces:
38
38
 
39
39
  - **LdapTreeViewer** - Interactive hierarchical tree view of LDAP organizations
40
40
  - **LdapUserEditor** - Complete user management interface with organization tree, user list, and edit form
@@ -455,6 +455,28 @@ export default class MyPlugin extends DmPlugin {
455
455
 
456
456
  The `this.server.ldap` object provides access to all LDAP operations:
457
457
 
458
+ > **Serving an HTTP request? Bind it first.**
459
+ >
460
+ > The authorization plugins (`core/auth/authzPerBranch`,
461
+ > `core/auth/authzDynamic`, …) hook `ldapsearchrequest`, `ldapaddrequest`,
462
+ > `ldapmodifyrequest`, `ldaprenamerequest` and `ldapdeleterequest`, and they
463
+ > **skip every check when the request is missing**. The methods below take it as a trailing
464
+ > optional argument, so forgetting it does not fail — it silently authorizes
465
+ > the operation. Three such bugs shipped in the SCIM plugin.
466
+ >
467
+ > In a request handler, bind once and use the result:
468
+ >
469
+ > ```typescript
470
+ > const directory = this.server.ldap.forRequest(req);
471
+ > await directory.search({ scope: 'base' }, dn);
472
+ > await directory.modify(dn, changes);
473
+ > ```
474
+ >
475
+ > `forRequest()` returns a `RequestBoundLdap`, whose methods have no request
476
+ > parameter at all — the omission stops being expressible. Use the unbound
477
+ > methods only for work that genuinely belongs to no request, such as startup
478
+ > or scheduled tasks, where passing nothing is the deliberate answer.
479
+
458
480
  #### Search
459
481
 
460
482
  ```typescript
@@ -190,15 +190,18 @@ dependencies = {
190
190
  - **Consumes Hooks**: (Hooks provided by `onLdapChange`)
191
191
  - **Notes**: Synchronizes LDAP users/groups with Apache James mail server
192
192
 
193
- #### `calendarResources` (twake/calendarResources)
193
+ #### `calendar` (twake/calendar)
194
194
 
195
- - **Dependencies**: None
195
+ - **Dependencies**:
196
+ - `onLdapChange: 'core/ldap/onChange'` ⚠️ **Required**
196
197
  - **Provides Hooks**:
197
198
  - `ldapcalendarResourceadddone` - After calendar resource added
198
199
  - `ldapcalendarResourcemodifydone` - After calendar resource modified
199
200
  - `ldapcalendarResourcedeletedone` - After calendar resource deleted
200
- - **Consumes Hooks**: None
201
- - **Notes**: Synchronizes calendar resources with Twake Calendar
201
+ - `onLdapChange` - Propagates user email / name changes
202
+ - **Consumes Hooks**: (Hooks provided by `onLdapChange`)
203
+ - **Notes**: Synchronizes calendar resources and registered users with Twake
204
+ Calendar. Formerly `calendarResources`, still loadable under that name
202
205
 
203
206
  #### `appAccountsConsistency` (twake/appAccountsConsistency)
204
207
 
@@ -361,7 +364,7 @@ ldapOrganizations (core/ldap/organization)
361
364
  ├─────────────────────────────────────────────────────────────────────────────┤
362
365
  │ │
363
366
  │ ┌────────────────────────────┐ ┌──────────────────────────┐ │
364
- │ │ james │ │ calendarResources │ │
367
+ │ │ james │ │ calendar │ │
365
368
  │ │ (Apache James sync) │ │ (Twake Calendar sync) │ │
366
369
  │ │ │ │ │ │
367
370
  │ │ Requires: │ │ │ │
@@ -466,7 +469,7 @@ graph TB
466
469
 
467
470
  %% Twake Integration
468
471
  james[james]
469
- calendar[calendarResources]
472
+ calendar[calendar]
470
473
 
471
474
  %% Utility
472
475
  static[static]
@@ -521,7 +524,7 @@ graph TB
521
524
  ### 4. Integration Plugins
522
525
 
523
526
  13. `james` - After `onChange` and `groups`
524
- 14. `calendarResources` - Calendar sync (optional)
527
+ 14. `calendar` - Twake Calendar sync (optional)
525
528
 
526
529
  ### 5. Utility Plugins (can load anytime)
527
530
 
@@ -100,7 +100,7 @@ export default class MyPlugin extends DmPlugin {
100
100
  ## Integrations
101
101
 
102
102
  - **twake/james** - Apache James Synchronization (mail)
103
- - **twake/calendarResources** - Calendar Resources
103
+ - **twake/calendar** - Twake Calendar resources and users
104
104
 
105
105
  <!-- end_slide -->
106
106
 
@@ -100,7 +100,7 @@ export default class MyPlugin extends DmPlugin {
100
100
  ## Intégrations
101
101
 
102
102
  - **twake/james** - Synchronisation Apache James (mail)
103
- - **twake/calendarResources** - Ressources calendrier
103
+ - **twake/calendar** - Ressources et utilisateurs Twake Calendar
104
104
 
105
105
  <!-- end_slide -->
106
106
 
@@ -86,4 +86,6 @@ The system will:
86
86
 
87
87
  - **[Configuration](configuration.md)** - CLI options and environment variables
88
88
  - **[Plugins](plugins/README.md)** - Choose and configure plugins
89
+ - **[Upgrading](upgrading.md)** - What to check before deploying a new version
90
+ - **[Directory audit](directory-audit.md)** - What a schema would refuse in an existing directory
89
91
  - **[Troubleshooting](troubleshooting.md)** - Problem resolution
@@ -35,7 +35,7 @@ All LDAP-Rest configuration options.
35
35
  - [core/auth/trustedProxy](#coreauthtrustedproxy)
36
36
  - [Twake Integration Plugins](#twake-integration-plugins)
37
37
  - [core/twake/james](#coretwakejames)
38
- - [core/twake/calendarResources](#coretwakecalendarresources)
38
+ - [core/twake/calendar](#coretwakecalendar)
39
39
  - [core/twake/applicativeAccounts](#coretwakeapplicativeaccounts)
40
40
  - [core/twake/appAccountsConsistency](#coretwakeappaccountsconsistency)
41
41
  - [Utility Plugins](#utility-plugins)
@@ -92,18 +92,34 @@ export DM_PLUGINS="core/auth/token,core/ldap/flatGeneric,core/ldap/groups"
92
92
 
93
93
  ## LDAP Connection
94
94
 
95
- | CLI | Plural | Env | Default | Description |
96
- | ---------------------------- | ---------------- | ------------------------ | ---------------------------------- | -------------------------- |
97
- | `--ldap-url` | `--ldap-urls` | `DM_LDAP_URL` | `ldap://localhost` | LDAP server URL(s) |
98
- | `--ldap-dn` | | `DM_LDAP_DN` | `cn=admin,dc=example,dc=com` | Bind DN |
99
- | `--ldap-pwd` | | `DM_LDAP_PWD` | `admin` | Password |
100
- | `--ldap-base` | | `DM_LDAP_BASE` | | Base DN for searches |
101
- | `--ldap-user-main-attribute` | | `DM_LDAP_USER_ATTRIBUTE` | `uid` | User identifier attribute |
102
- | `--ldap-cache-max` | | `DM_LDAP_CACHE_MAX` | `1000` | Max cache entries |
103
- | `--ldap-cache-ttl` | | `DM_LDAP_CACHE_TTL` | `300` | Cache TTL (seconds) |
104
- | `--ldap-pool-size` | | `DM_LDAP_POOL_SIZE` | `5` | Connection pool size |
105
- | `--ldap-connection-ttl` | | `DM_LDAP_CONNECTION_TTL` | `60` | Connection TTL (seconds) |
106
- | `--user-class` | `--user-classes` | `DM_USER_CLASSES` | `top,twakeAccount,twakeWhitePages` | Default user objectClasses |
95
+ | CLI | Plural | Env | Default | Description |
96
+ | ---------------------------- | ---------------- | ------------------------ | ---------------------------------- | ------------------------------------------- |
97
+ | `--ldap-url` | `--ldap-urls` | `DM_LDAP_URL` | `ldap://localhost` | LDAP server URL(s) |
98
+ | `--ldap-dn` | | `DM_LDAP_DN` | `cn=admin,dc=example,dc=com` | Bind DN |
99
+ | `--ldap-pwd` | | `DM_LDAP_PWD` | `admin` | Password |
100
+ | `--ldap-base` | | `DM_LDAP_BASE` | | Base DN for searches |
101
+ | `--ldap-user-main-attribute` | | `DM_LDAP_USER_ATTRIBUTE` | `uid` | User identifier attribute |
102
+ | `--ldap-cache-max` | | `DM_LDAP_CACHE_MAX` | `1000` | Max cache entries |
103
+ | `--ldap-cache-ttl` | | `DM_LDAP_CACHE_TTL` | `0` | Search cache TTL (seconds), `0` disables it |
104
+ | `--ldap-pool-size` | | `DM_LDAP_POOL_SIZE` | `5` | Connection pool size |
105
+ | `--ldap-connection-ttl` | | `DM_LDAP_CONNECTION_TTL` | `60` | Connection TTL (seconds) |
106
+ | `--user-class` | `--user-classes` | `DM_USER_CLASSES` | `top,twakeAccount,twakeWhitePages` | Default user objectClasses |
107
+
108
+ ### The search cache
109
+
110
+ `--ldap-cache-ttl` holds the answers of non-paginated, base-scope searches —
111
+ single-entry lookups by DN — for that many seconds. It is **off by default**:
112
+ until the branch storing a result was repaired, nothing was ever cached, so no
113
+ deployment has run with it on, and a cache that silently serves stale entries
114
+ is not something to switch on for everyone at once.
115
+
116
+ Writes made through this service (add, modify, rename, move, delete) drop what
117
+ they change, the moved subtree included. Writes made anywhere else do not:
118
+ another replica of this service, an LSC synchronisation, a hand-run
119
+ `ldapmodify`. Each process caches on its own, and only its own writes drop what
120
+ it cached. Turn the cache on when this service is the only writer, or when
121
+ serving an entry up to `--ldap-cache-ttl` seconds old is acceptable; leave it
122
+ at `0` otherwise.
107
123
 
108
124
  ## Special Attributes
109
125
 
@@ -188,7 +204,7 @@ Monitors LDAP modifications and triggers hooks for attribute changes. No configu
188
204
 
189
205
  #### `core/ldap/departmentSync`
190
206
 
191
- Maintains consistency of department links when organizations are renamed/moved. No configuration options - uses [core/ldap/organizations](#coreldaporganizations) settings.
207
+ Maintains consistency of department links when organizations are renamed/moved: it recomputes the path of the organization and of its sub-organizations (those already holding one), then updates the link and path of the entries linked to any of them. The attributes are those the loaded schemas declare through the `organizationPath` and `organizationLink` roles, then the configured `--ldap-organization-path-attribute` and `--ldap-organization-link-attribute`. No configuration options of its own - uses [core/ldap/organizations](#coreldaporganizations) settings.
192
208
 
193
209
  #### `core/ldap/raw`
194
210
 
@@ -308,7 +324,9 @@ Empty means the plugin guards every path. Scoping several instances to different
308
324
  | `--james-mailbox-type-attribute` | | `DM_JAMES_MAILBOX_TYPE_ATTRIBUTE` | `twakeMailboxType` | Mailbox type attribute |
309
325
  | `--ldap-concurrency` | | `DM_LDAP_CONCURRENCY` | `10` | LDAP operations concurrency |
310
326
 
311
- #### `core/twake/calendarResources`
327
+ #### `core/twake/calendar`
328
+
329
+ Formerly `core/twake/calendarResources`, still accepted as a deprecated alias.
312
330
 
313
331
  | CLI | Env | Default | Description |
314
332
  | --------------------------------- | ---------------------------------- | ----------------------- | ----------------------------- |
@@ -319,6 +337,8 @@ Empty means the plugin guards every path. Scoping several instances to different
319
337
  | `--calendar-resource-objectclass` | `DM_CALENDAR_RESOURCE_OBJECTCLASS` | | Resource objectClass |
320
338
  | `--calendar-resource-creator` | `DM_CALENDAR_RESOURCE_CREATOR` | | Resource creator |
321
339
  | `--calendar-resource-domain` | `DM_CALENDAR_RESOURCE_DOMAIN` | | Resource domain |
340
+ | `--calendar-firstname-attribute` | `DM_CALENDAR_FIRSTNAME_ATTRIBUTE` | `givenName` | Registered user first name |
341
+ | `--calendar-lastname-attribute` | `DM_CALENDAR_LASTNAME_ATTRIBUTE` | `sn` | Registered user last name |
322
342
 
323
343
  #### `core/twake/applicativeAccounts`
324
344