CodamAIDocs
Topicdone

Registering attributes

How a module declares an attribute, why a required attribute needs a default value, and how the attribute catalog comes about.

Variants
optionalrequired with default valuerequired without default value → rejectedpreference of the personper tenant

What this is about

A module registers its attributes in the same declaration as its roles, see Modules register their roles. A declaration is the list with which a module says: “This is what I need.” CIAS reads it during the reconciliation and turns it into three things:

  1. an entry in Keycloak’s user profile, so that an account can store the value at all,
  2. a claim mapper on the module’s client, so that the value arrives in the token,
  3. an entry in CIAS’s attribute catalog, which records who registered the attribute and who may write it.

What a registration contains

Each attribute has six parts:

PartMeaningif missing …
keythe name, exactly like this in the profile and in the tokenrequired
multivalueda list instead of one value. An attribute filter almost always needs a listone value
defaultValuewhat accounts get that are older than the attributeno default value
requiredthe module cannot work without the valueoptional
selfEditablethe person may change the value themselvesadministrators only
bindingUSER: one value per person. USER_IN_TENANT: one value per person and tenantUSER

In Java a module writes this with short building blocks:

Attribute.optional("region").asMultivalued()          // optional, list
Attribute.required("customerId", "unknown")           // required, with default value
Attribute.preference("locale", "de")                  // the person changes it themselves
Attribute.optional("areas").asMultivalued().perTenant() // one list per tenant

A module running separately delivers the same list as JSON under GET /cias/fetch:

Request
GET /cias/fetch
Response
{
  "roles": [ … ],
  "attributes": [
    { "key": "region", "multivalued": true, "defaultValue": null,
      "required": false, "selfEditable": false, "binding": "USER" }
  ]
}

The binding field arrives exactly as in embedded operation. If it is missing, USER applies, as for a module older than the field. CIAS does not guess a value it does not know: the registration of that module then counts like an unreachable module, and the reconciliation leaves everything as it was.

A registration cannot contain format rules such as a length or a pattern. Whether a new rule is safe depends on the values already stored in the accounts, and the module does not know them. Whoever runs the realm maintains such rules by hand in the user profile. CIAS leaves them there.

The variants

How an attribute can be registered

When: required is false. This is how CDMS registers every attribute from the model.

The account may have no value. What the module does then is up to the module. An attribute filter in CDMS refuses the request when the value is missing.

Result: accepted

When: required is true, defaultValue is set.

Keycloak fills in the default value for all accounts that do not have it yet. The value is required of a person filling in a form, never of CIAS writing through the admin API.

Result: accepted

When: required is true, defaultValue is missing or empty.

All accounts older than the attribute would have no value. Keycloak then refuses every change to such an account, including suspending it. Taking the permission away would be the first thing to break.

Result: rejected: the whole declaration of this module, including its roles, status REJECTED

When: selfEditable is true, as for locale.

The person may change the value themselves, also on Keycloak's own account page. Administrators still may too.

Result: accepted

When: binding is USER_IN_TENANT.

The person can have a different value in each tenant. CIAS keeps the values, not Keycloak. See One value per person or per tenant.

Result: accepted

When CIAS rejects a registration

What is wrong with the attributes?
FindingaffectsConsequence
required attribute without default valueone moduleREJECTED: the whole declaration of the module drops out of this run
the same attribute twice, differently, in the same moduleone moduleREJECTED
the catalog keeps the attribute for another module, and this module describes it differentlyone moduleREJECTED
two modules describe the same attribute differently in the same runeverythingSKIPPED: the run writes nothing, for no module
two modules describe the same attribute the same way–no contradiction, both may register it

Why so strict? The user profile is one document for the whole realm. Two different descriptions of the same attribute describe the same field, and whoever wrote last would win. Nobody would notice until a filter returns wrong rows.

What comes about in Keycloak

A reconciliation with a new attribute region
  1. 1
    CIAS→Keycloak
    adds region to the user profile: multivalued, administrator and person may view it, only administrators may change it, no default value, not required
  2. 2
    CIAS→Keycloak
    creates a claim mapper region on the module's client: value of the attribute → claim region in access token, ID token and UserInfo, as a list
  3. 3
    CIAS
    creates the entry region in the catalog, owner cdms
    Result: From now on an account can carry region, and the client's token brings it along

CIAS writes the profile once per run for all modules together, because Keycloak replaces it entirely on every write. Whatever CIAS did not register stays in it, including rules and display names someone maintained by hand. If a module changes its registration, for example from one value to a list, the next reconciliation adjusts profile and mapper.

The attribute catalog

Every logged-in person can read the catalog:

Request
GET /cias/admin/attributes/region
Response
HTTP 200
{
  "key": "region",
  "module": "cdms",
  "displayName": "region",
  "description": null,
  "group": null,
  "multivalued": true,
  "required": false,
  "defaultValue": null,
  "selfEditable": false,
  "binding": "USER",
  "assignableBy": [],
  "deprecatedAt": null
}
FieldMeaning
modulewho registered the attribute. As long as it is registered, no other module can describe it differently
displayName, description, groupfor admin screens. The registration does not name them; so the name is the key
assignableBywhich roles may write the value per tenant. Empty means: platform administrators only. See Who may write an attribute
deprecatedAtset when no module registers the attribute any more. See How an attribute goes away again

The catalog is read-only. Entries are created by the reconciliation alone: an attribute exists because a module needs it. GET /cias/admin/attributes returns all entries; without a login you get 403, for an unknown key 404.

Pitfalls

Next

Sources in the code and the knowledge base
  • commons – models.Attribute (key, multivalued, defaultValue, required, selfEditable, binding; optional, required, preference, asMultivalued, perTenant), models.AttributeBinding
  • CIAS/cias-authorization – RoleReconciliationService (defect, attributeContradictions, attributeTakenFromAnotherModule, writeProfile, deliver, catalogueAttributes), DeclaredAttribute (declare, redeclare, delegateTo)
  • CIAS/cias-authorization – AttributeCatalogService, AttributeAdminController (GET /cias/admin/attributes, /cias/admin/attributes/{key}), AttributeView
  • CIAS/cias-iam-api – ProfileAttribute (requiredForUser); CIAS/cias-iam-keycloak – KeycloakAttributeAdapter (ensureAttributes, apply, ensureClaim)
  • CIAS/cias-authorization/docs/adr – ADR-025, ADR-026, ADR-032, ADR-040; CIAS/cias-authentication/docs/adr – ADR-042
Search