NIFI-4885:

- Introducing more granular restricted component access policies.

This closes #2515.

Signed-off-by: Mark Payne <markap14@hotmail.com>
This commit is contained in:
Matt Gilman 2018-02-16 16:21:47 -05:00 committed by Mark Payne
parent d4632bdd5d
commit b1217f529b
69 changed files with 2040 additions and 275 deletions

View File

@ -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 {};
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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
<<UI-with-multi-tenant-authorization>>.
Clicking the `Add` button or double-clicking on a Processor Type will add the selected Processor to the canvas at the

View File

@ -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()

View File

@ -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()

View File

@ -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);
}
}

View File

@ -33,7 +33,9 @@ public class DocumentedTypeDTO {
private BundleDTO bundle;
private List<ControllerServiceApiDTO> controllerServiceApis;
private String description;
private boolean restricted;
private String usageRestriction;
private Set<ExplicitRestrictionDTO> explicitRestrictions;
private String deprecationReason;
private Set<String> 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<ExplicitRestrictionDTO> getExplicitRestrictions() {
return explicitRestrictions;
}
public void setExplicitRestrictions(Set<ExplicitRestrictionDTO> 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;

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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<ComponentRestrictionPermissionDTO> 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<ComponentRestrictionPermissionDTO> getComponentRestrictionPermissions() {
return componentRestrictionPermissions;
}
public void setComponentRestrictionPermissions(Set<ComponentRestrictionPermissionDTO> componentRestrictionPermissions) {
this.componentRestrictionPermissions = componentRestrictionPermissions;
}
/**
* @return whether the current user can version flows
*/

View File

@ -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.");
}

View File

@ -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")

View File

@ -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)

View File

@ -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.
*

View File

@ -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<String, String> 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<String, String> 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<Authorizable> getRestrictedComponentsAuthorizable(final Class<?> configurableComponentClass) {
final Set<Authorizable> 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;
}
}

View File

@ -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<Curr
mergePermissions(clientEntity.getPoliciesPermissions(), entity.getPoliciesPermissions());
mergePermissions(clientEntity.getProvenancePermissions(), entity.getProvenancePermissions());
mergePermissions(clientEntity.getTenantsPermissions(), entity.getTenantsPermissions());
mergePermissions(clientEntity.getSystemPermissions(), entity.getSystemPermissions());
mergePermissions(clientEntity.getTenantsPermissions(), entity.getTenantsPermissions());
final Set<ComponentRestrictionPermissionDTO> clientEntityComponentRestrictionsPermissions = clientEntity.getComponentRestrictionPermissions();
final Set<ComponentRestrictionPermissionDTO> 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());
});
}
}
}

View File

@ -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<ComponentRestrictionPermissionDTO> 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<ComponentRestrictionPermissionDTO> 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<NodeIdentifier, CurrentUserEntity> 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;
}
}

View File

@ -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<Authorizable> 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<Authorizable> restrictedComponentsAuthorizables = RestrictedComponentsAuthorizableFactory.getRestrictedComponentsAuthorizable(getComponentClass());
for (final Authorizable restrictedComponentsAuthorizable : restrictedComponentsAuthorizables) {
restrictedComponentsAuthorizable.authorize(authorizer, RequestAction.WRITE, user, resourceContext);
}
}
// defer to the base authorization check

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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<Authorizable> getRestrictedAuthorizables();
/**
* Returns the property descriptor for the specified property.
*

View File

@ -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<Authorizable> 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<Authorizable> 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<Authorizable> 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<Authorizable> getRestrictedAuthorizables() {
return RestrictedComponentsAuthorizableFactory.getRestrictedComponentsAuthorizable(reportingTaskNode.getComponentClass());
}
@Override
public String getValue(PropertyDescriptor propertyDescriptor) {
return reportingTaskNode.getProperty(propertyDescriptor);

View File

@ -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<ComponentRestrictionPermissionDTO> 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;
}

View File

@ -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.
*

View File

@ -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) {

View File

@ -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<ComponentAuthorizable> 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<ComponentAuthorizable> 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) {

View File

@ -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<ExplicitRestrictionDTO> 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);

View File

@ -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<Resource> 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

View File

@ -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;

View File

@ -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<ProcessorEntity, SnippetEntity> tuple = createSnippetWithRestrictedComponent();
final Tuple<ProcessorEntity, SnippetEntity> 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<ProcessorEntity, SnippetEntity> 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<ProcessorEntity, SnippetEntity> tuple = createSnippetWithRestrictedComponent();
final Tuple<ProcessorEntity, SnippetEntity> 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<ProcessorEntity, SnippetEntity> 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<ProcessorEntity, SnippetEntity> 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<ProcessorEntity, SnippetEntity> 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());

View File

@ -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<Relationship> getRelationships() {
final Set<Relationship> 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) {
}
}

View File

@ -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();
}

View File

@ -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() {

View File

@ -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
org.apache.nifi.integration.util.RestrictedProcessor
org.apache.nifi.integration.util.ExecuteCodeRestrictedProcessor

View File

@ -30,6 +30,7 @@
<div id="global-policy-controls" class="hidden policy-controls">
<div id="policy-type-list"></div>
<div id="controller-policy-target" class="hidden"></div>
<div id="restricted-component-required-permissions" class="hidden"></div>
<div class="clear"></div>
</div>
<div id="component-policy-controls" class="hidden policy-controls">
@ -90,6 +91,7 @@
</div>
<div id="policy-loading-container" class="loading-container"></div>
<div id="admin-policy-message" class="hidden">Only listing component specific administrators. Inherited administrators not shown.</div>
<div id="restriction-message" class="hidden">Only listing restriction specific users. Users with permission "regardless of restrictions" not shown but are also allowed.</div>
<div class="clear"></div>
</div>
</div>

View File

@ -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;

View File

@ -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 = $('<div></div>');
if (nfCommon.isBlank(item.usageRestriction)) {
restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text('Requires the following permissions:'));
} else {
restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').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);

View File

@ -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.
*

View File

@ -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 = $('<div></div>');
if (nfCommon.isBlank(item.usageRestriction)) {
restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text('Requires the following permissions:'));
} else {
restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').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);

View File

@ -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);
}

View File

@ -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 = $('<div></div>');
if (nfCommon.isBlank(item.usageRestriction)) {
restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text('Requires the following permissions:'));
} else {
restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').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);

View File

@ -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 += '<div class="view-usage-restriction fa fa-shield"></div><span class="hidden row-id">' + nfCommon.escapeHtml(dataContext.id) + '</span>';
} else {
markup += '<div class="fa"></div>';
@ -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.
*

View File

@ -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 '<span class="unset">' + nfCommon.escapeHtml(dataContext.id) + '</span>';
}
@ -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;
}

View File

@ -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",

View File

@ -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."),

View File

@ -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()

View File

@ -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";

View File

@ -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

View File

@ -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";

View File

@ -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

View File

@ -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()

View File

@ -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<Object> {
protected final AtomicReference<LookupService<Object>> lookupService = new AtomicReference<>();

View File

@ -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})

View File

@ -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> processor = new AtomicReference<>();

View File

@ -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<RecordReaderFactory> implements RecordReaderFactory {
@OnEnabled

View File

@ -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<RecordSetWriterFactory> implements RecordSetWriterFactory {
@Override

View File

@ -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();

View File

@ -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 {

View File

@ -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'";

View File

@ -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")

View File

@ -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()

View File

@ -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 <Move Destination Directory> property");

View File

@ -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()

View File

@ -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";

View File

@ -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 <File Location> 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.";