/* * 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.web; import org.apache.nifi.action.Component; import org.apache.nifi.action.FlowChangeAction; import org.apache.nifi.action.Operation; import org.apache.nifi.admin.service.AuditService; import org.apache.nifi.authorization.AccessDeniedException; import org.apache.nifi.authorization.AuthorizableLookup; import org.apache.nifi.authorization.AuthorizationRequest; import org.apache.nifi.authorization.AuthorizationResult; import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.authorization.ComponentAuthorizable; import org.apache.nifi.authorization.Resource; import org.apache.nifi.authorization.resource.Authorizable; import org.apache.nifi.authorization.resource.ResourceFactory; import org.apache.nifi.authorization.resource.ResourceType; import org.apache.nifi.authorization.user.NiFiUserDetails; import org.apache.nifi.authorization.user.StandardNiFiUser; import org.apache.nifi.controller.FlowController; import org.apache.nifi.history.History; import org.apache.nifi.history.HistoryQuery; import org.apache.nifi.web.api.dto.DtoFactory; import org.apache.nifi.web.api.dto.EntityFactory; import org.apache.nifi.web.api.dto.action.HistoryDTO; import org.apache.nifi.web.api.dto.action.HistoryQueryDTO; import org.apache.nifi.web.api.entity.ActionEntity; import org.apache.nifi.web.controller.ControllerFacade; import org.apache.nifi.web.security.token.NiFiAuthenticationToken; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentMatcher; import org.mockito.Mockito; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import java.util.Arrays; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class StandardNiFiServiceFacadeTest { private static final String USER_1 = "user-1"; private static final String USER_2 = "user-2"; private static final Integer UNKNOWN_ACTION_ID = 0; private static final Integer ACTION_ID_1 = 1; private static final String PROCESSOR_ID_1 = "processor-1"; private static final Integer ACTION_ID_2 = 2; private static final String PROCESSOR_ID_2 = "processor-2"; private StandardNiFiServiceFacade serviceFacade; private Authorizer authorizer; @Before public void setUp() throws Exception { // audit service final AuditService auditService = mock(AuditService.class); when(auditService.getAction(anyInt())).then(invocation -> { final Integer actionId = invocation.getArgumentAt(0, Integer.class); FlowChangeAction action = null; if (ACTION_ID_1.equals(actionId)) { action = getAction(actionId, PROCESSOR_ID_1); } else if (ACTION_ID_2.equals(actionId)) { action = getAction(actionId, PROCESSOR_ID_2); } return action; }); when(auditService.getActions(any(HistoryQuery.class))).then(invocation -> { final History history = new History(); history.setActions(Arrays.asList(getAction(ACTION_ID_1, PROCESSOR_ID_1), getAction(ACTION_ID_2, PROCESSOR_ID_2))); return history; }); // authorizable lookup final AuthorizableLookup authorizableLookup = mock(AuthorizableLookup.class); when(authorizableLookup.getProcessor(Mockito.anyString())).then(getProcessorInvocation -> { final String processorId = getProcessorInvocation.getArgumentAt(0, String.class); // processor-2 is no longer part of the flow if (processorId.equals(PROCESSOR_ID_2)) { throw new ResourceNotFoundException(""); } // component authorizable final ComponentAuthorizable componentAuthorizable = mock(ComponentAuthorizable.class); when(componentAuthorizable.getAuthorizable()).then(getAuthorizableInvocation -> { // authorizable final Authorizable authorizable = new Authorizable() { @Override public Authorizable getParentAuthorizable() { return null; } @Override public Resource getResource() { return ResourceFactory.getComponentResource(ResourceType.Processor, processorId, processorId); } }; return authorizable; }); return componentAuthorizable; }); // authorizer authorizer = mock(Authorizer.class); when(authorizer.authorize(any(AuthorizationRequest.class))).then(invocation -> { final AuthorizationRequest request = invocation.getArgumentAt(0, AuthorizationRequest.class); AuthorizationResult result = AuthorizationResult.denied(); if (request.getResource().getIdentifier().endsWith(PROCESSOR_ID_1)) { if (USER_1.equals(request.getIdentity())) { result = AuthorizationResult.approved(); } } else if (request.getResource().equals(ResourceFactory.getControllerResource())) { if (USER_2.equals(request.getIdentity())) { result = AuthorizationResult.approved(); } } return result; }); // flow controller final FlowController controller = mock(FlowController.class); when(controller.getResource()).thenCallRealMethod(); when(controller.getParentAuthorizable()).thenCallRealMethod(); // controller facade final ControllerFacade controllerFacade = new ControllerFacade(); controllerFacade.setFlowController(controller); serviceFacade = new StandardNiFiServiceFacade(); serviceFacade.setAuditService(auditService); serviceFacade.setAuthorizableLookup(authorizableLookup); serviceFacade.setAuthorizer(authorizer); serviceFacade.setEntityFactory(new EntityFactory()); serviceFacade.setDtoFactory(new DtoFactory()); serviceFacade.setControllerFacade(controllerFacade); } private FlowChangeAction getAction(final Integer actionId, final String processorId) { final FlowChangeAction action = new FlowChangeAction(); action.setId(actionId); action.setSourceId(processorId); action.setSourceType(Component.Processor); action.setOperation(Operation.Add); return action; } @Test(expected = ResourceNotFoundException.class) public void testGetUnknownAction() throws Exception { serviceFacade.getAction(UNKNOWN_ACTION_ID); } @Test public void testGetActionApprovedThroughAction() throws Exception { // set the user final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(USER_1))); SecurityContextHolder.getContext().setAuthentication(authentication); // get the action final ActionEntity entity = serviceFacade.getAction(ACTION_ID_1); // verify assertEquals(ACTION_ID_1, entity.getId()); assertTrue(entity.getCanRead()); // resource exists and is approved, no need to check the controller verify(authorizer, times(1)).authorize(argThat(new ArgumentMatcher<AuthorizationRequest>() { @Override public boolean matches(Object o) { return ((AuthorizationRequest) o).getResource().getIdentifier().endsWith(PROCESSOR_ID_1); } })); verify(authorizer, times(0)).authorize(argThat(new ArgumentMatcher<AuthorizationRequest>() { @Override public boolean matches(Object o) { return ((AuthorizationRequest) o).getResource().equals(ResourceFactory.getControllerResource()); } })); } @Test(expected = AccessDeniedException.class) public void testGetActionDeniedDespiteControllerAccess() throws Exception { // set the user final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(USER_2))); SecurityContextHolder.getContext().setAuthentication(authentication); try { // get the action serviceFacade.getAction(ACTION_ID_1); fail(); } finally { // resource exists, but should trigger access denied and will not check the controller verify(authorizer, times(1)).authorize(argThat(new ArgumentMatcher<AuthorizationRequest>() { @Override public boolean matches(Object o) { return ((AuthorizationRequest) o).getResource().getIdentifier().endsWith(PROCESSOR_ID_1); } })); verify(authorizer, times(0)).authorize(argThat(new ArgumentMatcher<AuthorizationRequest>() { @Override public boolean matches(Object o) { return ((AuthorizationRequest) o).getResource().equals(ResourceFactory.getControllerResource()); } })); } } @Test public void testGetActionApprovedThroughController() throws Exception { // set the user final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(USER_2))); SecurityContextHolder.getContext().setAuthentication(authentication); // get the action final ActionEntity entity = serviceFacade.getAction(ACTION_ID_2); // verify assertEquals(ACTION_ID_2, entity.getId()); assertTrue(entity.getCanRead()); // component does not exists, so only checks against the controller verify(authorizer, times(0)).authorize(argThat(new ArgumentMatcher<AuthorizationRequest>() { @Override public boolean matches(Object o) { return ((AuthorizationRequest) o).getResource().getIdentifier().endsWith(PROCESSOR_ID_2); } })); verify(authorizer, times(1)).authorize(argThat(new ArgumentMatcher<AuthorizationRequest>() { @Override public boolean matches(Object o) { return ((AuthorizationRequest) o).getResource().equals(ResourceFactory.getControllerResource()); } })); } @Test public void testGetActionsForUser1() throws Exception { // set the user final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(USER_1))); SecurityContextHolder.getContext().setAuthentication(authentication); final HistoryDTO dto = serviceFacade.getActions(new HistoryQueryDTO()); // verify user 1 only has access to actions for processor 1 dto.getActions().forEach(action -> { if (PROCESSOR_ID_1.equals(action.getSourceId())) { assertTrue(action.getCanRead()); } else if (PROCESSOR_ID_2.equals(action.getSourceId())) { assertFalse(action.getCanRead()); assertNull(action.getAction()); } }); } @Test public void testGetActionsForUser2() throws Exception { // set the user final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(USER_2))); SecurityContextHolder.getContext().setAuthentication(authentication); final HistoryDTO dto = serviceFacade.getActions(new HistoryQueryDTO()); // verify user 2 only has access to actions for processor 2 dto.getActions().forEach(action -> { if (PROCESSOR_ID_1.equals(action.getSourceId())) { assertFalse(action.getCanRead()); assertNull(action.getAction()); } else if (PROCESSOR_ID_2.equals(action.getSourceId())) { assertTrue(action.getCanRead()); } }); } }