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
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import DmPlugin from '../abstract/plugin';
|
|
21
21
|
import type { DM } from '../bin';
|
|
22
|
+
import type { HealthStatus } from '../lib/health';
|
|
22
23
|
/** JSON-serialisable message payload. */
|
|
23
24
|
export type RabbitMessage = Record<string, unknown>;
|
|
24
25
|
/** Async handler invoked for each consumed message. */
|
|
@@ -46,6 +47,18 @@ interface RabbitClient {
|
|
|
46
47
|
subscribe(exchange: string, routingKey: string, queue: string, handler: RabbitMessageHandler, options?: SubscribeOptions): Promise<void>;
|
|
47
48
|
unsubscribe(queue: string): Promise<void>;
|
|
48
49
|
close(clearSubscriptions?: boolean): Promise<void>;
|
|
50
|
+
isConnected(): boolean;
|
|
51
|
+
}
|
|
52
|
+
/** The logging contract of `@linagora/rabbitmq-client`: message first, then a context */
|
|
53
|
+
interface ClientLogger {
|
|
54
|
+
debug(message: string, context?: unknown): void;
|
|
55
|
+
info(message: string, context?: unknown): void;
|
|
56
|
+
warn(message: string, context?: unknown): void;
|
|
57
|
+
error(message: string, context?: unknown): void;
|
|
58
|
+
}
|
|
59
|
+
interface RabbitClientOptions {
|
|
60
|
+
url: string;
|
|
61
|
+
logger: ClientLogger;
|
|
49
62
|
}
|
|
50
63
|
export default class RabbitMq extends DmPlugin {
|
|
51
64
|
name: string;
|
|
@@ -59,6 +72,13 @@ export default class RabbitMq extends DmPlugin {
|
|
|
59
72
|
* the broker is reachable — the connection is still lazy.
|
|
60
73
|
*/
|
|
61
74
|
isAvailable(): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Degraded when a broker is configured and no connection to it stands:
|
|
77
|
+
* the state as it is, nothing opened for the probe, which would run the
|
|
78
|
+
* client's connection attempts on every probe while the broker is away.
|
|
79
|
+
* A plugin without a broker has nothing to report.
|
|
80
|
+
*/
|
|
81
|
+
health(): HealthStatus;
|
|
62
82
|
/**
|
|
63
83
|
* Publish a JSON message to a topic exchange. No-op (returns silently) when
|
|
64
84
|
* no broker is configured or the client is unavailable.
|
|
@@ -89,6 +109,22 @@ export default class RabbitMq extends DmPlugin {
|
|
|
89
109
|
* retry policy in `getClient()` can be tested without a live broker.
|
|
90
110
|
*/
|
|
91
111
|
protected connect(): Promise<RabbitClient>;
|
|
92
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Build and initialise a client of the given class: a test hands in a stub
|
|
114
|
+
* to see what the client is built with.
|
|
115
|
+
*/
|
|
116
|
+
protected open(Client: new (options: RabbitClientOptions) => unknown): Promise<RabbitClient>;
|
|
117
|
+
/**
|
|
118
|
+
* The client's lines go through the plugin's logger, under its name, so
|
|
119
|
+
* they follow `--logger` and `--log-level`; without one the client writes
|
|
120
|
+
* to the console. An `Error` in the context is written as text: the JSON
|
|
121
|
+
* format keeps only its enumerable fields, which for a channel error is
|
|
122
|
+
* the code without the broker's message.
|
|
123
|
+
*/
|
|
124
|
+
private clientLogger;
|
|
125
|
+
/** Set by `close`: a publish of what outlives the drain opens nothing */
|
|
126
|
+
private closed;
|
|
127
|
+
/** Close the broker connection; nothing opens one after */
|
|
128
|
+
close(): Promise<void>;
|
|
93
129
|
}
|
|
94
130
|
export {};
|
|
@@ -14,6 +14,8 @@ export declare class ScimError extends HttpError {
|
|
|
14
14
|
scimType?: ScimErrorType;
|
|
15
15
|
constructor(status: number, detail: string, scimType?: ScimErrorType);
|
|
16
16
|
}
|
|
17
|
+
/** The kind of a client value, for an error that quotes none of it */
|
|
18
|
+
export declare const kindOf: (v: unknown) => string;
|
|
17
19
|
export declare const scimInvalidFilter: (detail?: string) => ScimError;
|
|
18
20
|
export declare const scimInvalidPath: (detail?: string) => ScimError;
|
|
19
21
|
export declare const scimInvalidValue: (detail?: string) => ScimError;
|
|
@@ -120,7 +120,8 @@ export interface BulkRequest {
|
|
|
120
120
|
Operations: BulkOperationRequest[];
|
|
121
121
|
}
|
|
122
122
|
export interface BulkOperationResponse {
|
|
123
|
-
|
|
123
|
+
/** Absent when the operation was not even an object */
|
|
124
|
+
method?: 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
124
125
|
bulkId?: string;
|
|
125
126
|
version?: string;
|
|
126
127
|
location?: string;
|
|
@@ -65,6 +65,18 @@ export default class Static extends DmPlugin {
|
|
|
65
65
|
* @returns its URL, or undefined when this plugin does not serve it
|
|
66
66
|
*/
|
|
67
67
|
urlOf(file: string): string | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* The URL its schema routes serve a schema file at, merged with what it
|
|
70
|
+
* extends and its placeholders replaced, for another plugin to announce
|
|
71
|
+
* it: one of the configuration's schema files it serves, or a file its
|
|
72
|
+
* `schemas/` directory reaches, through a link included. One that
|
|
73
|
+
* express.static would send as it stands, outside `schemas/` or not named
|
|
74
|
+
* `.json`, has none.
|
|
75
|
+
*
|
|
76
|
+
* @param file the schema file, as the other plugin reads it
|
|
77
|
+
* @returns its URL, or undefined when its schema routes do not serve it
|
|
78
|
+
*/
|
|
79
|
+
schemaUrlOf(file: string): string | undefined;
|
|
68
80
|
/** The path it serves under, which an earlier plugin may have taken */
|
|
69
81
|
servedPath(): string;
|
|
70
82
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type winston from 'winston';
|
|
2
2
|
import Store, { type StoredRecord } from '../../lib/storage/store';
|
|
3
|
+
import type { HealthStatus } from '../../lib/health';
|
|
3
4
|
export default class FileStore extends Store {
|
|
4
5
|
name: string;
|
|
5
6
|
private dir;
|
|
@@ -8,6 +9,9 @@ export default class FileStore extends Store {
|
|
|
8
9
|
constructor(logger: winston.Logger, dir: string, abandonedAfterSeconds: number);
|
|
9
10
|
/** The directory is created on demand, readable by this user alone. */
|
|
10
11
|
ready(): Promise<void>;
|
|
12
|
+
open(): Promise<void>;
|
|
13
|
+
/** A directory this user may write a record in: entered and written */
|
|
14
|
+
health(): Promise<HealthStatus>;
|
|
11
15
|
private path;
|
|
12
16
|
/** `<deadline> <key>\n<value>` — the deadline first, so a short read still has it. */
|
|
13
17
|
private static encode;
|
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module plugins/storage/postgres
|
|
3
|
-
*
|
|
4
|
-
* A keyed store kept in one PostgreSQL table.
|
|
5
|
-
*
|
|
6
|
-
* `pg` is an optional dependency, loaded when this backend is chosen: a
|
|
7
|
-
* deployment that keeps its records elsewhere never needs it installed.
|
|
8
|
-
*
|
|
9
|
-
* The key is kept verbatim, split into the consumer's namespace and its own
|
|
10
|
-
* key: a `text` column has no reason to hash it the way a DN or a file name
|
|
11
|
-
* must, so a `SELECT` says what a record is about.
|
|
12
|
-
*
|
|
13
|
-
* @group Plugins
|
|
14
|
-
*/
|
|
15
1
|
import type winston from 'winston';
|
|
16
2
|
import Store, { type StoredRecord } from '../../lib/storage/store';
|
|
3
|
+
import { type HealthStatus } from '../../lib/health';
|
|
17
4
|
type Pg = typeof import('pg');
|
|
18
5
|
export default class PostgresStore extends Store {
|
|
19
6
|
name: string;
|
|
@@ -26,6 +13,12 @@ export default class PostgresStore extends Store {
|
|
|
26
13
|
/** Split out so a test can stand in for a missing package. */
|
|
27
14
|
protected driver(): Promise<Pg>;
|
|
28
15
|
open(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* On a client of its own, closed after, with the budget as its timeouts:
|
|
18
|
+
* a query the pool never got an answer to would hold one of its
|
|
19
|
+
* connections for as long as the socket lives
|
|
20
|
+
*/
|
|
21
|
+
health(): Promise<HealthStatus>;
|
|
29
22
|
close(): Promise<void>;
|
|
30
23
|
/**
|
|
31
24
|
* The pool, once the table is known to be there. A failure is not kept:
|
|
@@ -1,20 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module plugins/storage/valkey
|
|
3
|
-
*
|
|
4
|
-
* A keyed store kept in Valkey, or Redis.
|
|
5
|
-
*
|
|
6
|
-
* `iovalkey` is an optional dependency, loaded when this backend is chosen: a
|
|
7
|
-
* deployment that keeps its records elsewhere never needs it installed.
|
|
8
|
-
*
|
|
9
|
-
* The only backend that expires its own records, so it has nothing to sweep.
|
|
10
|
-
* The value still carries its deadline, in the shape the LDAP store uses:
|
|
11
|
-
* reads judge expiry by it, as with every other backend, and the TTL only
|
|
12
|
-
* reclaims the memory.
|
|
13
|
-
*
|
|
14
|
-
* @group Plugins
|
|
15
|
-
*/
|
|
16
1
|
import type winston from 'winston';
|
|
17
2
|
import Store, { type StoredRecord } from '../../lib/storage/store';
|
|
3
|
+
import type { HealthStatus } from '../../lib/health';
|
|
18
4
|
type IoValkey = typeof import('iovalkey');
|
|
19
5
|
export default class ValkeyStore extends Store {
|
|
20
6
|
name: string;
|
|
@@ -27,6 +13,7 @@ export default class ValkeyStore extends Store {
|
|
|
27
13
|
/** Split out so a test can stand in for a missing package. */
|
|
28
14
|
protected driver(): Promise<IoValkey>;
|
|
29
15
|
open(): Promise<void>;
|
|
16
|
+
health(): Promise<HealthStatus>;
|
|
30
17
|
close(): Promise<void>;
|
|
31
18
|
/**
|
|
32
19
|
* The client, once connected. A failed first connection is not kept: the
|
|
@@ -30,10 +30,18 @@
|
|
|
30
30
|
import DmPlugin from '../abstract/plugin';
|
|
31
31
|
import type { DM } from '../bin';
|
|
32
32
|
import type Store from '../lib/storage/store';
|
|
33
|
+
import type { HealthStatus } from '../lib/health';
|
|
33
34
|
export default class Storage extends DmPlugin {
|
|
34
35
|
name: string;
|
|
35
36
|
store: Store;
|
|
36
37
|
constructor(server: DM);
|
|
38
|
+
close(): Promise<void>;
|
|
39
|
+
/** Set while the backend fails the probe, so the reason is logged once */
|
|
40
|
+
private degraded;
|
|
41
|
+
/** Counts the probes: only the latest says what the log lines say */
|
|
42
|
+
private probes;
|
|
43
|
+
/** The backend's own answer; a backend that cannot answer is degraded */
|
|
44
|
+
health(): Promise<HealthStatus>;
|
|
37
45
|
/**
|
|
38
46
|
* Release the single-instance hold.
|
|
39
47
|
*
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Twake Drive Plugin
|
|
3
|
+
*
|
|
4
|
+
* Synchronizes LDAP user changes with Twake Drive via Admin API.
|
|
5
|
+
* Propagates email address, display name, and disk quota changes.
|
|
6
|
+
*
|
|
7
|
+
* @see https://docs.cozy.io/en/cozy-stack/admin/
|
|
8
|
+
*/
|
|
1
9
|
import TwakePlugin from '../../abstract/twakePlugin';
|
|
2
10
|
import { type Role } from '../../abstract/plugin';
|
|
3
11
|
import type { DM } from '../../bin';
|
|
@@ -11,6 +11,7 @@ import { type LifecycleAttributes } from './lifecycleAttributes';
|
|
|
11
11
|
*/
|
|
12
12
|
export declare const read: (entry: AttributesList, attribute: string) => string | undefined;
|
|
13
13
|
export declare const ORG = "{org}";
|
|
14
|
+
export declare function withOrg(pattern: string, org: string): string;
|
|
14
15
|
/** A refusal the routes answer as `{ error, code }`. */
|
|
15
16
|
export declare class RouteError extends Error {
|
|
16
17
|
readonly status: number;
|
|
@@ -36,8 +37,6 @@ export declare function values(value: AttributeValue | undefined): string[];
|
|
|
36
37
|
/** A DN spelled one way, to compare two spellings of it. */
|
|
37
38
|
export declare function dnKey(dn: string): string;
|
|
38
39
|
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
40
|
/**
|
|
42
41
|
* The entries one level under a DN pattern holding `{org}`, the organization
|
|
43
42
|
* captured as one RDN value: `(?:\\.|[^,])+` keeps an escaped comma inside it.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Express } from 'express';
|
|
2
2
|
import TwakePlugin from '../../abstract/twakePlugin';
|
|
3
|
+
import type { HealthStatus } from '../../lib/health';
|
|
3
4
|
import { type Role } from '../../abstract/plugin';
|
|
4
5
|
import type { DM } from '../../bin';
|
|
5
6
|
import type { AttributeValue } from '../../lib/ldapActions';
|
|
@@ -22,6 +23,7 @@ export default class James extends TwakePlugin {
|
|
|
22
23
|
onLdapChange: string;
|
|
23
24
|
ldapGroups: string;
|
|
24
25
|
};
|
|
26
|
+
health(): Promise<HealthStatus>;
|
|
25
27
|
private quotaAttr;
|
|
26
28
|
private aliasAttr;
|
|
27
29
|
private mailboxTypeAttr;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module plugins/twake/migrationProxy
|
|
3
|
+
*
|
|
4
|
+
* Keeps the migrated-users list of the Twake Mail migration proxy in step
|
|
5
|
+
* with the accounts of the directory: an account created during the
|
|
6
|
+
* migration has no mailbox on the old system, so the proxy has to route it
|
|
7
|
+
* to the new one from its first login, and the addresses of a migrated
|
|
8
|
+
* account have to follow it.
|
|
9
|
+
*
|
|
10
|
+
* See `docs/usage/plugins/integrations/mail-migration.md`.
|
|
11
|
+
*/
|
|
12
|
+
import type { Express } from 'express';
|
|
13
|
+
import TwakePlugin from '../../abstract/twakePlugin';
|
|
14
|
+
import { type Role } from '../../abstract/plugin';
|
|
15
|
+
import type { DM } from '../../bin';
|
|
16
|
+
import type { Hooks } from '../../hooks';
|
|
17
|
+
import type { HealthStatus } from '../../lib/health';
|
|
18
|
+
/**
|
|
19
|
+
* @openapi
|
|
20
|
+
* components:
|
|
21
|
+
* schemas:
|
|
22
|
+
* MailMigrationStatus:
|
|
23
|
+
* type: object
|
|
24
|
+
* required: [migrated]
|
|
25
|
+
* properties:
|
|
26
|
+
* migrated:
|
|
27
|
+
* type: boolean
|
|
28
|
+
* description: Whether the proxy routes the user to the new backend
|
|
29
|
+
*/
|
|
30
|
+
export default class MigrationProxy extends TwakePlugin {
|
|
31
|
+
name: string;
|
|
32
|
+
roles: Role[];
|
|
33
|
+
dependencies: {
|
|
34
|
+
onLdapChange: string;
|
|
35
|
+
};
|
|
36
|
+
private readonly branches;
|
|
37
|
+
private readonly aliasAttr;
|
|
38
|
+
/** The work on one entry waits for the work before it */
|
|
39
|
+
private readonly queues;
|
|
40
|
+
constructor(server: DM);
|
|
41
|
+
health(): Promise<HealthStatus>;
|
|
42
|
+
api(app: Express): void;
|
|
43
|
+
hooks: Hooks;
|
|
44
|
+
/**
|
|
45
|
+
* One entry at a time: two writes close together would otherwise race to
|
|
46
|
+
* the proxy, the second reading a list the first has not written yet
|
|
47
|
+
*/
|
|
48
|
+
private inOrder;
|
|
49
|
+
/**
|
|
50
|
+
* The address the entry logs in with, and the ones that reach it, each
|
|
51
|
+
* once: an alias repeating the mail would otherwise be put and taken off
|
|
52
|
+
* in the same breath
|
|
53
|
+
*/
|
|
54
|
+
private addresses;
|
|
55
|
+
/**
|
|
56
|
+
* An entry gaining its address is put on the list with all of them, one
|
|
57
|
+
* losing it is taken off; one keeping it has its addresses follow it when
|
|
58
|
+
* the proxy says it is migrated, and is left to the migration otherwise.
|
|
59
|
+
* The proxy folds usernames to lower case, so a change of case is none.
|
|
60
|
+
*/
|
|
61
|
+
private follow;
|
|
62
|
+
private userUrl;
|
|
63
|
+
private call;
|
|
64
|
+
/**
|
|
65
|
+
* `HEAD /migratedUsers/{user}`: 204 when migrated, 404 otherwise. With
|
|
66
|
+
* `retry`, an answer of {@link PROXY_UNAVAILABLE} is asked again as a
|
|
67
|
+
* write would be; throws once the proxy has not said
|
|
68
|
+
*/
|
|
69
|
+
isMigrated(address: string, retry?: boolean): Promise<boolean>;
|
|
70
|
+
private getMigrationStatus;
|
|
71
|
+
}
|
|
@@ -93,11 +93,13 @@ Complete technical reference for the LDAP-Rest REST API including detailed speci
|
|
|
93
93
|
| 500 | Internal Server Error | Server-side failure | LDAP connection issues, database errors, plugin failures |
|
|
94
94
|
| 503 | Service Unavailable | Server temporarily unavailable | LDAP server down, maintenance mode |
|
|
95
95
|
|
|
96
|
-
**Example 500 Response
|
|
96
|
+
**Example 500 Response** (the failure is in the server's logs; `message`
|
|
97
|
+
names it only when the server runs with `--debug`):
|
|
97
98
|
|
|
98
99
|
```json
|
|
99
100
|
{
|
|
100
|
-
"error": "
|
|
101
|
+
"error": "Internal Server Error",
|
|
102
|
+
"message": "An error occurred"
|
|
101
103
|
}
|
|
102
104
|
```
|
|
103
105
|
|
|
@@ -805,13 +807,7 @@ Failed to fetch
|
|
|
805
807
|
|
|
806
808
|
#### Problem: LDAP Connection Failed
|
|
807
809
|
|
|
808
|
-
**Error:**
|
|
809
|
-
|
|
810
|
-
```json
|
|
811
|
-
{
|
|
812
|
-
"error": "LDAP bind error"
|
|
813
|
-
}
|
|
814
|
-
```
|
|
810
|
+
**Error:** a 500 answer, with `LDAP bind error` in the server's logs.
|
|
815
811
|
|
|
816
812
|
**Checklist:**
|
|
817
813
|
|
|
@@ -1335,19 +1335,13 @@ no `limit` was given —
|
|
|
1335
1335
|
|
|
1336
1336
|
#### 500 Internal Server Error
|
|
1337
1337
|
|
|
1338
|
-
|
|
1338
|
+
The failure is in the server's logs, not in the response; the message names
|
|
1339
|
+
it only when the server runs with `--debug`:
|
|
1339
1340
|
|
|
1340
1341
|
```json
|
|
1341
1342
|
{
|
|
1342
|
-
"error": "
|
|
1343
|
-
|
|
1344
|
-
```
|
|
1345
|
-
|
|
1346
|
-
**General Server Error:**
|
|
1347
|
-
|
|
1348
|
-
```json
|
|
1349
|
-
{
|
|
1350
|
-
"error": "Internal server error: <details>"
|
|
1343
|
+
"error": "Internal Server Error",
|
|
1344
|
+
"message": "An error occurred"
|
|
1351
1345
|
}
|
|
1352
1346
|
```
|
|
1353
1347
|
|
|
@@ -169,7 +169,10 @@ This will be replaced with the actual LDAP base at runtime (e.g., `ou=users,dc=e
|
|
|
169
169
|
|
|
170
170
|
## Attribute Types
|
|
171
171
|
|
|
172
|
-
LDAP-Rest supports three main attribute types: `string`, `array`, and `pointer
|
|
172
|
+
LDAP-Rest supports three main attribute types: `string`, `array`, and `pointer`,
|
|
173
|
+
plus `number`, `integer`, `boolean` and `date` for single values. What a
|
|
174
|
+
client sends has to have the shape of the type, see
|
|
175
|
+
[what a value must look like](#what-a-value-must-look-like).
|
|
173
176
|
|
|
174
177
|
### String Type
|
|
175
178
|
|
|
@@ -337,7 +340,7 @@ Complete reference for all attribute properties.
|
|
|
337
340
|
|
|
338
341
|
| Property | Type | Applies To | Description | Example |
|
|
339
342
|
| ---------- | --------------- | ------------------- | ------------------------------------------------------------------------------------- | ----------------------------------- |
|
|
340
|
-
| `type` | string | All |
|
|
343
|
+
| `type` | string | All | `"string"`, `"array"`, `"pointer"`, `"number"`, `"integer"`, `"boolean"`, `"date"` | `"string"` |
|
|
341
344
|
| `required` | boolean | All | Whether the attribute must be present | `true` |
|
|
342
345
|
| `fixed` | boolean | All | Whether the attribute value cannot be changed after creation | `true` |
|
|
343
346
|
| `role` | string/string[] | String | Semantic role, or roles, for special handling (see [Semantic Roles](#semantic-roles)) | `"identifier"` |
|
|
@@ -364,6 +367,32 @@ Defines the data type of the attribute. Supported values:
|
|
|
364
367
|
- `"pointer"`: Reference to another LDAP entry (DN)
|
|
365
368
|
- `"number"`: Numeric value (integer or float)
|
|
366
369
|
- `"integer"`: Integer value only
|
|
370
|
+
- `"boolean"`: `true` or `false`, stored as `TRUE` / `FALSE`
|
|
371
|
+
- `"date"`: LDAP generalized time, as text
|
|
372
|
+
|
|
373
|
+
#### What a value must look like
|
|
374
|
+
|
|
375
|
+
A single-valued attribute takes one value, an `array` a list. A list of one
|
|
376
|
+
value stands for that value and one value for a list of one, so what a read
|
|
377
|
+
answers, one value where the attribute holds one, can be sent back as is;
|
|
378
|
+
two values or more for a single-valued attribute are refused. Each element
|
|
379
|
+
of an `array` follows `items.type`, text without `items`. A `number` or
|
|
380
|
+
`integer` is a JSON number or its text (`42`, `"42"`), a `boolean` a JSON
|
|
381
|
+
boolean or the directory's `TRUE` / `FALSE`; anything else for them, and
|
|
382
|
+
anything but text for a `string`, `date` or `pointer`, is refused; an
|
|
383
|
+
attribute with `normalize` takes any text, read after validation. In a
|
|
384
|
+
`replace`, `null` or `[]` clears an attribute; in an `add` or on a creation
|
|
385
|
+
they are refused as no value. A `delete` naming the attribute, or naming it
|
|
386
|
+
with no value, removes it, unless it is `required`; naming values removes
|
|
387
|
+
those, every value it holds included, as the server does not read what
|
|
388
|
+
remains, and `''` names no value. An empty string sent as a value is
|
|
389
|
+
checked against `test`, refused for a pointer, and missing for a `required`
|
|
390
|
+
attribute. Every refusal answers 400 with the attribute's name.
|
|
391
|
+
|
|
392
|
+
Every entity route applies these rules; `test/lib/entitySchema.contract.test.ts`
|
|
393
|
+
asserts them on a flat entity, a group and an organization alike. In the
|
|
394
|
+
server they are enforced by `EntityValidator` (`src/lib/entitySchema/validator.ts`)
|
|
395
|
+
alone: ESLint refuses the import of the schema helpers anywhere else.
|
|
367
396
|
|
|
368
397
|
#### `required` (Optional, default: `false`)
|
|
369
398
|
|
|
@@ -674,13 +703,8 @@ Common validation patterns:
|
|
|
674
703
|
|
|
675
704
|
### Type Validation
|
|
676
705
|
|
|
677
|
-
Attribute values must
|
|
678
|
-
|
|
679
|
-
- **String**: Single text value
|
|
680
|
-
- **Array**: Multiple values (may be empty unless required)
|
|
681
|
-
- **Number**: Numeric value (integer or float)
|
|
682
|
-
- **Integer**: Integer value only
|
|
683
|
-
- **Pointer**: Valid DN string
|
|
706
|
+
Attribute values must have the shape of their declared type, see
|
|
707
|
+
[what a value must look like](#what-a-value-must-look-like).
|
|
684
708
|
|
|
685
709
|
### Fixed Attributes
|
|
686
710
|
|
|
@@ -780,6 +804,7 @@ twake/
|
|
|
780
804
|
```bash
|
|
781
805
|
npx ldap-rest \
|
|
782
806
|
--ldap-base 'dc=example,dc=com' \
|
|
807
|
+
--ldap-dn 'cn=admin,dc=example,dc=com' --ldap-pwd secret \
|
|
783
808
|
--plugin core/ldap/flatGeneric \
|
|
784
809
|
--ldap-flat-schema ./static/schemas/twake/users.json \
|
|
785
810
|
--plugin core/ldap/groups \
|
|
@@ -842,6 +867,7 @@ ad/
|
|
|
842
867
|
```bash
|
|
843
868
|
npx ldap-rest \
|
|
844
869
|
--ldap-base 'dc=example,dc=com' \
|
|
870
|
+
--ldap-dn 'cn=admin,dc=example,dc=com' --ldap-pwd secret \
|
|
845
871
|
--plugin core/ldap/flatGeneric \
|
|
846
872
|
--ldap-flat-schema ./static/schemas/ad/users.json \
|
|
847
873
|
--plugin core/ldap/groups \
|
|
@@ -910,6 +936,7 @@ standard/
|
|
|
910
936
|
```bash
|
|
911
937
|
npx ldap-rest \
|
|
912
938
|
--ldap-base 'dc=example,dc=com' \
|
|
939
|
+
--ldap-dn 'cn=admin,dc=example,dc=com' --ldap-pwd secret \
|
|
913
940
|
--plugin core/ldap/flatGeneric \
|
|
914
941
|
--ldap-flat-schema ./static/schemas/standard/users.json \
|
|
915
942
|
--plugin core/ldap/groups \
|
|
@@ -1055,6 +1082,7 @@ Save your schema and reference it when starting LDAP-Rest:
|
|
|
1055
1082
|
```bash
|
|
1056
1083
|
npx ldap-rest \
|
|
1057
1084
|
--ldap-base 'dc=example,dc=com' \
|
|
1085
|
+
--ldap-dn 'cn=admin,dc=example,dc=com' --ldap-pwd secret \
|
|
1058
1086
|
--plugin core/ldap/flatGeneric \
|
|
1059
1087
|
--ldap-flat-schema ./path/to/custom-users.json
|
|
1060
1088
|
```
|
|
@@ -1131,18 +1159,29 @@ it with a 500.
|
|
|
1131
1159
|
### Served Schemas
|
|
1132
1160
|
|
|
1133
1161
|
`core/static` serves the merged schema. The configuration API gives each
|
|
1134
|
-
entity its merged schema inline (`schema`) and,
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
`/etc/ldap-rest/schemas/
|
|
1140
|
-
`/static/schemas/
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1162
|
+
entity its merged schema inline (`schema`) and, where a static plugin's
|
|
1163
|
+
schema routes serve that very file, its `schemaUrl`, under the plugin's own
|
|
1164
|
+
`--static-name`:
|
|
1165
|
+
|
|
1166
|
+
- a schema file of the configuration gets the file path from its first
|
|
1167
|
+
`schemas` directory, `/etc/ldap-rest/schemas/users.json` getting
|
|
1168
|
+
`/static/schemas/users.json`. `core/static` serves it at that URL even
|
|
1169
|
+
when it is outside `--static-path`, and before a shipped schema of the
|
|
1170
|
+
same URL: `/etc/ldap-rest/schemas/twake/users.json` is what
|
|
1171
|
+
`/static/schemas/twake/users.json` returns. If two configured files get
|
|
1172
|
+
the same URL, the first one is served there and a warning names the
|
|
1173
|
+
other;
|
|
1174
|
+
- a file the `schemas/` directory of `--static-path` holds, that directory
|
|
1175
|
+
being a link or not, gets its path in it, if it is named `.json`.
|
|
1176
|
+
|
|
1177
|
+
An option left at its default does not count as configured: without
|
|
1178
|
+
`--group-schema`, a `--static-path` of your own still serves its
|
|
1179
|
+
`schemas/twake/groups.json`, and no `schemaUrl` is given unless that file is
|
|
1180
|
+
the package's, through a link. A static plugin loaded under another name, or with a
|
|
1181
|
+
`static_name` of its own, is asked as well, the one registered as `static`
|
|
1182
|
+
first. A file no static plugin serves merged has no `schemaUrl`, one
|
|
1183
|
+
express.static would send as it stands included (outside `schemas/`, or not
|
|
1184
|
+
named `.json`); its schema is still inline.
|
|
1146
1185
|
|
|
1147
1186
|
---
|
|
1148
1187
|
|
|
@@ -61,20 +61,22 @@ export default class MyPlugin extends DmPlugin {
|
|
|
61
61
|
- **this.config**: Complete configuration object
|
|
62
62
|
- **this.logger**: Winston logger instance
|
|
63
63
|
- **this.registeredHooks**: Access to global hook registry
|
|
64
|
+
- **this.fetch**: `fetch` timed into the
|
|
65
|
+
[outbound metrics](../usage/operations.md#metrics) under the plugin's
|
|
66
|
+
name; the lint refuses a bare `fetch` or `node-fetch` under `src`, and a
|
|
67
|
+
plugin with a `fetch` member of its own has to rename it
|
|
64
68
|
|
|
65
69
|
### Error Handling and Server Resilience
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
All errors are logged with full stack traces without crashing the server.
|
|
71
|
+
An error a route handler or a hook throws is logged with its stack; the
|
|
72
|
+
request is answered with a 5xx when the hook ran before the operation, and
|
|
73
|
+
the server keeps serving. An exception nothing catches, in a timer or an
|
|
74
|
+
event handler, is not: the binary shuts down and exits with 1, and a
|
|
75
|
+
promise rejection nothing handles is logged and counted, see
|
|
76
|
+
[shutdown](../usage/operations.md#shutdown); a host assembling the server
|
|
77
|
+
chooses with `exitOnCrash()`. A plugin holding a connection, a timer or a
|
|
78
|
+
queue implements `close()`, see
|
|
79
|
+
[for plugin authors](../usage/operations.md#for-plugin-authors).
|
|
78
80
|
|
|
79
81
|
#### Using asyncHandler
|
|
80
82
|
|
|
@@ -175,7 +177,7 @@ export default class WebhookNotifier extends DmPlugin {
|
|
|
175
177
|
// Private methods
|
|
176
178
|
private async sendWebhook(event: string, data: unknown): Promise<void> {
|
|
177
179
|
try {
|
|
178
|
-
await fetch(this.config.webhook_url as string, {
|
|
180
|
+
await this.fetch(this.config.webhook_url as string, {
|
|
179
181
|
method: 'POST',
|
|
180
182
|
headers: { 'Content-Type': 'application/json' },
|
|
181
183
|
body: JSON.stringify({ event, data }),
|
|
@@ -1153,7 +1155,7 @@ export default class NotificationPlugin extends DmPlugin {
|
|
|
1153
1155
|
const timeoutId = setTimeout(() => controller.abort(), this.webhookTimeout);
|
|
1154
1156
|
|
|
1155
1157
|
try {
|
|
1156
|
-
const response = await fetch(this.webhookUrl, {
|
|
1158
|
+
const response = await this.fetch(this.webhookUrl, {
|
|
1157
1159
|
method: 'POST',
|
|
1158
1160
|
headers: {
|
|
1159
1161
|
'Content-Type': 'application/json',
|
|
@@ -204,6 +204,14 @@ overrides builds a second instance, and it needs a name of its own.
|
|
|
204
204
|
- **Consumes Hooks**: (Hooks provided by `onLdapChange`)
|
|
205
205
|
- **Notes**: Synchronizes LDAP users/groups with Apache James mail server
|
|
206
206
|
|
|
207
|
+
#### `migrationProxy` (twake/migrationProxy)
|
|
208
|
+
|
|
209
|
+
- **Dependencies**:
|
|
210
|
+
- `onLdapChange: 'core/ldap/onChange'` ⚠️ **Required**
|
|
211
|
+
- **Provides Hooks**: None
|
|
212
|
+
- **Consumes Hooks**: `onLdapEntryChange` (provided by `onLdapChange`)
|
|
213
|
+
- **Notes**: Keeps the migrated-users list of the Twake Mail migration proxy, see [mail-migration](../usage/plugins/integrations/mail-migration.md)
|
|
214
|
+
|
|
207
215
|
#### `calendar` (twake/calendar)
|
|
208
216
|
|
|
209
217
|
- **Dependencies**:
|
|
@@ -9,7 +9,7 @@ those typed in [hooks.ts](../../src/hooks.ts), which is the reference.
|
|
|
9
9
|
|
|
10
10
|
## [LDAP](../../src/lib/ldapActions.ts) hooks
|
|
11
11
|
|
|
12
|
-
- **
|
|
12
|
+
- **ldapsearchopts**: called before any ldapsearch to modify search options
|
|
13
13
|
- **ldapsearchresult**: called after any ldapsearch to modify search result
|
|
14
14
|
- **ldapaddrequest**: called before any ldapadd
|
|
15
15
|
- **ldapadddone**: launched once an ldapadd has written the entry, not awaited
|
package/docs/usage/README.md
CHANGED
|
@@ -88,4 +88,5 @@ The system will:
|
|
|
88
88
|
- **[Plugins](plugins/README.md)** - Choose and configure plugins
|
|
89
89
|
- **[Upgrading](upgrading.md)** - What to check before deploying a new version
|
|
90
90
|
- **[Directory audit](directory-audit.md)** - What a schema would refuse in an existing directory
|
|
91
|
+
- **[Operations](operations.md)** - Health and readiness probes
|
|
91
92
|
- **[Troubleshooting](troubleshooting.md)** - Problem resolution
|