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.
Files changed (115) hide show
  1. package/bin/index.mjs +8 -7
  2. package/dist/abstract/ldapFlat.js +1 -1
  3. package/dist/abstract/plugin.js +1 -1
  4. package/dist/abstract/twakePlugin.js +1 -1
  5. package/dist/bin/index.js +1 -1
  6. package/dist/config/args.js +1 -1
  7. package/dist/config/schema.js +1 -1
  8. package/dist/lib/changeContext.js +1 -1
  9. package/dist/lib/entitySchema/validator.js +1 -0
  10. package/dist/lib/expressFormatedResponses.js +1 -1
  11. package/dist/lib/health.js +1 -0
  12. package/dist/lib/instanceProviders.js +1 -1
  13. package/dist/lib/ldapActions.js +1 -1
  14. package/dist/lib/ldapSchema.js +1 -1
  15. package/dist/lib/metrics.js +1 -0
  16. package/dist/lib/requestContext.js +1 -1
  17. package/dist/lib/shutdown.js +1 -0
  18. package/dist/lib/staticFiles.js +1 -0
  19. package/dist/lib/storage/store.js +1 -1
  20. package/dist/lib/utils.js +1 -1
  21. package/dist/logger/winston.js +1 -1
  22. package/dist/plugins/auth/crowdsec.js +1 -1
  23. package/dist/plugins/auth/openidconnect.js +1 -1
  24. package/dist/plugins/demo/helloworld.js +1 -1
  25. package/dist/plugins/ldap/bulkImport.js +1 -1
  26. package/dist/plugins/ldap/departmentSync.js +1 -1
  27. package/dist/plugins/ldap/flatGeneric.js +1 -1
  28. package/dist/plugins/ldap/groups.js +1 -1
  29. package/dist/plugins/ldap/organizations.js +1 -1
  30. package/dist/plugins/metrics.js +1 -0
  31. package/dist/plugins/rabbitmq.js +1 -1
  32. package/dist/plugins/scim/baseResolver.js +1 -1
  33. package/dist/plugins/scim/bulk.js +1 -1
  34. package/dist/plugins/scim/errors.js +1 -1
  35. package/dist/plugins/scim/filter.js +1 -1
  36. package/dist/plugins/scim/groups.js +1 -1
  37. package/dist/plugins/scim/mapping.js +1 -1
  38. package/dist/plugins/scim/patch.js +1 -1
  39. package/dist/plugins/scim/projection.js +1 -1
  40. package/dist/plugins/scim/scim.js +1 -1
  41. package/dist/plugins/scim/users.js +1 -1
  42. package/dist/plugins/static.js +1 -1
  43. package/dist/plugins/storage/file.js +1 -1
  44. package/dist/plugins/storage/postgres.js +1 -1
  45. package/dist/plugins/storage/valkey.js +1 -1
  46. package/dist/plugins/storage.js +1 -1
  47. package/dist/plugins/twake/calendar.js +1 -1
  48. package/dist/plugins/twake/clouderyProvision.js +1 -1
  49. package/dist/plugins/twake/cozyProvision.js +1 -1
  50. package/dist/plugins/twake/drive.js +1 -1
  51. package/dist/plugins/twake/groups.js +1 -1
  52. package/dist/plugins/twake/james.js +1 -1
  53. package/dist/plugins/twake/migrationProxy.js +1 -0
  54. package/dist/plugins/twake/spaces.js +1 -1
  55. package/dist/plugins/weblogs.js +1 -1
  56. package/dist/src/abstract/ldapFlat.d.ts +3 -13
  57. package/dist/src/abstract/plugin.d.ts +20 -0
  58. package/dist/src/abstract/twakePlugin.d.ts +23 -1
  59. package/dist/src/bin/index.d.ts +46 -1
  60. package/dist/src/config/args.d.ts +12 -1
  61. package/dist/src/lib/changeContext.d.ts +11 -0
  62. package/dist/src/lib/entitySchema/validator.d.ts +64 -0
  63. package/dist/src/lib/expressFormatedResponses.d.ts +19 -0
  64. package/dist/src/lib/health.d.ts +63 -0
  65. package/dist/src/lib/instanceProviders.d.ts +4 -0
  66. package/dist/src/lib/ldapActions.d.ts +32 -2
  67. package/dist/src/lib/metrics.d.ts +44 -0
  68. package/dist/src/lib/requestContext.d.ts +40 -0
  69. package/dist/src/lib/shutdown.d.ts +20 -0
  70. package/dist/src/lib/staticFiles.d.ts +33 -0
  71. package/dist/src/lib/storage/store.d.ts +8 -0
  72. package/dist/src/lib/utils.d.ts +15 -0
  73. package/dist/src/logger/winston.d.ts +4 -0
  74. package/dist/src/plugins/ldap/groups.d.ts +18 -3
  75. package/dist/src/plugins/ldap/organizations.d.ts +3 -5
  76. package/dist/src/plugins/metrics.d.ts +17 -0
  77. package/dist/src/plugins/rabbitmq.d.ts +37 -1
  78. package/dist/src/plugins/scim/errors.d.ts +2 -0
  79. package/dist/src/plugins/scim/types.d.ts +2 -1
  80. package/dist/src/plugins/static.d.ts +12 -0
  81. package/dist/src/plugins/storage/file.d.ts +4 -0
  82. package/dist/src/plugins/storage/postgres.d.ts +7 -14
  83. package/dist/src/plugins/storage/valkey.d.ts +2 -15
  84. package/dist/src/plugins/storage.d.ts +8 -0
  85. package/dist/src/plugins/twake/drive.d.ts +8 -0
  86. package/dist/src/plugins/twake/groups.d.ts +1 -2
  87. package/dist/src/plugins/twake/james.d.ts +2 -0
  88. package/dist/src/plugins/twake/migrationProxy.d.ts +71 -0
  89. package/docs/client-development/api/reference.md +5 -9
  90. package/docs/client-development/api/rest-api.md +4 -10
  91. package/docs/client-development/schemas/README.md +60 -21
  92. package/docs/plugin-development/README.md +15 -13
  93. package/docs/plugin-development/dependencies.md +8 -0
  94. package/docs/plugin-development/hooks.md +1 -1
  95. package/docs/usage/README.md +1 -0
  96. package/docs/usage/configuration.md +62 -24
  97. package/docs/usage/operations.md +193 -0
  98. package/docs/usage/plugins/README.md +2 -0
  99. package/docs/usage/plugins/auth/authz-per-branch.md +2 -1
  100. package/docs/usage/plugins/integrations/README.md +8 -4
  101. package/docs/usage/plugins/integrations/groups.md +2 -2
  102. package/docs/usage/plugins/integrations/mail-migration.md +102 -0
  103. package/docs/usage/plugins/integrations/scim.md +16 -3
  104. package/docs/usage/plugins/integrations/spaces.md +1 -1
  105. package/docs/usage/plugins/ldap/bulk-import.md +3 -1
  106. package/docs/usage/plugins/ldap/flat-generic.md +5 -2
  107. package/docs/usage/plugins/ldap/on-change.md +2 -1
  108. package/docs/usage/plugins/ldap/organizations.md +7 -7
  109. package/docs/usage/plugins/utilities/metrics.md +21 -0
  110. package/docs/usage/plugins/utilities/static.md +16 -8
  111. package/docs/usage/plugins/utilities/storage.md +7 -0
  112. package/docs/usage/plugins/utilities/weblogs.md +51 -30
  113. package/docs/usage/upgrading.md +84 -0
  114. package/package.json +11 -2
  115. package/static/schemas/example/README.md +1 -0
@@ -39,17 +39,20 @@ All LDAP-Rest configuration options.
39
39
  - [core/auth/trustedProxy](#coreauthtrustedproxy)
40
40
  - [Twake Integration Plugins](#twake-integration-plugins)
41
41
  - [core/twake/james](#coretwakejames)
42
+ - [core/twake/migrationProxy](#coretwakemigrationproxy)
42
43
  - [core/twake/calendar](#coretwakecalendar)
43
44
  - [core/twake/applicativeAccounts](#coretwakeapplicativeaccounts)
44
45
  - [core/twake/appAccountsConsistency](#coretwakeappaccountsconsistency)
45
46
  - [Utility Plugins](#utility-plugins)
46
47
  - [core/static](#corestatic)
47
48
  - [core/weblogs](#coreweblogs)
49
+ - [core/metrics](#coremetrics)
48
50
  - [core/storage](#corestorage)
49
51
  - [core/configApi](#coreconfigapi)
50
52
  - [Configuration File](#configuration-file)
51
53
  - [LDAP Failover](#ldap-failover)
52
54
  - [Log Levels (`--log-level`)](#log-levels---log-level)
55
+ - [Logger (`--logger`)](#logger---logger)
53
56
 
54
57
  ## Environment Variables
55
58
 
@@ -148,15 +151,18 @@ to keep the default.
148
151
 
149
152
  ## General Options
150
153
 
151
- | CLI | Plural | Env | Default | Description |
152
- | ---------------- | ---------------- | ----------------- | ---------------- | ------------------------------------------- |
153
- | `--port` | | `DM_PORT` | `8081` | Listen port |
154
- | `--plugin` | `--plugins` | `DM_PLUGINS` | `[]` | Plugins to load |
155
- | `--log-level` | | `DM_LOG_LEVEL` | `notice` | Log level: error, warn, notice, info, debug |
156
- | `--logger` | | `DM_LOGGER` | `console` | Logger type |
157
- | `--api-prefix` | | `DM_API_PREFIX` | `/api` | API URL prefix |
158
- | `--mail-domain` | `--mail-domains` | `DM_MAIL_DOMAIN` | `[]` | Mail domains |
159
- | `--schemas-path` | | `DM_SCHEMAS_PATH` | `static/schemas` | Path to JSON schemas |
154
+ | CLI | Plural | Env | Default | Description |
155
+ | -------------------- | ---------------- | --------------------- | ---------------- | ------------------------------------------------------------------------------------------------- |
156
+ | `--port` | | `DM_PORT` | `8081` | Listen port |
157
+ | `--shutdown-timeout` | | `DM_SHUTDOWN_TIMEOUT` | `20000` | How long a stop may take, in ms, see [shutdown](operations.md#shutdown) |
158
+ | `--plugin` | `--plugins` | `DM_PLUGINS` | `[]` | Plugins to load |
159
+ | `--log-level` | | `DM_LOG_LEVEL` | `notice` | Log level: error, warn, notice, info, debug |
160
+ | `--logger` | | `DM_LOGGER` | `console` | `console` or `file`, see [Logger](#logger---logger) |
161
+ | `--log-file` | | `DM_LOG_FILE` | `error.log` | File `--logger file` writes to |
162
+ | `--debug` | | `DM_DEBUG` | `false` | Name the failure in the 5xx answers of the error middleware and `serverError`; not for production |
163
+ | `--api-prefix` | | `DM_API_PREFIX` | `/api` | API URL prefix |
164
+ | `--mail-domain` | `--mail-domains` | `DM_MAIL_DOMAIN` | `[]` | Mail domains |
165
+ | `--schemas-path` | | `DM_SCHEMAS_PATH` | `static/schemas` | Path to JSON schemas |
160
166
 
161
167
  A schema file named by an option may extend another one, shipped or not, and
162
168
  hold only the differences:
@@ -167,8 +173,8 @@ hold only the differences:
167
173
  | CLI | Plural | Env | Default | Description |
168
174
  | ---------------------------- | ---------------- | ------------------------ | ---------------------------------- | ------------------------------------------- |
169
175
  | `--ldap-url` | `--ldap-urls` | `DM_LDAP_URL` | `ldap://localhost` | LDAP server URL(s) |
170
- | `--ldap-dn` | | `DM_LDAP_DN` | `cn=admin,dc=example,dc=com` | Bind DN |
171
- | `--ldap-pwd` | | `DM_LDAP_PWD` | `admin` | Password |
176
+ | `--ldap-dn` | | `DM_LDAP_DN` | | Bind DN (required) |
177
+ | `--ldap-pwd` | | `DM_LDAP_PWD` | | Password (required) |
172
178
  | `--ldap-base` | | `DM_LDAP_BASE` | | Base DN for searches (required) |
173
179
  | `--ldap-user-main-attribute` | | `DM_LDAP_USER_ATTRIBUTE` | `uid` | User identifier attribute |
174
180
  | `--ldap-cache-max` | | `DM_LDAP_CACHE_MAX` | `1000` | Max cache entries |
@@ -493,6 +499,19 @@ whichever of the two names it selects. A value other than `req.user` or
493
499
 
494
500
  The wait before each retry grows with the attempt, so the total wait is `--james-retry-delay` × n(n−1)/2 for n attempts: 10 s with the defaults, 190 s with 20 attempts.
495
501
 
502
+ #### `core/twake/migrationProxy`
503
+
504
+ Keeps the migrated-users list of the Twake Mail migration proxy in step with the accounts, [details](plugins/integrations/mail-migration.md).
505
+
506
+ | CLI | Plural | Env | Default | Description |
507
+ | ---------------------------------- | ---------------------------- | ----------------------------------- | ------- | ---------------------------------------------------------------- |
508
+ | `--migration-proxy-url` | | `DM_MIGRATION_PROXY_URL` | | Webadmin URL of the proxy (required) |
509
+ | `--migration-proxy-token` | | `DM_MIGRATION_PROXY_TOKEN` | | Bearer token |
510
+ | `--migration-proxy-branch` | `--migration-proxy-branches` | `DM_MIGRATION_PROXY_BRANCHES` | `[]` | Branches whose entries are followed, `;`-separated (required) |
511
+ | `--migration-proxy-concurrency` | | `DM_MIGRATION_PROXY_CONCURRENCY` | `10` | Calls to the proxy at once |
512
+ | `--migration-proxy-retry-attempts` | | `DM_MIGRATION_PROXY_RETRY_ATTEMPTS` | `5` | Attempts in all of a call the proxy answers 500, 502, 503 or 504 |
513
+ | `--migration-proxy-retry-delay` | | `DM_MIGRATION_PROXY_RETRY_DELAY` | `1000` | Base retry delay (ms), growing with the attempt |
514
+
496
515
  #### `core/twake/calendar`
497
516
 
498
517
  Formerly `core/twake/calendarResources`, still accepted as a deprecated alias.
@@ -624,8 +643,8 @@ Extends `core/ldap/groups`, whose options it reads too.
624
643
 
625
644
  | CLI | Env | Default | Description |
626
645
  | --------------------------------------------- | ---------------------------------------------- | ------------------ | ----------------------------------------------------- |
627
- | `--twake-group-base` | `DM_TWAKE_GROUP_BASE` | | Group branch of an organization, with `{org}` |
628
- | `--twake-group-user-base` | `DM_TWAKE_GROUP_USER_BASE` | | User branch of an organization, with `{org}` |
646
+ | `--twake-group-base` | `DM_TWAKE_GROUP_BASE` | | Group branch of an organization, holding `{org}` once |
647
+ | `--twake-group-user-base` | `DM_TWAKE_GROUP_USER_BASE` | | User branch of an organization, holding `{org}` once |
629
648
  | `--twake-group-organization-dn` | `DM_TWAKE_GROUP_ORGANIZATION_DN` | | Organization entry, with `{org}` (empty: not checked) |
630
649
  | `--twake-group-organization-status-attribute` | `DM_TWAKE_GROUP_ORGANIZATION_STATUS_ATTRIBUTE` | `twakeOrgStatus` | Organization status attribute |
631
650
  | `--twake-group-organization-deleted-value` | `DM_TWAKE_GROUP_ORGANIZATION_DELETED_VALUE` | `deleted` | Status of a deleted organization |
@@ -644,17 +663,17 @@ organization entry, and `core/ldap/onChange`; with `--rabbitmq-url`, it
644
663
  requires `core/rabbitmq` and reads the organization domain from
645
664
  `--twake-instance-organization-domain-attribute`.
646
665
 
647
- | CLI | Env | Default | Description |
648
- | -------------------------------------- | --------------------------------------- | -------------------------------------- | -------------------------------------------------- |
649
- | `--twake-space-base` | `DM_TWAKE_SPACE_BASE` | | Space branch of an organization, with `{org}` |
650
- | `--twake-space-class` | `DM_TWAKE_SPACE_CLASS` | `top,twakeSpace` | Object classes of a space |
651
- | `--twake-space-display-name-attribute` | `DM_TWAKE_SPACE_DISPLAY_NAME_ATTRIBUTE` | `twakeDisplayName` | Space name attribute |
652
- | `--twake-space-admin-attribute` | `DM_TWAKE_SPACE_ADMIN_ATTRIBUTE` | `twakeSpaceAdmin` | DNs holding the admin role |
653
- | `--twake-space-editor-attribute` | `DM_TWAKE_SPACE_EDITOR_ATTRIBUTE` | `twakeSpaceEditor` | DNs holding the editor role |
654
- | `--twake-space-viewer-attribute` | `DM_TWAKE_SPACE_VIEWER_ATTRIBUTE` | `twakeSpaceViewer` | DNs holding the viewer role |
655
- | `--twake-space-user-role-attribute` | `DM_TWAKE_SPACE_USER_ROLE_ATTRIBUTE` | | User attribute holding `<space id>:<role>`, if set |
656
- | `--twake-space-exchange` | `DM_TWAKE_SPACE_EXCHANGE` | `space` | Topic exchange of the space events |
657
- | `--twake-space-sync-queue` | `DM_TWAKE_SPACE_SYNC_QUEUE` | `twake.space.sync.requested.ldap-rest` | Queue of the space sync requests |
666
+ | CLI | Env | Default | Description |
667
+ | -------------------------------------- | --------------------------------------- | -------------------------------------- | ----------------------------------------------------- |
668
+ | `--twake-space-base` | `DM_TWAKE_SPACE_BASE` | | Space branch of an organization, holding `{org}` once |
669
+ | `--twake-space-class` | `DM_TWAKE_SPACE_CLASS` | `top,twakeSpace` | Object classes of a space |
670
+ | `--twake-space-display-name-attribute` | `DM_TWAKE_SPACE_DISPLAY_NAME_ATTRIBUTE` | `twakeDisplayName` | Space name attribute |
671
+ | `--twake-space-admin-attribute` | `DM_TWAKE_SPACE_ADMIN_ATTRIBUTE` | `twakeSpaceAdmin` | DNs holding the admin role |
672
+ | `--twake-space-editor-attribute` | `DM_TWAKE_SPACE_EDITOR_ATTRIBUTE` | `twakeSpaceEditor` | DNs holding the editor role |
673
+ | `--twake-space-viewer-attribute` | `DM_TWAKE_SPACE_VIEWER_ATTRIBUTE` | `twakeSpaceViewer` | DNs holding the viewer role |
674
+ | `--twake-space-user-role-attribute` | `DM_TWAKE_SPACE_USER_ROLE_ATTRIBUTE` | | User attribute holding `<space id>:<role>`, if set |
675
+ | `--twake-space-exchange` | `DM_TWAKE_SPACE_EXCHANGE` | `space` | Topic exchange of the space events |
676
+ | `--twake-space-sync-queue` | `DM_TWAKE_SPACE_SYNC_QUEUE` | `twake.space.sync.requested.ldap-rest` | Queue of the space sync requests |
658
677
 
659
678
  See [spaces](plugins/integrations/spaces.md).
660
679
 
@@ -675,6 +694,15 @@ See [security headers](plugins/utilities/static.md#security-headers).
675
694
 
676
695
  Web access logging plugin. No configuration options - just add the plugin to enable access logs.
677
696
 
697
+ #### `core/metrics`
698
+
699
+ Prometheus metrics on a port of their own, see
700
+ [metrics](operations.md#metrics).
701
+
702
+ | CLI | Env | Default | Description |
703
+ | ---------------- | ----------------- | ------- | ---------------------------------------------------------- |
704
+ | `--metrics-port` | `DM_METRICS_PORT` | `9464` | Port `GET /metrics` is served on; keep it off the internet |
705
+
678
706
  #### `core/storage`
679
707
 
680
708
  Keyed storage with a deadline, for the plugins that need to keep something —
@@ -743,3 +771,13 @@ The system will:
743
771
  | `debug` | Everything, including debug output |
744
772
 
745
773
  The `notice` level is ideal for production as it shows web access logs without flooding with general info messages.
774
+
775
+ ## Logger _(`--logger`)_
776
+
777
+ | Logger | Output |
778
+ | --------- | ---------------------------------------------------------------------------------------------------- |
779
+ | `console` | One JSON object per line on standard output (errors and warnings on standard error) |
780
+ | `file` | Text lines appended to `--log-file` (`DM_LOG_FILE`), `error.log` in the working directory by default |
781
+
782
+ An empty value is `console`. Any other value, or `file` with an empty
783
+ `--log-file`, stops the server at startup.
@@ -0,0 +1,193 @@
1
+ # Operations
2
+
3
+ ## Health and readiness
4
+
5
+ Two routes answer outside the API prefix, without authentication, and are
6
+ never cached:
7
+
8
+ | Route | Answers |
9
+ | -------------- | ------------------------------------------------------------- |
10
+ | `GET /healthz` | `200 {"status":"ok"}` as long as the process serves |
11
+ | `GET /readyz` | `200` when the server can serve, `503` when it cannot (below) |
12
+
13
+ `/readyz` answers a JSON body:
14
+
15
+ | `status` | Code | Meaning |
16
+ | ------------- | ---- | ------------------------------------------------------------------------------------------------------ |
17
+ | `starting` | 503 | The plugins are still loading (a host serving before `ready`; the shipped binary opens the port after) |
18
+ | `stopping` | 503 | The server was told to stop and finishes what is in flight; a new connection is refused |
19
+ | `unavailable` | 503 | The directory does not answer a read of its root DSE in 2 s (`ldap: failed`) |
20
+ | `ok` | 200 | Everything answers |
21
+ | `degraded` | 200 | The directory answers, a plugin reports a resource of its own down |
22
+
23
+ ```json
24
+ { "status": "degraded", "ldap": "ok", "plugins": { "rabbitmq": "degraded" } }
25
+ ```
26
+
27
+ `plugins` lists the plugins that report their health; `failed` means the
28
+ report threw or did not answer in time. Only the directory takes a replica
29
+ out of rotation: a broken outbound service is visible in the body, and the
30
+ server keeps serving what does not need it.
31
+
32
+ The directory and the plugins are asked at once, so the answer takes 2 s at
33
+ most. The probe reads the root DSE, which any bound account may: a wrong
34
+ `--ldap-base` is not what it reports. The directory is probed on a connection of its own, bound as the
35
+ server, outside the connection pool and the search cache. One answer serves
36
+ 5 seconds whatever the number of probes; the probes arriving meanwhile
37
+ share it.
38
+
39
+ Both routes are served before the authentication and the rate limiting,
40
+ and `/readyz` names the loaded plugins that report: on an ingress exposing
41
+ the server to the internet, keep the two paths to the cluster.
42
+
43
+ The Helm chart probes both routes over HTTP (`livenessProbe` and
44
+ `readinessProbe` in `values.yaml`).
45
+
46
+ ### What the shipped plugins report
47
+
48
+ | Plugin | `degraded` when |
49
+ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
50
+ | `storage` | the backend refuses a `SELECT 1` or a `PING`, or the `file` backend's directory is not a directory this user may write in; the `ldap` backend has no answer of its own, the directory check above covers it |
51
+ | `rabbitmq` | a broker is configured and no connection to it stands: until the first publish or subscription opens one, and while the client reconnects |
52
+ | `james` | James answers its `GET /healthcheck` with 503, one of its own checks unhealthy, or not within 1.5 s; a James whose checks are only degraded answers 200, so `ok` here |
53
+ | `migrationProxy` | the migration proxy answers its `GET /healthcheck` with an error, or not within 1.5 s |
54
+
55
+ A check that leaves the probe to time out reads as `failed`.
56
+
57
+ ### For plugin authors
58
+
59
+ A plugin reports its own resources with an optional `health()` method
60
+ returning `ok` or `degraded`, called at most once every 5 seconds; it must
61
+ answer within 2 s and never throw for an ordinary outage. The probe stops
62
+ waiting at 2 s but cancels nothing: a plugin bounds its own call, as
63
+ `james` does with its 1.5 s.
64
+
65
+ ## Request id
66
+
67
+ Every response the API sends carries an `X-Request-Id` header: an
68
+ identifier the server generates for the request, the same one its writes
69
+ hand to the plugins and its log lines carry. An `X-Request-Id` the client sends is not taken: a
70
+ caller could otherwise make the writes of two requests look like one.
71
+
72
+ A proxy or an ingress generating an identifier of its own does not pass
73
+ it on either: log the answer's `X-Request-Id` there to match its lines with
74
+ the server's.
75
+
76
+ Every line logged while a request is served carries `requestId`, the lines
77
+ of the hooks it ran included, as a field of the JSON console output and as
78
+ `[<id>]` after the level in the log file. The line of `core/weblogs` carries
79
+ it too. A line without one was written outside any request: at startup, by
80
+ a scheduled task, by a message consumer, by a connection the server keeps.
81
+ Work shared between requests, a schema load or a readiness answer, logs
82
+ under the request that started it.
83
+
84
+ A line a hook writes carries `plugin`, the plugin that registered the hook,
85
+ and `op`, the hook's name, the `Hook error` line of a hook that threw
86
+ included: in the JSON console output as fields, in the log file as
87
+ `[<plugin> <op>]` after the request id. A line naming another `plugin`
88
+ itself keeps it, with no `op` from the hook, and reads `[<plugin>]` in the
89
+ file wherever it is written. These are the labels of
90
+ `ldap_rest_hook_failures_total`.
91
+
92
+ A line carrying an `error`, the reason of a failure, has it as a field of
93
+ the JSON console output and in parentheses after the message in the log
94
+ file; the file keeps no field other than these.
95
+
96
+ ## Metrics
97
+
98
+ `core/metrics` serves Prometheus metrics on `GET /metrics`, on
99
+ `--metrics-port` (9464): a port of its own, outside the authentication,
100
+ the rate limiting and the request id, to keep to the cluster or the host.
101
+
102
+ ```bash
103
+ --plugin core/metrics --metrics-port 9464
104
+ ```
105
+
106
+ | Metric | Type | Labels | Meaning |
107
+ | --------------------------------------------- | --------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
108
+ | `ldap_rest_http_request_duration_seconds` | histogram | `method`, `route`, `status` | Time to answer, by route template (`/api/v1/users/:id`; `none` unmatched or answered by a middleware, `core/static` included) |
109
+ | `ldap_rest_ldap_operation_duration_seconds` | histogram | `operation`, `result` | Time the directory takes: `bind`, `search`, `add`, `modify`, `rename`, `delete`; `ok`, the LDAP result code of a refusal (`32`: no such entry), `error` when it did not answer |
110
+ | `ldap_rest_ldap_pool_wait_seconds` | histogram | | Time to get a pooled connection, opening one included |
111
+ | `ldap_rest_ldap_pool_connections` | gauge | `state` | Pooled connections, `busy` or `idle` |
112
+ | `ldap_rest_ldap_pool_waiting` | gauge | | Operations waiting for a connection, the pool being full |
113
+ | `ldap_rest_ldap_pool_max` | gauge | | `--ldap-pool-size` |
114
+ | `ldap_rest_ldap_search_cache_total` | counter | `result` | Cacheable searches, `hit` or `miss`; nothing when the cache is off |
115
+ | `ldap_rest_hook_failures_total` | counter | `hook`, `plugin` | Notification hooks (`*done`, `onLdap*`) that threw, which the server logs and goes on |
116
+ | `ldap_rest_outbound_request_duration_seconds` | histogram | `target`, `status` | Time a service the server calls takes to answer: the calling plugin (`james`, `migrationProxy`, `calendar` or its old name `calendarResources`, `drive`, `cozyProvision`, `clouderyProvision`, `crowdsec`) or the instance provider (`cloudery`, `cozy-stack`); the HTTP status, `error` when it did not answer |
117
+ | `ldap_rest_unhandled_rejections_total` | counter | | Promise rejections nothing handled; counted by the binary, a host skipping `exitOnCrash()` leaves it at zero |
118
+ | `process_*`, `nodejs_*` | | | The process: CPU, memory, event loop lag, handles, GC |
119
+
120
+ A search the caller pages is timed page by page, what the caller does
121
+ between two pages left out; the readiness ping is not timed. The route
122
+ label is the template a route was declared with, never the URL: a label
123
+ per URL would grow without bound; under a router a plugin mounted, the
124
+ mount reads `*`. An outbound call is timed from the moment it is sent,
125
+ after the plugin's own queue, to the answer's headers, each attempt of a
126
+ retried call on its own; a plugin's health check that asks its service,
127
+ James's does, is a call like another. A plugin calling a service through
128
+ `this.fetch` has its calls counted under its name; the OpenID Connect
129
+ client's calls to its provider are made by its library, and not timed.
130
+
131
+ The instruments come from `prom-client` 15, kept while the server supports
132
+ Node 20, which its successor `@prometheus-io/client` dropped: the
133
+ deprecation notice `npm install` prints is expected.
134
+
135
+ The port listens on every interface. What to alert on: `/readyz` for a
136
+ replica out of rotation, `result="error"` in
137
+ `ldap_rest_ldap_operation_duration_seconds` (the directory not answering;
138
+ a refusal is a result code), a
139
+ `ldap_rest_ldap_pool_waiting` that stays above zero (raise
140
+ `--ldap-pool-size`), `ldap_rest_hook_failures_total` rising (a
141
+ synchronization that fails only in the log otherwise), `status="error"`
142
+ or the 5xx share in `ldap_rest_outbound_request_duration_seconds` by
143
+ `target` (a service the server depends on failing), the 5xx share of the
144
+ requests.
145
+
146
+ The Helm chart exposes the port on the Service with `metrics.enabled`, and
147
+ renders a `ServiceMonitor` for the Prometheus operator with
148
+ `metrics.serviceMonitor.enabled`; `core/metrics` still has to be in
149
+ `DM_PLUGINS`.
150
+
151
+ ## Shutdown
152
+
153
+ On `SIGTERM` or `SIGINT`, the binary stops in order:
154
+
155
+ 1. the listening socket closes: a new connection is refused, and
156
+ `/readyz` answers 503 `stopping` on the connections still open;
157
+ 2. idle keep-alive connections close at once; the others close with the
158
+ response they carry, sent with `Connection: close`, or are cut when the
159
+ drain is spent: `--shutdown-timeout` (20 s) less the 5 s kept for the
160
+ steps below, 1 s at least;
161
+ 3. every plugin's `close()` runs, at once: the broker connection, the
162
+ storage backend;
163
+ 4. the directory connections are closed;
164
+ 5. the signal ends the process, with its usual code (143 on `SIGTERM`).
165
+
166
+ The whole takes `--shutdown-timeout` at most: a `close()` that does not
167
+ settle in what remains of the budget is logged and left behind, by
168
+ `shutdown()` itself, so a host gets the same bound, and the directory
169
+ connections are closed anyway. The drain and the closes get 1 s each at
170
+ least, so a budget under 2 s is exceeded. Kubernetes takes the pod out
171
+ of its endpoints as it sends the signal, and the proxies follow a moment
172
+ later: a request routed meanwhile is refused. A `preStop` hook sleeping a
173
+ few seconds, before the signal, closes that window; the chart's `lifecycle`
174
+ value takes one. The sleep and `--shutdown-timeout` together must stay
175
+ under `terminationGracePeriodSeconds`, 30 s by default, which the chart
176
+ leaves to Kubernetes: 5 s and 20 s fit.
177
+
178
+ A host assembling the server in its own process calls `shutdown()` itself,
179
+ `shutdownOnSignals()` when it wants the same behaviour on a signal, and
180
+ `exitOnCrash()` for the policy below; without the latter, Node's own action
181
+ on an uncaught exception applies.
182
+
183
+ An exception nothing caught leaves the process in a state nobody knows: it
184
+ is logged, the shutdown above runs, and the process exits with 1, within
185
+ `--shutdown-timeout` whatever hangs, for its supervisor to start it again.
186
+ A promise rejection nothing handled is logged and counted, and the process
187
+ keeps serving.
188
+
189
+ ### For plugin authors
190
+
191
+ A plugin holding a connection, a timer or a queue implements `close()`,
192
+ called once after the server stopped serving; it must settle, a failure is
193
+ logged and does not hold the others.
@@ -57,6 +57,7 @@ Connect to external systems:
57
57
  | ---------------------------------------------------- | ------------------------------------ |
58
58
  | [james-mail](integrations/james-mail.md) | Apache James synchronization |
59
59
  | [james-mailboxes](integrations/james-mailboxes.md) | Apache James team mailboxes |
60
+ | [mail-migration](integrations/mail-migration.md) | Twake Mail migration proxy |
60
61
  | [calendar](integrations/calendar.md) | Twake Calendar resources and users |
61
62
  | [app-accounts](integrations/app-accounts.md) | Applicative accounts API |
62
63
  | [lifecycle-events](integrations/lifecycle-events.md) | Account lifecycle events to RabbitMQ |
@@ -70,6 +71,7 @@ Connect to external systems:
70
71
  | ------------------------------- | ------------------------------------------------------------------- |
71
72
  | [static](utilities/static.md) | Static file server |
72
73
  | [weblogs](utilities/weblogs.md) | HTTP logging |
74
+ | [metrics](utilities/metrics.md) | Prometheus metrics on a port of their own |
73
75
  | [storage](utilities/storage.md) | Keyed storage with a deadline, in LDAP, files, PostgreSQL or Valkey |
74
76
 
75
77
  ## Plugin Dependencies
@@ -536,7 +536,8 @@ This plugin only handles **authorization**. Combine with an authentication plugi
536
536
 
537
537
  ```json
538
538
  {
539
- "error": "check logs"
539
+ "error": "Internal Server Error",
540
+ "message": "An error occurred"
540
541
  }
541
542
  ```
542
543
 
@@ -4,10 +4,11 @@ Plugins for connecting LDAP-Rest to external systems.
4
4
 
5
5
  ## Apache James
6
6
 
7
- | Plugin | Description |
8
- | ------------------------------------- | ------------------------------- |
9
- | [james-mail](james-mail.md) | User and domain synchronization |
10
- | [james-mailboxes](james-mailboxes.md) | Team mailbox management |
7
+ | Plugin | Description |
8
+ | ------------------------------------- | -------------------------------- |
9
+ | [james-mail](james-mail.md) | User and domain synchronization |
10
+ | [james-mailboxes](james-mailboxes.md) | Team mailbox management |
11
+ | [mail-migration](mail-migration.md) | Migrated-users list of the proxy |
11
12
 
12
13
  ## Twake
13
14
 
@@ -45,6 +46,9 @@ core/twake/james
45
46
  core/twake/calendar
46
47
  └─ requires: core/ldap/onChange
47
48
 
49
+ core/twake/migrationProxy
50
+ └─ requires: core/ldap/onChange
51
+
48
52
  core/twake/lifecycleEvents
49
53
  ├─ requires: core/ldap/onChange
50
54
  └─ requires: core/rabbitmq
@@ -137,8 +137,8 @@ for it.
137
137
  describes department groups and requires `twakeDepartmentLink`, which these
138
138
  groups do not have. Groups created through SCIM are written directly and
139
139
  are not checked against this schema.
140
- - `--twake-group-base`, `--twake-group-user-base`: required, each with
141
- `{org}`.
140
+ - `--twake-group-base`, `--twake-group-user-base`: required, each holding
141
+ `{org}` once.
142
142
  - `--twake-group-organization-dn`: the organization entry. Empty means no
143
143
  organization is checked.
144
144
  - `--twake-group-organization-status-attribute` (default `twakeOrgStatus`),
@@ -0,0 +1,102 @@
1
+ # Mail migration proxy
2
+
3
+ `core/twake/migrationProxy` keeps the migrated-users list of the
4
+ [Twake Mail migration proxy](https://github.com/linagora/tmail-backend/blob/master/docs/modules/ROOT/pages/migration-proxy/objective.adoc)
5
+ in step with the directory, while a mail platform is moved to Twake Mail.
6
+
7
+ The proxy sits in front of the old and the new mail backends. It relays
8
+ each IMAP (and, when enabled, submission) connection to the backend hosting
9
+ the authenticated user, and routes each inbound recipient to the backend
10
+ hosting it. An address on its migrated list lives on the new backend; any
11
+ other address lives on the old one. An account created during the migration
12
+ has no mailbox on the old system, so it has to be on the list before its
13
+ first login and its first mail: this plugin puts it there, and keeps the
14
+ addresses of a migrated account on the list as they change.
15
+
16
+ Load it for the duration of the migration, next to `core/twake/james`, and
17
+ drop it once the old backend is gone. It requires `core/ldap/onChange`.
18
+
19
+ ## Scope
20
+
21
+ Only the entries under `--migration-proxy-branch` are followed, the
22
+ branches holding the accounts whose addresses the proxy routes: users, and
23
+ the groups whose address is a team mailbox or a mailing list when those are
24
+ created during the migration, as inbound mail for them is routed by
25
+ recipient too. Keep out the branches whose entries carry an address that is
26
+ not their own, the applicative accounts of
27
+ [app-accounts](app-accounts.md) for instance: they would put their owner on
28
+ the list or take it off.
29
+
30
+ The addresses of an entry are every value of `--mail-attribute` and of
31
+ `--alias-attribute`, the first mail being the one that says where the
32
+ account lives. The proxy folds usernames to lower case, so a change of case
33
+ is no change.
34
+
35
+ ## Behaviour
36
+
37
+ Through the `onLdapEntryChange` hook, after the write has answered, one
38
+ entry at a time:
39
+
40
+ - An entry that gains its mail, when created or later, is put on the list
41
+ with all its addresses: `PUT /migratedUsers/{address}`.
42
+ - An entry that keeps its mail and whose addresses change asks the proxy
43
+ about its first mail (`HEAD /migratedUsers/{mail}`). When the account is
44
+ migrated, the addresses it gained are put on the list, then the ones it
45
+ lost are taken off, once every addition holds; a user the proxy does not
46
+ know is on the old system, and its change is left to the migration, which
47
+ puts the new addresses on the list when it moves the mailbox.
48
+ - An entry deleted, or losing its mail, is taken off the list with all its
49
+ addresses: `DELETE /migratedUsers/{address}`. A tombstone or a trashed
50
+ entry keeps its address, so it stays on the list until it is erased.
51
+
52
+ The plugin cannot tell a new account from an existing one whose mail was
53
+ cleared and set again, or from one created anew over its tombstone: those
54
+ are put on the list too. Marking the existing accounts migrated is the
55
+ migration's job, through the proxy's webadmin or its `migration:switch`
56
+ exchange.
57
+
58
+ ### Failures
59
+
60
+ A call the proxy answers with 500, 502, 503 or 504 is made again,
61
+ `--migration-proxy-retry-attempts` times in all, after
62
+ `--migration-proxy-retry-delay` milliseconds times the number of attempts
63
+ made. A refusal, such as 400 for an address the proxy does not take as a
64
+ username or 401 for a wrong token, and a call the proxy does not answer at
65
+ all, connection refused or reset, are not. Each failed attempt logs a warning, the last failure an
66
+ error naming the address, the call and the entry. Every call is counted in
67
+ the [outbound metrics](../../operations.md#metrics), under the target
68
+ `migrationProxy`.
69
+
70
+ A failed `PUT` leaves the account routed to the old backend, which has no
71
+ mailbox for it: watch those errors, and put the address on the list by hand
72
+ through the proxy's webadmin. When the proxy has not said whether a renamed
73
+ account is migrated, nothing is changed and the error is logged.
74
+
75
+ ## Where an account lives
76
+
77
+ `GET /api/v1/users/:user/mail-migration` answers `{ "migrated": true }`
78
+ when the proxy routes the user to the new backend, `{ "migrated": false }`
79
+ otherwise, from the proxy's `HEAD`. The user is found by `uid` across the
80
+ directory, and answered only when the caller may read the entry: another
81
+ one gets 404, as a user that is not there. A user without mail gets 400,
82
+ and a proxy that does not answer 502.
83
+
84
+ ## Readiness
85
+
86
+ The plugin asks the proxy's `GET /healthcheck` for `/readyz`, and reports
87
+ `degraded` when it answers an error or not within 1.5 s, see
88
+ [what the shipped plugins report](../../operations.md#what-the-shipped-plugins-report).
89
+
90
+ ## Configuration
91
+
92
+ ```bash
93
+ --plugin core/ldap/onChange \
94
+ --plugin core/twake/james \
95
+ --plugin core/twake/migrationProxy \
96
+ --migration-proxy-url http://migration-proxy:8000 \
97
+ --migration-proxy-token "$MIGRATION_PROXY_TOKEN" \
98
+ --migration-proxy-branch "ou=users,dc=example,dc=com"
99
+ ```
100
+
101
+ The options are listed in
102
+ [configuration](../../configuration.md#coretwakemigrationproxy).
@@ -309,6 +309,16 @@ mapping contains an entry with `scim: "department"`; otherwise the server return
309
309
  `400 invalidFilter`. Same for PATCH: operations on unmapped paths produce
310
310
  `400 invalidPath`.
311
311
 
312
+ A User or Group body is checked against RFC 7643 before anything is written
313
+ and before any hook reads it: `userName`, `displayName` and `id` are
314
+ strings; a simple attribute takes a string, a number or a boolean, or a list
315
+ of them; a complex attribute takes an object whose sub-attributes are scalars
316
+ (or, for a reference such as `manager`, an object whose `value` is one); a
317
+ multi-valued complex attribute such as `emails` takes a list of objects whose
318
+ `value` is a scalar. Anything else is answered
319
+ `400 invalidValue`, as is a PATCH operation that is not an object or whose
320
+ `op` is not a string.
321
+
312
322
  A PATCH `add`, or a `remove` naming values, changes only the values it names:
313
323
  values the caller's read did not show, such as group members an
314
324
  `ldapsearchfilter` hides, stay. Two PATCHes changing different values of
@@ -369,7 +379,8 @@ via `escapeLdapFilter()` — no LDAP injection is possible.
369
379
  | `a eq "x" and b eq "y"` | `(&(a=x)(b=y))` |
370
380
  | `not (a eq "x")` | `(!(a=x))` |
371
381
 
372
- Unknown SCIM attributes raise `400 invalidFilter`.
382
+ Unknown SCIM attributes raise `400 invalidFilter`, as does a filter nested
383
+ more than 32 levels deep (a parenthesis and a `not (` each count one).
373
384
 
374
385
  ## Pagination
375
386
 
@@ -734,10 +745,12 @@ Notable fields:
734
745
  - `schemaUrls` — direct download URLs of the schema files SCIM reads
735
746
  (`User.json` and `Group.json` under `--schemas-path`, with the
736
747
  `default-mapping.json` template beside them when it is there), when a
737
- `core/static` plugin serves them, under its own `--static-name` (editor
748
+ `core/static` plugin serves them merged, through its schema routes, under
749
+ its own `--static-name` (editor
738
750
  UIs typically fetch them to render attribute pickers). Among plugins
739
751
  serving different paths, the one registered as `static` is preferred; a
740
- path an earlier plugin already serves is left to that one. Absent when
752
+ path an earlier plugin already serves, lies under or holds, is left to
753
+ that one. Absent when
741
754
  none serves them: read it with a guard.
742
755
  - `resourceTypes[].mapping` — the live SCIM↔LDAP mapping used server-side, so
743
756
  UI builders can render filter helpers / attribute selectors without second
@@ -249,7 +249,7 @@ A space reads, strongest role first:
249
249
  The plugin loads [groups](groups.md), and reads its options for the
250
250
  organization's users, groups and organization entry.
251
251
 
252
- - `--twake-space-base`: required, with `{org}`.
252
+ - `--twake-space-base`: required, holding `{org}` once.
253
253
  - `--twake-space-class` (default `top,twakeSpace`): the classes of a space
254
254
  created.
255
255
  - `--twake-space-display-name-attribute` (default `twakeDisplayName`).
@@ -174,7 +174,9 @@ mail: jdoe@company.com
174
174
 
175
175
  ### Fixed Attributes
176
176
 
177
- Attributes marked as `"fixed": true` in the schema are automatically added and should NOT be included in the CSV:
177
+ Attributes marked as `"fixed": true` in the schema are automatically added. A
178
+ row with a value under such a column fails, whatever the case of the column
179
+ name; an empty cell is ignored as any other:
178
180
 
179
181
  **Schema**:
180
182
 
@@ -74,7 +74,9 @@ Each schema file must include an `entity` metadata section and attribute definit
74
74
 
75
75
  - **name**: Internal name used for hooks (e.g., `ldap{name}add`)
76
76
  - **mainAttribute**: DN attribute (e.g., `uid`, `cn`)
77
- - **objectClass**: LDAP objectClass values
77
+ - **objectClass**: the classes of every entry created, whatever `objectClass`
78
+ the request carried; the schema's `objectClass` attribute only describes
79
+ them
78
80
  - **singularName**: Used in API paths and messages (e.g., "user")
79
81
  - **pluralName**: Used in API paths (e.g., "users")
80
82
  - **base**: LDAP base DN, supports config placeholders like `{ldap_base}`
@@ -138,7 +140,8 @@ never what it holds.
138
140
 
139
141
  Fixed attributes:
140
142
 
141
- - Auto-provisioned with default value on creation
143
+ - Auto-provisioned with default value on creation; `objectClass` takes the
144
+ classes of the entity metadata instead
142
145
  - Cannot be modified after creation
143
146
  - Cannot be deleted
144
147
 
@@ -28,7 +28,8 @@ side:
28
28
  says who made the write, and through which door:
29
29
 
30
30
  - `actor`: the caller's `userName`, or its `user` when it has none;
31
- - `requestId`: generated, the same for every write of one request;
31
+ - `requestId`: the answer's `X-Request-Id`, the same for every write of one
32
+ request, see [operations](../../operations.md#request-id);
32
33
  - `source`: `scim` for the SCIM API, `rest` otherwise.
33
34
 
34
35
  A write no request is behind, such as a scheduled task's, gets `{}`. The LDAP