CodamAIDocs
Topicdone

One value per person or per tenant

An attribute applies either to the person in all tenants (USER) or separately per tenant (USER_IN_TENANT). How the right value gets into the token and the attribute filter when a person belongs to several tenants.

Variants
USER: one value for all tenantsUSER_IN_TENANT: one value per tenantperson with one tenantperson with several tenantsCIAS unreachable

What this is about

Anna works for two tenants: nordbau and suedlogistik. In nordbau she is responsible for the region North, in suedlogistik for South. A single value region on the account cannot express that. Which value should it have?

That is why every attribute states at registration what it says something about:

BindingmeansExamplesWhere the value lives
USERone value per person, the same in every tenantlanguage, staff number, display nameon the account in Keycloak, arrives through the token
USER_IN_TENANTone value per person and tenantregions, plants, cost centersin CIAS, per person and tenant

How an attribute becomes per tenant

The module says so at registration, see Registering attributes:

Attribute.optional("region").asMultivalued().perTenant()

Only the registering module knows what it means: CIAS sees that an attribute is called region, but not whether an installation gives a person different regions in two tenants. What CDMS generates from the model is always USER, see Two origins of attributes. A module writes an attribute per tenant into its registration itself.

You write the values via POST /cias/admin/users/{id}/tenant-attributes?tenantKey=…, checked by delegation and ceiling, see Who may write an attribute. This works only for attributes the catalog lists per tenant. CIAS refuses a USER attribute such as locale there, even for a platform administrator.

The scenario

Anna in two tenants
  1. 1
    Admin→CIAS
    writes for Anna in nordbau: region = [nord]
  2. 2
    Admin→CIAS
    writes for Anna in suedlogistik: region = [sued]
  3. 3
    Client→CDMS
    Anna queries orders, tenant nordbau
  4. 4
    CIAS
    checks the token, settles the tenant, fetches Anna's values for nordbau
  5. 5
    CDMS
    filters with region IN (nord)
  6. 6
    Client→CDMS
    Anna chooses suedlogistik and asks again
  7. 7
    CDMS
    filters with region IN (sued)
    Result: The same person, the same token, different rows per tenant

Before and after

flowchart LR
    subgraph T["In Anna's token"]
      TR["region: nord, sued<br/>(one value for the whole account)"]
      TL["locale: de"]
    end
    subgraph C["In CIAS, tenant suedlogistik"]
      CR["region: sued"]
    end
    subgraph E["Effective in suedlogistik"]
      ER["region: sued"]
      EL["locale: de"]
    end
    CR -- "replaces" --> ER
    TL -- "unchanged" --> EL
    TR -. "is replaced" .-> ER

The value from CIAS replaces the value from the token for this key. All other attributes, such as locale, come unchanged from the token. That holds even if CIAS does keep a row for such a key in the tenant: only what the application itself registers with perTenant() is replaced. If CIAS mixed them, the filter in one tenant would be wider than in either one alone, which is exactly what this prevents.

The variants

Which value applies

When: The attribute is registered without a binding or with USER.

CIAS takes the value from the token, the same in every tenant. There is no extra lookup.

Result: value from the token

When: The attribute is registered with perTenant().

On every request with a tenant, CIAS fetches the person's values in this tenant and inserts them.

Result: value from CIAS for the active tenant

When: The person belongs to exactly one tenant.

The same applies as with several: the value comes from CIAS for this one tenant. The difference from USER only shows once a second tenant is added.

Result: value from CIAS

When: The person is a member of several tenants and chooses one of them for the request.

The value comes for the chosen tenant. On a privileged switch into a tenant the person is not a member of, CIAS determines the values beforehand, for the starting tenant, and they keep applying in the target, like the roles. See Switch between tenants.

Result: a separate value per tenant

When: Fetching the values fails.

If CIAS has cached the values of this person in this tenant, those apply, even if they are older. Otherwise CIAS refuses the request. An outage should give nobody more reach and take from nobody what they were already working with.

Result: cached value, or 403 cias.authentication.tenant-not-served

Where the values come from

Who answers the lookup?
Operating modeWhat must be presentLookup
embeddedcias-user with codamai.cias.user.persistence=jpamethod call in the same program
standalonecias-tenancy-client in the module; in CIAS codamai.cias.user.lookup-rest=true and codamai.cias.user.lookup-roles with the role of the asking serviceGET /cias/lookup/users/{sub}/attributes over HTTP
eithera module registers an attribute per tenant, but nothing can look up the valuesthe application does not start, the message names the attributes
standaloneendpoint in CIAS not switched on or role missingevery request with a tenant is refused: 403 cias.authentication.tenant-not-served

CIAS caches the answer per person and tenant for 30 seconds, configurable with codamai.cias.attribute-lookup.ttl. The number of cached pairs is limited by codamai.cias.attribute-lookup.max-entries. So the lookup costs at most one call per person, tenant and half minute. An installation without an attribute per tenant never asks.

A request without a tenant, for example in operating mode SINGLE, has no values per tenant. There the value from the token applies.

Pitfalls

Next

Sources in the code and the knowledge base
  • commons – models.AttributeBinding (USER, USER_IN_TENANT), models.Attribute.perTenant()
  • CIAS/cias-authentication – TokenParser (admit, boundAttributesFor), EffectiveAttributes.resolve, AttributeLookup (exactlyTheDeclaredAttributes), DeclaredTenantBoundAttributes, AttributeLookupProperties (codamai.cias.attribute-lookup.ttl, max-entries), TenantBoundAttributeWiringCheck, RequestAdmission.TENANT_NOT_SERVED
  • CIAS/cias-authorization – AttributeWritePermission (binding), RemoteModuleDeclarationAdapter (binding over GET /cias/fetch)
  • CIAS/cias-user – TenantBoundAttributeService, TenantBoundAttributeReader (cias_user_attribute.tenant_key), LocalTenantBoundAttributeAdapter, AttributeLookupController (GET /cias/lookup/users/{externalUserId}/attributes), CiasUserConfiguration (lookup-rest, lookup-roles)
  • CIAS/cias-tenancy-client – RemoteTenantBoundAttributeAdapter
  • CDMS/cdms-scaffold – CdmsReadmeWriter (Tenant-bound attributes)
  • CIAS/cias-authentication/docs/adr – ADR-042; CIAS/cias-kernel/docs/adr – ADR-022
Search