ldap-rest 0.13.4 → 0.15.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/bin/index.js +1 -1
- package/dist/config/args.js +1 -1
- package/dist/lib/errors.js +1 -1
- package/dist/lib/expressFormatedResponses.js +1 -1
- package/dist/lib/ldapActions.js +1 -1
- package/dist/lib/operation.js +1 -0
- package/dist/lib/parseConfig.js +1 -1
- package/dist/lib/rawBody.js +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/plugins/auth/hmac.js +1 -1
- package/dist/plugins/auth/totp.js +1 -1
- package/dist/plugins/ldap/onChange.js +1 -1
- package/dist/plugins/scim/errors.js +1 -1
- package/dist/plugins/scim/scim.js +1 -1
- package/dist/plugins/twake/groups.js +1 -1
- package/dist/plugins/twake/instances.js +1 -1
- package/dist/plugins/twake/lifecycleEvents.js +1 -1
- package/dist/plugins/twake/spaces.js +1 -0
- package/dist/plugins/twake/tombstone.js +1 -1
- package/dist/src/bin/index.d.ts +16 -0
- package/dist/src/config/args.d.ts +8 -1
- package/dist/src/hooks.d.ts +17 -0
- package/dist/src/lib/errors.d.ts +5 -0
- package/dist/src/lib/ldapActions.d.ts +12 -1
- package/dist/src/lib/operation.d.ts +29 -0
- package/dist/src/lib/parseConfig.d.ts +4 -1
- package/dist/src/lib/rawBody.d.ts +27 -8
- package/dist/src/lib/utils.d.ts +6 -0
- package/dist/src/plugins/auth/hmac.d.ts +1 -1
- package/dist/src/plugins/ldap/onChange.d.ts +5 -2
- package/dist/src/plugins/scim/scim.d.ts +11 -1
- package/dist/src/plugins/twake/groups.d.ts +72 -12
- package/dist/src/plugins/twake/instances.d.ts +0 -8
- package/dist/src/plugins/twake/spaces.d.ts +186 -0
- package/dist/src/plugins/twake/tombstone.d.ts +5 -0
- package/docs/plugin-development/README.md +21 -0
- package/docs/plugin-development/hooks.md +25 -0
- package/docs/usage/configuration.md +83 -4
- package/docs/usage/plugins/auth/hmac.md +6 -1
- package/docs/usage/plugins/integrations/README.md +6 -0
- package/docs/usage/plugins/integrations/groups.md +12 -5
- package/docs/usage/plugins/integrations/instances.md +0 -10
- package/docs/usage/plugins/integrations/lifecycle-events.md +3 -1
- package/docs/usage/plugins/integrations/spaces.md +221 -0
- package/docs/usage/plugins/ldap/raw.md +1 -1
- package/docs/usage/upgrading.md +131 -0
- package/package.json +6 -1
- package/static/browser/shared/utils/hmac.js +5 -0
- package/static/browser/shared/utils/hmac.js.map +1 -1
|
@@ -1,12 +1,55 @@
|
|
|
1
|
-
import type { Express } from 'express';
|
|
1
|
+
import type { Express, Request, Response } from 'express';
|
|
2
2
|
import type { DM } from '../../bin';
|
|
3
3
|
import type { Schema } from '../../config/schema';
|
|
4
4
|
import type { Hooks } from '../../hooks';
|
|
5
|
-
import type { AttributeValue } from '../../lib/ldapActions';
|
|
5
|
+
import type { AttributesList, AttributeValue } from '../../lib/ldapActions';
|
|
6
6
|
import LdapGroups from '../ldap/groups';
|
|
7
7
|
import { type LifecycleAttributes } from './lifecycleAttributes';
|
|
8
|
+
/**
|
|
9
|
+
* An attribute's first value, whatever case its name is configured in: the
|
|
10
|
+
* directory answers it under its schema's spelling.
|
|
11
|
+
*/
|
|
12
|
+
export declare const read: (entry: AttributesList, attribute: string) => string | undefined;
|
|
13
|
+
export declare const ORG = "{org}";
|
|
14
|
+
/** A refusal the routes answer as `{ error, code }`. */
|
|
15
|
+
export declare class RouteError extends Error {
|
|
16
|
+
readonly status: number;
|
|
17
|
+
readonly code: string;
|
|
18
|
+
constructor(status: number, message: string, code: string);
|
|
19
|
+
}
|
|
20
|
+
export declare const notFound: (what: string) => RouteError;
|
|
21
|
+
export declare const invalid: (message: string, code?: string) => RouteError;
|
|
22
|
+
export declare const NAME_RULE = "name must be a non-blank string of at most 256 characters with no control characters";
|
|
23
|
+
export declare function isDisplayName(name: unknown): name is string;
|
|
24
|
+
/** A request without a JSON object body leaves `req.body` undefined. */
|
|
25
|
+
export declare function bodyOf(req: Request): Record<string, unknown>;
|
|
26
|
+
export interface Page {
|
|
27
|
+
page: number;
|
|
28
|
+
limit: number;
|
|
29
|
+
offset: number;
|
|
30
|
+
search?: string;
|
|
31
|
+
sortBy?: string;
|
|
32
|
+
desc: boolean;
|
|
33
|
+
}
|
|
8
34
|
/** The values of an attribute, whatever shape the directory gave them. */
|
|
9
35
|
export declare function values(value: AttributeValue | undefined): string[];
|
|
36
|
+
/** A DN spelled one way, to compare two spellings of it. */
|
|
37
|
+
export declare function dnKey(dn: string): string;
|
|
38
|
+
export declare function parentOf(dn: string): string;
|
|
39
|
+
/** The RDN value of a DN, unescaped, or the DN itself if it has none. */
|
|
40
|
+
export declare function rdnValue(dn: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* The entries one level under a DN pattern holding `{org}`, the organization
|
|
43
|
+
* captured as one RDN value: `(?:\\.|[^,])+` keeps an escaped comma inside it.
|
|
44
|
+
*/
|
|
45
|
+
export declare function branchPattern(base: string): RegExp;
|
|
46
|
+
/**
|
|
47
|
+
* The organization a rename moves an entry into, under a branch pattern, if
|
|
48
|
+
* it changes the entry's parent: what it holds comes along unchecked.
|
|
49
|
+
*/
|
|
50
|
+
export declare function movedInto(pattern: RegExp, dn: string, newDn: string): string | undefined;
|
|
51
|
+
/** The organization of an entry one level under a branch pattern. */
|
|
52
|
+
export declare function organizationIn(pattern: RegExp, dn: string): string | undefined;
|
|
10
53
|
export default class TwakeGroups extends LdapGroups {
|
|
11
54
|
name: string;
|
|
12
55
|
protected readonly groupBase: string;
|
|
@@ -17,7 +60,7 @@ export default class TwakeGroups extends LdapGroups {
|
|
|
17
60
|
private readonly displayName;
|
|
18
61
|
private readonly color;
|
|
19
62
|
private readonly createdAt;
|
|
20
|
-
|
|
63
|
+
readonly maxPage: number;
|
|
21
64
|
private readonly memberFields;
|
|
22
65
|
constructor(server: DM);
|
|
23
66
|
/**
|
|
@@ -47,27 +90,36 @@ export default class TwakeGroups extends LdapGroups {
|
|
|
47
90
|
* within its own organization only. The placeholder belongs to none.
|
|
48
91
|
*/
|
|
49
92
|
private checkMembers;
|
|
93
|
+
/** A member that is not a user of the organization, if any. */
|
|
94
|
+
private foreign;
|
|
50
95
|
/**
|
|
51
96
|
* A tombstone keeps its memberships until it is erased, and is hidden from
|
|
52
97
|
* them, so every API reads the same member list.
|
|
53
98
|
*/
|
|
54
99
|
private hideTombstones;
|
|
55
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Read without a request: hiding a tombstone is integrity, not data, and
|
|
102
|
+
* must not depend on what the caller may see.
|
|
103
|
+
*/
|
|
104
|
+
tombstonesOf(org: string): Promise<string[]>;
|
|
56
105
|
/**
|
|
57
106
|
* Only the organization routes: the flat `/v1/ldap/groups` of
|
|
58
107
|
* core/ldap/groups would read one base that holds no group here.
|
|
59
108
|
*/
|
|
60
109
|
api(app: Express): void;
|
|
61
|
-
|
|
62
|
-
|
|
110
|
+
/** A route of the organization `:id`; see `RouteError` for refusals. */
|
|
111
|
+
organizationRoute(context: string, handler: (req: Request, res: Response, org: string) => Promise<void>, plugin?: string): (req: Request, res: Response) => Promise<void>;
|
|
112
|
+
groupDn(org: string, id: string): string;
|
|
113
|
+
get userAttribute(): string;
|
|
114
|
+
userDn(org: string, username: string): string;
|
|
63
115
|
/** Every route answers 404 for a missing organization, 410 for a deleted one. */
|
|
64
116
|
private checkOrganization;
|
|
65
117
|
/** Page, search and sort of a list route, or a refusal of them. */
|
|
66
|
-
|
|
67
|
-
|
|
118
|
+
page(req: Request, sortable: string[]): Page;
|
|
119
|
+
sorted<T extends Record<string, unknown>>(items: T[], sortBy: string | undefined, desc: boolean): T[];
|
|
68
120
|
/** A group as the routes answer it: members named by their RDN value. */
|
|
69
|
-
|
|
70
|
-
|
|
121
|
+
groupOf(org: string, entry: AttributesList): Record<string, unknown>;
|
|
122
|
+
groups(org: string, filter: string, attributes?: string[], req?: Request): Promise<AttributesList[]>;
|
|
71
123
|
private readGroup;
|
|
72
124
|
/** The group of that display name in the organization, if any. */
|
|
73
125
|
private named;
|
|
@@ -77,12 +129,20 @@ export default class TwakeGroups extends LdapGroups {
|
|
|
77
129
|
private createRoute;
|
|
78
130
|
private updateRoute;
|
|
79
131
|
private membersRoute;
|
|
132
|
+
/**
|
|
133
|
+
* A page of members' public profiles, each with what `extra` adds for its
|
|
134
|
+
* username.
|
|
135
|
+
*/
|
|
136
|
+
memberPage(p: Page, org: string, names: string[], extra?: (username: string) => Record<string, unknown>, req?: Request): Promise<{
|
|
137
|
+
members: Record<string, unknown>[];
|
|
138
|
+
pagination: Record<string, number | boolean>;
|
|
139
|
+
}>;
|
|
80
140
|
/**
|
|
81
141
|
* Users of the organization by name, read in pages of the size limit. A
|
|
82
142
|
* tombstone is no user: it cannot be added, as it would join hidden.
|
|
83
143
|
*/
|
|
84
|
-
|
|
85
|
-
|
|
144
|
+
users(org: string, usernames: string[], attributes: string[], req?: Request): Promise<AttributesList[]>;
|
|
145
|
+
profiles(org: string, usernames: string[], req?: Request): Promise<Map<string, Record<string, unknown>>>;
|
|
86
146
|
private addMembersRoute;
|
|
87
147
|
private dropPlaceholder;
|
|
88
148
|
private removeMemberRoute;
|
|
@@ -76,14 +76,6 @@ export default class TwakeInstances extends DmPlugin {
|
|
|
76
76
|
private provide;
|
|
77
77
|
protected ready(dn: string, entry: AttributesList, account: Account, fqdn: string, req?: Request): Promise<void>;
|
|
78
78
|
private organizationReady;
|
|
79
|
-
/** The organization whose account `dn` is, if it is one */
|
|
80
|
-
private organizationOf;
|
|
81
|
-
/**
|
|
82
|
-
* An organization account added once its organization is announced. One
|
|
83
|
-
* added before is filled by organizationReady
|
|
84
|
-
*/
|
|
85
|
-
private giveOrganizationAddress;
|
|
86
|
-
private fillOrganizationAccount;
|
|
87
79
|
/** Send the user.created held while the organization at `dn` was pending */
|
|
88
80
|
private releaseMembers;
|
|
89
81
|
/** Announce a held member, with the address rule ensureInstance applies */
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { Express } from 'express';
|
|
2
|
+
import DmPlugin, { type Role } from '../../abstract/plugin';
|
|
3
|
+
import type { DM } from '../../bin';
|
|
4
|
+
import type { Hooks } from '../../hooks';
|
|
5
|
+
/** From the weakest to the strongest. */
|
|
6
|
+
export declare const SPACE_ROLES: readonly ["viewer", "editor", "admin"];
|
|
7
|
+
export type SpaceRole = (typeof SPACE_ROLES)[number];
|
|
8
|
+
export type Space = {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
organizationId: string;
|
|
12
|
+
members: {
|
|
13
|
+
username: string;
|
|
14
|
+
role: SpaceRole;
|
|
15
|
+
}[];
|
|
16
|
+
groups: {
|
|
17
|
+
id: string;
|
|
18
|
+
role: SpaceRole;
|
|
19
|
+
}[];
|
|
20
|
+
};
|
|
21
|
+
export default class TwakeSpaces extends DmPlugin {
|
|
22
|
+
name: string;
|
|
23
|
+
roles: Role[];
|
|
24
|
+
dependencies: Record<string, string>;
|
|
25
|
+
private readonly spaceBase;
|
|
26
|
+
private readonly spacePattern;
|
|
27
|
+
private readonly userPattern;
|
|
28
|
+
private readonly lifecycle;
|
|
29
|
+
private readonly displayName;
|
|
30
|
+
readonly roleAttributes: Record<SpaceRole, string>;
|
|
31
|
+
private readonly userRole;
|
|
32
|
+
private readonly exchange;
|
|
33
|
+
/**
|
|
34
|
+
* Changes are followed one at a time, each from the directory as it is
|
|
35
|
+
* then, so the order they are followed in does not matter.
|
|
36
|
+
*/
|
|
37
|
+
private following;
|
|
38
|
+
private publishing;
|
|
39
|
+
/**
|
|
40
|
+
* The spaces of a group being deleted, by DN key, read before refint takes
|
|
41
|
+
* it out of them, until its deletion is followed. `op` is the delete
|
|
42
|
+
* still running, `owed` the unlinks space writes left to it meanwhile.
|
|
43
|
+
*/
|
|
44
|
+
private readonly unlinking;
|
|
45
|
+
/**
|
|
46
|
+
* The groups a deletion announced unlinked, by space DN key, until a write
|
|
47
|
+
* of the space takes their DN out of it, or finds it gone already.
|
|
48
|
+
*/
|
|
49
|
+
private readonly announced;
|
|
50
|
+
/**
|
|
51
|
+
* The spaces a user being deleted is an admin of, by operation and DN,
|
|
52
|
+
* read before refint takes the user out of them.
|
|
53
|
+
*/
|
|
54
|
+
private readonly handing;
|
|
55
|
+
constructor(server: DM);
|
|
56
|
+
/** Whether user roles are kept or events published. */
|
|
57
|
+
private get follows();
|
|
58
|
+
private get groups();
|
|
59
|
+
private get tombstone();
|
|
60
|
+
hooks: Hooks;
|
|
61
|
+
/**
|
|
62
|
+
* core/rabbitmq connects lazily and hands back no client when it cannot:
|
|
63
|
+
* every event would then be lost, so the server does not start.
|
|
64
|
+
*
|
|
65
|
+
* core/ldap/trash moves a deleted group or space away instead of deleting
|
|
66
|
+
* it: nothing follows the move, so users would keep the roles it gave,
|
|
67
|
+
* and no event would say it went.
|
|
68
|
+
*/
|
|
69
|
+
assertComposition(): Promise<void>;
|
|
70
|
+
private queue;
|
|
71
|
+
/** The organization a space DN belongs to, if it is a space at all. */
|
|
72
|
+
organizationOf(dn: string): string | undefined;
|
|
73
|
+
spaceBaseOf(org: string): string;
|
|
74
|
+
spaceDn(org: string, id: string): string;
|
|
75
|
+
/** An entry as it is, read for no one, or nothing for one missing. */
|
|
76
|
+
private entry;
|
|
77
|
+
/** A space holds users and groups of its organization, whichever API writes it. */
|
|
78
|
+
private checkHolders;
|
|
79
|
+
/** A holder that is neither a user nor a group of the organization. */
|
|
80
|
+
private foreign;
|
|
81
|
+
api(app: Express): void;
|
|
82
|
+
/** A space as the routes answer it, strongest role first. */
|
|
83
|
+
private spaceOf;
|
|
84
|
+
/**
|
|
85
|
+
* The users and groups an entry holds, strongest role first, each once
|
|
86
|
+
* with its strongest role. A tombstone is hidden.
|
|
87
|
+
*/
|
|
88
|
+
private entryOf;
|
|
89
|
+
/** The spaces of the organization matching a filter, or the one of an id. */
|
|
90
|
+
private spaces;
|
|
91
|
+
/**
|
|
92
|
+
* Whoever asks: a tombstone outside the caller's branches would otherwise
|
|
93
|
+
* count as a member, and as an admin.
|
|
94
|
+
*/
|
|
95
|
+
private hidden;
|
|
96
|
+
/** A filter for the spaces holding any of these DNs, whatever the role. */
|
|
97
|
+
private holding;
|
|
98
|
+
private space;
|
|
99
|
+
private write;
|
|
100
|
+
/**
|
|
101
|
+
* A change decided on a space read before another request wrote it fails
|
|
102
|
+
* with LDAP 16 or 20: it is decided again on the space as it is now.
|
|
103
|
+
*/
|
|
104
|
+
private again;
|
|
105
|
+
/** The live users of the organization by lowercased name, as DNs, or a 404. */
|
|
106
|
+
private liveUsers;
|
|
107
|
+
private get groupName();
|
|
108
|
+
/** Groups of the organization by id, read in pages of the size limit. */
|
|
109
|
+
private orgGroups;
|
|
110
|
+
/**
|
|
111
|
+
* The users a space write or a change of a group's members may have
|
|
112
|
+
* moved, and the events of the write. A deleted group is followed through
|
|
113
|
+
* its members as they were.
|
|
114
|
+
*/
|
|
115
|
+
private follow;
|
|
116
|
+
/**
|
|
117
|
+
* The spaces a user is an admin of. Read again when a tombstone is erased:
|
|
118
|
+
* its hand-over may have failed, or predate this plugin, and handing over
|
|
119
|
+
* a space that keeps another admin does nothing.
|
|
120
|
+
*/
|
|
121
|
+
private administered;
|
|
122
|
+
/**
|
|
123
|
+
* A space a deleted user was the last admin of goes to its editors, else
|
|
124
|
+
* its viewers; one left with no member is deleted.
|
|
125
|
+
*/
|
|
126
|
+
private handOver;
|
|
127
|
+
private handOverOnce;
|
|
128
|
+
/** A group about to be deleted, if it is one linked to spaces. */
|
|
129
|
+
private deletedGroup;
|
|
130
|
+
/**
|
|
131
|
+
* The spaces a deleted group left. One still holding its DN, without
|
|
132
|
+
* refint, remembers the announcement for the write that takes it out.
|
|
133
|
+
*/
|
|
134
|
+
private unlinked;
|
|
135
|
+
/**
|
|
136
|
+
* Rewrite each user's roles from the directory as it is now: a change
|
|
137
|
+
* only tells which users to look at, so two changes followed in either
|
|
138
|
+
* order, or late, leave the same roles.
|
|
139
|
+
*
|
|
140
|
+
* The roles each user had come from the values written before, or, with
|
|
141
|
+
* no user role attribute, from the directory with the change undone: two
|
|
142
|
+
* changes followed late may then announce a role twice, or not at all.
|
|
143
|
+
*/
|
|
144
|
+
private settle;
|
|
145
|
+
/**
|
|
146
|
+
* A filter for the spaces a user is in, directly or through a group, and
|
|
147
|
+
* their role in one of them. With a group's members undone, as they were.
|
|
148
|
+
*/
|
|
149
|
+
private userIn;
|
|
150
|
+
/**
|
|
151
|
+
* The events of a followed change: the space write itself, then each
|
|
152
|
+
* moved user's roles, except in a space created or deleted by it.
|
|
153
|
+
*
|
|
154
|
+
* A role may have moved in a space the change did not touch, by another
|
|
155
|
+
* change not followed yet. Without the role attribute, that change
|
|
156
|
+
* announces it. With it, this follow wrote the value, so that change
|
|
157
|
+
* will find nothing to announce: the role goes out here, without an
|
|
158
|
+
* actor, as this change's is not the one that moved it.
|
|
159
|
+
*/
|
|
160
|
+
private announce;
|
|
161
|
+
/** Users as space events name them, by DN key, live users only. */
|
|
162
|
+
private profiles;
|
|
163
|
+
private publishEvent;
|
|
164
|
+
private get rabbitmq();
|
|
165
|
+
private publish;
|
|
166
|
+
/** A user's roles, in the directory as it is or with a change undone. */
|
|
167
|
+
private rolesOf;
|
|
168
|
+
/**
|
|
169
|
+
* Write a user's `<space id>:<role>` values, and answer the roles they
|
|
170
|
+
* held, or nothing for a user gone.
|
|
171
|
+
*/
|
|
172
|
+
private writeRoles;
|
|
173
|
+
/** The groups of the organization by lowercased id, as DNs, or a 404. */
|
|
174
|
+
private groupDns;
|
|
175
|
+
/** Each of a space's members or groups holds one role. */
|
|
176
|
+
private add;
|
|
177
|
+
private addOnce;
|
|
178
|
+
/**
|
|
179
|
+
* Move a member or group to another role, or out of the space without
|
|
180
|
+
* one. A space keeps at least one admin among its users.
|
|
181
|
+
*/
|
|
182
|
+
private change;
|
|
183
|
+
private changeOnce;
|
|
184
|
+
private listRoute;
|
|
185
|
+
private createRoute;
|
|
186
|
+
}
|
|
@@ -53,6 +53,11 @@ export default class TwakeTombstone extends DmPlugin {
|
|
|
53
53
|
force?: boolean;
|
|
54
54
|
req?: Request;
|
|
55
55
|
}): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Whether a delete of this DN, asked for now, writes a tombstone instead of
|
|
58
|
+
* reaching the directory: not when it erases one.
|
|
59
|
+
*/
|
|
60
|
+
keeps(dn: string): boolean;
|
|
56
61
|
/**
|
|
57
62
|
* core/ldap/trash moves a deleted entry away before this plugin, last in
|
|
58
63
|
* the delete chain, sees it: no tombstone would be written, and an erase
|
|
@@ -690,6 +690,27 @@ api(app: Express): void {
|
|
|
690
690
|
}
|
|
691
691
|
```
|
|
692
692
|
|
|
693
|
+
#### Parsing request bodies
|
|
694
|
+
|
|
695
|
+
The server parses `application/json` (up to 100 kB) and
|
|
696
|
+
`application/x-www-form-urlencoded` bodies before authentication runs, so
|
|
697
|
+
that `core/auth/hmac` can check them. A plugin that needs another media type
|
|
698
|
+
or limit under its prefix registers its own parser, which runs ahead of
|
|
699
|
+
those:
|
|
700
|
+
|
|
701
|
+
```typescript
|
|
702
|
+
import { jsonBodyParser } from '../../lib/rawBody';
|
|
703
|
+
|
|
704
|
+
api(app: Express): void {
|
|
705
|
+
this.server.registerBodyParser('/myplugin', jsonBodyParser({ limit: '1mb' }));
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
`jsonBodyParser` keeps the bytes it reads, which `core/auth/hmac` hashes. Any
|
|
710
|
+
other parser must pass `keepRawBody` as `verify`, or `core/auth/hmac` refuses
|
|
711
|
+
its bodies with `401`. A parser mounted with `app.use()` in `api()` runs
|
|
712
|
+
after authentication, too late for that check.
|
|
713
|
+
|
|
693
714
|
#### Error Handling Best Practices
|
|
694
715
|
|
|
695
716
|
The `asyncHandler` wrapper automatically catches errors and passes them to the global error middleware. This ensures:
|
|
@@ -19,6 +19,9 @@ Typescript definitions into [hooks.ts](./src/hooks.ts)
|
|
|
19
19
|
- **ldapmodifyrequest**: called before any ldapmodify
|
|
20
20
|
- **ldapdeleterequest**: called before any ldapdelete
|
|
21
21
|
- **ldaprenamerequest**: called before any ldap rename/modifyDN operation
|
|
22
|
+
- **ldapmodifyend**, **ldapdeleteend**, **ldaprenameend**: launched once a
|
|
23
|
+
modify, delete or rename is over, whatever became of it, see
|
|
24
|
+
[below](#ldapmodifyend-ldapdeleteend-ldaprenameend)
|
|
22
25
|
|
|
23
26
|
### ldapaddafter
|
|
24
27
|
|
|
@@ -41,6 +44,28 @@ the account the entry describes.
|
|
|
41
44
|
no set order with it: an add event may or may not include what a
|
|
42
45
|
subscriber here wrote.
|
|
43
46
|
|
|
47
|
+
### ldapmodifyend, ldapdeleteend, ldaprenameend
|
|
48
|
+
|
|
49
|
+
For a plugin that keeps something from a request hook to the matching "done"
|
|
50
|
+
one, as core/ldap/onChange keeps the entry it read. A write that is refused
|
|
51
|
+
by a request hook, that one of them takes out of the request (core/ldap/trash
|
|
52
|
+
and core/twake/tombstone do with a delete), or that the directory refuses,
|
|
53
|
+
never reaches its "done" hook; its "end" hook fires all the same.
|
|
54
|
+
|
|
55
|
+
- Each gets the number of its operation. A modify's request and "done" hooks
|
|
56
|
+
are given it in their arguments; for every modify, delete and rename,
|
|
57
|
+
`currentOperation()` (`lib/operation`) returns it inside the request and
|
|
58
|
+
"done" hooks. Key what you keep by it rather than by DN: two writes of one
|
|
59
|
+
DN may be under way, and only the operation tells them apart.
|
|
60
|
+
Outside those hooks it is whatever encloses the call (an add made from a
|
|
61
|
+
delete's request hook sees the delete's number): in an end hook, use the
|
|
62
|
+
number it is given.
|
|
63
|
+
- It is launched once the "done" subscribers of the operation have returned,
|
|
64
|
+
and is not awaited by the write.
|
|
65
|
+
- A move (`ldapActions.move`) launches no request hook and no end hook, and
|
|
66
|
+
its `ldaprenamedone` runs outside any operation, even when a request hook
|
|
67
|
+
made it.
|
|
68
|
+
|
|
44
69
|
## [onChange](../usage/plugins/ldap/on-change.md) hooks
|
|
45
70
|
|
|
46
71
|
- **onLdapEntryChange**: called after any write, with the entry before and
|
|
@@ -59,18 +59,37 @@ All CLI options can be set via environment variables with the `DM_` prefix.
|
|
|
59
59
|
|
|
60
60
|
Some options accept multiple values (e.g., `--plugin`, `--auth-token`, `--ldap-url`). These can be configured in several ways:
|
|
61
61
|
|
|
62
|
-
**Via CLI - repeat the option:**
|
|
62
|
+
**Via CLI - repeat the option, or give it comma-separated values:**
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
|
-
ldap-rest --plugin core/auth/token --plugin core/ldap/flatGeneric
|
|
65
|
+
ldap-rest --plugin core/auth/token --plugin core/ldap/flatGeneric,core/ldap/groups
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
Only options holding identifiers split on commas: `--plugin`, `--mail-domain`,
|
|
69
|
+
`--ldap-url`, the object class options (`--user-class`, `--group-class`,
|
|
70
|
+
`--ldap-organization-class`, `--external-branch-class`,
|
|
71
|
+
`--scim-user-object-class`, `--scim-group-object-class`,
|
|
72
|
+
`--twake-space-class`), the attribute
|
|
73
|
+
options (`--ldap-raw-hidden-attribute`, `--applicative-account-attribute`,
|
|
74
|
+
`--ldap-operational-attribute`, `--twake-tombstone-clear-attributes`),
|
|
75
|
+
`--twake-tombstone-reasons`, `--authz-for`, `--authz-dynamic-bypass` and
|
|
76
|
+
`--trusted-proxy`. Any other
|
|
77
|
+
option takes each value whole, so a DN or a secret keeps its commas.
|
|
78
|
+
|
|
68
79
|
**Via CLI - use plural form with comma-separated values:**
|
|
69
80
|
|
|
70
81
|
```bash
|
|
71
82
|
ldap-rest --plugins core/auth/token,core/ldap/flatGeneric,core/ldap/groups
|
|
72
83
|
```
|
|
73
84
|
|
|
85
|
+
The plural form splits like the environment variable, see below. Give a
|
|
86
|
+
DN with the repeated singular option, or separate DNs with `;` in the plural
|
|
87
|
+
form:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
ldap-rest --ldap-raw-base ou=users,dc=example,dc=com --ldap-raw-base ou=groups,dc=example,dc=com
|
|
91
|
+
```
|
|
92
|
+
|
|
74
93
|
**Via environment variable - use `;` or `,` as separator:**
|
|
75
94
|
|
|
76
95
|
```bash
|
|
@@ -81,7 +100,49 @@ export DM_PLUGINS="core/auth/token;core/ldap/flatGeneric;core/ldap/groups"
|
|
|
81
100
|
export DM_PLUGINS="core/auth/token,core/ldap/flatGeneric,core/ldap/groups"
|
|
82
101
|
```
|
|
83
102
|
|
|
84
|
-
|
|
103
|
+
The split depends on what the option holds. The plural form splits the same
|
|
104
|
+
way:
|
|
105
|
+
|
|
106
|
+
- Options holding identifiers (the list above): a value containing `;` is
|
|
107
|
+
split on `;` and spaces, any other on `,` and spaces. A value starting with
|
|
108
|
+
`;` is split on `;` too.
|
|
109
|
+
- Options holding DNs or DN expressions (`--ldap-raw-base`,
|
|
110
|
+
`--james-mailing-list-branch`, `--twake-tombstone-dn`,
|
|
111
|
+
`--twake-tombstone-group-bases`, `--twake-instance-dn`): split on `;` only,
|
|
112
|
+
never on `,` nor spaces, as a DN holds both.
|
|
113
|
+
`DM_LDAP_RAW_BASE="ou=My Unit,dc=example,dc=com"` is one DN; separate
|
|
114
|
+
several DNs with `;`.
|
|
115
|
+
- `--auth-hmac` (`id:secret:name`) and `--auth-totp` (`secret:name[:digits]`):
|
|
116
|
+
split on `;` if the value contains one, else on `,`, and on newlines (one
|
|
117
|
+
entry per line), never on spaces, so a name keeps its spaces. A list
|
|
118
|
+
separated by spaces is read as one entry: for HMAC the services after the
|
|
119
|
+
first cannot authenticate, and for TOTP the entry is refused. Any entry that
|
|
120
|
+
cannot work stops the server: an HMAC one with an empty id, secret or name or
|
|
121
|
+
fewer than 3 fields, a TOTP one with an empty secret or name, too many
|
|
122
|
+
fields, a secret that is not Base32 or digits that are not an integer from 6
|
|
123
|
+
to 10.
|
|
124
|
+
- Any other option (tokens, paths, rules...): split on `;` if
|
|
125
|
+
the value contains one, else on `,`, and on spaces. `DM_AUTH_TOKENS="t1 t2"`
|
|
126
|
+
and `DM_AUTH_TOKENS="t1,t2"` are two tokens, and padded base64 secrets
|
|
127
|
+
(`YWJj=,ZGVm=`) split on the comma.
|
|
128
|
+
|
|
129
|
+
Empty items are dropped, and each item is trimmed. A variable that is empty,
|
|
130
|
+
only holds whitespace or only holds separators (`;`, `${A:-};${B:-}`), such as
|
|
131
|
+
`${VAR:-}` in a compose file, counts as unset: the default applies, so an
|
|
132
|
+
environment variable cannot give an empty list to an option whose default is
|
|
133
|
+
not empty. The exception is `DM_LDAP_URL`, whose default `ldap://localhost` is
|
|
134
|
+
a guess: an empty value stops the server. The same
|
|
135
|
+
goes for an empty number or JSON value such as `DM_PORT=`. A number option
|
|
136
|
+
takes an integer only: `DM_PORT=abc`, `DM_PORT=1e3`, `--port 80abc` and
|
|
137
|
+
`--port --log-level debug` stop the server, and so does an empty number on the
|
|
138
|
+
command line, as an empty array value does.
|
|
139
|
+
The plural form of an option takes `;` before `,` too: `--auth-tokens 'a;b,c'`
|
|
140
|
+
is `a` and `b,c`.
|
|
141
|
+
|
|
142
|
+
Values given on the command line replace the default, and are added to those
|
|
143
|
+
of the environment variable. An empty value on the command line, such as an
|
|
144
|
+
unset variable expanding to nothing, stops the server: leave the option out
|
|
145
|
+
to keep the default.
|
|
85
146
|
|
|
86
147
|
## General Options
|
|
87
148
|
|
|
@@ -500,7 +561,6 @@ See [lifecycle events](plugins/integrations/lifecycle-events.md).
|
|
|
500
561
|
| `--twake-instance-organization-domain-attribute` | `DM_TWAKE_INSTANCE_ORGANIZATION_DOMAIN_ATTRIBUTE` | `twakeDomain` | Attribute holding an organization's domain |
|
|
501
562
|
| `--twake-instance-organization-name-attribute` | `DM_TWAKE_INSTANCE_ORGANIZATION_NAME_ATTRIBUTE` | `description` | Attribute holding an organization's name |
|
|
502
563
|
| `--twake-instance-organization-fqdn-attribute` | `DM_TWAKE_INSTANCE_ORGANIZATION_FQDN_ATTRIBUTE` | | Address attribute of an organization (empty: not written) |
|
|
503
|
-
| `--twake-instance-organization-account` | `DM_TWAKE_INSTANCE_ORGANIZATION_ACCOUNT` | | Organization account DN, `{id}` its id (empty: none) |
|
|
504
564
|
|
|
505
565
|
See [instances](plugins/integrations/instances.md).
|
|
506
566
|
|
|
@@ -542,6 +602,25 @@ Extends `core/ldap/groups`, whose options it reads too.
|
|
|
542
602
|
|
|
543
603
|
See [groups](plugins/integrations/groups.md).
|
|
544
604
|
|
|
605
|
+
#### `core/twake/spaces`
|
|
606
|
+
|
|
607
|
+
Requires `core/twake/groups`, whose options it reads for users, groups and the
|
|
608
|
+
organization entry, and `core/ldap/onChange`; with `--rabbitmq-url`, it
|
|
609
|
+
requires `core/rabbitmq`.
|
|
610
|
+
|
|
611
|
+
| CLI | Env | Default | Description |
|
|
612
|
+
| -------------------------------------- | --------------------------------------- | ------------------ | -------------------------------------------------- |
|
|
613
|
+
| `--twake-space-base` | `DM_TWAKE_SPACE_BASE` | | Space branch of an organization, with `{org}` |
|
|
614
|
+
| `--twake-space-class` | `DM_TWAKE_SPACE_CLASS` | `top,twakeSpace` | Object classes of a space |
|
|
615
|
+
| `--twake-space-display-name-attribute` | `DM_TWAKE_SPACE_DISPLAY_NAME_ATTRIBUTE` | `twakeDisplayName` | Space name attribute |
|
|
616
|
+
| `--twake-space-admin-attribute` | `DM_TWAKE_SPACE_ADMIN_ATTRIBUTE` | `twakeSpaceAdmin` | DNs holding the admin role |
|
|
617
|
+
| `--twake-space-editor-attribute` | `DM_TWAKE_SPACE_EDITOR_ATTRIBUTE` | `twakeSpaceEditor` | DNs holding the editor role |
|
|
618
|
+
| `--twake-space-viewer-attribute` | `DM_TWAKE_SPACE_VIEWER_ATTRIBUTE` | `twakeSpaceViewer` | DNs holding the viewer role |
|
|
619
|
+
| `--twake-space-user-role-attribute` | `DM_TWAKE_SPACE_USER_ROLE_ATTRIBUTE` | | User attribute holding `<space id>:<role>`, if set |
|
|
620
|
+
| `--twake-space-exchange` | `DM_TWAKE_SPACE_EXCHANGE` | `space` | Topic exchange of the space events |
|
|
621
|
+
|
|
622
|
+
See [spaces](plugins/integrations/spaces.md).
|
|
623
|
+
|
|
545
624
|
### Utility Plugins
|
|
546
625
|
|
|
547
626
|
#### `core/static`
|
|
@@ -60,8 +60,13 @@ DM_AUTH_HMAC_WINDOW=120000 # Default: 120000ms (2 minutes)
|
|
|
60
60
|
|
|
61
61
|
Only a body the server parses before checking the signature can be signed:
|
|
62
62
|
`application/json`, `application/x-www-form-urlencoded` and, under the
|
|
63
|
-
|
|
63
|
+
prefix of a loaded `core/scim`, `application/scim+json`. A POST, PATCH or PUT carrying any other body,
|
|
64
64
|
such as a `multipart/form-data` bulk import upload, is refused with `401`.
|
|
65
|
+
So is a GET, DELETE or HEAD carrying a body, since they are signed without
|
|
66
|
+
one.
|
|
67
|
+
|
|
68
|
+
The `Content-Type` is not part of the signature. A signed body is read as
|
|
69
|
+
UTF-8 only: one sent under another `charset` is refused with `401`.
|
|
65
70
|
|
|
66
71
|
2. **Client sends Authorization header:**
|
|
67
72
|
|
|
@@ -19,6 +19,7 @@ Plugins for connecting LDAP-Rest to external systems.
|
|
|
19
19
|
| [instances](instances.md) | Workplace instance of each account |
|
|
20
20
|
| [tombstone](tombstone.md) | Deleted accounts kept as tombstones |
|
|
21
21
|
| [groups](groups.md) | Organization groups |
|
|
22
|
+
| [spaces](spaces.md) | Organization spaces, with member roles |
|
|
22
23
|
|
|
23
24
|
## Prerequisites
|
|
24
25
|
|
|
@@ -47,4 +48,9 @@ core/twake/calendar
|
|
|
47
48
|
core/twake/lifecycleEvents
|
|
48
49
|
├─ requires: core/ldap/onChange
|
|
49
50
|
└─ requires: core/rabbitmq
|
|
51
|
+
|
|
52
|
+
core/twake/spaces
|
|
53
|
+
├─ requires: core/twake/groups
|
|
54
|
+
├─ requires: core/ldap/onChange
|
|
55
|
+
└─ requires: core/rabbitmq (with RabbitMQ)
|
|
50
56
|
```
|
|
@@ -12,8 +12,14 @@ branch per organization. It serves the groups of an organization under
|
|
|
12
12
|
display name attribute, an optional color and its creation date.
|
|
13
13
|
- A group's members belong to its organization: adding a user of another
|
|
14
14
|
organization, or of no organization, is refused with a 400, whichever API
|
|
15
|
-
writes the group (REST, SCIM, another plugin)
|
|
16
|
-
|
|
15
|
+
writes the group (REST, SCIM, another plugin), and so is moving a group
|
|
16
|
+
with its members, tombstones included, straight into another
|
|
17
|
+
organization's group branch: that refusal names none of them. The
|
|
18
|
+
placeholder (`--group-dummy-user`) is the one exception. Not checked:
|
|
19
|
+
moving a group out of the organizations' branches (to `ou=archive,…`, for
|
|
20
|
+
instance) or deeper than one level under another organization, which
|
|
21
|
+
takes its members along, and moving a user to another organization, whose
|
|
22
|
+
groups in the one it left keep it as a member.
|
|
17
23
|
- An empty group holds the placeholder, because `groupOfNames` needs a member.
|
|
18
24
|
The placeholder is never listed.
|
|
19
25
|
- A tombstone (see [tombstone](tombstone.md)) keeps its memberships until it
|
|
@@ -33,7 +39,8 @@ branch per organization. It serves the groups of an organization under
|
|
|
33
39
|
|
|
34
40
|
Every route answers 404 `ORGANIZATION_NOT_FOUND` when the organization entry
|
|
35
41
|
is missing, and 410 `ORGANIZATION_DELETED` when it is deleted. Errors are
|
|
36
|
-
`{ "error": "...", "code": "..." }`.
|
|
42
|
+
`{ "error": "...", "code": "..." }`. A request an authorization plugin refuses
|
|
43
|
+
answers 403 `REFUSED`.
|
|
37
44
|
|
|
38
45
|
A group reads:
|
|
39
46
|
|
|
@@ -55,8 +62,8 @@ A group reads:
|
|
|
55
62
|
display name reads its `cn`.
|
|
56
63
|
|
|
57
64
|
- `GET /groups?page&limit&search&sortBy&sortOrder`: `search` (2 characters
|
|
58
|
-
or more) matches the display name and the
|
|
59
|
-
`displayName`, `description` or `createdAt`. Answers
|
|
65
|
+
or more; an empty one lists everything) matches the display name and the
|
|
66
|
+
description; `sortBy` is `displayName`, `description` or `createdAt`. Answers
|
|
60
67
|
`{ organizationId, groups, pagination: { page, limit, total, totalPages } }`.
|
|
61
68
|
- `POST /groups` with `{ name, description?, color? }`: `name` is at most 256
|
|
62
69
|
characters, not blank, without control characters, and unique in the
|
|
@@ -75,16 +75,6 @@ from `--twake-instance-organization-name-attribute`, which
|
|
|
75
75
|
`ensureOrganization` fills from `name` when the entry has none. The mark, not
|
|
76
76
|
the address, says the organization is done.
|
|
77
77
|
|
|
78
|
-
`--twake-instance-organization-account` names an account of the
|
|
79
|
-
organization by a DN template, `{id}` standing for the organization id, for
|
|
80
|
-
example `uid={id},ou=users,ou={id},ou=organizations,dc=example,dc=com`. It
|
|
81
|
-
is usually a technical account, kept from an instance of its own by
|
|
82
|
-
`--twake-instance-skip-attribute`. When the organization is announced, and on
|
|
83
|
-
any replay, the account gets the organization's address in
|
|
84
|
-
`--twake-instance-fqdn-attribute`, when it exists and has none. An account
|
|
85
|
-
added once the organization is marked gets it at creation, read from the
|
|
86
|
-
organization entry or else from the provider.
|
|
87
|
-
|
|
88
78
|
With the Cloudery, a member's `user.created` waits for its organization's: a
|
|
89
79
|
member under an organization entry that is not marked yet gets its address
|
|
90
80
|
but no event and no mark. A member outside its organization entry is not
|
|
@@ -195,7 +195,9 @@ Payload and `when` values are sources:
|
|
|
195
195
|
- anything else: the value itself
|
|
196
196
|
|
|
197
197
|
`$attr` and `$previous.attr` carry the first value of a multi-valued
|
|
198
|
-
attribute. A source with no value leaves its field out.
|
|
198
|
+
attribute. A source with no value leaves its field out. Every attribute a
|
|
199
|
+
source names is requested by name when the entry is read, so an operational
|
|
200
|
+
one such as `entryUUID` works anywhere in a payload or a `when`.
|
|
199
201
|
|
|
200
202
|
A payload field can also list members, as an object with one key:
|
|
201
203
|
|