What this is about
CIAS is not an application you start. It is a set of jars. A jar is a library that sits on an application’s classpath. Whether a CIAS module actually works inside that application is decided by the configuration alone. The jar being there is not enough.
Why no module switches itself on
Spring normally finds classes through a component scan: it searches packages for classes carrying markers such as @Service or @Component and wires them up. CDMS scans everything under com.codamai — and CIAS lives under com.codamai.
If a CIAS class carried such a marker, every CDMS application would pick it up as soon as the jar landed on the classpath somehow, for example as a dependency of a dependency. It would then be wired against objects that do not exist there.
So instead:
-
1Buildputs the jar on the classpathNo class in it carries
@Service,@Repositoryor@Component. The component scan finds nothing. -
2CIASone configuration class per module registers the objects explicitlyThe starter additionally registers its configurations as auto-configurations. Spring deliberately excludes auto-configurations from the scan — that is protection against accidental wiring too.
-
3CIASreads the module's switch
-
4CIASthe switch is not
true→ the module does not work -
5CIASthe switch is
true→ the module worksResult: A module is on because somebody wrote it down, never because a jar came along.
The switches
The picture shows where the switches sit. On the left, what is always there once the filter chain runs; on the right, the modules that are switched on one by one.
flowchart LR
subgraph P["One process"]
direction TB
A["Filter chain, tenant gate<br/><i>no switch: present or absent</i>"]
L{"codamai.cias.tenancy.lookup"}
A --> L
L -->|local| T["Tenant management<br/>codamai.cias.tenancy.enabled"]
L -->|remote| C["Tenant client<br/>+ client.base-url, client.credentials"]
A -.-> U["User management<br/>codamai.cias.user.enabled"]
A -.-> Z["Role management<br/>codamai.cias.authorization.enabled"]
A -.-> R["Registration<br/>codamai.cias.registration.enabled"]
A -.-> N["Notifications<br/>codamai.cias.notification.enabled"]
A -.-> AU["Audit<br/>codamai.cias.audit.enabled"]
end
IP{"codamai.cias.iam-provider"}
Z --> IP
U --> IP
IP -->|keycloak| K[(Keycloak)]
The complete list:
| Switch | What for | Values |
|---|---|---|
codamai.cias.tenancy.enabled | keeping tenants: create, suspend, close | true |
codamai.cias.tenancy.lookup | where the tenant gate gets its answer from | local or remote |
codamai.cias.tenancy.client.base-url | where CIAS is reachable | only with lookup: remote |
codamai.cias.tenancy.client.credentials.* | the service’s own client at the IAM, used to fetch the service token (token-uri, client-id, client-secret); without it a fixed .token | only with lookup: remote |
codamai.cias.tenancy.rest | the administrative API /cias/admin/tenants | true / false |
codamai.cias.tenancy.lookup-rest | the endpoint other services ask | true / false; true in the standalone CIAS service |
codamai.cias.tenancy.lookup-roles | who may ask that endpoint | role names |
codamai.cias.user.enabled, .rest | the user record and its API | true / false |
codamai.cias.user.lookup-rest, .lookup-roles | per-tenant attribute values for other services | true / false (true in the standalone CIAS service), role names |
codamai.cias.authorization.enabled, .rest | role catalog, grants, reconciliation with Keycloak | true / false |
codamai.cias.registration.enabled, .rest | registration and invitation | true / false |
codamai.cias.notification.enabled, .rest | mail templates and editing them | true / false |
codamai.cias.notification.editor-roles | who may change every mail template, also via CIAS_NOTIFICATION_EDITOR_ROLES | default platform-admin,mail-template-admin |
codamai.cias.notification.mail, .mail.from | how mails leave the house | smtp or log |
codamai.cias.audit.enabled | the audit trail | true / false |
codamai.cias.<module>.persistence | how the module reaches the database | jpa |
codamai.cias.iam-provider | which identity provider CIAS talks to | keycloak or memory |
codamai.cias.migration.enabled | creates and upgrades the CIAS tables | true / false |
codamai.cias.platform-administrator-roles | which roles administer the platform | role names, empty means nobody |
codamai.cdms.cias.reader-roles | who may read GET /cias/fetch in CDMS | role names |
codamai.persistence.tenant.mode | the operating mode of the data storage | SINGLE or MULTI |
The filter chain has no switch of its own. It checks every token, and it works as soon as its jar is there. What it needs is not a switch but an answer: codamai.cias.tenancy.lookup tells it whom to ask about the tenant.
The three variants
When: The switch is true.
-
1CIASwires up the module's objects
-
2CIASthe module's endpoints answer, its flows run
Result: The module works.
When: The switch is absent or false.
"Off" looks different per module, and both shapes are deliberate. With tenancy and audit the switch decides whether the objects exist at all: they are simply not there. With user, authorization, registration and notification the objects always exist, and the switch is read where it decides something: the flow is then an implementation that refuses every call, and the endpoints answer 404 before a controller is ever reached.
Result: In both cases nothing happens in the subject area. From the outside the only difference is whether an endpoint says 404 or does not exist at all.
When: A value is missing that CIAS would have to guess.
-
1CIASdoes not find the value; there is deliberately no default
-
2CIASaborts the start, the message names the setting or the type
Result: The application does not come up. That is better than one that comes up generously.
Which case applies is not a matter of taste. It depends on what a wrong default would do:
| What is missing | Consequence |
|---|---|
codamai.cias.tenancy.lookup | Start fails. Without it nobody answers the tenant gate — and a gate that let everybody through for lack of an answer would be a hole opened by a forgotten line. |
client.base-url with lookup: remote | Start fails. An invented address would be worse than none. |
codamai.cias.iam-provider where the modules need it | Start fails. A CIAS without an identity provider would accept registrations it can never carry out. |
codamai.persistence.tenant.mode where there is CDMS persistence | Start fails. A default would silently switch off a tenant isolation or invent one. |
codamai.cias.notification.mail while the mail module is on | Start fails. A default of log would report every mail as sent and deliver none. |
codamai.cias.user.enabled | No error. The module is off, and an installation without user management is a valid state. |
codamai.cias.tenant-gate.ttl and other durations | No error. They have defaults, because a wrong value makes the application slower but opens nothing. |
The whole rule behind this is under Reject when in doubt, together with the list of all mandatory values.
What the application itself has to supply
Some values are not a line in a file but beans. A bean is an object Spring wires up at start.
| Bean | What it decides | Does CIAS ship one? |
|---|---|---|
PlatformAdministrators | which roles administer the platform | no, and it stays that way: a library must not invent an administrator role |
RegistrationRoles | which roles a registration grants | no |
MailTemplateEditPolicy | which mails tenant administrators may rewrite; it reads the editor roles from CIAS_NOTIFICATION_EDITOR_ROLES | no |
ManagedTypesContribution | that the CIAS tables are known to the host’s persistence | no, only the host knows that |
CallerContextProvider | how CIAS learns who is acting right now | the starter ships one that looks at the RequestContext; without the starter you write it yourself |
Clock | the time source | as above |
If one of them is missing where it is needed, the start fails with the name of the type in the message.
What is decided at build time
Part of the decision is made earlier, when the project scaffold is produced. There is no field for it. The generator derives the integration from two things in the hub: whether the system authenticates through CIAS (authentication), and which runtime the project’s CIAS module runs in.
authentication | the project’s CIAS module | result |
|---|---|---|
NONE | not looked at | NONE, a public system |
OIDC | runs in this system’s runtime | EMBEDDED |
OIDC | runs in another runtime, as a service of its own or embedded in another CDMS module of the project | REMOTE, this system is a client |
OIDC | is placed in no runtime | the build is refused |
OIDC | the project has none | the build is refused |
A refused build answers with 400, and the message names what to model: give the project a CIAS module, place it in a runtime, or set authentication to NONE.
| NONE | REMOTE | EMBEDDED | |
|---|---|---|---|
| What it means | The application does not authenticate anybody through CIAS. | CIAS runs in another runtime. | CIAS runs in this process. |
| Which jars are added | the CDMS open access building block, no CIAS jar | the CIAS filter chain and the CIAS tenant client | the filter chain, CIAS tenant, user and role management, the Keycloak adapter, the starter |
| What the generator writes | the switch for open access | codamai.cias.tenancy.lookup: remote in application.yaml | the configuration classes, the codamai.cias block of application.yaml and, on every build, cias-configuration.yaml from the CIAS module |
| Which environment variables are added | CODAMAI_CDMS_OPEN_ACCESS_ALLOWED_NETWORKS | the sign-in variables (CIAS_ISSUER, CIAS_CLIENT …) and CODAMAI_CIAS_TENANCY_CLIENT_BASE_URL with the client's credentials | the sign-in variables and those of the embedded CIAS: administration client, users, roles, registration, mail, audit |
| Who answers the tenant gate | nobody — there is no CIAS filter chain | the CIAS service, over HTTP | CIAS tenant management in the same process, as a method call |
Two things here are easy to miss:
- Two CDMS modules of the same project can be integrated differently. If the CIAS module runs in the runtime of one, it is embedded there, and the other, in its own runtime, is its client. The second one does not need a CIAS service of its own for that.
- With
EMBEDDEDthe CIAS module decides what is switched on. The scaffold’sapplication.yamlleaves users, roles and Keycloak access off. The generatedcias-configuration.yamlsets them to what the CIAS module in the hub says. An environment variable such asCIAS_USERoverrides both. Here too, configuration decides what runs, not the classpath.