/*
* 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.action.details.FlowChangeMoveDetails;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.controller.ScheduledState;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.web.api.dto.ProcessGroupDTO;
import org.apache.nifi.web.dao.ProcessGroupDAO;
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.ArrayList;
import java.util.Collection;
import java.util.Date;
/**
* Audits process group creation/removal and configuration changes.
*/
@Aspect
public class ProcessGroupAuditor extends NiFiAuditor {
private static final Logger logger = LoggerFactory.getLogger(ProcessGroupAuditor.class);
/**
* Audits the creation of process groups via createProcessGroup().
*
* This method only needs to be run 'after returning'. However, in Java 7 the order in which these methods are returned from Class.getDeclaredMethods (even though there is no order guaranteed)
* seems to differ from Java 6. SpringAOP depends on this ordering to determine advice precedence. By normalizing all advice into Around advice we can alleviate this issue.
*
* @param proceedingJoinPoint join point
* @return group
* @throws java.lang.Throwable ex
*/
@Around("within(org.apache.nifi.web.dao.ProcessGroupDAO+) && "
+ "execution(org.apache.nifi.groups.ProcessGroup createProcessGroup(java.lang.String, org.apache.nifi.web.api.dto.ProcessGroupDTO))")
public ProcessGroup createProcessGroupAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// create the process group
ProcessGroup processGroup = (ProcessGroup) proceedingJoinPoint.proceed();
// if no exceptions were thrown, add the process group action...
// audit process group creation
final Action action = generateAuditRecord(processGroup, Operation.Add);
// save the actions
if (action != null) {
saveAction(action, logger);
}
return processGroup;
}
/**
* Audits the update of process group configuration.
*
* @param proceedingJoinPoint join point
* @param processGroupDTO dto
* @return group
* @throws Throwable ex
*/
@Around("within(org.apache.nifi.web.dao.ProcessGroupDAO+) && "
+ "execution(org.apache.nifi.groups.ProcessGroup updateProcessGroup(org.apache.nifi.web.api.dto.ProcessGroupDTO)) && "
+ "args(processGroupDTO)")
public ProcessGroup updateProcessGroupAdvice(ProceedingJoinPoint proceedingJoinPoint, ProcessGroupDTO processGroupDTO) throws Throwable {
ProcessGroupDAO processGroupDAO = getProcessGroupDAO();
ProcessGroup processGroup = processGroupDAO.getProcessGroup(processGroupDTO.getId());
String name = processGroup.getName();
String comments = processGroup.getComments();
// perform the underlying operation
ProcessGroup updatedProcessGroup = (ProcessGroup) proceedingJoinPoint.proceed();
// get the current user
NiFiUser user = NiFiUserUtils.getNiFiUser();
// ensure the user was found
if (user != null) {
Collection<ActionDetails> details = new ArrayList<>();
// see if the name has changed
if (name != null && updatedProcessGroup.getName() != null && !name.equals(updatedProcessGroup.getName())) {
// create the config details
FlowChangeConfigureDetails configDetails = new FlowChangeConfigureDetails();
configDetails.setName("name");
configDetails.setValue(updatedProcessGroup.getName());
configDetails.setPreviousValue(name);
details.add(configDetails);
}
// see if the comments has changed
if (comments != null && updatedProcessGroup.getComments() != null && !comments.equals(updatedProcessGroup.getComments())) {
// create the config details
FlowChangeConfigureDetails configDetails = new FlowChangeConfigureDetails();
configDetails.setName("comments");
configDetails.setValue(updatedProcessGroup.getComments());
configDetails.setPreviousValue(comments);
details.add(configDetails);
}
// hold all actions
Collection<Action> actions = new ArrayList<>();
// save the actions if necessary
if (!details.isEmpty()) {
Date timestamp = new Date();
// create the actions
for (ActionDetails detail : details) {
// determine the type of operation being performed
Operation operation = Operation.Configure;
if (detail instanceof FlowChangeMoveDetails) {
operation = Operation.Move;
}
// create the port action for updating the name
FlowChangeAction processGroupAction = new FlowChangeAction();
processGroupAction.setUserIdentity(user.getIdentity());
processGroupAction.setOperation(operation);
processGroupAction.setTimestamp(timestamp);
processGroupAction.setSourceId(updatedProcessGroup.getIdentifier());
processGroupAction.setSourceName(updatedProcessGroup.getName());
processGroupAction.setSourceType(Component.ProcessGroup);
processGroupAction.setActionDetails(detail);
actions.add(processGroupAction);
}
}
// save actions if necessary
if (!actions.isEmpty()) {
saveActions(actions, logger);
}
}
return updatedProcessGroup;
}
/**
* Audits the update of process group configuration.
*
* @param proceedingJoinPoint join point
* @param groupId group id
* @param state scheduled state
* @throws Throwable ex
*/
@Around("within(org.apache.nifi.web.dao.ProcessGroupDAO+) && "
+ "execution(void scheduleComponents(java.lang.String, org.apache.nifi.controller.ScheduledState, java.util.Set)) && "
+ "args(groupId, state)")
public void scheduleComponentsAdvice(ProceedingJoinPoint proceedingJoinPoint, String groupId, ScheduledState state) throws Throwable {
ProcessGroupDAO processGroupDAO = getProcessGroupDAO();
ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
// perform the action
proceedingJoinPoint.proceed();
// get the current user
NiFiUser user = NiFiUserUtils.getNiFiUser();
// if the user was starting/stopping this process group
FlowChangeAction action = new FlowChangeAction();
action.setUserIdentity(user.getIdentity());
action.setSourceId(processGroup.getIdentifier());
action.setSourceName(processGroup.getName());
action.setSourceType(Component.ProcessGroup);
action.setTimestamp(new Date());
// determine the running state
if (ScheduledState.RUNNING.equals(state)) {
action.setOperation(Operation.Start);
} else {
action.setOperation(Operation.Stop);
}
// add this action
saveAction(action, logger);
}
/**
* Audits the removal of a process group via deleteProcessGroup().
*
* @param proceedingJoinPoint join point
* @param groupId group id
* @throws Throwable ex
*/
@Around("within(org.apache.nifi.web.dao.ProcessGroupDAO+) && "
+ "execution(void deleteProcessGroup(java.lang.String)) && "
+ "args(groupId)")
public void removeProcessGroupAdvice(ProceedingJoinPoint proceedingJoinPoint, String groupId) throws Throwable {
// get the process group before removing it
ProcessGroupDAO processGroupDAO = getProcessGroupDAO();
ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
// remove the process group
proceedingJoinPoint.proceed();
// if no exceptions were thrown, add removal actions...
// audit the process group removal
final Action action = generateAuditRecord(processGroup, Operation.Remove);
// save the actions
if (action != null) {
saveAction(action, logger);
}
}
/**
* Generates an audit record for the creation of a process group.
*
* @param processGroup group
* @param operation operation
* @return action
*/
public Action generateAuditRecord(ProcessGroup processGroup, Operation operation) {
return generateAuditRecord(processGroup, operation, null);
}
/**
* Generates an audit record for the creation of a process group.
*
* @param processGroup group
* @param operation operation
* @param actionDetails details
* @return action
*/
public Action generateAuditRecord(ProcessGroup processGroup, Operation operation, ActionDetails actionDetails) {
FlowChangeAction action = null;
// get the current user
NiFiUser user = NiFiUserUtils.getNiFiUser();
// ensure the user was found
if (user != null) {
// create the process group action for adding this process group
action = new FlowChangeAction();
action.setUserIdentity(user.getIdentity());
action.setOperation(operation);
action.setTimestamp(new Date());
action.setSourceId(processGroup.getIdentifier());
action.setSourceName(processGroup.getName());
action.setSourceType(Component.ProcessGroup);
if (actionDetails != null) {
action.setActionDetails(actionDetails);
}
}
return action;
}
}