diff --git a/.github/workflows/verify-hibernate.yml b/.github/workflows/verify-hibernate.yml index 2ce0916e3..cc571790a 100644 --- a/.github/workflows/verify-hibernate.yml +++ b/.github/workflows/verify-hibernate.yml @@ -51,4 +51,4 @@ jobs: run: mvn license:check --batch-mode - name: Run tests & javadoc - run: mvn verify javadoc:javadoc --batch-mode -Djpa.vendor=hibernate -Dlogging.level.org.hibernate.collection.spi.AbstractPersistentCollection=ERROR \ No newline at end of file + run: mvn clean verify javadoc:javadoc --batch-mode -Djpa.vendor=hibernate -Dlogging.level.org.hibernate.collection.spi.AbstractPersistentCollection=ERROR \ No newline at end of file diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 310faa86f..d86eeb49a 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -51,4 +51,4 @@ jobs: run: mvn license:check --batch-mode - name: Run tests & javadoc - run: mvn verify javadoc:javadoc --batch-mode \ No newline at end of file + run: mvn clean verify javadoc:javadoc --batch-mode \ No newline at end of file diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java index 9e232a316..ba7235486 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java @@ -15,6 +15,7 @@ import java.util.Map; import java.util.stream.Collectors; import org.eclipse.hawkbit.ContextAware; +import org.eclipse.hawkbit.audit.AuditContextProvider; import org.eclipse.hawkbit.im.authentication.SpRole; import org.eclipse.hawkbit.tenancy.TenantAware.DefaultTenantResolver; import org.eclipse.hawkbit.tenancy.TenantAware.TenantResolver; @@ -115,6 +116,12 @@ public class SecurityAutoConfiguration { return new SpringSecurityAuditorAware(); } + @Bean + @ConditionalOnMissingBean + public AuditContextProvider auditContextProvider() { + return AuditContextProvider.getInstance(); + } + /** * @param tenantAware singleton bean * @return tenantAware {@link SystemSecurityContext} diff --git a/hawkbit-ddi/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java b/hawkbit-ddi/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java index 03e1a0fc4..a2558eb96 100644 --- a/hawkbit-ddi/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java +++ b/hawkbit-ddi/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java @@ -23,6 +23,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; import org.eclipse.hawkbit.artifact.repository.urlhandler.ArtifactUrlHandler; +import org.eclipse.hawkbit.audit.AuditLog; import org.eclipse.hawkbit.ddi.json.model.DdiActionFeedback; import org.eclipse.hawkbit.ddi.json.model.DdiActionHistory; import org.eclipse.hawkbit.ddi.json.model.DdiActivateAutoConfirmation; @@ -451,6 +452,7 @@ public class DdiRootController implements DdiRootControllerRestApi { } @Override + @AuditLog(entity = "DDI", type = AuditLog.Type.UPDATE, message = "Activate Auto Confirmation", logResponse = true) public ResponseEntity activateAutoConfirmation( final String tenant, final String controllerId, final DdiActivateAutoConfirmation body) { final String initiator = body == null ? null : body.getInitiator(); @@ -462,6 +464,7 @@ public class DdiRootController implements DdiRootControllerRestApi { } @Override + @AuditLog(entity = "DDI", type = AuditLog.Type.UPDATE, message = "Deactivate Auto Confirmation", logResponse = true) public ResponseEntity deactivateAutoConfirmation(final String tenant, final String controllerId) { log.debug("Deactivate auto-confirmation request for device ‘{}‘", controllerId); confirmationManagement.deactivateAutoConfirmation(controllerId); diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java index 5903160cb..c343db58b 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java @@ -26,6 +26,7 @@ import java.util.stream.Collectors; import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.audit.AuditLog; import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; import org.eclipse.hawkbit.dmf.amqp.api.MessageType; @@ -406,6 +407,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService { } } + @AuditLog(entity = "DMF", type = AuditLog.Type.DELETE, message = "Delete Target", logResponse = true) private void deleteTarget(final Message message) { final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL); controllerManagement.deleteExistingTarget(thingId); diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtBasicAuthResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtBasicAuthResource.java index 3a8adda7a..1e4a10116 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtBasicAuthResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtBasicAuthResource.java @@ -9,6 +9,7 @@ */ package org.eclipse.hawkbit.mgmt.rest.resource; +import org.eclipse.hawkbit.audit.AuditLog; import org.eclipse.hawkbit.mgmt.json.model.auth.MgmtUserInfo; import org.eclipse.hawkbit.mgmt.rest.api.MgmtBasicAuthRestApi; import org.eclipse.hawkbit.tenancy.TenantAware; @@ -28,6 +29,7 @@ public class MgmtBasicAuthResource implements MgmtBasicAuthRestApi { } @Override + @AuditLog(entity = "BasicAuth", type = AuditLog.Type.READ, message = "Validate Basic Auth") public ResponseEntity validateBasicAuth() { final MgmtUserInfo userInfo = new MgmtUserInfo(); userInfo.setUsername(tenantAware.getCurrentUsername()); diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java index ee739cbad..68a426bef 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java @@ -21,6 +21,7 @@ import java.util.stream.Collectors; import jakarta.validation.ValidationException; import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.audit.AuditLog; import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata; import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadataBodyPut; import org.eclipse.hawkbit.mgmt.json.model.PagedList; @@ -168,12 +169,14 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi { } @Override + @AuditLog(entity = "DistributionSet", type = AuditLog.Type.DELETE, message = "Delete Distribution Set") public ResponseEntity deleteDistributionSet(final Long distributionSetId) { distributionSetManagement.delete(distributionSetId); return ResponseEntity.ok().build(); } @Override + @AuditLog(entity = "DistributionSet", type = AuditLog.Type.UPDATE, message = "Update Distribution Set") public ResponseEntity updateDistributionSet( final Long distributionSetId, final MgmtDistributionSetRequestBodyPut toUpdate) { @@ -343,6 +346,7 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi { } @Override + @AuditLog(entity = "DistributionSet", type = AuditLog.Type.DELETE, message = "Delete Assigned Distribution Set") public ResponseEntity deleteAssignSoftwareModules(final Long distributionSetId, final Long softwareModuleId) { distributionSetManagement.unassignSoftwareModule(distributionSetId, softwareModuleId); return ResponseEntity.ok().build(); @@ -396,6 +400,7 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi { } @Override + @AuditLog(entity = "DistributionSet", type = AuditLog.Type.DELETE, message = "Invalidate Distribution Set") public ResponseEntity invalidateDistributionSet( final Long distributionSetId, final MgmtInvalidateDistributionSetRequestBody invalidateRequestBody) { distributionSetInvalidationManagement diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetTagResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetTagResource.java index e5cb09b48..ed5d2ad34 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetTagResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetTagResource.java @@ -12,6 +12,7 @@ package org.eclipse.hawkbit.mgmt.rest.resource; import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.audit.AuditLog; import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet; import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTag; @@ -99,6 +100,7 @@ public class MgmtDistributionSetTagResource implements MgmtDistributionSetTagRes } @Override + @AuditLog(entity = "DistributionSetTag", type = AuditLog.Type.UPDATE, message = "Update Distribution Set Tag") public ResponseEntity updateDistributionSetTag( final Long distributionsetTagId, final MgmtTagRequestBodyPut restDSTagRest) { @@ -114,6 +116,7 @@ public class MgmtDistributionSetTagResource implements MgmtDistributionSetTagRes } @Override + @AuditLog(entity = "DistributionSetTag", type = AuditLog.Type.DELETE, message = "Delete Distribution Set Tag") public ResponseEntity deleteDistributionSetTag( final Long distributionsetTagId) { log.debug("Delete {} distribution set tag", distributionsetTagId); @@ -169,6 +172,7 @@ public class MgmtDistributionSetTagResource implements MgmtDistributionSetTagRes } @Override + @AuditLog(entity = "DistributionSetTag", type = AuditLog.Type.UPDATE, message = "Unassign Distribution Set From Tag") public ResponseEntity unassignDistributionSet( final Long distributionsetTagId, final Long distributionsetId) { @@ -178,6 +182,7 @@ public class MgmtDistributionSetTagResource implements MgmtDistributionSetTagRes } @Override + @AuditLog(entity = "DistributionSetTag", type = AuditLog.Type.UPDATE, message = "Unassign Distribution Sets From Tag") public ResponseEntity unassignDistributionSets( final Long distributionsetTagId, final List distributionsetIds) { diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetTypeResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetTypeResource.java index ecbe651f2..7965918bb 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetTypeResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetTypeResource.java @@ -12,6 +12,7 @@ package org.eclipse.hawkbit.mgmt.rest.resource; import java.util.Collections; import java.util.List; +import org.eclipse.hawkbit.audit.AuditLog; import org.eclipse.hawkbit.mgmt.json.model.MgmtId; import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetType; @@ -87,6 +88,7 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR } @Override + @AuditLog(entity = "DistributionSetType", type = AuditLog.Type.DELETE, message = "Delete Distribution Set Type") public ResponseEntity deleteDistributionSetType(final Long distributionSetTypeId) { distributionSetTypeManagement.delete(distributionSetTypeId); @@ -94,6 +96,7 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR } @Override + @AuditLog(entity = "DistributionSetType", type = AuditLog.Type.UPDATE, message = "Update Distribution Set Type") public ResponseEntity updateDistributionSetType( final Long distributionSetTypeId, final MgmtDistributionSetTypeRequestBodyPut restDistributionSetType) { final DistributionSetType updated = distributionSetTypeManagement.update(entityFactory.distributionSetType() @@ -153,6 +156,7 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR } @Override + @AuditLog(entity = "DistributionSetType", type = AuditLog.Type.UPDATE, message = "Remove Mandatory Module From Distribution Set Type") public ResponseEntity removeMandatoryModule(final Long distributionSetTypeId, final Long softwareModuleTypeId) { distributionSetTypeManagement.unassignSoftwareModuleType(distributionSetTypeId, softwareModuleTypeId); return ResponseEntity.ok().build(); @@ -164,6 +168,7 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR } @Override + @AuditLog(entity = "DistributionSetType", type = AuditLog.Type.UPDATE, message = "Add Mandatory Module From Distribution Set Type") public ResponseEntity addMandatoryModule(final Long distributionSetTypeId, final MgmtId smtId) { distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(distributionSetTypeId, Collections.singletonList(smtId.getId())); return ResponseEntity.ok().build(); diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadArtifactResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadArtifactResource.java index 3083239ce..c8ede6bcd 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadArtifactResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadArtifactResource.java @@ -25,7 +25,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.rest.util.FileStreamingUtil; import org.eclipse.hawkbit.rest.util.HttpUtil; import org.eclipse.hawkbit.rest.util.RequestResponseContextHolder; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java index 998e0898c..219d2fd06 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java @@ -16,6 +16,7 @@ import java.util.Optional; import jakarta.validation.ValidationException; import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.audit.AuditLog; import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutResponseBody; import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutRestRequestBodyPost; @@ -180,24 +181,28 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi { } @Override + @AuditLog(entity = "Rollout", type = AuditLog.Type.UPDATE, message = "Start Rollout") public ResponseEntity start(final Long rolloutId) { this.rolloutManagement.start(rolloutId); return ResponseEntity.ok().build(); } @Override + @AuditLog(entity = "Rollout", type = AuditLog.Type.UPDATE, message = "Pause Rollout") public ResponseEntity pause(final Long rolloutId) { this.rolloutManagement.pauseRollout(rolloutId); return ResponseEntity.ok().build(); } @Override + @AuditLog(entity = "Rollout", type = AuditLog.Type.DELETE, message = "Delete Rollout") public ResponseEntity delete(final Long rolloutId) { this.rolloutManagement.delete(rolloutId); return ResponseEntity.ok().build(); } @Override + @AuditLog(entity = "Rollout", type = AuditLog.Type.UPDATE, message = "Resume Rollout") public ResponseEntity resume(final Long rolloutId) { this.rolloutManagement.resumeRollout(rolloutId); return ResponseEntity.ok().build(); @@ -276,6 +281,7 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi { } @Override + @AuditLog(entity = "Rollout", type = AuditLog.Type.UPDATE, message = "Trigger Next Rollout Group") public ResponseEntity triggerNextGroup(final Long rolloutId) { this.rolloutManagement.triggerNextGroup(rolloutId); return ResponseEntity.ok().build(); diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResource.java index fbcc692b4..9872085eb 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResource.java @@ -20,6 +20,7 @@ import jakarta.validation.ValidationException; import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.artifact.repository.urlhandler.ArtifactUrlHandler; +import org.eclipse.hawkbit.audit.AuditLog; import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifact; import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule; @@ -146,6 +147,7 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi { } @Override + @AuditLog(entity = "SoftwareModule", type = AuditLog.Type.DELETE, message = "Delete Software Module Artifact") public ResponseEntity deleteArtifact(final Long softwareModuleId, final Long artifactId) { findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId); artifactManagement.delete(artifactId); @@ -223,6 +225,7 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi { } @Override + @AuditLog(entity = "SoftwareModule", type = AuditLog.Type.DELETE, message = "Delete Software Module") public ResponseEntity deleteSoftwareModule(final Long softwareModuleId) { final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null); softwareModuleManagement.delete(module.getId()); @@ -274,6 +277,7 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi { } @Override + @AuditLog(entity = "SoftwareModule", type = AuditLog.Type.DELETE, message = "Delete Software Module Metadata") public ResponseEntity deleteMetadata(final Long softwareModuleId, final String metadataKey) { softwareModuleManagement.deleteMetaData(softwareModuleId, metadataKey); return ResponseEntity.ok().build(); diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleTypeResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleTypeResource.java index fa80aa946..cef721b95 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleTypeResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleTypeResource.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.mgmt.rest.resource; import java.util.List; +import org.eclipse.hawkbit.audit.AuditLog; import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleType; import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleTypeRequestBodyPost; @@ -74,6 +75,7 @@ public class MgmtSoftwareModuleTypeResource implements MgmtSoftwareModuleTypeRes } @Override + @AuditLog(entity = "SoftwareModuleType", type = AuditLog.Type.DELETE, message = "Delete Software Module Type") public ResponseEntity deleteSoftwareModuleType(final Long softwareModuleTypeId) { softwareModuleTypeManagement.delete(softwareModuleTypeId); return ResponseEntity.ok().build(); diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSystemManagementResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSystemManagementResource.java index ffd99dd05..9660a4c09 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSystemManagementResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSystemManagementResource.java @@ -12,7 +12,6 @@ package org.eclipse.hawkbit.mgmt.rest.resource; import java.util.Collection; import java.util.Collections; import java.util.Objects; -import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResource.java index 1d374b980..1d4460b90 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResource.java @@ -12,6 +12,7 @@ package org.eclipse.hawkbit.mgmt.rest.resource; import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.audit.AuditLog; import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet; import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtDistributionSetAutoAssignment; @@ -127,6 +128,7 @@ public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestA } @Override + @AuditLog(entity = "TargetFilter", type = AuditLog.Type.DELETE, message = "Delete Target Filter") public ResponseEntity deleteFilter(final Long filterId) { filterManagement.delete(filterId); log.debug("{} target filter query deleted, return status {}", filterId, HttpStatus.OK); @@ -169,6 +171,7 @@ public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestA } @Override + @AuditLog(entity = "TargetFilter", type = AuditLog.Type.DELETE, message = "Delete Target Filter Assigned Distribution Set") public ResponseEntity deleteAssignedDistributionSet(final Long filterId) { filterManagement.updateAutoAssignDS(entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(null)); return ResponseEntity.noContent().build(); diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java index b2f9fb0ac..050002d57 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import jakarta.validation.ValidationException; import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.audit.AuditLog; import org.eclipse.hawkbit.mgmt.json.model.MgmtId; import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata; import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadataBodyPut; @@ -138,6 +139,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi { } @Override + @AuditLog(entity = "Target", type = AuditLog.Type.UPDATE, message = "Update Target") public ResponseEntity updateTarget(final String targetId, final MgmtTargetRequestBody targetRest) { if (targetRest.getRequestAttributes() != null) { if (Boolean.TRUE.equals(targetRest.getRequestAttributes())) { @@ -172,6 +174,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi { } @Override + @AuditLog(entity = "Target", type = AuditLog.Type.DELETE, message = "Delete Target") public ResponseEntity deleteTarget(final String targetId) { this.targetManagement.deleteByControllerID(targetId); log.debug("{} target deleted, return status {}", targetId, HttpStatus.OK); @@ -179,12 +182,14 @@ public class MgmtTargetResource implements MgmtTargetRestApi { } @Override + @AuditLog(entity = "Target", type = AuditLog.Type.UPDATE, message = "Unassign Target Type") public ResponseEntity unassignTargetType(final String targetId) { this.targetManagement.unassignType(targetId); return ResponseEntity.ok().build(); } @Override + @AuditLog(entity = "Target", type = AuditLog.Type.UPDATE, message = "Assign Target") public ResponseEntity assignTargetType(final String targetId, final MgmtId targetTypeId) { this.targetManagement.assignType(targetId, targetTypeId.getId()); return ResponseEntity.ok().build(); @@ -245,6 +250,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi { } @Override + @AuditLog(entity = "Target", type = AuditLog.Type.UPDATE, message = "Cancel Target Action") public ResponseEntity cancelAction(final String targetId, final Long actionId, final boolean force) { final Action action = deploymentManagement.findAction(actionId) .orElseThrow(() -> new EntityNotFoundException(Action.class, actionId)); @@ -266,6 +272,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi { } @Override + @AuditLog(entity = "Target", type = AuditLog.Type.UPDATE, message = "Update Target Action") public ResponseEntity updateAction(final String targetId, final Long actionId, final MgmtActionRequestBodyPut actionUpdate) { Action action = deploymentManagement.findAction(actionId) @@ -285,6 +292,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi { } @Override + @AuditLog(entity = "Target", type = AuditLog.Type.UPDATE, message = "Cancel Target Action Confirmation") public ResponseEntity updateActionConfirmation(final String targetId, final Long actionId, final MgmtActionConfirmationRequestBodyPut actionConfirmation) { log.debug("updateActionConfirmation with data [targetId={}, actionId={}]: {}", targetId, actionId, actionConfirmation); @@ -369,6 +377,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi { } @Override + @AuditLog(entity = "Target", type = AuditLog.Type.UPDATE, message = "Set Distribution Set To Target") public ResponseEntity postAssignedDistributionSet( final String targetId, final MgmtDistributionSetAssignments dsAssignments, final Boolean offline) { if (offline != null && offline) { @@ -452,6 +461,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi { } @Override + @AuditLog(entity = "Target", type = AuditLog.Type.DELETE, message = "Delete Target Metadata") public ResponseEntity deleteMetadata(final String targetId, final String metadataKey) { targetManagement.deleteMetaData(targetId, metadataKey); return ResponseEntity.ok().build(); @@ -472,6 +482,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi { } @Override + @AuditLog(entity = "Target", type = AuditLog.Type.UPDATE, message = "Activate Target Auto Confirmation") public ResponseEntity activateAutoConfirm(final String targetId, final MgmtTargetAutoConfirmUpdate update) { final String initiator = getNullIfEmpty(update, MgmtTargetAutoConfirmUpdate::getInitiator); final String remark = getNullIfEmpty(update, MgmtTargetAutoConfirmUpdate::getRemark); @@ -480,6 +491,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi { } @Override + @AuditLog(entity = "Target", type = AuditLog.Type.UPDATE, message = "Deactivate Target Auto Confirmation") public ResponseEntity deactivateAutoConfirm(final String targetId) { confirmationManagement.deactivateAutoConfirmation(targetId); return new ResponseEntity<>(HttpStatus.OK); diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResource.java index 077dd25af..80d14094e 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResource.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicReference; import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.audit.AuditLog; import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTag; import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTagRequestBodyPut; @@ -112,6 +113,7 @@ public class MgmtTargetTagResource implements MgmtTargetTagRestApi { } @Override + @AuditLog(entity = "TargetTag", type = AuditLog.Type.DELETE, message = "Delete Target Tag") public ResponseEntity deleteTargetTag(final Long targetTagId) { log.debug("Delete {} target tag", targetTagId); final TargetTag targetTag = findTargetTagById(targetTagId); @@ -166,6 +168,7 @@ public class MgmtTargetTagResource implements MgmtTargetTagRestApi { } @Override + @AuditLog(entity = "TargetTag", type = AuditLog.Type.UPDATE, message = "Unassign Target From Target Tag") public ResponseEntity unassignTarget(final Long targetTagId, final String controllerId) { log.debug("Unassign target {} for target tag {}", controllerId, targetTagId); this.targetManagement.unassignTag(List.of(controllerId), targetTagId); @@ -173,6 +176,7 @@ public class MgmtTargetTagResource implements MgmtTargetTagRestApi { } @Override + @AuditLog(entity = "TargetTag", type = AuditLog.Type.UPDATE, message = "Unassign Targets From Target Tag") public ResponseEntity unassignTargets( final Long targetTagId, final OnNotFoundPolicy onNotFoundPolicy, final List controllerIds) { log.debug("Unassign {} targets for target tag {}", controllerIds.size(), targetTagId); diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeResource.java index b5192619c..0ffc1554d 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeResource.java @@ -12,6 +12,7 @@ package org.eclipse.hawkbit.mgmt.rest.resource; import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.audit.AuditLog; import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetType; import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetTypeAssignment; @@ -79,6 +80,7 @@ public class MgmtTargetTypeResource implements MgmtTargetTypeRestApi { } @Override + @AuditLog(entity = "TargetType", type = AuditLog.Type.DELETE, message = "Delete Target Type") public ResponseEntity deleteTargetType(final Long targetTypeId) { log.debug("Delete {} target type", targetTypeId); targetTypeManagement.delete(targetTypeId); @@ -109,12 +111,14 @@ public class MgmtTargetTypeResource implements MgmtTargetTypeRestApi { } @Override + @AuditLog(entity = "TargetType", type = AuditLog.Type.DELETE, message = "Remove Compatible Distribution Set From Target Type") public ResponseEntity removeCompatibleDistributionSet(final Long targetTypeId, final Long distributionSetTypeId) { targetTypeManagement.unassignDistributionSetType(targetTypeId, distributionSetTypeId); return ResponseEntity.ok().build(); } @Override + @AuditLog(entity = "TargetType", type = AuditLog.Type.UPDATE, message = "Add Compatible Distribution Set To Target Type") public ResponseEntity addCompatibleDistributionSets( final Long targetTypeId, final List distributionSetTypeIds) { targetTypeManagement.assignCompatibleDistributionSetTypes( diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTenantManagementResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTenantManagementResource.java index 90a9744ef..e4cdee7fb 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTenantManagementResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTenantManagementResource.java @@ -16,6 +16,7 @@ import java.util.Map; import java.util.Objects; import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.audit.AuditLog; import org.eclipse.hawkbit.mgmt.json.model.system.MgmtSystemTenantConfigurationValue; import org.eclipse.hawkbit.mgmt.json.model.system.MgmtSystemTenantConfigurationValueRequest; import org.eclipse.hawkbit.mgmt.rest.api.MgmtTenantManagementRestApi; @@ -72,6 +73,7 @@ public class MgmtTenantManagementResource implements MgmtTenantManagementRestApi } @Override + @AuditLog(entity = "TenantConfiguration", type = AuditLog.Type.DELETE, message = "Delete Tenant Configuration Value") public ResponseEntity deleteTenantConfigurationValue(final String keyName) { //Default DistributionSet Type cannot be deleted as is part of TenantMetadata if (isDefaultDistributionSetTypeKey(keyName)) { @@ -90,6 +92,7 @@ public class MgmtTenantManagementResource implements MgmtTenantManagementRestApi } @Override + @AuditLog(entity = "TenantConfiguration", type = AuditLog.Type.UPDATE, message = "Update Tenant Configuration Value") public ResponseEntity updateTenantConfigurationValue( final String keyName, final MgmtSystemTenantConfigurationValueRequest configurationValueRest) { Serializable configurationValue = configurationValueRest.getValue(); @@ -106,6 +109,7 @@ public class MgmtTenantManagementResource implements MgmtTenantManagementRestApi } @Override + @AuditLog(entity = "TenantConfiguration", type = AuditLog.Type.UPDATE, message = "Update Tenant Configuration") public ResponseEntity> updateTenantConfiguration( final Map configurationValueMap) { final boolean containsNull = configurationValueMap.keySet().stream().anyMatch(Objects::isNull); diff --git a/hawkbit-security-core/pom.xml b/hawkbit-security-core/pom.xml index d61d62c5b..a36656dd5 100644 --- a/hawkbit-security-core/pom.xml +++ b/hawkbit-security-core/pom.xml @@ -36,6 +36,10 @@ org.springframework.security spring-security-oauth2-core + + org.springframework.security + spring-security-aspects + org.springframework.security spring-security-web diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditContextProvider.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditContextProvider.java new file mode 100644 index 000000000..f85a6c145 --- /dev/null +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditContextProvider.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.audit; + +import java.util.Optional; + +import lombok.NoArgsConstructor; +import org.eclipse.hawkbit.tenancy.TenantAware; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.AuditorAware; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE) +@SuppressWarnings("java:S6548") // java:S6548 - singleton holder ensures static access to spring resources in some places +public class AuditContextProvider { + + private static final AuditContextProvider INSTANCE = new AuditContextProvider(); + + private TenantAware.TenantResolver resolver; + private AuditorAware auditorAware; + + public static AuditContextProvider getInstance() { + return INSTANCE; + } + + @Autowired + public void setTenantResolver(final TenantAware.TenantResolver resolver) { + this.resolver = resolver; + } + + @Autowired + public void setAuditorAware(final AuditorAware auditorAware) { + this.auditorAware = auditorAware; + } + + public AuditContext getAuditContext() { + return new AuditContext( + Optional.ofNullable(resolver.resolveTenant()).orElse("n/a"), + Optional.ofNullable(auditorAware).flatMap(AuditorAware::getCurrentAuditor).orElse("system")); + } + + public record AuditContext(String tenant, String username) {} +} \ No newline at end of file diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditLog.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditLog.java new file mode 100644 index 000000000..2deb8929e --- /dev/null +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditLog.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.audit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface AuditLog { + + enum Level { + INFO, WARN, ERROR + } + + enum Type { + CREATE, READ, UPDATE, DELETE, EXECUTE + } + + Level level() default Level.INFO; + + Type type(); + + String entity(); + + String message() default ""; + + String[] logParams() default {}; + + boolean logResponse() default false; +} \ No newline at end of file diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditLogger.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditLogger.java new file mode 100644 index 000000000..fbb0c3a02 --- /dev/null +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditLogger.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.audit; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class AuditLogger { + + private static final AuditContextProvider AUDIT_CONTEXT_PROVIDER = AuditContextProvider.getInstance(); + + public static void info(final String entity, final String message) { + logMessage(entity, message, AuditLog.Level.INFO); + } + + public static void info(final String tenant, final String username, final String entity, final String message) { + logMessage(tenant, username, entity, message, AuditLog.Level.INFO); + } + + public static void error(final String entity, final String message) { + logMessage(entity, message, AuditLog.Level.ERROR); + } + + public static void error(final String tenant, final String username, final String entity, final String message) { + logMessage(tenant, username, entity, message, AuditLog.Level.ERROR); + } + + public static void warn(final String entity, final String message) { + logMessage(entity, message, AuditLog.Level.WARN); + } + + public static void warn(final String tenant, final String username, final String entity, final String message) { + logMessage(tenant, username, entity, message, AuditLog.Level.WARN); + } + + private static void logMessage(final String entity, final String message, final AuditLog.Level level) { + logMessage(AUDIT_CONTEXT_PROVIDER.getAuditContext().tenant(), AUDIT_CONTEXT_PROVIDER.getAuditContext().username(), entity, message, + level); + } + + private static void logMessage( + final String tenant, final String username, final String entity, final String message, final AuditLog.Level level) { + final String logMessage = String.format("[%s] User: %s, Tenant: %s - %s", entity, username, tenant, message); + final Logger auditLogger = LoggerFactory.getLogger("AUDIT" + (entity != null ? ("." + entity.toUpperCase()) : "")); + switch (level) { + case INFO: + auditLogger.info(logMessage); + break; + case WARN: + auditLogger.warn(logMessage); + break; + case ERROR: + auditLogger.error(logMessage); + break; + } + } +} \ No newline at end of file diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditLoggingAspect.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditLoggingAspect.java new file mode 100644 index 000000000..9d0206a2b --- /dev/null +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditLoggingAspect.java @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.audit; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import lombok.Builder; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +@Slf4j +@Aspect +@Component +public class AuditLoggingAspect { + + /** + * Around advice that applies to methods annotated with @AuditLog. + * It logs the request and, if logResponse is true, the response as well. + */ + @Around("@annotation(auditLog)") + public Object handleAuditLogging(final ProceedingJoinPoint joinPoint, final AuditLog auditLog) throws Throwable { + try { + final Object result = joinPoint.proceed(); + try { // log success + final ResultMessage resultMessage = getResultMessage(result, auditLog); + final String paramsToLog = getParamsToLog(joinPoint, auditLog); + logAudit(joinPoint, auditLog, resultMessage.message(), paramsToLog, resultMessage.level()); + } catch (final Throwable logError) { + // should never fail! + log.debug("Failed to log success", logError); + } + return result; + } catch (final Throwable t) { + try { + final String paramsToLog = getParamsToLog(joinPoint, auditLog); + logAudit(joinPoint, auditLog, t.getMessage(), paramsToLog, AuditLog.Level.ERROR); + } catch (final Throwable logError) { + // should never fail! + log.debug("Failed to log error", logError); + } + throw t; + } + } + + /** + * Logs both the request details and the response. + */ + private void logAudit( + final JoinPoint joinPoint, final AuditLog auditLog, final String resultMessage, final String paramsToLog, + final AuditLog.Level logLevel) { + final String methodName = joinPoint.getSignature().getName(); + + final String logMessage = String.format( + "Type: %s, Method: %s - Message: %s - Parameters: %s - Response: %s", + auditLog.type(), methodName, auditLog.message(), paramsToLog, resultMessage + ); + + switch (logLevel) { + case INFO: + AuditLogger.info(auditLog.entity(), logMessage); + break; + case WARN: + AuditLogger.warn(auditLog.entity(), logMessage); + break; + case ERROR: + AuditLogger.error(auditLog.entity(), logMessage); + break; + } + } + + private String getParamsToLog(final JoinPoint joinPoint, final AuditLog auditLog) { + final Object[] args = joinPoint.getArgs(); + final String[] includeParams = auditLog.logParams(); + + if (includeParams.length == 0) { + return Arrays.deepToString(args); + } else { + final MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + final String[] paramNames = methodSignature.getParameterNames(); + final Map paramMap = IntStream.range(0, paramNames.length) + .boxed() + .collect(Collectors.toMap(i -> paramNames[i], i -> args[i])); + + return Arrays.stream(includeParams) + .filter(paramMap::containsKey) + .map(name -> name + "=" + paramMap.get(name)) + .collect(Collectors.joining(", ")); + } + } + + private ResultMessage getResultMessage(final Object result, final AuditLog auditLog) { + final ResultMessage.ResultMessageBuilder resultMessageBuilder = ResultMessage.builder(); + if (result instanceof ResponseEntity responseEntity) { + int statusCode = responseEntity.getStatusCode().value(); + if (statusCode >= 200 && statusCode < 300) { + resultMessageBuilder.level(AuditLog.Level.INFO); + if (auditLog.logResponse()) { + resultMessageBuilder.message(result.toString()); + } else { + resultMessageBuilder.message("OK - " + statusCode); + } + } else { + resultMessageBuilder.level(AuditLog.Level.WARN); + if (auditLog.logResponse()) { + resultMessageBuilder.message(result.toString()); + } else { + resultMessageBuilder.message("FAILED - " + statusCode); + } + } + return resultMessageBuilder.build(); + } + resultMessageBuilder.message(result != null ? result.toString() : "null"); + resultMessageBuilder.level(auditLog.level()); + return resultMessageBuilder.build(); + } + + @Builder + private record ResultMessage(String message, AuditLog.Level level) {} +} \ No newline at end of file