What this is about
Modules register their roles and attributes, see Modules register their roles. For this to have any effect, someone has to bring Keycloak and the catalog up to date. The reconciliation does that. It writes three things:
- the user profile in Keycloak: which attributes an account can have
- the client roles and the claim mappers per module. A claim mapper makes sure that an attribute arrives in the client’s token
- the catalog in CIAS
When it runs
| Occasion | How | Who |
|---|---|---|
| at startup | after startup, when codamai.cias.authorization.startup.enabled=true | the application itself |
| on request | POST /cias/admin/roles/reconcile | only a platform administrator, otherwise 403 |
There is no timer, on purpose. In Keycloak, the user profile is one document that is replaced completely on every write. Two runs at the same time would overwrite each other. So only one run runs at a time within a process: whoever presses the button while a run is going gets back a report “already running”, and nothing happens twice.
At startup, before the reconciliation, CIAS also creates the realm roles that the installation names in its configuration (startup.realm-roles, for example user). These never come from a declaration.
Three phases: read, reject, write
-
1CIASread: query every configured declaration; a module that cannot be reached is noted and otherwise left alone
-
2CIASreject: faulty declarations drop out, attribute conflicts stop the whole run, key collisions stop their client
-
3CIAS→Keycloakwrite 1: the user profile, once for all modules
-
4CIAS→Keycloakwrite 2: per module, create missing client roles, then the claim mappers
-
5CIASwrite 3: per module, the catalog, in its own transactionResult: One report per module: what was created, changed, retired, reinstated
First read everything, then check everything, then write. A run that already wrote while reading would apply a conflicting configuration halfway, and afterwards nobody would know which half. The rejections in detail are described in When CIAS does not accept a declaration.
Keycloak first, then the catalog
Keycloak and CIAS have no shared transaction. If something fails between the two, one half is left alone. The order decides which one:
When: The role is created in Keycloak, the catalog is not written anymore.
A role without holders in Keycloak changes nothing for anyone. The next run adds it to the catalog.
Result: harmless
When: The role is in the catalog but missing in Keycloak.
An administrator grants it, CIAS stores the grant, and the token still does not carry the role.
Result: a permission that CIAS offers and cannot deliver
If creating in Keycloak fails for a module, the report shows it as UNPROVISIONED, and its catalog stays unchanged, including renames and retirements. The next run catches up on everything at once, because every step can be repeated.
What happens to a module’s roles
When: The module registers order-export, the catalog does not know it.
Created in Keycloak, entered in the catalog: owner is the module, scope TENANT, delegation from the installation's configuration.
Result: in the report under defined
When: The module registers order-edit with a new display name.
Display name and display group are taken over. Description and delegation stay as an administrator set them. The module says nothing about those.
Result: in the report under updated
When: The module no longer registers order-archive.
The role is retired, with date and time. It stays in Keycloak, and all grants stay. It can no longer be granted again.
Result: in the report under deprecated
When: The module registers a retired role again.
The retirement is lifted. The grants were never gone, so nothing has to be given back.
Result: in the report under reinstated
When: The module does not answer.
Nothing changes, not even a retirement. A role is never retired just because its service is restarting.
Result: UNREADABLE
Why retire instead of delete: if CIAS deleted a withdrawn role, every person who has it would lose their permission, just because a module cleaned up its list. And nobody could check later that the role ever existed.
Only what belongs to this module is retired. If several modules share a client, the silence of one never retires the roles of the other. Roles without an owner, for example the installation’s realm roles, are never retired by a reconciliation.
Attributes
The same principle applies to attributes, only stricter:
- An attribute that no module registers anymore stays in the user profile. Deleting it would destroy the value in every account. A role is a permission, an attribute is content.
- CIAS rejects a required attribute without a default value, together with the whole declaration of this module. Otherwise, accounts that existed before would have no value. The profile would then claim that such accounts cannot exist, and after that Keycloak would even refuse changes to these accounts, including suspending them.
More in Registering attributes.
The report
POST /cias/admin/roles/reconcile
Authorization: Bearer <token of a platform administrator>{
"applied": true,
"modules": [
{ "module": "cias", "client": "cias-backend", "status": "RECONCILED",
"provisioned": [], "defined": [], "updated": [],
"deprecated": [], "reinstated": [], … },
{ "module": "cdms", "client": "cdms-backend", "status": "RECONCILED",
"provisioned": ["cdms-backend/order-export"],
"defined": ["cdms-backend/order-export"],
"deprecated": ["cdms-backend/order-archive"], … }
],
"attributes": [],
"refusals": []
}| Status | Meaning |
|---|---|
RECONCILED | read, created in Keycloak, catalog written |
UNPROVISIONED | creating in Keycloak failed, catalog unchanged |
UNREADABLE | module could not be queried, nothing changed |
REJECTED | declaration not acceptable, none of it applied |
REFUSED | key collision on the client, nothing written on this client |
SKIPPED | the whole run wrote nothing, the reason is in refusals |
The report always shows roles as client/key, because the key alone no longer says which role is meant.