Table of Contents

Auditing

TODO: Introduction

Auditing of entities is done by:

IdmAudit is the only entity in the CzechIdMng system which has Long as its primary key. All the other entities have UUID. This fact is unchangeable and needed by Envers.

New entity, how to enable auditing

There is a simple way of "enabling" versing for an entity in Hibernate envers. It is done simply by writing the annotation @Audited before the entity's definition. We DO NOT USE this way in the CzechIdMng system. Instead, we write this annotation to the individual entity attributes which we want to audit. For example:

 @Audited
 @NotNull
 @Column(name = "disabled", nullable = false)
 private boolean disabled = false;

IdmAuditService

IdmAuditService serves as a mediator between Hibernate envers and IdmAudit. The service provides an access to the audit metadata (IdmAudit) and the versions themselves. The basic methods of this service are divided into findRevision and findVersion. The description of their differences can be found below.

Revision

Revisions in the CzechIdMng system are basic metadata about the individual revisions. The metadata is propagated to the front-end to the agenda itself. Using revisions, we are able to ascertain the following metadata:

The findRevision methods are able to return exactly one revision for the given class, entity ID and revision ID, and also all the revisions for the class and entity ID. Using the getPreviousRevision method, it is possible to get the previous revision according to the ID of the revision.

Version

A version in the CzechIdMng system is a snapshot of the entity at a given time. Methods handling versions do not allow getting all the versions for the entity. Instead, they support getting one version getVersion, the previous version getPreviousVersion, and the difference (diff) between the versions getDiffBetweenVersion. If we need to ascertain the last entity version, we can use the getLastVersionNumber method.

If we do not know the exact entity type (class), an object will be returned as the version. If we want to get the individual values from this unidentifiable type, we can use the getValuesFromVersion method. If we need a list of the changed attributes between the versions, we can use the getDiffBetweenVersion method.

A complete list of the audited entities can be acquired by using the getAllAuditedEntitiesNames method.

Graphical description of the differences between revision and version

Exact description of the individual methods

The current version of these methods can be found in the IdmAuditService interface. The most important methods have been chosen.

<T> T findRevision(Class<T> classType, UUID entityId, Long revisionId)

The method returns one revision, if it exists, or it returns an exception RevisionDoesNotExistException

<T> List<IdmAudit> findRevisions(Class<T> classType, UUID entityId)

The method returns all revisions for the entity. If no revision is found, an empty list is returned.

<T> T getPreviousVersion(T entity, Long currentRevId)

It returns the exact type of version, to the previous revision. The previous version is established using the revision ID.

Object getPreviousVersion(Long currentRevId)

It returns the previous entity version for the id revision. The version type cannot be determined easily at this moment.

<T> T getPreviousVersion(Class<T> entityClass, UUID entityId, Long currentRevId)

The method returns the exact version type which is determined by revision ID and entity ID.

<T> Number getLastRevizionNumber(Class<T> entityClass, UUID entityId)

The method returns the last available revision number.

<T> List<String> getNameChangedColumns(Class<T> entityClass, UUID entityId, Long currentRevId, T currentEntity)

It returns a list of changed entity attributes. Note that only attributes with the @Audited annotation are accepted.

List<String> getAllAuditedEntitiesNames()

A list of all entities containing at least one @Audited annotation is returned.

Map<String, Object> getDiffBetweenVersion(String clazz, Long firstRevId, Long secondRevId)

A map is returned, where the key is the name of the attribute which has been changed in the second revision. The value is then the changed value from the second version.

Map<String, Object> getValuesFromVersion(Object revisionObject)

If we do not know the exact version type, we can get it using PropertyUtils.getPropertyDescriptor(getPropertyDescriptor)

REST endpoints

The information about audit is located on the /audits endpoint. The right for endpoints AUDIT_WRITE does not exist for audit. All the other information about audit are secured using AUDIT_READ. If a user owns a role with this permission, he is able, for example, to display data about users, to which he does not have to have any rights at all. This situation is desired.

Apart from the basic endpoints, the REST controller is extended by these endpoints.

/{revId}/diff/previous

The previous revisions is returned to the revision ID. If it does not exist, http status 404 is returned.

/{firstRevId}/diff/{secondRevId}

The endpoint compares the difference between two versions of revisions.

Front-end

Dynamic audit detail

The same detail has been created for all entities to display audit detail. This detail enables displaying and comparison of two revisions including the difference of their version values.

IdmAuditListener

The listener IdmAuditListener handles filling in of revision metadata.

At the moment, it performs these operations:

If it is the MOD (modification) type modification, comparison with the previous revision is done and the attribute changedColumns is given the names of the changed attributes.

Temporary errors

Two instances of saving in one transaction

During synchronization (and not only there), a problem has been found with multiple saving of one entity in one transaction. For Envers, one transaction means one revision (the revision can then contain multiple versions/snapshots of various entities. If a creation of an entity is done in a transaction and then, after saving it, it is updated, the revision will contain two versions/snapshots of the entity. The problem comes when we want to fill in the list of changed attributes in the version because the previous version has not been committed yet.

Temporary solution: The functionality of changed attributes is temporarily disabled, the list of changed attributes can only be seen in the detail.

Future development

Audit for identities

To make operating with the audit for identities better (for example filtering by username), a FE agenda was created for searching identities with their relationship. 6 new columns were added for the entity IdmAudit that are filled in the IdmAuditListener. These columns are filled only when the audited entity implements the interface AuditSearchable (for example IdmIdentityRole).

Columns description

The use of Sub owner columns is recommended only for intersection tables. The example of filling the attributes (IdmIdentityRole):

@Override
public String getOwnerId() {
	return this.getIdentityContract().getIdentity().getId().toString();
}
 
@Override
public String getOwnerCode() {
	return this.getIdentityContract().getIdentity().getCode();
}
 
@Override
public String getOwnerType() {
	return IdmIdentity.class.getName();
}
 
@Override
public String getSubOwnerId() {
	return this.getRole().getId().toString();
}
 
@Override
public String getSubOwnerCode() {
	return this.getRole().getCode();
}
 
@Override
public String getSubOwnerType() {
	return IdmRole.class.getName();
}

Endpoint for getting entities with their relations

Endpoint for getting some entity with their relation is /audit/search/entity (IdmAuditController). The parameter entity is required, the value is the name of the class. For this class the implementation of AbstractAuditEntityService is found (service method DefaultAuditService (findEntityWithRelation))