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]

Reply via email to