From 69586d8bd00b0e1e6cc606411363851b75684554 Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Tue, 26 Jul 2016 11:21:22 -0400 Subject: [PATCH] NIFI-2346: - Introducing data resource for authorizing provenance events and queue listing. - Authorizing entire proxy chain for data resource and data transfer resource. NIFI-2338: - Ensuring that replay authorization only happens once. - Allowing users with access to policies for a component to be able to access all policies for that component. -- Includes the component, data, data transfers, and policies. - Fixing drop request completion to update the correct queued field. - Fixing access control check for listing and emptying queues. - Reseting selected policy when re-opening the policy management page. - Fixing button/link visibility for available actions in policy management page. - Fixing policy issues with policy removal when the underlying component is deleted. - Updating file authorizer seeding to grant data access to node's in the cluster. This closes #720. --- .../authorization/resource/Authorizable.java | 8 + .../ProvenanceAuthorizableFactory.java | 7 +- .../nifi/authorization/FileAuthorizer.java | 9 + .../nifi/authorization/RoleAccessPolicy.java | 8 +- .../authorization/FileAuthorizerTest.java | 6 +- .../resource/AccessPolicyAuthorizable.java | 98 +++++++-- .../resource/DataAuthorizable.java | 125 +++++++++++ .../resource/DataTransferAuthorizable.java | 5 +- ...ePolicyPermissionsThroughBaseResource.java | 38 ++++ .../resource/ProvenanceEventAuthorizable.java | 41 ---- .../resource/ResourceFactory.java | 12 +- .../authorization/resource/ResourceType.java | 2 +- .../authorization/user/NiFiUserUtils.java | 26 +++ .../org/apache/nifi/controller/Template.java | 19 +- .../nifi/connectable/StandardConnection.java | 8 + .../nifi/controller/FlowController.java | 73 ++++--- .../authorization/AuthorizableLookup.java | 7 + .../StandardAuthorizableLookup.java | 17 +- .../nifi/web/StandardNiFiContentAccess.java | 12 +- .../nifi/web/StandardNiFiServiceFacade.java | 106 +++++----- .../nifi/web/api/AccessPolicyResource.java | 10 +- .../nifi/web/api/FlowFileQueueResource.java | 75 ++++--- .../nifi/web/controller/ControllerFacade.java | 115 +++-------- .../apache/nifi/web/dao/AccessPolicyDAO.java | 13 +- .../web/dao/impl/StandardConnectionDAO.java | 36 +--- .../StandardPolicyBasedAuthorizerDAO.java | 5 + .../web/security/ProxiedEntitiesUtils.java | 39 +--- .../nifi-web-ui/src/main/webapp/css/login.css | 2 +- .../main/webapp/js/nf/canvas/nf-actions.js | 21 +- .../webapp/js/nf/canvas/nf-context-menu.js | 8 - .../js/nf/canvas/nf-policy-management.js | 194 +++++++++++------- .../PersistentProvenanceRepository.java | 4 +- .../VolatileProvenanceRepository.java | 4 +- 33 files changed, 688 insertions(+), 465 deletions(-) create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/DataAuthorizable.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/EnforcePolicyPermissionsThroughBaseResource.java delete mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ProvenanceEventAuthorizable.java diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/resource/Authorizable.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/resource/Authorizable.java index 09829a9fcd..cc0d8fc3ff 100644 --- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/resource/Authorizable.java +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/resource/Authorizable.java @@ -69,6 +69,10 @@ public interface Authorizable { * @return is authorized */ default AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map resourceContext) { + if (user == null) { + return AuthorizationResult.denied("Unknown user"); + } + final Map userContext; if (user.getClientAddress() != null && !user.getClientAddress().trim().isEmpty()) { userContext = new HashMap<>(); @@ -128,6 +132,10 @@ public interface Authorizable { * @param resourceContext resource context */ default void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map resourceContext) throws AccessDeniedException { + if (user == null) { + throw new AccessDeniedException("Unknown user"); + } + final Map userContext; if (user.getClientAddress() != null && !user.getClientAddress().trim().isEmpty()) { userContext = new HashMap<>(); diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/provenance/ProvenanceAuthorizableFactory.java b/nifi-framework-api/src/main/java/org/apache/nifi/provenance/ProvenanceAuthorizableFactory.java index 23d6b3dfbe..96990414ec 100644 --- a/nifi-framework-api/src/main/java/org/apache/nifi/provenance/ProvenanceAuthorizableFactory.java +++ b/nifi-framework-api/src/main/java/org/apache/nifi/provenance/ProvenanceAuthorizableFactory.java @@ -23,13 +23,14 @@ import org.apache.nifi.web.ResourceNotFoundException; public interface ProvenanceAuthorizableFactory { /** - * Generates an Authorizable object for the Provenance events of the component with the given ID + * Generates an Authorizable object for the Data of the component with the given ID. This includes + * provenance data and queue's on outgoing relationships. * - * @param componentId the ID of the component to which the Provenance events belong + * @param componentId the ID of the component to which the Data belongs * * @return the Authorizable that can be use to authorize access to provenance events * @throws ResourceNotFoundException if no component can be found with the given ID */ - Authorizable createProvenanceAuthorizable(String componentId); + Authorizable createDataAuthorizable(String componentId); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java index f571c32baa..99672b6f17 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java @@ -276,6 +276,9 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer { // grant the user read access to the root process group resource if (rootGroupId != null) { + addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, adminUser.getIdentifier(), READ_CODE); + addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, adminUser.getIdentifier(), WRITE_CODE); + addAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, adminUser.getIdentifier(), READ_CODE); addAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, adminUser.getIdentifier(), WRITE_CODE); } @@ -322,6 +325,12 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer { // grant access to the proxy resource addAccessPolicy(authorizations, ResourceType.Proxy.getValue(), jaxbNodeUser.getIdentifier(), READ_CODE); addAccessPolicy(authorizations, ResourceType.Proxy.getValue(), jaxbNodeUser.getIdentifier(), WRITE_CODE); + + // grant the user read/write access data of the root group + if (rootGroupId != null) { + addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, jaxbNodeUser.getIdentifier(), READ_CODE); + addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, jaxbNodeUser.getIdentifier(), WRITE_CODE); + } } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/RoleAccessPolicy.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/RoleAccessPolicy.java index c8a0f53d7d..867423e008 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/RoleAccessPolicy.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/RoleAccessPolicy.java @@ -63,7 +63,7 @@ public final class RoleAccessPolicy { final Set provenancePolicies = new HashSet<>(); provenancePolicies.add(new RoleAccessPolicy(ResourceType.Provenance.getValue(), READ_ACTION)); if (rootGroupId != null) { - provenancePolicies.add(new RoleAccessPolicy(ResourceType.ProvenanceEvent.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, READ_ACTION)); + provenancePolicies.add(new RoleAccessPolicy(ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, READ_ACTION)); } roleAccessPolicies.put(Role.ROLE_PROVENANCE, Collections.unmodifiableSet(provenancePolicies)); @@ -75,6 +75,8 @@ public final class RoleAccessPolicy { if (rootGroupId != null) { dfmPolicies.add(new RoleAccessPolicy(ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, READ_ACTION)); dfmPolicies.add(new RoleAccessPolicy(ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, WRITE_ACTION)); + dfmPolicies.add(new RoleAccessPolicy(ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, READ_ACTION)); + dfmPolicies.add(new RoleAccessPolicy(ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, WRITE_ACTION)); } roleAccessPolicies.put(Role.ROLE_DFM, Collections.unmodifiableSet(dfmPolicies)); @@ -93,6 +95,10 @@ public final class RoleAccessPolicy { final Set proxyPolicies = new HashSet<>(); proxyPolicies.add(new RoleAccessPolicy(ResourceType.Proxy.getValue(), READ_ACTION)); proxyPolicies.add(new RoleAccessPolicy(ResourceType.Proxy.getValue(), WRITE_ACTION)); + if (rootGroupId != null) { + proxyPolicies.add(new RoleAccessPolicy(ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, READ_ACTION)); + proxyPolicies.add(new RoleAccessPolicy(ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, WRITE_ACTION)); + } roleAccessPolicies.put(Role.ROLE_PROXY, Collections.unmodifiableSet(proxyPolicies)); final Set nifiPolicies = new HashSet<>(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java index d1cf0c4cac..e5d6a54859 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java @@ -254,7 +254,7 @@ public class FileAuthorizerTest { // verify user3's policies final Map> user3Policies = getResourceActions(policies, user3); - assertEquals(4, user3Policies.size()); + assertEquals(5, user3Policies.size()); assertTrue(user3Policies.containsKey(ResourceType.Flow.getValue())); assertEquals(1, user3Policies.get(ResourceType.Flow.getValue()).size()); @@ -286,7 +286,7 @@ public class FileAuthorizerTest { // verify user5's policies final Map> user5Policies = getResourceActions(policies, user5); - assertEquals(1, user5Policies.size()); + assertEquals(2, user5Policies.size()); assertTrue(user5Policies.containsKey(ResourceType.Proxy.getValue())); assertEquals(2, user5Policies.get(ResourceType.Proxy.getValue()).size()); @@ -384,7 +384,7 @@ public class FileAuthorizerTest { assertEquals(adminIdentity, adminUser.getIdentity()); final Set policies = authorizer.getAccessPolicies(); - assertEquals(9, policies.size()); + assertEquals(11, policies.size()); final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/AccessPolicyAuthorizable.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/AccessPolicyAuthorizable.java index 08583e19b6..041b982e6c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/AccessPolicyAuthorizable.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/AccessPolicyAuthorizable.java @@ -16,12 +16,33 @@ */ package org.apache.nifi.authorization.resource; +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 java.util.Map; /** * Authorizable for policies of an Authorizable. */ -public class AccessPolicyAuthorizable implements Authorizable { +public class AccessPolicyAuthorizable implements Authorizable, EnforcePolicyPermissionsThroughBaseResource { + + private static final Authorizable POLICIES_AUTHORIZABLE = new Authorizable() { + @Override + public Authorizable getParentAuthorizable() { + return null; + } + + @Override + public Resource getResource() { + return ResourceFactory.getPoliciesResource(); + } + }; + final Authorizable authorizable; public AccessPolicyAuthorizable(Authorizable authorizable) { @@ -29,26 +50,73 @@ public class AccessPolicyAuthorizable implements Authorizable { } @Override - public Authorizable getParentAuthorizable() { - if (authorizable.getParentAuthorizable() == null) { - return new Authorizable() { - @Override - public Authorizable getParentAuthorizable() { - return null; - } + public Authorizable getBaseAuthorizable() { + return authorizable; + } - @Override - public Resource getResource() { - return ResourceFactory.getPoliciesResource(); - } - }; + @Override + public Authorizable getParentAuthorizable() { + final Authorizable effectiveAuthorizable = getEffectiveAuthorizable(); + if (effectiveAuthorizable.getParentAuthorizable() == null) { + return POLICIES_AUTHORIZABLE; } else { - return new AccessPolicyAuthorizable(authorizable.getParentAuthorizable()); + return new AccessPolicyAuthorizable(effectiveAuthorizable.getParentAuthorizable()); } } @Override public Resource getResource() { - return ResourceFactory.getPolicyResource(authorizable.getResource()); + return ResourceFactory.getPolicyResource(getEffectiveAuthorizable().getResource()); + } + + private Authorizable getEffectiveAuthorizable() { + // possibly consider the base resource if the authorizable uses it to enforce policy permissions + if (authorizable instanceof EnforcePolicyPermissionsThroughBaseResource) { + final Authorizable baseAuthorizable = ((EnforcePolicyPermissionsThroughBaseResource) authorizable).getBaseAuthorizable(); + + // if the base authorizable is for a policy, we don't want to use the base otherwise it would keep unwinding and would eventually + // evaluate to the policy of the component and not the policy of the policies for the component + if (baseAuthorizable instanceof AccessPolicyAuthorizable) { + return authorizable; + } else { + return baseAuthorizable; + } + } else { + return authorizable; + } + } + + @Override + public AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map resourceContext) { + if (user == null) { + throw new AccessDeniedException("Unknown user"); + } + + final AuthorizationResult resourceResult = Authorizable.super.checkAuthorization(authorizer, action, user, resourceContext); + + // if we're denied from the resource try inheriting + if (Result.Denied.equals(resourceResult.getResult())) { + return getParentAuthorizable().checkAuthorization(authorizer, action, user, resourceContext); + } else { + return resourceResult; + } + } + + @Override + public void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map resourceContext) throws AccessDeniedException { + if (user == null) { + throw new AccessDeniedException("Unknown user"); + } + + try { + Authorizable.super.authorize(authorizer, action, user, resourceContext); + } catch (final AccessDeniedException resourceDenied) { + // if we're denied from the resource try inheriting + try { + getParentAuthorizable().authorize(authorizer, action, user, resourceContext); + } catch (final AccessDeniedException policiesDenied) { + throw resourceDenied; + } + } } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/DataAuthorizable.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/DataAuthorizable.java new file mode 100644 index 0000000000..cb0d0f1dd4 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/DataAuthorizable.java @@ -0,0 +1,125 @@ +/* + * 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.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.authorization.user.NiFiUserUtils; +import org.apache.nifi.authorization.user.StandardNiFiUser; +import org.apache.nifi.web.ResourceNotFoundException; + +import java.util.List; +import java.util.Map; + +/** + * Authorizable for authorizing access to data. Data based authorizable requires authorization for the entire DN chain. + */ +public class DataAuthorizable implements Authorizable, EnforcePolicyPermissionsThroughBaseResource { + final Authorizable authorizable; + + public DataAuthorizable(final Authorizable authorizable) { + this.authorizable = authorizable; + } + + @Override + public Authorizable getBaseAuthorizable() { + return authorizable; + } + + @Override + public Authorizable getParentAuthorizable() { + if (authorizable.getParentAuthorizable() == null) { + return null; + } else { + return new DataAuthorizable(authorizable.getParentAuthorizable()); + } + } + + @Override + public Resource getResource() { + return ResourceFactory.getDataResource(authorizable.getResource()); + } + + @Override + public AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map resourceContext) { + if (user == null) { + return AuthorizationResult.denied("Unknown user"); + } + + AuthorizationResult result = null; + + // calculate the dn chain + final List dnChain = NiFiUserUtils.buildProxiedEntitiesChain(user); + for (final String identity : dnChain) { + try { + final String clientAddress = user.getIdentity().equals(identity) ? user.getClientAddress() : null; + final NiFiUser chainUser = new StandardNiFiUser(identity, clientAddress) { + @Override + public boolean isAnonymous() { + // allow current user to drive anonymous flag as anonymous users are never chained... supports single user case + return user.isAnonymous(); + } + }; + + result = Authorizable.super.checkAuthorization(authorizer, action, chainUser, resourceContext); + } catch (final ResourceNotFoundException e) { + result = AuthorizationResult.denied("Unknown source component."); + } + + if (!Result.Approved.equals(result.getResult())) { + break; + } + } + + if (result == null) { + result = AuthorizationResult.denied(); + } + + return result; + } + + @Override + public void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map resourceContext) throws AccessDeniedException { + if (user == null) { + throw new AccessDeniedException("Unknown user"); + } + + // calculate the dn chain + final List dnChain = NiFiUserUtils.buildProxiedEntitiesChain(user); + for (final String identity : dnChain) { + try { + final String clientAddress = user.getIdentity().equals(identity) ? user.getClientAddress() : null; + final NiFiUser chainUser = new StandardNiFiUser(identity, clientAddress) { + @Override + public boolean isAnonymous() { + // allow current user to drive anonymous flag as anonymous users are never chained... supports single user case + return user.isAnonymous(); + } + }; + + Authorizable.super.authorize(authorizer, action, chainUser, resourceContext); + } catch (final ResourceNotFoundException e) { + throw new AccessDeniedException("Unknown source component."); + } + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/DataTransferAuthorizable.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/DataTransferAuthorizable.java index 53aecbcd8b..21df4f8c45 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/DataTransferAuthorizable.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/DataTransferAuthorizable.java @@ -19,12 +19,13 @@ package org.apache.nifi.authorization.resource; import org.apache.nifi.authorization.Resource; /** - * Authorizable for policies of an Authorizable. + * Authorizable for authorizing data transfers. */ -public class DataTransferAuthorizable implements Authorizable { +public class DataTransferAuthorizable extends DataAuthorizable implements EnforcePolicyPermissionsThroughBaseResource { final Authorizable authorizable; public DataTransferAuthorizable(Authorizable authorizable) { + super(authorizable); this.authorizable = authorizable; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/EnforcePolicyPermissionsThroughBaseResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/EnforcePolicyPermissionsThroughBaseResource.java new file mode 100644 index 0000000000..115315f5dd --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/EnforcePolicyPermissionsThroughBaseResource.java @@ -0,0 +1,38 @@ +/* + * 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; + +/** + * Defers permissions on policies to the policies of the base authorizable. Required because we don't + * want to change the enforcement of the policies on the authorizable. For example... + * + * if a user has permissions to /policies/input-ports/1234 then they have permissions to the following + * + * - the policy for /input-ports/1234 -> /policies/input-ports/1234 + * - the policy for /data/input-ports/1234 -> /policies/data/input-ports/1234 + * - the policy for /data-transfers/input-ports/1234 -> /policies/data-transfers/input-ports/1234 + * - the policy for /policies/input-ports/1234 -> /policies/policies/input-ports/1234 + */ +public interface EnforcePolicyPermissionsThroughBaseResource { + + /** + * Returns the base authorizable. Cannot be null. + * + * @return base authorizable + */ + Authorizable getBaseAuthorizable(); +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ProvenanceEventAuthorizable.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ProvenanceEventAuthorizable.java deleted file mode 100644 index 153047edb8..0000000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ProvenanceEventAuthorizable.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.authorization.Resource; - -public class ProvenanceEventAuthorizable implements Authorizable { - final Authorizable authorizable; - - public ProvenanceEventAuthorizable(final Authorizable authorizable) { - this.authorizable = authorizable; - } - - @Override - public Authorizable getParentAuthorizable() { - if (authorizable.getParentAuthorizable() == null) { - return null; - } else { - return new ProvenanceEventAuthorizable(authorizable.getParentAuthorizable()); - } - } - - @Override - public Resource getResource() { - return ResourceFactory.getProvenanceEventResource(authorizable.getResource()); - } -} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java index baa58ac57e..426e7fd5aa 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java @@ -166,15 +166,15 @@ public final class ResourceFactory { } }; - private final static Resource PROVENANCE_EVENT_RESOURCE = new Resource() { + private final static Resource DATA_RESOURCE = new Resource() { @Override public String getIdentifier() { - return ResourceType.ProvenanceEvent.getValue(); + return ResourceType.Data.getValue(); } @Override public String getName() { - return "Provenance Event"; + return "Data"; } }; @@ -567,16 +567,16 @@ public final class ResourceFactory { * @param resource The resource for the component being accessed * @return The resource for the provenance of the component being accessed */ - public static Resource getProvenanceEventResource(final Resource resource) { + public static Resource getDataResource(final Resource resource) { return new Resource() { @Override public String getIdentifier() { - return String.format("%s%s", PROVENANCE_EVENT_RESOURCE.getIdentifier(), resource.getIdentifier()); + return String.format("%s%s", DATA_RESOURCE.getIdentifier(), resource.getIdentifier()); } @Override public String getName() { - return "Provenance Events for " + resource.getName(); + return "Data for " + resource.getName(); } }; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java index bce270ad68..37fe018d6b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java @@ -29,7 +29,7 @@ public enum ResourceType { Processor("/processors"), ProcessGroup("/process-groups"), Provenance("/provenance"), - ProvenanceEvent("/provenance-events"), + Data("/data"), Proxy("/proxy"), RemoteProcessGroup("/remote-process-groups"), ReportingTask("/reporting-tasks"), diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/NiFiUserUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/NiFiUserUtils.java index 4a752748e4..6a4776abaf 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/NiFiUserUtils.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/NiFiUserUtils.java @@ -20,6 +20,9 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import java.util.ArrayList; +import java.util.List; + /** * Utility methods for retrieving information about the current application user. * @@ -56,4 +59,27 @@ public final class NiFiUserUtils { return user.getIdentity(); } } + + /** + * Builds the proxy chain for the specified user. + * + * @param user The current user + * @return The proxy chain for that user in List form + */ + public static List buildProxiedEntitiesChain(final NiFiUser user) { + // calculate the dn chain + final List proxyChain = new ArrayList<>(); + + // build the dn chain + NiFiUser chainedUser = user; + do { + // add the entry for this user + proxyChain.add(chainedUser.getIdentity()); + + // go to the next user in the chain + chainedUser = chainedUser.getChain(); + } while (chainedUser != null); + + return proxyChain; + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/Template.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/Template.java index ecc9e6fc26..86a9f38e9f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/Template.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/Template.java @@ -158,20 +158,26 @@ public class Template implements Authorizable { } @Override - public void authorize(final Authorizer authorizer, final RequestAction action, final NiFiUser user) throws AccessDeniedException { - final AuthorizationResult result = checkAuthorization(authorizer, action, true, user); - if (Result.Denied.equals(result)) { + public void authorize(final Authorizer authorizer, final RequestAction action, final NiFiUser user, final Map resourceContext) throws AccessDeniedException { + final AuthorizationResult result = checkAuthorization(authorizer, action, true, user, resourceContext); + if (Result.Denied.equals(result.getResult())) { final String explanation = result.getExplanation() == null ? "Access is denied" : result.getExplanation(); throw new AccessDeniedException(explanation); } } @Override - public AuthorizationResult checkAuthorization(final Authorizer authorizer, final RequestAction action, final NiFiUser user) { - return checkAuthorization(authorizer, action, false, user); + public AuthorizationResult checkAuthorization(final Authorizer authorizer, final RequestAction action, final NiFiUser user, final Map resourceContext) { + return checkAuthorization(authorizer, action, false, user, resourceContext); } - private AuthorizationResult checkAuthorization(final Authorizer authorizer, final RequestAction action, final boolean accessAttempt, final NiFiUser user) { + private AuthorizationResult checkAuthorization(final Authorizer authorizer, final RequestAction action, final boolean accessAttempt, + final NiFiUser user, final Map resourceContext) { + + if (user == null) { + return AuthorizationResult.denied("Unknown user"); + } + final Map userContext; if (!StringUtils.isBlank(user.getClientAddress())) { userContext = new HashMap<>(); @@ -188,6 +194,7 @@ public class Template implements Authorizable { .action(action) .resource(getResource()) .userContext(userContext) + .resourceContext(resourceContext) .build(); // perform the authorization diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/connectable/StandardConnection.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/connectable/StandardConnection.java index 3745d7d97e..f77288a7a8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/connectable/StandardConnection.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/connectable/StandardConnection.java @@ -139,6 +139,10 @@ public final class StandardConnection implements Connection { @Override public AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map resourceContext) { + if (user == null) { + return AuthorizationResult.denied("Unknown user"); + } + // check the source final AuthorizationResult sourceResult = getSource().checkAuthorization(authorizer, action, user, resourceContext); if (Result.Denied.equals(sourceResult.getResult())) { @@ -151,6 +155,10 @@ public final class StandardConnection implements Connection { @Override public void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map resourceContext) throws AccessDeniedException { + if (user == null) { + throw new AccessDeniedException("Unknown user"); + } + getSource().authorize(authorizer, action, user, resourceContext); getDestination().authorize(authorizer, action, user, resourceContext); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java index 30e382d378..5de77f6006 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java @@ -18,38 +18,6 @@ package org.apache.nifi.controller; import com.sun.jersey.api.client.ClientHandlerException; import org.apache.commons.collections4.Predicate; -import static java.util.Objects.requireNonNull; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import javax.net.ssl.SSLContext; - import org.apache.commons.lang3.StringUtils; import org.apache.nifi.action.Action; import org.apache.nifi.admin.service.AuditService; @@ -63,7 +31,7 @@ import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.Resource; import org.apache.nifi.authorization.resource.Authorizable; -import org.apache.nifi.authorization.resource.ProvenanceEventAuthorizable; +import org.apache.nifi.authorization.resource.DataAuthorizable; import org.apache.nifi.authorization.resource.ResourceFactory; import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.cluster.HeartbeatPayload; @@ -238,6 +206,37 @@ import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.net.ssl.SSLContext; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import static java.util.Objects.requireNonNull; + public class FlowController implements EventAccess, ControllerServiceProvider, ReportingTaskProvider, QueueProvider, Authorizable, ProvenanceAuthorizableFactory, NodeTypeProvider { @@ -3902,15 +3901,15 @@ public class FlowController implements EventAccess, ControllerServiceProvider, R } @Override - public Authorizable createProvenanceAuthorizable(final String componentId) { + public Authorizable createDataAuthorizable(final String componentId) { final String rootGroupId = getRootGroupId(); // Provenance Events are generated only by connectable components, with the exception of DOWNLOAD events, // which have the root process group's identifier assigned as the component ID. So, we check if the component ID // is set to the root group and otherwise assume that the ID is that of a component. - final ProvenanceEventAuthorizable authorizable; + final DataAuthorizable authorizable; if (rootGroupId.equals(componentId)) { - authorizable = new ProvenanceEventAuthorizable(rootGroup); + authorizable = new DataAuthorizable(rootGroup); } else { final Connectable connectable = rootGroup.findConnectable(componentId); @@ -3918,7 +3917,7 @@ public class FlowController implements EventAccess, ControllerServiceProvider, R throw new ResourceNotFoundException("The component that generated this event is no longer part of the data flow."); } - authorizable = new ProvenanceEventAuthorizable(connectable); + authorizable = new DataAuthorizable(connectable); } return authorizable; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java index e0faafbb49..bc958c356f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java @@ -179,6 +179,13 @@ public interface AuthorizableLookup { */ Authorizable getTenant(); + /** + * Get the authorizable for data of a specified component. + * + * @return authorizable + */ + Authorizable getData(String id); + /** * Get the authorizable for access all policies. * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java index c0805c06cc..1e3c03ac0f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java @@ -20,7 +20,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.nifi.authorization.resource.AccessPolicyAuthorizable; import org.apache.nifi.authorization.resource.Authorizable; import org.apache.nifi.authorization.resource.DataTransferAuthorizable; -import org.apache.nifi.authorization.resource.ProvenanceEventAuthorizable; +import org.apache.nifi.authorization.resource.DataAuthorizable; import org.apache.nifi.authorization.resource.ResourceFactory; import org.apache.nifi.authorization.resource.ResourceType; import org.apache.nifi.authorization.resource.TenantAuthorizable; @@ -233,6 +233,11 @@ class StandardAuthorizableLookup implements AuthorizableLookup { return TENANT_AUTHORIZABLE; } + @Override + public Authorizable getData(final String id) { + return controllerFacade.getDataAuthorizable(id); + } + @Override public Authorizable getPolicies() { return POLICIES_AUTHORIZABLE; @@ -269,7 +274,7 @@ class StandardAuthorizableLookup implements AuthorizableLookup { } // if this is a policy or a provenance event resource, there should be another resource type - if (ResourceType.Policy.equals(resourceType) || ResourceType.ProvenanceEvent.equals(resourceType) || ResourceType.DataTransfer.equals(resourceType)) { + if (ResourceType.Policy.equals(resourceType) || ResourceType.Data.equals(resourceType) || ResourceType.DataTransfer.equals(resourceType)) { final ResourceType primaryResourceType = resourceType; // get the resource type @@ -288,8 +293,8 @@ class StandardAuthorizableLookup implements AuthorizableLookup { // must either be a policy, event, or data transfer if (ResourceType.Policy.equals(primaryResourceType)) { return new AccessPolicyAuthorizable(getAccessPolicy(resourceType, resource)); - } else if (ResourceType.ProvenanceEvent.equals(primaryResourceType)) { - return new ProvenanceEventAuthorizable(getAccessPolicy(resourceType, resource)); + } else if (ResourceType.Data.equals(primaryResourceType)) { + return new DataAuthorizable(getAccessPolicy(resourceType, resource)); } else { return new DataTransferAuthorizable(getAccessPolicy(resourceType, resource)); } @@ -340,8 +345,8 @@ class StandardAuthorizableLookup implements AuthorizableLookup { case Template: authorizable = getTemplate(componentId); break; - case ProvenanceEvent: - authorizable = controllerFacade.getProvenanceEventAuthorizable(componentId); + case Data: + authorizable = controllerFacade.getDataAuthorizable(componentId); break; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java index 1786bceaba..e17b1474c5 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java @@ -164,20 +164,12 @@ public class StandardNiFiContentAccess implements ContentAccess { } private DownloadableContent getFlowFileContent(final String connectionId, final String flowfileId, final String dataUri) { - // TODO - ensure the user is authorized - not checking with @PreAuthorized annotation as aspect not trigger on call within a class -// if (!NiFiUserUtils.getAuthorities().contains(Authority.ROLE_DFM.toString())) { -// throw new AccessDeniedException("Access is denied."); -// } - + // user authorization is handled once we have the actual content so we can utilize the flow file attributes in the resource context return serviceFacade.getContent(connectionId, flowfileId, dataUri); } private DownloadableContent getProvenanceEventContent(final Long eventId, final String dataUri, final ContentDirection direction) { - // TODO - ensure the user is authorized - not checking with @PreAuthorized annotation as aspect not trigger on call within a class -// if (!NiFiUserUtils.getAuthorities().contains(Authority.ROLE_PROVENANCE.toString())) { -// throw new AccessDeniedException("Access is denied."); -// } - + // user authorization is handled once we have the actual prov event so we can utilize the event attributes in the resource context return serviceFacade.getContent(eventId, dataUri, direction); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java index 6186bf1ef2..b2b93db677 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java @@ -37,6 +37,7 @@ import org.apache.nifi.authorization.Resource; import org.apache.nifi.authorization.User; import org.apache.nifi.authorization.resource.Authorizable; import org.apache.nifi.authorization.resource.DataTransferAuthorizable; +import org.apache.nifi.authorization.resource.ResourceFactory; import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.authorization.user.NiFiUserUtils; import org.apache.nifi.cluster.coordination.ClusterCoordinator; @@ -850,8 +851,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final Connection connection = connectionDAO.getConnection(connectionId); final ConnectionDTO snapshot = deleteComponent( revision, - connection, + connection.getResource().getIdentifier(), () -> connectionDAO.deleteConnection(connectionId), + false, dtoFactory.createConnectionDto(connection)); return entityFactory.createConnectionEntity(snapshot, null, null, null); @@ -883,8 +885,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final ProcessorNode processor = processorDAO.getProcessor(processorId); final ProcessorDTO snapshot = deleteComponent( revision, - processor, + processor.getResource().getIdentifier(), () -> processorDAO.deleteProcessor(processorId), + true, dtoFactory.createProcessorDto(processor)); return entityFactory.createProcessorEntity(snapshot, null, null, null, null); @@ -895,8 +898,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final Label label = labelDAO.getLabel(labelId); final LabelDTO snapshot = deleteComponent( revision, - label, + label.getResource().getIdentifier(), () -> labelDAO.deleteLabel(labelId), + true, dtoFactory.createLabelDto(label)); return entityFactory.createLabelEntity(snapshot, null, null); @@ -910,21 +914,13 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final Set policyEntities = user != null ? userGroupDAO.getAccessPoliciesForUser(userId).stream() .map(ap -> createAccessPolicySummaryEntity(ap)).collect(Collectors.toSet()) : null; - final RevisionClaim claim = new StandardRevisionClaim(revision); - final NiFiUser nifiUser = NiFiUserUtils.getNiFiUser(); - - // perform the deletion - final UserDTO snapshot = revisionManager.deleteRevision(claim, nifiUser, () -> { - logger.debug("Attempting to delete component {} with claim {}", user, claim); - - userDAO.deleteUser(userId); - - // save the flow - controllerFacade.save(); - logger.debug("Deletion of component {} was successful", user); - - return dtoFactory.createUserDto(user, userGroups, policyEntities); - }); + final String resourceIdentifier = ResourceFactory.getTenantResource().getIdentifier() + "/" + userId; + final UserDTO snapshot = deleteComponent( + revision, + resourceIdentifier, + () -> userDAO.deleteUser(userId), + false, // no user specific policies to remove + dtoFactory.createUserDto(user, userGroups, policyEntities)); return entityFactory.createUserEntity(snapshot, null, null); } @@ -936,21 +932,13 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { .map(mapUserIdToTenantEntity()).collect(Collectors.toSet()) : null; - final RevisionClaim claim = new StandardRevisionClaim(revision); - final NiFiUser nifiUser = NiFiUserUtils.getNiFiUser(); - - // perform the deletion - final UserGroupDTO snapshot = revisionManager.deleteRevision(claim, nifiUser, () -> { - logger.debug("Attempting to delete component {} with claim {}", userGroup, claim); - - userGroupDAO.deleteUserGroup(userGroupId); - - // save the flow - controllerFacade.save(); - logger.debug("Deletion of component {} was successful", userGroup); - - return dtoFactory.createUserGroupDto(userGroup, users); - }); + final String resourceIdentifier = ResourceFactory.getTenantResource().getIdentifier() + "/" + userGroupId; + final UserGroupDTO snapshot = deleteComponent( + revision, + resourceIdentifier, + () -> userGroupDAO.deleteUserGroup(userGroupId), + false, // no user group specific policies to remove + dtoFactory.createUserGroupDto(userGroup, users)); return entityFactory.createUserGroupEntity(snapshot, null, null); } @@ -962,8 +950,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final Set users = accessPolicy != null ? accessPolicy.getUsers().stream().map(mapUserIdToTenantEntity()).collect(Collectors.toSet()) : null; final AccessPolicyDTO snapshot = deleteComponent( revision, - authorizableLookup.getAccessPolicyById(accessPolicyId), + accessPolicy.getResource(), () -> accessPolicyDAO.deleteAccessPolicy(accessPolicyId), + false, // no need to clean up any policies as it's already been removed above dtoFactory.createAccessPolicyDto(accessPolicy, userGroups, users)); @@ -975,8 +964,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final Funnel funnel = funnelDAO.getFunnel(funnelId); final FunnelDTO snapshot = deleteComponent( revision, - funnel, + funnel.getResource().getIdentifier(), () -> funnelDAO.deleteFunnel(funnelId), + true, dtoFactory.createFunnelDto(funnel)); return entityFactory.createFunnelEntity(snapshot, null, null); @@ -986,47 +976,49 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { * Deletes a component using the Optimistic Locking Manager * * @param revision the current revision + * @param resourceIdentifier the identifier of the resource being removed * @param deleteAction the action that deletes the component via the appropriate DAO object + * @param cleanUpPolicies whether or not the policies for this resource should be removed as well - not necessary when there are + * no component specific policies or if the policies of the component are inherited * @return a dto that represents the new configuration */ - private D deleteComponent(final Revision revision, final Authorizable authorizable, final Runnable deleteAction, final D dto) { + private D deleteComponent(final Revision revision, final String resourceIdentifier, final Runnable deleteAction, final boolean cleanUpPolicies, final D dto) { final RevisionClaim claim = new StandardRevisionClaim(revision); final NiFiUser user = NiFiUserUtils.getNiFiUser(); return revisionManager.deleteRevision(claim, user, new DeleteRevisionTask() { @Override public D performTask() { - logger.debug("Attempting to delete component {} with claim {}", authorizable, claim); + logger.debug("Attempting to delete component {} with claim {}", resourceIdentifier, claim); + // run the delete action deleteAction.run(); // save the flow controllerFacade.save(); - logger.debug("Deletion of component {} was successful", authorizable); + logger.debug("Deletion of component {} was successful", resourceIdentifier); - // if configured with a policy based authorizer, attempt to remove the corresponding policies - if (authorizer instanceof AbstractPolicyBasedAuthorizer) { + // clean up the policy if necessary and configured with a policy based authorizer + if (cleanUpPolicies && authorizer instanceof AbstractPolicyBasedAuthorizer) { try { // since the component is being deleted, also delete any relevant read access policies - final AccessPolicy readPolicy = accessPolicyDAO.getAccessPolicy(RequestAction.READ, authorizable); - if (authorizable.getResource().getIdentifier().equals(readPolicy.getResource())) { + final AccessPolicy readPolicy = accessPolicyDAO.getAccessPolicy(RequestAction.READ, resourceIdentifier); + if (readPolicy != null) { accessPolicyDAO.deleteAccessPolicy(readPolicy.getIdentifier()); } - } catch (final ResourceNotFoundException e) { - // no policy exists for this component... no worries } catch (final Exception e) { - logger.warn(String.format("Unable to remove access policy for %s %s after component removal.", RequestAction.READ, authorizable.getResource().getIdentifier()), e); + logger.warn(String.format("Unable to remove access policy for %s %s after component removal.", RequestAction.READ, resourceIdentifier), e); } try { // since the component is being deleted, also delete any relevant write access policies - final AccessPolicy writePolicy = accessPolicyDAO.getAccessPolicy(RequestAction.WRITE, authorizable); - if (authorizable.getResource().getIdentifier().equals(writePolicy.getResource())) { + final AccessPolicy writePolicy = accessPolicyDAO.getAccessPolicy(RequestAction.WRITE, resourceIdentifier); + if (writePolicy != null) { accessPolicyDAO.deleteAccessPolicy(writePolicy.getIdentifier()); } } catch (final ResourceNotFoundException e) { // no policy exists for this component... no worries } catch (final Exception e) { - logger.warn(String.format("Unable to remove access policy for %s %s after component removal.", RequestAction.WRITE, authorizable.getResource().getIdentifier()), e); + logger.warn(String.format("Unable to remove access policy for %s %s after component removal.", RequestAction.WRITE, resourceIdentifier), e); } } @@ -1071,8 +1063,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final Port port = inputPortDAO.getPort(inputPortId); final PortDTO snapshot = deleteComponent( revision, - port, + port.getResource().getIdentifier(), () -> inputPortDAO.deletePort(inputPortId), + true, dtoFactory.createPortDto(port)); return entityFactory.createPortEntity(snapshot, null, null, null, null); @@ -1083,8 +1076,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final Port port = outputPortDAO.getPort(outputPortId); final PortDTO snapshot = deleteComponent( revision, - port, + port.getResource().getIdentifier(), () -> outputPortDAO.deletePort(outputPortId), + true, dtoFactory.createPortDto(port)); return entityFactory.createPortEntity(snapshot, null, null, null, null); @@ -1095,8 +1089,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId); final ProcessGroupDTO snapshot = deleteComponent( revision, - processGroup, + processGroup.getResource().getIdentifier(), () -> processGroupDAO.deleteProcessGroup(groupId), + true, dtoFactory.createProcessGroupDto(processGroup)); return entityFactory.createProcessGroupEntity(snapshot, null, null, null, null); @@ -1107,8 +1102,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final RemoteProcessGroup remoteProcessGroup = remoteProcessGroupDAO.getRemoteProcessGroup(remoteProcessGroupId); final RemoteProcessGroupDTO snapshot = deleteComponent( revision, - remoteProcessGroup, + remoteProcessGroup.getResource().getIdentifier(), () -> remoteProcessGroupDAO.deleteRemoteProcessGroup(remoteProcessGroupId), + true, dtoFactory.createRemoteProcessGroupDto(remoteProcessGroup)); return entityFactory.createRemoteProcessGroupEntity(snapshot, null, null, null, null); @@ -1740,8 +1736,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(controllerServiceId); final ControllerServiceDTO snapshot = deleteComponent( revision, - controllerService, + controllerService.getResource().getIdentifier(), () -> controllerServiceDAO.deleteControllerService(controllerServiceId), + true, dtoFactory.createControllerServiceDto(controllerService)); return entityFactory.createControllerServiceEntity(snapshot, null, null, null); @@ -1793,8 +1790,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final ReportingTaskNode reportingTask = reportingTaskDAO.getReportingTask(reportingTaskId); final ReportingTaskDTO snapshot = deleteComponent( revision, - reportingTask, + reportingTask.getResource().getIdentifier(), () -> reportingTaskDAO.deleteReportingTask(reportingTaskId), + true, dtoFactory.createReportingTaskDto(reportingTask)); return entityFactory.createReportingTaskEntity(snapshot, null, null, null); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessPolicyResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessPolicyResource.java index c77ed659e9..8a03dbf456 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessPolicyResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessPolicyResource.java @@ -83,9 +83,7 @@ public class AccessPolicyResource extends ApplicationResource { * @return accessPolicyEntity */ public AccessPolicyEntity populateRemainingAccessPolicyEntityContent(AccessPolicyEntity accessPolicyEntity) { - if (accessPolicyEntity.getComponent() != null) { - accessPolicyEntity.setUri(generateResourceUri("policies", accessPolicyEntity.getId())); - } + accessPolicyEntity.setUri(generateResourceUri("policies", accessPolicyEntity.getId())); return accessPolicyEntity; } @@ -229,7 +227,7 @@ public class AccessPolicyResource extends ApplicationResource { } // set the access policy id as appropriate - accessPolicyEntity.getComponent().setId(generateUuid()); + requestAccessPolicy.setId(generateUuid()); // get revision from the config final RevisionDTO revisionDTO = accessPolicyEntity.getRevision(); @@ -286,7 +284,7 @@ public class AccessPolicyResource extends ApplicationResource { // authorize access serviceFacade.authorizeAccess(lookup -> { Authorizable authorizable = lookup.getAccessPolicyById(id); - authorizable.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); + authorizable.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); }); // get the access policy @@ -438,7 +436,7 @@ public class AccessPolicyResource extends ApplicationResource { revision, lookup -> { final Authorizable accessPolicy = lookup.getAccessPolicyById(id); - accessPolicy.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); + accessPolicy.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); }, () -> { }, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowFileQueueResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowFileQueueResource.java index cea97da9c5..e5c10ba593 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowFileQueueResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowFileQueueResource.java @@ -24,6 +24,7 @@ import com.wordnik.swagger.annotations.ApiResponses; import com.wordnik.swagger.annotations.Authorization; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.authorization.Authorizer; +import org.apache.nifi.authorization.ConnectionAuthorizable; import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.resource.Authorizable; import org.apache.nifi.authorization.user.NiFiUserUtils; @@ -120,7 +121,7 @@ public class FlowFileQueueResource extends ApplicationResource { @GET @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) - @Path("{connection-id}/flowfiles/{flowfile-uuid}") + @Path("{id}/flowfiles/{flowfile-uuid}") // TODO - @PreAuthorize("hasRole('ROLE_DFM')") @ApiOperation( value = "Gets a FlowFile from a Connection.", @@ -142,7 +143,7 @@ public class FlowFileQueueResource extends ApplicationResource { value = "The connection id.", required = true ) - @PathParam("connection-id") final String connectionId, + @PathParam("id") final String connectionId, @ApiParam( value = "The flowfile uuid.", required = true @@ -170,11 +171,7 @@ public class FlowFileQueueResource extends ApplicationResource { } } - // authorize access - serviceFacade.authorizeAccess(lookup -> { - final Authorizable connection = lookup.getConnection(connectionId).getAuthorizable(); - connection.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); - }); + // NOTE - deferred authorization so we can consider flowfile attributes in the access decision // get the flowfile final FlowFileDTO flowfileDto = serviceFacade.getFlowFile(connectionId, flowFileUuid); @@ -200,7 +197,7 @@ public class FlowFileQueueResource extends ApplicationResource { @GET @Consumes(MediaType.WILDCARD) @Produces(MediaType.WILDCARD) - @Path("{connection-id}/flowfiles/{flowfile-uuid}/content") + @Path("{id}/flowfiles/{flowfile-uuid}/content") // TODO - @PreAuthorize("hasRole('ROLE_DFM')") @ApiOperation( value = "Gets the content for a FlowFile in a Connection.", @@ -227,7 +224,7 @@ public class FlowFileQueueResource extends ApplicationResource { value = "The connection id.", required = true ) - @PathParam("connection-id") final String connectionId, + @PathParam("id") final String connectionId, @ApiParam( value = "The flowfile uuid.", required = true @@ -255,11 +252,7 @@ public class FlowFileQueueResource extends ApplicationResource { } } - // authorize access - serviceFacade.authorizeAccess(lookup -> { - final Authorizable connection = lookup.getConnection(connectionId).getAuthorizable(); - connection.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); - }); + // NOTE - deferred authorization so we can consider flowfile attributes in the access decision // get the uri of the request final String uri = generateResourceUri("flowfile-queues", connectionId, "flowfiles", flowFileUuid, "content"); @@ -300,7 +293,7 @@ public class FlowFileQueueResource extends ApplicationResource { @POST @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) - @Path("{connection-id}/listing-requests") + @Path("{id}/listing-requests") // TODO - @PreAuthorize("hasRole('ROLE_DFM')") @ApiOperation( value = "Lists the contents of the queue in this connection.", @@ -325,7 +318,7 @@ public class FlowFileQueueResource extends ApplicationResource { value = "The connection id.", required = true ) - @PathParam("connection-id") final String id) { + @PathParam("id") final String id) { if (isReplicateRequest()) { return replicate(HttpMethod.POST); @@ -336,8 +329,9 @@ public class FlowFileQueueResource extends ApplicationResource { if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable connection = lookup.getConnection(id).getAuthorizable(); - connection.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); + final ConnectionAuthorizable connAuth = lookup.getConnection(id); + final Authorizable dataAuthorizable = lookup.getData(connAuth.getSource().getIdentifier()); + dataAuthorizable.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); }); } if (validationPhase) { @@ -371,7 +365,7 @@ public class FlowFileQueueResource extends ApplicationResource { @GET @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) - @Path("{connection-id}/listing-requests/{listing-request-id}") + @Path("{id}/listing-requests/{listing-request-id}") // TODO - @PreAuthorize("hasRole('ROLE_DFM')") @ApiOperation( value = "Gets the current status of a listing request for the specified connection.", @@ -394,7 +388,7 @@ public class FlowFileQueueResource extends ApplicationResource { value = "The connection id.", required = true ) - @PathParam("connection-id") final String connectionId, + @PathParam("id") final String connectionId, @ApiParam( value = "The listing request id.", required = true @@ -407,8 +401,9 @@ public class FlowFileQueueResource extends ApplicationResource { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable connection = lookup.getConnection(connectionId).getAuthorizable(); - connection.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); + final ConnectionAuthorizable connAuth = lookup.getConnection(connectionId); + final Authorizable dataAuthorizable = lookup.getData(connAuth.getSource().getIdentifier()); + dataAuthorizable.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); }); // get the listing request @@ -433,7 +428,7 @@ public class FlowFileQueueResource extends ApplicationResource { @DELETE @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) - @Path("{connection-id}/listing-requests/{listing-request-id}") + @Path("{id}/listing-requests/{listing-request-id}") // TODO - @PreAuthorize("hasRole('ROLE_DFM')") @ApiOperation( value = "Cancels and/or removes a request to list the contents of this connection.", @@ -457,7 +452,7 @@ public class FlowFileQueueResource extends ApplicationResource { value = "The connection id.", required = true ) - @PathParam("connection-id") final String connectionId, + @PathParam("id") final String connectionId, @ApiParam( value = "The listing request id.", required = true @@ -473,8 +468,9 @@ public class FlowFileQueueResource extends ApplicationResource { if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable connection = lookup.getConnection(connectionId).getAuthorizable(); - connection.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); + final ConnectionAuthorizable connAuth = lookup.getConnection(connectionId); + final Authorizable dataAuthorizable = lookup.getData(connAuth.getSource().getIdentifier()); + dataAuthorizable.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); }); } if (validationPhase) { @@ -507,7 +503,7 @@ public class FlowFileQueueResource extends ApplicationResource { @POST @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) - @Path("{connection-id}/drop-requests") + @Path("{id}/drop-requests") // TODO - @PreAuthorize("hasRole('ROLE_DFM')") @ApiOperation( value = "Creates a request to drop the contents of the queue in this connection.", @@ -532,7 +528,7 @@ public class FlowFileQueueResource extends ApplicationResource { value = "The connection id.", required = true ) - @PathParam("connection-id") final String id) { + @PathParam("id") final String id) { if (isReplicateRequest()) { return replicate(HttpMethod.POST); @@ -543,8 +539,9 @@ public class FlowFileQueueResource extends ApplicationResource { if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable connection = lookup.getConnection(id).getAuthorizable(); - connection.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); + final ConnectionAuthorizable connAuth = lookup.getConnection(id); + final Authorizable dataAuthorizable = lookup.getData(connAuth.getSource().getIdentifier()); + dataAuthorizable.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); }); } if (validationPhase) { @@ -577,7 +574,7 @@ public class FlowFileQueueResource extends ApplicationResource { @GET @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) - @Path("{connection-id}/drop-requests/{drop-request-id}") + @Path("{id}/drop-requests/{drop-request-id}") // TODO - @PreAuthorize("hasRole('ROLE_DFM')") @ApiOperation( value = "Gets the current status of a drop request for the specified connection.", @@ -600,7 +597,7 @@ public class FlowFileQueueResource extends ApplicationResource { value = "The connection id.", required = true ) - @PathParam("connection-id") final String connectionId, + @PathParam("id") final String connectionId, @ApiParam( value = "The drop request id.", required = true @@ -613,8 +610,9 @@ public class FlowFileQueueResource extends ApplicationResource { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable connection = lookup.getConnection(connectionId).getAuthorizable(); - connection.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); + final ConnectionAuthorizable connAuth = lookup.getConnection(connectionId); + final Authorizable dataAuthorizable = lookup.getData(connAuth.getSource().getIdentifier()); + dataAuthorizable.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); }); // get the drop request @@ -639,7 +637,7 @@ public class FlowFileQueueResource extends ApplicationResource { @DELETE @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) - @Path("{connection-id}/drop-requests/{drop-request-id}") + @Path("{id}/drop-requests/{drop-request-id}") // TODO - @PreAuthorize("hasRole('ROLE_DFM')") @ApiOperation( value = "Cancels and/or removes a request to drop the contents of this connection.", @@ -663,7 +661,7 @@ public class FlowFileQueueResource extends ApplicationResource { value = "The connection id.", required = true ) - @PathParam("connection-id") final String connectionId, + @PathParam("id") final String connectionId, @ApiParam( value = "The drop request id.", required = true @@ -679,8 +677,9 @@ public class FlowFileQueueResource extends ApplicationResource { if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable connection = lookup.getConnection(connectionId).getAuthorizable(); - connection.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); + final ConnectionAuthorizable connAuth = lookup.getConnection(connectionId); + final Authorizable dataAuthorizable = lookup.getData(connAuth.getSource().getIdentifier()); + dataAuthorizable.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); }); } if (validationPhase) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java index 8299181571..aa9a8acfab 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java @@ -30,7 +30,6 @@ import org.apache.nifi.authorization.resource.ResourceFactory; import org.apache.nifi.authorization.resource.ResourceType; import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.authorization.user.NiFiUserUtils; -import org.apache.nifi.authorization.user.StandardNiFiUser; import org.apache.nifi.cluster.coordination.ClusterCoordinator; import org.apache.nifi.cluster.protocol.NodeIdentifier; import org.apache.nifi.components.PropertyDescriptor; @@ -69,8 +68,8 @@ import org.apache.nifi.nar.NarCloseable; import org.apache.nifi.processor.DataUnit; import org.apache.nifi.processor.Processor; import org.apache.nifi.processor.Relationship; -import org.apache.nifi.provenance.ProvenanceRepository; import org.apache.nifi.provenance.ProvenanceEventRecord; +import org.apache.nifi.provenance.ProvenanceRepository; import org.apache.nifi.provenance.SearchableFields; import org.apache.nifi.provenance.lineage.ComputeLineageSubmission; import org.apache.nifi.provenance.search.Query; @@ -109,7 +108,6 @@ import org.apache.nifi.web.api.dto.search.ComponentSearchResultDTO; import org.apache.nifi.web.api.dto.search.SearchResultsDTO; import org.apache.nifi.web.api.dto.status.ControllerStatusDTO; import org.apache.nifi.web.api.dto.status.StatusHistoryDTO; -import org.apache.nifi.web.security.ProxiedEntitiesUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -767,7 +765,7 @@ public class ControllerFacade implements Authorizable { // add each processor for (final ProcessorNode processor : root.findAllProcessors()) { resources.add(ResourceFactory.getComponentResource(ResourceType.Processor, processor.getIdentifier(), processor.getName())); - resources.add(ResourceFactory.getProvenanceEventResource(processor.getResource())); + resources.add(ResourceFactory.getDataResource(processor.getResource())); } // add each label @@ -778,25 +776,25 @@ public class ControllerFacade implements Authorizable { // add each process group for (final ProcessGroup processGroup : root.findAllProcessGroups()) { resources.add(ResourceFactory.getComponentResource(ResourceType.ProcessGroup, processGroup.getIdentifier(), processGroup.getName())); - resources.add(ResourceFactory.getProvenanceEventResource(processGroup.getResource())); + resources.add(ResourceFactory.getDataResource(processGroup.getResource())); } // add each remote process group for (final RemoteProcessGroup remoteProcessGroup : root.findAllRemoteProcessGroups()) { resources.add(ResourceFactory.getComponentResource(ResourceType.RemoteProcessGroup, remoteProcessGroup.getIdentifier(), remoteProcessGroup.getName())); - resources.add(ResourceFactory.getProvenanceEventResource(remoteProcessGroup.getResource())); + resources.add(ResourceFactory.getDataResource(remoteProcessGroup.getResource())); } // add each input port for (final Port inputPort : root.findAllInputPorts()) { resources.add(ResourceFactory.getComponentResource(ResourceType.InputPort, inputPort.getIdentifier(), inputPort.getName())); - resources.add(ResourceFactory.getProvenanceEventResource(inputPort.getResource())); + resources.add(ResourceFactory.getDataResource(inputPort.getResource())); } // add each output port for (final Port outputPort : root.findAllOutputPorts()) { resources.add(ResourceFactory.getComponentResource(ResourceType.OutputPort, outputPort.getIdentifier(), outputPort.getName())); - resources.add(ResourceFactory.getProvenanceEventResource(outputPort.getResource())); + resources.add(ResourceFactory.getDataResource(outputPort.getResource())); } // add each controller service @@ -1087,7 +1085,7 @@ public class ControllerFacade implements Authorizable { final NiFiUser user = NiFiUserUtils.getNiFiUser(); // get the event in order to get the filename - final ProvenanceEventRecord event = flowController.getProvenanceRepository().getEvent(eventId, NiFiUserUtils.getNiFiUser()); + final ProvenanceEventRecord event = flowController.getProvenanceRepository().getEvent(eventId); if (event == null) { throw new ResourceNotFoundException("Unable to find the specified event."); } @@ -1101,7 +1099,8 @@ public class ControllerFacade implements Authorizable { } // authorize the event - authorizeEvent(event.getComponentId(), attributes); + final Authorizable dataAuthorizable = flowController.createDataAuthorizable(event.getComponentId()); + dataAuthorizable.authorize(authorizer, RequestAction.READ, user, attributes); // get the filename and fall back to the identifier (should never happen) String filename = attributes.get(CoreAttributes.FILENAME.key()); @@ -1137,7 +1136,7 @@ public class ControllerFacade implements Authorizable { } // lookup the original event - final ProvenanceEventRecord originalEvent = flowController.getProvenanceRepository().getEvent(eventId, NiFiUserUtils.getNiFiUser()); + final ProvenanceEventRecord originalEvent = flowController.getProvenanceRepository().getEvent(eventId); if (originalEvent == null) { throw new ResourceNotFoundException("Unable to find the specified event."); } @@ -1155,67 +1154,6 @@ public class ControllerFacade implements Authorizable { } } - /** - * Authorizes access to a provenance event generated by the specified component and containing the specified eventAttributes. - * - * @param componentId component id - * @param eventAttributes event attributes - */ - private AuthorizationResult checkAuthorizationForEvent(final String componentId, final Map eventAttributes) { - AuthorizationResult result = null; - - // calculate the dn chain - final NiFiUser user = NiFiUserUtils.getNiFiUser(); - final List dnChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(user); - for (final String identity : dnChain) { - final Authorizable eventAuthorizable = flowController.createProvenanceAuthorizable(componentId); - final String clientAddress = user.getIdentity().equals(identity) ? user.getClientAddress() : null; - final NiFiUser chainUser = new StandardNiFiUser(identity, clientAddress) { - @Override - public boolean isAnonymous() { - // allow current user to drive anonymous flag as anonymous users are never chained... supports single user case - return user.isAnonymous(); - } - }; - - result = eventAuthorizable.checkAuthorization(authorizer, RequestAction.READ, chainUser, eventAttributes); - if (!Result.Approved.equals(result.getResult())) { - break; - } - } - - if (result == null) { - result = AuthorizationResult.denied(); - } - - return result; - } - - /** - * Authorizes access to a provenance event generated by the specified component and containing the specified eventAttributes. - * - * @param componentId component id - * @param eventAttributes event attributes - */ - private void authorizeEvent(final String componentId, final Map eventAttributes) { - // calculate the dn chain - final NiFiUser user = NiFiUserUtils.getNiFiUser(); - final List dnChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(user); - for (final String identity : dnChain) { - final Authorizable eventAuthorizable = flowController.createProvenanceAuthorizable(componentId); - final String clientAddress = user.getIdentity().equals(identity) ? user.getClientAddress() : null; - final NiFiUser chainUser = new StandardNiFiUser(identity, clientAddress) { - @Override - public boolean isAnonymous() { - // allow current user to drive anonymous flag as anonymous users are never chained... supports single user case - return user.isAnonymous(); - } - }; - - eventAuthorizable.authorize(authorizer, RequestAction.READ, chainUser, eventAttributes); - } - } - /** * Authorizes access to replay a specified provenance event. * @@ -1229,16 +1167,17 @@ public class ControllerFacade implements Authorizable { return AuthorizationResult.denied(); } - final AuthorizationResult result = checkAuthorizationForEvent(componentId, eventAttributes); + final NiFiUser user = NiFiUserUtils.getNiFiUser(); + final Authorizable dataAuthorizable = flowController.createDataAuthorizable(componentId); + + // ensure we can read the data + final AuthorizationResult result = dataAuthorizable.checkAuthorization(authorizer, RequestAction.READ, user, eventAttributes); if (!Result.Approved.equals(result.getResult())) { return result; } - // authorize write permissions for the queue - final NiFiUser user = NiFiUserUtils.getNiFiUser(); - final ProcessGroup rootGroup = flowController.getGroup(flowController.getRootGroupId()); - final Connection connection = rootGroup.findConnection(connectionId); - return connection.checkAuthorization(authorizer, RequestAction.WRITE, user); + // ensure we can write the data + return dataAuthorizable.checkAuthorization(authorizer, RequestAction.WRITE, user, eventAttributes); } /** @@ -1254,13 +1193,12 @@ public class ControllerFacade implements Authorizable { throw new AccessDeniedException("The connection id is unknown."); } - authorizeEvent(componentId, eventAttributes); - - // authorize write permissions for the queue final NiFiUser user = NiFiUserUtils.getNiFiUser(); - final ProcessGroup rootGroup = flowController.getGroup(flowController.getRootGroupId()); - final Connection connection = rootGroup.findConnection(connectionId); - connection.authorize(authorizer, RequestAction.WRITE, user); + final Authorizable dataAuthorizable = flowController.createDataAuthorizable(componentId); + + // ensure we can read and write the data + dataAuthorizable.authorize(authorizer, RequestAction.READ, user, eventAttributes); + dataAuthorizable.authorize(authorizer, RequestAction.WRITE, user, eventAttributes); } /** @@ -1271,14 +1209,15 @@ public class ControllerFacade implements Authorizable { */ public ProvenanceEventDTO getProvenanceEvent(final Long eventId) { try { - final ProvenanceEventRecord event = flowController.getProvenanceRepository().getEvent(eventId, NiFiUserUtils.getNiFiUser()); + final ProvenanceEventRecord event = flowController.getProvenanceRepository().getEvent(eventId); if (event == null) { throw new ResourceNotFoundException("Unable to find the specified event."); } // get the flowfile attributes and authorize the event final Map attributes = event.getAttributes(); - authorizeEvent(event.getComponentId(), attributes); + final Authorizable dataAuthorizable = flowController.createDataAuthorizable(event.getComponentId()); + dataAuthorizable.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser(), attributes); // convert the event return createProvenanceEventDto(event); @@ -1293,8 +1232,8 @@ public class ControllerFacade implements Authorizable { * @param componentId component id * @return authorizable */ - public Authorizable getProvenanceEventAuthorizable(final String componentId) { - return flowController.createProvenanceAuthorizable(componentId); + public Authorizable getDataAuthorizable(final String componentId) { + return flowController.createDataAuthorizable(componentId); } /** diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/AccessPolicyDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/AccessPolicyDAO.java index d3f896541f..05b7fd771a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/AccessPolicyDAO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/AccessPolicyDAO.java @@ -46,7 +46,18 @@ public interface AccessPolicyDAO { AccessPolicy getAccessPolicy(String accessPolicyId); /** - * Gets the access policy according to the action and authorizable. + * Gets the access policy according to the action and authorizable. Will return null + * if no policy exists for the specific resource. + * + * @param requestAction action + * @param resource resource + * @return access policy + */ + AccessPolicy getAccessPolicy(RequestAction requestAction, String resource); + + /** + * Gets the access policy according to the action and authorizable. Will return the + * effective policy if no policy exists for the specific authorizable. * * @param requestAction action * @param authorizable authorizable diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java index a337854751..16cf03f8d7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java @@ -16,16 +16,11 @@ */ package org.apache.nifi.web.dao.impl; -import org.apache.commons.lang3.StringUtils; -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.UserContextKeys; +import org.apache.nifi.authorization.resource.Authorizable; import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.authorization.user.NiFiUserUtils; -import org.apache.nifi.authorization.user.StandardNiFiUser; import org.apache.nifi.connectable.Connectable; import org.apache.nifi.connectable.ConnectableType; import org.apache.nifi.connectable.Connection; @@ -50,7 +45,6 @@ import org.apache.nifi.web.api.dto.ConnectableDTO; import org.apache.nifi.web.api.dto.ConnectionDTO; import org.apache.nifi.web.api.dto.PositionDTO; import org.apache.nifi.web.dao.ConnectionDAO; -import org.apache.nifi.web.security.ProxiedEntitiesUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,7 +54,6 @@ import java.io.InputStream; 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; @@ -139,6 +132,11 @@ public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO throw new ResourceNotFoundException(String.format("The FlowFile with UUID %s is no longer in the active queue.", flowFileUuid)); } + // get the attributes and ensure appropriate access + final Map attributes = flowFile.getAttributes(); + final Authorizable dataAuthorizable = flowController.createDataAuthorizable(connection.getSource().getIdentifier()); + dataAuthorizable.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser(), attributes); + return flowFile; } catch (final IOException ioe) { logger.error(String.format("Unable to get the flowfile (%s) at this time.", flowFileUuid), ioe); @@ -597,26 +595,10 @@ public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO throw new ResourceNotFoundException(String.format("The FlowFile with UUID %s is no longer in the active queue.", flowFileUuid)); } + // get the attributes and ensure appropriate access final Map attributes = flowFile.getAttributes(); - - // calculate the dn chain - final List dnChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(user); - dnChain.forEach(identity -> { - // build the request - final Map userContext; - if (!StringUtils.isBlank(user.getClientAddress())) { - userContext = new HashMap<>(); - userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress()); - } else { - userContext = null; - } - - final NiFiUser chainUser = new StandardNiFiUser(identity, user.getClientAddress()); - final AuthorizationResult result = connection.checkAuthorization(authorizer, RequestAction.WRITE, chainUser, attributes); - if (!Result.Approved.equals(result.getResult())) { - throw new AccessDeniedException(result.getExplanation()); - } - }); + final Authorizable dataAuthorizable = flowController.createDataAuthorizable(connection.getSource().getIdentifier()); + dataAuthorizable.authorize(authorizer, RequestAction.READ, user, attributes); // get the filename and fall back to the identifier (should never happen) String filename = attributes.get(CoreAttributes.FILENAME.key()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAO.java index 7488fe10b9..41051e1a1e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAO.java @@ -179,6 +179,11 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr return accessPolicy; } + @Override + public AccessPolicy getAccessPolicy(final RequestAction requestAction, final String resource) { + return findAccessPolicy(requestAction, resource); + } + @Override public AccessPolicy getAccessPolicy(final RequestAction requestAction, final Authorizable authorizable) { final String resource = authorizable.getResource().getIdentifier(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java index 605e98b840..0ff9fed146 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java @@ -16,16 +16,18 @@ */ package org.apache.nifi.web.security; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.authorization.user.NiFiUser; +import org.apache.nifi.authorization.user.NiFiUserUtils; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.authorization.user.NiFiUser; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; /** * @@ -72,33 +74,10 @@ public class ProxiedEntitiesUtils { */ public static String buildProxiedEntitiesChainString(final NiFiUser user) { // calculate the dn chain - final List proxyChain = buildProxiedEntitiesChain(user); + final List proxyChain = NiFiUserUtils.buildProxiedEntitiesChain(user); return formatProxyDn(StringUtils.join(proxyChain, "><")); } - /** - * Builds the proxy chain for the specified user. - * - * @param user The current user - * @return The proxy chain for that user in List form - */ - public static List buildProxiedEntitiesChain(final NiFiUser user) { - // calculate the dn chain - final List proxyChain = new ArrayList<>(); - - // build the dn chain - NiFiUser chainedUser = user; - do { - // add the entry for this user - proxyChain.add(chainedUser.getIdentity()); - - // go to the next user in the chain - chainedUser = chainedUser.getChain(); - } while (chainedUser != null); - - return proxyChain; - } - /** * Builds the proxy chain from the specified request and user. * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css index 1c16d13204..fbc0de5835 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css @@ -68,7 +68,7 @@ */ body.login-body input, body.login-body textarea { - width: 400px; + width: 412px; } div.login-container { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js index 4057334749..aee9aafbe6 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js @@ -999,8 +999,9 @@ nf.Actions = (function () { $('#drop-request-status-message').text(dropRequest.state); // update the current number of enqueued flowfiles - if (nf.Common.isDefinedAndNotNull(connection.status) && nf.Common.isDefinedAndNotNull(dropRequest.currentCount)) { + if (nf.Common.isDefinedAndNotNull(dropRequest.currentCount)) { connection.status.queued = dropRequest.current; + connection.status.aggregateSnapshot.queued = dropRequest.current; nf.Connection.refresh(connection.id); } @@ -1028,13 +1029,19 @@ nf.Actions = (function () { }).done(function (response) { dropRequest = response.dropRequest; processDropRequest(nextDelay); - }).fail(completeDropRequest); + }).fail(function (xhr, status, error) { + if (xhr.status === 403) { + nf.Common.handleAjaxError(xhr, status, error); + } else { + completeDropRequest() + } + }); }; // issue the request to delete the flow files $.ajax({ type: 'POST', - url: '../nifi-api/flowfile-queues/' + connection.id + '/drop-requests', + url: '../nifi-api/flowfile-queues/' + encodeURIComponent(connection.id) + '/drop-requests', dataType: 'json', contentType: 'application/json' }).done(function (response) { @@ -1047,7 +1054,13 @@ nf.Actions = (function () { // process the drop request dropRequest = response.dropRequest; processDropRequest(1); - }).fail(completeDropRequest); + }).fail(function (xhr, status, error) { + if (xhr.status === 403) { + nf.Common.handleAjaxError(xhr, status, error); + } else { + completeDropRequest() + } + }); } }); }, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js index 2282fe2953..326f1a1496 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js @@ -316,10 +316,6 @@ nf.ContextMenu = (function () { * @param {selection} selection */ var canEmptyQueue = function (selection) { - if (nf.CanvasUtils.canModify(selection) === false) { - return false; - } - return isConnection(selection); }; @@ -329,10 +325,6 @@ nf.ContextMenu = (function () { * @param {selection} selection */ var canListQueue = function (selection) { - if (nf.CanvasUtils.canModify(selection) === false) { - return false; - } - return isConnection(selection); }; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js index 4ba2bcd21a..c207e80ecd 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js @@ -308,17 +308,13 @@ nf.PolicyManagement = (function () { value: 'write-component', description: 'Allows users to modify component configuration details' }, { - text: 'view the provenance events', - value: 'read-provenance-events', - description: 'Allows users to access provenance events and content for this component' + text: 'view the data', + value: 'read-data', + description: 'Allows users to view metadata and content for this component through provenance data and flowfile queues in outbound connections' }, { - text: 'view the policies', - value: 'read-policies', - description: 'Allows users to view the list of users who can view/modify this component' - }, { - text: 'modify the policies', - value: 'write-policies', - description: 'Allows users to modify the list of users who can view/modify this component' + text: 'modify the data', + value: 'write-data', + description: 'Allows users to empty flowfile queues in outbound connections and submit replays' }, { text: 'receive data via site-to-site', value: 'write-receive-data', @@ -329,6 +325,14 @@ nf.PolicyManagement = (function () { value: 'write-send-data', description: 'Allows this port to send data to these NiFi instances', disabled: true + }, { + text: 'view the policies', + value: 'read-policies', + description: 'Allows users to view the list of users who can view/modify this component' + }, { + text: 'modify the policies', + value: 'write-policies', + description: 'Allows users to modify the list of users who can view/modify this component' }], select: function (option) { if (initialized) { @@ -338,9 +342,12 @@ nf.PolicyManagement = (function () { $('#selected-policy-action').text('read'); } else if (option.value === 'write-component') { $('#selected-policy-action').text('write'); - } else if (option.value === 'read-provenance-events') { + } else if (option.value === 'read-data') { $('#selected-policy-action').text('read'); - resource = ('provenance-events/' + resource); + resource = ('data/' + resource); + } else if (option.value === 'write-data') { + $('#selected-policy-action').text('write'); + resource = ('data/' + resource); } else if (option.value === 'read-policies') { $('#selected-policy-action').text('read'); resource = ('policies/' + resource); @@ -380,7 +387,11 @@ nf.PolicyManagement = (function () { var actionFormatter = function (row, cell, value, columnDef, dataContext) { var markup = ''; - markup += '
'; + // see if the user has permissions for the current policy + var currentEntity = $('#policy-table').data('policy'); + if (currentEntity.permissions.canWrite === true) { + markup += '
'; + } return markup; }; @@ -538,7 +549,11 @@ nf.PolicyManagement = (function () { dataType: 'json' }).done(function () { loadPolicy(); - }).fail(nf.Common.handleAjaxError); + }).fail(function (xhr, status, error) { + nf.Common.handleAjaxError(xhr, status, error); + resetPolicy(); + loadPolicy(); + }); } else { nf.Dialog.showOkDialog({ headerText: 'Update Policy', @@ -600,6 +615,7 @@ nf.PolicyManagement = (function () { // re-sort and clear selection after updating policyData.reSort(); + policyGrid.invalidate(); policyGrid.getSelectionModel().setSelectedRows([]); }; @@ -634,20 +650,21 @@ nf.PolicyManagement = (function () { // store the current policy version $('#policy-table').data('policy', policyEntity); - - // allow removal and modification as the policy is not inherited - $('#new-policy-user-button').prop('disabled', false); + + // allow modification if allowed + $('#new-policy-user-button').prop('disabled', policyEntity.permissions.canWrite === false); // see if the policy is for this resource if (resourceAndAction.resource === policy.resource) { // allow remove when policy is not inherited - $('#delete-policy-button').prop('disabled', false); + $('#delete-policy-button').prop('disabled', policyEntity.permissions.canWrite === false); } else { $('#policy-message').text('Showing effective policy inherited from ' + convertToHumanReadableResource(policy.resource) + '. '); - $('#new-policy-message').hide(); + + // policy is inherited, we do not know if the user has permissions to modify the desired policy... show button and let server decide $('#override-policy-message').show(); - // require non inherited policy for removal + // to not support policy deletion $('#delete-policy-button').prop('disabled', true); } @@ -660,7 +677,6 @@ nf.PolicyManagement = (function () { */ var loadPolicy = function () { var resourceAndAction = getSelectedResourceAndAction(); - return $.Deferred(function (deferred) { $.ajax({ type: 'GET', @@ -673,7 +689,7 @@ nf.PolicyManagement = (function () { $('#policy-last-refreshed').text(policyEntity.generated); // ensure appropriate actions for the loaded policy - if (policyEntity.permissions.canRead === true && policyEntity.permissions.canWrite === true) { + if (policyEntity.permissions.canRead === true) { var policy = policyEntity.component; $('#policy-message').text(policy.resource); @@ -684,9 +700,10 @@ nf.PolicyManagement = (function () { // reset the policy resetPolicy(); - // show an appropriate message - $('#policy-message').text('No policy for the specified resource and not authorized to access the inherited policy. '); - $('#new-policy-message').hide(); + // since we cannot read, the policy may be inherited or not... we cannot tell + $('#policy-message').text('Not authorized to view the policy.'); + + // allow option to override because we don't know if it's supported or not $('#override-policy-message').show(); } @@ -698,8 +715,9 @@ nf.PolicyManagement = (function () { // show an appropriate message $('#policy-message').text('No policy for the specified resource.'); + + // 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(); - $('#override-policy-message').hide(); deferred.resolve(); } else if (xhr.status === 403) { @@ -708,8 +726,6 @@ nf.PolicyManagement = (function () { // show an appropriate message $('#policy-message').text('Not authorized to access the policy for the specified resource.'); - $('#new-policy-message').hide(); - $('#override-policy-message').hide(); deferred.resolve(); } else { @@ -747,7 +763,19 @@ nf.PolicyManagement = (function () { dataType: 'json', contentType: 'application/json' }).done(function (policyEntity) { - populatePolicy(policyEntity); + // ensure appropriate actions for the loaded policy + if (policyEntity.permissions.canRead === true) { + var policy = policyEntity.component; + + $('#policy-message').text(policy.resource); + + // populate the policy details + populatePolicy(policyEntity); + } else { + // the request succeeded but we don't have access to the policy... reset/reload the policy + resetPolicy(); + loadPolicy(); + } }).fail(nf.Common.handleAjaxError); }; @@ -792,7 +820,7 @@ nf.PolicyManagement = (function () { contentType: 'application/json' }).done(function (policyEntity) { // ensure appropriate actions for the loaded policy - if (policyEntity.permissions.canRead === true && policyEntity.permissions.canWrite === true) { + if (policyEntity.permissions.canRead === true) { var policy = policyEntity.component; $('#policy-message').text(policy.resource); @@ -800,15 +828,15 @@ nf.PolicyManagement = (function () { // populate the policy details populatePolicy(policyEntity); } else { - // reset the policy + // the request succeeded but we don't have access to the policy... reset/reload the policy resetPolicy(); - - // show an appropriate message - $('#policy-message').text('No policy for the specified resource and not authorized to access the inherited policy. '); - $('#new-policy-message').hide(); - $('#override-policy-message').show(); + loadPolicy(); } - }).fail(nf.Common.handleAjaxError); + }).fail(function (xhr, status, error) { + nf.Common.handleAjaxError(xhr, status, error) + resetPolicy(); + loadPolicy(); + }); } else { nf.Dialog.showOkDialog({ headerText: 'Update Policy', @@ -872,35 +900,6 @@ nf.PolicyManagement = (function () { $('div.policy-selected-component-container').hide(); }; - /** - * Populates the initial policy resource. - * - * @param resource - */ - var populateComponentResource = function (resource) { - // record the initial resource type - $('#selected-policy-component-type').text(resource); - - var policyTarget = $('#component-policy-target').combo('getSelectedOption').value; - if (policyTarget === 'read-component') { - $('#selected-policy-action').text('read'); - } else if (policyTarget === 'write-component') { - $('#selected-policy-action').text('write'); - } else if (policyTarget === 'read-provenance-events') { - $('#selected-policy-action').text('read'); - resource = ('provenance-events/' + resource); - } else if (policyTarget === 'read-policies') { - $('#selected-policy-action').text('read'); - resource = ('policies/' + resource); - } else if (policyTarget === 'write-policies') { - $('#selected-policy-action').text('write'); - resource = ('policies/' + resource); - } - - // update the policy type - $('#selected-policy-type').text(resource); - }; - return { /** * Initializes the settings page. @@ -909,6 +908,13 @@ nf.PolicyManagement = (function () { initAddTenantToPolicyDialog(); initPolicyTable(); + $('#policy-refresh-button').on('click', function () { + loadPolicy(); + }); + + // reset the policy to initialize + resetPolicy(); + // mark as initialized initialized = true; }, @@ -940,7 +946,7 @@ nf.PolicyManagement = (function () { $('#global-policy-controls').hide(); // update the visibility - if (d.permissions.canRead) { + if (d.permissions.canRead === true) { $('#policy-selected-controller-service-container div.policy-selected-component-name').text(d.component.name); } else { $('#policy-selected-controller-service-container div.policy-selected-component-name').text(d.id); @@ -949,7 +955,17 @@ nf.PolicyManagement = (function () { // populate the initial resource $('#selected-policy-component-id').text(d.id); - populateComponentResource('controller-services'); + $('#selected-policy-component-type').text('controller-services'); + $('#component-policy-target') + .combo('setOptionEnabled', { + value: 'write-receive-data' + }, false) + .combo('setOptionEnabled', { + value: 'write-send-data' + }, false) + .combo('setSelectedOption', { + value: 'read-component' + }); return loadPolicy().always(showPolicy); }, @@ -968,7 +984,7 @@ nf.PolicyManagement = (function () { $('#global-policy-controls').hide(); // update the visibility - if (d.permissions.canRead) { + if (d.permissions.canRead === true) { $('#policy-selected-reporting-task-container div.policy-selected-component-name').text(d.component.name); } else { $('#policy-selected-reporting-task-container div.policy-selected-component-name').text(d.id); @@ -977,7 +993,17 @@ nf.PolicyManagement = (function () { // populate the initial resource $('#selected-policy-component-id').text(d.id); - populateComponentResource('reporting-tasks'); + $('#selected-policy-component-type').text('reporting-tasks'); + $('#component-policy-target') + .combo('setOptionEnabled', { + value: 'write-receive-data' + }, false) + .combo('setOptionEnabled', { + value: 'write-send-data' + }, false) + .combo('setSelectedOption', { + value: 'read-component' + }); return loadPolicy().always(showPolicy); }, @@ -996,7 +1022,7 @@ nf.PolicyManagement = (function () { $('#global-policy-controls').hide(); // update the visibility - if (d.permissions.canRead) { + if (d.permissions.canRead === true) { $('#policy-selected-template-container div.policy-selected-component-name').text(d.template.name); } else { $('#policy-selected-template-container div.policy-selected-component-name').text(d.id); @@ -1005,7 +1031,17 @@ nf.PolicyManagement = (function () { // populate the initial resource $('#selected-policy-component-id').text(d.id); - populateComponentResource('templates'); + $('#selected-policy-component-type').text('templates'); + $('#component-policy-target') + .combo('setOptionEnabled', { + value: 'write-receive-data' + }, false) + .combo('setOptionEnabled', { + value: 'write-send-data' + }, false) + .combo('setSelectedOption', { + value: 'read-component' + }); return loadPolicy().always(showPolicy); }, @@ -1028,6 +1064,15 @@ nf.PolicyManagement = (function () { if (selection.empty()) { $('#selected-policy-component-id').text(nf.Canvas.getGroupId()); resource = 'process-groups'; + + // disable site to site option + $('#component-policy-target') + .combo('setOptionEnabled', { + value: 'write-receive-data' + }, false) + .combo('setOptionEnabled', { + value: 'write-send-data' + }, false); } else { var d = selection.datum(); $('#selected-policy-component-id').text(d.id); @@ -1059,7 +1104,10 @@ nf.PolicyManagement = (function () { } // populate the initial resource - populateComponentResource(resource); + $('#selected-policy-component-type').text(resource); + $('#component-policy-target').combo('setSelectedOption', { + value: 'read-component' + }); return loadPolicy().always(showPolicy); }, diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/PersistentProvenanceRepository.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/PersistentProvenanceRepository.java index e01c2b190c..7e8b9a6361 100644 --- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/PersistentProvenanceRepository.java +++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/PersistentProvenanceRepository.java @@ -411,7 +411,7 @@ public class PersistentProvenanceRepository implements ProvenanceRepository { final Authorizable eventAuthorizable; try { - eventAuthorizable = resourceFactory.createProvenanceAuthorizable(event.getComponentId()); + eventAuthorizable = resourceFactory.createDataAuthorizable(event.getComponentId()); } catch (final ResourceNotFoundException rnfe) { return false; } @@ -425,7 +425,7 @@ public class PersistentProvenanceRepository implements ProvenanceRepository { return; } - final Authorizable eventAuthorizable = resourceFactory.createProvenanceAuthorizable(event.getComponentId()); + final Authorizable eventAuthorizable = resourceFactory.createDataAuthorizable(event.getComponentId()); eventAuthorizable.authorize(authorizer, RequestAction.READ, user, event.getAttributes()); } diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/main/java/org/apache/nifi/provenance/VolatileProvenanceRepository.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/main/java/org/apache/nifi/provenance/VolatileProvenanceRepository.java index b65d99dd6d..10026cf9fa 100644 --- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/main/java/org/apache/nifi/provenance/VolatileProvenanceRepository.java +++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/main/java/org/apache/nifi/provenance/VolatileProvenanceRepository.java @@ -243,7 +243,7 @@ public class VolatileProvenanceRepository implements ProvenanceRepository { final Authorizable eventAuthorizable; try { - eventAuthorizable = resourceFactory.createProvenanceAuthorizable(event.getComponentId()); + eventAuthorizable = resourceFactory.createDataAuthorizable(event.getComponentId()); } catch (final ResourceNotFoundException rnfe) { return false; } @@ -257,7 +257,7 @@ public class VolatileProvenanceRepository implements ProvenanceRepository { return; } - final Authorizable eventAuthorizable = resourceFactory.createProvenanceAuthorizable(event.getComponentId()); + final Authorizable eventAuthorizable = resourceFactory.createDataAuthorizable(event.getComponentId()); eventAuthorizable.authorize(authorizer, RequestAction.READ, user, event.getAttributes()); }