CodamAIDocs
Topicdone

What a revision records

Number, time, person, IP address, browser, and that each database has its own revision log.

Variants
revision numbertimeperson (ID and name)acting person after a user switchIP addressuser agentchange without a userone revision log per database

What this is about

A revision has two parts:

  • the state of the object: all fields as they were after the change,
  • the revision data: number, time, and who made the change from where.

This page is about the second part.

A revision as an example

This is how a revision arrives in the history:

One entry from the history
Request
POST /api/rest/order/7e1…/history
{ "response": ["orderNr", "price"] }
Response
{ "data": [
    { "revision": { "id": "7e1…", "orderNr": "A-1000", "price": 120.5 },
      "revisionMeta": {
        "ref": 42,
        "ts": 1790000200000,
        "ip": "203.0.113.7",
        "useragent": "Mozilla/5.0 (X11; Linux x86_64) …",
        "username": "Anna Muster",
        "actingUsername": null
      },
      "revisionType": "MOD" },
    …
  ],
  "meta": { … } }
Field in the responseColumn in revinfoMeaning
revisionMeta.refidRevision number. You need it for a rollback.
revisionMeta.tstimestampTime in milliseconds since 1 January 1970 (UTC)
revisionMeta.usernameusernameName of the person on whose behalf the change was made. Without a switch, the person from the token.
revisionMeta.actingUsernameacting_usernameName of the logged-in person, up to 255 characters. Only filled after a user switch, otherwise null.
revisionMeta.ipip_addressIP address of the request, up to 45 characters, so IPv6 fits too
revisionMeta.useragentuser_agentbrowser or program, up to 512 characters
–user_idID of the person on whose behalf the change was made (claim sub). It is stored in the database, the history does not return it.
–acting_user_idID of the logged-in person, up to 36 characters. Only filled after a user switch. The history does not return it.
revisionType–ADD, MOD or DEL, see What is audited
revisionrow in <table>_AUDthe state of the object, only the requested fields

Where the values come from

CDMS does not invent any of this. It comes from the request that caused the change:

From the request to the row in revinfo
  1. 1
    Client→CIAS
    sends a writing request with a token
  2. 2
    CIAS
    The filter chain reads the IP address and the user agent from the request.
    IP: first entry in the header X-Forwarded-For; if it is missing, X-Real-IP; if that is missing too, the remote address of the connection. User agent: the header User-Agent, otherwise unknown.
  3. 3
    CIAS
    It reads the user ID and the name from the token.
    ID: claim sub. Name: by default claim name, otherwise preferred_username. Which claims apply can be configured in CIAS.
  4. 4
    CDMS→Database
    When the request ends successfully, Envers writes the revision and takes over these four values.
    Result: The database assigns number and time when writing. After a user switch, the ID and name of the logged-in person are added as the acting person.
Who is in the revision?

When: A person changes data through the API.

User ID, name, IP address and user agent of this request.

When: Lena acts on behalf of Ben with the header user.

user_id and username name Ben, acting_user_id and acting_username name Lena. In the history you see username: "Ben Beispiel" and actingUsername: "Lena Support". This holds with own roles just as with the roles of the target person.

Result: See User switch by header.

When: Code changes data outside an API request, for example a background job.

The revision is written anyway. If there is no request context at all, the four fields stay empty. If the job runs with a context of its own, they contain what the job sets, for example a technical user and internal.

One revision log per database

The table revinfo exists in every database in which CDMS stores data. The numbers count in each database on their own.

flowchart TB
    subgraph S["System database"]
      S1[("revinfo<br/>1, 2, 3 …")]
      S2[("&lt;systemmodel&gt;_AUD")]
    end
    subgraph A["Tenant database A"]
      A1[("revinfo<br/>1, 2, 3 …")]
      A2[("order_table_AUD")]
    end
    subgraph B["Tenant database B"]
      B1[("revinfo<br/>1, 2, 3 …")]
      B2[("order_table_AUD")]
    end

Which database that is depends on the level of the model: system models in the system database, tenant and user models in the database of the tenant. See Model levels: system, tenant, user and Which database? The persistence target.

In operating mode SINGLE everything lives in one database, and there is only one revinfo. See SINGLE and MULTI.

Pitfalls

What comes next

Sources in the code and the knowledge base
  • CDMS/cdms-persistence-database – auditing/AuditRevisionEntity (table revinfo: id, timestamp, user_id, username, acting_user_id, acting_username, ip_address, user_agent), AuditRevisionListener (values from the RequestContext, empty without context, acting person only after a switch), AuditHistoryReader.queryHistory (AuditRevisionMeta without userId), EntityClassFilterService (AuditRevisionEntity in every persistence unit)
  • CDMS/cdms-commons – models/response/AuditRevision, AuditRevisionMeta (ref, ts, ip, useragent, username, actingUsername)
  • CIAS/cias-authentication – JwtSessionFilter (IP from X-Forwarded-For, X-Real-IP, remote address; User-Agent or "unknown"), TokenParser (userId from sub, userName from name/preferred_username), CiasTokenProperties, TenantScope (internal runs)
  • documentation/50-auditierung/01-auditing-und-historie.md
Search