Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.web.api.dto.BundleDTO;

import java.util.List;
import java.util.Set;
import java.util.function.Predicate;

Expand Down Expand Up @@ -213,6 +214,15 @@ public interface AuthorizableLookup {
*/
Authorizable getControllerServiceReferencingComponent(String controllerServiceId, String id);

/**
* Get the authorizables for components of the requested type that reference the specified Controller Service
*
* @param controllerServiceId controller service id
* @param componentType type of referencing component to be returned
* @return referencing component authorizables
*/
List<Authorizable> getControllerServiceReferencingComponents(String controllerServiceId, Class<? extends Authorizable> componentType);

/**
* Get the authorizable ReportingTask.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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.authorization;

import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.OperationAuthorizable;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.controller.FlowAnalysisRuleNode;
import org.apache.nifi.controller.ProcessorNode;
import org.apache.nifi.controller.ReportingTaskNode;
import org.apache.nifi.controller.ScheduledState;
import org.apache.nifi.controller.service.ControllerServiceNode;
import org.apache.nifi.controller.service.ControllerServiceState;

import java.util.List;

/**
* Authorizes updates to components that reference a Controller Service.
*/
public final class AuthorizeControllerServiceReferencingComponents {

/**
* Authorizes operation of the Controller Service together with each component that the requested state change
* affects. Referencing Controller Services are authorized when a Controller Service state is requested. Referencing
* Processors, Reporting Tasks, and Flow Analysis Rules are authorized when a scheduled state is requested. The
* referencing components are resolved from the Controller Service reference graph, matching the components that the
* requested state change updates.
*
* @param authorizer authorizer
* @param lookup lookup
* @param controllerServiceId controller service id
* @param controllerServiceState requested Controller Service state or null when not requested
* @param scheduledState requested scheduled state or null when not requested
* @param user user
*/
public static void authorize(
final Authorizer authorizer,
final AuthorizableLookup lookup,
final String controllerServiceId,
final ControllerServiceState controllerServiceState,
final ScheduledState scheduledState,
final NiFiUser user) {

final Authorizable controllerService = lookup.getControllerService(controllerServiceId).getAuthorizable();
OperationAuthorizable.authorizeOperation(controllerService, authorizer, user);

if (controllerServiceState != null) {
authorizeReferencingComponents(authorizer, lookup, controllerServiceId, user, ControllerServiceNode.class);
return;
}

if (scheduledState == null) {
return;
}

authorizeReferencingComponents(authorizer, lookup, controllerServiceId, user, ProcessorNode.class);
authorizeReferencingComponents(authorizer, lookup, controllerServiceId, user, ReportingTaskNode.class);
authorizeReferencingComponents(authorizer, lookup, controllerServiceId, user, FlowAnalysisRuleNode.class);
}

private static void authorizeReferencingComponents(
final Authorizer authorizer,
final AuthorizableLookup lookup,
final String controllerServiceId,
final NiFiUser user,
final Class<? extends Authorizable> componentType) {

final List<Authorizable> referencingComponents = lookup.getControllerServiceReferencingComponents(controllerServiceId, componentType);
for (final Authorizable referencingComponent : referencingComponents) {
OperationAuthorizable.authorizeOperation(referencingComponent, authorizer, user);
}
}

private AuthorizeControllerServiceReferencingComponents() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,12 @@ public Authorizable getControllerServiceReferencingComponent(String controllerSe
return reference;
}

@Override
public List<Authorizable> getControllerServiceReferencingComponents(final String controllerServiceId, final Class<? extends Authorizable> componentType) {
final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(controllerServiceId);
return List.copyOf(controllerService.getReferences().findRecursiveReferences(componentType));
}

@Override
public ComponentAuthorizable getReportingTask(final String id) {
final ReportingTaskNode reportingTaskNode = reportingTaskDAO.getReportingTask(id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.apache.nifi.authorization.AuthorizeComponentReference;
import org.apache.nifi.authorization.AuthorizeConfigVerification;
import org.apache.nifi.authorization.AuthorizeControllerServiceReference;
import org.apache.nifi.authorization.AuthorizeControllerServiceReferencingComponents;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.ComponentAuthorizable;
import org.apache.nifi.authorization.RequestAction;
Expand Down Expand Up @@ -569,6 +570,10 @@ public Response updateControllerServiceReferences(
throw new IllegalArgumentException("The controller service identifier must be specified.");
}

if (!id.equals(requestUpdateReferenceRequest.getId())) {
throw new IllegalArgumentException("The controller service identifier in the request must match the identifier provided in the URL");
}

if (requestUpdateReferenceRequest.getReferencingComponentRevisions() == null) {
throw new IllegalArgumentException("The controller service referencing components revisions must be specified.");
}
Expand Down Expand Up @@ -623,13 +628,9 @@ public Response updateControllerServiceReferences(
serviceFacade,
requestUpdateReferenceRequest,
requestRevisions,
lookup -> {
requestReferencingRevisions.entrySet().stream().forEach(e -> {
final Authorizable controllerService = lookup.getControllerServiceReferencingComponent(id, e.getKey());
OperationAuthorizable.authorizeOperation(controllerService, authorizer, NiFiUserUtils.getNiFiUser());
});
},
() -> serviceFacade.verifyUpdateControllerServiceReferencingComponents(requestUpdateReferenceRequest.getId(), verifyScheduledState, verifyControllerServiceState),
lookup -> AuthorizeControllerServiceReferencingComponents.authorize(
authorizer, lookup, id, verifyControllerServiceState, verifyScheduledState, NiFiUserUtils.getNiFiUser()),
() -> serviceFacade.verifyUpdateControllerServiceReferencingComponents(id, verifyScheduledState, verifyControllerServiceState),
(revisions, updateReferenceRequest) -> {
ScheduledState scheduledState = null;
try {
Expand All @@ -651,7 +652,7 @@ public Response updateControllerServiceReferences(

// update the controller service references
final ControllerServiceReferencingComponentsEntity entity = serviceFacade.updateControllerServiceReferencingComponents(
referencingRevisions, updateReferenceRequest.getId(), scheduledState, controllerServiceState);
referencingRevisions, id, scheduledState, controllerServiceState);

if (updateReferenceRequest.getUiOnly() == Boolean.TRUE) {
entity.getControllerServiceReferencingComponents().forEach(this::stripNonUiRelevantFields);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*
* 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.authorization;

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.NiFiUser;
import org.apache.nifi.authorization.user.StandardNiFiUser;
import org.apache.nifi.controller.FlowAnalysisRuleNode;
import org.apache.nifi.controller.ProcessorNode;
import org.apache.nifi.controller.ReportingTaskNode;
import org.apache.nifi.controller.ScheduledState;
import org.apache.nifi.controller.service.ControllerServiceNode;
import org.apache.nifi.controller.service.ControllerServiceState;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class AuthorizeControllerServiceReferencingComponentsTest {

private static final String CONTROLLER_SERVICE_ID = "controller-service-id";

private static final String REFERENCING_SERVICE_ID = "referencing-service-id";

private final NiFiUser user = new StandardNiFiUser.Builder().identity("unit-test-user").build();

@Mock
private Authorizer authorizer;

@Mock
private AuthorizableLookup lookup;

@Mock
private ComponentAuthorizable controllerServiceAuthorizable;

@Mock
private Authorizable controllerService;

@Mock
private Authorizable referencingService;

@Mock
private Authorizable referencingProcessor;

@Mock
private Authorizable referencingReportingTask;

@Mock
private Authorizable referencingFlowAnalysisRule;

@Test
void testAuthorizeControllerServiceStateAuthorizesServiceAndReferencingServices() {
stubControllerService();
when(lookup.getControllerServiceReferencingComponents(CONTROLLER_SERVICE_ID, ControllerServiceNode.class))
.thenReturn(List.of(referencingService));

AuthorizeControllerServiceReferencingComponents.authorize(
authorizer, lookup, CONTROLLER_SERVICE_ID, ControllerServiceState.ENABLED, ScheduledState.DISABLED, user);

verify(controllerService).authorize(authorizer, RequestAction.WRITE, user);
verify(referencingService).authorize(authorizer, RequestAction.WRITE, user);
verify(lookup, never()).getControllerServiceReferencingComponents(eq(CONTROLLER_SERVICE_ID), eq(ProcessorNode.class));
verify(lookup, never()).getControllerServiceReferencingComponents(eq(CONTROLLER_SERVICE_ID), eq(ReportingTaskNode.class));
verify(lookup, never()).getControllerServiceReferencingComponents(eq(CONTROLLER_SERVICE_ID), eq(FlowAnalysisRuleNode.class));
}

@Test
void testAuthorizeScheduledStateAuthorizesServiceAndReferencingSchedulableComponents() {
stubControllerService();
when(lookup.getControllerServiceReferencingComponents(CONTROLLER_SERVICE_ID, ProcessorNode.class))
.thenReturn(List.of(referencingProcessor));
when(lookup.getControllerServiceReferencingComponents(CONTROLLER_SERVICE_ID, ReportingTaskNode.class))
.thenReturn(List.of(referencingReportingTask));
when(lookup.getControllerServiceReferencingComponents(CONTROLLER_SERVICE_ID, FlowAnalysisRuleNode.class))
.thenReturn(List.of(referencingFlowAnalysisRule));

AuthorizeControllerServiceReferencingComponents.authorize(
authorizer, lookup, CONTROLLER_SERVICE_ID, null, ScheduledState.RUNNING, user);

verify(controllerService).authorize(authorizer, RequestAction.WRITE, user);
verify(referencingProcessor).authorize(authorizer, RequestAction.WRITE, user);
verify(referencingReportingTask).authorize(authorizer, RequestAction.WRITE, user);
verify(referencingFlowAnalysisRule).authorize(authorizer, RequestAction.WRITE, user);
verify(lookup, never()).getControllerServiceReferencingComponents(eq(CONTROLLER_SERVICE_ID), eq(ControllerServiceNode.class));
}

@Test
void testAuthorizeEveryReferencingComponentEvaluated() {
stubControllerService();
when(lookup.getControllerServiceReferencingComponents(CONTROLLER_SERVICE_ID, ProcessorNode.class))
.thenReturn(List.of(referencingProcessor, referencingFlowAnalysisRule));
when(lookup.getControllerServiceReferencingComponents(CONTROLLER_SERVICE_ID, ReportingTaskNode.class))
.thenReturn(List.of(referencingReportingTask));
when(lookup.getControllerServiceReferencingComponents(CONTROLLER_SERVICE_ID, FlowAnalysisRuleNode.class))
.thenReturn(List.of());

AuthorizeControllerServiceReferencingComponents.authorize(
authorizer, lookup, CONTROLLER_SERVICE_ID, null, ScheduledState.STOPPED, user);

verify(referencingProcessor).authorize(authorizer, RequestAction.WRITE, user);
verify(referencingFlowAnalysisRule).authorize(authorizer, RequestAction.WRITE, user);
verify(referencingReportingTask).authorize(authorizer, RequestAction.WRITE, user);
}

@Test
void testAuthorizeDeniedControllerService() {
stubControllerService();
denyOperation(controllerService, CONTROLLER_SERVICE_ID);

assertThrows(AccessDeniedException.class, () -> AuthorizeControllerServiceReferencingComponents.authorize(
authorizer, lookup, CONTROLLER_SERVICE_ID, ControllerServiceState.ENABLED, null, user));

verify(lookup, never()).getControllerServiceReferencingComponents(anyString(), any());
}

@Test
void testAuthorizeDeniedReferencingComponent() {
stubControllerService();
when(lookup.getControllerServiceReferencingComponents(CONTROLLER_SERVICE_ID, ControllerServiceNode.class))
.thenReturn(List.of(referencingService));
denyOperation(referencingService, REFERENCING_SERVICE_ID);

assertThrows(AccessDeniedException.class, () -> AuthorizeControllerServiceReferencingComponents.authorize(
authorizer, lookup, CONTROLLER_SERVICE_ID, ControllerServiceState.DISABLED, null, user));
}

@Test
void testAuthorizeWithoutRequestedState() {
stubControllerService();

AuthorizeControllerServiceReferencingComponents.authorize(authorizer, lookup, CONTROLLER_SERVICE_ID, null, null, user);

verify(controllerService).authorize(authorizer, RequestAction.WRITE, user);
verify(lookup, never()).getControllerServiceReferencingComponents(anyString(), any());
}

@Test
void testAuthorizeWithoutReferencingComponents() {
stubControllerService();
when(lookup.getControllerServiceReferencingComponents(CONTROLLER_SERVICE_ID, ControllerServiceNode.class))
.thenReturn(List.of());

AuthorizeControllerServiceReferencingComponents.authorize(
authorizer, lookup, CONTROLLER_SERVICE_ID, ControllerServiceState.ENABLED, null, user);

verify(controllerService).authorize(authorizer, RequestAction.WRITE, user);
}

private void stubControllerService() {
when(lookup.getControllerService(CONTROLLER_SERVICE_ID)).thenReturn(controllerServiceAuthorizable);
when(controllerServiceAuthorizable.getAuthorizable()).thenReturn(controllerService);
}

private void denyOperation(final Authorizable authorizable, final String identifier) {
doThrow(new AccessDeniedException("Access is denied"))
.when(authorizable).authorize(authorizer, RequestAction.WRITE, user);
when(authorizable.getResource())
.thenReturn(ResourceFactory.getComponentResource(ResourceType.ControllerService, identifier, "Controller Service"));
when(authorizer.authorize(any())).thenReturn(AuthorizationResult.denied("Access is denied"));
}
}
Loading
Loading