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
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
8
|
-
*
|
|
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
|
-
|
|
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
|
-
['
|
|
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
|
|
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
|
-
#### `
|
|
193
|
+
#### `calendar` (twake/calendar)
|
|
194
194
|
|
|
195
|
-
- **Dependencies**:
|
|
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
|
-
-
|
|
201
|
-
- **
|
|
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 │ │
|
|
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[
|
|
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. `
|
|
527
|
+
14. `calendar` - Twake Calendar sync (optional)
|
|
525
528
|
|
|
526
529
|
### 5. Utility Plugins (can load anytime)
|
|
527
530
|
|
package/docs/presentations/en.md
CHANGED
|
@@ -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/
|
|
103
|
+
- **twake/calendar** - Twake Calendar resources and users
|
|
104
104
|
|
|
105
105
|
<!-- end_slide -->
|
|
106
106
|
|
package/docs/presentations/fr.md
CHANGED
|
@@ -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/
|
|
103
|
+
- **twake/calendar** - Ressources et utilisateurs Twake Calendar
|
|
104
104
|
|
|
105
105
|
<!-- end_slide -->
|
|
106
106
|
|
package/docs/usage/README.md
CHANGED
|
@@ -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/
|
|
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` | `
|
|
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/
|
|
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
|
|