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.
- package/README.md +1 -1
- package/dist/abstract/ldapFlat.js +1 -1
- package/dist/bin/index.js +1 -1
- package/dist/config/args.js +1 -1
- package/dist/lib/expressFormatedResponses.js +1 -1
- package/dist/plugins/auth/authzDynamic.js +1 -0
- package/dist/plugins/auth/authzDynamicHash.js +1 -0
- package/dist/plugins/ldap/organization.js +1 -1
- package/dist/plugins/ldap/organizations.js +1 -0
- package/dist/plugins/scim/baseResolver.js +1 -0
- package/dist/plugins/scim/bulk.js +1 -0
- package/dist/plugins/scim/discovery.js +1 -0
- package/dist/plugins/scim/errors.js +1 -0
- package/dist/plugins/scim/filter.js +1 -0
- package/dist/plugins/scim/groups.js +1 -0
- package/dist/plugins/scim/mapping.js +1 -0
- package/dist/plugins/scim/patch.js +1 -0
- package/dist/plugins/scim/scim.js +1 -0
- package/dist/plugins/scim/types.js +1 -0
- package/dist/plugins/scim/users.js +1 -0
- package/dist/src/abstract/ldapFlat.d.ts +127 -0
- package/dist/src/config/args.d.ts +24 -0
- package/dist/src/plugins/auth/authzDynamic.d.ts +117 -0
- package/dist/src/plugins/auth/authzDynamicHash.d.ts +27 -0
- package/dist/src/plugins/configApi.d.ts +51 -0
- package/dist/src/plugins/demo/helloworld.d.ts +21 -0
- package/dist/src/plugins/ldap/bulkImport.d.ts +65 -0
- package/dist/src/plugins/ldap/groups.d.ts +68 -0
- package/dist/src/plugins/ldap/organization.d.ts +8 -65
- package/dist/src/plugins/ldap/organizations.d.ts +155 -0
- package/dist/src/plugins/ldap/passwordPolicy.d.ts +178 -0
- package/dist/src/plugins/scim/baseResolver.d.ts +23 -0
- package/dist/src/plugins/scim/bulk.d.ts +43 -0
- package/dist/src/plugins/scim/discovery.d.ts +27 -0
- package/dist/src/plugins/scim/errors.d.ts +37 -0
- package/dist/src/plugins/scim/filter.d.ts +13 -0
- package/dist/src/plugins/scim/groups.d.ts +61 -0
- package/dist/src/plugins/scim/mapping.d.ts +52 -0
- package/dist/src/plugins/scim/patch.d.ts +39 -0
- package/dist/src/plugins/scim/scim.d.ts +412 -0
- package/dist/src/plugins/scim/types.d.ts +222 -0
- package/dist/src/plugins/scim/users.d.ts +58 -0
- package/dist/src/plugins/static.d.ts +33 -0
- package/dist/src/plugins/twake/appAccountsApi.d.ts +63 -0
- package/dist/src/plugins/twake/james.d.ts +47 -0
- package/docs/plugin-development/README.md +2 -1
- package/docs/plugin-development/openapi.md +144 -0
- package/docs/usage/plugins/auth/authz-dynamic.md +183 -0
- package/docs/usage/plugins/integrations/scim.md +567 -0
- package/package.json +72 -1
- package/static/schemas/scim/Group.json +31 -0
- package/static/schemas/scim/User.json +147 -0
- package/static/schemas/scim/default-mapping.json +48 -0
|
@@ -11,6 +11,184 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { Express } from 'express';
|
|
13
13
|
import DmPlugin, { type Role } from '../../abstract/plugin';
|
|
14
|
+
/**
|
|
15
|
+
* Shared OpenAPI schemas surfaced by this plugin. Picked up by
|
|
16
|
+
* scripts/generate-openapi.ts and merged into `components.schemas`.
|
|
17
|
+
*
|
|
18
|
+
* @openapi-component
|
|
19
|
+
* PasswordPolicy:
|
|
20
|
+
* type: object
|
|
21
|
+
* description: |
|
|
22
|
+
* Active OpenLDAP ppolicy configuration. All duration fields are in
|
|
23
|
+
* **seconds**. Absent fields mean the policy attribute is not set.
|
|
24
|
+
* properties:
|
|
25
|
+
* dn:
|
|
26
|
+
* type: string
|
|
27
|
+
* description: DN of the ppolicy entry.
|
|
28
|
+
* example: cn=default,ou=policies,dc=example,dc=com
|
|
29
|
+
* pwdMaxAge:
|
|
30
|
+
* type: integer
|
|
31
|
+
* description: Maximum password age in seconds (0 = never expires).
|
|
32
|
+
* example: 7776000
|
|
33
|
+
* pwdMinAge:
|
|
34
|
+
* type: integer
|
|
35
|
+
* description: Minimum password age in seconds before it may be changed.
|
|
36
|
+
* example: 0
|
|
37
|
+
* pwdInHistory:
|
|
38
|
+
* type: integer
|
|
39
|
+
* description: Number of previous passwords stored.
|
|
40
|
+
* example: 5
|
|
41
|
+
* pwdCheckQuality:
|
|
42
|
+
* type: integer
|
|
43
|
+
* description: Password quality enforcement level (0-2).
|
|
44
|
+
* example: 1
|
|
45
|
+
* pwdMinLength:
|
|
46
|
+
* type: integer
|
|
47
|
+
* description: Minimum password length.
|
|
48
|
+
* example: 12
|
|
49
|
+
* pwdMaxFailure:
|
|
50
|
+
* type: integer
|
|
51
|
+
* description: Maximum bind failures before lockout.
|
|
52
|
+
* example: 5
|
|
53
|
+
* pwdLockout:
|
|
54
|
+
* type: boolean
|
|
55
|
+
* description: Whether account lockout is enabled.
|
|
56
|
+
* example: true
|
|
57
|
+
* pwdLockoutDuration:
|
|
58
|
+
* type: integer
|
|
59
|
+
* description: Lockout duration in seconds (0 = permanent until admin reset).
|
|
60
|
+
* example: 300
|
|
61
|
+
* pwdGraceAuthNLimit:
|
|
62
|
+
* type: integer
|
|
63
|
+
* description: Number of grace logins allowed after password expires.
|
|
64
|
+
* example: 0
|
|
65
|
+
* pwdExpireWarning:
|
|
66
|
+
* type: integer
|
|
67
|
+
* description: Seconds before expiry at which a warning is issued.
|
|
68
|
+
* example: 1209600
|
|
69
|
+
* pwdMustChange:
|
|
70
|
+
* type: boolean
|
|
71
|
+
* description: If true, users must change their password after an admin reset.
|
|
72
|
+
* example: true
|
|
73
|
+
* pwdAllowUserChange:
|
|
74
|
+
* type: boolean
|
|
75
|
+
* description: Whether users are allowed to change their own password.
|
|
76
|
+
* example: true
|
|
77
|
+
* PasswordStatus:
|
|
78
|
+
* type: object
|
|
79
|
+
* description: Current password status for a specific user account.
|
|
80
|
+
* required: [dn, passwordSet, isExpired, isExpiringSoon, mustChange, isLocked, failureCount, graceLoginsUsed]
|
|
81
|
+
* properties:
|
|
82
|
+
* dn:
|
|
83
|
+
* type: string
|
|
84
|
+
* description: Fully-qualified DN of the user.
|
|
85
|
+
* example: uid=alice,ou=users,dc=example,dc=com
|
|
86
|
+
* passwordSet:
|
|
87
|
+
* type: boolean
|
|
88
|
+
* description: Whether a userPassword attribute is present.
|
|
89
|
+
* example: true
|
|
90
|
+
* lastChanged:
|
|
91
|
+
* type: string
|
|
92
|
+
* format: date-time
|
|
93
|
+
* nullable: true
|
|
94
|
+
* description: ISO-8601 timestamp of last password change, or null if never set.
|
|
95
|
+
* example: '2026-01-15T10:00:00.000Z'
|
|
96
|
+
* expiresAt:
|
|
97
|
+
* type: string
|
|
98
|
+
* format: date-time
|
|
99
|
+
* nullable: true
|
|
100
|
+
* description: ISO-8601 expiry timestamp, or null if no max age is configured.
|
|
101
|
+
* example: '2026-04-15T10:00:00.000Z'
|
|
102
|
+
* daysUntilExpiration:
|
|
103
|
+
* type: integer
|
|
104
|
+
* nullable: true
|
|
105
|
+
* description: Remaining days until expiry (negative means already expired).
|
|
106
|
+
* example: 80
|
|
107
|
+
* isExpired:
|
|
108
|
+
* type: boolean
|
|
109
|
+
* example: false
|
|
110
|
+
* isExpiringSoon:
|
|
111
|
+
* type: boolean
|
|
112
|
+
* description: True when expiry is within the configured warning window.
|
|
113
|
+
* example: false
|
|
114
|
+
* mustChange:
|
|
115
|
+
* type: boolean
|
|
116
|
+
* description: True when `pwdReset` is set (admin forced change).
|
|
117
|
+
* example: false
|
|
118
|
+
* isLocked:
|
|
119
|
+
* type: boolean
|
|
120
|
+
* description: True when the account is currently locked.
|
|
121
|
+
* example: false
|
|
122
|
+
* lockedAt:
|
|
123
|
+
* type: string
|
|
124
|
+
* format: date-time
|
|
125
|
+
* nullable: true
|
|
126
|
+
* description: ISO-8601 timestamp of lockout, or null.
|
|
127
|
+
* example: null
|
|
128
|
+
* failureCount:
|
|
129
|
+
* type: integer
|
|
130
|
+
* description: Number of recorded bind failures still within the window.
|
|
131
|
+
* example: 0
|
|
132
|
+
* graceLoginsUsed:
|
|
133
|
+
* type: integer
|
|
134
|
+
* description: Number of grace logins already consumed.
|
|
135
|
+
* example: 0
|
|
136
|
+
* ExpiringAccount:
|
|
137
|
+
* type: object
|
|
138
|
+
* description: User whose password is expiring within the warning window.
|
|
139
|
+
* required: [dn, expiresAt, daysUntilExpiration]
|
|
140
|
+
* properties:
|
|
141
|
+
* dn: { type: string, example: uid=bob,ou=users,dc=example,dc=com }
|
|
142
|
+
* uid: { type: string, example: bob }
|
|
143
|
+
* displayName: { type: string, example: Bob Builder }
|
|
144
|
+
* mail: { type: string, example: bob@example.com }
|
|
145
|
+
* expiresAt:
|
|
146
|
+
* type: string
|
|
147
|
+
* format: date-time
|
|
148
|
+
* example: '2026-05-01T08:00:00.000Z'
|
|
149
|
+
* daysUntilExpiration: { type: integer, example: 6 }
|
|
150
|
+
* LockedAccount:
|
|
151
|
+
* type: object
|
|
152
|
+
* description: User account that is currently locked.
|
|
153
|
+
* required: [dn, failureCount]
|
|
154
|
+
* properties:
|
|
155
|
+
* dn: { type: string, example: uid=charlie,ou=users,dc=example,dc=com }
|
|
156
|
+
* uid: { type: string, example: charlie }
|
|
157
|
+
* displayName: { type: string, example: Charlie Chaplin }
|
|
158
|
+
* mail: { type: string, example: charlie@example.com }
|
|
159
|
+
* lockedAt:
|
|
160
|
+
* type: string
|
|
161
|
+
* format: date-time
|
|
162
|
+
* nullable: true
|
|
163
|
+
* example: '2026-04-24T14:30:00.000Z'
|
|
164
|
+
* failureCount: { type: integer, example: 5 }
|
|
165
|
+
* PasswordValidationRequest:
|
|
166
|
+
* type: object
|
|
167
|
+
* required: [password]
|
|
168
|
+
* properties:
|
|
169
|
+
* password:
|
|
170
|
+
* type: string
|
|
171
|
+
* format: password
|
|
172
|
+
* description: The candidate password to validate.
|
|
173
|
+
* example: 'S3cur3P@ss!'
|
|
174
|
+
* dn:
|
|
175
|
+
* type: string
|
|
176
|
+
* description: Optional user DN for context (not used in local validation).
|
|
177
|
+
* example: uid=alice,ou=users,dc=example,dc=com
|
|
178
|
+
* PasswordValidationResponse:
|
|
179
|
+
* type: object
|
|
180
|
+
* required: [valid, errors]
|
|
181
|
+
* properties:
|
|
182
|
+
* valid:
|
|
183
|
+
* type: boolean
|
|
184
|
+
* description: True when the password meets all complexity rules.
|
|
185
|
+
* example: true
|
|
186
|
+
* errors:
|
|
187
|
+
* type: array
|
|
188
|
+
* items: { type: string }
|
|
189
|
+
* description: List of failed rule descriptions (empty when valid).
|
|
190
|
+
* example: []
|
|
191
|
+
*/
|
|
14
192
|
export default class PasswordPolicy extends DmPlugin {
|
|
15
193
|
name: string;
|
|
16
194
|
roles: Role[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Config } from '../../config/args';
|
|
2
|
+
import type { DmRequest } from '../../lib/auth/base';
|
|
3
|
+
export interface BaseMapEntry {
|
|
4
|
+
userBase?: string;
|
|
5
|
+
groupBase?: string;
|
|
6
|
+
}
|
|
7
|
+
export type BaseMap = Record<string, BaseMapEntry>;
|
|
8
|
+
export declare class BaseResolver {
|
|
9
|
+
private readonly defaultUserBase;
|
|
10
|
+
private readonly defaultGroupBase;
|
|
11
|
+
private readonly userTemplate;
|
|
12
|
+
private readonly groupTemplate;
|
|
13
|
+
private readonly map;
|
|
14
|
+
constructor(config: Config);
|
|
15
|
+
private applyTemplate;
|
|
16
|
+
private resolve;
|
|
17
|
+
userBase(req?: DmRequest | {
|
|
18
|
+
user?: string;
|
|
19
|
+
}): string;
|
|
20
|
+
groupBase(req?: DmRequest | {
|
|
21
|
+
user?: string;
|
|
22
|
+
}): string;
|
|
23
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module plugins/scim/bulk
|
|
3
|
+
* @author Xavier Guimard <xguimard@linagora.com>
|
|
4
|
+
*
|
|
5
|
+
* SCIM 2.0 Bulk operations (RFC 7644 §3.7).
|
|
6
|
+
*
|
|
7
|
+
* Executes operations sequentially. Supports `bulkId` cross-references:
|
|
8
|
+
* a POST with `"bulkId":"abc"` creates a resource; subsequent operations
|
|
9
|
+
* in the same request can reference it as "bulkId:abc" anywhere a SCIM
|
|
10
|
+
* id or member.value is expected (resolved to the concrete id before
|
|
11
|
+
* dispatch).
|
|
12
|
+
*/
|
|
13
|
+
import type winston from 'winston';
|
|
14
|
+
import type { Config } from '../../config/args';
|
|
15
|
+
import type { DmRequest } from '../../lib/auth/base';
|
|
16
|
+
import type { ScimUsers } from './users';
|
|
17
|
+
import type { ScimGroups } from './groups';
|
|
18
|
+
import { type BulkRequest, type BulkResponse } from './types';
|
|
19
|
+
import { writeScimErrorFromException } from './errors';
|
|
20
|
+
export interface ScimBulkOptions {
|
|
21
|
+
config: Config;
|
|
22
|
+
logger: winston.Logger;
|
|
23
|
+
users: ScimUsers;
|
|
24
|
+
groups: ScimGroups;
|
|
25
|
+
scimPrefix: string;
|
|
26
|
+
hooks: {
|
|
27
|
+
[K: string]: Function[] | undefined;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export declare class ScimBulk {
|
|
31
|
+
private readonly config;
|
|
32
|
+
private readonly logger;
|
|
33
|
+
private readonly users;
|
|
34
|
+
private readonly groups;
|
|
35
|
+
private readonly scimPrefix;
|
|
36
|
+
private readonly hooks;
|
|
37
|
+
constructor(opts: ScimBulkOptions);
|
|
38
|
+
execute(req: DmRequest, body: BulkRequest): Promise<BulkResponse>;
|
|
39
|
+
private executeOne;
|
|
40
|
+
private resolveRefs;
|
|
41
|
+
private buildLocation;
|
|
42
|
+
}
|
|
43
|
+
export { writeScimErrorFromException };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Config } from '../../config/args';
|
|
2
|
+
import type { DmRequest } from '../../lib/auth/base';
|
|
3
|
+
import { type ResourceTypeDefinition, type ServiceProviderConfig, type SchemaDefinition } from './types';
|
|
4
|
+
export interface DiscoveryOptions {
|
|
5
|
+
config: Config;
|
|
6
|
+
schemaDir: string;
|
|
7
|
+
scimPrefix: string;
|
|
8
|
+
loadedPlugins: {
|
|
9
|
+
[name: string]: unknown;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export declare class ScimDiscovery {
|
|
13
|
+
private readonly config;
|
|
14
|
+
private readonly schemaDir;
|
|
15
|
+
private readonly scimPrefix;
|
|
16
|
+
private readonly loadedPlugins;
|
|
17
|
+
constructor(opts: DiscoveryOptions);
|
|
18
|
+
private baseUrl;
|
|
19
|
+
private location;
|
|
20
|
+
private authSchemes;
|
|
21
|
+
serviceProviderConfig(req?: DmRequest): ServiceProviderConfig;
|
|
22
|
+
resourceTypes(req?: DmRequest): ResourceTypeDefinition[];
|
|
23
|
+
resourceType(name: string, req?: DmRequest): ResourceTypeDefinition | undefined;
|
|
24
|
+
schemas(req?: DmRequest): SchemaDefinition[];
|
|
25
|
+
schema(id: string, req?: DmRequest): SchemaDefinition | undefined;
|
|
26
|
+
private loadSchema;
|
|
27
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module plugins/scim/errors
|
|
3
|
+
* @author Xavier Guimard <xguimard@linagora.com>
|
|
4
|
+
*
|
|
5
|
+
* SCIM 2.0 error envelope (RFC 7644 §3.12) and async handler wrapper.
|
|
6
|
+
*/
|
|
7
|
+
import type { Request, Response, NextFunction, RequestHandler } from 'express';
|
|
8
|
+
import { HttpError } from '../../lib/errors';
|
|
9
|
+
import { type ScimErrorType } from './types';
|
|
10
|
+
export declare const SCIM_CONTENT_TYPE = "application/scim+json";
|
|
11
|
+
export declare class ScimError extends HttpError {
|
|
12
|
+
scimType?: ScimErrorType;
|
|
13
|
+
constructor(status: number, detail: string, scimType?: ScimErrorType);
|
|
14
|
+
}
|
|
15
|
+
export declare const scimInvalidFilter: (detail?: string) => ScimError;
|
|
16
|
+
export declare const scimInvalidPath: (detail?: string) => ScimError;
|
|
17
|
+
export declare const scimInvalidValue: (detail?: string) => ScimError;
|
|
18
|
+
export declare const scimInvalidSyntax: (detail?: string) => ScimError;
|
|
19
|
+
export declare const scimNoTarget: (detail?: string) => ScimError;
|
|
20
|
+
export declare const scimMutability: (detail?: string) => ScimError;
|
|
21
|
+
export declare const scimUniqueness: (detail?: string) => ScimError;
|
|
22
|
+
export declare const scimTooMany: (detail?: string) => ScimError;
|
|
23
|
+
export declare const scimNotFound: (detail?: string) => ScimError;
|
|
24
|
+
export declare function writeScimError(res: Response, status: number, detail: string, scimType?: ScimErrorType): void;
|
|
25
|
+
/**
|
|
26
|
+
* Extract an LDAP numeric error code from either a thrown ldapts error
|
|
27
|
+
* (has `.code`) or an Error wrapped by ldapActions (message contains the
|
|
28
|
+
* original text). Returns undefined if no code is detectable.
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractLdapCode(err: unknown): number | undefined;
|
|
31
|
+
export declare function writeScimErrorFromException(res: Response, err: unknown, fallbackStatus?: number): void;
|
|
32
|
+
/**
|
|
33
|
+
* Express wrapper that catches errors, serializes them as SCIM
|
|
34
|
+
* envelope and short-circuits the default Express error middleware.
|
|
35
|
+
* Accepts both sync and async handlers.
|
|
36
|
+
*/
|
|
37
|
+
export declare const scimAsyncHandler: (fn: (req: Request, res: Response, next: NextFunction) => void | Promise<void>) => RequestHandler;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ResourceMapping } from './types';
|
|
2
|
+
export interface TranslatedFilter {
|
|
3
|
+
ldapFilter: string;
|
|
4
|
+
/** True if the filter contains `id eq "..."`, which the caller should translate into a DN lookup. */
|
|
5
|
+
touchesId: boolean;
|
|
6
|
+
/** Extracted id value if the filter is a simple `id eq "..."` (single clause). */
|
|
7
|
+
idEquals?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Translate a SCIM filter string into an LDAP filter.
|
|
11
|
+
* Returns metadata enabling the caller to short-circuit id-equals queries.
|
|
12
|
+
*/
|
|
13
|
+
export declare function scimFilterToLdap(filter: string, mapping: ResourceMapping): TranslatedFilter;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module plugins/scim/groups
|
|
3
|
+
* @author Xavier Guimard <xguimard@linagora.com>
|
|
4
|
+
*
|
|
5
|
+
* SCIM Groups resource handler — direct access via ldapActions.
|
|
6
|
+
*/
|
|
7
|
+
import type winston from 'winston';
|
|
8
|
+
import type ldapActions from '../../lib/ldapActions';
|
|
9
|
+
import type { Config } from '../../config/args';
|
|
10
|
+
import type { DmRequest } from '../../lib/auth/base';
|
|
11
|
+
import { BaseResolver } from './baseResolver';
|
|
12
|
+
import { type ResourceMapping, type ScimGroup, type ListResponse, type PatchRequest } from './types';
|
|
13
|
+
import type { ScimUsers } from './users';
|
|
14
|
+
export interface GroupListQuery {
|
|
15
|
+
filter?: string;
|
|
16
|
+
startIndex?: number;
|
|
17
|
+
count?: number;
|
|
18
|
+
sortBy?: string;
|
|
19
|
+
sortOrder?: 'ascending' | 'descending';
|
|
20
|
+
}
|
|
21
|
+
export interface ScimGroupsOptions {
|
|
22
|
+
ldap: ldapActions;
|
|
23
|
+
config: Config;
|
|
24
|
+
logger: winston.Logger;
|
|
25
|
+
baseResolver: BaseResolver;
|
|
26
|
+
users: ScimUsers;
|
|
27
|
+
hooks: {
|
|
28
|
+
[K: string]: Function[] | undefined;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export declare class ScimGroups {
|
|
32
|
+
private readonly ldap;
|
|
33
|
+
private readonly config;
|
|
34
|
+
private readonly logger;
|
|
35
|
+
private readonly baseResolver;
|
|
36
|
+
private readonly users;
|
|
37
|
+
private readonly hooks;
|
|
38
|
+
private readonly mapping;
|
|
39
|
+
private readonly rdnAttribute;
|
|
40
|
+
private readonly objectClass;
|
|
41
|
+
private readonly idAttribute;
|
|
42
|
+
private readonly maxResults;
|
|
43
|
+
private readonly scimPrefix;
|
|
44
|
+
constructor(opts: ScimGroupsOptions);
|
|
45
|
+
private ctx;
|
|
46
|
+
private dnForId;
|
|
47
|
+
/**
|
|
48
|
+
* Resolve an LDAP member DN back to a SCIM reference (for GET responses).
|
|
49
|
+
* Best-effort: extract the RDN value and a guessed type (User if in user base).
|
|
50
|
+
*/
|
|
51
|
+
private buildMemberResolver;
|
|
52
|
+
get(req: DmRequest, id: string): Promise<ScimGroup>;
|
|
53
|
+
list(req: DmRequest, query: GroupListQuery): Promise<ListResponse<ScimGroup>>;
|
|
54
|
+
create(req: DmRequest, resource: ScimGroup): Promise<ScimGroup>;
|
|
55
|
+
replace(req: DmRequest, id: string, resource: ScimGroup): Promise<ScimGroup>;
|
|
56
|
+
patch(req: DmRequest, id: string, patch: PatchRequest): Promise<ScimGroup>;
|
|
57
|
+
delete(req: DmRequest, id: string): Promise<void>;
|
|
58
|
+
/** Used internally and by Bulk to resolve a Group SCIM reference to a DN. */
|
|
59
|
+
resolveRef(req: DmRequest, value: string): Promise<string | undefined>;
|
|
60
|
+
get groupMapping(): ResourceMapping;
|
|
61
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AttributesList } from '../../lib/ldapActions';
|
|
2
|
+
import { type ScimUser, type ScimGroup, type ResourceMapping, type MultiValued } from './types';
|
|
3
|
+
export declare const DEFAULT_USER_MAPPING: ResourceMapping;
|
|
4
|
+
export declare const DEFAULT_GROUP_MAPPING: ResourceMapping;
|
|
5
|
+
export declare function loadMappingFile(path: string): ResourceMapping | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Build the mapping used at runtime by merging a user-supplied JSON
|
|
8
|
+
* override on top of the default. Override entries replace default
|
|
9
|
+
* entries with the same `scim` key; new entries are appended.
|
|
10
|
+
*/
|
|
11
|
+
export declare function mergeMapping(base: ResourceMapping, override?: ResourceMapping): ResourceMapping;
|
|
12
|
+
export interface MappingContext {
|
|
13
|
+
/** 'rdn' (default), 'entryUUID', or any LDAP attribute name */
|
|
14
|
+
idAttribute: string;
|
|
15
|
+
rdnAttribute: string;
|
|
16
|
+
resourceType: 'User' | 'Group';
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
scimPrefix: string;
|
|
19
|
+
}
|
|
20
|
+
/** Resolve the SCIM id for an LDAP entry, per configuration. */
|
|
21
|
+
export declare function resolveScimId(entry: AttributesList, ctx: MappingContext): string | undefined;
|
|
22
|
+
export declare function buildLocation(id: string, ctx: MappingContext): string | undefined;
|
|
23
|
+
export declare function ldapToScimUser(entry: AttributesList, mapping: ResourceMapping, ctx: MappingContext): ScimUser;
|
|
24
|
+
/**
|
|
25
|
+
* Convert SCIM User body → LDAP attributes list.
|
|
26
|
+
* The RDN attribute is set from `userName` (or explicit `id`).
|
|
27
|
+
* Returns { rdn, attributes } so the caller can build the DN.
|
|
28
|
+
*/
|
|
29
|
+
export declare function scimUserToLdap(user: ScimUser, mapping: ResourceMapping, ctx: MappingContext, objectClass: string[]): {
|
|
30
|
+
rdn: string;
|
|
31
|
+
attributes: AttributesList;
|
|
32
|
+
};
|
|
33
|
+
export declare function ldapToScimGroup(entry: AttributesList, mapping: ResourceMapping, ctx: MappingContext, memberResolver?: (dn: string) => MultiValued | undefined): ScimGroup;
|
|
34
|
+
/**
|
|
35
|
+
* Convert SCIM Group → LDAP attributes (members excluded; they're
|
|
36
|
+
* resolved separately to DNs by the handler).
|
|
37
|
+
*/
|
|
38
|
+
export declare function scimGroupToLdap(group: ScimGroup, mapping: ResourceMapping, objectClass: string[]): {
|
|
39
|
+
rdn: string;
|
|
40
|
+
attributes: AttributesList;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* List of LDAP attributes to request from the directory so all
|
|
44
|
+
* mapped SCIM attributes can be populated.
|
|
45
|
+
*/
|
|
46
|
+
export declare function requiredLdapAttributes(mapping: ResourceMapping): string[];
|
|
47
|
+
/**
|
|
48
|
+
* Given a SCIM attribute path like "emails.value" or "name.familyName",
|
|
49
|
+
* return the corresponding LDAP attribute name (used by filter parser).
|
|
50
|
+
* Returns undefined if the path is not mapped.
|
|
51
|
+
*/
|
|
52
|
+
export declare function scimPathToLdapAttribute(path: string, mapping: ResourceMapping): string | undefined;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module plugins/scim/patch
|
|
3
|
+
* @author Xavier Guimard <xguimard@linagora.com>
|
|
4
|
+
*
|
|
5
|
+
* SCIM PatchOp applicator (RFC 7644 §3.5.2) → ldapts ModifyRequest.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - op: add / remove / replace
|
|
9
|
+
* - simple paths: "displayName", "userName"
|
|
10
|
+
* - sub-attribute paths: "name.familyName"
|
|
11
|
+
* - multi-valued paths: "emails"
|
|
12
|
+
* - Group member ops via filtered path: `members[value eq "alice"]`
|
|
13
|
+
* - implicit path (op.value is an object)
|
|
14
|
+
*
|
|
15
|
+
* Complex filtered paths on multi-valued attributes OTHER than `members`
|
|
16
|
+
* (e.g. `emails[type eq "work"]`) are rejected with `invalidPath`: the
|
|
17
|
+
* plugin only knows how to map them for member-type resolution. Filtered
|
|
18
|
+
* member operations for Groups are handled via the caller-provided
|
|
19
|
+
* `resolveMemberRef` hook (SCIM id → LDAP DN lookup).
|
|
20
|
+
*/
|
|
21
|
+
import type { ModifyRequest } from '../../lib/ldapActions';
|
|
22
|
+
import { type ResourceMapping, type PatchRequest } from './types';
|
|
23
|
+
export interface PatchContext {
|
|
24
|
+
mapping: ResourceMapping;
|
|
25
|
+
/** For Groups: resolve SCIM member value (id or $ref) → LDAP DN. Async. */
|
|
26
|
+
resolveMemberRef?: (value: string) => Promise<string | undefined>;
|
|
27
|
+
/** The LDAP attribute holding members, default 'member'. */
|
|
28
|
+
memberAttribute?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function patchToModifyRequest(patch: PatchRequest, ctx: PatchContext): Promise<ModifyRequest>;
|
|
31
|
+
/**
|
|
32
|
+
* Apply PATCH to a SCIM resource object in-memory (used for PUT-equivalent
|
|
33
|
+
* or when the resource handler prefers object-level manipulation before
|
|
34
|
+
* falling back to full replace).
|
|
35
|
+
*
|
|
36
|
+
* Returns the mutated resource. Used by tests; the main production path
|
|
37
|
+
* is patchToModifyRequest() → ldap.modify().
|
|
38
|
+
*/
|
|
39
|
+
export declare function applyPatchToResource<T extends Record<string, unknown>>(resource: T, patch: PatchRequest): T;
|