You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ex...@apache.org on 2023/02/27 18:56:13 UTC

[nifi] branch main updated: NIFI-11134 Added Label auditing to Flow Configuration History

This is an automated email from the ASF dual-hosted git repository.

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new b08ae75be4 NIFI-11134 Added Label auditing to Flow Configuration History
b08ae75be4 is described below

commit b08ae75be4b76b11d6fbb2d56e33d0092fd5553c
Author: Mark Bean <ma...@gmail.com>
AuthorDate: Fri Feb 10 21:09:35 2023 -0500

    NIFI-11134 Added Label auditing to Flow Configuration History
    
    This closes #6949
    
    Co-authored-by: David Handermann <ex...@apache.org>
    Signed-off-by: David Handermann <ex...@apache.org>
---
 .../java/org/apache/nifi/action/Component.java     |   3 +-
 .../java/org/apache/nifi/audit/LabelAuditor.java   | 177 ++++++++++++++++
 .../java/org/apache/nifi/audit/SnippetAuditor.java |  28 +++
 .../apache/nifi/web/StandardNiFiServiceFacade.java |   3 +
 .../src/main/resources/nifi-web-api-context.xml    |   6 +
 .../org/apache/nifi/audit/TestLabelAuditor.java    | 231 +++++++++++++++++++++
 6 files changed, 447 insertions(+), 1 deletion(-)

diff --git a/nifi-api/src/main/java/org/apache/nifi/action/Component.java b/nifi-api/src/main/java/org/apache/nifi/action/Component.java
index e9997d5902..77c84a881e 100644
--- a/nifi-api/src/main/java/org/apache/nifi/action/Component.java
+++ b/nifi-api/src/main/java/org/apache/nifi/action/Component.java
@@ -36,5 +36,6 @@ public enum Component {
     ParameterProvider,
     AccessPolicy,
     User,
-    UserGroup;
+    UserGroup,
+    Label;
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/LabelAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/LabelAuditor.java
new file mode 100644
index 0000000000..87986f4d37
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/LabelAuditor.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.audit;
+
+import org.apache.nifi.action.Action;
+import org.apache.nifi.action.Component;
+import org.apache.nifi.action.FlowChangeAction;
+import org.apache.nifi.action.Operation;
+import org.apache.nifi.action.details.ActionDetails;
+import org.apache.nifi.action.details.FlowChangeConfigureDetails;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserUtils;
+import org.apache.nifi.controller.label.Label;
+import org.apache.nifi.web.api.dto.LabelDTO;
+import org.apache.nifi.web.dao.LabelDAO;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+import java.util.Objects;
+
+@Aspect
+public class LabelAuditor extends NiFiAuditor {
+    private static final Logger logger = LoggerFactory.getLogger(LabelAuditor.class);
+
+    /**
+     * Audits the creation of a Label.
+     *
+     * @param proceedingJoinPoint Join Point observed
+     * @return Label
+     * @throws Throwable Thrown on failure to proceed with target invocation
+     */
+    @Around("within(org.apache.nifi.web.dao.LabelDAO+) && "
+            + "execution(org.apache.nifi.controller.label.Label createLabel(java.lang.String, org.apache.nifi.web.api.dto.LabelDTO))")
+    public Label createLabelAdvice(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
+        final Label label = (Label) proceedingJoinPoint.proceed();
+        final Action action = generateAuditRecord(label, Operation.Add);
+
+        if (action != null) {
+            saveAction(action, logger);
+        }
+
+        return label;
+    }
+
+    /**
+     * Audits the configuration of a Label.
+     *
+     * @param proceedingJoinPoint Join Point observed
+     * @param labelDTO Data Transfer Object
+     * @param labelDAO Data Access Object
+     * @return Label
+     * @throws Throwable Thrown on failure to proceed with target invocation
+     */
+    @Around("within(org.apache.nifi.web.dao.LabelDAO+) && "
+            + "execution(org.apache.nifi.controller.label.Label updateLabel(org.apache.nifi.web.api.dto.LabelDTO)) && "
+            + "args(labelDTO) && "
+            + "target(labelDAO)")
+    public Label updateLabelAdvice(final ProceedingJoinPoint proceedingJoinPoint, final LabelDTO labelDTO, final LabelDAO labelDAO) throws Throwable {
+        // determine the initial content of label
+        final Label label = labelDAO.getLabel(labelDTO.getId());
+        final String originalLabelValue = label.getValue();
+
+        final Label updatedLabel = (Label) proceedingJoinPoint.proceed();
+
+        // ensure the user was found
+        final NiFiUser user = NiFiUserUtils.getNiFiUser();
+        if (user != null) {
+            final String updatedLabelValue = updatedLabel.getValue();
+            if ((originalLabelValue == null && updatedLabelValue != null)
+                    || !Objects.equals(originalLabelValue, updatedLabelValue)) {
+                final FlowChangeAction labelAction = new FlowChangeAction();
+                labelAction.setUserIdentity(user.getIdentity());
+                labelAction.setTimestamp(new Date());
+                labelAction.setSourceId(label.getIdentifier());
+                labelAction.setSourceType(Component.Label);
+                labelAction.setOperation(Operation.Configure);
+                // Source Name is a required field for the database but not applicable for a label; use UUID to create a unique name
+                labelAction.setSourceName(label.getIdentifier());
+
+                final FlowChangeConfigureDetails actionDetails = new FlowChangeConfigureDetails();
+                actionDetails.setName(label.getIdentifier());
+                actionDetails.setValue(updatedLabelValue);
+                actionDetails.setPreviousValue(originalLabelValue);
+                labelAction.setActionDetails(actionDetails);
+
+                saveAction(labelAction, logger);
+            }
+        }
+        return updatedLabel;
+    }
+
+    /**
+     * Audits the removal of a Label.
+     *
+     * @param proceedingJoinPoint Join Point observed
+     * @param labelId Label identifier
+     * @param labelDAO Label Data Access Object
+     * @throws Throwable Thrown on failure to proceed with target invocation
+     */
+    @Around("within(org.apache.nifi.web.dao.LabelDAO+) && "
+            + "execution(void deleteLabel(java.lang.String)) && "
+            + "args(labelId) && "
+            + "target(labelDAO)")
+    public void removeLabelAdvice(final ProceedingJoinPoint proceedingJoinPoint, final String labelId, final LabelDAO labelDAO) throws Throwable {
+        // get the label before removing it
+        final Label label = labelDAO.getLabel(labelId);
+
+        // remove the label
+        proceedingJoinPoint.proceed();
+
+        // if no exceptions were thrown, add removal actions...
+        final Action action = generateAuditRecord(label, Operation.Remove);
+
+        if (action != null) {
+            saveAction(action, logger);
+        }
+    }
+
+    /**
+     * Generates an audit record for the creation of the specified label.
+     *
+     * @param label Label audited
+     * @param operation Operation audited
+     * @return Action description
+     */
+    public Action generateAuditRecord(final Label label, final Operation operation) {
+        return generateAuditRecord(label, operation, null);
+    }
+
+    /**
+     * Generates an audit record for the creation of the specified label.
+     *
+     * @param label Label audited
+     * @param operation Operation audited
+     * @param actionDetails Action Details or null when not provided
+     * @return Action description
+     */
+    public Action generateAuditRecord(final Label label, final Operation operation, final ActionDetails actionDetails) {
+        FlowChangeAction action = null;
+
+        final NiFiUser user = NiFiUserUtils.getNiFiUser();
+        if (user != null) {
+            action = new FlowChangeAction();
+            action.setUserIdentity(user.getIdentity());
+            action.setOperation(operation);
+            action.setTimestamp(new Date());
+            action.setSourceId(label.getIdentifier());
+            // Labels do not have a Name; use UUID to provide a unique name
+            action.setSourceName(label.getIdentifier());
+            action.setSourceType(Component.Label);
+
+            if (actionDetails != null) {
+                action.setActionDetails(actionDetails);
+            }
+        }
+
+        return action;
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/SnippetAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/SnippetAuditor.java
index af3d19ba8e..8b47037daf 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/SnippetAuditor.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/SnippetAuditor.java
@@ -33,6 +33,7 @@ import org.apache.nifi.connectable.Funnel;
 import org.apache.nifi.connectable.Port;
 import org.apache.nifi.controller.ProcessorNode;
 import org.apache.nifi.controller.Snippet;
+import org.apache.nifi.controller.label.Label;
 import org.apache.nifi.groups.ProcessGroup;
 import org.apache.nifi.groups.RemoteProcessGroup;
 import org.apache.nifi.authorization.user.NiFiUser;
@@ -47,6 +48,7 @@ import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
 import org.apache.nifi.web.api.dto.SnippetDTO;
 import org.apache.nifi.web.dao.ConnectionDAO;
 import org.apache.nifi.web.dao.FunnelDAO;
+import org.apache.nifi.web.dao.LabelDAO;
 import org.apache.nifi.web.dao.PortDAO;
 import org.apache.nifi.web.dao.ProcessGroupDAO;
 import org.apache.nifi.web.dao.ProcessorDAO;
@@ -78,6 +80,7 @@ public class SnippetAuditor extends NiFiAuditor {
     private ProcessorDAO processorDAO;
     private FunnelDAO funnelDAO;
     private ConnectionDAO connectionDAO;
+    private LabelDAO labelDAO;
 
     private PortAuditor portAuditor;
     private RemoteProcessGroupAuditor remoteProcessGroupAuditor;
@@ -85,6 +88,7 @@ public class SnippetAuditor extends NiFiAuditor {
     private ProcessorAuditor processorAuditor;
     private FunnelAuditor funnelAuditor;
     private RelationshipAuditor relationshipAuditor;
+    private LabelAuditor labelAuditor;
 
     /**
      * Audits copy/paste.
@@ -328,6 +332,14 @@ public class SnippetAuditor extends NiFiAuditor {
                 }
             }
 
+            for (String id : snippet.getLabels().keySet()) {
+                final Label label = labelDAO.getLabel(id);
+                final Action action = labelAuditor.generateAuditRecord(label, Operation.Move, createMoveDetails(previousGroupId, groupId, logger));
+                if (action != null) {
+                    actions.add(action);
+                }
+            }
+
             // save the actions
             if (CollectionUtils.isNotEmpty(actions)) {
                 saveActions(actions, logger);
@@ -390,6 +402,11 @@ public class SnippetAuditor extends NiFiAuditor {
             connections.add(connectionDAO.getConnection(id));
         }
 
+        final Set<Label> labels = new HashSet<>();
+        for (String id : snippet.getLabels().keySet()) {
+            labels.add(labelDAO.getLabel(id));
+        }
+
         // remove the snippet and components
         proceedingJoinPoint.proceed();
 
@@ -446,6 +463,13 @@ public class SnippetAuditor extends NiFiAuditor {
             }
         }
 
+        for (Label label : labels) {
+            final Action action = labelAuditor.generateAuditRecord(label, Operation.Remove);
+            if (action != null) {
+                actions.add(action);
+            }
+        }
+
         // save the actions
         if (CollectionUtils.isNotEmpty(actions)) {
             saveActions(actions, logger);
@@ -481,6 +505,10 @@ public class SnippetAuditor extends NiFiAuditor {
         this.remoteProcessGroupAuditor = remoteProcessGroupAuditor;
     }
 
+    public void setLabelAuditor(LabelAuditor labelAuditor) {
+        this.labelAuditor = labelAuditor;
+    }
+
     public void setRemoteProcessGroupDAO(RemoteProcessGroupDAO remoteProcessGroupDAO) {
         this.remoteProcessGroupDAO = remoteProcessGroupDAO;
     }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index 9547a6fed6..9cb0f94e50 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -5928,6 +5928,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 case UserGroup:
                     authorizable = authorizableLookup.getTenant();
                     break;
+                case Label:
+                    authorizable = authorizableLookup.getLabel(sourceId);
+                    break;
                 default:
                     throw new WebApplicationException(Response.serverError().entity("An unexpected type of component is the source of this action.").build());
             }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
index 6c38f0c029..bea03aaf49 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
@@ -707,6 +707,11 @@
         <property name="auditService" ref="auditService"/>
         <property name="processGroupDAO" ref="processGroupDAO"/>
     </bean>
+    <bean id="labelAuditor" class="org.apache.nifi.audit.LabelAuditor">
+        <property name="serviceFacade" ref="serviceFacade"/>
+        <property name="auditService" ref="auditService"/>
+        <property name="processGroupDAO" ref="processGroupDAO"/>
+    </bean>
     <bean id="snippetAuditor" class="org.apache.nifi.audit.SnippetAuditor">
         <property name="serviceFacade" ref="serviceFacade"/>
         <property name="auditService" ref="auditService"/>
@@ -723,6 +728,7 @@
         <property name="processGroupAuditor" ref="processGroupAuditor"/>
         <property name="processorAuditor" ref="processorAuditor"/>
         <property name="relationshipAuditor" ref="relationshipAuditor"/>
+        <property name="labelAuditor" ref="labelAuditor"/>
     </bean>
     <bean id="controllerServiceAuditor" class="org.apache.nifi.audit.ControllerServiceAuditor">
         <property name="serviceFacade" ref="serviceFacade"/>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestLabelAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestLabelAuditor.java
new file mode 100644
index 0000000000..d0bc8a845e
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestLabelAuditor.java
@@ -0,0 +1,231 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.audit;
+
+import org.apache.nifi.action.Action;
+import org.apache.nifi.action.Component;
+import org.apache.nifi.action.Operation;
+import org.apache.nifi.action.details.ActionDetails;
+import org.apache.nifi.action.details.FlowChangeConfigureDetails;
+import org.apache.nifi.admin.service.AuditService;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserDetails;
+import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.controller.FlowController;
+import org.apache.nifi.controller.flow.FlowManager;
+import org.apache.nifi.controller.label.StandardLabel;
+import org.apache.nifi.groups.ProcessGroup;
+import org.apache.nifi.web.api.dto.LabelDTO;
+import org.apache.nifi.web.dao.ProcessGroupDAO;
+import org.apache.nifi.web.dao.impl.StandardLabelDAO;
+import org.apache.nifi.controller.label.Label;
+import org.apache.nifi.web.dao.impl.StandardProcessGroupDAO;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+@ExtendWith({SpringExtension.class, MockitoExtension.class})
+@ContextConfiguration(classes = {TestLabelAuditor.AuditorConfiguration.class})
+public class TestLabelAuditor {
+
+    private static final String USER_IDENTITY = "username";
+
+    private static final String GROUP_ID = "group-1";
+
+    private static final String LABEL_ID = "label-1";
+
+    private static final String LABEL = "label";
+
+    private static final String UPDATED_LABEL = "updated-label";
+
+    @Mock
+    AuditService auditService;
+
+    @Mock
+    Authentication authentication;
+
+    @Mock
+    FlowController flowController;
+
+    @Mock
+    FlowManager flowManager;
+
+    @Mock
+    ProcessGroup processGroup;
+
+    @Captor
+    ArgumentCaptor<List<Action>> actionsCaptor;
+
+    @Autowired
+    StandardLabelDAO labelDao;
+
+    @Autowired
+    LabelAuditor labelAuditor;
+
+    @BeforeEach
+    void setAuditor() {
+        final SecurityContext securityContext = SecurityContextHolder.getContext();
+        securityContext.setAuthentication(authentication);
+        final NiFiUser user = new StandardNiFiUser.Builder().identity(USER_IDENTITY).build();
+        final NiFiUserDetails userDetail = new NiFiUserDetails(user);
+        when(authentication.getPrincipal()).thenReturn(userDetail);
+
+        when(flowController.getFlowManager()).thenReturn(flowManager);
+        labelDao.setFlowController(flowController);
+        labelAuditor.setAuditService(auditService);
+    }
+
+    @Test
+    void testCreateLabelAdvice() {
+        final LabelDTO labelDto = getLabelDto();
+        when(flowManager.getGroup(eq(GROUP_ID))).thenReturn(processGroup);
+        when(flowManager.createLabel(eq(LABEL_ID), eq(LABEL))).thenReturn(new StandardLabel(LABEL_ID, LABEL));
+
+        final Label label = labelDao.createLabel(GROUP_ID, labelDto);
+
+        assertNotNull(label);
+        verify(auditService).addActions(actionsCaptor.capture());
+        final List<Action> actions = actionsCaptor.getValue();
+        assertActionFound(actions, Operation.Add);
+    }
+
+    @Test
+    void testRemoveLabelAdvice() {
+        setFindLabel();
+
+        labelDao.deleteLabel(LABEL_ID);
+
+        verify(auditService).addActions(actionsCaptor.capture());
+        final List<Action> actions = actionsCaptor.getValue();
+        assertActionFound(actions, Operation.Remove);
+    }
+
+    @Test
+    void testUpdateLabelAdvice() {
+        setFindLabel();
+
+        final LabelDTO labelDto = getLabelDto();
+        labelDto.setLabel(UPDATED_LABEL);
+
+        labelDao.updateLabel(labelDto);
+
+        verify(auditService).addActions(actionsCaptor.capture());
+        final List<Action> actions = actionsCaptor.getValue();
+        final Action action = assertActionFound(actions, Operation.Configure);
+
+        final ActionDetails actionDetails = action.getActionDetails();
+        assertActionDetailsFound(actionDetails);
+    }
+
+    @Test
+    void testUpdateLabelAdviceLabelUnchanged() {
+        setFindLabel();
+
+        final LabelDTO labelDto = getLabelDto();
+
+        labelDao.updateLabel(labelDto);
+
+        verifyNoInteractions(auditService);
+    }
+
+    private void setFindLabel() {
+        when(flowManager.getRootGroup()).thenReturn(processGroup);
+        final Label label = new StandardLabel(LABEL_ID, LABEL);
+        label.setProcessGroup(processGroup);
+        when(processGroup.findLabel(eq(LABEL_ID))).thenReturn(label);
+    }
+
+    private void assertActionDetailsFound(final ActionDetails actionDetails) {
+        assertInstanceOf(FlowChangeConfigureDetails.class, actionDetails);
+        final FlowChangeConfigureDetails flowChangeConfigureDetails = (FlowChangeConfigureDetails) actionDetails;
+
+        assertEquals(LABEL_ID, flowChangeConfigureDetails.getName());
+        assertEquals(LABEL, flowChangeConfigureDetails.getPreviousValue());
+        assertEquals(UPDATED_LABEL, flowChangeConfigureDetails.getValue());
+    }
+
+    private Action assertActionFound(final List<Action> actions, final Operation operation) {
+        assertNotNull(actions);
+
+        final Optional<Action> actionFound = actions.stream().findFirst();
+        assertTrue(actionFound.isPresent());
+
+        final Action action = actionFound.get();
+        assertEquals(USER_IDENTITY, action.getUserIdentity());
+        assertEquals(operation, action.getOperation());
+        assertEquals(LABEL_ID, action.getSourceId());
+        assertEquals(LABEL_ID, action.getSourceName());
+        assertEquals(Component.Label, action.getSourceType());
+        assertNotNull(action.getTimestamp());
+
+        return action;
+    }
+
+    private LabelDTO getLabelDto() {
+        final LabelDTO labelDto = new LabelDTO();
+        labelDto.setLabel(LABEL);
+        labelDto.setId(LABEL_ID);
+        labelDto.setStyle(Collections.emptyMap());
+        return labelDto;
+    }
+
+    @Configuration
+    @EnableAspectJAutoProxy(proxyTargetClass = true)
+    public static class AuditorConfiguration {
+
+        @Bean
+        public LabelAuditor labelAuditor() {
+            return new LabelAuditor();
+        }
+
+        @Bean
+        public StandardLabelDAO labelDAO() {
+            return new StandardLabelDAO();
+        }
+
+        @Bean
+        public ProcessGroupDAO processGroupDAO() {
+            return new StandardProcessGroupDAO();
+        }
+    }
+}