ldap-rest 1.1.0 → 1.2.1
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/bin/index.mjs +8 -7
- package/dist/abstract/ldapFlat.js +1 -1
- package/dist/abstract/plugin.js +1 -1
- package/dist/abstract/twakePlugin.js +1 -1
- package/dist/bin/index.js +1 -1
- package/dist/config/args.js +1 -1
- package/dist/config/schema.js +1 -1
- package/dist/lib/changeContext.js +1 -1
- package/dist/lib/entitySchema/validator.js +1 -0
- package/dist/lib/expressFormatedResponses.js +1 -1
- package/dist/lib/health.js +1 -0
- package/dist/lib/instanceProviders.js +1 -1
- package/dist/lib/ldapActions.js +1 -1
- package/dist/lib/ldapSchema.js +1 -1
- package/dist/lib/metrics.js +1 -0
- package/dist/lib/requestContext.js +1 -1
- package/dist/lib/shutdown.js +1 -0
- package/dist/lib/staticFiles.js +1 -0
- package/dist/lib/storage/store.js +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/logger/winston.js +1 -1
- package/dist/plugins/auth/crowdsec.js +1 -1
- package/dist/plugins/auth/openidconnect.js +1 -1
- package/dist/plugins/demo/helloworld.js +1 -1
- package/dist/plugins/ldap/bulkImport.js +1 -1
- package/dist/plugins/ldap/departmentSync.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/metrics.js +1 -0
- package/dist/plugins/rabbitmq.js +1 -1
- package/dist/plugins/scim/baseResolver.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/mapping.js +1 -1
- package/dist/plugins/scim/patch.js +1 -1
- package/dist/plugins/scim/projection.js +1 -1
- package/dist/plugins/scim/scim.js +1 -1
- package/dist/plugins/scim/users.js +1 -1
- package/dist/plugins/static.js +1 -1
- package/dist/plugins/storage/file.js +1 -1
- package/dist/plugins/storage/postgres.js +1 -1
- package/dist/plugins/storage/valkey.js +1 -1
- package/dist/plugins/storage.js +1 -1
- package/dist/plugins/twake/calendar.js +1 -1
- package/dist/plugins/twake/clouderyProvision.js +1 -1
- package/dist/plugins/twake/cozyProvision.js +1 -1
- package/dist/plugins/twake/drive.js +1 -1
- package/dist/plugins/twake/groups.js +1 -1
- package/dist/plugins/twake/james.js +1 -1
- package/dist/plugins/twake/migrationProxy.js +1 -0
- package/dist/plugins/twake/spaces.js +1 -1
- package/dist/plugins/weblogs.js +1 -1
- package/dist/src/abstract/ldapFlat.d.ts +3 -13
- package/dist/src/abstract/plugin.d.ts +20 -0
- package/dist/src/abstract/twakePlugin.d.ts +23 -1
- package/dist/src/bin/index.d.ts +46 -1
- package/dist/src/config/args.d.ts +12 -1
- package/dist/src/lib/changeContext.d.ts +11 -0
- package/dist/src/lib/entitySchema/validator.d.ts +64 -0
- package/dist/src/lib/expressFormatedResponses.d.ts +19 -0
- package/dist/src/lib/health.d.ts +63 -0
- package/dist/src/lib/instanceProviders.d.ts +4 -0
- package/dist/src/lib/ldapActions.d.ts +32 -2
- package/dist/src/lib/metrics.d.ts +44 -0
- package/dist/src/lib/requestContext.d.ts +40 -0
- package/dist/src/lib/shutdown.d.ts +20 -0
- package/dist/src/lib/staticFiles.d.ts +33 -0
- package/dist/src/lib/storage/store.d.ts +8 -0
- package/dist/src/lib/utils.d.ts +15 -0
- package/dist/src/logger/winston.d.ts +4 -0
- package/dist/src/plugins/ldap/groups.d.ts +18 -3
- package/dist/src/plugins/ldap/organizations.d.ts +3 -5
- package/dist/src/plugins/metrics.d.ts +17 -0
- package/dist/src/plugins/rabbitmq.d.ts +37 -1
- package/dist/src/plugins/scim/errors.d.ts +2 -0
- package/dist/src/plugins/scim/types.d.ts +2 -1
- package/dist/src/plugins/static.d.ts +12 -0
- package/dist/src/plugins/storage/file.d.ts +4 -0
- package/dist/src/plugins/storage/postgres.d.ts +7 -14
- package/dist/src/plugins/storage/valkey.d.ts +2 -15
- package/dist/src/plugins/storage.d.ts +8 -0
- package/dist/src/plugins/twake/drive.d.ts +8 -0
- package/dist/src/plugins/twake/groups.d.ts +1 -2
- package/dist/src/plugins/twake/james.d.ts +2 -0
- package/dist/src/plugins/twake/migrationProxy.d.ts +71 -0
- package/docs/client-development/api/reference.md +5 -9
- package/docs/client-development/api/rest-api.md +4 -10
- package/docs/client-development/schemas/README.md +60 -21
- package/docs/plugin-development/README.md +15 -13
- package/docs/plugin-development/dependencies.md +8 -0
- package/docs/plugin-development/hooks.md +1 -1
- package/docs/usage/README.md +1 -0
- package/docs/usage/configuration.md +62 -24
- package/docs/usage/operations.md +193 -0
- package/docs/usage/plugins/README.md +2 -0
- package/docs/usage/plugins/auth/authz-per-branch.md +2 -1
- package/docs/usage/plugins/integrations/README.md +8 -4
- package/docs/usage/plugins/integrations/groups.md +2 -2
- package/docs/usage/plugins/integrations/mail-migration.md +102 -0
- package/docs/usage/plugins/integrations/scim.md +16 -3
- package/docs/usage/plugins/integrations/spaces.md +1 -1
- package/docs/usage/plugins/ldap/bulk-import.md +3 -1
- package/docs/usage/plugins/ldap/flat-generic.md +5 -2
- package/docs/usage/plugins/ldap/on-change.md +2 -1
- package/docs/usage/plugins/ldap/organizations.md +7 -7
- package/docs/usage/plugins/utilities/metrics.md +21 -0
- package/docs/usage/plugins/utilities/static.md +16 -8
- package/docs/usage/plugins/utilities/storage.md +7 -0
- package/docs/usage/plugins/utilities/weblogs.md +51 -30
- package/docs/usage/upgrading.md +84 -0
- package/package.json +11 -2
- package/static/schemas/example/README.md +1 -0
package/dist/src/bin/index.d.ts
CHANGED
|
@@ -30,7 +30,9 @@ export declare class DM {
|
|
|
30
30
|
app: express.Express;
|
|
31
31
|
config: Config;
|
|
32
32
|
ready: Promise<void>;
|
|
33
|
-
|
|
33
|
+
/** The listener, once `run()` opened it; shared by the `withConfig` views */
|
|
34
|
+
get server(): import('http').Server | undefined;
|
|
35
|
+
set server(server: import('http').Server | undefined);
|
|
34
36
|
hooks: {
|
|
35
37
|
[K in keyof Hooks]?: Function[];
|
|
36
38
|
};
|
|
@@ -60,6 +62,21 @@ export declare class DM {
|
|
|
60
62
|
* assembled by hand — is checked on its own as it arrives.
|
|
61
63
|
*/
|
|
62
64
|
private _composed;
|
|
65
|
+
/** Every plugin loaded and the composition checked */
|
|
66
|
+
get composed(): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* The listener, the shutdown once started, and the rejections nobody
|
|
69
|
+
* handled since the start. In an object, shared by the `withConfig` views
|
|
70
|
+
* as `_authState` is: a view with a flag of its own would stop on its own,
|
|
71
|
+
* and one without the listener would not close it.
|
|
72
|
+
*/
|
|
73
|
+
private readonly life;
|
|
74
|
+
/** The responses not sent yet, told to close their connection on a stop */
|
|
75
|
+
private readonly inFlight;
|
|
76
|
+
/** Told to stop: the readiness says so, what is in flight finishes */
|
|
77
|
+
get stopping(): boolean;
|
|
78
|
+
/** Rejections nobody handled since the start, logged and counted */
|
|
79
|
+
get unhandledRejections(): number;
|
|
63
80
|
private _errorMiddleware?;
|
|
64
81
|
/**
|
|
65
82
|
* The loads of `--plugin` entries without overrides, keyed by resolved
|
|
@@ -195,8 +212,36 @@ export declare class DM {
|
|
|
195
212
|
*/
|
|
196
213
|
warnUnauthenticatedRoutes(): string[];
|
|
197
214
|
setupErrorMiddleware(): void;
|
|
215
|
+
/**
|
|
216
|
+
* Exit on a failure nothing caught: an exception leaves the process in a
|
|
217
|
+
* state nobody knows, so it shuts down as cleanly as it can, within the
|
|
218
|
+
* shutdown budget, and exits with 1 for its supervisor to start it again.
|
|
219
|
+
* A rejection nothing handled is logged and counted; the request that
|
|
220
|
+
* owned it has been answered or will time out.
|
|
221
|
+
*
|
|
222
|
+
* For the binary; a host assembling the server decides for itself, and
|
|
223
|
+
* without it Node's own action applies.
|
|
224
|
+
*/
|
|
225
|
+
exitOnCrash(): void;
|
|
198
226
|
run(): Promise<void>;
|
|
199
227
|
stop(): void;
|
|
228
|
+
/**
|
|
229
|
+
* Stop serving: the readiness answers 503, the listening socket closes,
|
|
230
|
+
* what is in flight finishes, then every plugin's `close()` runs and the
|
|
231
|
+
* directory connections are closed, the whole within `--shutdown-timeout`
|
|
232
|
+
* whatever hangs. Called once; a second call waits for the first.
|
|
233
|
+
*
|
|
234
|
+
* @returns when everything let go, resolved whatever failed on the way
|
|
235
|
+
*/
|
|
236
|
+
shutdown(): Promise<void>;
|
|
237
|
+
private letGo;
|
|
238
|
+
/** @returns whether `done` settled before `ms` passed */
|
|
239
|
+
private static within;
|
|
240
|
+
/**
|
|
241
|
+
* Shut down on SIGTERM and SIGINT, then let the signal end the process.
|
|
242
|
+
* For the binary; a host assembling the server decides for itself.
|
|
243
|
+
*/
|
|
244
|
+
shutdownOnSignals(): void;
|
|
200
245
|
/**
|
|
201
246
|
* The module part of a `--plugin` entry.
|
|
202
247
|
*
|
|
@@ -32,7 +32,10 @@ export interface Config {
|
|
|
32
32
|
plugin?: string[];
|
|
33
33
|
schemas_path: string;
|
|
34
34
|
log_level: 'error' | 'warn' | 'notice' | 'info' | 'debug';
|
|
35
|
-
|
|
35
|
+
shutdown_timeout?: number;
|
|
36
|
+
debug: boolean;
|
|
37
|
+
logger: 'console' | 'file';
|
|
38
|
+
log_file?: string;
|
|
36
39
|
api_prefix: string;
|
|
37
40
|
mail_domain?: string[];
|
|
38
41
|
ldap_base?: string;
|
|
@@ -53,6 +56,8 @@ export interface Config {
|
|
|
53
56
|
groups_allow_unexistent_members?: boolean;
|
|
54
57
|
group_dummy_user?: string;
|
|
55
58
|
group_schema?: string;
|
|
59
|
+
/** Where core/metrics serves `GET /metrics` */
|
|
60
|
+
metrics_port?: number;
|
|
56
61
|
/**
|
|
57
62
|
* Keyed storage, shared by whoever needs to keep something with a
|
|
58
63
|
* deadline. `storage_backend` names the sub-plugin that keeps it; the rest
|
|
@@ -197,6 +202,12 @@ export interface Config {
|
|
|
197
202
|
james_mailbox_type_attribute?: string;
|
|
198
203
|
james_sync_forwards?: boolean;
|
|
199
204
|
james_team_mailbox_any_domain?: boolean;
|
|
205
|
+
migration_proxy_url?: string;
|
|
206
|
+
migration_proxy_token?: string;
|
|
207
|
+
migration_proxy_branch?: string[];
|
|
208
|
+
migration_proxy_concurrency?: number;
|
|
209
|
+
migration_proxy_retry_attempts?: number;
|
|
210
|
+
migration_proxy_retry_delay?: number;
|
|
200
211
|
twake_drive_webadmin_url?: string;
|
|
201
212
|
twake_drive_webadmin_token?: string;
|
|
202
213
|
twake_drive_concurrency?: number;
|
|
@@ -14,6 +14,17 @@ export interface ChangeContext {
|
|
|
14
14
|
* @param source its name, e.g. `scim`
|
|
15
15
|
*/
|
|
16
16
|
export declare function setChangeSource(req: Request, source: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* The identifier of a request, shared by its writes, its log lines and the
|
|
19
|
+
* `X-Request-Id` of its answer.
|
|
20
|
+
*
|
|
21
|
+
* Generated rather than read from an `X-Request-Id` header: a client could
|
|
22
|
+
* otherwise make the writes of two requests look like one.
|
|
23
|
+
*
|
|
24
|
+
* @param req the request
|
|
25
|
+
* @returns its identifier, the same on every call
|
|
26
|
+
*/
|
|
27
|
+
export declare function requestId(req: Request): string;
|
|
17
28
|
/**
|
|
18
29
|
* The context of a write, handed to the "done" hooks.
|
|
19
30
|
*
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { AttributesList, AttributeValue, ModifyRequest } from '../ldapActions';
|
|
2
|
+
import type { Schema } from '../../config/schema';
|
|
3
|
+
export interface EntityValidatorDeps {
|
|
4
|
+
/**
|
|
5
|
+
* Tells whether a DN names an entry. Asked as the server: a reference to
|
|
6
|
+
* an entry the caller cannot read is still a valid reference.
|
|
7
|
+
*/
|
|
8
|
+
entryExists: (dn: string) => Promise<boolean>;
|
|
9
|
+
/** Asked whether a `generated` attribute will be filled after validation */
|
|
10
|
+
loadedPlugins: Record<string, unknown>;
|
|
11
|
+
/** Names the entity in the messages */
|
|
12
|
+
singularName: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Enforces an entity schema on what a client sends: the entry of a creation
|
|
16
|
+
* and the parts of a change. Every refusal is a `BadRequestError`.
|
|
17
|
+
*
|
|
18
|
+
* See `docs/client-development/schemas/README.md` for the rules.
|
|
19
|
+
*/
|
|
20
|
+
export declare class EntityValidator {
|
|
21
|
+
private readonly schema;
|
|
22
|
+
private readonly deps;
|
|
23
|
+
constructor(schema: Schema | undefined, deps: EntityValidatorDeps);
|
|
24
|
+
/**
|
|
25
|
+
* Check a new entry, then convert it in place for the directory.
|
|
26
|
+
*
|
|
27
|
+
* @param entry entry as the client sent it, `objectClass` included
|
|
28
|
+
*/
|
|
29
|
+
validateNew(entry: AttributesList): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Check the parts of a change, then convert them in place. No value under
|
|
32
|
+
* a name is the empty list for the directory layer, which clears the
|
|
33
|
+
* attribute in a `replace` and refuses it in an `add`.
|
|
34
|
+
*
|
|
35
|
+
* @param changes change as the client sent it
|
|
36
|
+
*/
|
|
37
|
+
validateChanges(changes: ModifyRequest): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Check one value against its attribute's rules: its shape, then the
|
|
40
|
+
* entries a pointer names, then the pattern.
|
|
41
|
+
*
|
|
42
|
+
* @param field attribute name, as the client wrote it
|
|
43
|
+
* @param value value sent; no value clears the attribute
|
|
44
|
+
* @returns the value in the shape of the type: one value for a
|
|
45
|
+
* single-valued attribute, a list for an `array`
|
|
46
|
+
*/
|
|
47
|
+
validateValue(field: string, value: AttributeValue | null | undefined): Promise<AttributeValue | null | undefined>;
|
|
48
|
+
/**
|
|
49
|
+
* The schema's definition of an attribute, under any case.
|
|
50
|
+
*
|
|
51
|
+
* @throws BadRequestError for a name a strict schema does not declare
|
|
52
|
+
*/
|
|
53
|
+
private attribute;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Write every fixed attribute with a default as the schema says, whatever
|
|
57
|
+
* the request carried. `objectClass` is left to the entity's configuration.
|
|
58
|
+
* Run on the entry an authorization judges, so that it judges the entry
|
|
59
|
+
* written.
|
|
60
|
+
*
|
|
61
|
+
* @param schema entity schema, nothing to apply without one
|
|
62
|
+
* @param entry entry about to be created, changed in place
|
|
63
|
+
*/
|
|
64
|
+
export declare function applyFixedDefaults(schema: Schema | undefined, entry: AttributesList): void;
|
|
@@ -13,6 +13,25 @@ import type winston from 'winston';
|
|
|
13
13
|
export declare const setLogger: (logger: winston.Logger | undefined) => void;
|
|
14
14
|
/** @returns the installed logger, or undefined before any `DM` exists */
|
|
15
15
|
export declare const getLogger: () => winston.Logger | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Whether `serverError` names the failure in its answer: the `--debug` of
|
|
18
|
+
* the server, installed as its logger is.
|
|
19
|
+
*
|
|
20
|
+
* @param debug true to name the failure in the response
|
|
21
|
+
*/
|
|
22
|
+
export declare const setDebug: (debug: boolean) => void;
|
|
23
|
+
/**
|
|
24
|
+
* The body of a 5xx answer, for the error middleware of `DM` and
|
|
25
|
+
* `serverError` alike. The failure is for the logs: the message names it
|
|
26
|
+
* only under `--debug`.
|
|
27
|
+
*
|
|
28
|
+
* @param err what failed
|
|
29
|
+
* @param debug whether to name the failure; `serverError`'s setting by default
|
|
30
|
+
*/
|
|
31
|
+
export declare const serverErrorBody: (err: unknown, debug?: boolean) => {
|
|
32
|
+
error: string;
|
|
33
|
+
message: string;
|
|
34
|
+
};
|
|
16
35
|
export declare const tryMethod: (res: Response, method: Function, ...args: unknown[]) => Promise<void>;
|
|
17
36
|
export declare const tryMethodData: (res: Response, method: Function, ...args: unknown[]) => Promise<void>;
|
|
18
37
|
export declare const ok: (res: Response, data?: object) => void;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Express } from 'express';
|
|
2
|
+
import type { MaybePromise } from '../hooks';
|
|
3
|
+
/** What a plugin answers about the resources it depends on */
|
|
4
|
+
export type HealthStatus = 'ok' | 'degraded';
|
|
5
|
+
/** The paths of the two probes, served before authentication */
|
|
6
|
+
export declare const HEALTH_PATHS: readonly ["/healthz", "/readyz"];
|
|
7
|
+
/** How long one readiness answer serves */
|
|
8
|
+
export declare const PROBE_TTL_MS = 5000;
|
|
9
|
+
/**
|
|
10
|
+
* How long a readiness answer may take, the directory and every plugin
|
|
11
|
+
* asked at once: under the 3 s a kubelet waits, so that a hanging resource
|
|
12
|
+
* is reported, not timed out
|
|
13
|
+
*/
|
|
14
|
+
export declare const PROBE_TIMEOUT_MS = 2000;
|
|
15
|
+
/** What the readiness probe reads of the server */
|
|
16
|
+
export interface Probed {
|
|
17
|
+
/** Every plugin loaded and the composition checked */
|
|
18
|
+
readonly composed: boolean;
|
|
19
|
+
/** Told to stop: what is in flight finishes, nothing new is taken */
|
|
20
|
+
readonly stopping: boolean;
|
|
21
|
+
readonly ldap: {
|
|
22
|
+
ping: (timeoutMs: number) => Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
readonly loadedPlugins: {
|
|
25
|
+
[name: string]: {
|
|
26
|
+
health?: () => MaybePromise<HealthStatus>;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface Readiness {
|
|
31
|
+
/** `starting`, `stopping` and `unavailable` answer 503, the two others 200 */
|
|
32
|
+
status: 'starting' | 'stopping' | 'ok' | 'degraded' | 'unavailable';
|
|
33
|
+
ldap?: 'ok' | 'failed';
|
|
34
|
+
/** The plugins implementing `health()`; `failed` when it threw or hung */
|
|
35
|
+
plugins?: {
|
|
36
|
+
[name: string]: HealthStatus | 'failed';
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Tell whether the server can serve: loaded, with a directory that answers.
|
|
41
|
+
* A plugin's resource being down degrades the answer without failing it,
|
|
42
|
+
* so that one broken outbound service does not take every replica out of
|
|
43
|
+
* rotation.
|
|
44
|
+
*
|
|
45
|
+
* One answer serves `PROBE_TTL_MS`, and the probes arriving while it is
|
|
46
|
+
* made share it: a probe every few seconds from every replica, or a flood
|
|
47
|
+
* on an unauthenticated route, must not become a load on the directory nor
|
|
48
|
+
* on what the plugins ask.
|
|
49
|
+
*
|
|
50
|
+
* @param server what is probed
|
|
51
|
+
* @param now the moment, for the answer's age
|
|
52
|
+
* @param timeoutMs how long the whole answer may take
|
|
53
|
+
*/
|
|
54
|
+
export declare function readiness(server: Probed, now?: number, timeoutMs?: number): Promise<Readiness>;
|
|
55
|
+
/**
|
|
56
|
+
* Serve `GET /healthz`, which answers as long as the process serves, and
|
|
57
|
+
* `GET /readyz`, which answers 200 when `readiness` says the server can
|
|
58
|
+
* serve. Both are outside the API prefix and carry no identity.
|
|
59
|
+
*
|
|
60
|
+
* @param app the application, before its authentication
|
|
61
|
+
* @param server what the readiness probe reads
|
|
62
|
+
*/
|
|
63
|
+
export declare function mountHealth(app: Express, server: Probed): void;
|
|
@@ -69,6 +69,8 @@ export interface InstanceProvider {
|
|
|
69
69
|
declare abstract class HttpProvider {
|
|
70
70
|
protected readonly domain: string;
|
|
71
71
|
protected readonly timeout: number;
|
|
72
|
+
/** The `target` label of this provider's calls in the outbound metrics */
|
|
73
|
+
protected abstract readonly target: string;
|
|
72
74
|
constructor(domain: string, timeout: number);
|
|
73
75
|
address(request: InstanceRequest): string;
|
|
74
76
|
protected call(url: string, init?: RequestInit): Promise<Response>;
|
|
@@ -79,6 +81,7 @@ export declare class ClouderyProvider extends HttpProvider implements InstancePr
|
|
|
79
81
|
private readonly url;
|
|
80
82
|
private readonly token;
|
|
81
83
|
private readonly offer;
|
|
84
|
+
protected readonly target = "cloudery";
|
|
82
85
|
constructor(url: string, token: string, domain: string, offer: string, timeout: number);
|
|
83
86
|
private headers;
|
|
84
87
|
create(request: InstanceRequest): Promise<undefined>;
|
|
@@ -97,6 +100,7 @@ export declare class CozyStackProvider extends HttpProvider implements InstanceP
|
|
|
97
100
|
private readonly url;
|
|
98
101
|
private readonly context;
|
|
99
102
|
private readonly apps;
|
|
103
|
+
protected readonly target = "cozy-stack";
|
|
100
104
|
private readonly auth;
|
|
101
105
|
constructor(url: string, user: string, passphrase: string, domain: string, context: string, apps: string, timeout: number);
|
|
102
106
|
private headers;
|
|
@@ -126,11 +126,39 @@ declare class ldapActions {
|
|
|
126
126
|
* Uses a flag to prevent concurrent cleanup operations
|
|
127
127
|
*/
|
|
128
128
|
private cleanupExpiredConnections;
|
|
129
|
+
/**
|
|
130
|
+
* Ask the directory for its root DSE on a connection of its own, bound
|
|
131
|
+
* as the server and closed after: the readiness probe. Neither the pool
|
|
132
|
+
* nor the search cache nor the hooks answer for a directory that does
|
|
133
|
+
* not, and a directory that stops answering holds nothing of the pool.
|
|
134
|
+
* The URLs are tried as `createConnection` tries them.
|
|
135
|
+
*
|
|
136
|
+
* @param timeoutMs how long the connection, the bind and the search may
|
|
137
|
+
* each take; the client closes the socket when it passes
|
|
138
|
+
* @throws when no URL answers in time
|
|
139
|
+
*/
|
|
140
|
+
ping(timeoutMs: number): Promise<void>;
|
|
141
|
+
/** Set by `close`: no connection is handed out any more */
|
|
142
|
+
private closed;
|
|
143
|
+
/**
|
|
144
|
+
* Close every pooled connection, the ones in use included: an operation
|
|
145
|
+
* still running on one is rejected by the client, and a waiter is refused
|
|
146
|
+
* rather than kept.
|
|
147
|
+
*/
|
|
148
|
+
close(): Promise<void>;
|
|
129
149
|
/**
|
|
130
150
|
* Acquire a connection from the pool or create a new one
|
|
131
151
|
* Optimized for O(1) lookup using separate available connections queue
|
|
132
152
|
*/
|
|
133
153
|
private acquireConnection;
|
|
154
|
+
/** The pool as it is, for the metrics */
|
|
155
|
+
poolStats(): {
|
|
156
|
+
busy: number;
|
|
157
|
+
idle: number;
|
|
158
|
+
waiting: number;
|
|
159
|
+
max: number;
|
|
160
|
+
};
|
|
161
|
+
private takeConnection;
|
|
134
162
|
/**
|
|
135
163
|
* Release a connection back to the pool
|
|
136
164
|
* If there are waiting requests, hand off the connection directly
|
|
@@ -335,10 +363,12 @@ declare class ldapActions {
|
|
|
335
363
|
* the directory applies both, and only one would have been judged.
|
|
336
364
|
*
|
|
337
365
|
* Without the schema, the case is still set, and a name given as an OID,
|
|
338
|
-
* which nothing here could resolve, is refused.
|
|
366
|
+
* which nothing here could resolve, is refused. So is, with or without
|
|
367
|
+
* it, a name that is neither a descriptor nor a numeric OID.
|
|
339
368
|
*
|
|
340
369
|
* @param entry attributes of an add, or one part of a modify
|
|
341
|
-
* @throws BadRequestError on an option,
|
|
370
|
+
* @throws BadRequestError on an option, on a name that describes no
|
|
371
|
+
* attribute, or on one attribute named twice
|
|
342
372
|
*/
|
|
343
373
|
canonicalAttributes(entry: AttributesList | undefined): Promise<void>;
|
|
344
374
|
/**
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the server measures of itself, for `core/metrics` to expose; see
|
|
3
|
+
* docs/usage/operations.md#metrics
|
|
4
|
+
* @module lib/metrics
|
|
5
|
+
*/
|
|
6
|
+
import type { Request } from 'express';
|
|
7
|
+
import { Counter, Histogram, Registry } from 'prom-client';
|
|
8
|
+
/** The instruments below register here; `core/metrics` adds its own */
|
|
9
|
+
export declare const registry: Registry<"text/plain; version=0.0.4; charset=utf-8">;
|
|
10
|
+
export declare const httpRequestDuration: Histogram<"status" | "method" | "route">;
|
|
11
|
+
export type LdapOperation = 'bind' | 'search' | 'add' | 'modify' | 'rename' | 'delete';
|
|
12
|
+
export declare const ldapOperationDuration: Histogram<"operation" | "result">;
|
|
13
|
+
export declare const ldapPoolWait: Histogram<string>;
|
|
14
|
+
export declare const ldapSearchCache: Counter<"result">;
|
|
15
|
+
export declare const hookFailures: Counter<"plugin" | "hook">;
|
|
16
|
+
export declare const outboundRequestDuration: Histogram<"status" | "target">;
|
|
17
|
+
export declare const unhandledRejections: Counter<string>;
|
|
18
|
+
/**
|
|
19
|
+
* The `result` label of a directory call that threw: a refusal is the
|
|
20
|
+
* directory answering, by its result code (32 for an entry that is not
|
|
21
|
+
* there); `error` is for not answering.
|
|
22
|
+
*/
|
|
23
|
+
export declare const ldapResult: (err: unknown) => string;
|
|
24
|
+
/** @returns the seconds since `started`, a `process.hrtime.bigint()` */
|
|
25
|
+
export declare const secondsSince: (started: bigint) => number;
|
|
26
|
+
/**
|
|
27
|
+
* Time a directory call into the operation histogram, whatever becomes of
|
|
28
|
+
* it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function timedLdap<T>(operation: LdapOperation, call: () => Promise<T>): Promise<T>;
|
|
31
|
+
/**
|
|
32
|
+
* Time a call to another service into the outbound histogram, to its
|
|
33
|
+
* answer's headers: the body is read by the caller, at its own pace.
|
|
34
|
+
*/
|
|
35
|
+
export declare function timedOutbound<T extends {
|
|
36
|
+
status: number;
|
|
37
|
+
}>(target: string, call: () => Promise<T>): Promise<T>;
|
|
38
|
+
/**
|
|
39
|
+
* The template of the route that answered, never the URL: a label per URL
|
|
40
|
+
* would grow without bound. See docs/usage/operations.md#metrics.
|
|
41
|
+
*
|
|
42
|
+
* @returns the template, `none` when no route matched
|
|
43
|
+
*/
|
|
44
|
+
export declare const routeTemplate: (req: Request) => string;
|
|
@@ -21,3 +21,43 @@ export declare function outsideRequest<T>(fn: () => T): T;
|
|
|
21
21
|
* @returns the request, or undefined outside any, or as the server
|
|
22
22
|
*/
|
|
23
23
|
export declare function currentRequest(): Request | undefined;
|
|
24
|
+
/** What a log line carries beside what it says */
|
|
25
|
+
export interface LogContext {
|
|
26
|
+
/** The request being served */
|
|
27
|
+
requestId?: string;
|
|
28
|
+
/** The plugin whose hook is running */
|
|
29
|
+
plugin?: string;
|
|
30
|
+
/** That hook */
|
|
31
|
+
op?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Run something that outlives the request being served, a connection
|
|
35
|
+
* opened for the pool or a client kept for the process: its operations are
|
|
36
|
+
* judged by nobody, and what it logs later is nobody's request.
|
|
37
|
+
*
|
|
38
|
+
* @param fn what to run
|
|
39
|
+
* @returns what it returns
|
|
40
|
+
*/
|
|
41
|
+
export declare function outsideAnyRequest<T>(fn: () => T): T;
|
|
42
|
+
/**
|
|
43
|
+
* Serve a request with its log context: what runs inside, synchronously or
|
|
44
|
+
* not, logs with it.
|
|
45
|
+
*
|
|
46
|
+
* @param context what the lines carry
|
|
47
|
+
* @param fn what serves the request
|
|
48
|
+
* @returns what `fn` returns
|
|
49
|
+
*/
|
|
50
|
+
export declare function runWithLogContext<T>(context: LogContext, fn: () => T): T;
|
|
51
|
+
/**
|
|
52
|
+
* Run a hook: what it logs names its plugin and itself, under the request
|
|
53
|
+
* being served; a hook no plugin registered names none, even inside
|
|
54
|
+
* another's.
|
|
55
|
+
*
|
|
56
|
+
* @param plugin the plugin's instance name, none for an unregistered hook
|
|
57
|
+
* @param op the hook
|
|
58
|
+
* @param fn the hook's run
|
|
59
|
+
* @returns what `fn` returns
|
|
60
|
+
*/
|
|
61
|
+
export declare function runAsPlugin<T>(plugin: string | undefined, op: string | undefined, fn: () => T): T;
|
|
62
|
+
/** The log context of what is running, if any: request, plugin, hook */
|
|
63
|
+
export declare function currentLogContext(): LogContext | undefined;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type ShutdownCallback = () => void | Promise<void>;
|
|
2
|
+
/**
|
|
3
|
+
* How long the shutdown callbacks get before the signal is raised again.
|
|
4
|
+
* The binary sets it from `--shutdown-timeout`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const shutdownTimeout: {
|
|
7
|
+
ms: number;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Run a callback when the process is told to stop. The first registration
|
|
11
|
+
* installs the signal listeners; a callback registered while the shutdown
|
|
12
|
+
* runs is awaited with the others.
|
|
13
|
+
*
|
|
14
|
+
* Once every callback has settled, or the timeout has passed, the signal is
|
|
15
|
+
* raised again for its default action, unless another listener is there to
|
|
16
|
+
* decide.
|
|
17
|
+
*
|
|
18
|
+
* @param cb what to run, awaited when it answers a promise
|
|
19
|
+
*/
|
|
20
|
+
export declare function registerProcessShutdown(cb: ShutdownCallback): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the static plugins serve a schema file, for another plugin to
|
|
3
|
+
* announce it
|
|
4
|
+
* @module lib/staticFiles
|
|
5
|
+
*/
|
|
6
|
+
import type { DM } from '../bin';
|
|
7
|
+
/** A static plugin, as another plugin asks it where it serves a file */
|
|
8
|
+
export interface StaticFiles {
|
|
9
|
+
schemaUrlOf(file: string): string | undefined;
|
|
10
|
+
servedPath(): string;
|
|
11
|
+
}
|
|
12
|
+
/** Told by its methods, not its class: another copy of ldap-rest has its own */
|
|
13
|
+
export declare const isStaticFiles: (plugin: unknown) => plugin is StaticFiles;
|
|
14
|
+
/**
|
|
15
|
+
* The static plugins loaded, whatever their class or name: one loaded under
|
|
16
|
+
* another name, or with a name or a directory of its own, serves files
|
|
17
|
+
* elsewhere or not at all. The one registered as `static` comes first; one
|
|
18
|
+
* whose path an earlier plugin took, lies under it or holds it, is left
|
|
19
|
+
* out, the earlier one's routes answering first.
|
|
20
|
+
*
|
|
21
|
+
* @param server the server whose plugins are asked
|
|
22
|
+
* @returns the plugins, in the order to ask them
|
|
23
|
+
*/
|
|
24
|
+
export declare function staticPlugins(server: Pick<DM, 'loadedPlugins'>): StaticFiles[];
|
|
25
|
+
/**
|
|
26
|
+
* URL a static plugin serves a schema file at, merged, asked of
|
|
27
|
+
* {@link staticPlugins} in their order
|
|
28
|
+
*
|
|
29
|
+
* @param server the server whose plugins are asked
|
|
30
|
+
* @param file the file, as the plugin announcing it reads it
|
|
31
|
+
* @returns its URL, or undefined when no static plugin serves it merged
|
|
32
|
+
*/
|
|
33
|
+
export declare function staticSchemaUrl(server: Pick<DM, 'loadedPlugins'>, file: string): string | undefined;
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* @group Libraries
|
|
27
27
|
*/
|
|
28
28
|
import type winston from 'winston';
|
|
29
|
+
import type { HealthStatus } from '../health';
|
|
29
30
|
/** What a backend keeps: a value, and when it stops counting. */
|
|
30
31
|
export interface StoredRecord {
|
|
31
32
|
value: string;
|
|
@@ -45,6 +46,11 @@ export default abstract class Store {
|
|
|
45
46
|
protected abstract drop(key: string): Promise<void>;
|
|
46
47
|
/** Drop every expired record. Returns how many went. */
|
|
47
48
|
abstract sweep(): Promise<number>;
|
|
49
|
+
/**
|
|
50
|
+
* Whether the backend answers, asked by the readiness probe; a backend
|
|
51
|
+
* with a resource of its own asks it and throws when it does not answer.
|
|
52
|
+
*/
|
|
53
|
+
health(): Promise<HealthStatus>;
|
|
48
54
|
/**
|
|
49
55
|
* The key a backend actually sees, namespaced so two consumers cannot
|
|
50
56
|
* collide.
|
|
@@ -72,6 +78,8 @@ export default abstract class Store {
|
|
|
72
78
|
* operator reads it, instead of the first request that needs the store.
|
|
73
79
|
*/
|
|
74
80
|
open(): Promise<void>;
|
|
81
|
+
/** Set by `close`: a backend opens nothing past it */
|
|
82
|
+
protected closed: boolean;
|
|
75
83
|
/** Stop sweeping and let go of any connection. */
|
|
76
84
|
close(): Promise<void>;
|
|
77
85
|
/**
|
package/dist/src/lib/utils.d.ts
CHANGED
|
@@ -22,6 +22,15 @@ export declare function getCompiledRegex(pattern: string, flags?: string): RegEx
|
|
|
22
22
|
* @returns The escaped string safe for use in RegExp
|
|
23
23
|
*/
|
|
24
24
|
export declare function escapeRegex(str: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Run a hook under its plugin's name and its own, for the lines it logs:
|
|
27
|
+
* what `launchHooks` does, for a caller walking the hooks itself.
|
|
28
|
+
*
|
|
29
|
+
* @param hook the registered function
|
|
30
|
+
* @param fn its run
|
|
31
|
+
* @returns what `fn` returns
|
|
32
|
+
*/
|
|
33
|
+
export declare const runAsHookOwner: <T>(hook: Function, fn: () => T) => T;
|
|
25
34
|
export declare const launchHooks: (hooks: Function[] | undefined, ...args: unknown[]) => Promise<void>;
|
|
26
35
|
/**
|
|
27
36
|
* Record the plugin a hook function belongs to.
|
|
@@ -91,6 +100,12 @@ export declare function escapeLdapFilter(value: string): string;
|
|
|
91
100
|
* ```
|
|
92
101
|
*/
|
|
93
102
|
export declare function substringSearchFilter(match: string, attribute: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* `pattern` with every `placeholder` replaced by `value`, taken literally:
|
|
105
|
+
* `String.replace` would expand `$&`, `$'`, `` $` `` and `$$` in a value
|
|
106
|
+
* that a request chose.
|
|
107
|
+
*/
|
|
108
|
+
export declare function fillPattern(pattern: string, placeholder: string, value: string): string;
|
|
94
109
|
/**
|
|
95
110
|
* Escape special characters in LDAP DN attribute values according to RFC 4514
|
|
96
111
|
* Prevents LDAP injection attacks by escaping characters that have special meaning in DNs
|
|
@@ -5,4 +5,8 @@ declare module 'winston' {
|
|
|
5
5
|
notice: winston.LeveledLogMethod;
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* @throws when `--logger` names no known logger, or `--logger file` has no
|
|
10
|
+
* file: never a file nobody asked for
|
|
11
|
+
*/
|
|
8
12
|
export declare const buildLogger: (config: Config) => winston.Logger;
|
|
@@ -14,6 +14,7 @@ import type { Hooks } from '../../hooks';
|
|
|
14
14
|
import type ldapActions from '../../lib/ldapActions';
|
|
15
15
|
import type { AttributesList, AttributeValue, LdapList, ModifyRequest } from '../../lib/ldapActions';
|
|
16
16
|
import type { Schema } from '../../config/schema';
|
|
17
|
+
import { EntityValidator } from '../../lib/entitySchema/validator';
|
|
17
18
|
export interface postAdd {
|
|
18
19
|
cn?: string;
|
|
19
20
|
[key: string]: AttributeValue | undefined;
|
|
@@ -308,9 +309,23 @@ export default class LdapGroups extends DmPlugin {
|
|
|
308
309
|
* @returns nothing, throw if error
|
|
309
310
|
*/
|
|
310
311
|
validateMembers(dn: string, members: string[], req?: Request): Promise<void>;
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
312
|
+
/**
|
|
313
|
+
* Check a new group's entry, after the `ldapgroupvalidatenew` hooks, and
|
|
314
|
+
* convert it in place.
|
|
315
|
+
*
|
|
316
|
+
* @returns the entry the hooks handed on; `addGroup` writes its
|
|
317
|
+
* attributes over the entry it judged, the members aside
|
|
318
|
+
*/
|
|
319
|
+
validateNewGroup(dn: string, entry: AttributesList): Promise<AttributesList>;
|
|
320
|
+
/**
|
|
321
|
+
* Check a change, after the `ldapgroupvalidatechanges` hooks, and convert
|
|
322
|
+
* it in place.
|
|
323
|
+
*
|
|
324
|
+
* @returns the change the hooks handed on
|
|
325
|
+
*/
|
|
326
|
+
validateChanges(dn: string, changes: ModifyRequest): Promise<ModifyRequest>;
|
|
327
|
+
/** The schema's rules; the pointer lookups run as the server */
|
|
328
|
+
validator(): EntityValidator;
|
|
314
329
|
/**
|
|
315
330
|
* Provide configuration for config API
|
|
316
331
|
*/
|
|
@@ -2,7 +2,7 @@ import type { Express, Request, Response } from 'express';
|
|
|
2
2
|
import DmPlugin, { type Role } from '../../abstract/plugin';
|
|
3
3
|
import { DM } from '../../bin';
|
|
4
4
|
import { Hooks } from '../../hooks';
|
|
5
|
-
import { AttributesList,
|
|
5
|
+
import { AttributesList, ModifyRequest } from '../../lib/ldapActions';
|
|
6
6
|
import type { Schema } from '../../config/schema';
|
|
7
7
|
/**
|
|
8
8
|
* Shared OpenAPI schemas surfaced by this plugin. Picked up by
|
|
@@ -231,10 +231,8 @@ export default class LdapOrganizations extends DmPlugin {
|
|
|
231
231
|
private linkedEntities;
|
|
232
232
|
addOrganization(dn: string, entry: AttributesList, req?: Request): Promise<boolean>;
|
|
233
233
|
modifyOrganization(dn: string, changes: ModifyRequest, req?: Request): Promise<boolean>;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
validateChanges(dn: string, changes: ModifyRequest): Promise<boolean>;
|
|
237
|
-
_validateOneChange(field: string, value: AttributeValue | null): Promise<boolean>;
|
|
234
|
+
/** The schema's rules; the pointer lookups run as the server */
|
|
235
|
+
private validator;
|
|
238
236
|
/**
|
|
239
237
|
* Tell whether a DN names an existing entry.
|
|
240
238
|
*
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Express } from 'express';
|
|
2
|
+
import DmPlugin from '../abstract/plugin';
|
|
3
|
+
import type { DM } from '../bin';
|
|
4
|
+
export default class Metrics extends DmPlugin {
|
|
5
|
+
name: string;
|
|
6
|
+
/** The gauges read at scrape time and the process metrics */
|
|
7
|
+
private readonly own;
|
|
8
|
+
private readonly exposed;
|
|
9
|
+
private listener?;
|
|
10
|
+
constructor(server: DM);
|
|
11
|
+
/** Open the listener; the server does not start on a port it cannot take */
|
|
12
|
+
api(_app: Express): Promise<void>;
|
|
13
|
+
/** @returns the port the metrics are served on, once `api` opened it */
|
|
14
|
+
port(): number | undefined;
|
|
15
|
+
private answer;
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
}
|