feat(core): audit implementation#69
Conversation
db2e766 to
acac340
Compare
Code Coverage OverviewLanguages: Java Java / code-coverage/jacocoThe overall coverage in the branch is 91%. The coverage in the branch is 90%. Show a code coverage summary of the most impacted files.
Updated |
b7be593 to
3a4e46a
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces entity audit/history capabilities to IDP-Core using Hibernate Envers, exposing a new REST endpoint to retrieve revision history, and adding supporting schema, security identity extraction, tests, and documentation.
Changes:
- Add Envers audit schema via Flyway and enable Envers delete snapshot storage.
- Add audit retrieval flow (domain port/service + persistence adapter + REST controller + DTOs/mapper).
- Add identity extraction abstraction (
UserIdentityProvider) plus local mock security support, and update tests/docs accordingly.
Reviewed changes
Copilot reviewed 43 out of 45 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| src/test/resources/integration_test/json/audit/v1/getAudit_200_history_update.json | Test JSON payload for audit update scenario |
| src/test/resources/integration_test/json/audit/v1/getAudit_200_history_create.json | Test JSON payload for audit create scenario |
| src/test/resources/db/test/R__2_Insert_entities_test_data.sql | Adjust test entity seed data ordering/content |
| src/test/resources/db/test/R__1_Insert_test_data.sql | Add audited template seed data for tests |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/audit/CustomRevisionListenerTest.java | Unit tests for Envers revision listener behavior |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/api/controller/EntityTemplateControllerTest.java | Update expected template counts due to new seed template |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/api/controller/AuditControllerTest.java | New integration tests for audit endpoint |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/api/auth/UnifiedUserProviderTest.java | Unit tests for unified identity extraction |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/api/auth/mock/MockSecurityConfigurationTest.java | Tests for mock security configuration/filter |
| src/test/java/com/decathlon/idp_core/AbstractIntegrationTest.java | Test configuration tweaks for integration tests |
| src/main/resources/db/migration/V4_1__create_envers_audit_schema.sql | New Flyway migration creating Envers audit tables |
| src/main/resources/application.yml | Enable Envers delete snapshot storage |
| src/main/resources/application-local.yml | Local security/mock-security configuration updates |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/repository/JpaRelationRepository.java | Add revision repository support |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/repository/JpaEntityTemplateRepository.java | Add revision repository support |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/repository/JpaEntityRepository.java | Add revision repository support |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/PostgresEntityAuditAdapter.java | New persistence adapter to query audit history |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/entity/RelationJpaEntity.java | Enable auditing on relation entity |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/entity/PropertyRulesJpaEntity.java | Enable auditing on property rules entity |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/entity/PropertyJpaEntity.java | Enable auditing on property entity |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/entity/EntityJpaEntity.java | Enable auditing on entity entity |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/entity_template/RelationDefinitionJpaEntity.java | Enable auditing on relation definition entity |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/entity_template/PropertyDefinitionJpaEntity.java | Enable auditing on property definition entity |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/entity_template/EntityTemplateJpaEntity.java | Enable auditing on entity template entity |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/audit/UserIdentityProviderHolder.java | Bridge Envers listener to Spring identity provider |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/audit/CustomRevisionListener.java | Custom Envers revision listener implementation |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/audit/CustomRevisionEntity.java | Custom Envers revision entity mapping |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/audit/CustomRevinfoRecord.java | Embeddable record for per-transaction changed-entity metadata |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/entity/EntityAuditDtoOutMapper.java | Map domain audit model to API DTO |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/entity/audit/EntitySnapshotDtoOut.java | New audit snapshot DTO |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/entity/audit/EntityAuditDtoOut.java | New audit response DTO |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/controller/AuditController.java | New REST endpoint to retrieve audit history |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/configuration/SwaggerDescription.java | Swagger constants for audit endpoint |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/configuration/SecurityConfiguration.java | Conditional load when mock security is disabled |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/auth/UserIdentityProvider.java | New abstraction for identity lookup |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/auth/UnifiedUserProvider.java | Implementation of identity extraction from SecurityContext |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/auth/mock/MockSecurityConfiguration.java | Local mock JWT security filter chain |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/auth/mock/exception/MockSecurityConfigurationException.java | Specific exception for mock security setup failures |
| src/main/java/com/decathlon/idp_core/domain/service/entity/EntityAuditService.java | Domain service to retrieve audit history |
| src/main/java/com/decathlon/idp_core/domain/port/audit/EntityAuditPort.java | Domain port for audit retrieval |
| src/main/java/com/decathlon/idp_core/domain/model/entity/EntityAuditInfo.java | Domain audit model record |
| pom.xml | Add Envers dependencies |
| docs/src/static/swagger.yaml | Document new audit endpoint + schemas |
| docs/src/concepts/index.md | Link audit concept page from concepts index |
| docs/src/concepts/audit.md | New audit feature documentation |
| } | ||
| ``` | ||
|
|
||
| ## Step 4: Add Domain Service and REST Endpoint |
There was a problem hiding this comment.
Well not really / in all cases?
There was a problem hiding this comment.
add it with necessary i can add^^
There was a problem hiding this comment.
changed for more fexibility / readability :
Step 4: Add/Update Domain Service and REST Endpoint
Depending on the domain design, you can either reuse existing files to minimize boilerplate, or create dedicated audit files for clean separation of concerns.
- Service: Create
PropertyAuditServiceto handle business logic (like ensuring the parent object exists before querying history). - Controller: Expose the endpoint, using standard DTOs formatted with Jackson's
SnakeCaseStrategy.
Approach 1: Reusing Existing Structures (Recommended for simple resources)
If you already have a PropertyService and a PropertyController, simply append the new functionality directly to them to keep things concise.
- Service: In PropertyService: Inject the PropertyAuditPort and add a getPropertyHistory(UUID id) method.
- Controller: In PropertyController: Expose a nested route following clean RESTful guidelines.
// Within your existing PropertyController.java
@GetMapping("/{propertyId}/history")
public List<PropertyAuditDtoOut> getPropertyHistory(@PathVariable UUID propertyId) {
return auditMapper.fromDomainList(propertyService.getPropertyHistory(propertyId));
}Approach 2: Creating Dedicated Audit Handlers (Recommended for complex auditing rules)
If retrieving audit details requires dedicated permissions, complex filtering, or distinct business rules, decouple them into dedicated files.
-
Service: Establish a PropertyAuditService to guarantee domain invariant verification (e.g., verifying object access permissions before compiling historical records).
-
Controller: Build a focused controller parsing outputs cleanly with Jackson's SnakeCaseStrategy.
package com.company.project.infrastructure.adapters.api;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/api/v1/audit/")
@RequiredArgsConstructor
public class PropertyAuditController {
private final PropertyAuditService auditService;
private final PropertyAuditDtoOutMapper mapper;
@GetMapping("properties/{propertyId}")
public List<PropertyAuditDtoOut> getPropertyAuditHistory(@PathVariable UUID propertyId) {
return mapper.fromDomainList(auditService.getHistory(propertyId));
}
}@RestController
@RequestMapping("/api/v1/audit/")
@RequiredArgsConstructor
public class PropertyAuditController {
private final PropertyAuditService auditService;
private final PropertyAuditDtoOutMapper mapper;
@GetMapping("properties/{propertyId}")
public List<PropertyAuditDtoOut> getPropertyAuditHistory(@PathVariable UUID propertyId) {
return mapper.fromDomainList(auditService.getHistory(propertyId));
}
}0f0c069 to
9e05177
Compare
|



PR Description
📝 Summary of Changes
This Pull Request introduces comprehensive database version control and auditing mechanisms across core entity tables using Hibernate Envers and Spring Data Envers.
Key Architectures & Feature Additions:
hibernate-enversto capture and persist historical snapshots into matching database target tables (*_aud) on all core entity transactions.spring-data-enversand updated repositories (JpaEntityRepository, etc.) to extendRevisionRepository. This unlocks access to the full timeline history natively without relying on complex handwritten native JOIN operations.EntityTrackingRevisionListenercoupled with@ElementCollectionmapping to dynamically log the precise list of elements modified inside a single transaction unit.suborid) across JWT, OAuth2, and OpenID scopes.AnonymousAuthenticationToken) and missing context environments to avoid NullPointerExceptions (NPEs) when interacting with unauthenticated callers, background jobs, or malicious scanner traffic.*_MOD), and deploying optimal query indexes to support scale performance.Review
The reviewer must double-check these points :
!after the type/scope to identify the breakingchange in the release note and ensure we will release a major version.
How to test
🧪 Local Testing & Verification Report
The feature set has been thoroughly validated against local environments using target endpoint APIs. The results below verify successful timeline serialization, event capture types, and active identity mapping logging.
1. Create Operation (Revision 1)
Action: Submitting a
POSTrequest to register a brand new entity (web-api-audit).Audit Trail Output (GET /api/v1/audit/entities/web-service/web-api-audit):
[ { "modified_by": "local-developer", "revision_date": "2026-06-22T06:09:35.637Z", "revision_number": 4, "revision_type": "CREATED", "snapshot": { "id": "a0f8f1ce-b131-4a59-8d89-406232ca2135", "identifier": "web-api-audit", "name": "web-api-audit", "properties": [ { "id": "d01a3ddf-fdae-48d0-9027-09cdda8f121c", "name": "applicationName", "value": "audit" }, { "id": "977a9290-fd8a-47f8-8bef-5506483f9107", "name": "port", "value": "8080" }, { "id": "652d106f-a15c-493e-bd11-3ef23d0e29c1", "name": "environment", "value": "PROD" }, { "id": "4e79545c-1b2d-41d3-a52c-c0e9f597a86a", "name": "ownerEmail", "value": "audit@lazar.com" }, { "id": "9716175f-e6c7-4f96-8ceb-48740aaf498d", "name": "programmingLanguage", "value": "JAVA" }, { "id": "fc6b7538-3a47-4d4f-b4cd-98c4e5069fe3", "name": "baseUrl", "value": "http://www.audit.fr" }, { "id": "a69e2f85-1ebe-4c8a-a371-f3d9639be050", "name": "teamName", "value": "lazar" }, { "id": "13b7f63d-4108-4570-9387-9d08ece20cd7", "name": "version", "value": "1.1.1" }, { "id": "9344c5bc-d94c-4f25-adea-bf6b906bd46e", "name": "protocol", "value": "HTTP" } ], "relations": [], "template_identifier": "web-service" } } ]Verification Status: ✅ Passthrough Successful. revision_number: 1 created with revision_type: "CREATED". Context operator identity local-developer extracted properly.
Action: Submitting a PUT request modifying structural parameter fields (e.g. updating baseUrl and port).
Audit Trail Output:
[ { "modified_by": "local-developer", "revision_date": "2026-06-22T06:17:50.502Z", "revision_number": 5, "revision_type": "UPDATED", "snapshot": { "id": "a0f8f1ce-b131-4a59-8d89-406232ca2135", "identifier": "web-api-audit", "name": "web-api-audit", "properties": [ { "id": "0c4b4099-1fdd-4779-8508-34696bcda5e5", "name": "environment", "value": "PROD" }, { "id": "a458eb16-4cb6-493c-aae0-01605abce081", "name": "version", "value": "1.1.1" }, { "id": "dd6990a3-9dcf-4582-86ae-e76681405023", "name": "protocol", "value": "HTTP" }, { "id": "e6f3d681-26d1-4118-a3b8-082f8b476fb6", "name": "programmingLanguage", "value": "JAVA" }, { "id": "702f6e49-8d40-4a45-890b-84ad5055a490", "name": "applicationName", "value": "audit" }, { "id": "17d86f4b-eca6-4c68-80cc-aa0b05b69ff4", "name": "port", "value": "8084" }, { "id": "dccab136-9068-4b3b-ae56-9482f9860eee", "name": "teamName", "value": "lazar" }, { "id": "175e6033-ea91-4ecc-a700-d84cc7b585b9", "name": "baseUrl", "value": "http://www.audit.com" }, { "id": "44d6de3f-7e49-42f2-ae2a-66059549fc64", "name": "ownerEmail", "value": "audit@lazar.com" } ], "relations": [], "template_identifier": "web-service" } }, { "modified_by": "local-developer", "revision_date": "2026-06-22T06:09:35.637Z", "revision_number": 4, "revision_type": "CREATED", "snapshot": { "id": "a0f8f1ce-b131-4a59-8d89-406232ca2135", "identifier": "web-api-audit", "name": "web-api-audit", "properties": [ { "id": "d01a3ddf-fdae-48d0-9027-09cdda8f121c", "name": "applicationName", "value": "audit" }, { "id": "977a9290-fd8a-47f8-8bef-5506483f9107", "name": "port", "value": "8080" }, { "id": "652d106f-a15c-493e-bd11-3ef23d0e29c1", "name": "environment", "value": "PROD" }, { "id": "4e79545c-1b2d-41d3-a52c-c0e9f597a86a", "name": "ownerEmail", "value": "audit@lazar.com" }, { "id": "9716175f-e6c7-4f96-8ceb-48740aaf498d", "name": "programmingLanguage", "value": "JAVA" }, { "id": "fc6b7538-3a47-4d4f-b4cd-98c4e5069fe3", "name": "baseUrl", "value": "http://www.audit.fr" }, { "id": "a69e2f85-1ebe-4c8a-a371-f3d9639be050", "name": "teamName", "value": "lazar" }, { "id": "13b7f63d-4108-4570-9387-9d08ece20cd7", "name": "version", "value": "1.1.1" }, { "id": "9344c5bc-d94c-4f25-adea-bf6b906bd46e", "name": "protocol", "value": "HTTP" } ], "relations": [], "template_identifier": "web-service" } } ]Verification Status: ✅ Passthrough Successful. System append logs a separate record with revision_number: 2 tagged as UPDATED.
Action: Issuing a DELETE command targeting the runtime asset item tracker.
Audit Trail Output:
[ { "modified_by": "local-developer", "revision_date": "2026-06-22T06:41:49.346Z", "revision_number": 9, "revision_type": "DELETED", "snapshot": { "id": "8d2c9812-b272-445d-9a67-11ee55ef0220", "identifier": "web-api-audit", "name": "web-api-audit", "properties": [ { "id": "9fa8cc88-fee7-4a3e-ac50-fec7060ad1ce", "name": "ownerEmail", "value": "audit@lazar.com" }, { "id": "5d916cbb-1d6e-4973-9c13-8fb62858c109", "name": "port", "value": "8084" }, { "id": "33101c6f-7447-4044-998f-4c38e275be0b", "name": "applicationName", "value": "audit" }, { "id": "045509c2-1350-4ab7-87ec-76e6b74b1749", "name": "baseUrl", "value": "http://www.audit.com" }, { "id": "bafbb337-6dec-4a65-b831-1bd5c1902c8f", "name": "environment", "value": "PROD" }, { "id": "30d37907-0129-46c5-a219-421fddd700d4", "name": "protocol", "value": "HTTP" }, { "id": "596fbac8-ec4a-48ec-8458-8c29b8dfdf4d", "name": "version", "value": "1.1.1" }, { "id": "69ea298e-7d5c-4b9c-8386-c6c2a081aff5", "name": "programmingLanguage", "value": "JAVA" }, { "id": "66a6e3bf-0521-44a7-b3f4-6a43bbcaf347", "name": "teamName", "value": "lazar" } ], "relations": [], "template_identifier": "web-service" } }, { "modified_by": "local-developer", "revision_date": "2026-06-22T06:40:58.279Z", "revision_number": 8, "revision_type": "UPDATED", "snapshot": { "id": "8d2c9812-b272-445d-9a67-11ee55ef0220", "identifier": "web-api-audit", "name": "web-api-audit", "properties": [ ... ]Verification Status: ✅ Passthrough Successful. Final chronological list accurately outputs DELETED state logic (revision_number: 3) with structural string identifiers reset gracefully to null, while maintaining the immutable historical data snapshots from earlier transactions.
Breaking changes (if any)
N/A