Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id B7471200D1B for ; Thu, 12 Oct 2017 21:35:31 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id B5EC71609E8; Thu, 12 Oct 2017 19:35:31 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 6EC711609E4 for ; Thu, 12 Oct 2017 21:35:29 +0200 (CEST) Received: (qmail 88541 invoked by uid 500); 12 Oct 2017 19:35:28 -0000 Mailing-List: contact commits-help@nifi.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@nifi.apache.org Delivered-To: mailing list commits@nifi.apache.org Received: (qmail 88532 invoked by uid 99); 12 Oct 2017 19:35:28 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 12 Oct 2017 19:35:28 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 7859BDFA0C; Thu, 12 Oct 2017 19:35:28 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: bbende@apache.org To: commits@nifi.apache.org Message-Id: X-Mailer: ASF-Git Admin Mailer Subject: nifi-registry git commit: NIFIREG-32: Add delete as available action for access policy management. Date: Thu, 12 Oct 2017 19:35:28 +0000 (UTC) archived-at: Thu, 12 Oct 2017 19:35:31 -0000 Repository: nifi-registry Updated Branches: refs/heads/master 2f7523538 -> 870b94787 NIFIREG-32: Add delete as available action for access policy management. - Add 'delete' as available action for authorization access policies, which are (resource, action) pairs. - Add unit tests for authorization classes. - Add BadRequestExceptionMapper. - Move StandardManagedAuthorizer from nifi-registry-security-api-imple to nifi-registry-framework. - Add missing default constructor for User and UserGroup This closes #18. Signed-off-by: Bryan Bende Project: http://git-wip-us.apache.org/repos/asf/nifi-registry/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi-registry/commit/870b9478 Tree: http://git-wip-us.apache.org/repos/asf/nifi-registry/tree/870b9478 Diff: http://git-wip-us.apache.org/repos/asf/nifi-registry/diff/870b9478 Branch: refs/heads/master Commit: 870b94787aa907ba4a85e3c7b32cd84dfd897fc7 Parents: 2f75235 Author: Kevin Doran Authored: Wed Oct 11 09:44:45 2017 -0400 Committer: Bryan Bende Committed: Thu Oct 12 15:35:10 2017 -0400 ---------------------------------------------------------------------- .../nifi/registry/model/authorization/User.java | 2 + .../registry/model/authorization/UserGroup.java | 2 + .../StandardManagedAuthorizer.java | 264 ++++++++ .../authorization/resource/Authorizable.java | 2 +- .../registry/service/AuthorizationService.java | 15 +- .../service/AuthorizationServiceSpec.groovy | 658 +++++++++++++++++++ .../AbstractPolicyBasedAuthorizer.java | 2 + .../StandardManagedAuthorizer.java | 264 -------- .../file/AuthorizationsHolder.java | 2 + .../file/FileAccessPolicyProvider.java | 14 +- .../src/main/xsd/authorizations.xsd | 1 + .../registry/authorization/RequestAction.java | 10 +- .../registry/web/api/AccessPolicyResource.java | 4 +- .../registry/web/api/BucketFlowResource.java | 2 +- .../nifi/registry/web/api/BucketResource.java | 2 +- .../nifi/registry/web/api/TenantResource.java | 4 +- .../web/mapper/BadRequestExceptionMapper.java | 47 ++ .../mapper/IllegalArgumentExceptionMapper.java | 2 +- 18 files changed, 1012 insertions(+), 285 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/model/authorization/User.java ---------------------------------------------------------------------- diff --git a/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/model/authorization/User.java b/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/model/authorization/User.java index a15cbc7..76ed867 100644 --- a/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/model/authorization/User.java +++ b/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/model/authorization/User.java @@ -29,6 +29,8 @@ public class User extends Tenant { private Set userGroups; private Set accessPolicies; + public User() {} + public User(String identifier, String identity) { super(identifier, identity); } http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/model/authorization/UserGroup.java ---------------------------------------------------------------------- diff --git a/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/model/authorization/UserGroup.java b/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/model/authorization/UserGroup.java index d504e66..51b1297 100644 --- a/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/model/authorization/UserGroup.java +++ b/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/model/authorization/UserGroup.java @@ -32,6 +32,8 @@ public class UserGroup extends Tenant { private Set users; private Set accessPolicies; + public UserGroup() {} + public UserGroup(String identifier, String identity) { super(identifier, identity); } http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/StandardManagedAuthorizer.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/StandardManagedAuthorizer.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/StandardManagedAuthorizer.java new file mode 100644 index 0000000..8cd4fea --- /dev/null +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/StandardManagedAuthorizer.java @@ -0,0 +1,264 @@ +/* + * 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.registry.authorization; + +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.registry.authorization.exception.UninheritableAuthorizationsException; +import org.apache.nifi.registry.util.PropertyValue; +import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.Set; + +public class StandardManagedAuthorizer implements ManagedAuthorizer { + + private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); + private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance(); + + private static final String USER_GROUP_PROVIDER_ELEMENT = "userGroupProvider"; + private static final String ACCESS_POLICY_PROVIDER_ELEMENT = "accessPolicyProvider"; + + private AccessPolicyProviderLookup accessPolicyProviderLookup; + private AccessPolicyProvider accessPolicyProvider; + private UserGroupProvider userGroupProvider; + + public StandardManagedAuthorizer() {} + + // exposed for testing to inject mocks + public StandardManagedAuthorizer(AccessPolicyProvider accessPolicyProvider, UserGroupProvider userGroupProvider) { + this.accessPolicyProvider = accessPolicyProvider; + this.userGroupProvider = userGroupProvider; + } + + @Override + public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { + accessPolicyProviderLookup = initializationContext.getAccessPolicyProviderLookup(); + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + final PropertyValue accessPolicyProviderKey = configurationContext.getProperty("Access Policy Provider"); + if (!accessPolicyProviderKey.isSet()) { + throw new AuthorizerCreationException("The Access Policy Provider must be set."); + } + + accessPolicyProvider = accessPolicyProviderLookup.getAccessPolicyProvider(accessPolicyProviderKey.getValue()); + + // ensure the desired access policy provider was found + if (accessPolicyProvider == null) { + throw new AuthorizerCreationException(String.format("Unable to locate configured Access Policy Provider: %s", accessPolicyProviderKey)); + } + + userGroupProvider = accessPolicyProvider.getUserGroupProvider(); + + // ensure the desired access policy provider has a user group provider + if (userGroupProvider == null) { + throw new AuthorizerCreationException(String.format("Configured Access Policy Provider %s does not contain a User Group Provider", accessPolicyProviderKey)); + } + } + + @Override + public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException { + final String resourceIdentifier = request.getResource().getIdentifier(); + final AccessPolicy policy = accessPolicyProvider.getAccessPolicy(resourceIdentifier, request.getAction()); + if (policy == null) { + return AuthorizationResult.resourceNotFound(); + } + + final UserAndGroups userAndGroups = userGroupProvider.getUserAndGroups(request.getIdentity()); + + final User user = userAndGroups.getUser(); + if (user == null) { + return AuthorizationResult.denied(String.format("Unknown user with identity '%s'.", request.getIdentity())); + } + + final Set userGroups = userAndGroups.getGroups(); + if (policy.getUsers().contains(user.getIdentifier()) || containsGroup(userGroups, policy)) { + return AuthorizationResult.approved(); + } + + return AuthorizationResult.denied(request.getExplanationSupplier().get()); + } + + /** + * Determines if the policy contains one of the user's groups. + * + * @param userGroups the set of the user's groups + * @param policy the policy + * @return true if one of the Groups in userGroups is contained in the policy + */ + private boolean containsGroup(final Set userGroups, final AccessPolicy policy) { + if (userGroups == null || userGroups.isEmpty() || policy.getGroups().isEmpty()) { + return false; + } + + for (Group userGroup : userGroups) { + if (policy.getGroups().contains(userGroup.getIdentifier())) { + return true; + } + } + + return false; + } + + @Override + public String getFingerprint() throws AuthorizationAccessException { + XMLStreamWriter writer = null; + final StringWriter out = new StringWriter(); + try { + writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out); + writer.writeStartDocument(); + writer.writeStartElement("managedAuthorizations"); + + writer.writeStartElement(ACCESS_POLICY_PROVIDER_ELEMENT); + if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) { + writer.writeCharacters(((ConfigurableAccessPolicyProvider) accessPolicyProvider).getFingerprint()); + } + writer.writeEndElement(); + + writer.writeStartElement(USER_GROUP_PROVIDER_ELEMENT); + if (userGroupProvider instanceof ConfigurableUserGroupProvider) { + writer.writeCharacters(((ConfigurableUserGroupProvider) userGroupProvider).getFingerprint()); + } + writer.writeEndElement(); + + writer.writeEndElement(); + writer.writeEndDocument(); + writer.flush(); + } catch (XMLStreamException e) { + throw new AuthorizationAccessException("Unable to generate fingerprint", e); + } finally { + if (writer != null) { + try { + writer.close(); + } catch (XMLStreamException e) { + // nothing to do here + } + } + } + + return out.toString(); + } + + @Override + public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { + if (StringUtils.isBlank(fingerprint)) { + return; + } + + final FingerprintHolder fingerprintHolder = parseFingerprint(fingerprint); + + if (StringUtils.isNotBlank(fingerprintHolder.getPolicyFingerprint()) && accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) { + ((ConfigurableAccessPolicyProvider) accessPolicyProvider).inheritFingerprint(fingerprintHolder.getPolicyFingerprint()); + } + + if (StringUtils.isNotBlank(fingerprintHolder.getUserGroupFingerprint()) && userGroupProvider instanceof ConfigurableUserGroupProvider) { + ((ConfigurableUserGroupProvider) userGroupProvider).inheritFingerprint(fingerprintHolder.getUserGroupFingerprint()); + } + } + + @Override + public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { + final FingerprintHolder fingerprintHolder = parseFingerprint(proposedFingerprint); + + if (StringUtils.isNotBlank(fingerprintHolder.getPolicyFingerprint())) { + if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) { + ((ConfigurableAccessPolicyProvider) accessPolicyProvider).checkInheritability(fingerprintHolder.getPolicyFingerprint()); + } else { + throw new UninheritableAuthorizationsException("Policy fingerprint is not blank and the configured AccessPolicyProvider does not support fingerprinting."); + } + } + + if (StringUtils.isNotBlank(fingerprintHolder.getUserGroupFingerprint())) { + if (userGroupProvider instanceof ConfigurableUserGroupProvider) { + ((ConfigurableUserGroupProvider) userGroupProvider).checkInheritability(fingerprintHolder.getUserGroupFingerprint()); + } else { + throw new UninheritableAuthorizationsException("User/Group fingerprint is not blank and the configured UserGroupProvider does not support fingerprinting."); + } + } + } + + private final FingerprintHolder parseFingerprint(final String fingerprint) throws AuthorizationAccessException { + final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8); + + try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) { + final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder(); + final Document document = docBuilder.parse(in); + final Element rootElement = document.getDocumentElement(); + + final NodeList accessPolicyProviderList = rootElement.getElementsByTagName(ACCESS_POLICY_PROVIDER_ELEMENT); + if (accessPolicyProviderList.getLength() != 1) { + throw new AuthorizationAccessException(String.format("Only one %s element is allowed: %s", ACCESS_POLICY_PROVIDER_ELEMENT, fingerprint)); + } + + final NodeList userGroupProviderList = rootElement.getElementsByTagName(USER_GROUP_PROVIDER_ELEMENT); + if (userGroupProviderList.getLength() != 1) { + throw new AuthorizationAccessException(String.format("Only one %s element is allowed: %s", USER_GROUP_PROVIDER_ELEMENT, fingerprint)); + } + + final Node accessPolicyProvider = accessPolicyProviderList.item(0); + final Node userGroupProvider = userGroupProviderList.item(0); + return new FingerprintHolder(accessPolicyProvider.getTextContent(), userGroupProvider.getTextContent()); + } catch (SAXException | ParserConfigurationException | IOException e) { + throw new AuthorizationAccessException("Unable to parse fingerprint", e); + } + } + + @Override + public AccessPolicyProvider getAccessPolicyProvider() { + return accessPolicyProvider; + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + + } + + private static class FingerprintHolder { + private final String policyFingerprint; + private final String userGroupFingerprint; + + public FingerprintHolder(String policyFingerprint, String userGroupFingerprint) { + this.policyFingerprint = policyFingerprint; + this.userGroupFingerprint = userGroupFingerprint; + } + + public String getPolicyFingerprint() { + return policyFingerprint; + } + + public String getUserGroupFingerprint() { + return userGroupFingerprint; + } + } +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/Authorizable.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/Authorizable.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/Authorizable.java index 3e694e7..77f0fec 100644 --- a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/Authorizable.java +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/Authorizable.java @@ -112,7 +112,7 @@ public interface Authorizable { if (RequestAction.READ.equals(action)) { safeDescription.append("view "); } else { - safeDescription.append("modify "); + safeDescription.append("modify "); // covers write or delete } safeDescription.append(resource.getSafeDescription()).append("."); http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java index cc63af0..48a7ae3 100644 --- a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java @@ -51,6 +51,7 @@ import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.locks.Lock; @@ -197,7 +198,7 @@ public class AuthorizationService { } } - public List getUserGroupsForUser(String userIdentifier) { + private List getUserGroupsForUser(String userIdentifier) { this.readLock.lock(); try { return userGroupProvider.getGroups() @@ -306,7 +307,7 @@ public class AuthorizationService { } } - public List getAccessPolicySummariesForUser(String userIdentifier) { + private List getAccessPolicySummariesForUser(String userIdentifier) { readLock.lock(); try { return accessPolicyProvider.getAccessPolicies().stream() @@ -318,7 +319,7 @@ public class AuthorizationService { } } - public List getAccessPolicySummariesForUserGroup(String userGroupIdentifier) { + private List getAccessPolicySummariesForUserGroup(String userGroupIdentifier) { readLock.lock(); try { return accessPolicyProvider.getAccessPolicies().stream() @@ -340,9 +341,9 @@ public class AuthorizationService { accessPolicy.setResource(currentAccessPolicy.getResource()); accessPolicy.setAction(currentAccessPolicy.getAction().toString()); - org.apache.nifi.registry.authorization.AccessPolicy updateAccessPolicy = + org.apache.nifi.registry.authorization.AccessPolicy updatedAccessPolicy = ((ConfigurableAccessPolicyProvider) accessPolicyProvider).updateAccessPolicy(accessPolicyFromDTO(accessPolicy)); - return accessPolicyToDTO(updateAccessPolicy); + return accessPolicyToDTO(updatedAccessPolicy); } finally { writeLock.unlock(); } @@ -465,9 +466,9 @@ public class AuthorizationService { } Collection users = accessPolicy.getUsers() != null - ? accessPolicy.getUsers().stream().map(this::getTenant).collect(Collectors.toList()) : null; + ? accessPolicy.getUsers().stream().map(this::getTenant).filter(Objects::nonNull).collect(Collectors.toList()) : null; Collection userGroups = accessPolicy.getGroups() != null - ? accessPolicy.getGroups().stream().map(this::getTenant).collect(Collectors.toList()) : null; + ? accessPolicy.getGroups().stream().map(this::getTenant).filter(Objects::nonNull).collect(Collectors.toList()) : null; Boolean isConfigurable = AuthorizerCapabilityDetection.isAccessPolicyConfigurable(authorizer, accessPolicy); http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy b/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy new file mode 100644 index 0000000..3e99483 --- /dev/null +++ b/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy @@ -0,0 +1,658 @@ +/* + * 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.registry.service + +import org.apache.nifi.registry.authorization.AccessPolicy as AuthAccessPolicy +import org.apache.nifi.registry.authorization.AuthorizableLookup +import org.apache.nifi.registry.authorization.ConfigurableAccessPolicyProvider +import org.apache.nifi.registry.authorization.ConfigurableUserGroupProvider +import org.apache.nifi.registry.authorization.Group +import org.apache.nifi.registry.authorization.RequestAction +import org.apache.nifi.registry.authorization.StandardManagedAuthorizer +import org.apache.nifi.registry.authorization.User as AuthUser +import org.apache.nifi.registry.authorization.exception.AccessDeniedException +import org.apache.nifi.registry.authorization.resource.Authorizable +import org.apache.nifi.registry.authorization.resource.ResourceType +import org.apache.nifi.registry.bucket.Bucket +import org.apache.nifi.registry.model.authorization.AccessPolicy +import org.apache.nifi.registry.model.authorization.User +import org.apache.nifi.registry.model.authorization.UserGroup +import spock.lang.Specification + +class AuthorizationServiceSpec extends Specification { + + def registryService = Mock(RegistryService) + def authorizableLookup = Mock(AuthorizableLookup) + def userGroupProvider = Mock(ConfigurableUserGroupProvider) + def accessPolicyProvider = Mock(ConfigurableAccessPolicyProvider) + + AuthorizationService authorizationService + + def setup() { + accessPolicyProvider.getUserGroupProvider() >> userGroupProvider + def authorizer = new StandardManagedAuthorizer(accessPolicyProvider, userGroupProvider) + authorizationService = new AuthorizationService(authorizableLookup, authorizer, registryService) + } + + // ----- Tenant tests ----------------------------------------------------- + + def "get tenant"() { + + when: "get tenant for existing user identifier" + userGroupProvider.getUser("userId") >> new AuthUser.Builder().identifier("userId").identity("username").build() + def userResult = authorizationService.getTenant("userId") + + then: "user with identifier is returned as DTO" + with(userResult) { + identifier == "userId" + identity == "username" + } + + + when: "get tenant for existing group identifier" + userGroupProvider.getGroup("groupId") >> new Group.Builder().identifier("groupId").name("groupname").build() + def groupResult = authorizationService.getTenant("groupId") + + then: "group with identifier is returned as DTO" + with(groupResult) { + identifier == "groupId" + identity == "groupname" + } + + + when: "get tenant for non-existent identifier" + userGroupProvider.getUser("id") >> null + userGroupProvider.getGroup("id") >> null + def result = authorizationService.getTenant("id") + + then: "null is returned" + result == null + + } + + // ----- User tests ------------------------------------------------------- + + def "create user"() { + + setup: + userGroupProvider.addUser(!null as AuthUser) >> { + AuthUser u -> new AuthUser.Builder().identifier(u.identifier).identity(u.identity).build() + } + userGroupProvider.getGroups() >> new HashSet() // needed for converting user to DTO + accessPolicyProvider.getAccessPolicies() >> new HashSet() // needed for converting user to DTO + + when: "new user is created successfully" + def user = new User(null, "username") + User createdUser = authorizationService.createUser(user) + + then: "created user has been assigned an identifier" + with(createdUser) { + identifier != null + identity == "username" + } + + } + + def "list users"() { + + setup: + userGroupProvider.getUsers() >> [ + new AuthUser.Builder().identifier("user1").identity("username1").build(), + new AuthUser.Builder().identifier("user2").identity("username2").build(), + new AuthUser.Builder().identifier("user3").identity("username3").build(), + ] + userGroupProvider.getGroups() >> new HashSet() + accessPolicyProvider.getAccessPolicies() >> new HashSet() + + when: "list of users is queried" + def users = authorizationService.getUsers() + + then: "users are successfully returned as list of DTO objects" + users != null + users.size() == 3 + with(users[0]) { + identifier == "user1" + identity == "username1" + } + with(users[1]) { + identifier == "user2" + identity == "username2" + } + with(users[2]) { + identifier == "user3" + identity == "username3" + } + + } + + def "get user"() { + + setup: + userGroupProvider.getGroups() >> new HashSet() + accessPolicyProvider.getAccessPolicies() >> new HashSet() + + + when: "get user for existing user identifier" + userGroupProvider.getUser("userId") >> new AuthUser.Builder().identifier("userId").identity("username").build() + def user1 = authorizationService.getUser("userId") + + then: "user is returned converted to DTO" + with(user1) { + identifier == "userId" + identity == "username" + } + + + when: "get user for non-existent user identifier" + userGroupProvider.getUser("nonExistentUserId") >> null + userGroupProvider.getGroup("nonExistentUserId") >> null + def user2 = authorizationService.getUser("nonExistentUserId") + + then: "no user is returned" + user2 == null + + } + + def "update user"() { + + setup: + userGroupProvider.updateUser(!null as AuthUser) >> { + AuthUser u -> new AuthUser.Builder().identifier(u.identifier).identity(u.identity).build() + } + userGroupProvider.getGroups() >> new HashSet() + accessPolicyProvider.getAccessPolicies() >> new HashSet() + + + when: "user is updated" + def user = authorizationService.updateUser(new User("userId", "username")) + + then: "updated user is returned" + with(user) { + identifier == "userId" + identity == "username" + } + + } + + def "delete user"() { + + setup: + userGroupProvider.getUser("userId") >> new AuthUser.Builder().identifier("userId").identity("username").build() + userGroupProvider.deleteUser("userId") >> new AuthUser.Builder().identifier("userId").identity("username").build() + userGroupProvider.getGroups() >> new HashSet() + accessPolicyProvider.getAccessPolicies() >> new HashSet() + + + when: "user is deleted" + def user = authorizationService.deleteUser("userId") + + then: "deleted user is returned converted to DTO" + with(user) { + identifier == "userId" + identity == "username" + } + + } + + // ----- User Group tests ------------------------------------------------- + + def "create user group"() { + + setup: + userGroupProvider.addGroup(!null as Group) >> { + Group g -> new Group.Builder().identifier(g.identifier).name(g.name).build() + } + accessPolicyProvider.getAccessPolicies() >> new HashSet() // needed for converting to DTO + + when: "new group is created successfully" + def group = new UserGroup(null, "groupName") + UserGroup createdGroup = authorizationService.createUserGroup(group) + + then: "created group has been assigned an identifier" + with(createdGroup) { + identifier != null + identity == "groupName" + } + + } + + def "list user groups"() { + + setup: + userGroupProvider.getGroups() >> [ + new Group.Builder().identifier("groupId1").name("groupName1").build(), + new Group.Builder().identifier("groupId2").name("groupName2").build(), + new Group.Builder().identifier("groupId3").name("groupName3").build(), + ] + accessPolicyProvider.getAccessPolicies() >> new HashSet() + + when: "list of groups is queried" + def groups = authorizationService.getUserGroups() + + then: "groups are successfully returned as list of DTO objects" + groups != null + groups.size() == 3 + with(groups[0]) { + identifier == "groupId1" + identity == "groupName1" + } + with(groups[1]) { + identifier == "groupId2" + identity == "groupName2" + } + with(groups[2]) { + identifier == "groupId3" + identity == "groupName3" + } + + } + + def "get user group"() { + + setup: + accessPolicyProvider.getAccessPolicies() >> new HashSet() + + + when: "get group for existing user identifier" + userGroupProvider.getGroup("groupId") >> new Group.Builder().identifier("groupId").name ("groupName").build() + def g1 = authorizationService.getUserGroup("groupId") + + then: "group is returned converted to DTO" + with(g1) { + identifier == "groupId" + identity == "groupName" + } + + + when: "get group for non-existent group identifier" + userGroupProvider.getUser("nonExistentId") >> null + userGroupProvider.getGroup("nonExistentId") >> null + def g2 = authorizationService.getUserGroup("nonExistentId") + + then: "no group is returned" + g2 == null + + } + + def "update user group"() { + + setup: + userGroupProvider.updateGroup(!null as Group) >> { + Group g -> new Group.Builder().identifier(g.identifier).name(g.name).build() + } + accessPolicyProvider.getAccessPolicies() >> new HashSet() + + + when: "group is updated" + def group = authorizationService.updateUserGroup(new UserGroup("id", "name")) + + then: "updated group is returned converted to DTO" + with(group) { + identifier == "id" + identity == "name" + } + + } + + def "delete user group"() { + + setup: + userGroupProvider.getGroup("id") >> new Group.Builder().identifier("id").name("name").build() + userGroupProvider.deleteGroup("id") >> new Group.Builder().identifier("id").name("name").build() + accessPolicyProvider.getAccessPolicies() >> new HashSet() + + + when: "group is deleted" + def group = authorizationService.deleteUserGroup("id") + + then: "deleted user is returned" + with(group) { + identifier == "id" + identity == "name" + } + + } + + // ----- Access Policy tests ---------------------------------------------- + + def "create access policy"() { + + setup: + accessPolicyProvider.addAccessPolicy(!null as AuthAccessPolicy) >> { + AuthAccessPolicy p -> new AuthAccessPolicy.Builder() + .identifier(p.identifier) + .resource(p.resource) + .action(p.action) + .addGroups(p.groups) + .addUsers(p.users) + .build() + } + accessPolicyProvider.isConfigurable(_ as AuthAccessPolicy) >> true + + + when: "new access policy is created successfully" + def createdPolicy = authorizationService.createAccessPolicy(new AccessPolicy([resource: "/resource", action: "read"])) + + then: "created policy has been assigned an identifier" + with(createdPolicy) { + identifier != null + resource == "/resource" + action == "read" + configurable == true + } + + } + + def "list access policies"() { + + setup: + accessPolicyProvider.getAccessPolicies() >> [ + new AuthAccessPolicy.Builder().identifier("ap1").resource("r1").action(RequestAction.READ).build(), + new AuthAccessPolicy.Builder().identifier("ap2").resource("r2").action(RequestAction.WRITE).build() + ] + + when: "list access polices is queried" + def policies = authorizationService.getAccessPolicies() + + then: "access policies are successfully returned as list of DTO objects" + policies != null + policies.size() == 2 + with(policies[0]) { + identifier == "ap1" + resource == "r1" + action == RequestAction.READ.toString() + } + with(policies[1]) { + identifier == "ap2" + resource == "r2" + action == RequestAction.WRITE.toString() + } + + } + + def "get access policy"() { + + when: "get policy for existing identifier" + accessPolicyProvider.getAccessPolicy("id") >> new AuthAccessPolicy.Builder() + .identifier("id") + .resource("/resource") + .action(RequestAction.READ) + .build() + def p1 = authorizationService.getAccessPolicy("id") + + then: "policy is returned converted to DTO" + with(p1) { + identifier == "id" + resource == "/resource" + action == RequestAction.READ.toString() + } + + + when: "get policy for non-existent identifier" + accessPolicyProvider.getAccessPolicy("nonExistentId") >> null + def p2 = authorizationService.getAccessPolicy("nonExistentId") + + then: "no policy is returned" + p2 == null + + } + + + def "update access policy"() { + + setup: + def users = [ + "user1": "alice", + "user2": "bob", + "user3": "charlie" ] + def groups = [ + "group1": "users", + "group2": "devs", + "group3": "admins" ] + def policies = [ + "policy1": [ + "resource": "/resource1", + "action": "read", + "users": [ "user1" ], + "groups": [] + ] + ] + def mapDtoUser = { String id -> new User(id, users[id])} + def mapDtoGroup = { String id -> new UserGroup(id, groups[id])} + def mapAuthUser = { String id -> new AuthUser.Builder().identifier(id).identity(users[id]).build() } + def mapAuthGroup = { String id -> new Group.Builder().identifier(id).name(groups[id]).build() } + def mapAuthAccessPolicy = { + String id -> return new AuthAccessPolicy.Builder() + .identifier(id) + .resource(policies[id]["resource"] as String) + .action(RequestAction.valueOfValue(policies[id]["action"] as String)) + .addUsers(policies[id]["users"] as Set) + .addGroups(policies[id]["groups"] as Set) + .build() + } + userGroupProvider.getUser(!null as String) >> { String id -> users.containsKey(id) ? mapAuthUser(id) : null } + userGroupProvider.getGroup(!null as String) >> { String id -> groups.containsKey(id) ? mapAuthGroup(id) : null } + userGroupProvider.getUsers() >> { + def authUsers = [] + users.each{ k, v -> authUsers.add(new AuthUser.Builder().identifier(k).identity(v).build()) } + return authUsers + } + userGroupProvider.getGroups() >> { + def authGroups = [] + users.each{ k, v -> authGroups.add(new Group.Builder().identifier(k).name(v).build()) } + return authGroups + } + accessPolicyProvider.getAccessPolicy(!null as String) >> { String id -> policies.containsKey(id) ? mapAuthAccessPolicy(id) : null } + accessPolicyProvider.updateAccessPolicy(!null as AuthAccessPolicy) >> { + AuthAccessPolicy p -> new AuthAccessPolicy.Builder() + .identifier(p.identifier) + .resource(p.resource) + .action(p.action) + .addGroups(p.groups) + .addUsers(p.users) + .build() + } + accessPolicyProvider.isConfigurable(_ as AuthAccessPolicy) >> true + + + when: "policy is updated" + def policy = new AccessPolicy([identifier: "policy1", resource: "/resource1", action: "read"]) + policy.addUsers([mapDtoUser("user1"), mapDtoUser("user2")]) + policy.addUserGroups([mapDtoGroup("group1")]) + def p1 = authorizationService.updateAccessPolicy(policy) + + then: "updated group is returned converted to DTO" + p1 != null + p1.users.size() == 2 + def sortedUsers = p1.users.sort{it.identifier} + with(sortedUsers[0]) { + identifier == "user1" + identity == "alice" + } + with(sortedUsers[1]) { + identifier == "user2" + identity == "bob" + } + p1.userGroups.size() == 1 + with(p1.userGroups[0]) { + identifier == "group1" + identity == "users" + } + + + when: "attempt to change policy resource and action" + def p2 = authorizationService.updateAccessPolicy(new AccessPolicy([identifier: "policy1", resource: "/newResource", action: "write"])) + + then: "resource and action are unchanged" + with(p2) { + identifier == "policy1" + resource == "/resource1" + action == "read" + } + + } + + def "delete access policy"() { + + setup: + userGroupProvider.getGroups() >> new HashSet() + userGroupProvider.getUsers() >> new HashSet() + accessPolicyProvider.getAccessPolicy("id") >> { + String id -> new AuthAccessPolicy.Builder() + .identifier("id") + .resource("/resource") + .action(RequestAction.READ) + .addGroups(new HashSet()) + .addUsers(new HashSet()) + .build() + } + accessPolicyProvider.deleteAccessPolicy(!null as String) >> { + String id -> new AuthAccessPolicy.Builder() + .identifier(id) + .resource("/resource") + .action(RequestAction.READ) + .addGroups(new HashSet()) + .addUsers(new HashSet()) + .build() + } + + when: "access policy is deleted" + def policy = authorizationService.deleteAccessPolicy("id") + + then: "deleted policy is returned" + with(policy) { + identifier == "id" + resource == "/resource" + action == RequestAction.READ.toString() + } + + } + + // ----- Resource tests --------------------------------------------------- + + def "get resources"() { + + setup: + def buckets = [ + "b1": [ + "name": "Bucket #1", + "description": "An initial bucket for testing", + "createdTimestamp": 1 + ], + "b2": [ + "name": "Bucket #2", + "description": "A second bucket for testing", + "createdTimestamp": 2 + ], + ] + def mapBucket = { + String id -> new Bucket([ + identifier: id, + name: buckets[id]["name"] as String, + description: buckets[id]["description"] as String]) } + + registryService.getBuckets() >> {[ mapBucket("b1"), mapBucket("b2") ]} + + when: + def resources = authorizationService.getResources() + + then: + resources != null + resources.size() == 7 + def sortedResources = resources.sort{it.identifier} + sortedResources[0].identifier == "/buckets" + sortedResources[1].identifier == "/buckets/b1" + sortedResources[2].identifier == "/buckets/b2" + sortedResources[3].identifier == "/policies" + sortedResources[4].identifier == "/proxy" + sortedResources[5].identifier == "/resources" + sortedResources[6].identifier == "/tenants" + + } + + def "get authorized resources"() { + + setup: + def buckets = [ + "b1": [ + "name": "Bucket #1", + "description": "An initial bucket for testing", + "createdTimestamp": 1 + ], + "b2": [ + "name": "Bucket #2", + "description": "A second bucket for testing", + "createdTimestamp": 2 + ], + ] + def mapBucket = { + String id -> new Bucket([ + identifier: id, + name: buckets[id]["name"] as String, + description: buckets[id]["description"] as String]) } + + registryService.getBuckets() >> {[ mapBucket("b1"), mapBucket("b2") ]} + + def authorized = Mock(Authorizable) + authorized.authorize(_, _, _) >> { return } + def denied = Mock(Authorizable) + denied.authorize(_, _, _) >> { throw new AccessDeniedException("") } + + authorizableLookup.getAuthorizableByResource("/buckets") >> authorized + authorizableLookup.getAuthorizableByResource("/buckets/b1") >> authorized + authorizableLookup.getAuthorizableByResource("/buckets/b2") >> denied + authorizableLookup.getAuthorizableByResource("/policies") >> authorized + authorizableLookup.getAuthorizableByResource("/proxy") >> denied + authorizableLookup.getAuthorizableByResource("/resources") >> authorized + authorizableLookup.getAuthorizableByResource("/tenants") >> authorized + + + when: + def resources = authorizationService.getAuthorizedResources(RequestAction.READ) + + then: + resources != null + resources.size() == 5 + def sortedResources = resources.sort{it.identifier} + sortedResources[0].identifier == "/buckets" + sortedResources[1].identifier == "/buckets/b1" + sortedResources[2].identifier == "/policies" + sortedResources[3].identifier == "/resources" + sortedResources[4].identifier == "/tenants" + + + when: + def filteredResources = authorizationService.getAuthorizedResources(RequestAction.READ, ResourceType.Bucket) + + then: + filteredResources != null + filteredResources.size() == 2 + def sortedFilteredResources = filteredResources.sort{it.identifier} + sortedFilteredResources[0].identifier == "/buckets" + sortedFilteredResources[1].identifier == "/buckets/b1" + + } + + + + + + + + + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/AbstractPolicyBasedAuthorizer.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/AbstractPolicyBasedAuthorizer.java b/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/AbstractPolicyBasedAuthorizer.java index 5313911..718ecc7 100644 --- a/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/AbstractPolicyBasedAuthorizer.java +++ b/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/AbstractPolicyBasedAuthorizer.java @@ -468,6 +468,8 @@ public abstract class AbstractPolicyBasedAuthorizer implements ManagedAuthorizer builder.action(RequestAction.READ); } else if (actions.equals(RequestAction.WRITE.name())) { builder.action(RequestAction.WRITE); + } else if (actions.equals(RequestAction.DELETE.name())) { + builder.action(RequestAction.DELETE); } else { throw new IllegalStateException("Unknown Policy Action: " + actions); } http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/StandardManagedAuthorizer.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/StandardManagedAuthorizer.java b/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/StandardManagedAuthorizer.java deleted file mode 100644 index 8cd4fea..0000000 --- a/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/StandardManagedAuthorizer.java +++ /dev/null @@ -1,264 +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.registry.authorization; - -import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.registry.authorization.exception.UninheritableAuthorizationsException; -import org.apache.nifi.registry.util.PropertyValue; -import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException; -import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException; -import org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.util.Set; - -public class StandardManagedAuthorizer implements ManagedAuthorizer { - - private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); - private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance(); - - private static final String USER_GROUP_PROVIDER_ELEMENT = "userGroupProvider"; - private static final String ACCESS_POLICY_PROVIDER_ELEMENT = "accessPolicyProvider"; - - private AccessPolicyProviderLookup accessPolicyProviderLookup; - private AccessPolicyProvider accessPolicyProvider; - private UserGroupProvider userGroupProvider; - - public StandardManagedAuthorizer() {} - - // exposed for testing to inject mocks - public StandardManagedAuthorizer(AccessPolicyProvider accessPolicyProvider, UserGroupProvider userGroupProvider) { - this.accessPolicyProvider = accessPolicyProvider; - this.userGroupProvider = userGroupProvider; - } - - @Override - public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { - accessPolicyProviderLookup = initializationContext.getAccessPolicyProviderLookup(); - } - - @Override - public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { - final PropertyValue accessPolicyProviderKey = configurationContext.getProperty("Access Policy Provider"); - if (!accessPolicyProviderKey.isSet()) { - throw new AuthorizerCreationException("The Access Policy Provider must be set."); - } - - accessPolicyProvider = accessPolicyProviderLookup.getAccessPolicyProvider(accessPolicyProviderKey.getValue()); - - // ensure the desired access policy provider was found - if (accessPolicyProvider == null) { - throw new AuthorizerCreationException(String.format("Unable to locate configured Access Policy Provider: %s", accessPolicyProviderKey)); - } - - userGroupProvider = accessPolicyProvider.getUserGroupProvider(); - - // ensure the desired access policy provider has a user group provider - if (userGroupProvider == null) { - throw new AuthorizerCreationException(String.format("Configured Access Policy Provider %s does not contain a User Group Provider", accessPolicyProviderKey)); - } - } - - @Override - public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException { - final String resourceIdentifier = request.getResource().getIdentifier(); - final AccessPolicy policy = accessPolicyProvider.getAccessPolicy(resourceIdentifier, request.getAction()); - if (policy == null) { - return AuthorizationResult.resourceNotFound(); - } - - final UserAndGroups userAndGroups = userGroupProvider.getUserAndGroups(request.getIdentity()); - - final User user = userAndGroups.getUser(); - if (user == null) { - return AuthorizationResult.denied(String.format("Unknown user with identity '%s'.", request.getIdentity())); - } - - final Set userGroups = userAndGroups.getGroups(); - if (policy.getUsers().contains(user.getIdentifier()) || containsGroup(userGroups, policy)) { - return AuthorizationResult.approved(); - } - - return AuthorizationResult.denied(request.getExplanationSupplier().get()); - } - - /** - * Determines if the policy contains one of the user's groups. - * - * @param userGroups the set of the user's groups - * @param policy the policy - * @return true if one of the Groups in userGroups is contained in the policy - */ - private boolean containsGroup(final Set userGroups, final AccessPolicy policy) { - if (userGroups == null || userGroups.isEmpty() || policy.getGroups().isEmpty()) { - return false; - } - - for (Group userGroup : userGroups) { - if (policy.getGroups().contains(userGroup.getIdentifier())) { - return true; - } - } - - return false; - } - - @Override - public String getFingerprint() throws AuthorizationAccessException { - XMLStreamWriter writer = null; - final StringWriter out = new StringWriter(); - try { - writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out); - writer.writeStartDocument(); - writer.writeStartElement("managedAuthorizations"); - - writer.writeStartElement(ACCESS_POLICY_PROVIDER_ELEMENT); - if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) { - writer.writeCharacters(((ConfigurableAccessPolicyProvider) accessPolicyProvider).getFingerprint()); - } - writer.writeEndElement(); - - writer.writeStartElement(USER_GROUP_PROVIDER_ELEMENT); - if (userGroupProvider instanceof ConfigurableUserGroupProvider) { - writer.writeCharacters(((ConfigurableUserGroupProvider) userGroupProvider).getFingerprint()); - } - writer.writeEndElement(); - - writer.writeEndElement(); - writer.writeEndDocument(); - writer.flush(); - } catch (XMLStreamException e) { - throw new AuthorizationAccessException("Unable to generate fingerprint", e); - } finally { - if (writer != null) { - try { - writer.close(); - } catch (XMLStreamException e) { - // nothing to do here - } - } - } - - return out.toString(); - } - - @Override - public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { - if (StringUtils.isBlank(fingerprint)) { - return; - } - - final FingerprintHolder fingerprintHolder = parseFingerprint(fingerprint); - - if (StringUtils.isNotBlank(fingerprintHolder.getPolicyFingerprint()) && accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) { - ((ConfigurableAccessPolicyProvider) accessPolicyProvider).inheritFingerprint(fingerprintHolder.getPolicyFingerprint()); - } - - if (StringUtils.isNotBlank(fingerprintHolder.getUserGroupFingerprint()) && userGroupProvider instanceof ConfigurableUserGroupProvider) { - ((ConfigurableUserGroupProvider) userGroupProvider).inheritFingerprint(fingerprintHolder.getUserGroupFingerprint()); - } - } - - @Override - public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { - final FingerprintHolder fingerprintHolder = parseFingerprint(proposedFingerprint); - - if (StringUtils.isNotBlank(fingerprintHolder.getPolicyFingerprint())) { - if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) { - ((ConfigurableAccessPolicyProvider) accessPolicyProvider).checkInheritability(fingerprintHolder.getPolicyFingerprint()); - } else { - throw new UninheritableAuthorizationsException("Policy fingerprint is not blank and the configured AccessPolicyProvider does not support fingerprinting."); - } - } - - if (StringUtils.isNotBlank(fingerprintHolder.getUserGroupFingerprint())) { - if (userGroupProvider instanceof ConfigurableUserGroupProvider) { - ((ConfigurableUserGroupProvider) userGroupProvider).checkInheritability(fingerprintHolder.getUserGroupFingerprint()); - } else { - throw new UninheritableAuthorizationsException("User/Group fingerprint is not blank and the configured UserGroupProvider does not support fingerprinting."); - } - } - } - - private final FingerprintHolder parseFingerprint(final String fingerprint) throws AuthorizationAccessException { - final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8); - - try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) { - final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder(); - final Document document = docBuilder.parse(in); - final Element rootElement = document.getDocumentElement(); - - final NodeList accessPolicyProviderList = rootElement.getElementsByTagName(ACCESS_POLICY_PROVIDER_ELEMENT); - if (accessPolicyProviderList.getLength() != 1) { - throw new AuthorizationAccessException(String.format("Only one %s element is allowed: %s", ACCESS_POLICY_PROVIDER_ELEMENT, fingerprint)); - } - - final NodeList userGroupProviderList = rootElement.getElementsByTagName(USER_GROUP_PROVIDER_ELEMENT); - if (userGroupProviderList.getLength() != 1) { - throw new AuthorizationAccessException(String.format("Only one %s element is allowed: %s", USER_GROUP_PROVIDER_ELEMENT, fingerprint)); - } - - final Node accessPolicyProvider = accessPolicyProviderList.item(0); - final Node userGroupProvider = userGroupProviderList.item(0); - return new FingerprintHolder(accessPolicyProvider.getTextContent(), userGroupProvider.getTextContent()); - } catch (SAXException | ParserConfigurationException | IOException e) { - throw new AuthorizationAccessException("Unable to parse fingerprint", e); - } - } - - @Override - public AccessPolicyProvider getAccessPolicyProvider() { - return accessPolicyProvider; - } - - @Override - public void preDestruction() throws AuthorizerDestructionException { - - } - - private static class FingerprintHolder { - private final String policyFingerprint; - private final String userGroupFingerprint; - - public FingerprintHolder(String policyFingerprint, String userGroupFingerprint) { - this.policyFingerprint = policyFingerprint; - this.userGroupFingerprint = userGroupFingerprint; - } - - public String getPolicyFingerprint() { - return policyFingerprint; - } - - public String getUserGroupFingerprint() { - return userGroupFingerprint; - } - } -} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/AuthorizationsHolder.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/AuthorizationsHolder.java b/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/AuthorizationsHolder.java index 0c219d3..38a1571 100644 --- a/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/AuthorizationsHolder.java +++ b/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/AuthorizationsHolder.java @@ -101,6 +101,8 @@ public class AuthorizationsHolder { builder.action(RequestAction.READ); } else if (authorizationCode.equals(FileAccessPolicyProvider.WRITE_CODE)){ builder.action(RequestAction.WRITE); + } else if (authorizationCode.equals(FileAccessPolicyProvider.DELETE_CODE)){ + builder.action(RequestAction.DELETE); } else { throw new IllegalStateException("Unknown Policy Action: " + authorizationCode); } http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileAccessPolicyProvider.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileAccessPolicyProvider.java b/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileAccessPolicyProvider.java index d17edec..50184dd 100644 --- a/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileAccessPolicyProvider.java +++ b/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileAccessPolicyProvider.java @@ -105,21 +105,24 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide private static final String RESOURCE_ATTR = "resource"; private static final String ACTIONS_ATTR = "actions"; + /* These codes must match the enumeration values set in authorizations.xsd */ static final String READ_CODE = "R"; static final String WRITE_CODE = "W"; - /* TODO, add DELETE_CODE */ + static final String DELETE_CODE = "D"; /* TODO - move this somewhere into nifi-registry-security-framework so it can be applied to any ConfigurableAccessPolicyProvider * (and also gets us away from requiring magic strings here) */ private static final ResourceActionPair[] INITIAL_ADMIN_ACCESS_POLICIES = { new ResourceActionPair("/resources", READ_CODE), - new ResourceActionPair("/resources", WRITE_CODE), new ResourceActionPair("/tenants", READ_CODE), new ResourceActionPair("/tenants", WRITE_CODE), + new ResourceActionPair("/tenants", DELETE_CODE), new ResourceActionPair("/policies", READ_CODE), new ResourceActionPair("/policies", WRITE_CODE), + new ResourceActionPair("/policies", DELETE_CODE), new ResourceActionPair("/buckets", READ_CODE), new ResourceActionPair("/buckets", WRITE_CODE), + new ResourceActionPair("/buckets", DELETE_CODE), }; static final String PROP_NODE_IDENTITY_PREFIX = "Node Identity "; @@ -406,6 +409,8 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide builder.action(RequestAction.READ); } else if (actions.equals(RequestAction.WRITE.name())) { builder.action(RequestAction.WRITE); + } else if (actions.equals(RequestAction.DELETE.name())) { + builder.action(RequestAction.DELETE); } else { throw new IllegalStateException("Unknown Policy Action: " + actions); } @@ -566,6 +571,8 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide builder.action(RequestAction.READ); } else if (action.equals(WRITE_CODE)) { builder.action(RequestAction.WRITE); + } else if (action.equals(DELETE_CODE)) { + builder.action(RequestAction.DELETE); } else { throw new IllegalStateException("Unknown Policy Action: " + action); } @@ -593,6 +600,9 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide case WRITE: policy.setAction(WRITE_CODE); break; + case DELETE: + policy.setAction(DELETE_CODE); + break; default: break; } http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-security-api-impl/src/main/xsd/authorizations.xsd ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api-impl/src/main/xsd/authorizations.xsd b/nifi-registry-security-api-impl/src/main/xsd/authorizations.xsd index 0ab27a9..2c8f805 100644 --- a/nifi-registry-security-api-impl/src/main/xsd/authorizations.xsd +++ b/nifi-registry-security-api-impl/src/main/xsd/authorizations.xsd @@ -63,6 +63,7 @@ + http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/RequestAction.java ---------------------------------------------------------------------- diff --git a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/RequestAction.java b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/RequestAction.java index 231b9eb..a489ecc 100644 --- a/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/RequestAction.java +++ b/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/authorization/RequestAction.java @@ -23,8 +23,8 @@ import java.util.StringJoiner; */ public enum RequestAction { READ("read"), - WRITE("write"); - // TODO, add DELETE RequestAction feature + WRITE("write"), + DELETE("delete"); private String value; @@ -38,10 +38,12 @@ public enum RequestAction { } public static RequestAction valueOfValue(final String action) { - if (RequestAction.READ.toString().equals(action)) { + if (RequestAction.READ.toString().equalsIgnoreCase(action)) { return RequestAction.READ; - } else if (RequestAction.WRITE.toString().equals(action)) { + } else if (RequestAction.WRITE.toString().equalsIgnoreCase(action)) { return RequestAction.WRITE; + } else if (RequestAction.DELETE.toString().equalsIgnoreCase(action)) { + return RequestAction.DELETE; } else { StringJoiner stringJoiner = new StringJoiner(", "); for(RequestAction ra : RequestAction.values()) { http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java index d72aa94..013d9c9 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java @@ -204,7 +204,7 @@ public class AccessPolicyResource extends AuthorizableApplicationResource { @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404), @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) }) public Response getAccessPolicyForResource( - @ApiParam(value = "The request action.", allowableValues = "read, write" /* todo, +delete */, required = true) + @ApiParam(value = "The request action.", allowableValues = "read, write, delete", required = true) @PathParam("action") final String action, @ApiParam(value = "The resource of the policy.", required = true) @@ -300,7 +300,7 @@ public class AccessPolicyResource extends AuthorizableApplicationResource { final String identifier) { verifyAuthorizerSupportsConfigurablePolicies(); - authorizeAccessToPolicy(RequestAction.WRITE, identifier); + authorizeAccessToPolicy(RequestAction.DELETE, identifier); AccessPolicy deletedPolicy = authorizationService.deleteAccessPolicy(identifier); return generateOkResponse(deletedPolicy).build(); } http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketFlowResource.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketFlowResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketFlowResource.java index a42a739..0bf5b48 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketFlowResource.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketFlowResource.java @@ -211,7 +211,7 @@ public class BucketFlowResource extends AuthorizableApplicationResource { @PathParam("bucketId") final String bucketId, @PathParam("flowId") final String flowId) { - authorizeBucketAccess(RequestAction.WRITE, bucketId); + authorizeBucketAccess(RequestAction.DELETE, bucketId); final VersionedFlow deletedFlow = registryService.deleteFlow(bucketId, flowId); return Response.status(Response.Status.OK).entity(deletedFlow).build(); } http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketResource.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketResource.java index 907a63a..86c3262 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketResource.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketResource.java @@ -217,7 +217,7 @@ public class BucketResource extends AuthorizableApplicationResource { if (StringUtils.isBlank(bucketId)) { throw new BadRequestException("Bucket id cannot be blank"); } - authorizeBucketAccess(RequestAction.WRITE, bucketId); + authorizeBucketAccess(RequestAction.DELETE, bucketId); final Bucket deletedBucket = registryService.deleteBucket(bucketId); return Response.status(Response.Status.OK).entity(deletedBucket).build(); } http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/TenantResource.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/TenantResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/TenantResource.java index 6b02938..f6fd15f 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/TenantResource.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/TenantResource.java @@ -257,7 +257,7 @@ public class TenantResource extends AuthorizableApplicationResource { @PathParam("id") final String identifier) { verifyAuthorizerSupportsConfigurableUserGroups(); - authorizeAccess(RequestAction.WRITE); + authorizeAccess(RequestAction.DELETE); final User user = authorizationService.deleteUser(identifier); return generateOkResponse(user).build(); @@ -449,7 +449,7 @@ public class TenantResource extends AuthorizableApplicationResource { @PathParam("id") final String identifier) { verifyAuthorizerSupportsConfigurableUserGroups(); - authorizeAccess(RequestAction.WRITE); + authorizeAccess(RequestAction.DELETE); final UserGroup userGroup = authorizationService.deleteUserGroup(identifier); return generateOkResponse(userGroup).build(); http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/BadRequestExceptionMapper.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/BadRequestExceptionMapper.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/BadRequestExceptionMapper.java new file mode 100644 index 0000000..7be2a36 --- /dev/null +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/BadRequestExceptionMapper.java @@ -0,0 +1,47 @@ +/* + * 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.registry.web.mapper; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.BadRequestException; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +/** + * Maps exceptions into client responses. + */ +@Provider +public class BadRequestExceptionMapper implements ExceptionMapper { + + private static final Logger logger = LoggerFactory.getLogger(BadRequestExceptionMapper.class); + + @Override + public Response toResponse(BadRequestException exception) { + logger.info(String.format("%s. Returning %s response.", exception, Response.Status.BAD_REQUEST)); + + if (logger.isDebugEnabled()) { + logger.debug(StringUtils.EMPTY, exception); + } + + return Response.status(Response.Status.BAD_REQUEST).entity(exception.getMessage()).type("text/plain").build(); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/870b9478/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/IllegalArgumentExceptionMapper.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/IllegalArgumentExceptionMapper.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/IllegalArgumentExceptionMapper.java index 3ae44cf..f00cc7d 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/IllegalArgumentExceptionMapper.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/IllegalArgumentExceptionMapper.java @@ -25,7 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Maps resource not found exceptions into client responses. + * Maps exceptions into client responses. */ @Provider public class IllegalArgumentExceptionMapper implements ExceptionMapper {