diff --git a/nifi-api/src/main/java/org/apache/nifi/annotation/behavior/Restricted.java b/nifi-api/src/main/java/org/apache/nifi/annotation/behavior/Restricted.java index 07d729d66f..3237f0dd47 100644 --- a/nifi-api/src/main/java/org/apache/nifi/annotation/behavior/Restricted.java +++ b/nifi-api/src/main/java/org/apache/nifi/annotation/behavior/Restricted.java @@ -45,7 +45,15 @@ import java.lang.annotation.Target; @Inherited public @interface Restricted { /** - * Provides a description of why the component usage is restricted + * Provides a description of why the component usage is restricted. If using granular + * restrictions, specific explanations should be set in the Restriction. */ - String value(); + String value() default ""; + + /** + * Provides a listing of specific Restrictions. If unspecified, this component will + * require access to restricted components regardless of restrictions. + */ + Restriction[] restrictions() default {}; + } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/RestrictedComponentsAuthorizable.java b/nifi-api/src/main/java/org/apache/nifi/annotation/behavior/Restriction.java similarity index 50% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/RestrictedComponentsAuthorizable.java rename to nifi-api/src/main/java/org/apache/nifi/annotation/behavior/Restriction.java index 92a905bfa9..2a07108eec 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/RestrictedComponentsAuthorizable.java +++ b/nifi-api/src/main/java/org/apache/nifi/annotation/behavior/Restriction.java @@ -14,20 +14,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.nifi.authorization.resource; +package org.apache.nifi.annotation.behavior; -import org.apache.nifi.authorization.Resource; +import org.apache.nifi.components.RequiredPermission; -public class RestrictedComponentsAuthorizable implements Authorizable { +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; - @Override - public Authorizable getParentAuthorizable() { - return null; - } +/** + * Specific restriction for a component. Indicates what the required permission is and why the restriction exists. + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface Restriction { - @Override - public Resource getResource() { - return ResourceFactory.getRestrictedComponentsResource(); - } + /** + * Provides a listing of RequiredPermissions. + */ + RequiredPermission requiredPermission(); -} + /** + * Provides a explanation of why the component usage is restricted + */ + String explanation(); + +} \ No newline at end of file diff --git a/nifi-api/src/main/java/org/apache/nifi/components/RequiredPermission.java b/nifi-api/src/main/java/org/apache/nifi/components/RequiredPermission.java new file mode 100644 index 0000000000..cd4a376bd2 --- /dev/null +++ b/nifi-api/src/main/java/org/apache/nifi/components/RequiredPermission.java @@ -0,0 +1,52 @@ +/* + * 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.components; + +import java.util.Arrays; + +/** + * + */ +public enum RequiredPermission { + READ_FILESYSTEM("read-filesystem", "read filesystem"), + WRITE_FILESYSTEM("write-filesystem", "write filesystem"), + EXECUTE_CODE("execute-code", "execute code"), + ACCESS_KEYTAB("access-keytab", "access keytab"), + EXPORT_NIFI_DETAILS("export-nifi-details", "export nifi details"); + + private String permissionIdentifier; + private String permissionLabel; + + RequiredPermission(String permissionIdentifier, String permissionLabel) { + this.permissionIdentifier = permissionIdentifier; + this.permissionLabel = permissionLabel; + } + + public String getPermissionIdentifier() { + return permissionIdentifier; + } + + public String getPermissionLabel() { + return permissionLabel; + } + + public static RequiredPermission valueOfPermissionIdentifier(final String permissionIdentifier) { + return Arrays.stream(RequiredPermission.values()) + .filter(candidate -> candidate.getPermissionIdentifier().equals(permissionIdentifier)) + .findFirst().orElse(null); + } +} diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc index 81a667ab31..ea60e0a3c4 100644 --- a/nifi-docs/src/main/asciidoc/administration-guide.adoc +++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc @@ -1044,7 +1044,10 @@ Global access policies govern the following system level authorizations: |Data Provenance |access restricted components -|Allows users to create/modify restricted components assuming otherwise sufficient permissions +|Allows users to create/modify restricted components assuming other permissions are sufficient. The restricted +components may indicate which specific permissions are required. Permissions can be granted for specific +restrictions or be granted regardless of restrictions. If permission is granted regardless of restrictions, +the user can create/modify all restricted components. |N/A |access all policies diff --git a/nifi-docs/src/main/asciidoc/user-guide.adoc b/nifi-docs/src/main/asciidoc/user-guide.adoc index 7b414cf6f6..3e55ffede8 100644 --- a/nifi-docs/src/main/asciidoc/user-guide.adoc +++ b/nifi-docs/src/main/asciidoc/user-guide.adoc @@ -191,7 +191,9 @@ The available global access policies are: |view the UI |Allows users to view the UI |access the controller |Allows users to view and modify the controller including reporting tasks, Controller Services, and nodes in the cluster |query provenance |Allows users to submit a provenance search and request even lineage -|access restricted components |Allows users to create/modify restricted components assuming otherwise sufficient permissions +|access restricted components |Allows users to create/modify restricted components assuming other permissions are sufficient. The restricted +components may indicate which specific permissions are required. Permissions can be granted for specific restrictions or be granted regardless +of restrictions. If permission is granted regardless of restrictions, the user can create/modify all restricted components. |access all policies |Allows users to view and modify the policies for all components |access users/groups |Allows users view and modify the users and user groups |retrieve site-to-site details | Allows other NiFi instances to retrieve Site-To-Site details @@ -267,13 +269,18 @@ image::add-processor-with-tag-cloud.png["Add Processor with Tag Cloud"] Restricted components will be marked with a image:restricted.png["Restricted"] -icon next to their name. These are components that can be used to execute arbitrary unsanitized code provided by the operator -through the NiFi REST API/UI or can be used to obtain or alter data on the NiFi host system using the NiFi OS credentials. -These components could be used by an otherwise authorized NiFi user to go beyond the intended use of the application, escalate -privilege, or could expose data about the internals of the NiFi process or the host system. All of these capabilities should -be considered privileged, and admins should be aware of these capabilities and explicitly enable them for a subset of trusted users. +icon next to their name. Hovering over the tooltip will display the specific restrictions this component requires. If the component +does not list any specific restrictions it will require access to restricted components regardless of restrictions. These are components +that can be used to execute arbitrary unsanitized code provided by the operator through the NiFi REST API/UI or can be used to obtain +or alter data on the NiFi host system using the NiFi OS credentials. These components could be used by an otherwise authorized NiFi +user to go beyond the intended use of the application, escalate privilege, or could expose data about the internals of the NiFi process +or the host system. All of these capabilities should be considered privileged, and admins should be aware of these capabilities and +explicitly enable them for a subset of trusted users. -Before a user is allowed to create and modify restricted components they must be granted access to restricted components. For more information refer to +Before a user is allowed to create and modify restricted components they must be granted access to restricted components. This can be +assigned regardless of restrictions. In this case, the user will have access to all restricted components. Alternatively, users can +be assigned access to specific restrictions. If the user has been granted access to all restrictions a component requires, they will +have access to that component assuming otherwise sufficient permissions. For more information refer to <>. Clicking the `Add` button or double-clicking on a Processor Type will add the selected Processor to the canvas at the diff --git a/nifi-nar-bundles/nifi-flume-bundle/nifi-flume-processors/src/main/java/org/apache/nifi/processors/flume/ExecuteFlumeSink.java b/nifi-nar-bundles/nifi-flume-bundle/nifi-flume-processors/src/main/java/org/apache/nifi/processors/flume/ExecuteFlumeSink.java index ec99acbe65..70bcd12cb7 100644 --- a/nifi-nar-bundles/nifi-flume-bundle/nifi-flume-processors/src/main/java/org/apache/nifi/processors/flume/ExecuteFlumeSink.java +++ b/nifi-nar-bundles/nifi-flume-bundle/nifi-flume-processors/src/main/java/org/apache/nifi/processors/flume/ExecuteFlumeSink.java @@ -25,12 +25,14 @@ import org.apache.flume.conf.Configurables; import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.TriggerSerially; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnScheduled; import org.apache.nifi.annotation.lifecycle.OnStopped; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.Validator; import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessSession; @@ -49,7 +51,13 @@ import java.util.Set; @Tags({"flume", "hadoop", "put", "sink", "restricted"}) @InputRequirement(Requirement.INPUT_REQUIRED) @CapabilityDescription("Execute a Flume sink. Each input FlowFile is converted into a Flume Event for processing by the sink.") -@Restricted("Provides operator the ability to execute arbitrary Flume configurations assuming all permissions that NiFi has.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.EXECUTE_CODE, + explanation = "Provides operator the ability to execute arbitrary Flume configurations assuming all permissions that NiFi has.") + } +) public class ExecuteFlumeSink extends AbstractFlumeProcessor { public static final PropertyDescriptor SINK_TYPE = new PropertyDescriptor.Builder() diff --git a/nifi-nar-bundles/nifi-flume-bundle/nifi-flume-processors/src/main/java/org/apache/nifi/processors/flume/ExecuteFlumeSource.java b/nifi-nar-bundles/nifi-flume-bundle/nifi-flume-processors/src/main/java/org/apache/nifi/processors/flume/ExecuteFlumeSource.java index 63ed190e48..4daed7019b 100644 --- a/nifi-nar-bundles/nifi-flume-bundle/nifi-flume-processors/src/main/java/org/apache/nifi/processors/flume/ExecuteFlumeSource.java +++ b/nifi-nar-bundles/nifi-flume-bundle/nifi-flume-processors/src/main/java/org/apache/nifi/processors/flume/ExecuteFlumeSource.java @@ -29,12 +29,14 @@ import org.apache.flume.source.EventDrivenSourceRunner; import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.TriggerSerially; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnScheduled; import org.apache.nifi.annotation.lifecycle.OnStopped; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.Validator; import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessSession; @@ -55,7 +57,13 @@ import java.util.concurrent.atomic.AtomicReference; @Tags({"flume", "hadoop", "get", "source", "restricted"}) @InputRequirement(Requirement.INPUT_FORBIDDEN) @CapabilityDescription("Execute a Flume source. Each Flume Event is sent to the success relationship as a FlowFile") -@Restricted("Provides operator the ability to execute arbitrary Flume configurations assuming all permissions that NiFi has.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.EXECUTE_CODE, + explanation = "Provides operator the ability to execute arbitrary Flume configurations assuming all permissions that NiFi has.") + } +) public class ExecuteFlumeSource extends AbstractFlumeProcessor { public static final PropertyDescriptor SOURCE_TYPE = new PropertyDescriptor.Builder() diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentRestrictionPermissionDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentRestrictionPermissionDTO.java new file mode 100644 index 0000000000..6fb57995f8 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentRestrictionPermissionDTO.java @@ -0,0 +1,82 @@ +/* + * 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.dto; + +import io.swagger.annotations.ApiModelProperty; + +import javax.xml.bind.annotation.XmlType; +import java.util.Objects; + +/** + * Class used for providing details about a components usage restriction. + */ +@XmlType(name = "componentRestrictionPermission") +public class ComponentRestrictionPermissionDTO { + + private RequiredPermissionDTO requiredPermission; + private PermissionsDTO permissions; + + /** + * @return The required permission necessary for this restriction. + */ + @ApiModelProperty( + value = "The required permission necessary for this restriction." + ) + public RequiredPermissionDTO getRequiredPermission() { + return requiredPermission; + } + + public void setRequiredPermission(RequiredPermissionDTO requiredPermission) { + this.requiredPermission = requiredPermission; + } + + /** + * @return The permissions for this component restriction. + */ + @ApiModelProperty( + value = "The permissions for this component restriction. Note: the read permission are not used and will always be false." + ) + public PermissionsDTO getPermissions() { + return permissions; + } + + public void setPermissions(PermissionsDTO permissions) { + this.permissions = permissions; + } + + @Override + public int hashCode() { + return Objects.hash(requiredPermission); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + + if (obj == this) { + return true; + } + + if (obj.getClass() != getClass()) { + return false; + } + + return Objects.equals(requiredPermission, ((ComponentRestrictionPermissionDTO)obj).requiredPermission); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DocumentedTypeDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DocumentedTypeDTO.java index 7f38a08ba6..386f3a55b1 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DocumentedTypeDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DocumentedTypeDTO.java @@ -33,7 +33,9 @@ public class DocumentedTypeDTO { private BundleDTO bundle; private List controllerServiceApis; private String description; + private boolean restricted; private String usageRestriction; + private Set explicitRestrictions; private String deprecationReason; private Set tags; @@ -51,11 +53,39 @@ public class DocumentedTypeDTO { this.description = description; } + /** + * @return Whether this type is restricted + */ + @ApiModelProperty( + value = "Whether this type is restricted." + ) + public boolean isRestricted() { + return restricted; + } + + public void setRestricted(boolean restricted) { + this.restricted = restricted; + } + + /** + * @return An optional collection of explicit restrictions + */ + @ApiModelProperty( + value = "An optional collection of explicit restrictions. If specified, these explicit restrictions will be enfored." + ) + public Set getExplicitRestrictions() { + return explicitRestrictions; + } + + public void setExplicitRestrictions(Set explicitRestrictions) { + this.explicitRestrictions = explicitRestrictions; + } + /** * @return An optional description of why the usage of this component is restricted */ @ApiModelProperty( - value = "The description of why the usage of this component is restricted." + value = "The optional description of why the usage of this component is restricted." ) public String getUsageRestriction() { return usageRestriction; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ExplicitRestrictionDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ExplicitRestrictionDTO.java new file mode 100644 index 0000000000..87c336d495 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ExplicitRestrictionDTO.java @@ -0,0 +1,59 @@ +/* + * 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.dto; + +import io.swagger.annotations.ApiModelProperty; + +import javax.xml.bind.annotation.XmlType; + +/** + * Class used for providing details about a components usage restriction. + */ +@XmlType(name = "explicitRestriction") +public class ExplicitRestrictionDTO { + + private RequiredPermissionDTO requiredPermission; + private String explanation; + + /** + * @return The required permission necessary for this restriction. + */ + @ApiModelProperty( + value = "The required permission necessary for this restriction." + ) + public RequiredPermissionDTO getRequiredPermission() { + return requiredPermission; + } + + public void setRequiredPermission(RequiredPermissionDTO requiredPermission) { + this.requiredPermission = requiredPermission; + } + + /** + * @return The description of why the usage of this component is restricted for this required permission. + */ + @ApiModelProperty( + value = "The description of why the usage of this component is restricted for this required permission." + ) + public String getExplanation() { + return explanation; + } + + public void setExplanation(String explanation) { + this.explanation = explanation; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/RequiredPermissionDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/RequiredPermissionDTO.java new file mode 100644 index 0000000000..1fa3ba4b03 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/RequiredPermissionDTO.java @@ -0,0 +1,82 @@ +/* + * 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.dto; + +import io.swagger.annotations.ApiModelProperty; + +import javax.xml.bind.annotation.XmlType; +import java.util.Objects; + +/** + * Class used for providing details about a components usage restriction. + */ +@XmlType(name = "requiredPermission") +public class RequiredPermissionDTO { + + private String id; + private String label; + + /** + * @return The required sub-permission necessary for this restriction. + */ + @ApiModelProperty( + value = "The required sub-permission necessary for this restriction." + ) + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + /** + * @return The label for the required sub-permission necessary for this restriction. + */ + @ApiModelProperty( + value = "The label for the required sub-permission necessary for this restriction." + ) + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + + if (obj == this) { + return true; + } + + if (obj.getClass() != getClass()) { + return false; + } + + return Objects.equals(id, ((RequiredPermissionDTO)obj).id); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CurrentUserEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CurrentUserEntity.java index 8121ce4bce..c157c8bbf3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CurrentUserEntity.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CurrentUserEntity.java @@ -17,9 +17,11 @@ package org.apache.nifi.web.api.entity; import io.swagger.annotations.ApiModelProperty; +import org.apache.nifi.web.api.dto.ComponentRestrictionPermissionDTO; import org.apache.nifi.web.api.dto.PermissionsDTO; import javax.xml.bind.annotation.XmlRootElement; +import java.util.Set; /** * A serialized representation of this class can be placed in the entity body of a response to the API. This particular entity holds the users identity. @@ -37,6 +39,7 @@ public class CurrentUserEntity extends Entity { private PermissionsDTO policiesPermissions; private PermissionsDTO systemPermissions; private PermissionsDTO restrictedComponentsPermissions; + private Set componentRestrictionPermissions; private boolean canVersionFlows; @@ -148,6 +151,18 @@ public class CurrentUserEntity extends Entity { this.restrictedComponentsPermissions = restrictedComponentsPermissions; } + /** + * @return permissions for specific component restrictions + */ + @ApiModelProperty("Permissions for specific component restrictions.") + public Set getComponentRestrictionPermissions() { + return componentRestrictionPermissions; + } + + public void setComponentRestrictionPermissions(Set componentRestrictionPermissions) { + this.componentRestrictionPermissions = componentRestrictionPermissions; + } + /** * @return whether the current user can version flows */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java index e96fe1c4ae..9294906ab9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java @@ -18,6 +18,7 @@ package org.apache.nifi.documentation.html; import org.apache.nifi.annotation.behavior.DynamicProperties; import org.apache.nifi.annotation.behavior.DynamicProperty; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.SystemResourceConsideration; import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.Restricted; @@ -248,7 +249,32 @@ public class HtmlDocumentationWriter implements DocumentationWriter { writeSimpleElement(xmlStreamWriter, "h3", "Restricted: "); if(restricted != null) { - xmlStreamWriter.writeCharacters(restricted.value()); + final String value = restricted.value(); + + if (!StringUtils.isBlank(value)) { + xmlStreamWriter.writeCharacters(restricted.value()); + } + + final Restriction[] restrictions = restricted.restrictions(); + if (restrictions != null && restrictions.length > 0) { + xmlStreamWriter.writeStartElement("table"); + xmlStreamWriter.writeAttribute("id", "restrictions"); + xmlStreamWriter.writeStartElement("tr"); + writeSimpleElement(xmlStreamWriter, "th", "Required Permission"); + writeSimpleElement(xmlStreamWriter, "th", "Explanation"); + xmlStreamWriter.writeEndElement(); + + for (Restriction restriction : restrictions) { + xmlStreamWriter.writeStartElement("tr"); + writeSimpleElement(xmlStreamWriter, "td", restriction.requiredPermission().getPermissionLabel()); + writeSimpleElement(xmlStreamWriter, "td", restriction.explanation()); + xmlStreamWriter.writeEndElement(); + } + + xmlStreamWriter.writeEndElement(); + } else { + xmlStreamWriter.writeCharacters("This component requires access to restricted components regardless of restriction."); + } } else { xmlStreamWriter.writeCharacters("This component is not restricted."); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/example/FullyDocumentedProcessor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/example/FullyDocumentedProcessor.java index 999040db28..8442a33ce6 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/example/FullyDocumentedProcessor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/example/FullyDocumentedProcessor.java @@ -18,15 +18,16 @@ package org.apache.nifi.documentation.example; import org.apache.nifi.annotation.behavior.DynamicProperty; import org.apache.nifi.annotation.behavior.DynamicRelationship; -import org.apache.nifi.annotation.behavior.SystemResourceConsideration; -import org.apache.nifi.annotation.behavior.SystemResource; import org.apache.nifi.annotation.behavior.InputRequirement; +import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.ReadsAttribute; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.Stateful; +import org.apache.nifi.annotation.behavior.SystemResource; +import org.apache.nifi.annotation.behavior.SystemResourceConsideration; import org.apache.nifi.annotation.behavior.WritesAttribute; import org.apache.nifi.annotation.behavior.WritesAttributes; -import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.SeeAlso; import org.apache.nifi.annotation.documentation.Tags; @@ -34,6 +35,7 @@ import org.apache.nifi.annotation.lifecycle.OnRemoved; import org.apache.nifi.annotation.lifecycle.OnShutdown; import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.state.Scope; import org.apache.nifi.processor.AbstractProcessor; import org.apache.nifi.processor.ProcessContext; @@ -59,7 +61,14 @@ import java.util.Set; @DynamicProperty(name = "Relationship Name", supportsExpressionLanguage = true, value = "some XPath", description = "Routes FlowFiles to relationships based on XPath") @DynamicRelationship(name = "name from dynamic property", description = "all files that match the properties XPath") @Stateful(scopes = {Scope.CLUSTER, Scope.LOCAL}, description = "state management description") -@Restricted("processor restriction description") +@Restricted( + value = "processor restriction description", + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.READ_FILESYSTEM, + explanation = "Requires read filesystem permission") + } +) @InputRequirement(Requirement.INPUT_FORBIDDEN) @SystemResourceConsideration(resource = SystemResource.CPU) @SystemResourceConsideration(resource = SystemResource.DISK, description = "Customized disk usage description") diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/html/ProcessorDocumentationWriterTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/html/ProcessorDocumentationWriterTest.java index 325f18b41a..10333d0100 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/html/ProcessorDocumentationWriterTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/html/ProcessorDocumentationWriterTest.java @@ -19,6 +19,7 @@ package org.apache.nifi.documentation.html; import org.apache.nifi.annotation.behavior.SystemResourceConsideration; import org.apache.nifi.annotation.behavior.SystemResource; import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.documentation.DocumentationWriter; import org.apache.nifi.documentation.example.DeprecatedProcessor; import org.apache.nifi.documentation.example.FullyDocumentedProcessor; @@ -73,6 +74,8 @@ public class ProcessorDocumentationWriterTest { assertContains(results, "state management description"); assertContains(results, "processor restriction description"); + assertContains(results, RequiredPermission.READ_FILESYSTEM.getPermissionLabel()); + assertContains(results, "Requires read filesystem permission"); assertNotContains(results, "iconSecure.png"); assertContains(results, FullyDocumentedProcessor.class.getAnnotation(CapabilityDescription.class) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java index 79392b7394..c18598bb2d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java @@ -17,6 +17,7 @@ package org.apache.nifi.authorization.resource; import org.apache.nifi.authorization.Resource; +import org.apache.nifi.components.RequiredPermission; import java.util.Objects; @@ -327,6 +328,31 @@ public final class ResourceFactory { return RESTRICTED_COMPONENTS_RESOURCE; } + /** + * Gets a Resource for accessing certain kinds of restricted components. + * + * @param requiredPermission The required permission + * @return The restricted components resource + */ + public static Resource getRestrictedComponentsResource(final RequiredPermission requiredPermission) { + return new Resource() { + @Override + public String getIdentifier() { + return String.format("%s/%s", RESTRICTED_COMPONENTS_RESOURCE.getIdentifier(), requiredPermission.getPermissionIdentifier()); + } + + @Override + public String getName() { + return requiredPermission.getPermissionLabel(); + } + + @Override + public String getSafeDescription() { + return "Components requiring additional permission: " + requiredPermission.getPermissionLabel(); + } + }; + } + /** * Gets the Resource for accessing Tenants which includes creating, modifying, and deleting Users and UserGroups. * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/RestrictedComponentsAuthorizableFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/RestrictedComponentsAuthorizableFactory.java new file mode 100644 index 0000000000..95ab7fc586 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/RestrictedComponentsAuthorizableFactory.java @@ -0,0 +1,119 @@ +/* + * 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.resource; + +import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; +import org.apache.nifi.authorization.AccessDeniedException; +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.user.NiFiUser; +import org.apache.nifi.components.RequiredPermission; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class RestrictedComponentsAuthorizableFactory { + + private static final Authorizable RESTRICTED_COMPONENTS_AUTHORIZABLE = new Authorizable() { + @Override + public Authorizable getParentAuthorizable() { + return null; + } + + @Override + public Resource getResource() { + return ResourceFactory.getRestrictedComponentsResource(); + } + }; + + public static Authorizable getRestrictedComponentsAuthorizable() { + return RESTRICTED_COMPONENTS_AUTHORIZABLE; + } + + public static Authorizable getRestrictedComponentsAuthorizable(final RequiredPermission requiredPermission) { + return new Authorizable() { + @Override + public Authorizable getParentAuthorizable() { + return RESTRICTED_COMPONENTS_AUTHORIZABLE; + } + + @Override + public Resource getResource() { + return ResourceFactory.getRestrictedComponentsResource(requiredPermission); + } + + @Override + public AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map resourceContext) { + if (user == null) { + throw new AccessDeniedException("Unknown user."); + } + + final AuthorizationResult resourceResult = Authorizable.super.checkAuthorization(authorizer, action, user, resourceContext); + + // if we're denied from the resource try inheriting + if (Result.Denied.equals(resourceResult.getResult())) { + return getParentAuthorizable().checkAuthorization(authorizer, action, user, resourceContext); + } else { + return resourceResult; + } + } + + @Override + public void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map resourceContext) throws AccessDeniedException { + if (user == null) { + throw new AccessDeniedException("Unknown user."); + } + + try { + Authorizable.super.authorize(authorizer, action, user, resourceContext); + } catch (final AccessDeniedException resourceDenied) { + // if we're denied from the resource try inheriting + try { + getParentAuthorizable().authorize(authorizer, action, user, resourceContext); + } catch (final AccessDeniedException policiesDenied) { + throw resourceDenied; + } + } + } + }; + } + + public static Set getRestrictedComponentsAuthorizable(final Class configurableComponentClass) { + final Set authorizables = new HashSet<>(); + + final Restricted restricted = configurableComponentClass.getAnnotation(Restricted.class); + + if (restricted != null) { + final Restriction[] restrictions = restricted.restrictions(); + + if (restrictions != null && restrictions.length > 0) { + Arrays.stream(restrictions).forEach(restriction -> authorizables.add(getRestrictedComponentsAuthorizable(restriction.requiredPermission()))); + } else { + authorizables.add(getRestrictedComponentsAuthorizable()); + } + } + + return authorizables; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/CurrentUserEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/CurrentUserEndpointMerger.java index 4a97d7a63d..03691c4bc7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/CurrentUserEndpointMerger.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/CurrentUserEndpointMerger.java @@ -19,6 +19,7 @@ package org.apache.nifi.cluster.coordination.http.endpoints; import org.apache.nifi.cluster.manager.NodeResponse; import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.web.api.dto.ComponentRestrictionPermissionDTO; import org.apache.nifi.web.api.dto.PermissionsDTO; import org.apache.nifi.web.api.entity.CurrentUserEntity; @@ -53,6 +54,23 @@ public class CurrentUserEndpointMerger extends AbstractSingleEntityEndpoint clientEntityComponentRestrictionsPermissions = clientEntity.getComponentRestrictionPermissions(); + final Set entityComponentRestrictionsPermissions = entity.getComponentRestrictionPermissions(); + + // only retain the component restriction permissions in common + clientEntityComponentRestrictionsPermissions.retainAll(entityComponentRestrictionsPermissions); + + // merge the component restriction permissions + clientEntityComponentRestrictionsPermissions.forEach(clientEntityPermission -> { + final ComponentRestrictionPermissionDTO entityPermission = entityComponentRestrictionsPermissions.stream().filter(entityComponentRestrictionsPermission -> { + return entityComponentRestrictionsPermission.getRequiredPermission().getId().equals(clientEntityPermission.getRequiredPermission().getId()); + }).findFirst().orElse(null); + + mergePermissions(clientEntityPermission.getPermissions(), entityPermission.getPermissions()); + }); } } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/endpoints/CurrentUserEndpointMergerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/endpoints/CurrentUserEndpointMergerTest.java new file mode 100644 index 0000000000..c1cfdf8a46 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/endpoints/CurrentUserEndpointMergerTest.java @@ -0,0 +1,126 @@ +/* + * 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.cluster.coordination.http.endpoints; + +import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.components.RequiredPermission; +import org.apache.nifi.web.api.dto.ComponentRestrictionPermissionDTO; +import org.apache.nifi.web.api.dto.PermissionsDTO; +import org.apache.nifi.web.api.dto.RequiredPermissionDTO; +import org.apache.nifi.web.api.entity.CurrentUserEntity; +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class CurrentUserEndpointMergerTest { + + @Test + public void testMergeUserPermissions() { + final NodeIdentifier nodeId1 = new NodeIdentifier("1", "localhost", 9000, "localhost", 9001, "localhost", 9002, 9003, false); + final CurrentUserEntity userNode1 = new CurrentUserEntity(); + userNode1.setControllerPermissions(buildPermissions(true, false)); + userNode1.setCountersPermissions(buildPermissions(true, true)); + userNode1.setPoliciesPermissions(buildPermissions(true, true)); + userNode1.setProvenancePermissions(buildPermissions(false, false)); + userNode1.setRestrictedComponentsPermissions(buildPermissions(false, false)); + userNode1.setSystemPermissions(buildPermissions(true, true)); + userNode1.setTenantsPermissions(buildPermissions(false, true)); + + final Set componentRestrictionsNode1 = new HashSet<>(); + componentRestrictionsNode1.add(buildComponentRestriction(RequiredPermission.ACCESS_KEYTAB, true, true)); + componentRestrictionsNode1.add(buildComponentRestriction(RequiredPermission.WRITE_FILESYSTEM, false, true)); + componentRestrictionsNode1.add(buildComponentRestriction(RequiredPermission.READ_FILESYSTEM, true, true)); + userNode1.setComponentRestrictionPermissions(componentRestrictionsNode1); + + final NodeIdentifier nodeId2 = new NodeIdentifier("2", "localhost", 8000, "localhost", 8001, "localhost", 8002, 8003, false); + final CurrentUserEntity userNode2 = new CurrentUserEntity(); + userNode2.setControllerPermissions(buildPermissions(false, true)); + userNode2.setCountersPermissions(buildPermissions(true, false)); + userNode2.setPoliciesPermissions(buildPermissions(true, true)); + userNode2.setProvenancePermissions(buildPermissions(false, false)); + userNode2.setRestrictedComponentsPermissions(buildPermissions(true, true)); + userNode2.setSystemPermissions(buildPermissions(false, false)); + userNode2.setTenantsPermissions(buildPermissions(true, true)); + + final Set componentRestrictionsNode2 = new HashSet<>(); + componentRestrictionsNode2.add(buildComponentRestriction(RequiredPermission.ACCESS_KEYTAB, true, false)); + componentRestrictionsNode2.add(buildComponentRestriction(RequiredPermission.WRITE_FILESYSTEM, true, false)); + componentRestrictionsNode2.add(buildComponentRestriction(RequiredPermission.EXECUTE_CODE, true, true)); + userNode2.setComponentRestrictionPermissions(componentRestrictionsNode2); + + final Map entityMap = new HashMap<>(); + entityMap.put(nodeId1, userNode1); + entityMap.put(nodeId2, userNode2); + + final CurrentUserEndpointMerger merger = new CurrentUserEndpointMerger(); + merger.mergeResponses(userNode1, entityMap, Collections.emptySet(), Collections.emptySet()); + + assertFalse(userNode1.getControllerPermissions().getCanRead()); + assertFalse(userNode1.getControllerPermissions().getCanWrite()); + assertTrue(userNode1.getCountersPermissions().getCanRead()); + assertFalse(userNode1.getCountersPermissions().getCanWrite()); + assertTrue(userNode1.getPoliciesPermissions().getCanRead()); + assertTrue(userNode1.getPoliciesPermissions().getCanWrite()); + assertFalse(userNode1.getProvenancePermissions().getCanRead()); + assertFalse(userNode1.getProvenancePermissions().getCanWrite()); + assertFalse(userNode1.getRestrictedComponentsPermissions().getCanRead()); + assertFalse(userNode1.getRestrictedComponentsPermissions().getCanWrite()); + assertFalse(userNode1.getSystemPermissions().getCanRead()); + assertFalse(userNode1.getSystemPermissions().getCanWrite()); + assertFalse(userNode1.getTenantsPermissions().getCanRead()); + assertTrue(userNode1.getTenantsPermissions().getCanWrite()); + + userNode1.getComponentRestrictionPermissions().forEach(componentRestriction -> { + if (RequiredPermission.ACCESS_KEYTAB.getPermissionIdentifier().equals(componentRestriction.getRequiredPermission().getId())) { + assertTrue(componentRestriction.getPermissions().getCanRead()); + assertFalse(componentRestriction.getPermissions().getCanWrite()); + } else if (RequiredPermission.WRITE_FILESYSTEM.getPermissionIdentifier().equals(componentRestriction.getRequiredPermission().getId())) { + assertFalse(componentRestriction.getPermissions().getCanRead()); + assertFalse(componentRestriction.getPermissions().getCanWrite()); + } else { + fail(); + } + }); + } + + private PermissionsDTO buildPermissions(final boolean canRead, final boolean canWrite) { + final PermissionsDTO permissionsDto = new PermissionsDTO(); + permissionsDto.setCanRead(canRead); + permissionsDto.setCanWrite(canWrite); + return permissionsDto; + } + + private ComponentRestrictionPermissionDTO buildComponentRestriction(final RequiredPermission requiredPermission, final boolean canRead, final boolean canWrite) { + final RequiredPermissionDTO requiredPermissionDto = new RequiredPermissionDTO(); + requiredPermissionDto.setId(requiredPermission.getPermissionIdentifier()); + requiredPermissionDto.setLabel(requiredPermission.getPermissionLabel()); + + final ComponentRestrictionPermissionDTO componentRestrictionPermissionDto = new ComponentRestrictionPermissionDTO(); + componentRestrictionPermissionDto.setRequiredPermission(requiredPermissionDto); + componentRestrictionPermissionDto.setPermissions(buildPermissions(canRead, canWrite)); + return componentRestrictionPermissionDto; + } +} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ConfiguredComponent.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ConfiguredComponent.java index b0f65a7a28..34bf575056 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ConfiguredComponent.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ConfiguredComponent.java @@ -21,8 +21,9 @@ 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.Authorizable; import org.apache.nifi.authorization.resource.ComponentAuthorizable; -import org.apache.nifi.authorization.resource.RestrictedComponentsAuthorizable; +import org.apache.nifi.authorization.resource.RestrictedComponentsAuthorizableFactory; import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.bundle.BundleCoordinate; import org.apache.nifi.components.ConfigurableComponent; @@ -90,13 +91,18 @@ public interface ConfiguredComponent extends ComponentAuthorizable { */ String getComponentType(); + /** + * @return the class of the underlying + */ + Class getComponentClass(); + /** * @return the Canonical Class Name of the component */ String getCanonicalClassName(); /** - * @return whether or not the underlying implementation is restricted + * @return whether or not the underlying implementation has any restrictions */ boolean isRestricted(); @@ -115,10 +121,13 @@ public interface ConfiguredComponent extends ComponentAuthorizable { // if this is a modification request and the reporting task is restricted ensure the user has elevated privileges. if this // is not a modification request, we just want to use the normal rules if (RequestAction.WRITE.equals(action) && isRestricted()) { - final RestrictedComponentsAuthorizable restrictedComponentsAuthorizable = new RestrictedComponentsAuthorizable(); - final AuthorizationResult result = restrictedComponentsAuthorizable.checkAuthorization(authorizer, RequestAction.WRITE, user, resourceContext); - if (Result.Denied.equals(result.getResult())) { - return result; + final Set restrictedComponentsAuthorizables = RestrictedComponentsAuthorizableFactory.getRestrictedComponentsAuthorizable(getComponentClass()); + + for (final Authorizable restrictedComponentsAuthorizable : restrictedComponentsAuthorizables) { + final AuthorizationResult result = restrictedComponentsAuthorizable.checkAuthorization(authorizer, RequestAction.WRITE, user, resourceContext); + if (Result.Denied.equals(result.getResult())) { + return result; + } } } @@ -131,8 +140,11 @@ public interface ConfiguredComponent extends ComponentAuthorizable { // if this is a modification request and the reporting task is restricted ensure the user has elevated privileges. if this // is not a modification request, we just want to use the normal rules if (RequestAction.WRITE.equals(action) && isRestricted()) { - final RestrictedComponentsAuthorizable restrictedComponentsAuthorizable = new RestrictedComponentsAuthorizable(); - restrictedComponentsAuthorizable.authorize(authorizer, RequestAction.WRITE, user, resourceContext); + final Set restrictedComponentsAuthorizables = RestrictedComponentsAuthorizableFactory.getRestrictedComponentsAuthorizable(getComponentClass()); + + for (final Authorizable restrictedComponentsAuthorizable : restrictedComponentsAuthorizables) { + restrictedComponentsAuthorizable.authorize(authorizer, RequestAction.WRITE, user, resourceContext); + } } // defer to the base authorization check diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java index 12f4b1e1ed..c6d62a26ea 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java @@ -244,6 +244,11 @@ public class StandardProcessorNode extends ProcessorNode implements Connectable return getProcessor().getClass().isAnnotationPresent(Restricted.class); } + @Override + public Class getComponentClass() { + return getProcessor().getClass(); + } + @Override public boolean isDeprecated() { return getProcessor().getClass().isAnnotationPresent(DeprecationNotice.class); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingTaskNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingTaskNode.java index 40bdf8bba8..dbe2f51fdb 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingTaskNode.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingTaskNode.java @@ -67,6 +67,11 @@ public class StandardReportingTaskNode extends AbstractReportingTaskNode impleme return getReportingTask().getClass().isAnnotationPresent(Restricted.class); } + @Override + public Class getComponentClass() { + return getReportingContext().getClass(); + } + @Override public boolean isDeprecated() { return getReportingTask().getClass().isAnnotationPresent(DeprecationNotice.class); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java index 53fd166a6c..b51faa8a7a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java @@ -150,6 +150,11 @@ public class StandardControllerServiceNode extends AbstractConfiguredComponent i return getControllerServiceImplementation().getClass().isAnnotationPresent(Restricted.class); } + @Override + public Class getComponentClass() { + return getControllerServiceImplementation().getClass(); + } + @Override public boolean isDeprecated() { return getControllerServiceImplementation().getClass().isAnnotationPresent(DeprecationNotice.class); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java index 95c5539321..3f956569f5 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java @@ -17,6 +17,7 @@ package org.apache.nifi.authorization; import org.apache.nifi.authorization.resource.Authorizable; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.web.api.dto.BundleDTO; import org.apache.nifi.web.api.dto.FlowSnippetDTO; @@ -263,4 +264,12 @@ public interface AuthorizableLookup { * @return authorizable */ Authorizable getRestrictedComponents(); + + /** + * Get the authorizable for accessing restricted components with a specific required permission. + * + * @param requiredPermission required permission + * @return authorizable + */ + Authorizable getRestrictedComponents(RequiredPermission requiredPermission); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/ComponentAuthorizable.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/ComponentAuthorizable.java index 1b7f8cd3e6..4e29b3005d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/ComponentAuthorizable.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/ComponentAuthorizable.java @@ -20,6 +20,7 @@ import org.apache.nifi.authorization.resource.Authorizable; import org.apache.nifi.components.PropertyDescriptor; import java.util.List; +import java.util.Set; /** * Authorizable for a component that references a ControllerService. @@ -39,6 +40,13 @@ public interface ComponentAuthorizable { */ boolean isRestricted(); + /** + * Returns all component restriction authorizables for this component. + * + * @return all component restriction authorizables + */ + Set getRestrictedAuthorizables(); + /** * Returns the property descriptor for the specified property. * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java index 8cad74079a..42b3a55cc4 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java @@ -24,12 +24,13 @@ import org.apache.nifi.authorization.resource.DataAuthorizable; import org.apache.nifi.authorization.resource.DataTransferAuthorizable; import org.apache.nifi.authorization.resource.ResourceFactory; import org.apache.nifi.authorization.resource.ResourceType; -import org.apache.nifi.authorization.resource.RestrictedComponentsAuthorizable; +import org.apache.nifi.authorization.resource.RestrictedComponentsAuthorizableFactory; import org.apache.nifi.authorization.resource.TenantAuthorizable; import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.bundle.BundleCoordinate; import org.apache.nifi.components.ConfigurableComponent; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.connectable.Connectable; import org.apache.nifi.connectable.Connection; import org.apache.nifi.connectable.Port; @@ -70,7 +71,6 @@ import java.util.stream.Collectors; class StandardAuthorizableLookup implements AuthorizableLookup { private static final TenantAuthorizable TENANT_AUTHORIZABLE = new TenantAuthorizable(); - private static final Authorizable RESTRICTED_COMPONENTS_AUTHORIZABLE = new RestrictedComponentsAuthorizable(); private static final Authorizable POLICIES_AUTHORIZABLE = new Authorizable() { @Override @@ -500,6 +500,20 @@ class StandardAuthorizableLookup implements AuthorizableLookup { } else { return new DataTransferAuthorizable(getAccessPolicy(resourceType, resource)); } + } else if (ResourceType.RestrictedComponents.equals(resourceType)) { + final String slashRequiredPermission = StringUtils.substringAfter(resource, resourceType.getValue()); + + if (slashRequiredPermission.startsWith("/")) { + final RequiredPermission requiredPermission = RequiredPermission.valueOfPermissionIdentifier(slashRequiredPermission.substring(1)); + + if (requiredPermission == null) { + throw new ResourceNotFoundException("Unrecognized resource: " + resource); + } + + return getRestrictedComponents(requiredPermission); + } else { + return getRestrictedComponents(); + } } else { return getAccessPolicy(resourceType, resource); } @@ -629,9 +643,6 @@ class StandardAuthorizableLookup implements AuthorizableLookup { case Tenant: authorizable = getTenant(); break; - case RestrictedComponents: - authorizable = getRestrictedComponents(); - break; } if (authorizable == null) { @@ -724,7 +735,12 @@ class StandardAuthorizableLookup implements AuthorizableLookup { @Override public Authorizable getRestrictedComponents() { - return RESTRICTED_COMPONENTS_AUTHORIZABLE; + return RestrictedComponentsAuthorizableFactory.getRestrictedComponentsAuthorizable(); + } + + @Override + public Authorizable getRestrictedComponents(final RequiredPermission requiredPermission) { + return RestrictedComponentsAuthorizableFactory.getRestrictedComponentsAuthorizable(requiredPermission); } @Override @@ -753,6 +769,11 @@ class StandardAuthorizableLookup implements AuthorizableLookup { return configurableComponent.getClass().isAnnotationPresent(Restricted.class); } + @Override + public Set getRestrictedAuthorizables() { + return RestrictedComponentsAuthorizableFactory.getRestrictedComponentsAuthorizable(configurableComponent.getClass()); + } + @Override public String getValue(PropertyDescriptor propertyDescriptor) { return null; @@ -794,6 +815,11 @@ class StandardAuthorizableLookup implements AuthorizableLookup { return processorNode.isRestricted(); } + @Override + public Set getRestrictedAuthorizables() { + return RestrictedComponentsAuthorizableFactory.getRestrictedComponentsAuthorizable(processorNode.getComponentClass()); + } + @Override public String getValue(PropertyDescriptor propertyDescriptor) { return processorNode.getProperty(propertyDescriptor); @@ -835,6 +861,11 @@ class StandardAuthorizableLookup implements AuthorizableLookup { return controllerServiceNode.isRestricted(); } + @Override + public Set getRestrictedAuthorizables() { + return RestrictedComponentsAuthorizableFactory.getRestrictedComponentsAuthorizable(controllerServiceNode.getComponentClass()); + } + @Override public String getValue(PropertyDescriptor propertyDescriptor) { return controllerServiceNode.getProperty(propertyDescriptor); @@ -876,6 +907,11 @@ class StandardAuthorizableLookup implements AuthorizableLookup { return reportingTaskNode.isRestricted(); } + @Override + public Set getRestrictedAuthorizables() { + return RestrictedComponentsAuthorizableFactory.getRestrictedComponentsAuthorizable(reportingTaskNode.getComponentClass()); + } + @Override public String getValue(PropertyDescriptor propertyDescriptor) { return reportingTaskNode.getProperty(propertyDescriptor); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java index f57f628cef..204455931b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java @@ -55,6 +55,7 @@ import org.apache.nifi.cluster.manager.exception.UnknownNodeException; import org.apache.nifi.cluster.protocol.NodeIdentifier; import org.apache.nifi.components.ConfigurableComponent; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.ValidationResult; import org.apache.nifi.components.Validator; import org.apache.nifi.components.state.Scope; @@ -136,6 +137,7 @@ import org.apache.nifi.web.api.dto.ComponentDTO; import org.apache.nifi.web.api.dto.ComponentDifferenceDTO; import org.apache.nifi.web.api.dto.ComponentHistoryDTO; import org.apache.nifi.web.api.dto.ComponentReferenceDTO; +import org.apache.nifi.web.api.dto.ComponentRestrictionPermissionDTO; import org.apache.nifi.web.api.dto.ComponentStateDTO; import org.apache.nifi.web.api.dto.ConnectionDTO; import org.apache.nifi.web.api.dto.ControllerConfigurationDTO; @@ -168,6 +170,7 @@ import org.apache.nifi.web.api.dto.RegistryDTO; import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO; import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO; import org.apache.nifi.web.api.dto.ReportingTaskDTO; +import org.apache.nifi.web.api.dto.RequiredPermissionDTO; import org.apache.nifi.web.api.dto.ResourceDTO; import org.apache.nifi.web.api.dto.RevisionDTO; import org.apache.nifi.web.api.dto.SnippetDTO; @@ -3506,9 +3509,26 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { entity.setControllerPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getController())); entity.setPoliciesPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getPolicies())); entity.setSystemPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getSystem())); - entity.setRestrictedComponentsPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getRestrictedComponents())); entity.setCanVersionFlows(CollectionUtils.isNotEmpty(flowRegistryClient.getRegistryIdentifiers())); + entity.setRestrictedComponentsPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getRestrictedComponents())); + + final Set componentRestrictionPermissions = new HashSet<>(); + Arrays.stream(RequiredPermission.values()).forEach(requiredPermission -> { + final PermissionsDTO restrictionPermissions = dtoFactory.createPermissionsDto(authorizableLookup.getRestrictedComponents(requiredPermission)); + + final RequiredPermissionDTO requiredPermissionDto = new RequiredPermissionDTO(); + requiredPermissionDto.setId(requiredPermission.getPermissionIdentifier()); + requiredPermissionDto.setLabel(requiredPermission.getPermissionLabel()); + + final ComponentRestrictionPermissionDTO componentRestrictionPermissionDto = new ComponentRestrictionPermissionDTO(); + componentRestrictionPermissionDto.setRequiredPermission(requiredPermissionDto); + componentRestrictionPermissionDto.setPermissions(restrictionPermissions); + + componentRestrictionPermissions.add(componentRestrictionPermissionDto); + }); + entity.setComponentRestrictionPermissions(componentRestrictionPermissions); + return entity; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java index 7e19d9821d..db0a568187 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java @@ -23,6 +23,7 @@ import org.apache.nifi.authorization.AuthorizableLookup; import org.apache.nifi.authorization.AuthorizeAccess; import org.apache.nifi.authorization.AuthorizeControllerServiceReference; import org.apache.nifi.authorization.Authorizer; +import org.apache.nifi.authorization.ComponentAuthorizable; import org.apache.nifi.authorization.ProcessGroupAuthorizable; import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.SnippetAuthorizable; @@ -447,6 +448,16 @@ public abstract class ApplicationResource { return getRevision(entity.getRevision(), componentId); } + /** + * Authorize any restrictions for the specified ComponentAuthorizable. + * + * @param authorizer authorizer + * @param authorizable component authorizable + */ + protected void authorizeRestrictions(final Authorizer authorizer, final ComponentAuthorizable authorizable) { + authorizable.getRestrictedAuthorizables().forEach(restrictionAuthorizable -> restrictionAuthorizable.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser())); + } + /** * Authorizes the specified process group. * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java index c5c40bed77..004b5d20aa 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java @@ -46,8 +46,8 @@ import org.apache.nifi.web.api.entity.ControllerServiceEntity; import org.apache.nifi.web.api.entity.Entity; import org.apache.nifi.web.api.entity.HistoryEntity; import org.apache.nifi.web.api.entity.NodeEntity; -import org.apache.nifi.web.api.entity.RegistryClientsEntity; import org.apache.nifi.web.api.entity.RegistryClientEntity; +import org.apache.nifi.web.api.entity.RegistryClientsEntity; import org.apache.nifi.web.api.entity.ReportingTaskEntity; import org.apache.nifi.web.api.request.ClientIdParameter; import org.apache.nifi.web.api.request.DateTimeParameter; @@ -277,7 +277,7 @@ public class ControllerResource extends ApplicationResource { authorizable = lookup.getConfigurableComponent(requestReportingTask.getType(), requestReportingTask.getBundle()); if (authorizable.isRestricted()) { - lookup.getRestrictedComponents().authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); + authorizeRestrictions(authorizer, authorizable); } if (requestReportingTask.getProperties() != null) { @@ -775,7 +775,7 @@ public class ControllerResource extends ApplicationResource { authorizable = lookup.getConfigurableComponent(requestControllerService.getType(), requestControllerService.getBundle()); if (authorizable.isRestricted()) { - lookup.getRestrictedComponents().authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); + authorizeRestrictions(authorizer, authorizable); } if (requestControllerService.getProperties() != null) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java index 7c932205b3..ce266da69e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java @@ -159,7 +159,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -1894,7 +1893,7 @@ public class ProcessGroupResource extends ApplicationResource { authorizable = lookup.getConfigurableComponent(requestProcessor.getType(), requestProcessor.getBundle()); if (authorizable.isRestricted()) { - lookup.getRestrictedComponents().authorize(authorizer, RequestAction.WRITE, user); + authorizeRestrictions(authorizer, authorizable); } final ProcessorConfigDTO config = requestProcessor.getConfig(); @@ -3037,11 +3036,9 @@ public class ProcessGroupResource extends ApplicationResource { final NiFiUser user = NiFiUserUtils.getNiFiUser(); final SnippetAuthorizable snippet = authorizeSnippetUsage(lookup, groupId, requestCopySnippetEntity.getSnippetId(), false); - // flag to only perform the restricted check once, atomic reference so we can mark final and use in lambda - final AtomicBoolean restrictedCheckPerformed = new AtomicBoolean(false); final Consumer authorizeRestricted = authorizable -> { - if (authorizable.isRestricted() && restrictedCheckPerformed.compareAndSet(false, true)) { - lookup.getRestrictedComponents().authorize(authorizer, RequestAction.WRITE, user); + if (authorizable.isRestricted()) { + authorizeRestrictions(authorizer, authorizable); } }; @@ -3213,11 +3210,9 @@ public class ProcessGroupResource extends ApplicationResource { // ensure read on the template final TemplateContentsAuthorizable templateContents = lookup.getTemplateContents(requestInstantiateTemplateRequestEntity.getSnippet()); - // flag to only perform the restricted check once, atomic reference so we can mark final and use in lambda - final AtomicBoolean restrictedCheckPerformed = new AtomicBoolean(false); final Consumer authorizeRestricted = authorizable -> { - if (authorizable.isRestricted() && restrictedCheckPerformed.compareAndSet(false, true)) { - lookup.getRestrictedComponents().authorize(authorizer, RequestAction.WRITE, user); + if (authorizable.isRestricted()) { + authorizeRestrictions(authorizer, authorizable); } }; @@ -3595,7 +3590,7 @@ public class ProcessGroupResource extends ApplicationResource { authorizable = lookup.getConfigurableComponent(requestControllerService.getType(), requestControllerService.getBundle()); if (authorizable.isRestricted()) { - lookup.getRestrictedComponents().authorize(authorizer, RequestAction.WRITE, user); + authorizeRestrictions(authorizer, authorizable); } if (requestControllerService.getProperties() != null) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java index 359b5241b7..4306d0debc 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java @@ -34,6 +34,7 @@ import org.apache.nifi.action.details.FlowChangePurgeDetails; import org.apache.nifi.action.details.MoveDetails; import org.apache.nifi.action.details.PurgeDetails; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.Stateful; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.DeprecationNotice; @@ -2408,9 +2409,47 @@ public final class DtoFactory { return dto; } + private boolean isRestricted(final Class cls) { + return cls.isAnnotationPresent(Restricted.class); + } + private String getUsageRestriction(final Class cls) { - final Restricted restriction = cls.getAnnotation(Restricted.class); - return restriction == null ? null : restriction.value(); + final Restricted restricted = cls.getAnnotation(Restricted.class); + + if (restricted == null) { + return null; + } + + if (StringUtils.isBlank(restricted.value())) { + return null; + } + + return restricted.value(); + } + + private Set getExplicitRestrictions(final Class cls) { + final Restricted restricted = cls.getAnnotation(Restricted.class); + + if (restricted == null) { + return null; + } + + final Restriction[] restrictions = restricted.restrictions(); + + if (restrictions == null || restrictions.length == 0) { + return null; + } + + return Arrays.stream(restrictions).map(restriction -> { + final RequiredPermissionDTO requiredPermission = new RequiredPermissionDTO(); + requiredPermission.setId(restriction.requiredPermission().getPermissionIdentifier()); + requiredPermission.setLabel(restriction.requiredPermission().getPermissionLabel()); + + final ExplicitRestrictionDTO usageRestriction = new ExplicitRestrictionDTO(); + usageRestriction.setRequiredPermission(requiredPermission); + usageRestriction.setExplanation(restriction.explanation()); + return usageRestriction; + }).collect(Collectors.toSet()); } private String getDeprecationReason(final Class cls) { @@ -2649,7 +2688,9 @@ public final class DtoFactory { dto.setBundle(createBundleDto(coordinate)); dto.setControllerServiceApis(createControllerServiceApiDto(cls)); dto.setDescription(getCapabilityDescription(cls)); + dto.setRestricted(isRestricted(cls)); dto.setUsageRestriction(getUsageRestriction(cls)); + dto.setExplicitRestrictions(getExplicitRestrictions(cls)); dto.setDeprecationReason(getDeprecationReason(cls)); dto.setTags(getTags(cls)); types.add(dto); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java index 72702a6c49..1463468d43 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java @@ -33,6 +33,7 @@ import org.apache.nifi.bundle.Bundle; import org.apache.nifi.bundle.BundleCoordinate; import org.apache.nifi.cluster.protocol.NodeIdentifier; import org.apache.nifi.components.ConfigurableComponent; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.connectable.Connectable; import org.apache.nifi.connectable.Connection; import org.apache.nifi.connectable.Port; @@ -110,6 +111,7 @@ import java.io.IOException; import java.io.InputStream; import java.text.Collator; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -808,7 +810,6 @@ public class ControllerFacade implements Authorizable { final List resources = new ArrayList<>(); resources.add(ResourceFactory.getFlowResource()); resources.add(ResourceFactory.getSystemResource()); - resources.add(ResourceFactory.getRestrictedComponentsResource()); resources.add(ResourceFactory.getControllerResource()); resources.add(ResourceFactory.getCountersResource()); resources.add(ResourceFactory.getProvenanceResource()); @@ -818,6 +819,10 @@ public class ControllerFacade implements Authorizable { resources.add(ResourceFactory.getResourceResource()); resources.add(ResourceFactory.getSiteToSiteResource()); + // restricted components + resources.add(ResourceFactory.getRestrictedComponentsResource()); + Arrays.stream(RequiredPermission.values()).forEach(requiredPermission -> resources.add(ResourceFactory.getRestrictedComponentsResource(requiredPermission))); + final ProcessGroup root = flowController.getGroup(flowController.getRootGroupId()); // include the root group diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java index a7293b25b1..279da21f4d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java @@ -46,6 +46,7 @@ public class AccessControlHelper { private NiFiTestUser readWriteUser; private NiFiTestUser noneUser; private NiFiTestUser privilegedUser; + private NiFiTestUser executeCodeUser; private static final String CONTEXT_PATH = "/nifi-api"; @@ -83,6 +84,7 @@ public class AccessControlHelper { readWriteUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.READ_WRITE_USER_DN); noneUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.NONE_USER_DN); privilegedUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.PRIVILEGED_USER_DN); + executeCodeUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.EXECUTED_CODE_USER_DN); // populate the initial data flow NiFiWebApiTest.populateFlow(server.getClient(), baseUrl, readWriteUser, READ_WRITE_CLIENT_ID); @@ -108,6 +110,10 @@ public class AccessControlHelper { return privilegedUser; } + public NiFiTestUser getExecuteCodeUser() { + return executeCodeUser; + } + public void testGenericGetUri(final String uri) throws Exception { Response response; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITProcessorAccessControl.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITProcessorAccessControl.java index 2b7093039a..a42d16992a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITProcessorAccessControl.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITProcessorAccessControl.java @@ -16,6 +16,7 @@ */ package org.apache.nifi.integration.accesscontrol; +import org.apache.nifi.integration.util.ExecuteCodeRestrictedProcessor; import org.apache.nifi.integration.util.NiFiTestAuthorizer; import org.apache.nifi.integration.util.NiFiTestUser; import org.apache.nifi.integration.util.RestrictedProcessor; @@ -439,6 +440,12 @@ public class ITProcessorAccessControl { // ensure the request is successful assertEquals(403, response.getStatus()); + // perform the request as a user with read/write and only execute code restricted access + response = helper.getExecuteCodeUser().testPost(url, entity); + + // ensure the request is successful + assertEquals(403, response.getStatus()); + // perform the request as a user with read/write and restricted access response = helper.getPrivilegedUser().testPost(url, entity); @@ -448,7 +455,54 @@ public class ITProcessorAccessControl { final ProcessorEntity responseEntity = response.readEntity(ProcessorEntity.class); // remove the restricted component - deleteRestrictedComponent(responseEntity); + deleteRestrictedComponent(responseEntity, helper.getPrivilegedUser()); + } + + /** + * Tests attempt to create a restricted processor requiring execute code permissions. + * + * @throws Exception if there is an error creating this processor + */ + @Test + public void testCreateExecuteCodeRestrictedProcessor() throws Exception { + createExecuteCodeRestrictedProcessor(helper.getPrivilegedUser()); + createExecuteCodeRestrictedProcessor(helper.getExecuteCodeUser()); + } + + private void createExecuteCodeRestrictedProcessor(final NiFiTestUser user) throws Exception { + String url = helper.getBaseUrl() + "/process-groups/root/processors"; + + // create the processor + ProcessorDTO processor = new ProcessorDTO(); + processor.setName("execute code restricted"); + processor.setType(ExecuteCodeRestrictedProcessor.class.getName()); + + // create the revision + final RevisionDTO revision = new RevisionDTO(); + revision.setClientId(READ_WRITE_CLIENT_ID); + revision.setVersion(0L); + + // create the entity body + ProcessorEntity entity = new ProcessorEntity(); + entity.setRevision(revision); + entity.setComponent(processor); + + // perform the request as a user with read/write but no restricted access + Response response = helper.getReadWriteUser().testPost(url, entity); + + // ensure the request is successful + assertEquals(403, response.getStatus()); + + // perform the request as a user with read/write and restricted access + response = user.testPost(url, entity); + + // ensure the request is successful + assertEquals(201, response.getStatus()); + + final ProcessorEntity responseEntity = response.readEntity(ProcessorEntity.class); + + // remove the restricted component + deleteRestrictedComponent(responseEntity, user); } /** @@ -459,7 +513,7 @@ public class ITProcessorAccessControl { @Test public void testCopyPasteRestrictedProcessor() throws Exception { final String copyUrl = helper.getBaseUrl() + "/process-groups/root/snippet-instance"; - final Tuple tuple = createSnippetWithRestrictedComponent(); + final Tuple tuple = createSnippetWithRestrictedComponent(RestrictedProcessor.class.getName(), helper.getPrivilegedUser()); final SnippetEntity snippetEntity = tuple.getValue(); // build the copy/paste request @@ -474,6 +528,12 @@ public class ITProcessorAccessControl { // ensure the request failed... need privileged users since snippet comprised of the restricted components assertEquals(403, response.getStatus()); + // perform the request as a user with read/write and only execute code restricted access + response = helper.getExecuteCodeUser().testPost(copyUrl, copyRequest); + + // ensure the request is successful + assertEquals(403, response.getStatus()); + // create the snippet response = helper.getPrivilegedUser().testPost(copyUrl, copyRequest); @@ -483,8 +543,49 @@ public class ITProcessorAccessControl { final FlowEntity flowEntity = response.readEntity(FlowEntity.class); // remove the restricted processors - deleteRestrictedComponent(tuple.getKey()); - deleteRestrictedComponent(flowEntity.getFlow().getProcessors().stream().findFirst().orElse(null)); + deleteRestrictedComponent(tuple.getKey(), helper.getPrivilegedUser()); + deleteRestrictedComponent(flowEntity.getFlow().getProcessors().stream().findFirst().orElse(null), helper.getPrivilegedUser()); + } + + /** + * Tests attempting to copy/paste a restricted processor requiring execute code permissions. + * + * @throws Exception ex + */ + @Test + public void testCopyPasteExecuteCodeRestrictedProcessor() throws Exception { + copyPasteExecuteCodeRestrictedProcessor(helper.getPrivilegedUser()); + copyPasteExecuteCodeRestrictedProcessor(helper.getExecuteCodeUser()); + } + + private void copyPasteExecuteCodeRestrictedProcessor(final NiFiTestUser user) throws Exception { + final String copyUrl = helper.getBaseUrl() + "/process-groups/root/snippet-instance"; + final Tuple tuple = createSnippetWithRestrictedComponent(ExecuteCodeRestrictedProcessor.class.getName(), user); + final SnippetEntity snippetEntity = tuple.getValue(); + + // build the copy/paste request + final CopySnippetRequestEntity copyRequest = new CopySnippetRequestEntity(); + copyRequest.setSnippetId(snippetEntity.getSnippet().getId()); + copyRequest.setOriginX(0.0); + copyRequest.setOriginY(0.0); + + // create the snippet + Response response = helper.getReadWriteUser().testPost(copyUrl, copyRequest); + + // ensure the request failed... need privileged users since snippet comprised of the restricted components + assertEquals(403, response.getStatus()); + + // perform the request as a user with read/write and only execute code restricted access + response = user.testPost(copyUrl, copyRequest); + + // ensure the request is successful + assertEquals(201, response.getStatus()); + + final FlowEntity flowEntity = response.readEntity(FlowEntity.class); + + // remove the restricted processors + deleteRestrictedComponent(tuple.getKey(), user); + deleteRestrictedComponent(flowEntity.getFlow().getProcessors().stream().findFirst().orElse(null), user); } /** @@ -496,7 +597,7 @@ public class ITProcessorAccessControl { public void testTemplateWithRestrictedProcessor() throws Exception { final String createTemplateUrl = helper.getBaseUrl() + "/process-groups/root/templates"; final String instantiateTemplateUrl = helper.getBaseUrl() + "/process-groups/root/template-instance"; - final Tuple tuple = createSnippetWithRestrictedComponent(); + final Tuple tuple = createSnippetWithRestrictedComponent(RestrictedProcessor.class.getName(), helper.getPrivilegedUser()); final SnippetEntity snippetEntity = tuple.getValue(); // create the template @@ -512,7 +613,7 @@ public class ITProcessorAccessControl { response = helper.getReadWriteUser().testPost(createTemplateUrl, createTemplateRequest); - // ensure the request is successfull + // ensure the request is successful assertEquals(201, response.getStatus()); final TemplateEntity templateEntity = response.readEntity(TemplateEntity.class); @@ -529,6 +630,12 @@ public class ITProcessorAccessControl { // ensure the request failed... need privileged user since the template is comprised of restricted components assertEquals(403, response.getStatus()); + // create the snippet + response = helper.getExecuteCodeUser().testPost(instantiateTemplateUrl, instantiateTemplateRequest); + + // ensure the request failed... need privileged user since the template is comprised of restricted components + assertEquals(403, response.getStatus()); + // create the snippet response = helper.getPrivilegedUser().testPost(instantiateTemplateUrl, instantiateTemplateRequest); @@ -539,18 +646,79 @@ public class ITProcessorAccessControl { // clean up the resources created during this test deleteTemplate(templateEntity); - deleteRestrictedComponent(tuple.getKey()); - deleteRestrictedComponent(flowEntity.getFlow().getProcessors().stream().findFirst().orElse(null)); + deleteRestrictedComponent(tuple.getKey(), helper.getPrivilegedUser()); + deleteRestrictedComponent(flowEntity.getFlow().getProcessors().stream().findFirst().orElse(null), helper.getPrivilegedUser()); } - private Tuple createSnippetWithRestrictedComponent() throws Exception { + /** + * Tests attempting to use a template with a restricted processor requiring execute code permissions. + * + * @throws Exception ex + */ + @Test + public void testTemplateWithExecuteCodeRestrictedProcessor() throws Exception { + templateWithExecuteCodeRestrictedProcessor(helper.getPrivilegedUser()); + templateWithExecuteCodeRestrictedProcessor(helper.getExecuteCodeUser()); + } + + private void templateWithExecuteCodeRestrictedProcessor(final NiFiTestUser user) throws Exception { + final String createTemplateUrl = helper.getBaseUrl() + "/process-groups/root/templates"; + final String instantiateTemplateUrl = helper.getBaseUrl() + "/process-groups/root/template-instance"; + final Tuple tuple = createSnippetWithRestrictedComponent(ExecuteCodeRestrictedProcessor.class.getName(), helper.getPrivilegedUser()); + final SnippetEntity snippetEntity = tuple.getValue(); + + // create the template + final CreateTemplateRequestEntity createTemplateRequest = new CreateTemplateRequestEntity(); + createTemplateRequest.setSnippetId(snippetEntity.getSnippet().getId()); + createTemplateRequest.setName("test"); + + // create the snippet + Response response = helper.getWriteUser().testPost(createTemplateUrl, createTemplateRequest); + + // ensure the request failed... need read perms to the components in the snippet + assertEquals(403, response.getStatus()); + + response = helper.getReadWriteUser().testPost(createTemplateUrl, createTemplateRequest); + + // ensure the request is successful + assertEquals(201, response.getStatus()); + + final TemplateEntity templateEntity = response.readEntity(TemplateEntity.class); + + // build the template request + final InstantiateTemplateRequestEntity instantiateTemplateRequest = new InstantiateTemplateRequestEntity(); + instantiateTemplateRequest.setTemplateId(templateEntity.getTemplate().getId()); + instantiateTemplateRequest.setOriginX(0.0); + instantiateTemplateRequest.setOriginY(0.0); + + // create the snippet + response = helper.getReadWriteUser().testPost(instantiateTemplateUrl, instantiateTemplateRequest); + + // ensure the request failed... need privileged user since the template is comprised of restricted components + assertEquals(403, response.getStatus()); + + // create the snippet + response = user.testPost(instantiateTemplateUrl, instantiateTemplateRequest); + + // ensure the request is successful + assertEquals(201, response.getStatus()); + + final FlowEntity flowEntity = response.readEntity(FlowEntity.class); + + // clean up the resources created during this test + deleteTemplate(templateEntity); + deleteRestrictedComponent(tuple.getKey(), user); + deleteRestrictedComponent(flowEntity.getFlow().getProcessors().stream().findFirst().orElse(null), user); + } + + private Tuple createSnippetWithRestrictedComponent(final String restrictedClassName, final NiFiTestUser user) throws Exception { final String processorUrl = helper.getBaseUrl() + "/process-groups/root/processors"; final String snippetUrl = helper.getBaseUrl() + "/snippets"; // create the processor ProcessorDTO processor = new ProcessorDTO(); processor.setName("restricted"); - processor.setType(RestrictedProcessor.class.getName()); + processor.setType(restrictedClassName); // create the revision final RevisionDTO revision = new RevisionDTO(); @@ -563,7 +731,7 @@ public class ITProcessorAccessControl { entity.setComponent(processor); // perform the request as a user with read/write and restricted access - Response response = helper.getPrivilegedUser().testPost(processorUrl, entity); + Response response = user.testPost(processorUrl, entity); // ensure the request is successful assertEquals(201, response.getStatus()); @@ -604,7 +772,7 @@ public class ITProcessorAccessControl { assertEquals(200, response.getStatus()); } - private void deleteRestrictedComponent(final ProcessorEntity entity) throws Exception { + private void deleteRestrictedComponent(final ProcessorEntity entity, final NiFiTestUser user) throws Exception { if (entity == null) { Assert.fail("Failed to get Processor from template or snippet request."); return; @@ -621,7 +789,7 @@ public class ITProcessorAccessControl { // ensure the request fails... needs access to restricted components assertEquals(403, response.getStatus()); - response = helper.getPrivilegedUser().testDelete(entity.getUri(), queryParams); + response = user.testDelete(entity.getUri(), queryParams); // ensure the request is successful assertEquals(200, response.getStatus()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/ExecuteCodeRestrictedProcessor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/ExecuteCodeRestrictedProcessor.java new file mode 100644 index 0000000000..c1e951d802 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/ExecuteCodeRestrictedProcessor.java @@ -0,0 +1,59 @@ +/* + * 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.integration.util; + +import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; +import org.apache.nifi.components.RequiredPermission; +import org.apache.nifi.processor.AbstractSessionFactoryProcessor; +import org.apache.nifi.processor.ProcessContext; +import org.apache.nifi.processor.ProcessSessionFactory; +import org.apache.nifi.processor.ProcessorInitializationContext; +import org.apache.nifi.processor.Relationship; +import org.apache.nifi.processor.exception.ProcessException; + +import java.util.HashSet; +import java.util.Set; + +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.EXECUTE_CODE, + explanation = "specifically executes code") + } +) +public class ExecuteCodeRestrictedProcessor extends AbstractSessionFactoryProcessor { + + public ExecuteCodeRestrictedProcessor() { + } + + @Override + public Set getRelationships() { + final Set rels = new HashSet<>(); + rels.add(new Relationship.Builder().name("success").build()); + return rels; + } + + @Override + public void onTrigger(final ProcessContext context, final ProcessSessionFactory sessionFactory) throws ProcessException { + } + + @Override + protected void init(final ProcessorInitializationContext context) { + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java index c72d51291b..dba618e1aa 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java @@ -25,6 +25,7 @@ import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.exception.AuthorizationAccessException; import org.apache.nifi.authorization.exception.AuthorizerCreationException; import org.apache.nifi.authorization.resource.ResourceFactory; +import org.apache.nifi.components.RequiredPermission; /** * Contains extra rules to convenience when in component based access control tests. @@ -40,6 +41,7 @@ public class NiFiTestAuthorizer implements Authorizer { public static final String WRITE_USER_DN = "write@nifi"; public static final String READ_WRITE_USER_DN = "readwrite@nifi"; public static final String PRIVILEGED_USER_DN = "privileged@nifi"; + public static final String EXECUTED_CODE_USER_DN = "executecode@nifi"; public static final String TOKEN_USER = "user@nifi"; @@ -88,15 +90,28 @@ public class NiFiTestAuthorizer implements Authorizer { } } + // execute code access + if (ResourceFactory.getRestrictedComponentsResource(RequiredPermission.EXECUTE_CODE).getIdentifier().equals(request.getResource().getIdentifier())) { + if (EXECUTED_CODE_USER_DN.equals(request.getIdentity())) { + return AuthorizationResult.approved(); + } else { + return AuthorizationResult.denied(); + } + } + // read access - if (READ_USER_DN.equals(request.getIdentity()) || READ_WRITE_USER_DN.equals(request.getIdentity()) || PRIVILEGED_USER_DN.equals(request.getIdentity())) { + if (READ_USER_DN.equals(request.getIdentity()) || READ_WRITE_USER_DN.equals(request.getIdentity()) || + PRIVILEGED_USER_DN.equals(request.getIdentity()) || EXECUTED_CODE_USER_DN.equals(request.getIdentity())) { + if (RequestAction.READ.equals(request.getAction())) { return AuthorizationResult.approved(); } } // write access - if (WRITE_USER_DN.equals(request.getIdentity()) || READ_WRITE_USER_DN.equals(request.getIdentity()) || PRIVILEGED_USER_DN.equals(request.getIdentity())) { + if (WRITE_USER_DN.equals(request.getIdentity()) || READ_WRITE_USER_DN.equals(request.getIdentity()) || + PRIVILEGED_USER_DN.equals(request.getIdentity()) || EXECUTED_CODE_USER_DN.equals(request.getIdentity())) { + if (RequestAction.WRITE.equals(request.getAction())) { return AuthorizationResult.approved(); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/RestrictedProcessor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/RestrictedProcessor.java index 4da4b5a3f7..2b793f6cfd 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/RestrictedProcessor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/RestrictedProcessor.java @@ -27,7 +27,9 @@ import org.apache.nifi.processor.exception.ProcessException; import java.util.HashSet; import java.util.Set; -@Restricted("") +@Restricted( + value = "generally restricted" +) public class RestrictedProcessor extends AbstractSessionFactoryProcessor { public RestrictedProcessor() { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor index 366dc4ff6f..e6ab19142b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor @@ -14,4 +14,5 @@ # limitations under the License. org.apache.nifi.integration.util.SourceTestProcessor org.apache.nifi.integration.util.TerminationTestProcessor -org.apache.nifi.integration.util.RestrictedProcessor \ No newline at end of file +org.apache.nifi.integration.util.RestrictedProcessor +org.apache.nifi.integration.util.ExecuteCodeRestrictedProcessor \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp index 7cd08e0825..946c53dd75 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp @@ -30,6 +30,7 @@
+
\ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css index 667291da0c..90777fad10 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css @@ -89,7 +89,7 @@ div.policy-controls { float: left; } -#policy-type-list { +#policy-type-list, #restricted-component-required-permissions { float: left; width: 225px; margin-right: 3px; @@ -275,7 +275,7 @@ div.remove-allowed-entity { admin policy message */ -#admin-policy-message { +#admin-policy-message, #restriction-message { float: right; margin-top: 8px; color: #775351; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-processor-component.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-processor-component.js index 55adc4d7e2..c111dcfc88 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-processor-component.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-processor-component.js @@ -288,7 +288,7 @@ * @param item process type */ var isSelectable = function (item) { - return nfCommon.isBlank(item.usageRestriction) || nfCommon.canAccessRestrictedComponents(); + return item.restricted === false || nfCommon.canAccessComponentRestrictions(item.explicitRestrictions); }; function ProcessorComponent() { @@ -436,9 +436,28 @@ var item = processorTypesData.getItemById(rowId); // show the tooltip - if (nfCommon.isDefinedAndNotNull(item.usageRestriction)) { + if (item.restricted === true) { + var restrictionTip = $('
'); + + if (nfCommon.isBlank(item.usageRestriction)) { + restrictionTip.append($('

').text('Requires the following permissions:')); + } else { + restrictionTip.append($('

').text(item.usageRestriction + ' Requires the following permissions:')); + } + + var restrictions = []; + if (nfCommon.isDefinedAndNotNull(item.explicitRestrictions)) { + $.each(item.explicitRestrictions, function (_, explicitRestriction) { + var requiredPermission = explicitRestriction.requiredPermission; + restrictions.push("'" + requiredPermission.label + "' - " + nfCommon.escapeHtml(explicitRestriction.explanation)); + }); + } else { + restrictions.push('Access to restricted components regardless of restrictions.'); + } + restrictionTip.append(nfCommon.formatUnorderedList(restrictions)); + usageRestriction.qtip($.extend({}, nfCommon.config.tooltipConfig, { - content: item.usageRestriction, + content: restrictionTip, position: { container: $('#summary'), at: 'bottom right', @@ -453,6 +472,8 @@ } }); + var generalRestriction = nfCommon.getPolicyTypeListing('restricted-components'); + // load the available processor types, this select is shown in the // new processor dialog when a processor is dragged onto the screen $.ajax({ @@ -462,6 +483,8 @@ }).done(function (response) { var tags = []; var groups = d3.set(); + var restrictedUsage = d3.map(); + var requiredPermissions = d3.map(); // begin the update processorTypesData.beginUpdate(); @@ -470,6 +493,46 @@ $.each(response.processorTypes, function (i, documentedType) { var type = documentedType.type; + if (documentedType.restricted === true) { + if (nfCommon.isDefinedAndNotNull(documentedType.explicitRestrictions)) { + $.each(documentedType.explicitRestrictions, function (_, explicitRestriction) { + var requiredPermission = explicitRestriction.requiredPermission; + + // update required permissions + if (!requiredPermissions.has(requiredPermission.id)) { + requiredPermissions.set(requiredPermission.id, requiredPermission.label); + } + + // update component restrictions + if (!restrictedUsage.has(requiredPermission.id)) { + restrictedUsage.set(requiredPermission.id, []); + } + + restrictedUsage.get(requiredPermission.id).push({ + type: nfCommon.formatType(documentedType), + bundle: nfCommon.formatBundle(documentedType.bundle), + explanation: nfCommon.escapeHtml(explicitRestriction.explanation) + }) + }); + } else { + // update required permissions + if (!requiredPermissions.has(generalRestriction.value)) { + requiredPermissions.set(generalRestriction.value, generalRestriction.text); + } + + // update component restrictions + if (!restrictedUsage.has(generalRestriction.value)) { + restrictedUsage.set(generalRestriction.value, []); + } + + restrictedUsage.get(generalRestriction.value).push({ + type: nfCommon.formatType(documentedType), + bundle: nfCommon.formatBundle(documentedType.bundle), + explanation: nfCommon.escapeHtml(documentedType.usageRestriction) + }); + } + } + // record the group groups.add(documentedType.bundle.group); @@ -480,7 +543,9 @@ type: type, bundle: documentedType.bundle, description: nfCommon.escapeHtml(documentedType.description), + restricted: documentedType.restricted, usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction), + explicitRestrictions: documentedType.explicitRestrictions, tags: documentedType.tags.join(', ') }); @@ -497,6 +562,9 @@ processorTypesData.reSort(); processorTypesGrid.invalidate(); + // set the component restrictions and the corresponding required permissions + nfCanvasUtils.addComponentRestrictions(restrictedUsage, requiredPermissions); + // set the total number of processors $('#total-processor-types, #displayed-processor-types').text(response.processorTypes.length); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js index 9347bd4995..7a8cf653d1 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js @@ -56,6 +56,9 @@ var nfBirdseye; var nfGraph; + var restrictedUsage = d3.map(); + var requiredPermissions = d3.map(); + var config = { storage: { namePrefix: 'nifi-view-' @@ -1842,6 +1845,41 @@ return nfCanvas.isConfigurableUsersAndGroups(); }, + /** + * Adds the restricted usage and the required permissions. + * + * @param additionalRestrictedUsages + * @param additionalRequiredPermissions + */ + addComponentRestrictions: function (additionalRestrictedUsages, additionalRequiredPermissions) { + additionalRestrictedUsages.each(function (componentRestrictions, requiredPermissionId) { + if (!restrictedUsage.has(requiredPermissionId)) { + restrictedUsage.set(requiredPermissionId, []); + } + + componentRestrictions.forEach(function (componentRestriction) { + restrictedUsage.get(requiredPermissionId).push(componentRestriction); + }); + }); + additionalRequiredPermissions.each(function (requiredPermissionLabel, requiredPermissionId) { + if (!requiredPermissions.has(requiredPermissionId)) { + requiredPermissions.set(requiredPermissionId, requiredPermissionLabel); + } + }); + }, + + /** + * Gets the component restrictions and the require permissions. + * + * @returns {{restrictedUsage: map, requiredPermissions: map}} component restrictions + */ + getComponentRestrictions: function () { + return { + restrictedUsage: restrictedUsage, + requiredPermissions: requiredPermissions + }; + }, + /** * Set the group id. * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js index 0a604a9b0d..9797a47d3e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js @@ -106,7 +106,7 @@ * @param item controller service type */ var isSelectable = function (item) { - return nfCommon.isBlank(item.usageRestriction) || nfCommon.canAccessRestrictedComponents(); + return item.restricted === false || nfCommon.canAccessComponentRestrictions(item.explicitRestrictions); }; /** @@ -462,9 +462,28 @@ var item = controllerServiceTypesData.getItemById(rowId); // show the tooltip - if (nfCommon.isDefinedAndNotNull(item.usageRestriction)) { + if (item.restricted === true) { + var restrictionTip = $('
'); + + if (nfCommon.isBlank(item.usageRestriction)) { + restrictionTip.append($('

').text('Requires the following permissions:')); + } else { + restrictionTip.append($('

').text(item.usageRestriction + ' Requires the following permissions:')); + } + + var restrictions = []; + if (nfCommon.isDefinedAndNotNull(item.explicitRestrictions)) { + $.each(item.explicitRestrictions, function (_, explicitRestriction) { + var requiredPermission = explicitRestriction.requiredPermission; + restrictions.push("'" + requiredPermission.label + "' - " + nfCommon.escapeHtml(explicitRestriction.explanation)); + }); + } else { + restrictions.push('Access to restricted components regardless of restrictions.'); + } + restrictionTip.append(nfCommon.formatUnorderedList(restrictions)); + usageRestriction.qtip($.extend({}, nfCommon.config.tooltipConfig, { - content: item.usageRestriction, + content: restrictionTip, position: { container: $('#summary'), at: 'bottom right', @@ -508,6 +527,8 @@ } }); + var generalRestriction = nfCommon.getPolicyTypeListing('restricted-components'); + // load the available controller services $.ajax({ type: 'GET', @@ -517,12 +538,54 @@ var id = 0; var tags = []; var groups = d3.set(); + var restrictedUsage = d3.map(); + var requiredPermissions = d3.map(); // begin the update controllerServiceTypesData.beginUpdate(); // go through each controller service type $.each(response.controllerServiceTypes, function (i, documentedType) { + if (documentedType.restricted === true) { + if (nfCommon.isDefinedAndNotNull(documentedType.explicitRestrictions)) { + $.each(documentedType.explicitRestrictions, function (_, explicitRestriction) { + var requiredPermission = explicitRestriction.requiredPermission; + + // update required permissions + if (!requiredPermissions.has(requiredPermission.id)) { + requiredPermissions.set(requiredPermission.id, requiredPermission.label); + } + + // update component restrictions + if (!restrictedUsage.has(requiredPermission.id)) { + restrictedUsage.set(requiredPermission.id, []); + } + + restrictedUsage.get(requiredPermission.id).push({ + type: nfCommon.formatType(documentedType), + bundle: nfCommon.formatBundle(documentedType.bundle), + explanation: explicitRestriction.explanation + }) + }); + } else { + // update required permissions + if (!requiredPermissions.has(generalRestriction.value)) { + requiredPermissions.set(generalRestriction.value, generalRestriction.text); + } + + // update component restrictions + if (!restrictedUsage.has(generalRestriction.value)) { + restrictedUsage.set(generalRestriction.value, []); + } + + restrictedUsage.get(generalRestriction.value).push({ + type: nfCommon.formatType(documentedType), + bundle: nfCommon.formatBundle(documentedType.bundle), + explanation: documentedType.usageRestriction + }); + } + } + // record the group groups.add(documentedType.bundle.group); @@ -534,7 +597,9 @@ bundle: documentedType.bundle, controllerServiceApis: documentedType.controllerServiceApis, description: nfCommon.escapeHtml(documentedType.description), + restricted: documentedType.restricted, usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction), + explicitRestrictions: documentedType.explicitRestrictions, tags: documentedType.tags.join(', ') }); @@ -551,6 +616,9 @@ controllerServiceTypesData.reSort(); controllerServiceTypesGrid.invalidate(); + // set the component restrictions and the corresponding required permissions + nfCanvasUtils.addComponentRestrictions(restrictedUsage, requiredPermissions); + // set the total number of processors $('#total-controller-service-types, #displayed-controller-service-types').text(response.controllerServiceTypes.length); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js index 99830e4d94..c0da4815f2 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js @@ -64,6 +64,7 @@ }; var initialized = false; + var initializedComponentRestrictions = false; var initAddTenantToPolicyDialog = function () { $('#new-policy-user-button').on('click', function () { @@ -418,7 +419,10 @@ // if the option is for a specific component if (globalPolicySupportsReadWrite(option.value)) { - // update the policy target and let it relaod the policy + $('#restricted-component-required-permissions').hide(); + $('#restriction-message').hide(); + + // update the policy target and let it reload the policy $('#controller-policy-target').combo('setSelectedOption', { 'value': 'read' }).show(); @@ -432,6 +436,59 @@ $('#selected-policy-action').text('read'); } + // handle any granular restrictions + if (option.value === 'restricted-components') { + if (!initializedComponentRestrictions) { + var regardlessOfRestrictions = 'regardless of restrictions'; + var componentRestrictions = nfCanvasUtils.getComponentRestrictions(); + var requiredPermissions = componentRestrictions.requiredPermissions; + + var options = [{ + text: regardlessOfRestrictions, + value: '', + description: 'Allows users to create/modify all restricted components regardless of restrictions.' + }]; + + requiredPermissions.each(function (label, id) { + if (id !== option.value) { + options.push({ + text: "requiring '" + label + "'", + value: id, + description: "Allows users to create/modify restricted components requiring '" + nfCommon.escapeHtml(label) + "'" + }); + } + }); + + options.sort(function (a, b) { + if (a.text === regardlessOfRestrictions) { + return -1; + } else if (b.text === regardlessOfRestrictions) { + return 1; + } + + return a.text < b.text ? -1 : a.text > b.text ? 1 : 0; + }); + + $('#restricted-component-required-permissions').combo({ + options: options, + select: function (restrictionOption) { + if (restrictionOption.text === regardlessOfRestrictions) { + $('#restriction-message').hide(); + } else { + $('#restriction-message').show(); + } + + loadPolicy(); + } + }); + } + + $('#restricted-component-required-permissions').show(); + } else { + $('#restriction-message').hide(); + $('#restricted-component-required-permissions').hide(); + } + // reload the policy loadPolicy(); } @@ -766,6 +823,14 @@ resource += ('/' + componentId); } + // identify more granular restrict component access if applicable + if (resource === 'restricted-components') { + var requiredPermission = $('#restricted-component-required-permissions').combo('getSelectedOption').value; + if (!nfCommon.isBlank(requiredPermission)) { + resource += ('/' + requiredPermission); + } + } + return { 'action': $('#selected-policy-action').text(), 'resource': '/' + resource @@ -909,7 +974,10 @@ var policyDeferred; if (resourceAndAction.resource.startsWith('/policies')) { - $('#admin-policy-message').show(); + // if this is a component specific policy permission, show the admin policy message + if (resourceAndAction.resource.endsWith('/policies')) { + $('#admin-policy-message').show(); + } policyDeferred = $.Deferred(function (deferred) { $.ajax({ @@ -980,6 +1048,87 @@ // reset the policy resetPolicy(); + deferred.reject(); + nfErrorHandler.handleAjaxError(xhr, status, error); + } + }); + }).promise(); + } else if (resourceAndAction.resource.startsWith('/restricted-components')) { + $('#admin-policy-message').hide(); + + policyDeferred = $.Deferred(function (deferred) { + $.ajax({ + type: 'GET', + url: '../nifi-api/policies/' + resourceAndAction.action + resourceAndAction.resource, + dataType: 'json' + }).done(function (policyEntity) { + // update the refresh timestamp + $('#policy-last-refreshed').text(policyEntity.generated); + + // ensure appropriate actions for the loaded policy + if (policyEntity.permissions.canRead === true) { + var policy = policyEntity.component; + + // if the return policy is for the desired policy (not inherited, show it) + if (resourceAndAction.resource === policy.resource) { + // populate the policy details + populatePolicy(policyEntity); + } else { + // reset the policy + resetPolicy(); + + // show an appropriate message + $('#policy-message').text('No restriction specific users.'); + + if (nfCanvasUtils.isConfigurableAuthorizer()) { + // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide + $('#new-policy-message').show(); + } + } + } else { + // reset the policy + resetPolicy(); + + // show an appropriate message + $('#policy-message').text('Not authorized to view the policy.'); + + if (nfCanvasUtils.isConfigurableAuthorizer()) { + // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide + $('#new-policy-message').show(); + } + } + + deferred.resolve(); + }).fail(function (xhr, status, error) { + if (xhr.status === 404) { + // reset the policy + resetPolicy(); + + // show an appropriate message + if (resourceAndAction.resource === '/restricted-components') { + $('#policy-message').text('No users with permission "regardless of restrictions."'); + } else { + $('#policy-message').text('No users with permission to specific restriction.'); + } + + if (nfCanvasUtils.isConfigurableAuthorizer()) { + // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide + $('#new-policy-message').show(); + } + + deferred.resolve(); + } else if (xhr.status === 403) { + // reset the policy + resetPolicy(); + + // show an appropriate message + $('#policy-message').text('Not authorized to access the policy for the specified resource.'); + + deferred.resolve(); + } else { + // reset the policy + resetPolicy(); + deferred.reject(); nfErrorHandler.handleAjaxError(xhr, status, error); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js index 824312adb5..563aff37fd 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js @@ -218,7 +218,7 @@ * @param item reporting task type */ var isSelectable = function (item) { - return nfCommon.isBlank(item.usageRestriction) || nfCommon.canAccessRestrictedComponents(); + return item.restricted === false || nfCommon.canAccessComponentRestrictions(item.explicitRestrictions); }; /** @@ -682,9 +682,28 @@ var item = reportingTaskTypesData.getItemById(rowId); // show the tooltip - if (nfCommon.isDefinedAndNotNull(item.usageRestriction)) { + if (item.restricted === true) { + var restrictionTip = $('
'); + + if (nfCommon.isBlank(item.usageRestriction)) { + restrictionTip.append($('

').text('Requires the following permissions:')); + } else { + restrictionTip.append($('

').text(item.usageRestriction + ' Requires the following permissions:')); + } + + var restrictions = []; + if (nfCommon.isDefinedAndNotNull(item.explicitRestrictions)) { + $.each(item.explicitRestrictions, function (_, explicitRestriction) { + var requiredPermission = explicitRestriction.requiredPermission; + restrictions.push("'" + requiredPermission.label + "' - " + nfCommon.escapeHtml(explicitRestriction.explanation)); + }); + } else { + restrictions.push('Access to restricted components regardless of restrictions.'); + } + restrictionTip.append(nfCommon.formatUnorderedList(restrictions)); + usageRestriction.qtip($.extend({}, nfCommon.config.tooltipConfig, { - content: item.usageRestriction, + content: restrictionTip, position: { container: $('#summary'), at: 'bottom right', @@ -699,6 +718,8 @@ } }); + var generalRestriction = nfCommon.getPolicyTypeListing('restricted-components'); + // load the available reporting tasks $.ajax({ type: 'GET', @@ -708,12 +729,54 @@ var id = 0; var tags = []; var groups = d3.set(); + var restrictedUsage = d3.map(); + var requiredPermissions = d3.map(); // begin the update reportingTaskTypesData.beginUpdate(); // go through each reporting task type $.each(response.reportingTaskTypes, function (i, documentedType) { + if (documentedType.restricted === true) { + if (nfCommon.isDefinedAndNotNull(documentedType.explicitRestrictions)) { + $.each(documentedType.explicitRestrictions, function (_, explicitRestriction) { + var requiredPermission = explicitRestriction.requiredPermission; + + // update required permissions + if (!requiredPermissions.has(requiredPermission.id)) { + requiredPermissions.set(requiredPermission.id, requiredPermission.label); + } + + // update component restrictions + if (!restrictedUsage.has(requiredPermission.id)) { + restrictedUsage.set(requiredPermission.id, []); + } + + restrictedUsage.get(requiredPermission.id).push({ + type: nfCommon.formatType(documentedType), + bundle: nfCommon.formatBundle(documentedType.bundle), + explanation: nfCommon.escapeHtml(explicitRestriction.explanation) + }) + }); + } else { + // update required permissions + if (!requiredPermissions.has(generalRestriction.value)) { + requiredPermissions.set(generalRestriction.value, generalRestriction.text); + } + + // update component restrictions + if (!restrictedUsage.has(generalRestriction.value)) { + restrictedUsage.set(generalRestriction.value, []); + } + + restrictedUsage.get(generalRestriction.value).push({ + type: nfCommon.formatType(documentedType), + bundle: nfCommon.formatBundle(documentedType.bundle), + explanation: nfCommon.escapeHtml(documentedType.usageRestriction) + }); + } + } + // record the group groups.add(documentedType.bundle.group); @@ -724,7 +787,9 @@ type: documentedType.type, bundle: documentedType.bundle, description: nfCommon.escapeHtml(documentedType.description), + restricted: documentedType.restricted, usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction), + explicitRestrictions: documentedType.explicitRestrictions, tags: documentedType.tags.join(', ') }); @@ -741,6 +806,9 @@ reportingTaskTypesData.reSort(); reportingTaskTypesGrid.invalidate(); + // set the component restrictions and the corresponding required permissions + nfCanvasUtils.addComponentRestrictions(restrictedUsage, requiredPermissions); + // set the total number of processors $('#total-reporting-task-types, #displayed-reporting-task-types').text(response.reportingTaskTypes.length); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js index f89c4aa427..42f0debc12 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js @@ -119,7 +119,7 @@ }, { text: 'access restricted components', value: 'restricted-components', - description: 'Allows users to create/modify restricted components assuming otherwise sufficient permissions' + description: 'Allows users to create/modify restricted components assuming other permissions are sufficient' }, { text: 'access all policies', value: 'policies', @@ -315,7 +315,7 @@ var markup = ''; // restriction - if (nfCommon.isBlank(dataContext.usageRestriction) === false) { + if (dataContext.restricted === true) { markup += '
'; } else { markup += '
'; @@ -590,7 +590,7 @@ }, /** - * Determines whether the current user can access restricted comopnents. + * Determines whether the current user can access restricted components. * * @returns {boolean} */ @@ -602,6 +602,56 @@ } }, + /** + * Determines whether the current user can access the specific explicit component restrictions. + * + * @param {object} explicitRestrictions + * @returns {boolean} + */ + canAccessComponentRestrictions: function (explicitRestrictions) { + if (nfCommon.isDefinedAndNotNull(nfCommon.currentUser)) { + if (nfCommon.currentUser.restrictedComponentsPermissions.canWrite === true) { + return true; + } + + var satisfiesRequiredPermission = function (requiredPermission) { + if (nfCommon.isEmpty(nfCommon.currentUser.componentRestrictionPermissions)) { + return false; + } + + var hasPermission = false; + + $.each(nfCommon.currentUser.componentRestrictionPermissions, function (_, componentRestrictionPermission) { + if (componentRestrictionPermission.requiredPermission.id === requiredPermission.id) { + if (componentRestrictionPermission.permissions.canWrite === true) { + hasPermission = true; + return false; + } + } + }); + + return hasPermission; + }; + + var satisfiesRequiredPermissions = true; + + if (nfCommon.isEmpty(explicitRestrictions)) { + satisfiesRequiredPermissions = false; + } else { + $.each(explicitRestrictions, function (_, explicitRestriction) { + if (!satisfiesRequiredPermission(explicitRestriction.requiredPermission)) { + satisfiesRequiredPermissions = false; + return false; + } + }); + } + + return satisfiesRequiredPermissions; + } else { + return false; + } + }, + /** * Determines whether the current user can access counters. * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js index 6c55dd81e3..6da6668945 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js @@ -517,7 +517,7 @@ }; /** - * Generates a human readable global policy strung. + * Generates a human readable global policy string. * * @param dataContext * @returns {string} @@ -527,6 +527,23 @@ nfCommon.getPolicyTypeListing(nfCommon.substringAfterFirst(dataContext.component.resource, '/')).text; }; + /** + * Generates a human readable restricted component policy string. + * + * @param dataContext + * @returns {string} + */ + var restrictedComponentResourceParser = function (dataContext) { + var resource = dataContext.component.resource; + + if (resource === '/restricted-components') { + return 'Restricted components regardless of restrictions'; + } + + var subResource = nfCommon.substringAfterFirst(resource, '/restricted-components/'); + return "Restricted components requiring '" + subResource + "'"; + }; + /** * Generates a human readable component policy string. * @@ -591,12 +608,7 @@ var policyDisplayNameFormatter = function (row, cell, value, columnDef, dataContext) { // if the user has permission to the policy if (dataContext.permissions.canRead === true) { - // check if Global policy - if (nfCommon.isUndefinedOrNull(dataContext.component.componentReference)) { - return globalResourceParser(dataContext); - } - // not a global policy... check if user has access to the component reference - return componentResourceParser(dataContext); + return formatPolicy(dataContext); } else { return '' + nfCommon.escapeHtml(dataContext.id) + ''; } @@ -942,6 +954,25 @@ data.sort(comparer, sortDetails.sortAsc); }; + /** + * Formats the specified policy. + * + * @param dataContext + * @returns {string} + */ + var formatPolicy = function (dataContext) { + if (dataContext.component.resource.startsWith('/restricted-components')) { + // restricted components policy + return restrictedComponentResourceParser(dataContext); + } else if (nfCommon.isUndefinedOrNull(dataContext.component.componentReference)) { + // global policy + return globalResourceParser(dataContext); + } else { + // not restricted/global policy... check if user has access to the component reference + return componentResourceParser(dataContext); + } + }; + /** * Sorts the specified data using the specified sort details. * @@ -962,26 +993,14 @@ // if the user has permission to the policy if (a.permissions.canRead === true) { - // check if Global policy - if (nfCommon.isUndefinedOrNull(a.component.componentReference)) { - aString = globalResourceParser(a); - } else { - // not a global policy... check if user has access to the component reference - aString = componentResourceParser(a); - } + aString = formatPolicy(a); } else { aString = a.id; } // if the user has permission to the policy if (b.permissions.canRead === true) { - // check if Global policy - if (nfCommon.isUndefinedOrNull(b.component.componentReference)) { - bString = globalResourceParser(b); - } else { - // not a global policy... check if user has access to the component reference - bString = componentResourceParser(b); - } + bString = formatPolicy(b); } else { bString = b.id; } diff --git a/nifi-nar-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/ExecuteGroovyScript.java b/nifi-nar-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/ExecuteGroovyScript.java index 3979f6c524..30c52804af 100644 --- a/nifi-nar-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/ExecuteGroovyScript.java +++ b/nifi-nar-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/ExecuteGroovyScript.java @@ -16,28 +16,22 @@ */ package org.apache.nifi.processors.groovyx; -import java.io.File; -import java.lang.reflect.Method; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.nifi.annotation.behavior.Restricted; +import groovy.lang.GroovyShell; +import groovy.lang.Script; import org.apache.nifi.annotation.behavior.DynamicProperty; import org.apache.nifi.annotation.behavior.EventDriven; import org.apache.nifi.annotation.behavior.InputRequirement; +import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.SeeAlso; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnScheduled; import org.apache.nifi.annotation.lifecycle.OnStopped; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; import org.apache.nifi.controller.ControllerService; import org.apache.nifi.dbcp.DBCPService; import org.apache.nifi.processor.AbstractProcessor; @@ -47,20 +41,25 @@ import org.apache.nifi.processor.ProcessorInitializationContext; import org.apache.nifi.processor.Relationship; import org.apache.nifi.processor.exception.ProcessException; import org.apache.nifi.processor.util.StandardValidators; +import org.apache.nifi.processors.groovyx.flow.GroovyProcessSessionWrap; +import org.apache.nifi.processors.groovyx.sql.OSql; +import org.apache.nifi.processors.groovyx.util.Files; +import org.apache.nifi.processors.groovyx.util.Validators; import org.codehaus.groovy.control.CompilerConfiguration; import org.codehaus.groovy.runtime.ResourceGroovyMethods; import org.codehaus.groovy.runtime.StackTraceUtils; -import org.apache.nifi.processors.groovyx.sql.OSql; -import org.apache.nifi.processors.groovyx.util.Files; -import org.apache.nifi.processors.groovyx.util.Validators; -import org.apache.nifi.processors.groovyx.flow.GroovyProcessSessionWrap; - -import groovy.lang.GroovyShell; -import groovy.lang.Script; - -import org.apache.nifi.components.ValidationResult; -import org.apache.nifi.components.ValidationContext; +import java.io.File; +import java.lang.reflect.Method; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; @EventDriven @InputRequirement(InputRequirement.Requirement.INPUT_ALLOWED) @@ -69,7 +68,13 @@ import org.apache.nifi.components.ValidationContext; "Experimental Extended Groovy script processor. The script is responsible for " + "handling the incoming flow file (transfer to SUCCESS or remove, e.g.) as well as any flow files created by " + "the script. If the handling is incomplete or incorrect, the session will be rolled back.") -@Restricted("Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.EXECUTE_CODE, + explanation = "Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") + } +) @SeeAlso(classNames={"org.apache.nifi.processors.script.ExecuteScript"}) @DynamicProperty(name = "A script engine property to update", value = "The value to set it to", diff --git a/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/DeleteHDFS.java b/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/DeleteHDFS.java index c75be68aad..ca1460f406 100644 --- a/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/DeleteHDFS.java +++ b/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/DeleteHDFS.java @@ -16,6 +16,29 @@ */ package org.apache.nifi.processors.hadoop; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.apache.hadoop.fs.FileStatus; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.nifi.annotation.behavior.InputRequirement; +import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; +import org.apache.nifi.annotation.behavior.TriggerWhenEmpty; +import org.apache.nifi.annotation.behavior.WritesAttribute; +import org.apache.nifi.annotation.behavior.WritesAttributes; +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.SeeAlso; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; +import org.apache.nifi.flowfile.FlowFile; +import org.apache.nifi.processor.ProcessContext; +import org.apache.nifi.processor.ProcessSession; +import org.apache.nifi.processor.Relationship; +import org.apache.nifi.processor.exception.ProcessException; +import org.apache.nifi.processor.util.StandardValidators; + import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -26,28 +49,6 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.hadoop.fs.FileStatus; -import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.Path; -import org.apache.nifi.annotation.behavior.InputRequirement; -import org.apache.nifi.annotation.behavior.Restricted; -import org.apache.nifi.annotation.behavior.TriggerWhenEmpty; -import org.apache.nifi.annotation.behavior.WritesAttribute; -import org.apache.nifi.annotation.behavior.WritesAttributes; -import org.apache.nifi.annotation.documentation.CapabilityDescription; -import org.apache.nifi.annotation.documentation.SeeAlso; -import org.apache.nifi.annotation.documentation.Tags; -import org.apache.nifi.components.PropertyDescriptor; -import org.apache.nifi.flowfile.FlowFile; -import org.apache.nifi.processor.ProcessContext; -import org.apache.nifi.processor.ProcessSession; -import org.apache.nifi.processor.Relationship; -import org.apache.nifi.processor.exception.ProcessException; -import org.apache.nifi.processor.util.StandardValidators; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - @TriggerWhenEmpty @InputRequirement(InputRequirement.Requirement.INPUT_ALLOWED) @Tags({"hadoop", "HDFS", "delete", "remove", "filesystem", "restricted"}) @@ -58,7 +59,16 @@ import com.google.common.collect.Maps; + " no incoming connections no flowfiles will be transfered to any output relationships. If there is an incoming" + " flowfile then provided there are no detected failures it will be transferred to success otherwise it will be sent to false. If" + " knowledge of globbed files deleted is necessary use ListHDFS first to produce a specific list of files to delete. ") -@Restricted("Provides operator the ability to delete any file that NiFi has access to in HDFS or the local filesystem.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.WRITE_FILESYSTEM, + explanation = "Provides operator the ability to delete any file that NiFi has access to in HDFS or the local filesystem."), + @Restriction( + requiredPermission = RequiredPermission.ACCESS_KEYTAB, + explanation = "Provides operator the ability to make use of any keytab and principal on the local filesystem that NiFi has access to."), + } +) @WritesAttributes({ @WritesAttribute(attribute="hdfs.filename", description="HDFS file to be deleted. " + "If multiple files are deleted, then only the last filename is set."), diff --git a/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/FetchHDFS.java b/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/FetchHDFS.java index 4237503aae..9e619a06e4 100644 --- a/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/FetchHDFS.java +++ b/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/FetchHDFS.java @@ -28,12 +28,14 @@ import org.apache.hadoop.security.UserGroupInformation; import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.SupportsBatching; import org.apache.nifi.annotation.behavior.WritesAttribute; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.SeeAlso; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.flowfile.attributes.CoreAttributes; import org.apache.nifi.processor.ProcessContext; @@ -61,7 +63,16 @@ import java.util.concurrent.TimeUnit; @WritesAttribute(attribute="hdfs.failure.reason", description="When a FlowFile is routed to 'failure', this attribute is added indicating why the file could " + "not be fetched from HDFS") @SeeAlso({ListHDFS.class, GetHDFS.class, PutHDFS.class}) -@Restricted("Provides operator the ability to retrieve any file that NiFi has access to in HDFS or the local filesystem.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.READ_FILESYSTEM, + explanation = "Provides operator the ability to retrieve any file that NiFi has access to in HDFS or the local filesystem."), + @Restriction( + requiredPermission = RequiredPermission.ACCESS_KEYTAB, + explanation = "Provides operator the ability to make use of any keytab and principal on the local filesystem that NiFi has access to."), + } +) public class FetchHDFS extends AbstractHadoopProcessor { static final PropertyDescriptor FILENAME = new PropertyDescriptor.Builder() diff --git a/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/GetHDFS.java b/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/GetHDFS.java index 27b7375b8b..5e0ef38a09 100644 --- a/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/GetHDFS.java +++ b/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/GetHDFS.java @@ -29,6 +29,7 @@ import org.apache.hadoop.io.compress.CompressionCodecFactory; import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.TriggerWhenEmpty; import org.apache.nifi.annotation.behavior.WritesAttribute; import org.apache.nifi.annotation.behavior.WritesAttributes; @@ -37,6 +38,7 @@ import org.apache.nifi.annotation.documentation.SeeAlso; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnScheduled; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.ValidationContext; import org.apache.nifi.components.ValidationResult; import org.apache.nifi.flowfile.FlowFile; @@ -76,7 +78,19 @@ import java.util.regex.Pattern; + "is set to /tmp, then files picked up from /tmp will have the path attribute set to \"./\". If the Recurse Subdirectories property is set to true and " + "a file is picked up from /tmp/abc/1/2/3, then the path attribute will be set to \"abc/1/2/3\".") }) @SeeAlso({PutHDFS.class, ListHDFS.class}) -@Restricted("Provides operator the ability to retrieve and delete any file that NiFi has access to in HDFS or the local filesystem.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.READ_FILESYSTEM, + explanation = "Provides operator the ability to retrieve any file that NiFi has access to in HDFS or the local filesystem."), + @Restriction( + requiredPermission = RequiredPermission.WRITE_FILESYSTEM, + explanation = "Provides operator the ability to delete any file that NiFi has access to in HDFS or the local filesystem."), + @Restriction( + requiredPermission = RequiredPermission.ACCESS_KEYTAB, + explanation = "Provides operator the ability to make use of any keytab and principal on the local filesystem that NiFi has access to."), + } +) public class GetHDFS extends AbstractHadoopProcessor { public static final String BUFFER_SIZE_KEY = "io.file.buffer.size"; diff --git a/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/MoveHDFS.java b/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/MoveHDFS.java index 5c889d684b..dfa9690b76 100644 --- a/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/MoveHDFS.java +++ b/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/MoveHDFS.java @@ -16,6 +16,35 @@ */ package org.apache.nifi.processors.hadoop; +import com.google.common.base.Preconditions; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileStatus; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.FileUtil; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.PathFilter; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.nifi.annotation.behavior.ReadsAttribute; +import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; +import org.apache.nifi.annotation.behavior.WritesAttribute; +import org.apache.nifi.annotation.behavior.WritesAttributes; +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.SeeAlso; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.annotation.lifecycle.OnScheduled; +import org.apache.nifi.components.AllowableValue; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; +import org.apache.nifi.flowfile.FlowFile; +import org.apache.nifi.flowfile.attributes.CoreAttributes; +import org.apache.nifi.processor.ProcessContext; +import org.apache.nifi.processor.ProcessSession; +import org.apache.nifi.processor.Relationship; +import org.apache.nifi.processor.exception.ProcessException; +import org.apache.nifi.processor.util.StandardValidators; +import org.apache.nifi.util.StopWatch; + import java.io.FileNotFoundException; import java.io.IOException; import java.security.PrivilegedAction; @@ -32,32 +61,6 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Pattern; -import com.google.common.base.Preconditions; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.FileStatus; -import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.FileUtil; -import org.apache.hadoop.fs.Path; -import org.apache.hadoop.fs.PathFilter; -import org.apache.hadoop.security.UserGroupInformation; -import org.apache.nifi.annotation.behavior.ReadsAttribute; -import org.apache.nifi.annotation.behavior.WritesAttribute; -import org.apache.nifi.annotation.behavior.WritesAttributes; -import org.apache.nifi.annotation.documentation.CapabilityDescription; -import org.apache.nifi.annotation.documentation.SeeAlso; -import org.apache.nifi.annotation.documentation.Tags; -import org.apache.nifi.annotation.lifecycle.OnScheduled; -import org.apache.nifi.components.AllowableValue; -import org.apache.nifi.components.PropertyDescriptor; -import org.apache.nifi.flowfile.FlowFile; -import org.apache.nifi.flowfile.attributes.CoreAttributes; -import org.apache.nifi.processor.ProcessContext; -import org.apache.nifi.processor.ProcessSession; -import org.apache.nifi.processor.Relationship; -import org.apache.nifi.processor.exception.ProcessException; -import org.apache.nifi.processor.util.StandardValidators; -import org.apache.nifi.util.StopWatch; - /** * This processor renames files on HDFS. */ @@ -68,6 +71,19 @@ import org.apache.nifi.util.StopWatch; @WritesAttribute(attribute = "filename", description = "The name of the file written to HDFS is stored in this attribute."), @WritesAttribute(attribute = "absolute.hdfs.path", description = "The absolute path to the file on HDFS is stored in this attribute.")}) @SeeAlso({PutHDFS.class, GetHDFS.class}) +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.READ_FILESYSTEM, + explanation = "Provides operator the ability to retrieve any file that NiFi has access to in HDFS or the local filesystem."), + @Restriction( + requiredPermission = RequiredPermission.WRITE_FILESYSTEM, + explanation = "Provides operator the ability to delete any file that NiFi has access to in HDFS or the local filesystem."), + @Restriction( + requiredPermission = RequiredPermission.ACCESS_KEYTAB, + explanation = "Provides operator the ability to make use of any keytab and principal on the local filesystem that NiFi has access to."), + } +) public class MoveHDFS extends AbstractHadoopProcessor { // static global diff --git a/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/PutHDFS.java b/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/PutHDFS.java index fe627024b7..564fcf0a54 100644 --- a/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/PutHDFS.java +++ b/nifi-nar-bundles/nifi-hadoop-bundle/nifi-hdfs-processors/src/main/java/org/apache/nifi/processors/hadoop/PutHDFS.java @@ -27,6 +27,7 @@ import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.ReadsAttribute; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.WritesAttribute; import org.apache.nifi.annotation.behavior.WritesAttributes; import org.apache.nifi.annotation.documentation.CapabilityDescription; @@ -35,6 +36,7 @@ import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.components.PropertyValue; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.flowfile.attributes.CoreAttributes; import org.apache.nifi.processor.DataUnit; @@ -72,7 +74,16 @@ import java.util.concurrent.TimeUnit; @WritesAttribute(attribute = "absolute.hdfs.path", description = "The absolute path to the file on HDFS is stored in this attribute.") }) @SeeAlso(GetHDFS.class) -@Restricted("Provides operator the ability to write to any file that NiFi has access to in HDFS or the local filesystem.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.WRITE_FILESYSTEM, + explanation = "Provides operator the ability to delete any file that NiFi has access to in HDFS or the local filesystem."), + @Restriction( + requiredPermission = RequiredPermission.ACCESS_KEYTAB, + explanation = "Provides operator the ability to make use of any keytab and principal on the local filesystem that NiFi has access to."), + } +) public class PutHDFS extends AbstractHadoopProcessor { public static final String REPLACE_RESOLUTION = "replace"; diff --git a/nifi-nar-bundles/nifi-parquet-bundle/nifi-parquet-processors/src/main/java/org/apache/nifi/processors/parquet/FetchParquet.java b/nifi-nar-bundles/nifi-parquet-bundle/nifi-parquet-processors/src/main/java/org/apache/nifi/processors/parquet/FetchParquet.java index 8014cad5ac..e571233b5a 100644 --- a/nifi-nar-bundles/nifi-parquet-bundle/nifi-parquet-processors/src/main/java/org/apache/nifi/processors/parquet/FetchParquet.java +++ b/nifi-nar-bundles/nifi-parquet-bundle/nifi-parquet-processors/src/main/java/org/apache/nifi/processors/parquet/FetchParquet.java @@ -21,12 +21,14 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.SupportsBatching; import org.apache.nifi.annotation.behavior.WritesAttribute; import org.apache.nifi.annotation.behavior.WritesAttributes; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.SeeAlso; import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processors.hadoop.AbstractFetchHDFSRecord; @@ -50,7 +52,16 @@ import java.io.IOException; @WritesAttribute(attribute = "record.count", description = "The number of records in the resulting flow file") }) @SeeAlso({PutParquet.class}) -@Restricted("Provides operator the ability to retrieve any file that NiFi has access to in HDFS or the local filesystem.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.READ_FILESYSTEM, + explanation = "Provides operator the ability to retrieve any file that NiFi has access to in HDFS or the local filesystem."), + @Restriction( + requiredPermission = RequiredPermission.ACCESS_KEYTAB, + explanation = "Provides operator the ability to make use of any keytab and principal on the local filesystem that NiFi has access to."), + } +) public class FetchParquet extends AbstractFetchHDFSRecord { @Override diff --git a/nifi-nar-bundles/nifi-parquet-bundle/nifi-parquet-processors/src/main/java/org/apache/nifi/processors/parquet/PutParquet.java b/nifi-nar-bundles/nifi-parquet-bundle/nifi-parquet-processors/src/main/java/org/apache/nifi/processors/parquet/PutParquet.java index eb105430a3..c3907b0f42 100644 --- a/nifi-nar-bundles/nifi-parquet-bundle/nifi-parquet-processors/src/main/java/org/apache/nifi/processors/parquet/PutParquet.java +++ b/nifi-nar-bundles/nifi-parquet-bundle/nifi-parquet-processors/src/main/java/org/apache/nifi/processors/parquet/PutParquet.java @@ -23,6 +23,7 @@ import org.apache.hadoop.fs.Path; import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.ReadsAttribute; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.WritesAttribute; import org.apache.nifi.annotation.behavior.WritesAttributes; import org.apache.nifi.annotation.documentation.CapabilityDescription; @@ -30,6 +31,7 @@ import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.avro.AvroTypeUtil; import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.processor.DataUnit; import org.apache.nifi.processor.ProcessContext; @@ -67,7 +69,16 @@ import java.util.List; @WritesAttribute(attribute = "absolute.hdfs.path", description = "The absolute path to the file is stored in this attribute."), @WritesAttribute(attribute = "record.count", description = "The number of records written to the Parquet file") }) -@Restricted("Provides operator the ability to write to any file that NiFi has access to in HDFS or the local filesystem.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.READ_FILESYSTEM, + explanation = "Provides operator the ability to write any file that NiFi has access to in HDFS or the local filesystem."), + @Restriction( + requiredPermission = RequiredPermission.ACCESS_KEYTAB, + explanation = "Provides operator the ability to make use of any keytab and principal on the local filesystem that NiFi has access to."), + } +) public class PutParquet extends AbstractPutHDFSRecord { public static final PropertyDescriptor ROW_GROUP_SIZE = new PropertyDescriptor.Builder() diff --git a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/lookup/script/ScriptedLookupService.java b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/lookup/script/ScriptedLookupService.java index 64fa4ec1eb..b2f37620f2 100644 --- a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/lookup/script/ScriptedLookupService.java +++ b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/lookup/script/ScriptedLookupService.java @@ -17,12 +17,14 @@ package org.apache.nifi.lookup.script; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnDisabled; import org.apache.nifi.annotation.lifecycle.OnEnabled; import org.apache.nifi.components.ConfigurableComponent; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.ValidationResult; import org.apache.nifi.components.state.StateManager; import org.apache.nifi.controller.ConfigurationContext; @@ -34,9 +36,9 @@ import org.apache.nifi.lookup.LookupService; import org.apache.nifi.processor.exception.ProcessException; import org.apache.nifi.processor.util.StandardValidators; import org.apache.nifi.processors.script.ScriptEngineConfigurator; +import org.apache.nifi.script.AbstractScriptedControllerService; import org.apache.nifi.script.ScriptingComponentHelper; import org.apache.nifi.script.ScriptingComponentUtils; -import org.apache.nifi.script.AbstractScriptedControllerService; import javax.script.Invocable; import javax.script.ScriptException; @@ -56,7 +58,13 @@ import java.util.concurrent.atomic.AtomicReference; */ @Tags({"lookup", "record", "script", "invoke", "groovy", "python", "jython", "jruby", "ruby", "javascript", "js", "lua", "luaj", "restricted"}) @CapabilityDescription("Allows the user to provide a scripted LookupService instance in order to enrich records from an incoming flow file.") -@Restricted("Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.EXECUTE_CODE, + explanation = "Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") + } +) public class ScriptedLookupService extends AbstractScriptedControllerService implements LookupService { protected final AtomicReference> lookupService = new AtomicReference<>(); diff --git a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/processors/script/ExecuteScript.java b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/processors/script/ExecuteScript.java index 108da9803e..db9a8556a9 100644 --- a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/processors/script/ExecuteScript.java +++ b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/processors/script/ExecuteScript.java @@ -20,17 +20,19 @@ package org.apache.nifi.processors.script; import org.apache.commons.io.IOUtils; import org.apache.nifi.annotation.behavior.DynamicProperty; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.Stateful; +import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.SeeAlso; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.annotation.lifecycle.OnScheduled; import org.apache.nifi.annotation.lifecycle.OnStopped; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.ValidationContext; import org.apache.nifi.components.ValidationResult; import org.apache.nifi.components.state.Scope; import org.apache.nifi.logging.ComponentLog; -import org.apache.nifi.annotation.lifecycle.OnScheduled; -import org.apache.nifi.annotation.documentation.CapabilityDescription; -import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.processor.AbstractSessionFactoryProcessor; import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessSession; @@ -41,7 +43,6 @@ import org.apache.nifi.processor.util.StandardValidators; import org.apache.nifi.script.ScriptingComponentHelper; import org.apache.nifi.script.ScriptingComponentUtils; -import java.nio.charset.Charset; import javax.script.Bindings; import javax.script.ScriptContext; import javax.script.ScriptEngine; @@ -49,6 +50,7 @@ import javax.script.ScriptException; import javax.script.SimpleBindings; import java.io.FileInputStream; import java.io.IOException; +import java.nio.charset.Charset; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -67,7 +69,13 @@ import java.util.Set; supportsExpressionLanguage = true, description = "Updates a script engine property specified by the Dynamic Property's key with the value " + "specified by the Dynamic Property's value") -@Restricted("Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.EXECUTE_CODE, + explanation = "Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") + } +) @Stateful(scopes = {Scope.LOCAL, Scope.CLUSTER}, description = "Scripts can store and retrieve state using the State Management APIs. Consult the State Manager section of the Developer's Guide for more details.") @SeeAlso({InvokeScriptedProcessor.class}) diff --git a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/processors/script/InvokeScriptedProcessor.java b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/processors/script/InvokeScriptedProcessor.java index 6abf93ed38..5d34a10448 100644 --- a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/processors/script/InvokeScriptedProcessor.java +++ b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/processors/script/InvokeScriptedProcessor.java @@ -20,6 +20,7 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.annotation.behavior.DynamicProperty; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.Stateful; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.SeeAlso; @@ -27,6 +28,7 @@ import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnScheduled; import org.apache.nifi.annotation.lifecycle.OnStopped; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.ValidationContext; import org.apache.nifi.components.ValidationResult; import org.apache.nifi.components.state.Scope; @@ -70,7 +72,13 @@ import java.util.concurrent.atomic.AtomicReference; @Stateful(scopes = {Scope.LOCAL, Scope.CLUSTER}, description = "Scripts can store and retrieve state using the State Management APIs. Consult the State Manager section of the Developer's Guide for more details.") @SeeAlso({ExecuteScript.class}) -@Restricted("Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.EXECUTE_CODE, + explanation = "Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") + } +) public class InvokeScriptedProcessor extends AbstractSessionFactoryProcessor { private final AtomicReference processor = new AtomicReference<>(); diff --git a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/record/script/ScriptedReader.java b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/record/script/ScriptedReader.java index 7b544fb9a6..d395e6d403 100644 --- a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/record/script/ScriptedReader.java +++ b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/record/script/ScriptedReader.java @@ -17,9 +17,11 @@ package org.apache.nifi.record.script; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnEnabled; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.ValidationResult; import org.apache.nifi.controller.ConfigurationContext; import org.apache.nifi.logging.ComponentLog; @@ -43,7 +45,13 @@ import java.util.Map; */ @Tags({"record", "recordFactory", "script", "invoke", "groovy", "python", "jython", "jruby", "ruby", "javascript", "js", "lua", "luaj", "restricted"}) @CapabilityDescription("Allows the user to provide a scripted RecordReaderFactory instance in order to read/parse/generate records from an incoming flow file.") -@Restricted("Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.EXECUTE_CODE, + explanation = "Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") + } +) public class ScriptedReader extends AbstractScriptedRecordFactory implements RecordReaderFactory { @OnEnabled diff --git a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/record/script/ScriptedRecordSetWriter.java b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/record/script/ScriptedRecordSetWriter.java index 7544ae2f50..fe5fc637f3 100644 --- a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/record/script/ScriptedRecordSetWriter.java +++ b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/record/script/ScriptedRecordSetWriter.java @@ -16,20 +16,12 @@ */ package org.apache.nifi.record.script; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.UndeclaredThrowableException; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; - -import javax.script.Invocable; -import javax.script.ScriptException; - import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnEnabled; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.ValidationResult; import org.apache.nifi.controller.ConfigurationContext; import org.apache.nifi.logging.ComponentLog; @@ -39,12 +31,27 @@ import org.apache.nifi.serialization.RecordSetWriter; import org.apache.nifi.serialization.RecordSetWriterFactory; import org.apache.nifi.serialization.record.RecordSchema; +import javax.script.Invocable; +import javax.script.ScriptException; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; + /** * A RecordSetWriter implementation that allows the user to script the RecordWriter instance */ @Tags({"record", "writer", "script", "invoke", "groovy", "python", "jython", "jruby", "ruby", "javascript", "js", "lua", "luaj", "restricted"}) @CapabilityDescription("Allows the user to provide a scripted RecordSetWriterFactory instance in order to write records to an outgoing flow file.") -@Restricted("Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.EXECUTE_CODE, + explanation = "Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") + } +) public class ScriptedRecordSetWriter extends AbstractScriptedRecordFactory implements RecordSetWriterFactory { @Override diff --git a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/reporting/script/ScriptedReportingTask.java b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/reporting/script/ScriptedReportingTask.java index 05454c3527..111839de23 100644 --- a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/reporting/script/ScriptedReportingTask.java +++ b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/reporting/script/ScriptedReportingTask.java @@ -20,10 +20,12 @@ import com.yammer.metrics.core.VirtualMachineMetrics; import org.apache.commons.io.IOUtils; import org.apache.nifi.annotation.behavior.DynamicProperty; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnScheduled; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.ValidationContext; import org.apache.nifi.components.ValidationResult; import org.apache.nifi.controller.ConfigurationContext; @@ -31,9 +33,9 @@ import org.apache.nifi.logging.ComponentLog; import org.apache.nifi.processor.exception.ProcessException; import org.apache.nifi.processor.util.StandardValidators; import org.apache.nifi.processors.script.ScriptEngineConfigurator; -import org.apache.nifi.script.ScriptingComponentHelper; import org.apache.nifi.reporting.AbstractReportingTask; import org.apache.nifi.reporting.ReportingContext; +import org.apache.nifi.script.ScriptingComponentHelper; import javax.script.Bindings; import javax.script.ScriptContext; @@ -61,7 +63,13 @@ import java.util.Map; supportsExpressionLanguage = true, description = "Updates a script engine property specified by the Dynamic Property's key with the value " + "specified by the Dynamic Property's value") -@Restricted("Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.EXECUTE_CODE, + explanation = "Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") + } +) public class ScriptedReportingTask extends AbstractReportingTask { protected volatile ScriptingComponentHelper scriptingComponentHelper = new ScriptingComponentHelper(); diff --git a/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/SiteToSiteBulletinReportingTask.java b/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/SiteToSiteBulletinReportingTask.java index 226e8791ea..03d8f3b724 100644 --- a/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/SiteToSiteBulletinReportingTask.java +++ b/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/SiteToSiteBulletinReportingTask.java @@ -17,6 +17,27 @@ package org.apache.nifi.reporting; +import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; +import org.apache.nifi.annotation.behavior.Stateful; +import org.apache.nifi.annotation.configuration.DefaultSchedule; +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; +import org.apache.nifi.components.state.Scope; +import org.apache.nifi.processor.exception.ProcessException; +import org.apache.nifi.processor.util.StandardValidators; +import org.apache.nifi.remote.Transaction; +import org.apache.nifi.remote.TransferDirection; +import org.apache.nifi.scheduling.SchedulingStrategy; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonBuilderFactory; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.text.DateFormat; @@ -31,32 +52,18 @@ import java.util.TimeZone; import java.util.UUID; import java.util.concurrent.TimeUnit; -import javax.json.Json; -import javax.json.JsonArray; -import javax.json.JsonArrayBuilder; -import javax.json.JsonBuilderFactory; -import javax.json.JsonObject; -import javax.json.JsonObjectBuilder; - -import org.apache.nifi.annotation.behavior.Restricted; -import org.apache.nifi.annotation.behavior.Stateful; -import org.apache.nifi.annotation.configuration.DefaultSchedule; -import org.apache.nifi.annotation.documentation.CapabilityDescription; -import org.apache.nifi.annotation.documentation.Tags; -import org.apache.nifi.components.PropertyDescriptor; -import org.apache.nifi.components.state.Scope; -import org.apache.nifi.processor.exception.ProcessException; -import org.apache.nifi.processor.util.StandardValidators; -import org.apache.nifi.remote.Transaction; -import org.apache.nifi.remote.TransferDirection; -import org.apache.nifi.scheduling.SchedulingStrategy; - @Tags({"bulletin", "site", "site to site", "restricted"}) @CapabilityDescription("Publishes Bulletin events using the Site To Site protocol. Note: only up to 5 bulletins are stored per component and up to " + "10 bulletins at controller level for a duration of up to 5 minutes. If this reporting task is not scheduled frequently enough some bulletins " + "may not be sent.") @Stateful(scopes = Scope.LOCAL, description = "Stores the Reporting Task's last bulletin ID so that on restart the task knows where it left off.") -@Restricted("Provides operator the ability to send sensitive details contained in bulletin events to any external system.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.EXPORT_NIFI_DETAILS, + explanation = "Provides operator the ability to send sensitive details contained in bulletin events to any external system.") + } +) @DefaultSchedule(strategy = SchedulingStrategy.TIMER_DRIVEN, period = "1 min") public class SiteToSiteBulletinReportingTask extends AbstractSiteToSiteReportingTask { diff --git a/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/SiteToSiteProvenanceReportingTask.java b/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/SiteToSiteProvenanceReportingTask.java index b458d737c8..f7a59db34d 100644 --- a/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/SiteToSiteProvenanceReportingTask.java +++ b/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/SiteToSiteProvenanceReportingTask.java @@ -19,6 +19,7 @@ package org.apache.nifi.reporting; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.Stateful; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; @@ -26,6 +27,7 @@ import org.apache.nifi.annotation.lifecycle.OnScheduled; import org.apache.nifi.annotation.lifecycle.OnUnscheduled; import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.state.Scope; import org.apache.nifi.controller.ConfigurationContext; import org.apache.nifi.controller.status.ProcessGroupStatus; @@ -64,7 +66,13 @@ import java.util.concurrent.TimeUnit; @Tags({"provenance", "lineage", "tracking", "site", "site to site", "restricted"}) @CapabilityDescription("Publishes Provenance events using the Site To Site protocol.") @Stateful(scopes = Scope.LOCAL, description = "Stores the Reporting Task's last event Id so that on restart the task knows where it left off.") -@Restricted("Provides operator the ability send sensitive details contained in Provenance events to any external system.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.EXPORT_NIFI_DETAILS, + explanation = "Provides operator the ability to send sensitive details contained in Provenance events to any external system.") + } +) public class SiteToSiteProvenanceReportingTask extends AbstractSiteToSiteReportingTask { static final String TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteProcess.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteProcess.java index c1ab946e8b..79c4a9b4bd 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteProcess.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteProcess.java @@ -21,6 +21,7 @@ import org.apache.nifi.annotation.behavior.DynamicProperty; import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.WritesAttribute; import org.apache.nifi.annotation.behavior.WritesAttributes; import org.apache.nifi.annotation.documentation.CapabilityDescription; @@ -28,6 +29,7 @@ import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnScheduled; import org.apache.nifi.annotation.lifecycle.OnUnscheduled; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.Validator; import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.logging.ComponentLog; @@ -71,7 +73,13 @@ import java.util.concurrent.locks.ReentrantLock; + "to be long-running, the Processor can output the partial data on a specified interval. When this option is used, the output is expected to be in textual " + "format, as it typically does not make sense to split binary data on arbitrary time-based intervals.") @DynamicProperty(name = "An environment variable name", value = "An environment variable value", description = "These environment variables are passed to the process spawned by this Processor") -@Restricted("Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.EXECUTE_CODE, + explanation = "Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") + } +) @WritesAttributes({ @WritesAttribute(attribute = "command", description = "Executed command"), @WritesAttribute(attribute = "command.arguments", description = "Arguments of the command") diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteStreamCommand.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteStreamCommand.java index 1c1137c0d3..68e55ccf91 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteStreamCommand.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteStreamCommand.java @@ -23,12 +23,14 @@ import org.apache.nifi.annotation.behavior.EventDriven; import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.SupportsBatching; import org.apache.nifi.annotation.behavior.WritesAttribute; import org.apache.nifi.annotation.behavior.WritesAttributes; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.ValidationContext; import org.apache.nifi.components.ValidationResult; import org.apache.nifi.components.Validator; @@ -138,7 +140,13 @@ import java.util.concurrent.atomic.AtomicReference; @WritesAttribute(attribute = "execution.command.args", description = "The semi-colon delimited list of arguments"), @WritesAttribute(attribute = "execution.status", description = "The exit status code returned from executing the command"), @WritesAttribute(attribute = "execution.error", description = "Any error messages returned from executing the command")}) -@Restricted("Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.EXECUTE_CODE, + explanation = "Provides operator the ability to execute arbitrary code assuming all permissions that NiFi has.") + } +) public class ExecuteStreamCommand extends AbstractProcessor { public static final Relationship ORIGINAL_RELATIONSHIP = new Relationship.Builder() diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/FetchFile.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/FetchFile.java index 29710ad082..416ba8b2e9 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/FetchFile.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/FetchFile.java @@ -21,11 +21,13 @@ import org.apache.commons.lang3.StringUtils; import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.SeeAlso; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.ValidationContext; import org.apache.nifi.components.ValidationResult; import org.apache.nifi.flowfile.FlowFile; @@ -58,7 +60,16 @@ import java.util.concurrent.TimeUnit; @CapabilityDescription("Reads the contents of a file from disk and streams it into the contents of an incoming FlowFile. Once this is done, the file is optionally moved elsewhere or deleted " + "to help keep the file system organized.") @SeeAlso({GetFile.class, PutFile.class, ListFile.class}) -@Restricted("Provides operator the ability to read from and delete any file that NiFi has access to.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.READ_FILESYSTEM, + explanation = "Provides operator the ability to read from any file that NiFi has access to."), + @Restriction( + requiredPermission = RequiredPermission.WRITE_FILESYSTEM, + explanation = "Provides operator the ability to delete any file that NiFi has access to.") + } +) public class FetchFile extends AbstractProcessor { static final AllowableValue COMPLETION_NONE = new AllowableValue("None", "None", "Leave the file as-is"); static final AllowableValue COMPLETION_MOVE = new AllowableValue("Move File", "Move File", "Moves the file to the directory specified by the property"); diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/GetFile.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/GetFile.java index e5d2827195..b193d0af64 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/GetFile.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/GetFile.java @@ -19,6 +19,7 @@ package org.apache.nifi.processors.standard; import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.TriggerWhenEmpty; import org.apache.nifi.annotation.behavior.WritesAttribute; import org.apache.nifi.annotation.behavior.WritesAttributes; @@ -27,6 +28,7 @@ import org.apache.nifi.annotation.documentation.SeeAlso; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnScheduled; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.flowfile.attributes.CoreAttributes; import org.apache.nifi.logging.ComponentLog; @@ -93,7 +95,16 @@ import java.util.regex.Pattern; @WritesAttribute(attribute = "absolute.path", description = "The full/absolute path from where a file was picked up. The current 'path' " + "attribute is still populated, but may be a relative path")}) @SeeAlso({PutFile.class, FetchFile.class}) -@Restricted("Provides operator the ability to read from and delete any file that NiFi has access to.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.READ_FILESYSTEM, + explanation = "Provides operator the ability to read from any file that NiFi has access to."), + @Restriction( + requiredPermission = RequiredPermission.WRITE_FILESYSTEM, + explanation = "Provides operator the ability to delete any file that NiFi has access to.") + } +) public class GetFile extends AbstractProcessor { public static final PropertyDescriptor DIRECTORY = new PropertyDescriptor.Builder() diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PutFile.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PutFile.java index 87c09c68e3..b770486269 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PutFile.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PutFile.java @@ -21,11 +21,13 @@ import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.ReadsAttribute; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; import org.apache.nifi.annotation.behavior.SupportsBatching; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.SeeAlso; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.flowfile.attributes.CoreAttributes; import org.apache.nifi.logging.ComponentLog; @@ -63,7 +65,13 @@ import java.util.regex.Pattern; @CapabilityDescription("Writes the contents of a FlowFile to the local file system") @SeeAlso({FetchFile.class, GetFile.class}) @ReadsAttribute(attribute = "filename", description = "The filename to use when writing the FlowFile to disk.") -@Restricted("Provides operator the ability to write to any file that NiFi has access to.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.WRITE_FILESYSTEM, + explanation = "Provides operator the ability to write to any file that NiFi has access to.") + } +) public class PutFile extends AbstractProcessor { public static final String REPLACE_RESOLUTION = "replace"; diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/TailFile.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/TailFile.java index 2234265f8b..886930916e 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/TailFile.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/TailFile.java @@ -16,6 +16,39 @@ */ package org.apache.nifi.processors.standard; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.annotation.behavior.InputRequirement; +import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; +import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; +import org.apache.nifi.annotation.behavior.Stateful; +import org.apache.nifi.annotation.behavior.TriggerSerially; +import org.apache.nifi.annotation.behavior.WritesAttribute; +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.annotation.lifecycle.OnScheduled; +import org.apache.nifi.annotation.lifecycle.OnStopped; +import org.apache.nifi.annotation.notification.OnPrimaryNodeStateChange; +import org.apache.nifi.components.AllowableValue; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.components.state.Scope; +import org.apache.nifi.components.state.StateMap; +import org.apache.nifi.flowfile.FlowFile; +import org.apache.nifi.flowfile.attributes.CoreAttributes; +import org.apache.nifi.processor.AbstractProcessor; +import org.apache.nifi.processor.ProcessContext; +import org.apache.nifi.processor.ProcessSession; +import org.apache.nifi.processor.Relationship; +import org.apache.nifi.processor.exception.ProcessException; +import org.apache.nifi.processor.io.OutputStreamCallback; +import org.apache.nifi.processor.util.StandardValidators; +import org.apache.nifi.stream.io.NullOutputStream; +import org.apache.nifi.stream.io.StreamUtils; + import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.EOFException; @@ -47,37 +80,6 @@ import java.util.zip.CRC32; import java.util.zip.CheckedInputStream; import java.util.zip.Checksum; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.annotation.behavior.InputRequirement; -import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; -import org.apache.nifi.annotation.behavior.Restricted; -import org.apache.nifi.annotation.behavior.Stateful; -import org.apache.nifi.annotation.behavior.TriggerSerially; -import org.apache.nifi.annotation.behavior.WritesAttribute; -import org.apache.nifi.annotation.documentation.CapabilityDescription; -import org.apache.nifi.annotation.documentation.Tags; -import org.apache.nifi.annotation.lifecycle.OnScheduled; -import org.apache.nifi.annotation.lifecycle.OnStopped; -import org.apache.nifi.annotation.notification.OnPrimaryNodeStateChange; -import org.apache.nifi.components.AllowableValue; -import org.apache.nifi.components.PropertyDescriptor; -import org.apache.nifi.components.ValidationContext; -import org.apache.nifi.components.ValidationResult; -import org.apache.nifi.components.state.Scope; -import org.apache.nifi.components.state.StateMap; -import org.apache.nifi.flowfile.FlowFile; -import org.apache.nifi.flowfile.attributes.CoreAttributes; -import org.apache.nifi.processor.AbstractProcessor; -import org.apache.nifi.processor.ProcessContext; -import org.apache.nifi.processor.ProcessSession; -import org.apache.nifi.processor.Relationship; -import org.apache.nifi.processor.exception.ProcessException; -import org.apache.nifi.processor.io.OutputStreamCallback; -import org.apache.nifi.processor.util.StandardValidators; -import org.apache.nifi.stream.io.NullOutputStream; -import org.apache.nifi.stream.io.StreamUtils; - // note: it is important that this Processor is not marked as @SupportsBatching because the session commits must complete before persisting state locally; otherwise, data loss may occur @TriggerSerially @InputRequirement(Requirement.INPUT_FORBIDDEN) @@ -91,7 +93,13 @@ import org.apache.nifi.stream.io.StreamUtils; @Stateful(scopes = {Scope.LOCAL, Scope.CLUSTER}, description = "Stores state about where in the Tailed File it left off so that on restart it does not have to duplicate data. " + "State is stored either local or clustered depend on the property.") @WritesAttribute(attribute = "tailfile.original.path", description = "Path of the original file the flow file comes from.") -@Restricted("Provides operator the ability to read from any file that NiFi has access to.") +@Restricted( + restrictions = { + @Restriction( + requiredPermission = RequiredPermission.READ_FILESYSTEM, + explanation = "Provides operator the ability to read from any file that NiFi has access to.") + } +) public class TailFile extends AbstractProcessor { static final String MAP_PREFIX = "file.";