CodamAIDocs
Topicdone

Configuration decides what runs

Every CIAS module sits behind a switch with no default value. Why no CIAS class activates itself, and what is decided at build time (which provider adapter).

Variants
module onmodule offswitch missing → startup failsgenerator: EMBEDDED / REMOTE / NONE

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:

How a CIAS module gets into an application
  1. 1
    Build
    puts the jar on the classpath
    No class in it carries @Service, @Repository or @Component. The component scan finds nothing.
  2. 2
    CIAS
    one configuration class per module registers the objects explicitly
    The starter additionally registers its configurations as auto-configurations. Spring deliberately excludes auto-configurations from the scan — that is protection against accidental wiring too.
  3. 3
    CIAS
    reads the module's switch
  4. 4
    CIAS
    the switch is not true → the module does not work
  5. 5
    CIAS
    the switch is true → the module works
    Result: 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["cias-authentication<br/>filter chain, tenant gate<br/><i>no switch: present or absent</i>"]
        L{"codamai.cias.tenancy.lookup"}
        A --> L
        L -->|local| T["cias-tenancy<br/>codamai.cias.tenancy.enabled"]
        L -->|remote| C["cias-tenancy-client<br/>+ client.base-url, client.credentials"]
        A -.-> U["cias-user<br/>codamai.cias.user.enabled"]
        A -.-> Z["cias-authorization<br/>codamai.cias.authorization.enabled"]
        A -.-> R["cias-registration<br/>codamai.cias.registration.enabled"]
        A -.-> N["cias-notification<br/>codamai.cias.notification.enabled"]
        A -.-> AU["cias-audit<br/>codamai.cias.audit.enabled"]
    end
    IP{"codamai.cias.iam-provider"}
    Z --> IP
    U --> IP
    IP -->|keycloak| K[(Keycloak)]

The complete list:

SwitchWhat forValues
codamai.cias.tenancy.enabledkeeping tenants: create, suspend, closetrue
codamai.cias.tenancy.lookupwhere the tenant gate gets its answer fromlocal or remote
codamai.cias.tenancy.client.base-urlwhere CIAS is reachableonly 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 .tokenonly with lookup: remote
codamai.cias.tenancy.restthe administrative API /cias/admin/tenantstrue / false
codamai.cias.tenancy.lookup-restthe endpoint other services asktrue / false; true in the standalone CIAS service
codamai.cias.tenancy.lookup-roleswho may ask that endpointrole names
codamai.cias.user.enabled, .restthe user record and its APItrue / false
codamai.cias.user.lookup-rest, .lookup-rolesper-tenant attribute values for other servicestrue / false (true in the standalone CIAS service), role names
codamai.cias.authorization.enabled, .restrole catalog, grants, reconciliation with Keycloaktrue / false
codamai.cias.registration.enabled, .restregistration and invitationtrue / false
codamai.cias.notification.enabled, .restmail templates and editing themtrue / false
codamai.cias.notification.editor-roleswho may change every mail template, also via CIAS_NOTIFICATION_EDITOR_ROLESdefault platform-admin,mail-template-admin
codamai.cias.notification.mail, .mail.fromhow mails leave the housesmtp or log
codamai.cias.audit.enabledthe audit trailtrue / false
codamai.cias.<module>.persistencehow the module reaches the databasejpa
codamai.cias.iam-providerwhich identity provider CIAS talks tokeycloak or memory
codamai.cias.migration.enabledcreates and upgrades the CIAS tablestrue / false
codamai.cias.platform-administrator-roleswhich roles administer the platformrole names, empty means nobody
codamai.cdms.cias.reader-roleswho may read GET /cias/fetch in CDMSrole names
codamai.persistence.tenant.modethe operating mode of the data storageSINGLE or MULTI

cias-authentication has no switch of its own. The filter chain checks every token, and it works as soon as the 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

What a switch does

When: The switch is true.

  1. 1
    CIAS
    wires up the module's objects
  2. 2
    CIAS
    the 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.

  1. 1
    CIAS
    does not find the value; there is deliberately no default
  2. 2
    CIAS
    aborts 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:

Why some missing values abort the start
What is missingConsequence
codamai.cias.tenancy.lookupStart 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: remoteStart fails. An invented address would be worse than none.
codamai.cias.iam-provider where the modules need itStart fails. A CIAS without an identity provider would accept registrations it can never carry out.
codamai.persistence.tenant.mode where there is CDMS persistenceStart fails. A default would silently switch off a tenant isolation or invent one.
codamai.cias.notification.mail while the mail module is onStart fails. A default of log would report every mail as sent and deliver none.
codamai.cias.user.enabledNo error. The module is off, and an installation without user management is a valid state.
codamai.cias.tenant-gate.ttl and other durationsNo 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.

BeanWhat it decidesDoes CIAS ship one?
PlatformAdministratorswhich roles administer the platformno, and it stays that way: a library must not invent an administrator role
RegistrationRoleswhich roles a registration grantsno
MailTemplateEditPolicywhich mails tenant administrators may rewrite; it reads the editor roles from CIAS_NOTIFICATION_EDITOR_ROLESno
ManagedTypesContributionthat the CIAS tables are known to the host’s persistenceno, only the host knows that
CallerContextProviderhow CIAS learns who is acting right nowthe starter ships one that looks at the RequestContext; without the starter you write it yourself
Clockthe time sourceas 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. In the hub, a system carries a field cias with three possible values. From it the generator decides which jars go into the application and which configuration classes it writes.

The cias field on the system
NONEREMOTEEMBEDDED
What it meansThe application does not authenticate anybody through CIAS.CIAS is a separate service.CIAS runs in this process.
Which jars are addednonecias-tenancy-clientcias-tenancy, cias-user, cias-authorization, cias-iam-keycloak
What the generator writesnone of itonly the entry in .envthe configuration classes and the whole codamai.cias block of application.yaml
Which environment variables are addednoneCODAMAI_CIAS_TENANCY_CLIENT_BASE_URLCIAS_IAM_PROVIDER, CIAS_USER, CIAS_AUTHORIZATION and the credentials of the administration client
Who answers the tenant gatenobody — there is no CIAS filter chainthe CIAS service, over HTTPcias-tenancy in the same process, as a method call

Two things here are easy to miss:

  • REMOTE is the value that applies when nobody says anything. As soon as an application authenticates through CIAS it needs an answer to the tenant gate’s question, or it does not start. So the generator never leaves that question open. REMOTE is the more restrained choice: one jar and two settings. EMBEDDED pulls tenants, users and the role catalog into the application, and that is a decision about the installation, not a default.
  • With EMBEDDED the identity half comes along dormant. The jars for users, the role catalog and Keycloak access sit on the classpath, but CIAS_IAM_PROVIDER, CIAS_USER and CIAS_AUTHORIZATION start out empty or false. Only setting them gives you those modules. That is exactly the point of this page: configuration decides what runs, not the classpath.

A system that does not authenticate through CIAS at all always gets NONE — even if the field says otherwise. Without a filter chain there is no gate that would need an answer.

Watch out

Next

Sources in the code and the knowledge base
  • CIAS/cias-spring-boot-starter – AutoConfiguration.imports (registration instead of component scan), CiasIamAutoConfiguration (iam-provider keycloak|memory, iamPorts), CiasSchemaAutoConfiguration (migration.enabled), CiasSecurityAutoConfiguration, CiasBootstrapAutoConfiguration, CiasProperties
  • CIAS/cias-tenancy – CiasTenancyConfiguration (enabled, persistence, lookup local, rest, lookup-rest); CIAS/cias-audit – CiasAuditConfiguration (enabled, persistence)
  • CIAS/cias-user, cias-authorization, cias-registration, cias-notification – module switches read at runtime (`${…:false}`), Disabled implementations, EndpointGuard
  • CIAS/cias-authentication – CiasTokenConfiguration (TenantLookupPort mandatory bean, tenantGate, attributeLookup); cias-tenancy-client – CiasTenancyClientConfiguration (lookup remote, base-url, token)
  • CIAS/cias-runtime – application.yml; hub-backend – application.yaml, CiasEmbeddedConfiguration
  • CDMS/cdms-scaffold – CdmsSystemContext.getCiasTopology, cdms-version-registry.yaml (ciasDependencies), CdmsScaffoldService (EMBEDDED_CIAS_CLASSES, EMBEDDED_CIAS_YAML), CdmsReadmeWriter
  • hub-backend – structure/enumerations.yaml MODULE_CIAS (NONE, REMOTE, EMBEDDED), structure/system.yaml
  • commons-persistence – TenantProperties (codamai.persistence.tenant.mode, @NotNull), TenantProvisioningConfiguration
Search