ldap-rest 0.1.5 → 0.1.7

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.
@@ -0,0 +1,454 @@
1
+ # Password Policy Plugin
2
+
3
+ The `passwordPolicy` plugin provides a REST API for managing OpenLDAP password policies (ppolicy overlay). It allows administrators to monitor password expiration, manage locked accounts, and optionally validate password complexity.
4
+
5
+ ## Overview
6
+
7
+ This plugin acts as an administration interface for OpenLDAP's ppolicy overlay. It provides:
8
+
9
+ - Password status monitoring (expiration, lockout, grace logins)
10
+ - Account unlock functionality
11
+ - List users with expiring passwords
12
+ - List locked accounts
13
+ - Optional local password complexity validation
14
+
15
+ **Note:** ldap-rest is an administration backend. Users authenticate directly against OpenLDAP, not through ldap-rest. This plugin exposes ppolicy attributes via REST API for administrative purposes.
16
+
17
+ ## Prerequisites
18
+
19
+ - OpenLDAP configured with the **ppolicy overlay**
20
+ - A password policy entry (pwdPolicy objectClass) in your LDAP directory
21
+
22
+ ## Configuration
23
+
24
+ ### CLI Arguments
25
+
26
+ ```bash
27
+ --plugin core/ldap/passwordPolicy \
28
+ --ppolicy-default-dn "cn=default,ou=policies,dc=example,dc=com" \
29
+ --ppolicy-warn-days 14
30
+ ```
31
+
32
+ ### Configuration Options
33
+
34
+ | Argument | Environment Variable | Default | Description |
35
+ | ---------------------- | ----------------------- | ------------- | ------------------------------------------------- |
36
+ | `--ppolicy-default-dn` | `DM_PPOLICY_DEFAULT_DN` | (auto-detect) | DN of the default password policy entry |
37
+ | `--ppolicy-warn-days` | `DM_PPOLICY_WARN_DAYS` | `14` | Days before expiration to flag as "expiring soon" |
38
+ | `--ldap-users-base` | `DM_LDAP_USERS_BASE` | `{ldap_base}` | Base DN for user searches |
39
+
40
+ ### Optional Complexity Validation
41
+
42
+ Enable local password complexity validation (for client-side feedback before submitting to LDAP):
43
+
44
+ ```bash
45
+ --ppolicy-validate-complexity true \
46
+ --ppolicy-min-length 12 \
47
+ --ppolicy-require-uppercase true \
48
+ --ppolicy-require-lowercase true \
49
+ --ppolicy-require-digit true \
50
+ --ppolicy-require-special true
51
+ ```
52
+
53
+ | Argument | Environment Variable | Default | Description |
54
+ | ------------------------------- | -------------------------------- | ------- | ---------------------------------- |
55
+ | `--ppolicy-validate-complexity` | `DM_PPOLICY_VALIDATE_COMPLEXITY` | `false` | Enable /password/validate endpoint |
56
+ | `--ppolicy-min-length` | `DM_PPOLICY_MIN_LENGTH` | `12` | Minimum password length |
57
+ | `--ppolicy-require-uppercase` | `DM_PPOLICY_REQUIRE_UPPERCASE` | `true` | Require uppercase letter |
58
+ | `--ppolicy-require-lowercase` | `DM_PPOLICY_REQUIRE_LOWERCASE` | `true` | Require lowercase letter |
59
+ | `--ppolicy-require-digit` | `DM_PPOLICY_REQUIRE_DIGIT` | `true` | Require digit |
60
+ | `--ppolicy-require-special` | `DM_PPOLICY_REQUIRE_SPECIAL` | `true` | Require special character |
61
+
62
+ ## REST API
63
+
64
+ ### Get Password Policy Configuration
65
+
66
+ ```http
67
+ GET /api/v1/password-policy
68
+ ```
69
+
70
+ Returns the current ppolicy configuration from LDAP.
71
+
72
+ **Response (200):**
73
+
74
+ ```json
75
+ {
76
+ "dn": "cn=default,ou=policies,dc=example,dc=com",
77
+ "pwdMaxAge": 7776000,
78
+ "pwdMinAge": 0,
79
+ "pwdInHistory": 5,
80
+ "pwdCheckQuality": 2,
81
+ "pwdMinLength": 12,
82
+ "pwdMaxFailure": 5,
83
+ "pwdLockout": true,
84
+ "pwdLockoutDuration": 900,
85
+ "pwdGraceAuthNLimit": 3,
86
+ "pwdExpireWarning": 604800,
87
+ "pwdMustChange": true,
88
+ "pwdAllowUserChange": true
89
+ }
90
+ ```
91
+
92
+ ### Get User Password Status
93
+
94
+ ```http
95
+ GET /api/v1/users/{id}/password-status
96
+ ```
97
+
98
+ **Path Parameter:**
99
+
100
+ - `id`: User uid or full DN (URL-encoded if DN)
101
+
102
+ **Examples:**
103
+
104
+ ```bash
105
+ # Get by uid
106
+ curl "http://localhost:8081/api/v1/users/john.doe/password-status"
107
+
108
+ # Get by full DN
109
+ curl "http://localhost:8081/api/v1/users/uid%3Djohn.doe%2Cou%3Dusers%2Cdc%3Dexample%2Cdc%3Dcom/password-status"
110
+ ```
111
+
112
+ **Response (200):**
113
+
114
+ ```json
115
+ {
116
+ "dn": "uid=john.doe,ou=users,dc=example,dc=com",
117
+ "passwordSet": true,
118
+ "lastChanged": "2024-01-15T10:30:00.000Z",
119
+ "expiresAt": "2024-04-14T10:30:00.000Z",
120
+ "daysUntilExpiration": 45,
121
+ "isExpired": false,
122
+ "isExpiringSoon": false,
123
+ "mustChange": false,
124
+ "isLocked": false,
125
+ "lockedAt": null,
126
+ "failureCount": 0,
127
+ "graceLoginsUsed": 0
128
+ }
129
+ ```
130
+
131
+ **Response (404):**
132
+
133
+ ```json
134
+ {
135
+ "error": "User not found: john.doe"
136
+ }
137
+ ```
138
+
139
+ ### Unlock User Account
140
+
141
+ ```http
142
+ POST /api/v1/users/{id}/unlock
143
+ ```
144
+
145
+ Unlocks a locked account by removing `pwdAccountLockedTime` and `pwdFailureTime` attributes.
146
+
147
+ **Path Parameter:**
148
+
149
+ - `id`: User uid or full DN
150
+
151
+ **Example:**
152
+
153
+ ```bash
154
+ curl -X POST "http://localhost:8081/api/v1/users/john.doe/unlock"
155
+ ```
156
+
157
+ **Response (200):**
158
+
159
+ ```json
160
+ {
161
+ "success": true,
162
+ "message": "Account unlocked"
163
+ }
164
+ ```
165
+
166
+ ### List Users with Expiring Passwords
167
+
168
+ ```http
169
+ GET /api/v1/password-policy/expiring-soon
170
+ ```
171
+
172
+ **Query Parameters:**
173
+
174
+ - `days` (optional): Number of days to look ahead (default: value of `--ppolicy-warn-days` or 14)
175
+
176
+ **Example:**
177
+
178
+ ```bash
179
+ curl "http://localhost:8081/api/v1/password-policy/expiring-soon?days=7"
180
+ ```
181
+
182
+ **Response (200):**
183
+
184
+ ```json
185
+ {
186
+ "warningDays": 7,
187
+ "users": [
188
+ {
189
+ "dn": "uid=alice,ou=users,dc=example,dc=com",
190
+ "uid": "alice",
191
+ "displayName": "Alice Smith",
192
+ "mail": "alice@example.com",
193
+ "expiresAt": "2024-02-20T14:30:00.000Z",
194
+ "daysUntilExpiration": 3
195
+ },
196
+ {
197
+ "dn": "uid=bob,ou=users,dc=example,dc=com",
198
+ "uid": "bob",
199
+ "displayName": "Bob Jones",
200
+ "mail": "bob@example.com",
201
+ "expiresAt": "2024-02-22T09:15:00.000Z",
202
+ "daysUntilExpiration": 5
203
+ }
204
+ ]
205
+ }
206
+ ```
207
+
208
+ ### List Locked Accounts
209
+
210
+ ```http
211
+ GET /api/v1/password-policy/locked-accounts
212
+ ```
213
+
214
+ **Example:**
215
+
216
+ ```bash
217
+ curl "http://localhost:8081/api/v1/password-policy/locked-accounts"
218
+ ```
219
+
220
+ **Response (200):**
221
+
222
+ ```json
223
+ {
224
+ "accounts": [
225
+ {
226
+ "dn": "uid=charlie,ou=users,dc=example,dc=com",
227
+ "uid": "charlie",
228
+ "displayName": "Charlie Brown",
229
+ "mail": "charlie@example.com",
230
+ "lockedAt": "2024-02-15T08:45:00.000Z",
231
+ "failureCount": 5
232
+ }
233
+ ]
234
+ }
235
+ ```
236
+
237
+ ### Validate Password Complexity
238
+
239
+ Only available when `--ppolicy-validate-complexity true` is set.
240
+
241
+ ```http
242
+ POST /api/v1/password/validate
243
+ ```
244
+
245
+ **Request Body:**
246
+
247
+ ```json
248
+ {
249
+ "password": "MySecureP@ssw0rd!"
250
+ }
251
+ ```
252
+
253
+ **Response (200) - Valid:**
254
+
255
+ ```json
256
+ {
257
+ "valid": true,
258
+ "errors": []
259
+ }
260
+ ```
261
+
262
+ **Response (200) - Invalid:**
263
+
264
+ ```json
265
+ {
266
+ "valid": false,
267
+ "errors": [
268
+ "Minimum 12 characters required",
269
+ "At least one special character required"
270
+ ]
271
+ }
272
+ ```
273
+
274
+ **Response (400):**
275
+
276
+ ```json
277
+ {
278
+ "error": "password required"
279
+ }
280
+ ```
281
+
282
+ ## OpenLDAP ppolicy Attributes
283
+
284
+ The plugin reads these operational attributes from user entries:
285
+
286
+ | Attribute | Description |
287
+ | ---------------------- | --------------------------------------------------- |
288
+ | `pwdChangedTime` | When password was last changed |
289
+ | `pwdAccountLockedTime` | When account was locked (000001010000Z = permanent) |
290
+ | `pwdFailureTime` | Timestamps of failed authentication attempts |
291
+ | `pwdGraceUseTime` | Timestamps of grace logins used |
292
+ | `pwdReset` | TRUE if password was reset by admin (must change) |
293
+ | `pwdPolicySubentry` | DN of applied password policy |
294
+
295
+ And these configuration attributes from the ppolicy entry:
296
+
297
+ | Attribute | Description |
298
+ | -------------------- | ------------------------------------------- |
299
+ | `pwdMaxAge` | Password expiration time in seconds |
300
+ | `pwdMinAge` | Minimum password age in seconds |
301
+ | `pwdInHistory` | Number of passwords kept in history |
302
+ | `pwdCheckQuality` | Password quality check level (0-2) |
303
+ | `pwdMinLength` | Minimum password length |
304
+ | `pwdMaxFailure` | Failed attempts before lockout |
305
+ | `pwdLockout` | TRUE to enable account lockout |
306
+ | `pwdLockoutDuration` | Lockout duration in seconds (0 = permanent) |
307
+ | `pwdGraceAuthNLimit` | Grace logins after password expires |
308
+ | `pwdExpireWarning` | Warning period in seconds |
309
+ | `pwdMustChange` | TRUE to require password change after reset |
310
+ | `pwdAllowUserChange` | TRUE to allow users to change password |
311
+
312
+ ## Examples
313
+
314
+ ### Example 1: Monitor Expiring Passwords (cron job)
315
+
316
+ ```bash
317
+ #!/bin/bash
318
+ # Send alerts for passwords expiring within 7 days
319
+
320
+ USERS=$(curl -s "http://localhost:8081/api/v1/password-policy/expiring-soon?days=7" | jq -r '.users[] | "\(.mail) expires in \(.daysUntilExpiration) days"')
321
+
322
+ if [ -n "$USERS" ]; then
323
+ echo "$USERS" | mail -s "Password Expiration Warning" admin@example.com
324
+ fi
325
+ ```
326
+
327
+ ### Example 2: Unlock Multiple Accounts
328
+
329
+ ```bash
330
+ #!/bin/bash
331
+ # Unlock all currently locked accounts
332
+
333
+ LOCKED=$(curl -s "http://localhost:8081/api/v1/password-policy/locked-accounts" | jq -r '.accounts[].uid')
334
+
335
+ for uid in $LOCKED; do
336
+ echo "Unlocking $uid..."
337
+ curl -X POST "http://localhost:8081/api/v1/users/$uid/unlock"
338
+ done
339
+ ```
340
+
341
+ ### Example 3: Pre-validate Password in UI
342
+
343
+ ```javascript
344
+ async function validatePassword(password) {
345
+ const response = await fetch('/api/v1/password/validate', {
346
+ method: 'POST',
347
+ headers: { 'Content-Type': 'application/json' },
348
+ body: JSON.stringify({ password }),
349
+ });
350
+
351
+ const result = await response.json();
352
+
353
+ if (!result.valid) {
354
+ showErrors(result.errors);
355
+ return false;
356
+ }
357
+ return true;
358
+ }
359
+ ```
360
+
361
+ ## Troubleshooting
362
+
363
+ ### Empty Policy Response
364
+
365
+ **Problem:** `GET /password-policy` returns `{}`
366
+
367
+ **Solution:** The ppolicy overlay may not be configured in OpenLDAP, or the ppolicy entry doesn't exist. Configure the overlay and create a pwdPolicy entry:
368
+
369
+ ```ldif
370
+ dn: cn=default,ou=policies,dc=example,dc=com
371
+ objectClass: pwdPolicy
372
+ objectClass: person
373
+ cn: default
374
+ sn: default
375
+ pwdAttribute: userPassword
376
+ pwdMaxAge: 7776000
377
+ pwdLockout: TRUE
378
+ pwdMaxFailure: 5
379
+ pwdLockoutDuration: 900
380
+ ```
381
+
382
+ ### User Not Found
383
+
384
+ **Problem:** `GET /users/john.doe/password-status` returns 404
385
+
386
+ **Solution:** Check that:
387
+
388
+ 1. The user exists in LDAP
389
+ 2. `--ldap-users-base` is correctly configured
390
+ 3. The uid matches exactly (case-sensitive)
391
+
392
+ ### Cannot Read Operational Attributes
393
+
394
+ **Problem:** Password status shows null for all dates
395
+
396
+ **Solution:** Ensure the LDAP bind user has permissions to read operational attributes. These require special ACLs in OpenLDAP:
397
+
398
+ ```
399
+ access to attrs=pwdChangedTime,pwdAccountLockedTime,pwdFailureTime,pwdGraceUseTime,pwdReset
400
+ by dn="cn=admin,dc=example,dc=com" read
401
+ by self read
402
+ by * none
403
+ ```
404
+
405
+ ## Config API Integration
406
+
407
+ The plugin automatically exports its configuration via the `configApi` plugin. When loaded with `core/ldap/passwordPolicy`, the configuration is available at `GET /api/v1/config`:
408
+
409
+ ```json
410
+ {
411
+ "apiPrefix": "/api",
412
+ "ldapBase": "dc=example,dc=com",
413
+ "features": {
414
+ "ldapPasswordPolicy": {
415
+ "name": "ldapPasswordPolicy",
416
+ "enabled": true,
417
+ "endpoints": {
418
+ "getPolicy": "/api/v1/password-policy",
419
+ "getUserStatus": "/api/v1/users/:id/password-status",
420
+ "unlockUser": "/api/v1/users/:id/unlock",
421
+ "getExpiringSoon": "/api/v1/password-policy/expiring-soon",
422
+ "getLockedAccounts": "/api/v1/password-policy/locked-accounts",
423
+ "validatePassword": "/api/v1/password/validate"
424
+ },
425
+ "config": {
426
+ "warnDays": 14,
427
+ "validateComplexity": true,
428
+ "complexityRules": {
429
+ "minLength": 12,
430
+ "requireUppercase": true,
431
+ "requireLowercase": true,
432
+ "requireDigit": true,
433
+ "requireSpecial": true
434
+ }
435
+ },
436
+ "ldapPolicy": {
437
+ "dn": "cn=default,ou=policies,dc=example,dc=com",
438
+ "pwdMaxAge": 7776000,
439
+ "pwdMinLength": 12,
440
+ "pwdLockout": true
441
+ }
442
+ }
443
+ }
444
+ }
445
+ ```
446
+
447
+ **Note:** The `ldapPolicy` field contains the LDAP ppolicy configuration and is populated after the first request to any passwordPolicy endpoint.
448
+
449
+ ## See Also
450
+
451
+ - [OpenLDAP ppolicy overlay](https://www.openldap.org/doc/admin24/overlays.html#Password%20Policies)
452
+ - [LDAP Users Plugin](./flat-generic.md) - For managing user entries
453
+ - [Rate Limiting](../auth/rate-limit.md) - For API rate limiting (separate from LDAP lockout)
454
+ - [Config API](../../configuration.md) - For API configuration discovery
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ldap-rest",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Light directory manager",
5
5
  "keywords": [
6
6
  "Identity-Manager"
@@ -32,6 +32,10 @@
32
32
  "import": "./dist/lib/expressFormatedResponses.js",
33
33
  "types": "./dist/src/lib/expressFormatedResponses.d.ts"
34
34
  },
35
+ "./ldapactions": {
36
+ "import": "./dist/lib/ldapActions.js",
37
+ "types": "./dist/src/lib/ldapActions.d.ts"
38
+ },
35
39
  "./plugin-auth-authzlinid1": {
36
40
  "import": "./dist/plugins/auth/authzLinid1.js",
37
41
  "types": "./dist/src/plugins/auth/authzLinid1.d.ts"
@@ -108,6 +112,10 @@
108
112
  "import": "./dist/plugins/ldap/organization.js",
109
113
  "types": "./dist/src/plugins/ldap/organization.d.ts"
110
114
  },
115
+ "./plugin-ldap-passwordpolicy": {
116
+ "import": "./dist/plugins/ldap/passwordPolicy.js",
117
+ "types": "./dist/src/plugins/ldap/passwordPolicy.d.ts"
118
+ },
111
119
  "./plugin-ldap-trash": {
112
120
  "import": "./dist/plugins/ldap/trash.js",
113
121
  "types": "./dist/src/plugins/ldap/trash.d.ts"
@@ -364,18 +372,50 @@
364
372
  "import": "./static/schemas/obm/users.json",
365
373
  "require": "./static/schemas/obm/users.json"
366
374
  },
375
+ "./schema-standard-automountmaps": {
376
+ "import": "./static/schemas/standard/automountMaps.json",
377
+ "require": "./static/schemas/standard/automountMaps.json"
378
+ },
367
379
  "./schema-standard-devices": {
368
380
  "import": "./static/schemas/standard/devices.json",
369
381
  "require": "./static/schemas/standard/devices.json"
370
382
  },
383
+ "./schema-standard-dhcphosts": {
384
+ "import": "./static/schemas/standard/dhcpHosts.json",
385
+ "require": "./static/schemas/standard/dhcpHosts.json"
386
+ },
387
+ "./schema-standard-dnsrecords": {
388
+ "import": "./static/schemas/standard/dnsRecords.json",
389
+ "require": "./static/schemas/standard/dnsRecords.json"
390
+ },
371
391
  "./schema-standard-groups": {
372
392
  "import": "./static/schemas/standard/groups.json",
373
393
  "require": "./static/schemas/standard/groups.json"
374
394
  },
395
+ "./schema-standard-netgroups": {
396
+ "import": "./static/schemas/standard/netgroups.json",
397
+ "require": "./static/schemas/standard/netgroups.json"
398
+ },
375
399
  "./schema-standard-organizations": {
376
400
  "import": "./static/schemas/standard/organizations.json",
377
401
  "require": "./static/schemas/standard/organizations.json"
378
402
  },
403
+ "./schema-standard-posixaccounts": {
404
+ "import": "./static/schemas/standard/posixAccounts.json",
405
+ "require": "./static/schemas/standard/posixAccounts.json"
406
+ },
407
+ "./schema-standard-posixgroups": {
408
+ "import": "./static/schemas/standard/posixGroups.json",
409
+ "require": "./static/schemas/standard/posixGroups.json"
410
+ },
411
+ "./schema-standard-sshpublickeys": {
412
+ "import": "./static/schemas/standard/sshPublicKeys.json",
413
+ "require": "./static/schemas/standard/sshPublicKeys.json"
414
+ },
415
+ "./schema-standard-sudorules": {
416
+ "import": "./static/schemas/standard/sudoRules.json",
417
+ "require": "./static/schemas/standard/sudoRules.json"
418
+ },
379
419
  "./schema-standard-users": {
380
420
  "import": "./static/schemas/standard/users.json",
381
421
  "require": "./static/schemas/standard/users.json"
@@ -422,6 +462,7 @@
422
462
  }
423
463
  },
424
464
  "main": "dist/bin/index.mjs",
465
+ "types": "./dist/src/bin/index.d.ts",
425
466
  "bin": {
426
467
  "cleanup-external-users": "bin/cleanup-external-users.mjs",
427
468
  "ldap-rest": "bin/index.mjs",
@@ -463,6 +504,7 @@
463
504
  "test:one": "mocha --config .mocharc-one.json"
464
505
  },
465
506
  "overrides": {
507
+ "diff": ">=8.0.3",
466
508
  "glob": ">=11.0.4"
467
509
  },
468
510
  "dependencies": {
@@ -0,0 +1,37 @@
1
+ {
2
+ "entity": {
3
+ "name": "automount",
4
+ "mainAttribute": "automountKey",
5
+ "objectClass": ["top", "automount"],
6
+ "singularName": "automount",
7
+ "pluralName": "automounts",
8
+ "base": "ou=automount,__ldap_base__"
9
+ },
10
+ "strict": true,
11
+ "attributes": {
12
+ "objectClass": {
13
+ "type": "array",
14
+ "items": {
15
+ "type": "string",
16
+ "test": "^[a-zA-Z][a-zA-Z0-9-]*$"
17
+ },
18
+ "default": ["top", "automount"],
19
+ "required": true,
20
+ "fixed": true
21
+ },
22
+ "automountKey": {
23
+ "type": "string",
24
+ "test": "^[a-zA-Z0-9/_.*-]+$",
25
+ "required": true,
26
+ "role": "identifier"
27
+ },
28
+ "automountInformation": {
29
+ "type": "string",
30
+ "required": true
31
+ },
32
+ "description": {
33
+ "type": "string",
34
+ "required": false
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "entity": {
3
+ "name": "dhcpHost",
4
+ "mainAttribute": "cn",
5
+ "objectClass": ["top", "dhcpHost"],
6
+ "singularName": "dhcpHost",
7
+ "pluralName": "dhcpHosts",
8
+ "base": "ou=dhcp,__ldap_base__"
9
+ },
10
+ "strict": false,
11
+ "attributes": {
12
+ "objectClass": {
13
+ "type": "array",
14
+ "items": {
15
+ "type": "string",
16
+ "test": "^[a-zA-Z][a-zA-Z0-9-]*$"
17
+ },
18
+ "default": ["top", "dhcpHost"],
19
+ "required": true,
20
+ "fixed": true
21
+ },
22
+ "cn": {
23
+ "type": "string",
24
+ "test": "^[a-zA-Z0-9._-]{1,255}$",
25
+ "required": true,
26
+ "role": "identifier"
27
+ },
28
+ "dhcpHWAddress": {
29
+ "type": "string",
30
+ "test": "^ethernet ([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$",
31
+ "required": true
32
+ },
33
+ "dhcpStatements": {
34
+ "type": "array",
35
+ "items": {
36
+ "type": "string"
37
+ },
38
+ "required": false
39
+ },
40
+ "dhcpOption": {
41
+ "type": "array",
42
+ "items": {
43
+ "type": "string"
44
+ },
45
+ "required": false
46
+ },
47
+ "dhcpComments": {
48
+ "type": "string",
49
+ "required": false
50
+ }
51
+ }
52
+ }