exceptionfactory commented on code in PR #9535: URL: https://github.com/apache/nifi/pull/9535#discussion_r1849490515
########## nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CopyResponseEntity.java: ########## @@ -0,0 +1,220 @@ +/* + * 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.api.entity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.xml.bind.annotation.XmlType; +import org.apache.nifi.flow.ExternalControllerServiceReference; +import org.apache.nifi.flow.ParameterProviderReference; +import org.apache.nifi.flow.VersionedConnection; +import org.apache.nifi.flow.VersionedFunnel; +import org.apache.nifi.flow.VersionedLabel; +import org.apache.nifi.flow.VersionedParameterContext; +import org.apache.nifi.flow.VersionedPort; +import org.apache.nifi.flow.VersionedProcessGroup; +import org.apache.nifi.flow.VersionedProcessor; +import org.apache.nifi.flow.VersionedRemoteProcessGroup; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * A response to copy a portion of the flow. + */ +@XmlType(name = "copyResponseEntity") +public class CopyResponseEntity extends Entity { + + private String id; + + private Map<String, ExternalControllerServiceReference> externalControllerServiceReferences; + private Map<String, VersionedParameterContext> parameterContexts; + private Map<String, ParameterProviderReference> parameterProviders; Review Comment: Is there a reason for declaring default `HashSet` values below but not declaring default `HashMap` values for these properties? ########## nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/ProcessGroupAuthorizable.java: ########## @@ -89,10 +105,17 @@ public interface ProcessGroupAuthorizable extends AuthorizableHolder { Set<Authorizable> getEncapsulatedRemoteProcessGroups(); /** - * The authorizables for all encapsulated input ports. Non null + * The authorizables for all encapsulated controller services. Non null * - * @return all encapsulated input ports + * @return all encapsulated controller services */ Set<ComponentAuthorizable> getEncapsulatedControllerServices(); + /** + * The authorizables for all encapsulated controller services that meet the specified predicate. Non null + * + * @return all encapsulated controller services + */ + Set<ComponentAuthorizable> getEncapsulatedControllerServices(Predicate<org.apache.nifi.authorization.resource.ComponentAuthorizable> filter); Review Comment: See note above on `filter` parameter. ########## nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/ProcessGroupAuthorizable.java: ########## @@ -32,13 +33,28 @@ public interface ProcessGroupAuthorizable extends AuthorizableHolder { */ ProcessGroup getProcessGroup(); + /** + * Returns the Parameter Context Authorizable. May be null if the underlying Process Group is not + * bound to a Parameter Context. + * + * @return the Parameter Context authorizable + */ + Authorizable getParameterContextAuthorizable(); Review Comment: If this method can return `null`, would it be better to return it in an `Optional` wrapper? ########## nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java: ########## @@ -326,6 +336,85 @@ public Response exportProcessGroup( return generateOkResponse(currentVersionedFlowSnapshot).header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=\"%s\"", filename)).build(); } + /** + * Generates a copy response for the given copy request. + * + * @param groupId The id of the process group + * @param copyRequestEntity The copy request + * @return A copyResponseEntity. + */ + @POST + @Consumes(MediaType.WILDCARD) Review Comment: Should this MediaType be `APPLICATION_JSON`? ########## nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java: ########## @@ -326,6 +336,85 @@ public Response exportProcessGroup( return generateOkResponse(currentVersionedFlowSnapshot).header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=\"%s\"", filename)).build(); } + /** + * Generates a copy response for the given copy request. + * + * @param groupId The id of the process group + * @param copyRequestEntity The copy request + * @return A copyResponseEntity. + */ + @POST + @Consumes(MediaType.WILDCARD) + @Produces(MediaType.APPLICATION_JSON) + @Path("{id}/copy") + @Operation( + summary = "Generates a copy response for the given copy request", + responses = @ApiResponse(content = @Content(schema = @Schema(implementation = CopyResponseEntity.class))), + security = { + @SecurityRequirement(name = "Read - /{component-type}/{uuid} - For all encapsulated components") + } + ) + @ApiResponses( + value = { + @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), + @ApiResponse(responseCode = "401", description = "Client could not be authenticated."), + @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."), + @ApiResponse(responseCode = "404", description = "The specified resource could not be found."), + @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.") + } + ) + public Response copy( + @Parameter( + description = "The process group id.", + required = true + ) + @PathParam("id") final String groupId, + @Parameter( + description = "The request including the components to be copied from the specified Process Group.", + required = true + ) final CopyRequestEntity copyRequestEntity) { Review Comment: Does this `copyRequestEntity` need to be null-checked? ########## nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/ProcessGroupAuthorizable.java: ########## @@ -32,13 +33,28 @@ public interface ProcessGroupAuthorizable extends AuthorizableHolder { */ ProcessGroup getProcessGroup(); + /** + * Returns the Parameter Context Authorizable. May be null if the underlying Process Group is not + * bound to a Parameter Context. + * + * @return the Parameter Context authorizable + */ + Authorizable getParameterContextAuthorizable(); + /** * The authorizables for all encapsulated processors. Non null * * @return all encapsulated processors */ Set<ComponentAuthorizable> getEncapsulatedProcessors(); + /** + * The authorizables for all encapsulated processors that meet the specified predicate. Non null + * + * @return all encapsulated processors + */ + Set<ComponentAuthorizable> getEncapsulatedProcessors(Predicate<org.apache.nifi.authorization.resource.ComponentAuthorizable> filter); Review Comment: Since the method has a comment, it would be helpful to add a description for the `filter` parameter. ########## nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java: ########## @@ -2836,6 +2946,331 @@ public Response importProcessGroup( } + /** + * Pastes the specified payload into the given Process Group. + * + * @param pasteRequestEntity A PasteResponseEntity. + * @return A pasteResponseEntity. + */ + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("{id}/paste") + @Operation( + summary = "Pastes into the specified process group", + responses = @ApiResponse(content = @Content(schema = @Schema(implementation = PasteResponseEntity.class))), + security = { + @SecurityRequirement(name = "Write - /process-groups/{uuid}") + } + ) + @ApiResponses( + value = { + @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), + @ApiResponse(responseCode = "401", description = "Client could not be authenticated."), + @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."), + @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.") + } + ) + public Response paste( + @Parameter( + description = "The process group id.", + required = true + ) + @PathParam("id") final String groupId, + @Parameter( + description = "The request including the components to be pasted into the specified Process Group.", + required = true + ) final PasteRequestEntity pasteRequestEntity) { + + // verify the payload was specified + if (pasteRequestEntity == null) { + throw new IllegalArgumentException("The paste payload must be specified."); + } + + // verify the revision is specified + if (pasteRequestEntity.getRevision() == null) { + throw new IllegalArgumentException("Revision must be specified."); + } + + // verify the copy response is specified + if (pasteRequestEntity.getCopyResponse() == null) { + throw new IllegalArgumentException("The details of the copied components must be specified."); + } + + if (isReplicateRequest()) { + return replicate(HttpMethod.PUT, pasteRequestEntity); + } else if (isDisconnectedFromCluster()) { + verifyDisconnectedNodeModification(pasteRequestEntity.getDisconnectedNodeAcknowledged()); + } + + final CopyResponseEntity copyResponseEntity = pasteRequestEntity.getCopyResponse(); + final VersionedProcessGroup versionedProcessGroup = getVersionedProcessGroup(copyResponseEntity); + mapVersionedIds(versionedProcessGroup, new HashMap<>(), new HashMap<>()); + + // resolve Bundle info + serviceFacade.discoverCompatibleBundles(versionedProcessGroup); + + // prep a pasted flow snapshot to attempt to resolve external services and referenced parameter providers + final RegisteredFlowSnapshot pastedFlowSnapshot = new RegisteredFlowSnapshot(); + pastedFlowSnapshot.setExternalControllerServices(copyResponseEntity.getExternalControllerServiceReferences()); + pastedFlowSnapshot.setFlowContents(versionedProcessGroup); + pastedFlowSnapshot.setParameterContexts(copyResponseEntity.getParameterContexts()); + pastedFlowSnapshot.setParameterProviders(copyResponseEntity.getParameterProviders()); + + // if there are any Controller Services referenced that are inherited from the parent group, + // resolve those to point to the appropriate Controller Service, if we are able to. + final FlowSnapshotContainer flowSnapshotContainer = new FlowSnapshotContainer(pastedFlowSnapshot); + final Set<String> unresolvedControllerServices = serviceFacade.resolveInheritedControllerServices(flowSnapshotContainer, groupId, NiFiUserUtils.getNiFiUser()); + + // If there are any Parameter Providers referenced by Parameter Contexts, resolve these to point to the appropriate Parameter Provider, if we are able to. + final Set<String> unresolvedParameterProviders = serviceFacade.resolveParameterProviders(pastedFlowSnapshot, NiFiUserUtils.getNiFiUser()); + + final Revision requestRevision = getRevision(pasteRequestEntity.getRevision(), groupId); + return withWriteLock( + serviceFacade, + pasteRequestEntity, + requestRevision, + lookup -> { + final NiFiUser user = NiFiUserUtils.getNiFiUser(); + + // ensure the user can write to the current group + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); + processGroup.authorize(authorizer, RequestAction.WRITE, user); + + // if the pasted content contains restricted components, ensure the user is allowed those restrictions + final Set<ConfigurableComponent> restrictedComponents = FlowRegistryUtils.getRestrictedComponents(versionedProcessGroup, serviceFacade); + restrictedComponents.forEach(restrictedComponent -> { + final ComponentAuthorizable restrictedComponentAuthorizable = lookup.getConfigurableComponent(restrictedComponent); + authorizeRestrictions(authorizer, restrictedComponentAuthorizable); + }); + + // authorizer controller services Review Comment: ```suggestion // authorize controller services ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
