ldap-rest 0.2.2 → 0.3.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.
Files changed (53) hide show
  1. package/README.md +1 -1
  2. package/dist/abstract/ldapFlat.js +1 -1
  3. package/dist/bin/index.js +1 -1
  4. package/dist/config/args.js +1 -1
  5. package/dist/lib/expressFormatedResponses.js +1 -1
  6. package/dist/plugins/auth/authzDynamic.js +1 -0
  7. package/dist/plugins/auth/authzDynamicHash.js +1 -0
  8. package/dist/plugins/ldap/organization.js +1 -1
  9. package/dist/plugins/ldap/organizations.js +1 -0
  10. package/dist/plugins/scim/baseResolver.js +1 -0
  11. package/dist/plugins/scim/bulk.js +1 -0
  12. package/dist/plugins/scim/discovery.js +1 -0
  13. package/dist/plugins/scim/errors.js +1 -0
  14. package/dist/plugins/scim/filter.js +1 -0
  15. package/dist/plugins/scim/groups.js +1 -0
  16. package/dist/plugins/scim/mapping.js +1 -0
  17. package/dist/plugins/scim/patch.js +1 -0
  18. package/dist/plugins/scim/scim.js +1 -0
  19. package/dist/plugins/scim/types.js +1 -0
  20. package/dist/plugins/scim/users.js +1 -0
  21. package/dist/src/abstract/ldapFlat.d.ts +127 -0
  22. package/dist/src/config/args.d.ts +24 -0
  23. package/dist/src/plugins/auth/authzDynamic.d.ts +117 -0
  24. package/dist/src/plugins/auth/authzDynamicHash.d.ts +27 -0
  25. package/dist/src/plugins/configApi.d.ts +51 -0
  26. package/dist/src/plugins/demo/helloworld.d.ts +21 -0
  27. package/dist/src/plugins/ldap/bulkImport.d.ts +65 -0
  28. package/dist/src/plugins/ldap/groups.d.ts +68 -0
  29. package/dist/src/plugins/ldap/organization.d.ts +8 -65
  30. package/dist/src/plugins/ldap/organizations.d.ts +155 -0
  31. package/dist/src/plugins/ldap/passwordPolicy.d.ts +178 -0
  32. package/dist/src/plugins/scim/baseResolver.d.ts +23 -0
  33. package/dist/src/plugins/scim/bulk.d.ts +43 -0
  34. package/dist/src/plugins/scim/discovery.d.ts +27 -0
  35. package/dist/src/plugins/scim/errors.d.ts +37 -0
  36. package/dist/src/plugins/scim/filter.d.ts +13 -0
  37. package/dist/src/plugins/scim/groups.d.ts +61 -0
  38. package/dist/src/plugins/scim/mapping.d.ts +52 -0
  39. package/dist/src/plugins/scim/patch.d.ts +39 -0
  40. package/dist/src/plugins/scim/scim.d.ts +412 -0
  41. package/dist/src/plugins/scim/types.d.ts +222 -0
  42. package/dist/src/plugins/scim/users.d.ts +58 -0
  43. package/dist/src/plugins/static.d.ts +33 -0
  44. package/dist/src/plugins/twake/appAccountsApi.d.ts +63 -0
  45. package/dist/src/plugins/twake/james.d.ts +47 -0
  46. package/docs/plugin-development/README.md +2 -1
  47. package/docs/plugin-development/openapi.md +144 -0
  48. package/docs/usage/plugins/auth/authz-dynamic.md +183 -0
  49. package/docs/usage/plugins/integrations/scim.md +567 -0
  50. package/package.json +72 -1
  51. package/static/schemas/scim/Group.json +31 -0
  52. package/static/schemas/scim/User.json +147 -0
  53. package/static/schemas/scim/default-mapping.json +48 -0
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @module plugins/auth/authzDynamic
3
+ * @author Xavier Guimard <xguimard@linagora.com>
4
+ *
5
+ * LDAP-backed authentication + per-branch authorization plugin.
6
+ *
7
+ * Tokens are stored as entries under a dedicated LDAP branch (one entry per
8
+ * bearer token). The plugin caches them and enforces read / write / delete
9
+ * permissions on subsequent LDAP operations — so each client is scoped to
10
+ * its own subtree without touching the process configuration.
11
+ *
12
+ * Entry shape (conventions, overridable via CLI):
13
+ * dn: cn=<token-name>,<authz-dynamic-base>
14
+ * cn: <token-name>
15
+ * userPassword: {SSHA}… ← secret, hashed
16
+ * description: {"tenant":"acme", ← JSON config
17
+ * "bases":[
18
+ * {"dn":"ou=users,ou=acme,dc=example,dc=com",
19
+ * "read":true,"write":true,"delete":true}
20
+ * ]}
21
+ *
22
+ * The entry is treated as "token for tenant `acme`, permitted to read/write/
23
+ * delete under `ou=users,ou=acme,…`". Multiple bases per token are allowed.
24
+ * Sub-branch matching is used (a permission on `ou=acme` covers
25
+ * `ou=users,ou=acme`).
26
+ */
27
+ import { AsyncLocalStorage } from 'async_hooks';
28
+ import type { Express, Response } from 'express';
29
+ import { type Role } from '../../abstract/plugin';
30
+ import type { DM } from '../../bin';
31
+ import AuthBase, { type DmRequest } from '../../lib/auth/base';
32
+ import type { Hooks } from '../../hooks';
33
+ export interface BranchAcl {
34
+ dn: string;
35
+ read?: boolean;
36
+ write?: boolean;
37
+ delete?: boolean;
38
+ }
39
+ export interface TokenConfig {
40
+ tenant?: string;
41
+ bases?: BranchAcl[];
42
+ }
43
+ export interface TokenEntry {
44
+ dn: string;
45
+ cn: string;
46
+ tenant: string;
47
+ hash: string;
48
+ bases: BranchAcl[];
49
+ }
50
+ /** Extends DmRequest with the resolved token entry for downstream hooks. */
51
+ export interface AuthzDynamicRequest extends DmRequest {
52
+ authzToken?: TokenEntry;
53
+ }
54
+ /**
55
+ * Per-request async context holding the authenticated token.
56
+ *
57
+ * Core plugins (ldapGroups, ldapFlat, etc.) do not always forward the Express
58
+ * `req` object down to `ldapActions.search()` / `.modify()`. Relying solely
59
+ * on `req` would therefore let un-tracked operations bypass authorization.
60
+ * Using AsyncLocalStorage gives us a request-scoped, ambient handle that the
61
+ * authz hooks can read regardless of whether the caller threaded `req`.
62
+ */
63
+ export declare const authzContext: AsyncLocalStorage<TokenEntry>;
64
+ export default class AuthzDynamic extends AuthBase {
65
+ name: string;
66
+ roles: Role[];
67
+ private tokens;
68
+ private lastLoad;
69
+ private lastFailure;
70
+ private readonly cacheTtlMs;
71
+ private readonly failureBackoffMs;
72
+ private readonly base;
73
+ private readonly tokenAttr;
74
+ private readonly configAttr;
75
+ private readonly tenantAttr;
76
+ private readonly reloadEndpoint;
77
+ private loading?;
78
+ constructor(server: DM);
79
+ api(app: Express): void;
80
+ /**
81
+ * Load token entries from LDAP. Swallows errors so a transient LDAP hiccup
82
+ * does not poison the cache — the previous snapshot stays in use.
83
+ */
84
+ reload(): Promise<void>;
85
+ /**
86
+ * Ensure the cache is fresh (blocks if reloading).
87
+ *
88
+ * On a bootstrap or successful reload the cache is valid for
89
+ * `cacheTtlMs`. If the load fails, we still refuse to retry on every
90
+ * request — `failureBackoffMs` (default 1/4 of TTL, min 5 s) keeps the
91
+ * previous snapshot in use and shields LDAP from a thundering-herd
92
+ * during an outage.
93
+ */
94
+ private ensureFresh;
95
+ authMethod(req: DmRequest, res: Response, next: () => void): void;
96
+ /**
97
+ * Authorization hooks. These run after the auth middleware has populated
98
+ * `req.authzToken`, so we can read the allowed bases directly from the
99
+ * request.
100
+ */
101
+ hooks: Hooks;
102
+ /** Read the active token from the async context, or (as a fallback) from `req`. */
103
+ private activeToken;
104
+ /**
105
+ * Throw a ForbiddenError (403) if the token cannot perform `permission` on
106
+ * the branch containing `branchOrDn`. Any ACL entry that is `branchOrDn`
107
+ * itself or a parent of it (sub-branch match) qualifies.
108
+ *
109
+ * The client-facing message omits the target DN to avoid leaking tenant
110
+ * topology through authorization failures; the full context is still
111
+ * logged server-side at debug level for operators.
112
+ */
113
+ private requireBranchPermission;
114
+ /** Expose current cache (primarily for the configApi auto-discovery). */
115
+ getConfigApiData(): Record<string, unknown>;
116
+ _tokens(): TokenEntry[];
117
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Verify a provided bearer token against a stored LDAP-style hash.
3
+ *
4
+ * The stored hash is typically the contents of `userPassword`:
5
+ * either `{SCHEME}<data>` or cleartext.
6
+ */
7
+ export declare function verifyLdapPassword(provided: string, stored: string): boolean;
8
+ /**
9
+ * Convenience helpers for tests / tooling to generate an LDAP-style hash.
10
+ *
11
+ * These are NOT used by the runtime verification path and NOT intended for
12
+ * use as a general password-hashing function: the primary use case is
13
+ * seeding LDAP token fixtures in tests. Production `userPassword` values
14
+ * should be generated by `slappasswd` (or another operator tool) with a
15
+ * scheme of your choice. The RFC 3112 `{SSHA}` scheme uses a single SHA-1
16
+ * round — adequate for LDAP bearer-token interop, not adequate for
17
+ * user-chosen passwords. Prefer `sshaHash('sha512', …)` (`{SSHA512}`) when
18
+ * generating new fixtures.
19
+ */
20
+ export type SshaAlgo = 'sha1' | 'sha256' | 'sha512';
21
+ export declare function sshaHash(algo: SshaAlgo, password: string, salt?: Buffer): string;
22
+ /**
23
+ * Legacy alias: generate an {SSHA} (salted SHA-1) hash.
24
+ * Retained so existing tests keep working. Prefer `sshaHash('sha512', ...)`
25
+ * for new fixtures.
26
+ */
27
+ export declare function ssha(password: string, salt?: Buffer): string;
@@ -10,6 +10,57 @@
10
10
  */
11
11
  import type { Express } from 'express';
12
12
  import DmPlugin, { type Role } from '../abstract/plugin';
13
+ /**
14
+ * @openapi-component
15
+ * Config:
16
+ * type: object
17
+ * description: |
18
+ * Aggregated server configuration. The top-level fields are always
19
+ * present; `features` is a dynamic map whose keys are the names of
20
+ * loaded plugins that declare the `configurable` role. Each plugin
21
+ * contributes an arbitrary sub-object — consult the individual plugin
22
+ * documentation for its exact shape.
23
+ * additionalProperties: true
24
+ * properties:
25
+ * apiPrefix:
26
+ * type: string
27
+ * description: The API base path configured on this server.
28
+ * example: /api
29
+ * ldapBase:
30
+ * type: string
31
+ * description: The root LDAP suffix used by this server.
32
+ * example: dc=example,dc=com
33
+ * features:
34
+ * type: object
35
+ * description: |
36
+ * Per-plugin configuration blocks. Keys match the plugin's
37
+ * `name` field; values are plugin-specific objects.
38
+ * additionalProperties: true
39
+ * example:
40
+ * ldapGroups:
41
+ * enabled: true
42
+ * base: ou=groups,dc=example,dc=com
43
+ * mainAttribute: cn
44
+ * objectClass: [top, groupOfNames]
45
+ * static:
46
+ * enabled: true
47
+ * staticPath: /static
48
+ * appAccountsApi:
49
+ * enabled: true
50
+ * base: ou=app-accounts,dc=example,dc=com
51
+ * maxAccounts: 5
52
+ * example:
53
+ * apiPrefix: /api
54
+ * ldapBase: dc=example,dc=com
55
+ * features:
56
+ * ldapGroups:
57
+ * enabled: true
58
+ * base: ou=groups,dc=example,dc=com
59
+ * mainAttribute: cn
60
+ * static:
61
+ * enabled: true
62
+ * staticPath: /static
63
+ */
13
64
  export default class ConfigApi extends DmPlugin {
14
65
  name: string;
15
66
  roles: Role[];
@@ -5,6 +5,27 @@
5
5
  */
6
6
  import type { Express } from 'express';
7
7
  import DmPlugin, { type Role } from '../../abstract/plugin';
8
+ /**
9
+ * @openapi-component
10
+ * Hello:
11
+ * type: object
12
+ * description: Response returned by the hello-world demo endpoint.
13
+ * properties:
14
+ * message:
15
+ * type: string
16
+ * description: Static greeting string.
17
+ * example: Hello
18
+ * hookResults:
19
+ * type: array
20
+ * description: |
21
+ * Results collected from every function registered on the `hello`
22
+ * hook. Empty when no hook listeners are installed.
23
+ * items: {}
24
+ * example: []
25
+ * example:
26
+ * message: Hello
27
+ * hookResults: []
28
+ */
8
29
  export default class HelloWorld extends DmPlugin {
9
30
  name: string;
10
31
  roles: Role[];
@@ -6,6 +6,71 @@
6
6
  import type { Express } from 'express';
7
7
  import DmPlugin, { type Role } from '../../abstract/plugin';
8
8
  import type { DM } from '../../bin';
9
+ /**
10
+ * Shared OpenAPI schemas surfaced by this plugin. Picked up by
11
+ * scripts/generate-openapi.ts and merged into `components.schemas`.
12
+ *
13
+ * @openapi-component
14
+ * BulkImportResult:
15
+ * type: object
16
+ * description: Summary returned after a bulk import operation.
17
+ * required: [success, total, created, updated, skipped, failed, errors, details]
18
+ * properties:
19
+ * success:
20
+ * type: boolean
21
+ * description: True when no fatal error aborted the import early.
22
+ * total:
23
+ * type: integer
24
+ * description: Total number of CSV data rows (excluding header).
25
+ * example: 42
26
+ * created:
27
+ * type: integer
28
+ * description: Entries successfully created (or counted in dry-run).
29
+ * example: 38
30
+ * updated:
31
+ * type: integer
32
+ * description: Existing entries updated.
33
+ * example: 2
34
+ * skipped:
35
+ * type: integer
36
+ * description: Existing entries skipped because `updateExisting` was false.
37
+ * example: 1
38
+ * failed:
39
+ * type: integer
40
+ * description: Rows that caused an error.
41
+ * example: 1
42
+ * errors:
43
+ * type: array
44
+ * items:
45
+ * type: object
46
+ * required: [line, error]
47
+ * properties:
48
+ * line:
49
+ * type: integer
50
+ * description: 1-based CSV line number (2 = first data row).
51
+ * identifier:
52
+ * type: string
53
+ * description: Value of the main attribute for the failing row (if known).
54
+ * error:
55
+ * type: string
56
+ * description: Error message.
57
+ * example:
58
+ * - line: 5
59
+ * identifier: bob
60
+ * error: 'Missing required attribute: mail'
61
+ * details:
62
+ * type: object
63
+ * required: [duration, linesProcessed]
64
+ * properties:
65
+ * duration:
66
+ * type: string
67
+ * description: Wall-clock time for the import (e.g. `1.2s`).
68
+ * example: 1.2s
69
+ * linesProcessed:
70
+ * type: integer
71
+ * description: Total data rows attempted.
72
+ * example: 42
73
+ */
9
74
  export default class LdapBulkImport extends DmPlugin {
10
75
  name: string;
11
76
  roles: Role[];
@@ -10,6 +10,74 @@ export interface postAdd {
10
10
  [key: string]: AttributeValue | undefined;
11
11
  }
12
12
  export type postModify = ModifyRequest;
13
+ /**
14
+ * Shared OpenAPI schemas surfaced by this plugin. Picked up by
15
+ * scripts/generate-openapi.ts and merged into `components.schemas`.
16
+ *
17
+ * @openapi-component
18
+ * Group:
19
+ * type: object
20
+ * description: An LDAP group entry (groupOfNames / groupOfUniqueNames).
21
+ * required: [dn, cn]
22
+ * properties:
23
+ * dn:
24
+ * type: string
25
+ * description: Fully-qualified distinguished name of the entry.
26
+ * example: cn=admins,ou=groups,dc=example,dc=com
27
+ * cn:
28
+ * type: string
29
+ * description: Common name (RDN attribute) of the group.
30
+ * example: admins
31
+ * description:
32
+ * type: string
33
+ * example: Server administrators
34
+ * member:
35
+ * type: array
36
+ * items: { type: string }
37
+ * description: DNs of the group's members.
38
+ * example:
39
+ * - uid=alice,ou=users,dc=example,dc=com
40
+ * - uid=bob,ou=users,dc=example,dc=com
41
+ * GroupCreate:
42
+ * type: object
43
+ * required: [cn]
44
+ * properties:
45
+ * cn:
46
+ * type: string
47
+ * description: Common name of the new group.
48
+ * description:
49
+ * type: string
50
+ * member:
51
+ * type: array
52
+ * items: { type: string }
53
+ * description: Initial members (DNs).
54
+ * example:
55
+ * cn: admins
56
+ * description: Server administrators
57
+ * member:
58
+ * - uid=alice,ou=users,dc=example,dc=com
59
+ * GroupModify:
60
+ * type: object
61
+ * description: |
62
+ * Partial update. Each provided attribute is replaced wholesale. Use
63
+ * the dedicated `/members` endpoints to add/remove members one at a
64
+ * time without supplying the full list.
65
+ * properties:
66
+ * description: { type: string }
67
+ * member:
68
+ * type: array
69
+ * items: { type: string }
70
+ * example:
71
+ * description: Updated description
72
+ * Error:
73
+ * type: object
74
+ * properties:
75
+ * error: { type: string }
76
+ * code: { type: integer }
77
+ * example:
78
+ * error: Group not found
79
+ * code: 404
80
+ */
13
81
  export default class LdapGroups extends DmPlugin {
14
82
  name: string;
15
83
  roles: Role[];
@@ -1,65 +1,8 @@
1
- import type { Express, Request, Response } from 'express';
2
- import DmPlugin, { type Role } from '../../abstract/plugin';
3
- import { DM } from '../../bin';
4
- import { Hooks } from '../../hooks';
5
- import { AttributesList, AttributeValue, ModifyRequest } from '../../lib/ldapActions';
6
- import type { Schema } from '../../config/schema';
7
- export default class LdapOrganizations extends DmPlugin {
8
- name: string;
9
- roles: Role[];
10
- pathAttr: string;
11
- linkAttr: string;
12
- schema?: Schema;
13
- constructor(dm: DM);
14
- /**
15
- * API routes for LDAP organizations
16
- * @param app Express application
17
- */
18
- api(app: Express): void;
19
- apiAdd(req: Request, res: Response): Promise<void>;
20
- apiModify(req: Request, res: Response): Promise<void>;
21
- apiDelete(req: Request, res: Response): Promise<void>;
22
- apiMove(req: Request, res: Response): Promise<void>;
23
- /**
24
- * Consistency checks on any entry
25
- */
26
- hooks: Hooks;
27
- /**
28
- * Check if the department link is valid
29
- * @param entry LDAP entry to check
30
- */
31
- checkDeptLink(entry: AttributesList): Promise<void>;
32
- /**
33
- * Check if the department path is valid
34
- * @param entry LDAP entry to check
35
- */
36
- checkDeptPath(entry: AttributesList): Promise<void>;
37
- isEmptyOrganization(dn: string): Promise<void>;
38
- /**
39
- * Check if entry is an organisation
40
- * @param entry LDAP entry to check
41
- * @returns True if entry is an organisation, false otherwise
42
- */
43
- isOu(entry: AttributesList): boolean;
44
- getOrganisationTop(req?: Request): Promise<AttributesList | AttributesList[]>;
45
- getOrganisationByDn(dn: string, req?: Request): Promise<AttributesList>;
46
- getOrganisationSubnodes(dn: string, req?: Request): Promise<AttributesList[]>;
47
- addOrganization(dn: string, entry: AttributesList, req?: Request): Promise<boolean>;
48
- modifyOrganization(dn: string, changes: ModifyRequest): Promise<boolean>;
49
- validateNewOrganization(dn: string, entry: AttributesList): boolean;
50
- validateChanges(dn: string, changes: ModifyRequest): boolean;
51
- _validateOneChange(field: string, value: AttributeValue | null): boolean;
52
- /**
53
- * Move an organization to a different parent organization
54
- * Uses LDAP modifyDN to change the DN hierarchy
55
- */
56
- moveOrganization(dn: string, targetOrgDn: string, req?: Request): Promise<{
57
- newDn: string;
58
- }>;
59
- deleteOrganization(dn: string): Promise<boolean>;
60
- searchOrganisationSubnodes(dn: string, query: string): Promise<AttributesList[]>;
61
- /**
62
- * Provide configuration for config API
63
- */
64
- getConfigApiData(): Record<string, unknown>;
65
- }
1
+ /**
2
+ * @deprecated Use `core/ldap/organizations` instead. This file is a
3
+ * backwards-compatibility shim for setups that still load the plugin under
4
+ * the historical (singular) filename. It will be removed in a future major
5
+ * release.
6
+ */
7
+ import LdapOrganizations from './organizations';
8
+ export default LdapOrganizations;
@@ -0,0 +1,155 @@
1
+ import type { Express, Request, Response } from 'express';
2
+ import DmPlugin, { type Role } from '../../abstract/plugin';
3
+ import { DM } from '../../bin';
4
+ import { Hooks } from '../../hooks';
5
+ import { AttributesList, AttributeValue, ModifyRequest } from '../../lib/ldapActions';
6
+ import type { Schema } from '../../config/schema';
7
+ /**
8
+ * Shared OpenAPI schemas surfaced by this plugin. Picked up by
9
+ * scripts/generate-openapi.ts and merged into `components.schemas`.
10
+ *
11
+ * @openapi-component
12
+ * Organization:
13
+ * type: object
14
+ * description: An LDAP organizational unit entry.
15
+ * required: [dn, ou]
16
+ * properties:
17
+ * dn:
18
+ * type: string
19
+ * description: Fully-qualified distinguished name of the entry.
20
+ * example: ou=Engineering,ou=organizations,dc=example,dc=com
21
+ * ou:
22
+ * type: string
23
+ * description: Organizational unit name (RDN attribute).
24
+ * example: Engineering
25
+ * o:
26
+ * type: string
27
+ * description: Organization display name.
28
+ * example: Engineering Department
29
+ * l:
30
+ * type: string
31
+ * description: Locality / city.
32
+ * example: Paris
33
+ * description:
34
+ * type: string
35
+ * example: Software engineering division
36
+ * organizationLink:
37
+ * type: string
38
+ * description: DN of the parent organization (link attribute).
39
+ * example: ou=organizations,dc=example,dc=com
40
+ * organizationPath:
41
+ * type: string
42
+ * description: |
43
+ * Human-readable slash-separated path to this node, used by the
44
+ * UI tree. Set automatically on creation and move.
45
+ * example: Engineering / Backend
46
+ * member:
47
+ * type: array
48
+ * items: { type: string }
49
+ * description: DNs of entries linked to this organization.
50
+ * example:
51
+ * - uid=alice,ou=users,dc=example,dc=com
52
+ * OrgSummary:
53
+ * type: object
54
+ * description: Lightweight organization reference (used in node lists).
55
+ * required: [dn, ou]
56
+ * properties:
57
+ * dn: { type: string, example: ou=HR,ou=organizations,dc=example,dc=com }
58
+ * ou: { type: string, example: HR }
59
+ * description: { type: string }
60
+ * objectClass:
61
+ * type: array
62
+ * items: { type: string }
63
+ * example: [top, organizationalUnit]
64
+ * OrganizationCreate:
65
+ * type: object
66
+ * required: [ou]
67
+ * properties:
68
+ * ou:
69
+ * type: string
70
+ * description: Name of the new organizational unit.
71
+ * example: Marketing
72
+ * parentDn:
73
+ * type: string
74
+ * description: |
75
+ * DN of the parent organizational unit. Defaults to the configured
76
+ * top organization when omitted.
77
+ * example: ou=organizations,dc=example,dc=com
78
+ * description: { type: string }
79
+ * l: { type: string }
80
+ * example:
81
+ * ou: Marketing
82
+ * parentDn: ou=organizations,dc=example,dc=com
83
+ * description: Marketing division
84
+ * OrganizationModify:
85
+ * type: object
86
+ * description: |
87
+ * Partial update. Any provided attribute is replaced wholesale.
88
+ * `ou` cannot be changed via this endpoint — use the move endpoint
89
+ * to relocate the node within the tree.
90
+ * properties:
91
+ * description: { type: string }
92
+ * l: { type: string }
93
+ * o: { type: string }
94
+ * example:
95
+ * description: Updated description for Marketing
96
+ */
97
+ export default class LdapOrganizations extends DmPlugin {
98
+ name: string;
99
+ roles: Role[];
100
+ pathAttr: string;
101
+ linkAttr: string;
102
+ schema?: Schema;
103
+ constructor(dm: DM);
104
+ /**
105
+ * API routes for LDAP organizations
106
+ * @param app Express application
107
+ */
108
+ api(app: Express): void;
109
+ apiAdd(req: Request, res: Response): Promise<void>;
110
+ apiModify(req: Request, res: Response): Promise<void>;
111
+ apiDelete(req: Request, res: Response): Promise<void>;
112
+ apiMove(req: Request, res: Response): Promise<void>;
113
+ /**
114
+ * Consistency checks on any entry
115
+ */
116
+ hooks: Hooks;
117
+ /**
118
+ * Check if the department link is valid
119
+ * @param entry LDAP entry to check
120
+ */
121
+ checkDeptLink(entry: AttributesList): Promise<void>;
122
+ /**
123
+ * Check if the department path is valid
124
+ * @param entry LDAP entry to check
125
+ */
126
+ checkDeptPath(entry: AttributesList): Promise<void>;
127
+ isEmptyOrganization(dn: string): Promise<void>;
128
+ /**
129
+ * Check if entry is an organisation
130
+ * @param entry LDAP entry to check
131
+ * @returns True if entry is an organisation, false otherwise
132
+ */
133
+ isOu(entry: AttributesList): boolean;
134
+ getOrganisationTop(req?: Request): Promise<AttributesList | AttributesList[]>;
135
+ getOrganisationByDn(dn: string, req?: Request): Promise<AttributesList>;
136
+ getOrganisationSubnodes(dn: string, req?: Request): Promise<AttributesList[]>;
137
+ addOrganization(dn: string, entry: AttributesList, req?: Request): Promise<boolean>;
138
+ modifyOrganization(dn: string, changes: ModifyRequest): Promise<boolean>;
139
+ validateNewOrganization(dn: string, entry: AttributesList): boolean;
140
+ validateChanges(dn: string, changes: ModifyRequest): boolean;
141
+ _validateOneChange(field: string, value: AttributeValue | null): boolean;
142
+ /**
143
+ * Move an organization to a different parent organization
144
+ * Uses LDAP modifyDN to change the DN hierarchy
145
+ */
146
+ moveOrganization(dn: string, targetOrgDn: string, req?: Request): Promise<{
147
+ newDn: string;
148
+ }>;
149
+ deleteOrganization(dn: string): Promise<boolean>;
150
+ searchOrganisationSubnodes(dn: string, query: string): Promise<AttributesList[]>;
151
+ /**
152
+ * Provide configuration for config API
153
+ */
154
+ getConfigApiData(): Record<string, unknown>;
155
+ }