What this is about
An attribute value is a permission. If Anna has region = nord, she sees the data of the north in CDMS. Whoever adds sued for her widens her view without any role changing. That is why, before writing any value per tenant, CIAS asks two things:
- Delegation: may you write this attribute at all?
- Ceiling: may you write this value? You never grant more than you hold yourself.
Which store, which rule
The check applies to values per tenant. The other stores, see Maintain a person’s attributes, are written only by a platform administrator:
| Store | Call | Who may |
|---|---|---|
| Values per tenant | POST /cias/admin/users/{id}/tenant-attributes?tenantKey=… | delegation and ceiling, as on this page |
| Profile in Keycloak | POST /cias/admin/users/{id}/profile-attributes | platform administrators only. For a preference such as locale also the person themselves, on Keycloak’s account page |
| CIAS attributes (notes) | POST /cias/admin/users/{id}/attributes | platform administrators only |
The check, step by step
-
CIASlogged inDoes the request have a valid token?↳ no 403
-
CIASCatalogHas a module registered the attribute?↳ no 403, even for a platform administrator
-
CIASBindingIs the attribute registered per tenant (
USER_IN_TENANT)?↳ no 403, even for a platform administrator -
CIASPlatform administrator?yes → allowed, all further checks are skipped
-
CIASstill registeredDoes a module still register the attribute, or has it been withdrawn?↳ no 403
-
CIASDelegationIs one of the caller's roles in the attribute's
assignableBy?↳ no 403 -
CIASCeilingIs every value within what the caller holds themselves in this tenant?↳ no 403
- Values are written
CIAS checks all attributes of a call before it writes any. If one fails, everything stays as it was. Every refusal looks the same: 403 cias.user.administration-denied. Which attribute and which station is only in the log.
CIAS looks up the person only after the check. Whoever is refused therefore always gets 403, even for an id that does not exist, and so does not learn which persons exist. Only someone who passed the check sees 404 cias.user.not-found.
The delegation
Who may write an attribute is decided neither by the module nor by a screen, but by the installation. It provides a bean DeclaredAttributeDelegation for this: per attribute, the roles whose holders may write it.
@Bean
DeclaredAttributeDelegation attributeDelegation() {
return new DeclaredAttributeDelegation(Map.of("region", Set.of("tenant-admin")));
}
The reconciliation transfers this statement into the catalog on every run. You see it in the field assignableBy under GET /cias/admin/attributes/{key}. It can only be changed through the bean, not through a REST interface. Without a bean, assignableBy is empty everywhere, and only platform administrators write values.
The ceiling as a set diagram
flowchart LR
subgraph A["Anna, tenant-admin in nordbau<br/>own value: region = nord, west"]
direction TB
N["nord"]
W["west"]
end
N -->|"may grant"| OK1["Ben: region = nord"]
W -->|"may grant"| OK2["Ben: region = nord, west"]
X["sued"] -. "not in Anna's value" .-> NO1["Ben: region = sued → 403"]
S["*"] -. "more than anything specific" .-> NO2["Ben: region = * → 403"]
The comparison works exactly the way the attribute filter reads the values: split at commas, spaces trimmed, duplicates count once, * means “everything”. The complete table is in The ceiling.
Your own value here is your value per tenant in CIAS for exactly this tenant. A value that is only in your profile does not count. If you have no value in the tenant, you cannot grant one there.
The variants
When: The token carries a role that counts as platform administrator.
Delegation and ceiling are skipped. A platform administrator holds every permission, including every value. Only an attribute that is not registered, and one that is bound to the person, they may not write here either.
Result: allowed, also for withdrawn attributes
When: region is delegated to tenant-admin, Anna is tenant-admin.
The delegation matches. Now the value decides.
Result: on to the ceiling
When: Anna has nord, west and writes nord for Ben.
nord is contained in Anna's value.
Result: allowed
When: Anna has nord, west and writes nord, sued for Ben.
Anna does not have sued. A single value too many refuses the whole call.
Result: 403, nothing written
When: Anna writes * for Ben, or Anna herself has *.
* switches the filter off completely. Only whoever has * themselves may grant *. Whoever has * may grant any value.
Result: with own *: allowed. Otherwise 403
When: The attribute is not in the catalog, or no module registers it any more.
Not registered: nobody says who may write it, so nobody may. Withdrawn: the delegation belonged to a registration that no longer exists.
Result: 403. Withdrawn ones are written only by a platform administrator
When: The attribute is registered with USER, such as locale or tenant, and someone wants to write it as a value per tenant.
A value per tenant replaces the value from the token in that tenant. A locale per tenant would therefore overwrite what Keycloak says about the person. This is not a question of permission but the wrong store. You maintain such attributes in the profile, see Maintaining a person's attributes.
Result: 403, even for a platform administrator
Reading
Values per tenant can be read by platform administrators and by persons whose own tenant is the one asked for: GET /cias/admin/users/{id}/tenant-attributes?tenantKey=nordbau. Values of other tenants stay unreadable. Whoever may write values must also be able to see them, which is why reading is somewhat looser than writing.