What this is about
CIAS invents no roles. Every module states for itself which roles it knows and which values it needs about a person. That list is called the declaration, and CIAS turns it into client roles in Keycloak, entries in the user profile and entries in its own catalog.
This page follows the path across both modules: where the list is created in CDMS, how it reaches CIAS, and what happens when CIAS does not accept it.
What CIAS does with an accepted declaration is under Modules register their roles and Reconciliation with Keycloak. This page is about the stretch before that.
Where the list in CDMS comes from
A CDMS project does not write its declaration by hand. The generator builds it from the models at build time, as the class RoleRegistryService.
-
1Developer→Hubmodels
customerand puts role names on the operations, for examplecrm-read -
2Buildcollects every role a model requires for create, read, update, delete, download, history or rollbackPlus every role that sits on a field. A field role CIAS does not know would be a role that works at runtime and can be granted to nobody.
-
3Buildcollects every attribute an attribute filter restricts rows byA project without attribute filters declares no attribute at all. An invented entry would have CIAS write a field into the user profile that no filter ever reads.
-
4Generatorwrites a class with two methods out of it:
roles()andattributes()Result: That class is exactly what CIAS reads — as a bean or as JSON. There is only the one.
What the generator decides while doing so:
| Item | How it is derived |
|---|---|
| role key | exactly the name written in the model |
| display group | the part before the first dash, so crm for crm-read. Without a dash: no group |
| display name | what the role is required for in this project, per model with its operations |
| attributes | optional and multivalued, without a default value |
Why the attributes are optional: a required attribute needs a default value in Keycloak, otherwise CIAS rejects the whole declaration. The only default a generator could write here would be an empty one — and that would fail the request just the same, after it had been put into every account in the realm. Why they are multivalued: the runtime filter splits the value into several and searches with them; declared single-valued, Keycloak would return exactly the shape the filter is not written for.
The two ways
When: CDMS and CIAS run in the same process.
-
1CIASlooks up the bean whose name the configuration states, for example
roleRegistryService -
2CIAS→CDMScalls
roles()andattributes()A method call. No network, no token, no timeout. -
3CIASgets the two lists
Result: If the bean returns null instead of an empty list, or throws, the module counts as unreadable — never as a module that no longer has any roles.
When: CDMS is a service of its own.
sequenceDiagram
participant S as CIAS service
participant C as CDMS service
S->>C: GET /cias/fetch (reader token)
C->>C: Does the caller hold a reader role?
alt yes
C-->>S: 200 { roles: [...], attributes: [...] }
else no
C-->>S: 403
end
Result: Only 200 is an answer. Everything else means unreadable, and every case gets its own reason in the report.
The choice is stated in the CIAS configuration, one line per module:
codamai:
cias:
authorization:
declarations:
reader-client:
token-uri: https://iam.example.com/realms/codamai/protocol/openid-connect/token
client-id: ${CIAS_DECLARATION_CLIENT_ID}
client-secret: ${CIAS_DECLARATION_CLIENT_SECRET}
modules:
- name: cias
client: cias-backend
bean: ciasIdentityRegistry # in the same process
- name: cdms
client: cdms-backend
url: https://cdms.internal/cias/fetch # a service of its own
Both forms occur side by side: CIAS is a module itself and always reads its own declaration as a bean.
namesays who a role belongs to. It has to be unique and must not change between runs.clientbelongs to the deployment, not to the module. One process is one client, so every module in one process shares it. The hub backend, for instance, carriesciasandcdmson the same client — which is exactly why it needs the name beside it.- Exactly one of
beanandurl. Both or neither fails the start, see Cold start of an installation.
The reader role on both sides
The endpoint serves the application’s complete permission map: every role a model requires, and every value a filter reads. That is nothing that may stand open.
-
CDMSIs there a token?Did the filter chain fill a caller context?↳ no refused — a request without a token holds no roles
-
CDMSIs there a reader role?Does the caller hold one of the realm roles in
codamai.cdms.cias.reader-roles?↳ no 403, without revealing which role would have been enough - The two lists as JSON
Two things about this are easy to miss:
- What is checked are realm roles, not the application’s business roles. The caller is CIAS, and it reads the same endpoint on every module; a client role would have to be granted once per client.
codamai.cdms.cias.reader-roleshas no default. An application that states nothing does not start. An explicitly empty value is legitimate and means nobody — the right setting for an installation whose roles are not reconciled by CIAS at all.
On the other side CIAS needs a token carrying one of those roles. CIAS gets it from the IAM (Identity and Access Management, here Keycloak) itself, with a client of its own (reader-client). The procedure is called client credentials: the service signs in at the token endpoint with its client ID and secret and receives a token for itself, with no person involved. CIAS keeps the token for three quarters of its lifetime and then fetches a new one.
- If no client is configured, CIAS uses a fixed token from
reader-token. It does not refresh. - A client with a part missing (the secret, say) stops the startup.
- If no token can be obtained, the module counts as unreadable for this run.
This call is not on a request path — it runs at startup and on request — so it may take seconds.
When CIAS does not accept
CIAS reads every module first, then checks everything, and only then writes. A pass that wrote while reading would apply a contradictory configuration by halves.
| Finding | Reach | Consequence |
|---|---|---|
| the module does not answer, or does not answer with 200 | this module | nothing about its roles changes, not even a retirement |
| required attribute without a default value, or one attribute described twice differently | this module | the whole declaration drops out of the pass, its roles included |
| a role key on this client already belongs to another module | this client | nothing is written on this client, other clients run normally |
| two modules declare the same key on one client | this client | as above |
| two modules describe one attribute differently in the same pass | everything | the pass writes nothing, for no module |
The middle case is the one you are most likely to meet when two modules work together:
Two modules describing an attribute identically are not a contradiction; both may declare it. And a rejected declaration never stops CIAS: CIAS is what everybody registers with, and one module’s fault must not block the way to fixing it.
What ends up in Keycloak
GET /cias/fetch
Authorization: Bearer <token with the reader role>{
"roles": [
{ "key": "crm-read", "group": "crm", "name": "Customer: read" },
{ "key": "crm-write", "group": "crm", "name": "Customer: create, update, delete" }
],
"attributes": [
{ "key": "regionen", "multivalued": true, "defaultValue": null,
"required": false, "selfEditable": false, "binding": "USER" }
]
}Every role entry becomes a client role on the module’s client, plus a catalog entry with an owner and tenant scope. Every attribute entry becomes a field in the user profile and a claim mapper, so the value arrives in the token.
What the module does not decide: the client, the scope and the delegation. Those are set by the installation — a module must not decide who hands out its roles.
Watch out
Next
- Modules register their roles: the CIAS view, with every reason for a rejection
- Reconciliation with Keycloak and Registering attributes
- Cold start of an installation: when the declaration is read
- CIAS embedded and CIAS as a separate service
- How role names are built and Attribute filter