ldap-rest 1.0.1 → 1.2.0

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 (100) 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/metrics.js +1 -0
  15. package/dist/lib/requestContext.js +1 -1
  16. package/dist/lib/schemaFile.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/flatGeneric.js +1 -1
  27. package/dist/plugins/ldap/groups.js +1 -1
  28. package/dist/plugins/ldap/organizations.js +1 -1
  29. package/dist/plugins/metrics.js +1 -0
  30. package/dist/plugins/rabbitmq.js +1 -1
  31. package/dist/plugins/scim/scim.js +1 -1
  32. package/dist/plugins/static.js +1 -1
  33. package/dist/plugins/storage/file.js +1 -1
  34. package/dist/plugins/storage/postgres.js +1 -1
  35. package/dist/plugins/storage/valkey.js +1 -1
  36. package/dist/plugins/storage.js +1 -1
  37. package/dist/plugins/twake/calendar.js +1 -1
  38. package/dist/plugins/twake/clouderyProvision.js +1 -1
  39. package/dist/plugins/twake/cozyProvision.js +1 -1
  40. package/dist/plugins/twake/drive.js +1 -1
  41. package/dist/plugins/twake/james.js +1 -1
  42. package/dist/plugins/twake/migrationProxy.js +1 -0
  43. package/dist/plugins/weblogs.js +1 -1
  44. package/dist/src/abstract/ldapFlat.d.ts +3 -13
  45. package/dist/src/abstract/plugin.d.ts +20 -0
  46. package/dist/src/abstract/twakePlugin.d.ts +23 -1
  47. package/dist/src/bin/index.d.ts +46 -1
  48. package/dist/src/config/args.d.ts +17 -1
  49. package/dist/src/lib/changeContext.d.ts +11 -0
  50. package/dist/src/lib/entitySchema/validator.d.ts +64 -0
  51. package/dist/src/lib/expressFormatedResponses.d.ts +19 -0
  52. package/dist/src/lib/health.d.ts +63 -0
  53. package/dist/src/lib/instanceProviders.d.ts +4 -0
  54. package/dist/src/lib/ldapActions.d.ts +28 -0
  55. package/dist/src/lib/metrics.d.ts +44 -0
  56. package/dist/src/lib/requestContext.d.ts +40 -0
  57. package/dist/src/lib/schemaFile.d.ts +10 -0
  58. package/dist/src/lib/shutdown.d.ts +20 -0
  59. package/dist/src/lib/staticFiles.d.ts +33 -0
  60. package/dist/src/lib/storage/store.d.ts +8 -0
  61. package/dist/src/lib/utils.d.ts +9 -0
  62. package/dist/src/logger/winston.d.ts +4 -0
  63. package/dist/src/plugins/ldap/groups.d.ts +18 -3
  64. package/dist/src/plugins/ldap/organizations.d.ts +3 -5
  65. package/dist/src/plugins/metrics.d.ts +17 -0
  66. package/dist/src/plugins/rabbitmq.d.ts +37 -1
  67. package/dist/src/plugins/scim/scim.d.ts +2 -0
  68. package/dist/src/plugins/static.d.ts +28 -0
  69. package/dist/src/plugins/storage/file.d.ts +4 -0
  70. package/dist/src/plugins/storage/postgres.d.ts +7 -14
  71. package/dist/src/plugins/storage/valkey.d.ts +2 -15
  72. package/dist/src/plugins/storage.d.ts +8 -0
  73. package/dist/src/plugins/twake/drive.d.ts +8 -0
  74. package/dist/src/plugins/twake/james.d.ts +2 -0
  75. package/dist/src/plugins/twake/migrationProxy.d.ts +71 -0
  76. package/docs/client-development/api/reference.md +5 -9
  77. package/docs/client-development/api/rest-api.md +4 -10
  78. package/docs/client-development/schemas/README.md +60 -21
  79. package/docs/plugin-development/README.md +15 -13
  80. package/docs/plugin-development/dependencies.md +8 -0
  81. package/docs/plugin-development/hooks.md +1 -1
  82. package/docs/usage/README.md +1 -0
  83. package/docs/usage/configuration.md +57 -15
  84. package/docs/usage/operations.md +193 -0
  85. package/docs/usage/plugins/README.md +2 -0
  86. package/docs/usage/plugins/auth/authz-per-branch.md +2 -1
  87. package/docs/usage/plugins/integrations/README.md +8 -4
  88. package/docs/usage/plugins/integrations/mail-migration.md +102 -0
  89. package/docs/usage/plugins/integrations/scim.md +10 -2
  90. package/docs/usage/plugins/ldap/bulk-import.md +3 -1
  91. package/docs/usage/plugins/ldap/flat-generic.md +5 -2
  92. package/docs/usage/plugins/ldap/on-change.md +2 -1
  93. package/docs/usage/plugins/ldap/organizations.md +7 -7
  94. package/docs/usage/plugins/utilities/metrics.md +21 -0
  95. package/docs/usage/plugins/utilities/static.md +81 -34
  96. package/docs/usage/plugins/utilities/storage.md +7 -0
  97. package/docs/usage/plugins/utilities/weblogs.md +51 -30
  98. package/docs/usage/upgrading.md +97 -0
  99. package/package.json +11 -2
  100. package/static/schemas/example/README.md +1 -0
@@ -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
@@ -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).
@@ -731,8 +731,16 @@ Response shape (truncated):
731
731
 
732
732
  Notable fields:
733
733
 
734
- - `schemaUrls` — direct download URLs when `core/static` is loaded (editor UIs
735
- typically fetch them to render attribute pickers).
734
+ - `schemaUrls` — direct download URLs of the schema files SCIM reads
735
+ (`User.json` and `Group.json` under `--schemas-path`, with the
736
+ `default-mapping.json` template beside them when it is there), when a
737
+ `core/static` plugin serves them merged, through its schema routes, under
738
+ its own `--static-name` (editor
739
+ UIs typically fetch them to render attribute pickers). Among plugins
740
+ serving different paths, the one registered as `static` is preferred; a
741
+ path an earlier plugin already serves, lies under or holds, is left to
742
+ that one. Absent when
743
+ none serves them: read it with a guard.
736
744
  - `resourceTypes[].mapping` — the live SCIM↔LDAP mapping used server-side, so
737
745
  UI builders can render filter helpers / attribute selectors without second
738
746
  round-trip.
@@ -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
@@ -28,13 +28,13 @@ This plugin manages hierarchical organizations stored as `organizationalUnit` (o
28
28
 
29
29
  ### Configuration Options
30
30
 
31
- | Argument | Environment Variable | Default | Description |
32
- | ----------------------------------------------------------- | ------------------------------------- | ------------------------------- | --------------------------------------------------- |
33
- | `--ldap-top-organization` | `DM_LDAP_TOP_ORGANIZATION` | _Required_ | DN of the top-level organization |
34
- | `--ldap-organization-class` / `--ldap-organization-classes` | `DM_LDAP_ORGANIZATION_CLASSES` | `["top", "organizationalUnit"]` | Object classes for organizations |
35
- | `--ldap-organization-link-attribute` | `DM_LDAP_ORGANIZATION_LINK_ATTRIBUTE` | `twakeDepartmentLink` | Attribute for linking users/groups to organizations |
36
- | `--ldap-organization-path-attribute` | `DM_LDAP_ORGANIZATION_PATH_ATTRIBUTE` | `twakeDepartmentPath` | Attribute for the organizational path |
37
- | `--ldap-organization-path-separator` | `DM_LDAP_ORGANIZATION_PATH_SEPARATOR` | `" / "` | Separator for path components |
31
+ | Argument | Environment Variable | Default | Description |
32
+ | ----------------------------------------------------------- | ------------------------------------- | ---------------------------------------- | --------------------------------------------------- |
33
+ | `--ldap-top-organization` | `DM_LDAP_TOP_ORGANIZATION` | _Required_ | DN of the top-level organization |
34
+ | `--ldap-organization-class` / `--ldap-organization-classes` | `DM_LDAP_ORGANIZATION_CLASSES` | `top,organizationalUnit,twakeDepartment` | Object classes for organizations |
35
+ | `--ldap-organization-link-attribute` | `DM_LDAP_ORGANIZATION_LINK_ATTRIBUTE` | `twakeDepartmentLink` | Attribute for linking users/groups to organizations |
36
+ | `--ldap-organization-path-attribute` | `DM_LDAP_ORGANIZATION_PATH_ATTRIBUTE` | `twakeDepartmentPath` | Attribute for the organizational path |
37
+ | `--ldap-organization-path-separator` | `DM_LDAP_ORGANIZATION_PATH_SEPARATOR` | `" / "` | Separator for path components |
38
38
 
39
39
  ### Important Notes
40
40
 
@@ -0,0 +1,21 @@
1
+ # Metrics Plugin
2
+
3
+ Prometheus metrics of the server, on a port of their own.
4
+
5
+ ## Configuration
6
+
7
+ ```bash
8
+ --plugin core/metrics --metrics-port 9464
9
+ ```
10
+
11
+ `--metrics-port` (`DM_METRICS_PORT`, 9464) is the port `GET /metrics` is
12
+ served on, see [configuration](../../configuration.md#coremetrics).
13
+
14
+ The port is separate from the API's: nothing on it goes through the
15
+ authentication, the rate limiting or the request id, so it must not be
16
+ reachable from outside the cluster or the host. The Helm chart exposes it
17
+ on the Service and to a Prometheus operator with `metrics.enabled` and
18
+ `metrics.serviceMonitor.enabled`.
19
+
20
+ What is measured, and how to read it, is in
21
+ [operations](../../operations.md#metrics).