ldap-rest 1.3.0 → 2.0.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 (71) 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/options.js +1 -1
  5. package/dist/config/schema.js +1 -1
  6. package/dist/lib/authz/base.js +1 -1
  7. package/dist/lib/errors.js +1 -1
  8. package/dist/lib/ldap/callerView.js +1 -0
  9. package/dist/lib/ldap/pool.js +1 -0
  10. package/dist/lib/ldap/schema.js +1 -0
  11. package/dist/lib/ldap/searchCache.js +1 -0
  12. package/dist/lib/ldapActions.js +1 -1
  13. package/dist/lib/parseConfig.js +1 -1
  14. package/dist/lib/services/index.js +1 -0
  15. package/dist/lib/utils.js +1 -1
  16. package/dist/plugins/ldap/accountLifecycle.js +1 -1
  17. package/dist/plugins/ldap/enterpriseRules.js +1 -1
  18. package/dist/plugins/ldap/groups.js +1 -1
  19. package/dist/plugins/ldap/organizations.js +1 -1
  20. package/dist/plugins/ldap/passwordPolicy.js +1 -1
  21. package/dist/plugins/ldap/raw.js +1 -1
  22. package/dist/plugins/ldap/trash.js +1 -1
  23. package/dist/plugins/scim/mapping.js +1 -1
  24. package/dist/plugins/scim/patch.js +1 -1
  25. package/dist/plugins/storage/file.js +1 -1
  26. package/dist/plugins/twake/instances.js +1 -1
  27. package/dist/plugins/twake/lifecycleAttributes.js +1 -1
  28. package/dist/plugins/twake/lifecycleEvents.js +1 -1
  29. package/dist/plugins/twake/tombstone.js +1 -1
  30. package/dist/src/abstract/ldapFlat.d.ts +13 -0
  31. package/dist/src/abstract/plugin.d.ts +21 -0
  32. package/dist/src/bin/index.d.ts +18 -0
  33. package/dist/src/config/options.d.ts +16 -1
  34. package/dist/src/lib/errors.d.ts +2 -1
  35. package/dist/src/lib/ldap/callerView.d.ts +45 -0
  36. package/dist/src/lib/ldap/pool.d.ts +108 -0
  37. package/dist/src/lib/ldap/schema.d.ts +157 -0
  38. package/dist/src/lib/ldap/searchCache.d.ts +112 -0
  39. package/dist/src/lib/ldapActions.d.ts +25 -262
  40. package/dist/src/lib/parseConfig.d.ts +26 -2
  41. package/dist/src/lib/services/index.d.ts +56 -0
  42. package/dist/src/lib/utils.d.ts +72 -3
  43. package/dist/src/plugins/ldap/enterpriseRules.d.ts +0 -14
  44. package/dist/src/plugins/ldap/passwordPolicy.d.ts +3 -2
  45. package/dist/src/plugins/scim/mapping.d.ts +16 -3
  46. package/dist/src/plugins/scim/types.d.ts +2 -0
  47. package/dist/src/plugins/twake/instances.d.ts +31 -0
  48. package/dist/src/plugins/twake/lifecycleAttributes.d.ts +13 -2
  49. package/dist/src/plugins/twake/tombstone.d.ts +9 -2
  50. package/docs/README.md +16 -2
  51. package/docs/client-development/api/rest-api.md +4 -0
  52. package/docs/development/contracts.md +109 -0
  53. package/docs/plugin-development/README.md +8 -1
  54. package/docs/plugin-development/dependencies.md +13 -2
  55. package/docs/plugin-development/hooks.md +2 -1
  56. package/docs/plugin-development/options.md +40 -13
  57. package/docs/plugin-development/services.md +95 -0
  58. package/docs/usage/operations.md +9 -0
  59. package/docs/usage/plugins/integrations/instances.md +11 -3
  60. package/docs/usage/plugins/integrations/lifecycle-events.md +4 -1
  61. package/docs/usage/plugins/integrations/scim.md +20 -18
  62. package/docs/usage/plugins/integrations/tombstone.md +10 -6
  63. package/docs/usage/plugins/ldap/flat-generic.md +8 -1
  64. package/docs/usage/plugins/ldap/groups.md +4 -1
  65. package/docs/usage/plugins/ldap/password-policy.md +7 -0
  66. package/docs/usage/upgrading.md +59 -0
  67. package/package.json +17 -12
  68. package/static/browser/ldap-tree-viewer/LdapTreeViewer.js.map +1 -1
  69. package/static/browser/ldap-tree-viewer.esm.js.map +1 -1
  70. package/static/browser/ldap-tree-viewer.js.map +1 -1
  71. package/static/browser/ldap-tree-viewer.min.js.map +1 -1
@@ -0,0 +1,157 @@
1
+ /**
2
+ * The directory schema: where it lives, its index, how this server spells
3
+ * attribute names, the object classes an entity declares and the repair a
4
+ * write needs.
5
+ */
6
+ import type { SearchOptions, SearchResult } from 'ldapts';
7
+ import type { AttributesList, ModifyRequest } from '../ldapActions';
8
+ import { SchemaIndex } from '../ldapSchema';
9
+ export interface SchemaLogger {
10
+ info(message: string): void;
11
+ warn(message: string): void;
12
+ }
13
+ export type DirectorySearch = (options: SearchOptions, base: string) => Promise<SearchResult | AsyncGenerator<SearchResult>>;
14
+ export interface DirectorySchemaOptions {
15
+ logger: SchemaLogger;
16
+ /** Run a search as the server itself. */
17
+ search: DirectorySearch;
18
+ /** Whether a modify may add the classes it needs. */
19
+ autoRepair: () => boolean;
20
+ /** How long the schema index is kept, in milliseconds. */
21
+ schemaCacheTtl: () => number;
22
+ }
23
+ export default class DirectorySchema {
24
+ private readonly logger;
25
+ private readonly search;
26
+ private readonly autoRepair;
27
+ private readonly schemaCacheTtl;
28
+ /** See {@link declareObjectClasses}. */
29
+ private declaredClasses;
30
+ private schemaCache?;
31
+ private schemaLoading?;
32
+ /** Whether the last schema read failed, so a failure is logged once. */
33
+ private schemaUnavailable;
34
+ /**
35
+ * How this server spells attribute names, by name in lower case: the
36
+ * configuration's, then the loaded schemas'. See {@link canonicalName}.
37
+ */
38
+ private spellings;
39
+ /** The first name given for each attribute type, to warn of another. */
40
+ private spelledTypes;
41
+ /** Class problems already reported, per entry. See {@link warnOnce}. */
42
+ private classWarnings;
43
+ constructor(options: DirectorySchemaOptions);
44
+ /**
45
+ * Declare the object classes an entity gives the entries of its branch.
46
+ *
47
+ * An entry created by another tool may lack some of them, and a class it
48
+ * lacks can forbid an attribute written here: the organization link of an
49
+ * account made as a bare `inetOrgPerson` is refused by the directory. A
50
+ * modify writing such an attribute adds the auxiliary class that allows
51
+ * it, see {@link classRepair}.
52
+ *
53
+ * @param base branch holding the entity's entries
54
+ * @param classes object classes the entity's schema declares; a schema
55
+ * file may give a single one as a string
56
+ * @param includeBase whether the base is itself one of the entries, as the
57
+ * top organization is
58
+ */
59
+ declareObjectClasses(base: string, classes: string[] | string, includeBase?: boolean): void;
60
+ /**
61
+ * The classes declared for the branch holding a DN. Several entities may
62
+ * share a branch — the shipped `users`, `posixAccounts` and
63
+ * `sshPublicKeys` schemas all live in `ou=users` — so every declaration at
64
+ * the deepest matching base counts.
65
+ */
66
+ private declaredFor;
67
+ /**
68
+ * Log a class problem of an entry once, not on every write to it: nothing
69
+ * an administrator can do stops a sync job from touching it again.
70
+ */
71
+ private warnOnce;
72
+ /**
73
+ * The auxiliary classes a modify needs added: for each attribute it writes
74
+ * that the entry's classes do not allow, a class declared for its branch
75
+ * or by the writer that allows it, provided the entry holds or receives
76
+ * that class's mandatory attributes once the change is applied.
77
+ *
78
+ * Only what the write needs is added. Entities sharing a branch declare
79
+ * classes that are not all meant for every entry: `shadowAccount` is not
80
+ * something every account should be given because it sits in `ou=users`.
81
+ * And a write that the directory would have accepted is never changed.
82
+ *
83
+ * Nothing is added when the change names `objectClass` itself, and a
84
+ * schema or entry that cannot be read skips the repair.
85
+ *
86
+ * @param dn entry being modified
87
+ * @param changes the modification, after the hooks
88
+ * @param extra classes the writer declares, see {@link ModifyOptions}
89
+ * @returns names of the classes to add, or `allowed` when the entry's
90
+ * classes take the change as it is
91
+ */
92
+ classRepair(dn: string, changes: ModifyRequest, extra?: string[]): Promise<string[] | 'allowed'>;
93
+ /**
94
+ * DN of the subschema entry the root DSE advertises, or the conventional
95
+ * `cn=Subschema` when it advertises none.
96
+ */
97
+ subschemaDn(): Promise<string>;
98
+ /**
99
+ * The directory schema, kept for `--ldap-raw-schema-cache-ttl`: it is
100
+ * large and changes rarely. The raw browser and the object class repair
101
+ * share it, and concurrent callers share a single read.
102
+ *
103
+ * @returns indexed schema
104
+ */
105
+ schemaIndex(): Promise<SchemaIndex>;
106
+ /**
107
+ * Say how this server spells attribute names: a schema loaded by a plugin
108
+ * names its attributes as the plugins compare them. The first spelling of
109
+ * a name wins.
110
+ *
111
+ * @param names attribute names
112
+ */
113
+ knowAttributeNames(names: Iterable<string>): void;
114
+ /**
115
+ * Name each attribute of a write as this server does, in place.
116
+ *
117
+ * The directory takes `MAIL`, `rfc822Mailbox` and `0.9.2342.19200300.100.1.3`
118
+ * for `mail`, while the authorization plugins and the hooks look for
119
+ * `mail`: a write naming the organization link by its OID was judged as
120
+ * if it left the link alone. Each name is resolved through the directory
121
+ * schema — case, aliases, OIDs — and written as the configuration or a
122
+ * loaded schema spells it, or as the directory declares it first.
123
+ *
124
+ * An option names a value of its own (`member;lang-en` is not `member` to
125
+ * the hooks guarding `member`), and is refused, `;binary` aside. Two names
126
+ * for one attribute in one part of a write are refused too: on a modify
127
+ * the directory applies both, and only one would have been judged.
128
+ *
129
+ * Without the schema, the case is still set, and a name given as an OID,
130
+ * which nothing here could resolve, is refused. So is, with or without
131
+ * it, a name that is neither a descriptor nor a numeric OID.
132
+ *
133
+ * @param entry attributes of an add, or one part of a modify
134
+ * @throws BadRequestError on an option, on a name that describes no
135
+ * attribute, or on one attribute named twice
136
+ */
137
+ canonicalAttributes(entry: AttributesList | undefined): Promise<void>;
138
+ /**
139
+ * {@link canonicalAttributes} for every part of a modify, in place.
140
+ *
141
+ * @param changes the modify
142
+ * @throws BadRequestError as {@link canonicalAttributes}
143
+ */
144
+ canonicalChanges(changes: ModifyRequest | undefined): Promise<void>;
145
+ /**
146
+ * The names of one part of a write, as {@link canonicalAttributes} writes
147
+ * them.
148
+ */
149
+ private canonicalNames;
150
+ /**
151
+ * One attribute description, named as this server does.
152
+ *
153
+ * @param key the name as written, options included
154
+ * @param index the directory schema, when it could be read
155
+ */
156
+ private canonicalName;
157
+ }
@@ -0,0 +1,112 @@
1
+ import type { SearchOptions, SearchResult } from 'ldapts';
2
+ export interface CacheLogger {
3
+ info(message: string): void;
4
+ }
5
+ export interface SearchCacheOptions {
6
+ max: number;
7
+ /** Milliseconds; 0 switches the cache off. */
8
+ ttl: number;
9
+ logger: CacheLogger;
10
+ }
11
+ /**
12
+ * Short, stable stand-in for a cache key in a log line.
13
+ *
14
+ * The key is built from the search base, which comes from configuration and
15
+ * therefore ultimately from the environment — logging it verbatim is what
16
+ * CodeQL flags as clear-text logging of environment-derived data. A digest
17
+ * carries none of the key's content, but the same key always digests to the
18
+ * same token, so a "cache hit" line can still be matched back to the
19
+ * "cached" line that filled the entry it hit.
20
+ */
21
+ export declare const digestKey: (key: string) => string;
22
+ /** The DNs that attributes of an entry or a change name as members. */
23
+ export declare const membersNamed: (attributes: Record<string, unknown> | undefined) => string[];
24
+ /** The parts of a change that name members, as `applyModify` reads them. */
25
+ type MembershipChanges = {
26
+ add?: Record<string, unknown>;
27
+ replace?: Record<string, unknown>;
28
+ delete?: string[] | Record<string, unknown>;
29
+ };
30
+ export default class SearchCache {
31
+ readonly enabled: boolean;
32
+ private readonly logger;
33
+ private readonly searchCache;
34
+ /**
35
+ * How many writes this cache has seen.
36
+ *
37
+ * A read can be overtaken by a write: the write lands and `invalidate`
38
+ * finds nothing to drop, because the read has not stored its answer yet —
39
+ * and the read then stores what it read *before* the write, an entry
40
+ * nothing will drop until its TTL runs out. A search remembers the count
41
+ * it started at and stores only if no write happened in between, so a read
42
+ * a write overtook costs a cache entry, never a stale answer.
43
+ *
44
+ * The count is per cache and per write, not per DN: a write to another
45
+ * branch keeps a concurrent read out of the cache too. That is hit rate
46
+ * given up, not freshness.
47
+ */
48
+ private cacheGeneration;
49
+ constructor(options: SearchCacheOptions);
50
+ get generation(): number;
51
+ /**
52
+ * Generate cache key for LDAP search
53
+ *
54
+ * Every option that changes the answer belongs in here, or a search that
55
+ * asked for more is served what a search that asked for less got: the
56
+ * requested attributes above all, but also the ones that change the shape
57
+ * of what comes back. `timeLimit` is deliberately absent — it bounds how
58
+ * long the server may spend, not what it returns.
59
+ */
60
+ key(base: string, opts: SearchOptions): string;
61
+ /** The cached answer, as a copy the caller may edit. */
62
+ get(key: string): SearchResult | undefined;
63
+ /** Store a copy of the answer; later callers cannot edit what was stored. */
64
+ set(key: string, result: SearchResult): void;
65
+ /**
66
+ * Drop every cached read of `dn`, and of everything below it.
67
+ *
68
+ * Called after each write, so the next read of what just changed goes to
69
+ * the directory. It compares DNs the way the directory does (see
70
+ * {@link foldDn}), so a write spelling a DN differently from the read that
71
+ * cached it still drops it; and it drops the subtree, because a rename or
72
+ * a move re-parents a whole branch in one operation — renaming an
73
+ * organization changes the DN of every entry under it — leaving the cached
74
+ * reads of those children filed under DNs that no longer exist. A leaf has
75
+ * nothing below it and the extra test costs nothing.
76
+ */
77
+ invalidate(dn: string): void;
78
+ /**
79
+ * Drop every cached read holding a value that names `dn`, or an entry
80
+ * below it.
81
+ *
82
+ * The directory mirrors some writes onto other entries than the one
83
+ * written: the `memberof` overlay keeps each member's `memberOf` in step
84
+ * with the group's `member` / `uniqueMember`, and `refint` rewrites or
85
+ * removes the references to an entry renamed or deleted. Those entries'
86
+ * cached reads are what {@link invalidate} cannot find, and they name
87
+ * the entry written: the members of a group name it in their `memberOf`.
88
+ * A member a write adds names it only once the write has landed; the
89
+ * caller drops those by their own DN.
90
+ *
91
+ * Every attribute is looked at, not `memberOf` alone: which attributes an
92
+ * overlay maintains is the deployment's configuration. A read naming `dn`
93
+ * for another reason is dropped too, which costs a miss.
94
+ */
95
+ invalidateReferences(dn: string): void;
96
+ /**
97
+ * Drop what a change of a group's members makes stale: the group, what
98
+ * names it, and the members the change names. A replace or a delete of
99
+ * every value removes members the change no longer names; their cached
100
+ * reads are found by the group's DN in their `memberOf`.
101
+ */
102
+ invalidateMembership(dn: string, changes: MembershipChanges): void;
103
+ /**
104
+ * Get a sorted signature for an attribute list.
105
+ *
106
+ * The signature is JSON, not a separator-joined string: an attribute
107
+ * description may contain any character that could stand for a separator,
108
+ * so joining would let `['cn|mail']` and `['cn', 'mail']` share one.
109
+ */
110
+ private getAttributeSignature;
111
+ }
112
+ export {};
@@ -1,12 +1,16 @@
1
+ /**
2
+ * LDAP low-level library
3
+ * @author Xavier Guimard <xguimard@linagora.com>
4
+ */
1
5
  import type { Request } from 'express';
2
- import type { ClientOptions, SearchResult, SearchOptions } from 'ldapts';
6
+ import type { SearchResult, SearchOptions } from 'ldapts';
3
7
  import type winston from 'winston';
4
8
  import pLimit from 'p-limit';
5
9
  import { type Config } from '../config/args';
6
10
  import { type DM } from '../bin';
7
11
  import type { Hooks } from '../hooks';
8
12
  import { type ChangeContext } from './changeContext';
9
- import { SchemaIndex } from './ldapSchema';
13
+ import type { SchemaIndex } from './ldapSchema';
10
14
  export type AttributeValue = Buffer | Buffer[] | string[] | string;
11
15
  export type AttributesList = Record<string, AttributeValue>;
12
16
  export type LdapList = Record<string, AttributesList>;
@@ -68,91 +72,19 @@ export interface ModifyOptions {
68
72
  type AuthorizedHook = 'ldapaddrequest' | 'ldapmodifyrequest' | 'ldapdeleterequest' | 'ldaprenamerequest';
69
73
  declare class ldapActions {
70
74
  config: Config;
71
- options: ClientOptions;
72
- dn: string;
73
- pwd: string;
74
75
  base: string;
75
76
  parent: DM;
76
77
  logger: winston.Logger;
77
- private searchCache;
78
- private cacheEnabled;
79
- /**
80
- * How many writes this instance has made since it was built.
81
- *
82
- * A read can be overtaken by a write: the write lands and `invalidateCache`
83
- * finds nothing to drop, because the read has not stored its answer yet —
84
- * and the read then stores what it read *before* the write, an entry
85
- * nothing will drop until its TTL runs out. `search()` remembers the count
86
- * it started at and stores only if no write happened in between, so a read
87
- * a write overtook costs a cache entry, never a stale answer.
88
- *
89
- * The count is per instance and per write, not per DN: a write to another
90
- * branch keeps a concurrent read out of the cache too. That is hit rate
91
- * given up, not freshness.
92
- */
93
- private cacheGeneration;
78
+ private cache;
94
79
  queryLimit: ReturnType<typeof pLimit>;
95
- private connectionPool;
96
- private poolSize;
97
- private connectionTtl;
98
- private ldapUrls;
99
- private currentUrlIndex;
100
- private attrSignatureCache;
101
- private waitingResolvers;
102
- private availableConnections;
103
- /** Connections being opened, not yet in `connectionPool` */
104
- private opening;
105
- private isCleaningUp;
106
- /** See {@link declareObjectClasses}. */
107
- private declaredClasses;
108
- private schemaCache?;
109
- private schemaLoading?;
110
- /** Whether the last schema read failed, so a failure is logged once. */
111
- private schemaUnavailable;
112
- /**
113
- * How this server spells attribute names, by name in lower case: the
114
- * configuration's, then the loaded schemas'. See {@link canonicalName}.
115
- */
116
- private spellings;
117
- /** The first name given for each attribute type, to warn of another. */
118
- private spelledTypes;
119
- /** Class problems already reported, per entry. See {@link warnOnce}. */
120
- private classWarnings;
80
+ private pool;
81
+ private schema;
82
+ private caller;
121
83
  constructor(server: DM);
122
- /**
123
- * Create a new LDAP connection with failover support
124
- */
125
- private createConnection;
126
- /**
127
- * Clean up expired connections from the pool
128
- * Uses a flag to prevent concurrent cleanup operations
129
- */
130
- private cleanupExpiredConnections;
131
- /**
132
- * Ask the directory for its root DSE on a connection of its own, bound
133
- * as the server and closed after: the readiness probe. Neither the pool
134
- * nor the search cache nor the hooks answer for a directory that does
135
- * not, and a directory that stops answering holds nothing of the pool.
136
- * The URLs are tried as `createConnection` tries them.
137
- *
138
- * @param timeoutMs how long the connection, the bind and the search may
139
- * each take; the client closes the socket when it passes
140
- * @throws when no URL answers in time
141
- */
84
+ /** See {@link LdapPool.ping}. */
142
85
  ping(timeoutMs: number): Promise<void>;
143
- /** Set by `close`: no connection is handed out any more */
144
- private closed;
145
- /**
146
- * Close every pooled connection, the ones in use included: an operation
147
- * still running on one is rejected by the client, and a waiter is refused
148
- * rather than kept.
149
- */
86
+ /** See {@link LdapPool.close}. */
150
87
  close(): Promise<void>;
151
- /**
152
- * Acquire a connection from the pool or create a new one
153
- * Optimized for O(1) lookup using separate available connections queue
154
- */
155
- private acquireConnection;
156
88
  /** The pool as it is, for the metrics */
157
89
  poolStats(): {
158
90
  busy: number;
@@ -161,62 +93,13 @@ declare class ldapActions {
161
93
  opening: number;
162
94
  max: number;
163
95
  };
164
- private takeConnection;
165
- /** Refuse every queued request with `error` */
166
- private refuseWaiters;
167
- /** Let one queued request take a place that freed with no connection */
168
- private wakeWaiter;
169
- /**
170
- * Release a connection back to the pool
171
- * If there are waiting requests, hand off the connection directly
172
- */
173
- private releaseConnection;
174
- /**
175
- * Get a sorted signature for attribute list, using cache for performance
176
- */
177
- private getAttributeSignature;
178
- /**
179
- * Generate cache key for LDAP search
180
- *
181
- * Every option that changes the answer belongs in here, or a search that
182
- * asked for more is served what a search that asked for less got: the
183
- * requested attributes above all, but also the ones that change the shape
184
- * of what comes back. `timeLimit` is deliberately absent — it bounds how
185
- * long the server may spend, not what it returns.
186
- */
187
- private getCacheKey;
188
96
  /**
189
97
  * Drop every cached read of `dn`, and of everything below it.
190
98
  *
191
99
  * Called after each write, so the next read of what just changed goes to
192
- * the directory. Two things this does that a `key.startsWith(dn)` test did
193
- * not: it compares DNs the way the directory does (see {@link foldDn}), so
194
- * a write spelling a DN differently from the read that cached it still
195
- * drops it; and it drops the subtree, because `rename()` and `move()`
196
- * re-parent a whole branch in one operation — renaming an organization
197
- * changes the DN of every entry under it — leaving the cached reads of
198
- * those children filed under DNs that no longer exist. A leaf has nothing
199
- * below it and the extra test costs nothing.
100
+ * the directory. See {@link SearchCache.invalidate}.
200
101
  */
201
102
  invalidateCache(dn: string): void;
202
- /**
203
- * Drop every cached read holding a value that names `dn`, or an entry
204
- * below it.
205
- *
206
- * The directory mirrors some writes onto other entries than the one
207
- * written: the `memberof` overlay keeps each member's `memberOf` in step
208
- * with the group's `member` / `uniqueMember`, and `refint` rewrites or
209
- * removes the references to an entry renamed or deleted. Those entries'
210
- * cached reads are what {@link invalidateCache} cannot find, and they name
211
- * the entry written: the members of a group name it in their `memberOf`.
212
- * A member a write adds names it only once the write has landed; the
213
- * caller drops those by their own DN.
214
- *
215
- * Every attribute is looked at, not `memberOf` alone: which attributes an
216
- * overlay maintains is the deployment's configuration. A read naming `dn`
217
- * for another reason is dropped too, which costs a miss.
218
- */
219
- private invalidateReferences;
220
103
  /**
221
104
  * The directory as the server itself, with no request behind it.
222
105
  *
@@ -249,20 +132,7 @@ declare class ldapActions {
249
132
  * hooks always see it. See {@link RequestBoundLdap}.
250
133
  */
251
134
  forRequest(req: Request): RequestBoundLdap;
252
- /**
253
- * Whether the caller of a request can read an entry.
254
- *
255
- * For a route that finds an entry as the server — by an attribute, across
256
- * branches — and then answers about it: an entry the caller cannot read
257
- * must get the answer an entry that is not there gets, or the route tells
258
- * them what the directory holds where they cannot read. Refused or
259
- * filtered out by an authorization plugin, the entry reads as absent.
260
- * Without a request, as for any operation, nobody judges.
261
- *
262
- * @param dn the entry
263
- * @param req the request whose caller asks
264
- * @returns whether a read of the entry, as that caller, finds it
265
- */
135
+ /** See {@link CallerView.readableBy}. */
266
136
  readableBy(dn: string, req?: Request): Promise<boolean>;
267
137
  /**
268
138
  * Have the authorization plugins judge an operation, without making it.
@@ -284,134 +154,27 @@ declare class ldapActions {
284
154
  * @throws the refusal of the first authorization plugin refusing
285
155
  */
286
156
  authorize<H extends AuthorizedHook>(hook: H, args: Parameters<NonNullable<Hooks[H]>>[0]): Promise<void>;
287
- /**
288
- * Declare the object classes an entity gives the entries of its branch.
289
- *
290
- * An entry created by another tool may lack some of them, and a class it
291
- * lacks can forbid an attribute written here: the organization link of an
292
- * account made as a bare `inetOrgPerson` is refused by the directory. A
293
- * modify writing such an attribute adds the auxiliary class that allows
294
- * it, see {@link classRepair}.
295
- *
296
- * @param base branch holding the entity's entries
297
- * @param classes object classes the entity's schema declares; a schema
298
- * file may give a single one as a string
299
- * @param includeBase whether the base is itself one of the entries, as the
300
- * top organization is
301
- */
157
+ /** See {@link DirectorySchema.declareObjectClasses}. */
302
158
  declareObjectClasses(base: string, classes: string[] | string, includeBase?: boolean): void;
303
- /**
304
- * The classes declared for the branch holding a DN. Several entities may
305
- * share a branch — the shipped `users`, `posixAccounts` and
306
- * `sshPublicKeys` schemas all live in `ou=users` — so every declaration at
307
- * the deepest matching base counts.
308
- */
309
- private declaredFor;
310
- /**
311
- * Log a class problem of an entry once, not on every write to it: nothing
312
- * an administrator can do stops a sync job from touching it again.
313
- */
314
- private warnOnce;
315
- /**
316
- * The auxiliary classes a modify needs added: for each attribute it writes
317
- * that the entry's classes do not allow, a class declared for its branch
318
- * or by the writer that allows it, provided the entry holds or receives
319
- * that class's mandatory attributes once the change is applied.
320
- *
321
- * Only what the write needs is added. Entities sharing a branch declare
322
- * classes that are not all meant for every entry: `shadowAccount` is not
323
- * something every account should be given because it sits in `ou=users`.
324
- * And a write that the directory would have accepted is never changed.
325
- *
326
- * Nothing is added when the change names `objectClass` itself, and a
327
- * schema or entry that cannot be read skips the repair.
328
- *
329
- * @param dn entry being modified
330
- * @param changes the modification, after the hooks
331
- * @param extra classes the writer declares, see {@link ModifyOptions}
332
- * @returns names of the classes to add, or `allowed` when the entry's
333
- * classes take the change as it is
334
- */
335
- private classRepair;
336
- /**
337
- * DN of the subschema entry the root DSE advertises, or the conventional
338
- * `cn=Subschema` when it advertises none.
339
- */
159
+ /** See {@link DirectorySchema.subschemaDn}. */
340
160
  subschemaDn(): Promise<string>;
341
- /**
342
- * The directory schema, kept for `--ldap-raw-schema-cache-ttl`: it is
343
- * large and changes rarely. The raw browser and the object class repair
344
- * share it, and concurrent callers share a single read.
345
- *
346
- * @returns indexed schema
347
- */
161
+ /** See {@link DirectorySchema.schemaIndex}. */
348
162
  schemaIndex(): Promise<SchemaIndex>;
349
- /**
350
- * Say how this server spells attribute names: a schema loaded by a plugin
351
- * names its attributes as the plugins compare them. The first spelling of
352
- * a name wins.
353
- *
354
- * @param names attribute names
355
- */
163
+ /** See {@link DirectorySchema.knowAttributeNames}. */
356
164
  knowAttributeNames(names: Iterable<string>): void;
357
- /**
358
- * Name each attribute of a write as this server does, in place.
359
- *
360
- * The directory takes `MAIL`, `rfc822Mailbox` and `0.9.2342.19200300.100.1.3`
361
- * for `mail`, while the authorization plugins and the hooks look for
362
- * `mail`: a write naming the organization link by its OID was judged as
363
- * if it left the link alone. Each name is resolved through the directory
364
- * schema — case, aliases, OIDs — and written as the configuration or a
365
- * loaded schema spells it, or as the directory declares it first.
366
- *
367
- * An option names a value of its own (`member;lang-en` is not `member` to
368
- * the hooks guarding `member`), and is refused, `;binary` aside. Two names
369
- * for one attribute in one part of a write are refused too: on a modify
370
- * the directory applies both, and only one would have been judged.
371
- *
372
- * Without the schema, the case is still set, and a name given as an OID,
373
- * which nothing here could resolve, is refused. So is, with or without
374
- * it, a name that is neither a descriptor nor a numeric OID.
375
- *
376
- * @param entry attributes of an add, or one part of a modify
377
- * @throws BadRequestError on an option, on a name that describes no
378
- * attribute, or on one attribute named twice
379
- */
165
+ /** See {@link DirectorySchema.canonicalAttributes}. */
380
166
  canonicalAttributes(entry: AttributesList | undefined): Promise<void>;
381
- /**
382
- * {@link canonicalAttributes} for every part of a modify, in place.
383
- *
384
- * @param changes the modify
385
- * @throws BadRequestError as {@link canonicalAttributes}
386
- */
167
+ /** See {@link DirectorySchema.canonicalChanges}. */
387
168
  canonicalChanges(changes: ModifyRequest | undefined): Promise<void>;
388
- /**
389
- * The names of one part of a write, as {@link canonicalAttributes} writes
390
- * them.
391
- */
392
- private canonicalNames;
393
- /**
394
- * One attribute description, named as this server does.
395
- *
396
- * @param key the name as written, options included
397
- * @param index the directory schema, when it could be read
398
- */
399
- private canonicalName;
400
- /**
401
- * Hand a search result to `ldapsearchfilter`, which may drop entries the
402
- * caller is not allowed to see.
403
- *
404
- * Applied on every return path of `search`, and deliberately after the
405
- * cache: `ldapsearchresult` fires before caching and knows no request, so
406
- * what it removed for one caller would be served to the next. A paginated
407
- * search is filtered chunk by chunk, as it is consumed.
408
- */
169
+ /** See {@link CallerView.filter}. */
409
170
  private filterForCaller;
410
171
  search(options: SearchOptions, base?: string, req?: Request): Promise<SearchResult | AsyncGenerator<SearchResult>>;
411
172
  /**
412
- * Wrap paginated search to ensure connection is released when done
173
+ * A paged search, opened on its first page: a generator that is never
174
+ * started holds no connection. The pool is taken here, in the generator
175
+ * body, not by `search()`.
413
176
  */
414
- private wrapPaginatedSearch;
177
+ private pagedSearch;
415
178
  add(dn: string, entry: AttributesList, req?: Request): Promise<boolean>;
416
179
  modify(dn: string, changes: ModifyRequest, req?: Request, options?: ModifyOptions): Promise<boolean>;
417
180
  private applyModify;
@@ -9,18 +9,42 @@ export type ArrayKind = 'identifiers' | 'dns' | 'phrases';
9
9
  export type ConfigEntry = [
10
10
  string,
11
11
  string,
12
- (string | string[] | boolean | number | Record<string, any>),
12
+ // env value
13
+ string | string[] | boolean | number | Record<string, any>,
13
14
  ('string' | 'number' | 'boolean' | 'array' | 'json' | null | undefined)?,
14
15
  (string | null | undefined)?,
15
16
  ArrayKind?,
16
- boolean?
17
+ boolean?,
18
+ (string[] | null | undefined)?,
19
+ (string[] | null | undefined)?
17
20
  ];
18
21
  export declare class ConfigParser {
19
22
  private config;
20
23
  /** CLI arguments no entry declared, kept in `Config` by the parser */
21
24
  private unknownArgs;
25
+ /** Deprecated names the configuration used, reported once each */
26
+ private readonly deprecated;
22
27
  get unknownOptions(): string[];
28
+ /** Names read through a deprecated alias, for the server to warn about */
29
+ get deprecationWarnings(): string[];
30
+ private warnOnce;
31
+ /** The entry a CLI name stands for, whether canonical or a deprecated alias */
32
+ private findEntry;
33
+ /**
34
+ * The environment variable an entry reads: its own, or a deprecated alias
35
+ * when that one is set and its own is not.
36
+ */
37
+ private envNameOf;
38
+ /**
39
+ * Whether a variable value counts as set. An empty value is unset for the
40
+ * types that read it as nothing: a compose file expands an unset
41
+ * `${VAR:-}` to one. For a string or a boolean, an empty value means
42
+ * something and stays set.
43
+ */
44
+ private environmentIsSet;
23
45
  constructor(config: ConfigTemplate);
46
+ /** A deprecated name must not shadow a declared one, or be declared twice */
47
+ private checkAliases;
24
48
  parse(argv?: string[]): Config;
25
49
  private parseCliArgs;
26
50
  private getKeyFromCliArg;