NIFI-2272:

- Ensuring the appropriate visibilty of the action in the policy management page.
NIFI-2273:
- Ensuring we load the policy or inform the user of the appropriate permissions of the effective policy.
NIFI-2239:
- Providing help tooltips for the policies in the management page.
NIFI-2283:
- Adding auditing for access policies, users, and groups.
NIFI-2263:
- Not replicating history requests throughout the cluster.
NIFI-2096:
- Fixing upload template file input in Firefox.
NIFI-2301:
- Removing relevant policies after component deletion.
This commit is contained in:
Matt Gilman 2016-07-18 14:05:04 -04:00 committed by Mark Payne
parent 5c8636edf4
commit aa91032cde
12 changed files with 961 additions and 112 deletions

View File

@ -30,5 +30,8 @@ public enum Component {
Funnel,
Connection,
ControllerService,
ReportingTask;
ReportingTask,
AccessPolicy,
User,
UserGroup;
}

View File

@ -0,0 +1,274 @@
/*
* 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.commons.lang3.StringUtils;
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.AccessPolicy;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.web.api.dto.AccessPolicyDTO;
import org.apache.nifi.web.dao.AccessPolicyDAO;
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.text.Collator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* Audits policy creation/removal and configuration changes.
*/
@Aspect
public class AccessPolicyAuditor extends NiFiAuditor {
private static final Logger logger = LoggerFactory.getLogger(AccessPolicyAuditor.class);
private static final String USERS = "Users";
private static final String USER_GROUPS = "User Groups";
/**
* Audits the creation of policies via createAccessPolicy().
*
* 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 node
* @throws Throwable ex
*/
@Around("within(org.apache.nifi.web.dao.AccessPolicyDAO+) && "
+ "execution(org.apache.nifi.authorization.AccessPolicy createAccessPolicy(org.apache.nifi.web.api.dto.AccessPolicyDTO))")
public AccessPolicy createAccessPolicyAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// create the access policy
AccessPolicy policy = (AccessPolicy) proceedingJoinPoint.proceed();
// if no exceptions were thrown, add the policy action...
final Action action = generateAuditRecord(policy, Operation.Add);
// save the actions
if (action != null) {
saveAction(action, logger);
}
return policy;
}
/**
* Audits the configuration of a single policy.
*
* @param proceedingJoinPoint join point
* @param accessPolicyDTO dto
* @param accessPolicyDAO dao
* @return node
* @throws Throwable ex
*/
@Around("within(org.apache.nifi.web.dao.AccessPolicyDAO+) && "
+ "execution(org.apache.nifi.authorization.AccessPolicy updateAccessPolicy(org.apache.nifi.web.api.dto.AccessPolicyDTO)) && "
+ "args(accessPolicyDTO) && "
+ "target(accessPolicyDAO)")
public AccessPolicy updateAccessPolicyAdvice(ProceedingJoinPoint proceedingJoinPoint, AccessPolicyDTO accessPolicyDTO, AccessPolicyDAO accessPolicyDAO) throws Throwable {
// determine the initial values for each property/setting thats changing
AccessPolicy accessPolicy = accessPolicyDAO.getAccessPolicy(accessPolicyDTO.getId());
final Map<String, String> values = extractConfiguredPropertyValues(accessPolicy, accessPolicyDTO);
// update the policy state
final AccessPolicy updatedAccessPolicy = (AccessPolicy) proceedingJoinPoint.proceed();
// if no exceptions were thrown, add the policy action...
// get the updated verbose state
accessPolicy = accessPolicyDAO.getAccessPolicy(updatedAccessPolicy.getIdentifier());
// get the current user
NiFiUser user = NiFiUserUtils.getNiFiUser();
// ensure the user was found
if (user != null) {
// determine the updated values
Map<String, String> updatedValues = extractConfiguredPropertyValues(accessPolicy, accessPolicyDTO);
// create a policy action
Date actionTimestamp = new Date();
Collection<Action> actions = new ArrayList<>();
// go through each updated value
for (String property : updatedValues.keySet()) {
String newValue = updatedValues.get(property);
String oldValue = values.get(property);
Operation operation = null;
// determine the type of operation
if (oldValue == null || newValue == null || !newValue.equals(oldValue)) {
operation = Operation.Configure;
}
// create a configuration action accordingly
if (operation != null) {
final FlowChangeConfigureDetails actionDetails = new FlowChangeConfigureDetails();
actionDetails.setName(property);
actionDetails.setValue(newValue);
actionDetails.setPreviousValue(oldValue);
// create a configuration action
FlowChangeAction configurationAction = new FlowChangeAction();
configurationAction.setUserIdentity(user.getIdentity());
configurationAction.setOperation(operation);
configurationAction.setTimestamp(actionTimestamp);
configurationAction.setSourceId(accessPolicy.getIdentifier());
configurationAction.setSourceName(formatPolicyName(accessPolicy));
configurationAction.setSourceType(Component.AccessPolicy);
configurationAction.setActionDetails(actionDetails);
actions.add(configurationAction);
}
}
// ensure there are actions to record
if (!actions.isEmpty()) {
// save the actions
saveActions(actions, logger);
}
}
return updatedAccessPolicy;
}
/**
* Audits the removal of a policy via deleteAccessPolicy().
*
* @param proceedingJoinPoint join point
* @param policyId policy id
* @param accessPolicyDAO dao
* @throws Throwable ex
*/
@Around("within(org.apache.nifi.web.dao.AccessPolicyDAO+) && "
+ "execution(org.apache.nifi.authorization.AccessPolicy deleteAccessPolicy(java.lang.String)) && "
+ "args(policyId) && "
+ "target(accessPolicyDAO)")
public AccessPolicy removePolicyAdvice(ProceedingJoinPoint proceedingJoinPoint, String policyId, AccessPolicyDAO accessPolicyDAO) throws Throwable {
// get the policy before removing it
AccessPolicy accessPolicy = accessPolicyDAO.getAccessPolicy(policyId);
// remove the policy
final AccessPolicy removedAccessPolicy = (AccessPolicy)proceedingJoinPoint.proceed();
// if no exceptions were thrown, add removal actions...
// audit the policy removal
final Action action = generateAuditRecord(accessPolicy, Operation.Remove);
// save the actions
if (action != null) {
saveAction(action, logger);
}
return removedAccessPolicy;
}
/**
* Generates an audit record for the creation of a policy.
*
* @param policy policy
* @param operation operation
* @return action
*/
public Action generateAuditRecord(AccessPolicy policy, Operation operation) {
return generateAuditRecord(policy, operation, null);
}
/**
* Generates an audit record for the creation of a policy.
*
* @param policy policy
* @param operation operation
* @param actionDetails details
* @return action
*/
public Action generateAuditRecord(AccessPolicy policy, 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 policy action for adding this policy
action = new FlowChangeAction();
action.setUserIdentity(user.getIdentity());
action.setOperation(operation);
action.setTimestamp(new Date());
action.setSourceId(policy.getIdentifier());
action.setSourceName(formatPolicyName(policy));
action.setSourceType(Component.AccessPolicy);
if (actionDetails != null) {
action.setActionDetails(actionDetails);
}
}
return action;
}
/**
* Formats the name of the specified policy.
*
* @param policy policy
* @return formatted name
*/
private String formatPolicyName(final AccessPolicy policy) {
return policy.getAction().toString() + " " + policy.getResource();
}
/**
* Extracts the values for the configured properties from the specified policy.
*/
private Map<String, String> extractConfiguredPropertyValues(AccessPolicy policy, AccessPolicyDTO policyDTO) {
Map<String, String> values = new HashMap<>();
if (policyDTO.getUsers() != null) {
// get each of the auto terminated relationship names
final List<String> currentUsers = new ArrayList<>(policy.getUsers());
// sort them and include in the configuration
Collections.sort(currentUsers, Collator.getInstance(Locale.US));
values.put(USERS, StringUtils.join(currentUsers, ", "));
}
if (policyDTO.getUserGroups() != null) {
// get each of the auto terminated relationship names
final List<String> currentUserGroups = new ArrayList<>(policy.getGroups());
// sort them and include in the configuration
Collections.sort(currentUserGroups, Collator.getInstance(Locale.US));
values.put(USER_GROUPS, StringUtils.join(currentUserGroups, ", "));
}
return values;
}
}

View File

@ -0,0 +1,245 @@
/*
* 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;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.web.api.dto.UserDTO;
import org.apache.nifi.web.dao.UserDAO;
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;
import java.util.HashMap;
import java.util.Map;
/**
* Audits user creation/removal and configuration changes.
*/
@Aspect
public class UserAuditor extends NiFiAuditor {
private static final Logger logger = LoggerFactory.getLogger(UserAuditor.class);
private static final String IDENTITY = "Identity";
/**
* Audits the creation of policies via createUser().
*
* 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 node
* @throws Throwable ex
*/
@Around("within(org.apache.nifi.web.dao.UserDAO+) && "
+ "execution(org.apache.nifi.authorization.User createUser(org.apache.nifi.web.api.dto.UserDTO))")
public User createUserAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// create the access user
User user = (User) proceedingJoinPoint.proceed();
// if no exceptions were thrown, add the user action...
final Action action = generateAuditRecord(user, Operation.Add);
// save the actions
if (action != null) {
saveAction(action, logger);
}
return user;
}
/**
* Audits the configuration of a single user.
*
* @param proceedingJoinPoint join point
* @param userDTO dto
* @param userDAO dao
* @return node
* @throws Throwable ex
*/
@Around("within(org.apache.nifi.web.dao.UserDAO+) && "
+ "execution(org.apache.nifi.authorization.User updateUser(org.apache.nifi.web.api.dto.UserDTO)) && "
+ "args(userDTO) && "
+ "target(userDAO)")
public User updateUserAdvice(ProceedingJoinPoint proceedingJoinPoint, UserDTO userDTO, UserDAO userDAO) throws Throwable {
// determine the initial values for each property/setting thats changing
User user = userDAO.getUser(userDTO.getId());
final Map<String, String> values = extractConfiguredPropertyValues(user, userDTO);
// update the user state
final User updatedUser = (User) proceedingJoinPoint.proceed();
// if no exceptions were thrown, add the user action...
// get the updated verbose state
user = userDAO.getUser(updatedUser.getIdentifier());
// get the current user
NiFiUser niFiUser = NiFiUserUtils.getNiFiUser();
// ensure the user was found
if (niFiUser != null) {
// determine the updated values
Map<String, String> updatedValues = extractConfiguredPropertyValues(user, userDTO);
// create a user action
Date actionTimestamp = new Date();
Collection<Action> actions = new ArrayList<>();
// go through each updated value
for (String property : updatedValues.keySet()) {
String newValue = updatedValues.get(property);
String oldValue = values.get(property);
Operation operation = null;
// determine the type of operation
if (oldValue == null || newValue == null || !newValue.equals(oldValue)) {
operation = Operation.Configure;
}
// create a configuration action accordingly
if (operation != null) {
final FlowChangeConfigureDetails actionDetails = new FlowChangeConfigureDetails();
actionDetails.setName(property);
actionDetails.setValue(newValue);
actionDetails.setPreviousValue(oldValue);
// create a configuration action
FlowChangeAction configurationAction = new FlowChangeAction();
configurationAction.setUserIdentity(niFiUser.getIdentity());
configurationAction.setOperation(operation);
configurationAction.setTimestamp(actionTimestamp);
configurationAction.setSourceId(user.getIdentifier());
configurationAction.setSourceName(user.getIdentity());
configurationAction.setSourceType(Component.User);
configurationAction.setActionDetails(actionDetails);
actions.add(configurationAction);
}
}
// ensure there are actions to record
if (!actions.isEmpty()) {
// save the actions
saveActions(actions, logger);
}
}
return updatedUser;
}
/**
* Audits the removal of a user via deleteUser().
*
* @param proceedingJoinPoint join point
* @param userId user id
* @param userDAO dao
* @throws Throwable ex
*/
@Around("within(org.apache.nifi.web.dao.UserDAO+) && "
+ "execution(org.apache.nifi.authorization.User deleteUser(java.lang.String)) && "
+ "args(userId) && "
+ "target(userDAO)")
public User removeUserAdvice(ProceedingJoinPoint proceedingJoinPoint, String userId, UserDAO userDAO) throws Throwable {
// get the user before removing it
User user = userDAO.getUser(userId);
// remove the user
final User removedUser = (User) proceedingJoinPoint.proceed();
// if no exceptions were thrown, add removal actions...
// audit the user removal
final Action action = generateAuditRecord(user, Operation.Remove);
// save the actions
if (action != null) {
saveAction(action, logger);
}
return removedUser;
}
/**
* Generates an audit record for the creation of a user.
*
* @param user user
* @param operation operation
* @return action
*/
public Action generateAuditRecord(User user, Operation operation) {
return generateAuditRecord(user, operation, null);
}
/**
* Generates an audit record for the creation of a user.
*
* @param user user
* @param operation operation
* @param actionDetails details
* @return action
*/
public Action generateAuditRecord(User user, Operation operation, ActionDetails actionDetails) {
FlowChangeAction action = null;
// get the current user
NiFiUser niFiUser = NiFiUserUtils.getNiFiUser();
// ensure the user was found
if (niFiUser != null) {
// create the user action for adding this user
action = new FlowChangeAction();
action.setUserIdentity(niFiUser.getIdentity());
action.setOperation(operation);
action.setTimestamp(new Date());
action.setSourceId(user.getIdentifier());
action.setSourceName(user.getIdentity());
action.setSourceType(Component.User);
if (actionDetails != null) {
action.setActionDetails(actionDetails);
}
}
return action;
}
/**
* Extracts the values for the configured properties from the specified user.
*/
private Map<String, String> extractConfiguredPropertyValues(User user, UserDTO userDTO) {
Map<String, String> values = new HashMap<>();
if (userDTO.getIdentity() != null) {
values.put(IDENTITY, user.getIdentity());
}
return values;
}
}

View File

@ -0,0 +1,259 @@
/*
* 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.commons.lang3.StringUtils;
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.Group;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.web.api.dto.UserGroupDTO;
import org.apache.nifi.web.dao.UserGroupDAO;
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.text.Collator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* Audits user creation/removal and configuration changes.
*/
@Aspect
public class UserGroupAuditor extends NiFiAuditor {
private static final Logger logger = LoggerFactory.getLogger(UserGroupAuditor.class);
private static final String NAME = "Name";
private static final String USERS = "Users";
/**
* Audits the creation of policies via createUser().
*
* 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 node
* @throws Throwable ex
*/
@Around("within(org.apache.nifi.web.dao.UserGroupDAO+) && "
+ "execution(org.apache.nifi.authorization.Group createUserGroup(org.apache.nifi.web.api.dto.UserGroupDTO))")
public Group createUserGroupAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// create the access user group
Group userGroup = (Group) proceedingJoinPoint.proceed();
// if no exceptions were thrown, add the user action...
final Action action = generateAuditRecord(userGroup, Operation.Add);
// save the actions
if (action != null) {
saveAction(action, logger);
}
return userGroup;
}
/**
* Audits the configuration of a single user.
*
* @param proceedingJoinPoint join point
* @param userGroupDTO dto
* @param userGroupDAO dao
* @return node
* @throws Throwable ex
*/
@Around("within(org.apache.nifi.web.dao.UserGroupDAO+) && "
+ "execution(org.apache.nifi.authorization.Group updateUserGroup(org.apache.nifi.web.api.dto.UserGroupDTO)) && "
+ "args(userGroupDTO) && "
+ "target(userGroupDAO)")
public Group updateUserAdvice(ProceedingJoinPoint proceedingJoinPoint, UserGroupDTO userGroupDTO, UserGroupDAO userGroupDAO) throws Throwable {
// determine the initial values for each property/setting thats changing
Group user = userGroupDAO.getUserGroup(userGroupDTO.getId());
final Map<String, String> values = extractConfiguredPropertyValues(user, userGroupDTO);
// update the user state
final Group updatedUserGroup = (Group) proceedingJoinPoint.proceed();
// if no exceptions were thrown, add the user group action...
// get the updated verbose state
user = userGroupDAO.getUserGroup(updatedUserGroup.getIdentifier());
// get the current user
NiFiUser niFiUser = NiFiUserUtils.getNiFiUser();
// ensure the user was found
if (niFiUser != null) {
// determine the updated values
Map<String, String> updatedValues = extractConfiguredPropertyValues(user, userGroupDTO);
// create a user action
Date actionTimestamp = new Date();
Collection<Action> actions = new ArrayList<>();
// go through each updated value
for (String property : updatedValues.keySet()) {
String newValue = updatedValues.get(property);
String oldValue = values.get(property);
Operation operation = null;
// determine the type of operation
if (oldValue == null || newValue == null || !newValue.equals(oldValue)) {
operation = Operation.Configure;
}
// create a configuration action accordingly
if (operation != null) {
final FlowChangeConfigureDetails actionDetails = new FlowChangeConfigureDetails();
actionDetails.setName(property);
actionDetails.setValue(newValue);
actionDetails.setPreviousValue(oldValue);
// create a configuration action
FlowChangeAction configurationAction = new FlowChangeAction();
configurationAction.setUserIdentity(niFiUser.getIdentity());
configurationAction.setOperation(operation);
configurationAction.setTimestamp(actionTimestamp);
configurationAction.setSourceId(user.getIdentifier());
configurationAction.setSourceName(user.getName());
configurationAction.setSourceType(Component.UserGroup);
configurationAction.setActionDetails(actionDetails);
actions.add(configurationAction);
}
}
// ensure there are actions to record
if (!actions.isEmpty()) {
// save the actions
saveActions(actions, logger);
}
}
return updatedUserGroup;
}
/**
* Audits the removal of a user via deleteUser().
*
* @param proceedingJoinPoint join point
* @param userGroupId user id
* @param userGroupDAO dao
* @throws Throwable ex
*/
@Around("within(org.apache.nifi.web.dao.UserGroupDAO+) && "
+ "execution(org.apache.nifi.authorization.Group deleteUserGroup(java.lang.String)) && "
+ "args(userGroupId) && "
+ "target(userGroupDAO)")
public Group removeUserAdvice(ProceedingJoinPoint proceedingJoinPoint, String userGroupId, UserGroupDAO userGroupDAO) throws Throwable {
// get the user group before removing it
Group userGroup = userGroupDAO.getUserGroup(userGroupId);
// remove the user group
final Group removedUserGroup = (Group) proceedingJoinPoint.proceed();
// if no exceptions were thrown, add removal actions...
// audit the user removal
final Action action = generateAuditRecord(userGroup, Operation.Remove);
// save the actions
if (action != null) {
saveAction(action, logger);
}
return removedUserGroup;
}
/**
* Generates an audit record for the creation of a user group.
*
* @param userGroup userGroup
* @param operation operation
* @return action
*/
public Action generateAuditRecord(Group userGroup, Operation operation) {
return generateAuditRecord(userGroup, operation, null);
}
/**
* Generates an audit record for the creation of a user group.
*
* @param userGroup userGroup
* @param operation operation
* @param actionDetails details
* @return action
*/
public Action generateAuditRecord(Group userGroup, Operation operation, ActionDetails actionDetails) {
FlowChangeAction action = null;
// get the current user
NiFiUser niFiUser = NiFiUserUtils.getNiFiUser();
// ensure the user was found
if (niFiUser != null) {
// create the user action for adding this user
action = new FlowChangeAction();
action.setUserIdentity(niFiUser.getIdentity());
action.setOperation(operation);
action.setTimestamp(new Date());
action.setSourceId(userGroup.getIdentifier());
action.setSourceName(userGroup.getName());
action.setSourceType(Component.UserGroup);
if (actionDetails != null) {
action.setActionDetails(actionDetails);
}
}
return action;
}
/**
* Extracts the values for the configured properties from the specified user group.
*/
private Map<String, String> extractConfiguredPropertyValues(Group group, UserGroupDTO userGroupDTO) {
Map<String, String> values = new HashMap<>();
if (userGroupDTO.getIdentity() != null) {
values.put(NAME, group.getName());
}
if (userGroupDTO.getUsers() != null) {
// get each of the auto terminated relationship names
final List<String> currentUsers = new ArrayList<>(group.getUsers());
// sort them and include in the configuration
Collections.sort(currentUsers, Collator.getInstance(Locale.US));
values.put(USERS, StringUtils.join(currentUsers, ", "));
}
return values;
}
}

View File

@ -16,28 +16,7 @@
*/
package org.apache.nifi.web;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import com.google.common.collect.Sets;
import org.apache.nifi.action.Action;
import org.apache.nifi.action.Component;
import org.apache.nifi.action.FlowChangeAction;
@ -215,7 +194,26 @@ import org.apache.nifi.web.util.SnippetUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Sets;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Implementation of NiFiServiceFacade that performs revision checking.
@ -978,6 +976,20 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
controllerFacade.save();
logger.debug("Deletion of component {} was successful", authorizable);
try {
// since the component is being deleted, also delete any relevant access policies
final AccessPolicy readPolicy = accessPolicyDAO.getAccessPolicy(RequestAction.READ, authorizable);
if (authorizable.getResource().getIdentifier().equals(readPolicy.getResource())) {
accessPolicyDAO.deleteAccessPolicy(readPolicy.getIdentifier());
}
final AccessPolicy writePolicy = accessPolicyDAO.getAccessPolicy(RequestAction.WRITE, authorizable);
if (authorizable.getResource().getIdentifier().equals(writePolicy.getResource())) {
accessPolicyDAO.deleteAccessPolicy(writePolicy.getIdentifier());
}
} catch (final Exception e) {
logger.warn(String.format("Unable to remove access policy for %s after component removal.", authorizable.getResource().getIdentifier()), e);
}
return dto;
}
});
@ -2650,6 +2662,13 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
case Connection:
authorizable = authorizableLookup.getConnection(sourceId);
break;
case AccessPolicy:
authorizable = authorizableLookup.getAccessPolicyById(sourceId);
break;
case User:
case UserGroup:
authorizable = authorizableLookup.getTenant();
break;
default:
throw new WebApplicationException(Response.serverError().entity("An unexpected type of component is the source of this action.").build());
}

View File

@ -646,9 +646,7 @@ public class ControllerResource extends ApplicationResource {
throw new IllegalArgumentException("The end date must be specified.");
}
if (isReplicateRequest()) {
return replicate(HttpMethod.DELETE);
}
// Note: History requests are not replicated throughout the cluster and are instead handled by the nodes independently
// handle expects request (usually from the cluster manager)
final boolean validationPhase = isValidationPhase(httpServletRequest);

View File

@ -30,7 +30,6 @@ import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.Resource;
import org.apache.nifi.authorization.UserContextKeys;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.ResourceFactory;
@ -50,8 +49,8 @@ import org.apache.nifi.web.api.dto.AboutDTO;
import org.apache.nifi.web.api.dto.BannerDTO;
import org.apache.nifi.web.api.dto.BulletinBoardDTO;
import org.apache.nifi.web.api.dto.BulletinQueryDTO;
import org.apache.nifi.web.api.dto.ClusterSummaryDTO;
import org.apache.nifi.web.api.dto.ClusterDTO;
import org.apache.nifi.web.api.dto.ClusterSummaryDTO;
import org.apache.nifi.web.api.dto.NodeDTO;
import org.apache.nifi.web.api.dto.ProcessGroupDTO;
import org.apache.nifi.web.api.dto.RevisionDTO;
@ -237,30 +236,6 @@ public class FlowResource extends ApplicationResource {
}
}
private boolean isAuthorized(final RequestAction action, final Resource resource) {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
final Map<String,String> userContext;
if (!StringUtils.isBlank(user.getClientAddress())) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(resource)
.identity(user.getIdentity())
.anonymous(user.isAnonymous())
.accessAttempt(false)
.action(action)
.userContext(userContext)
.build();
final AuthorizationResult result = authorizer.authorize(request);
return Result.Approved.equals(result.getResult());
}
// ----
// flow
// ----
@ -2146,9 +2121,7 @@ public class FlowResource extends ApplicationResource {
}
}
if (isReplicateRequest()) {
return replicate(HttpMethod.GET);
}
// Note: History requests are not replicated throughout the cluster and are instead handled by the nodes independently
// create a history query
final HistoryQueryDTO query = new HistoryQueryDTO();
@ -2231,9 +2204,7 @@ public class FlowResource extends ApplicationResource {
throw new IllegalArgumentException("The action id must be specified.");
}
if (isReplicateRequest()) {
return replicate(HttpMethod.GET);
}
// Note: History requests are not replicated throughout the cluster and are instead handled by the nodes independently
// get the specified action
final ActionDTO action = serviceFacade.getAction(id.getInteger());
@ -2284,9 +2255,7 @@ public class FlowResource extends ApplicationResource {
authorizeFlow();
if (isReplicateRequest()) {
return replicate(HttpMethod.GET);
}
// Note: History requests are not replicated throughout the cluster and are instead handled by the nodes independently
// create the response entity
final ComponentHistoryEntity entity = new ComponentHistoryEntity();

View File

@ -491,6 +491,21 @@
<property name="auditService" ref="auditService"/>
<property name="processGroupDAO" ref="processGroupDAO"/>
</bean>
<bean id="policyAuditor" class="org.apache.nifi.audit.AccessPolicyAuditor">
<property name="serviceFacade" ref="serviceFacade"/>
<property name="auditService" ref="auditService"/>
<property name="processGroupDAO" ref="processGroupDAO"/>
</bean>
<bean id="userAuditor" class="org.apache.nifi.audit.UserAuditor">
<property name="serviceFacade" ref="serviceFacade"/>
<property name="auditService" ref="auditService"/>
<property name="processGroupDAO" ref="processGroupDAO"/>
</bean>
<bean id="userGroupAuditor" class="org.apache.nifi.audit.UserGroupAuditor">
<property name="serviceFacade" ref="serviceFacade"/>
<property name="auditService" ref="auditService"/>
<property name="processGroupDAO" ref="processGroupDAO"/>
</bean>
<!-- NiFi locking -->
<bean id="serviceFacadeLock" class="org.apache.nifi.web.NiFiServiceFacadeLock"/>

View File

@ -102,7 +102,7 @@
<div class="button-spacer-small">&nbsp;</div>
<div id="operate-policy" class="action-button" title="Access Policies">
<button ng-click="appCtrl.nf.Actions['managePolicies'](appCtrl.nf.CanvasUtils.getSelection());"
ng-disabled="appCtrl.nf.CanvasUtils.getSelection().size() > 1">
ng-disabled="!(appCtrl.nf.CanvasUtils.getSelection().size() <= 1 && appCtrl.nf.Common.canAccessTenants())">
<div class="graph-control-action-icon fa fa-key"></div></button>
</div>
<div class="button-spacer-large">&nbsp;</div>

View File

@ -28,7 +28,7 @@
</div>
<div id="global-policy-controls" class="hidden policy-controls">
<div id="policy-type-list"></div>
<div id="controller-policy-target"></div>
<div id="controller-policy-target" class="hidden"></div>
<div class="clear"></div>
</div>
<div id="component-policy-controls" class="hidden policy-controls">

View File

@ -20,11 +20,12 @@
<div id="select-template-container">
<div id="template-browse-container">
<span id="select-template-label">Select Template</span>
<button id="select-template-button" class="fa fa-search">
<div id="select-template-button">
<button class="fa fa-search"></button>
<form id="template-upload-form" enctype="multipart/form-data" method="post">
<input type="file" name="template" id="template-file-field"/>
</form>
</button>
</div>
</div>
</div>
<div id="submit-template-container">

View File

@ -213,29 +213,41 @@ nf.PolicyManagement = (function () {
// policy type listing
$('#policy-type-list').combo({
options: [{
text: 'access the user interface',
value: 'flow'
text: 'view the user interface',
value: 'flow',
description: 'Allows users to view the user interface'
}, {
text: 'access the controller',
value: 'controller'
value: 'controller',
description: 'Allows users to view/modify the controller including Reporting Tasks, Controller Services, and Nodes in the Cluster'
}, {
text: 'query provenance',
value: 'provenance'
value: 'provenance',
description: 'Allows users to submit a Provenance Search and request Event Lineage'
}, {
text: 'access all policies',
value: 'policies'
value: 'policies',
description: 'Allows users to view/modify the policies for all components'
}, {
text: 'access users/user groups',
value: 'tenants',
description: 'Allows users to view/modify the users and user groups'
}, {
text: 'retrieve site-to-site details',
value: 'site-to-site'
value: 'site-to-site',
description: 'Allows other NiFi instances to retrieve Site-To-Site details of this NiFi'
}, {
text: 'view system diagnostics',
value: 'system'
value: 'system',
description: 'Allows users to view System Diagnostics'
}, {
text: 'proxy user requests',
value: 'proxy'
value: 'proxy',
description: 'Allows proxy machines to send requests on the behalf of others'
}, {
text: 'access counters',
value: 'counters'
value: 'counters',
description: 'Allows users to view/modify Counters'
}],
select: function (option) {
if (initialized) {
@ -243,7 +255,7 @@ nf.PolicyManagement = (function () {
$('#selected-policy-type').text(option.value);
// if the option is for a specific component
if (option.value === 'controller' || option.value === 'counters' || option.value === 'policies') {
if (option.value === 'controller' || option.value === 'counters' || option.value === 'policies' || option.value === 'tenants') {
// update the policy target and let it relaod the policy
$('#controller-policy-target').combo('setSelectedOption', {
'value': 'read'
@ -289,26 +301,33 @@ nf.PolicyManagement = (function () {
$('#component-policy-target').combo({
options: [{
text: 'view the component',
value: 'read-component'
value: 'read-component',
description: 'Allows users to view component configuration details'
}, {
text: 'modify the component',
value: 'write-component'
value: 'write-component',
description: 'Allows users to modify component configuration details'
}, {
text: 'view the provenance events',
value: 'read-provenance-events'
value: 'read-provenance-events',
description: 'Allows users to access provenance events and content for this component'
}, {
text: 'view the policies',
value: 'read-policies'
value: 'read-policies',
description: 'Allows users to view the list of users who can view/modify this component'
}, {
text: 'modify the policies',
value: 'write-policies'
value: 'write-policies',
description: 'Allows users to modify the list of users who can view/modify this component'
}, {
text: 'receive data via site-to-site',
value: 'write-receive-data',
description: 'Allows this port to receive data from these NiFi instances',
disabled: true
}, {
}, {
text: 'send data via site-to-site',
value: 'write-send-data',
description: 'Allows this port to send data to these NiFi instances',
disabled: true
}],
select: function (option) {
@ -618,9 +637,6 @@ nf.PolicyManagement = (function () {
// allow removal and modification as the policy is not inherited
$('#new-policy-user-button').prop('disabled', false);
// update the refresh timestamp
$('#policy-last-refreshed').text(policyEntity.generated);
// see if the policy is for this resource
if (resourceAndAction.resource === policy.resource) {
@ -651,33 +667,54 @@ nf.PolicyManagement = (function () {
url: '../nifi-api/policies/' + resourceAndAction.action + resourceAndAction.resource,
dataType: 'json'
}).done(function (policyEntity) {
var policy = policyEntity.component;
// return OK so we either have access to the policy or we don't have access to an inherited policy
$('#policy-message').text(policy.resource);
// update the refresh timestamp
$('#policy-last-refreshed').text(policyEntity.generated);
// populate the policy details
populatePolicy(policyEntity);
// ensure appropriate actions for the loaded policy
if (policyEntity.permissions.canRead === true && policyEntity.permissions.canWrite === true) {
var policy = policyEntity.component;
$('#policy-message').text(policy.resource);
// populate the policy details
populatePolicy(policyEntity);
} else {
// reset the policy
resetPolicy();
// show an appropriate message
$('#policy-message').text('No policy for the specified resource and not authorized to access the inherited policy. ');
$('#new-policy-message').hide();
$('#override-policy-message').show();
}
deferred.resolve();
}).fail(function (xhr, status, error) {
if (xhr.status === 404) {
// show an appropriate messate
$('#policy-message').text('No policy for the specified resource. ');
// reset the policy
resetPolicy();
// show an appropriate message
$('#policy-message').text('No policy for the specified resource.');
$('#new-policy-message').show();
$('#override-policy-message').hide();
// reset the current policy
$('#policy-table').removeData('policy');
deferred.resolve();
} else if (xhr.status === 403) {
// reset the policy
resetPolicy();
// require non inherited policy for removal and modification
$('#new-policy-user-button').prop('disabled', true);
$('#delete-policy-button').prop('disabled', true);
// populate the table with no users
populateTable([], []);
// show an appropriate message
$('#policy-message').text('Not authorized to access the policy for the specified resource.');
$('#new-policy-message').hide();
$('#override-policy-message').hide();
deferred.resolve();
} else {
resetPolicy();
deferred.reject();
nf.Common.handleAjaxError(xhr, status, error);
}
@ -754,7 +791,23 @@ nf.PolicyManagement = (function () {
dataType: 'json',
contentType: 'application/json'
}).done(function (policyEntity) {
populatePolicy(policyEntity);
// ensure appropriate actions for the loaded policy
if (policyEntity.permissions.canRead === true && policyEntity.permissions.canWrite === true) {
var policy = policyEntity.component;
$('#policy-message').text(policy.resource);
// populate the policy details
populatePolicy(policyEntity);
} else {
// reset the policy
resetPolicy();
// show an appropriate message
$('#policy-message').text('No policy for the specified resource and not authorized to access the inherited policy. ');
$('#new-policy-message').hide();
$('#override-policy-message').show();
}
}).fail(nf.Common.handleAjaxError);
} else {
nf.Dialog.showOkDialog({
@ -769,7 +822,7 @@ nf.PolicyManagement = (function () {
*/
var showPolicy = function () {
// show the configuration dialog
nf.Shell.showContent('#policy-management').done(function () {
nf.Shell.showContent('#policy-management').always(function () {
reset();
});
@ -786,11 +839,28 @@ nf.PolicyManagement = (function () {
$('#override-policy-message').hide();
};
/**
* Reset the policy.
*/
var resetPolicy = function () {
resetPolicyMessage();
// reset button state
$('#delete-policy-button').prop('disabled', true);
$('#new-policy-user-button').prop('disabled', true);
// reset the current policy
$('#policy-table').removeData('policy');
// populate the table with no users
populateTable([], []);
}
/**
* Resets the policy management dialog.
*/
var reset = function () {
resetPolicyMessage();
resetPolicy();
// clear the selected policy details
$('#selected-policy-type').text('');
@ -800,10 +870,6 @@ nf.PolicyManagement = (function () {
// clear the selected component details
$('div.policy-selected-component-container').hide();
// reset button state
$('#delete-policy-button').prop('disabled', false);
$('#new-policy-user-button').prop('disabled', false);
};
/**
@ -885,7 +951,7 @@ nf.PolicyManagement = (function () {
$('#selected-policy-component-id').text(d.id);
populateComponentResource('controller-services');
return loadPolicy().done(showPolicy);
return loadPolicy().always(showPolicy);
},
/**
@ -913,7 +979,7 @@ nf.PolicyManagement = (function () {
$('#selected-policy-component-id').text(d.id);
populateComponentResource('reporting-tasks');
return loadPolicy().done(showPolicy);
return loadPolicy().always(showPolicy);
},
/**
@ -941,7 +1007,7 @@ nf.PolicyManagement = (function () {
$('#selected-policy-component-id').text(d.id);
populateComponentResource('templates');
return loadPolicy().done(showPolicy);
return loadPolicy().always(showPolicy);
},
/**
@ -995,7 +1061,7 @@ nf.PolicyManagement = (function () {
// populate the initial resource
populateComponentResource(resource);
return loadPolicy().done(showPolicy);
return loadPolicy().always(showPolicy);
},
/**
@ -1021,7 +1087,7 @@ nf.PolicyManagement = (function () {
$('#selected-policy-action').text('read');
}
return loadPolicy().done(showPolicy);
},
return loadPolicy().always(showPolicy);
}
};
}());