What this is about
Not all work starts with a request. A timer cleans up old entries every night, a message listener processes a message from a queue, a job computes reports for every customer. This work has no token and so does not run through the filter chain, and therefore not through the tenant gate either.
That is dangerous. The persistence itself no longer checks whether a tenant is served, because the gate takes care of that. If it gets a tenant key, it routes the work into that tenant’s database, and where automatic creation is enabled, it even creates a new database for an unknown key. A job that works for a suspended or misspelled tenant would not be stopped.
How it works
@Component
class NightlyCleanup {
private final TenantScope scope;
private final CleanupService cleanup;
void run(List<String> tenantKeys) {
for (String tenant : tenantKeys) {
try {
// asks the gate, fills the context, restores it afterwards
scope.runAsTenant(tenant, "svc-nightly-cleanup", cleanup::purgeOldEntries);
} catch (TenantNotServedException refused) {
// tenant suspended, unknown, or CIAS unreachable: skip it
}
}
}
}
-
1CIASchecks the input: tenant, acting identifier and work must be present, the key must be valid
-
2CIASasks the tenant gate: Is
nordbauserved? Same memory, same rule during an outage -
3CIASremembers the previous RequestContext and sets a new one: tenant
nordbau, allowed tenants onlynordbau, personsvc-nightly-cleanup, no roles -
4CIASruns the work
-
5CIASrestores the previous RequestContext, even if the work ends with an errorResult: The work ran in
nordbau, and only there.
The variants
When: nordbau is ACTIVE and within the validity window.
The work runs in the tenant nordbau. The persistence finds nordbau in the list of allowed tenants and routes into its database.
Result: callAsTenant returns the result of the work, runAsTenant returns nothing.
When: nordbau is suspended, closed, expired or unknown, or CIAS is unreachable and nothing is remembered.
The work does not run. TenantScope throws a TenantNotServedException, and the log says for which tenant and as whom it was refused.
Result: The caller catches the exception and continues with the next tenant.
When: A job goes over several tenants and opens its own scope for each, even inside another one.
Every scope remembers the context it found and restores exactly that one.
Result: After the inner scope, the outer one works in its tenant again.
When: The work calls code that requires a role.
The context has no roles, so every role check refuses. A job works with the authority of its own code, not with that of a person.
Result: If a job could give itself roles, any code that can name a tenant could grant itself any right.
Why an acting identifier is required
Besides the tenant, runAsTenant requires an identifier saying as whom the work runs, for example svc-nightly-cleanup. Without it TenantScope refuses. A job that changes data leaves traces in the audit, and “the system” says nothing there. The identifier appears in the revision the same way as, for a request, the person who sent it. As address and browser it records internal.
The three places that may set a tenant
Writing a tenant into the RequestContext means: deciding in which database the work happens. Exactly three places may do this, all in CIAS:
| Place | When | What was checked before |
|---|---|---|
Filter chain (TokenParser) | on every request with a token | token valid, tenant resolved, gate admitted |
Tenant switch (ContextSwitch) | when the header tenant takes effect | realm role, target in the list of allowed tenants; afterwards the filter chain asks the gate for the target |
TenantScope | for work without a request | gate admitted |
Any other code that sets the tenant itself bypasses the gate. To prevent this there is an architecture rule in cias-test-support: TenantContextRules.tenantContextComesFromTheGate(). A module adds it to its tests, and the build fails as soon as a class sets the tenant in the RequestContext itself. CDMS runs this rule in its integration tests.
Pitfalls
Where to go next
- Admit the tenant (tenant gate)
- The CDMS view: Is the tenant served?
- What a revision records: What a revision records