From 55524cc7d2f45c9c17dab02627e7c29159acbe28 Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Fri, 26 May 2017 15:02:44 -0400 Subject: [PATCH 1/2] NIFI-3653: - Introducing UserGroup and Policy provider interfaces. - Introducing FileUserGroupProvider and FileAccessPolicyProvider. - Refactoring FileAuthorizer to utilize the file based implementations. - Introducing the StandardManagedAuthorizer. - Decorating the configured ManagedAuthorizer to ensure integrity checks are still performed. - Loading user groups if possible to use during access decisions. - Merging responses for requests for AccessPolicies, Users, and UserGroups. - Adding unit tests as appropriate. --- .../main/asciidoc/administration-guide.adoc | 133 +- .../AbstractPolicyBasedAuthorizer.java | 364 ++++-- .../authorization/AccessPolicyProvider.java | 90 ++ ...ssPolicyProviderInitializationContext.java | 30 + .../AccessPolicyProviderLookup.java | 31 + .../authorization/AuthorizationRequest.java | 19 + .../apache/nifi/authorization/Authorizer.java | 4 + .../AuthorizerConfigurationContext.java | 4 +- .../AuthorizerInitializationContext.java | 11 +- .../ConfigurableAccessPolicyProvider.java | 84 ++ .../ConfigurableUserGroupProvider.java | 115 ++ .../nifi/authorization/ManagedAuthorizer.java | 59 + .../nifi/authorization/UserAndGroups.java | 40 + .../nifi/authorization/UserGroupProvider.java | 108 ++ ...serGroupProviderInitializationContext.java | 37 + .../UserGroupProviderLookup.java | 31 + .../UninheritableAuthorizationsException.java | 28 + .../authorization/resource/Authorizable.java | 2 + .../nifi/authorization/user/NiFiUser.java | 7 + .../TestAbstractPolicyBasedAuthorizer.java | 211 --- .../nifi-framework/nifi-authorizer/pom.xml | 18 +- .../AccessPolicyProviderFactory.java | 179 +++ .../AuthorizerCapabilityDetection.java | 45 + .../nifi/authorization/AuthorizerFactory.java | 426 ++++++ .../authorization/AuthorizerFactoryBean.java | 332 ++--- .../UserGroupProviderFactory.java | 228 ++++ .../src/main/xsd/authorizers.xsd | 24 +- .../authorization/AuthorizerFactoryTest.java | 264 ++++ .../MockPolicyBasedAuthorizer.java | 183 +++ .../web/api/dto/FlowConfigurationDTO.java | 33 + .../web/api/entity/AccessPolicyEntity.java | 2 +- .../nifi/web/api/entity/TenantsEntity.java | 2 +- .../nifi/web/api/entity/UserEntity.java | 2 +- .../nifi/web/api/entity/UserGroupEntity.java | 5 +- .../nifi/web/api/entity/UserGroupsEntity.java | 2 +- .../nifi-file-authorizer/pom.xml | 15 +- .../authorization/AuthorizationsHolder.java | 209 +-- .../FileAccessPolicyProvider.java | 948 ++++++++++++++ .../nifi/authorization/FileAuthorizer.java | 1151 ++--------------- .../authorization/FileUserGroupProvider.java | 820 ++++++++++++ .../nifi/authorization/IdentifierUtil.java | 35 + .../nifi/authorization/UserGroupHolder.java | 239 ++++ ...he.nifi.authorization.AccessPolicyProvider | 15 + ...pache.nifi.authorization.UserGroupProvider | 15 + .../FileAccessPolicyProviderTest.java | 1050 +++++++++++++++ .../authorization/FileAuthorizerTest.java | 125 +- .../FileUserGroupProviderTest.java | 698 ++++++++++ ...andardAuthorizerInitializationContext.java | 16 +- .../StandardManagedAuthorizer.java | 251 ++++ .../authorization/user/StandardNiFiUser.java | 132 +- .../authorization/util/UserGroupUtil.java | 54 + .../org.apache.nifi.authorization.Authorizer | 15 + .../StandardManagedAuthorizerTest.java | 438 +++++++ .../resource/DataAuthorizableTest.java | 38 +- .../http/StandardHttpResponseMapper.java | 12 + .../endpoints/AccessPolicyEndpointMerger.java | 61 + .../endpoints/SearchUsersEndpointMerger.java | 59 + .../http/endpoints/UserEndpointMerger.java | 58 + .../endpoints/UserGroupEndpointMerger.java | 58 + .../endpoints/UserGroupsEndpointMerger.java | 76 ++ .../http/endpoints/UsersEndpointMerger.java | 76 ++ .../manager/AccessPolicyEntityMerger.java | 74 ++ .../cluster/manager/UserEntityMerger.java | 76 ++ .../manager/UserGroupEntityMerger.java | 75 ++ .../manager/UserGroupsEntityMerger.java | 39 + .../cluster/manager/UsersEntityMerger.java | 39 + .../AccessPolicyEndpointMergerTest.java | 41 + .../TestThreadPoolRequestReplicator.java | 7 +- .../manager/AccessPolicyEntityMergerTest.java | 96 ++ .../cluster/manager/UserEntityMergerTest.java | 114 ++ .../manager/UserGroupEntityMergerTest.java | 114 ++ .../nifi/cluster/protocol/DataFlow.java | 2 +- .../nifi-framework-core/pom.xml | 4 + .../nifi/controller/StandardFlowService.java | 7 +- .../controller/StandardFlowSynchronizer.java | 41 +- .../org/apache/nifi/nar/ExtensionManager.java | 4 + .../nifi/nar/NarThreadContextClassLoader.java | 4 + .../nifi-framework/nifi-resources/pom.xml | 2 +- .../src/main/resources/conf/authorizers.xml | 104 +- .../nifi/remote/StandardRootGroupPort.java | 43 +- .../nifi/web/StandardNiFiServiceFacade.java | 1 + .../StandardNiFiWebConfigurationContext.java | 30 +- .../nifi/web/api/AccessPolicyResource.java | 22 +- .../nifi/web/api/ControllerResource.java | 1 + .../apache/nifi/web/api/CountersResource.java | 1 + .../org/apache/nifi/web/api/FlowResource.java | 2 + .../nifi/web/api/ProvenanceResource.java | 1 + .../apache/nifi/web/api/ResourceResource.java | 1 + .../nifi/web/api/SiteToSiteResource.java | 1 + .../web/api/SystemDiagnosticsResource.java | 1 + .../apache/nifi/web/api/TenantsResource.java | 48 +- .../config/AccessDeniedExceptionMapper.java | 2 +- .../apache/nifi/web/api/dto/DtoFactory.java | 6 +- .../apache/nifi/web/dao/AccessPolicyDAO.java | 4 +- .../StandardPolicyBasedAuthorizerDAO.java | 270 ++-- .../web/StandardNiFiServiceFacadeSpec.groovy | 30 +- ...tandardPolicyBasedAuthorizerDAOSpec.groovy | 116 +- .../audit/TestRemoteProcessGroupAuditor.java | 4 +- .../web/StandardNiFiServiceFacadeTest.java | 12 +- .../revision/TestNaiveRevisionManager.java | 10 +- .../security/NiFiAuthenticationProvider.java | 9 +- .../jwt/JwtAuthenticationProvider.java | 13 +- .../otp/OtpAuthenticationProvider.java | 10 +- .../x509/X509AuthenticationProvider.java | 26 +- .../resources/nifi-web-security-context.xml | 12 +- .../NiFiAuthenticationProviderTest.java | 7 +- .../otp/OtpAuthenticationProviderTest.java | 4 +- .../x509/X509AuthenticationProviderTest.java | 33 +- .../WEB-INF/partials/canvas/canvas-header.jsp | 6 +- .../WEB-INF/partials/canvas/navigation.jsp | 4 +- .../src/main/webapp/css/policy-management.css | 2 + .../nifi-web-ui/src/main/webapp/css/users.css | 1 + .../js/nf/canvas/nf-canvas-bootstrap.js | 2 + .../webapp/js/nf/canvas/nf-canvas-utils.js | 14 + .../src/main/webapp/js/nf/canvas/nf-canvas.js | 34 + .../webapp/js/nf/canvas/nf-context-menu.js | 2 +- .../js/nf/canvas/nf-controller-services.js | 2 +- .../js/nf/canvas/nf-policy-management.js | 84 +- .../main/webapp/js/nf/canvas/nf-settings.js | 2 +- .../js/nf/templates/nf-templates-table.js | 2 +- .../main/webapp/js/nf/users/nf-users-table.js | 36 +- .../src/main/webapp/js/nf/users/nf-users.js | 18 +- .../TestPersistentProvenanceRepository.java | 77 +- .../index/lucene/TestLuceneEventIndex.java | 42 +- .../TestVolatileProvenanceRepository.java | 7 + pom.xml | 2 +- 126 files changed, 9366 insertions(+), 2351 deletions(-) create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProvider.java create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderInitializationContext.java create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderLookup.java create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableAccessPolicyProvider.java create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableUserGroupProvider.java create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/authorization/ManagedAuthorizer.java create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserAndGroups.java create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProvider.java create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderInitializationContext.java create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderLookup.java create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/authorization/exception/UninheritableAuthorizationsException.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderFactory.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerCapabilityDetection.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactory.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupProviderFactory.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/AuthorizerFactoryTest.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/IdentifierUtil.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupHolder.java create mode 100755 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.AccessPolicyProvider create mode 100755 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAccessPolicyProviderTest.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/UserGroupUtil.java create mode 100755 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMerger.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/SearchUsersEndpointMerger.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserEndpointMerger.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupEndpointMerger.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupsEndpointMerger.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UsersEndpointMerger.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMerger.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserEntityMerger.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupEntityMerger.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupsEntityMerger.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UsersEntityMerger.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMergerTest.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMergerTest.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserEntityMergerTest.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserGroupEntityMergerTest.java diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc index bc823bcc6aa4..95697c681b4e 100644 --- a/nifi-docs/src/main/asciidoc/administration-guide.adoc +++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc @@ -385,7 +385,32 @@ Authorizers are configured using two properties in the 'nifi.properties' file: Authorizers.xml Setup ~~~~~~~~~~~~~~~~~~~~~ -The 'authorizers.xml' file is used to define and configure available authorizers. The default authorizer is the FileAuthorizer, however, you can develop additional authorizers as extensions. The FileAuthorizer has the following properties: +The 'authorizers.xml' file is used to define and configure available authorizers. The default authorizer is the StandardManagedAuthorizer. The managed authorizer is comprised of a UserGroupProvider +and a AccessPolicyProvider. The users, group, and access policies will be loaded and optionally configured through these providers. The managed authorizer will make all access decisions based on +these provided users, groups, and access policies. + +The default UserGroupProvider is the FileUserGroupProvider, however, you can develop additional UserGroupProviders as extensions. The FileUserGroupProvider has the following properties: + +* Users File - The file where the FileUserGroupProvider stores users and groups. By default, the 'users.xml' in the 'conf' directory is chosen. +* Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically be used to load the users and groups into the Users File. +* Initial User Identity - The identity of a users and systems to seed the Users File. The name of each property must be unique, for example: "Initial User Identity A", "Initial User Identity B", "Initial User Identity C" or "Initial User Identity 1", "Initial User Identity 2", "Initial User Identity 3" + +The default AccessPolicyProvider is the FileAccessPolicyProvider, however, you can develop additional AccessPolicyProvider as extensions. The FileAccessPolicyProvider has the following properties: + +* User Group Provider - The identifier for an User Group Provider defined above that will be used to access users and groups for use in the managed access policies. +* Authorizations File - The file where the FileAuthorizer will store policies. +* Initial Admin Identity - The identity of an initial admin user that will be granted access to the UI and given the ability to create additional users, groups, and policies. The value of this property could be a DN when using certificates or LDAP, or a Kerberos principal. This property will only be used when there are no other policies defined. If this property is specified then a Legacy Authorized Users File can not be specified. +* Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically converted to the new authorizations model. If this property is specified then an Initial Admin Identity can not be specified, and this property will only be used when there are no other users, groups, and policies defined. +* Node Identity - The identity of a NiFi cluster node. When clustered, a property for each node should be defined, so that every node knows about every other node. If not clustered these properties can be ignored. The name of each property must be unique, for example for a three node cluster: "Node Identity A", "Node Identity B", "Node Identity C" or "Node Identity 1", "Node Identity 2", "Node Identity 3" + +The identities configured in the Initial Admin Identity, the Node Identity properties, or discovered in a Legacy Authorized Users File must be available in the configured User Group Provider. + +The default authorizer is the StandardManagedAuthorizer, however, you can develop additional authorizers as extensions. The StandardManagedAuthorizer has the following properties: + +* Access Policy Provider - The identifier for an Access Policy Provider defined above. + +The FileAuthorizer has been replaced with the more granular StandardManagedAuthorizer approach described above. However, it is still available for backwards compatibility reasons. The +FileAuthorizer has the following properties. * Authorizations File - The file where the FileAuthorizer stores policies. By default, the 'authorizations.xml' in the 'conf' directory is chosen. * Users File - The file where the FileAuthorizer stores users and groups. By default, the 'users.xml' in the 'conf' directory is chosen. @@ -402,17 +427,29 @@ If you are setting up a secured NiFi instance for the first time, you must manua Here is an example LDAP entry using the name John Smith: ---- - - file-provider - org.apache.nifi.authorization.FileAuthorizer - ./conf/authorizations.xml + + + file-user-group-provider + org.apache.nifi.authorization.FileUserGroupProvider ./conf/users.xml + + + cn=John Smith,ou=people,dc=example,dc=com + + + file-access-policy-provider + org.apache.nifi.authorization.FileAccessPolicyProvider + file-user-group-provider + ./conf/authorizations.xml cn=John Smith,ou=people,dc=example,dc=com - + + + managed-authorizer + org.apache.nifi.authorization.StandardManagedAuthorizer + file-access-policy-provider ---- @@ -420,17 +457,29 @@ Here is an example LDAP entry using the name John Smith: Here is an example Kerberos entry using the name John Smith and realm `NIFI.APACHE.ORG`: ---- - - file-provider - org.apache.nifi.authorization.FileAuthorizer - ./conf/authorizations.xml + + + file-user-group-provider + org.apache.nifi.authorization.FileUserGroupProvider ./conf/users.xml + + + johnsmith@NIFI.APACHE.ORG + + + file-access-policy-provider + org.apache.nifi.authorization.FileAccessPolicyProvider + file-user-group-provider + ./conf/authorizations.xml johnsmith@NIFI.APACHE.ORG - + + + managed-authorizer + org.apache.nifi.authorization.StandardManagedAuthorizer + file-access-policy-provider ---- @@ -449,13 +498,28 @@ Here is an example entry: ---- - - file-provider - org.apache.nifi.authorization.FileAuthorizer - ./conf/authorizations.xml + + file-user-group-provider + org.apache.nifi.authorization.FileUserGroupProvider ./conf/users.xml + /Users/johnsmith/config_files/authorized-users.xml + + + + + file-access-policy-provider + org.apache.nifi.authorization.FileAccessPolicyProvider + file-user-group-provider + ./conf/authorizations.xml /Users/johnsmith/config_files/authorized-users.xml + + + + + managed-authorizer + org.apache.nifi.authorization.StandardManagedAuthorizer + file-access-policy-provider ---- @@ -514,15 +578,32 @@ cn=nifi-2,ou=people,dc=example,dc=com ---- ---- - - file-provider - org.apache.nifi.authorization.FileAuthorizer - ./conf/authorizations.xml + + + file-user-group-provider + org.apache.nifi.authorization.FileUserGroupProvider ./conf/users.xml + + + johnsmith@NIFI.APACHE.ORG + cn=nifi-1,ou=people,dc=example,dc=com + cn=nifi-2,ou=people,dc=example,dc=com + + + file-access-policy-provider + org.apache.nifi.authorization.FileAccessPolicyProvider + file-user-group-provider + ./conf/authorizations.xml johnsmith@NIFI.APACHE.ORG + cn=nifi-1,ou=people,dc=example,dc=com cn=nifi-2,ou=people,dc=example,dc=com + + + managed-authorizer + org.apache.nifi.authorization.StandardManagedAuthorizer + file-access-policy-provider ---- @@ -535,7 +616,11 @@ Now that initial authorizations have been created, additional users, groups and Configuring Users & Access Policies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This section describes: +Depending on the capabilities of the configured UserGroupProvider and AccessPolicyProvider the users, groups, and policies will be configurable in the UI. If the extensions are not configurable the +users, groups, and policies will read-only in the UI. If the configured authorizer does not use UserGroupProvider and AccessPolicyProvider the users and policies may or may not be visible and +configurable in the UI based on the underlying implementation. + +This section assumes the users, groups, and policies are configurable in the UI and describes: * How to create users and groups * How access policies are used to define authorizations diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AbstractPolicyBasedAuthorizer.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AbstractPolicyBasedAuthorizer.java index 0d047f188158..929e2ad92115 100644 --- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AbstractPolicyBasedAuthorizer.java +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AbstractPolicyBasedAuthorizer.java @@ -18,6 +18,8 @@ import org.apache.nifi.authorization.exception.AuthorizationAccessException; import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.authorization.exception.AuthorizerDestructionException; +import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -43,7 +45,7 @@ /** * An Authorizer that provides management of users, groups, and policies. */ -public abstract class AbstractPolicyBasedAuthorizer implements Authorizer { +public abstract class AbstractPolicyBasedAuthorizer implements ManagedAuthorizer { static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance(); @@ -60,32 +62,9 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer { static final String RESOURCE_ATTR = "resource"; static final String ACTIONS_ATTR = "actions"; - public static final String EMPTY_FINGERPRINT = "EMPTY"; - @Override public final void onConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { doOnConfigured(configurationContext); - - // ensure that only one policy per resource-action exists - for (AccessPolicy accessPolicy : getAccessPolicies()) { - if (policyExists(accessPolicy)) { - throw new AuthorizerCreationException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction())); - } - } - - // ensure that only one group exists per identity - for (User user : getUsers()) { - if (tenantExists(user.getIdentifier(), user.getIdentity())) { - throw new AuthorizerCreationException(String.format("Found multiple users/user groups with identity '%s'.", user.getIdentity())); - } - } - - // ensure that only one group exists per identity - for (Group group : getGroups()) { - if (tenantExists(group.getIdentifier(), group.getName())) { - throw new AuthorizerCreationException(String.format("Found multiple users/user groups with name '%s'.", group.getName())); - } - } } /** @@ -96,48 +75,6 @@ public final void onConfigured(final AuthorizerConfigurationContext configuratio */ protected abstract void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException; - /** - * Checks if another policy exists with the same resource and action as the given policy. - * - * @param checkAccessPolicy an access policy being checked - * @return true if another access policy exists with the same resource and action, false otherwise - */ - private boolean policyExists(final AccessPolicy checkAccessPolicy) { - for (AccessPolicy accessPolicy : getAccessPolicies()) { - if (!accessPolicy.getIdentifier().equals(checkAccessPolicy.getIdentifier()) - && accessPolicy.getResource().equals(checkAccessPolicy.getResource()) - && accessPolicy.getAction().equals(checkAccessPolicy.getAction())) { - return true; - } - } - return false; - } - - /** - * Checks if another user exists with the same identity. - * - * @param identifier identity of the user - * @param identity identity of the user - * @return true if another user exists with the same identity, false otherwise - */ - private boolean tenantExists(final String identifier, final String identity) { - for (User user : getUsers()) { - if (!user.getIdentifier().equals(identifier) - && user.getIdentity().equals(identity)) { - return true; - } - } - - for (Group group : getGroups()) { - if (!group.getIdentifier().equals(identifier) - && group.getName().equals(identity)) { - return true; - } - } - - return false; - } - @Override public final AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException { final UsersAndAccessPolicies usersAndAccessPolicies = getUsersAndAccessPolicies(); @@ -191,9 +128,6 @@ private boolean containsGroup(final Set userGroups, final AccessPolicy po * @throws IllegalStateException if a group with the same name already exists */ public final synchronized Group addGroup(Group group) throws AuthorizationAccessException { - if (tenantExists(group.getIdentifier(), group.getName())) { - throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName())); - } return doAddGroup(group); } @@ -224,9 +158,6 @@ public final synchronized Group addGroup(Group group) throws AuthorizationAccess * @throws IllegalStateException if there is already a group with the same name */ public final synchronized Group updateGroup(Group group) throws AuthorizationAccessException { - if (tenantExists(group.getIdentifier(), group.getName())) { - throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName())); - } return doUpdateGroup(group); } @@ -266,9 +197,6 @@ public final synchronized Group updateGroup(Group group) throws AuthorizationAcc * @throws IllegalStateException if there is already a user with the same identity */ public final synchronized User addUser(User user) throws AuthorizationAccessException { - if (tenantExists(user.getIdentifier(), user.getIdentity())) { - throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity())); - } return doAddUser(user); } @@ -308,9 +236,6 @@ public final synchronized User addUser(User user) throws AuthorizationAccessExce * @throws IllegalStateException if there is already a user with the same identity */ public final synchronized User updateUser(final User user) throws AuthorizationAccessException { - if (tenantExists(user.getIdentifier(), user.getIdentity())) { - throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity())); - } return doUpdateUser(user); } @@ -348,9 +273,6 @@ public final synchronized User updateUser(final User user) throws AuthorizationA * @throws AuthorizationAccessException if there was an unexpected error performing the operation */ public final synchronized AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { - if (policyExists(accessPolicy)) { - throw new IllegalStateException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction())); - } return doAddAccessPolicy(accessPolicy); } @@ -406,18 +328,55 @@ public final synchronized AccessPolicy addAccessPolicy(AccessPolicy accessPolicy */ public abstract UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException; + /** + * Returns whether the proposed fingerprint is inheritable. + * + * @param proposedFingerprint the proposed fingerprint + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + * @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable + */ + @Override + public final void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { + try { + // ensure we understand the proposed fingerprint + parsePoliciesUsersAndGroups(proposedFingerprint); + } catch (final AuthorizationAccessException e) { + throw new UninheritableAuthorizationsException("Unable to parse proposed fingerprint: " + e); + } + + final List users = getSortedUsers(); + final List groups = getSortedGroups(); + final List accessPolicies = getSortedAccessPolicies(); + + // ensure we're in a state to inherit + if (!users.isEmpty() || !groups.isEmpty() || !accessPolicies.isEmpty()) { + throw new UninheritableAuthorizationsException("Proposed fingerprint is not inheritable because the current Authorizations is not empty.."); + } + } + /** * Parses the fingerprint and adds any users, groups, and policies to the current Authorizer. * * @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer. */ + @Override public final void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException { if (fingerprint == null || fingerprint.trim().isEmpty()) { return; } - final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8); + final PoliciesUsersAndGroups policiesUsersAndGroups = parsePoliciesUsersAndGroups(fingerprint); + policiesUsersAndGroups.getUsers().forEach(user -> addUser(user)); + policiesUsersAndGroups.getGroups().forEach(group -> addGroup(group)); + policiesUsersAndGroups.getAccessPolicies().forEach(policy -> addAccessPolicy(policy)); + } + + private PoliciesUsersAndGroups parsePoliciesUsersAndGroups(final String fingerprint) { + final List accessPolicies = new ArrayList<>(); + final List users = new ArrayList<>(); + final List groups = new ArrayList<>(); + 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); @@ -427,29 +386,27 @@ public final void inheritFingerprint(final String fingerprint) throws Authorizat NodeList userNodes = rootElement.getElementsByTagName(USER_ELEMENT); for (int i=0; i < userNodes.getLength(); i++) { Node userNode = userNodes.item(i); - User user = parseUser((Element) userNode); - addUser(user); + users.add(parseUser((Element) userNode)); } // parse all the groups and add them to the current authorizer NodeList groupNodes = rootElement.getElementsByTagName(GROUP_ELEMENT); for (int i=0; i < groupNodes.getLength(); i++) { Node groupNode = groupNodes.item(i); - Group group = parseGroup((Element) groupNode); - addGroup(group); + groups.add(parseGroup((Element) groupNode)); } // parse all the policies and add them to the current authorizer NodeList policyNodes = rootElement.getElementsByTagName(POLICY_ELEMENT); for (int i=0; i < policyNodes.getLength(); i++) { Node policyNode = policyNodes.item(i); - AccessPolicy policy = parsePolicy((Element) policyNode); - addAccessPolicy(policy); + accessPolicies.add(parsePolicy((Element) policyNode)); } - } catch (SAXException | ParserConfigurationException | IOException e) { throw new AuthorizationAccessException("Unable to parse fingerprint", e); } + + return new PoliciesUsersAndGroups(accessPolicies, users, groups); } private User parseUser(final Element element) { @@ -503,6 +460,181 @@ private AccessPolicy parsePolicy(final Element element) { return builder.build(); } + @Override + public final AccessPolicyProvider getAccessPolicyProvider() { + return new ConfigurableAccessPolicyProvider() { + @Override + public Set getAccessPolicies() throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.getAccessPolicies(); + } + + @Override + public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.getAccessPolicy(identifier); + } + + @Override + public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.addAccessPolicy(accessPolicy); + } + + @Override + public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.updateAccessPolicy(accessPolicy); + } + + @Override + public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.deleteAccessPolicy(accessPolicy); + } + + @Override + public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException { + final UsersAndAccessPolicies usersAndAccessPolicies = AbstractPolicyBasedAuthorizer.this.getUsersAndAccessPolicies(); + return usersAndAccessPolicies.getAccessPolicy(resourceIdentifier, action); + } + + @Override + public String getFingerprint() throws AuthorizationAccessException { + // fingerprint is managed by the encapsulating class + throw new UnsupportedOperationException(); + } + + @Override + public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { + // fingerprint is managed by the encapsulating class + throw new UnsupportedOperationException(); + } + + @Override + public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { + // fingerprint is managed by the encapsulating class + throw new UnsupportedOperationException(); + } + + @Override + public UserGroupProvider getUserGroupProvider() { + return new ConfigurableUserGroupProvider() { + @Override + public User addUser(User user) throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.addUser(user); + } + + @Override + public User updateUser(User user) throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.updateUser(user); + } + + @Override + public User deleteUser(User user) throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.deleteUser(user); + } + + @Override + public Group addGroup(Group group) throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.addGroup(group); + } + + @Override + public Group updateGroup(Group group) throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.updateGroup(group); + } + + @Override + public Group deleteGroup(Group group) throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.deleteGroup(group); + } + + @Override + public Set getUsers() throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.getUsers(); + } + + @Override + public User getUser(String identifier) throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.getUser(identifier); + } + + @Override + public User getUserByIdentity(String identity) throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.getUserByIdentity(identity); + } + + @Override + public Set getGroups() throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.getGroups(); + } + + @Override + public Group getGroup(String identifier) throws AuthorizationAccessException { + return AbstractPolicyBasedAuthorizer.this.getGroup(identifier); + } + + @Override + public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException { + final UsersAndAccessPolicies usersAndAccessPolicies = AbstractPolicyBasedAuthorizer.this.getUsersAndAccessPolicies(); + final User user = usersAndAccessPolicies.getUser(identity); + final Set groups = usersAndAccessPolicies.getGroups(identity); + + return new UserAndGroups() { + @Override + public User getUser() { + return user; + } + + @Override + public Set getGroups() { + return groups; + } + }; + } + + @Override + public String getFingerprint() throws AuthorizationAccessException { + // fingerprint is managed by the encapsulating class + throw new UnsupportedOperationException(); + } + + @Override + public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { + // fingerprint is managed by the encapsulating class + throw new UnsupportedOperationException(); + } + + @Override + public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { + // fingerprint is managed by the encapsulating class + throw new UnsupportedOperationException(); + } + + @Override + public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException { + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + } + }; + } + + @Override + public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException { + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + } + }; + } + /** * Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be * used for comparison to determine if two policy-based authorizers represent a compatible set of users, @@ -510,17 +642,12 @@ private AccessPolicy parsePolicy(final Element element) { * * @return the fingerprint for this Authorizer */ + @Override public final String getFingerprint() throws AuthorizationAccessException { final List users = getSortedUsers(); final List groups = getSortedGroups(); final List policies = getSortedAccessPolicies(); - // when there are no users, groups, policies we want to always return a simple indicator so - // it can easily be determined when comparing fingerprints - if (users.isEmpty() && groups.isEmpty() && policies.isEmpty()) { - return EMPTY_FINGERPRINT; - } - XMLStreamWriter writer = null; final StringWriter out = new StringWriter(); try { @@ -611,38 +738,43 @@ private void writePolicy(final XMLStreamWriter writer, final AccessPolicy policy private List getSortedAccessPolicies() { final List policies = new ArrayList<>(getAccessPolicies()); - - Collections.sort(policies, new Comparator() { - @Override - public int compare(AccessPolicy p1, AccessPolicy p2) { - return p1.getIdentifier().compareTo(p2.getIdentifier()); - } - }); + Collections.sort(policies, Comparator.comparing(AccessPolicy::getIdentifier)); return policies; } private List getSortedGroups() { final List groups = new ArrayList<>(getGroups()); - - Collections.sort(groups, new Comparator() { - @Override - public int compare(Group g1, Group g2) { - return g1.getIdentifier().compareTo(g2.getIdentifier()); - } - }); + Collections.sort(groups, Comparator.comparing(Group::getIdentifier)); return groups; } private List getSortedUsers() { final List users = new ArrayList<>(getUsers()); - - Collections.sort(users, new Comparator() { - @Override - public int compare(User u1, User u2) { - return u1.getIdentifier().compareTo(u2.getIdentifier()); - } - }); + Collections.sort(users, Comparator.comparing(User::getIdentifier)); return users; } + private static class PoliciesUsersAndGroups { + final List accessPolicies; + final List users; + final List groups; + + public PoliciesUsersAndGroups(List accessPolicies, List users, List groups) { + this.accessPolicies = accessPolicies; + this.users = users; + this.groups = groups; + } + + public List getAccessPolicies() { + return accessPolicies; + } + + public List getUsers() { + return users; + } + + public List getGroups() { + return groups; + } + } } diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProvider.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProvider.java new file mode 100644 index 000000000000..59ada2426596 --- /dev/null +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProvider.java @@ -0,0 +1,90 @@ +/* + * 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; + +import org.apache.nifi.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.authorization.exception.AuthorizerDestructionException; + +import java.util.Set; + +/** + * Provides access to AccessPolicies and the configured UserGroupProvider. + * + * NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to + * make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results. + * + * Additionally, extensions need to be thread safe. + */ +public interface AccessPolicyProvider { + + /** + * Retrieves all access policies. Must be non null + * + * @return a list of policies + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + Set getAccessPolicies() throws AuthorizationAccessException; + + /** + * Retrieves the policy with the given identifier. + * + * @param identifier the id of the policy to retrieve + * @return the policy with the given id, or null if no matching policy exists + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException; + + /** + * Gets the access policies for the specified resource identifier and request action. + * + * @param resourceIdentifier the resource identifier + * @param action the request action + * @return the policy matching the resouce and action, or null if no matching policy exists + * @throws AuthorizationAccessException if there was any unexpected error performing the operation + */ + AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException; + + /** + * Returns the UserGroupProvider for this managed Authorizer. Must be non null + * + * @return the UserGroupProvider + */ + UserGroupProvider getUserGroupProvider(); + + /** + * Called immediately after instance creation for implementers to perform additional setup + * + * @param initializationContext in which to initialize + */ + void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException; + + /** + * Called to configure the Authorizer. + * + * @param configurationContext at the time of configuration + * @throws AuthorizerCreationException for any issues configuring the provider + */ + void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException; + + /** + * Called immediately before instance destruction for implementers to release resources. + * + * @throws AuthorizerDestructionException If pre-destruction fails. + */ + void preDestruction() throws AuthorizerDestructionException; +} diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderInitializationContext.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderInitializationContext.java new file mode 100644 index 000000000000..1013c150a9a9 --- /dev/null +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderInitializationContext.java @@ -0,0 +1,30 @@ +/* + * 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; + +/** + * Initialization content for AccessPolicyProviders. + */ +public interface AccessPolicyProviderInitializationContext extends UserGroupProviderInitializationContext { + + /** + * The lookup for accessing other configured AccessPolicyProviders. + * + * @return The AccessPolicyProvider lookup + */ + AccessPolicyProviderLookup getAccessPolicyProviderLookup(); +} diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderLookup.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderLookup.java new file mode 100644 index 000000000000..d387761f4593 --- /dev/null +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderLookup.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** + * + */ +public interface AccessPolicyProviderLookup { + + /** + * Looks up the AccessPolicyProvider with the specified identifier + * + * @param identifier The identifier of the AccessPolicyProvider + * @return The AccessPolicyProvider + */ + AccessPolicyProvider getAccessPolicyProvider(String identifier); +} diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizationRequest.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizationRequest.java index 4f5a8f8dd465..d5b16007ba00 100644 --- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizationRequest.java +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizationRequest.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.function.Supplier; /** @@ -31,6 +32,7 @@ public class AuthorizationRequest { private final Resource resource; private final String identity; + private final Set groups; private final RequestAction action; private final boolean isAccessAttempt; private final boolean isAnonymous; @@ -46,6 +48,7 @@ private AuthorizationRequest(final Builder builder) { this.resource = builder.resource; this.identity = builder.identity; + this.groups = builder.groups == null ? null : Collections.unmodifiableSet(builder.groups); this.action = builder.action; this.isAccessAttempt = builder.isAccessAttempt; this.isAnonymous = builder.isAnonymous; @@ -81,6 +84,16 @@ public String getIdentity() { return identity; } + /** + * The groups the user making this request belongs to. May be null if this NiFi is not configured to load user + * groups or empty if the user has no groups + * + * @return The groups + */ + public Set getGroups() { + return groups; + } + /** * Whether this is a direct access attempt of the Resource if if it's being checked as part of another response. * @@ -142,6 +155,7 @@ public static final class Builder { private Resource resource; private String identity; + private Set groups; private Boolean isAnonymous; private Boolean isAccessAttempt; private RequestAction action; @@ -159,6 +173,11 @@ public Builder identity(final String identity) { return this; } + public Builder groups(final Set groups) { + this.groups = groups; + return this; + } + public Builder anonymous(final Boolean isAnonymous) { this.isAnonymous = isAnonymous; return this; diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Authorizer.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Authorizer.java index cb8c7f1dc379..28c73ff2d296 100644 --- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Authorizer.java +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Authorizer.java @@ -27,6 +27,10 @@ public interface Authorizer { /** * Determines if the specified user/entity is authorized to access the specified resource within the given context. + * These details are all contained in the AuthorizationRequest. + * + * NOTE: This method will be called often and frequently. Because of this, if the underlying implementation needs to + * make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results. * * @param request The authorization request * @return the authorization result diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java index 3721ab4e57a6..cd126acd6cd1 100644 --- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java @@ -42,9 +42,7 @@ public interface AuthorizerConfigurationContext { /** * @param property to lookup the descriptor and value of - * @return the value the component currently understands for the given - * PropertyDescriptor. This method does not substitute default - * PropertyDescriptor values, so the value returned will be null if not set + * @return the value the component currently understands for the given PropertyDescriptor */ PropertyValue getProperty(String property); } diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerInitializationContext.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerInitializationContext.java index 4b3d77c15c37..179a1790ceb7 100644 --- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerInitializationContext.java +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerInitializationContext.java @@ -19,19 +19,12 @@ /** * Initialization content for Authorizers. */ -public interface AuthorizerInitializationContext { - - /** - * The identifier of the Authorizer. - * - * @return The identifier - */ - public String getIdentifier(); +public interface AuthorizerInitializationContext extends AccessPolicyProviderInitializationContext { /** * The lookup for accessing other configured Authorizers. * * @return The Authorizer lookup */ - public AuthorizerLookup getAuthorizerLookup(); + AuthorizerLookup getAuthorizerLookup(); } diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableAccessPolicyProvider.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableAccessPolicyProvider.java new file mode 100644 index 000000000000..71258c303a35 --- /dev/null +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableAccessPolicyProvider.java @@ -0,0 +1,84 @@ +/* + * 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; + +import org.apache.nifi.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException; + +/** + * Provides support for configuring AccessPolicies. + * + * NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to + * make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results. + * + * Additionally, extensions need to be thread safe. + */ +public interface ConfigurableAccessPolicyProvider extends AccessPolicyProvider { + + /** + * Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be + * used for comparison to determine if two policy-based authorizers represent a compatible set of policies. + * + * @return the fingerprint for this Authorizer + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + String getFingerprint() throws AuthorizationAccessException; + + /** + * Parses the fingerprint and adds any policies to the current AccessPolicyProvider. + * + * @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer. + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException; + + /** + * When the fingerprints are not equal, this method will check if the proposed fingerprint is inheritable. + * If the fingerprint is an exact match, this method will not be invoked as there is nothing to inherit. + * + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + * @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable + */ + void checkInheritability(final String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException; + + /** + * Adds the given policy ensuring that multiple policies can not be added for the same resource and action. + * + * @param accessPolicy the policy to add + * @return the policy that was added + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException; + + /** + * The policy represented by the provided instance will be updated based on the provided instance. + * + * @param accessPolicy an updated policy + * @return the updated policy, or null if no matching policy was found + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException; + + /** + * Deletes the given policy. + * + * @param accessPolicy the policy to delete + * @return the deleted policy, or null if no matching policy was found + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException; +} diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableUserGroupProvider.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableUserGroupProvider.java new file mode 100644 index 000000000000..90c0def7acc7 --- /dev/null +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableUserGroupProvider.java @@ -0,0 +1,115 @@ +/* + * 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; + +import org.apache.nifi.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException; + +/** + * Provides support for configuring Users and Groups. + * + * NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to + * make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results. + * + * Additionally, extensions need to be thread safe. + */ +public interface ConfigurableUserGroupProvider extends UserGroupProvider { + + /** + * Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be + * used for comparison to determine if two policy-based authorizers represent a compatible set of users and/or groups. + * + * @return the fingerprint for this Authorizer + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + String getFingerprint() throws AuthorizationAccessException; + + /** + * Parses the fingerprint and adds any users and groups to the current Authorizer. + * + * @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer. + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException; + + /** + * When the fingerprints are not equal, this method will check if the proposed fingerprint is inheritable. + * If the fingerprint is an exact match, this method will not be invoked as there is nothing to inherit. + * + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + * @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable + */ + void checkInheritability(final String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException; + + /** + * Adds the given user. + * + * @param user the user to add + * @return the user that was added + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + * @throws IllegalStateException if there is already a user with the same identity + */ + User addUser(User user) throws AuthorizationAccessException; + + /** + * The user represented by the provided instance will be updated based on the provided instance. + * + * @param user an updated user instance + * @return the updated user instance, or null if no matching user was found + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + * @throws IllegalStateException if there is already a user with the same identity + */ + User updateUser(final User user) throws AuthorizationAccessException; + + /** + * Deletes the given user. + * + * @param user the user to delete + * @return the user that was deleted, or null if no matching user was found + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + User deleteUser(User user) throws AuthorizationAccessException; + + /** + * Adds a new group. + * + * @param group the Group to add + * @return the added Group + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + * @throws IllegalStateException if a group with the same name already exists + */ + Group addGroup(Group group) throws AuthorizationAccessException; + + /** + * The group represented by the provided instance will be updated based on the provided instance. + * + * @param group an updated group instance + * @return the updated group instance, or null if no matching group was found + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + * @throws IllegalStateException if there is already a group with the same name + */ + Group updateGroup(Group group) throws AuthorizationAccessException; + + /** + * Deletes the given group. + * + * @param group the group to delete + * @return the deleted group, or null if no matching group was found + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + Group deleteGroup(Group group) throws AuthorizationAccessException; +} diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ManagedAuthorizer.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ManagedAuthorizer.java new file mode 100644 index 000000000000..d70ee550a48e --- /dev/null +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ManagedAuthorizer.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.authorization; + +import org.apache.nifi.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException; + +public interface ManagedAuthorizer extends Authorizer { + + /** + * Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be + * used for comparison to determine if two managed authorizers represent a compatible set of users, + * groups, and/or policies. Must be non null + * + * @return the fingerprint for this Authorizer + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + String getFingerprint() throws AuthorizationAccessException; + + /** + * Parses the fingerprint and adds any users, groups, and policies to the current Authorizer. + * + * @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer. + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException; + + /** + * When the fingerprints are not equal, this method will check if the proposed fingerprint is inheritable. + * If the fingerprint is an exact match, this method will not be invoked as there is nothing to inherit. + * + * @param proposedFingerprint the proposed fingerprint + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + * @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable + */ + void checkInheritability(final String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException; + + /** + * Returns the AccessPolicy provider for this managed Authorizer. Must be non null + * + * @return the AccessPolicy provider + */ + AccessPolicyProvider getAccessPolicyProvider(); + +} diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserAndGroups.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserAndGroups.java new file mode 100644 index 000000000000..486eba495b00 --- /dev/null +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserAndGroups.java @@ -0,0 +1,40 @@ +/* + * 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; + +import java.util.Set; + +/** + * A holder object to provide atomic access to a user and their groups. + */ +public interface UserAndGroups { + + /** + * Retrieves the user, or null if the user is unknown + * + * @return the user with the given identity + */ + User getUser(); + + /** + * Retrieves the groups for the user, or null if the user is unknown or has no groups. + * + * @return the set of groups for the given user identity + */ + Set getGroups(); + +} diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProvider.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProvider.java new file mode 100644 index 000000000000..a7b7a0bf32ad --- /dev/null +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProvider.java @@ -0,0 +1,108 @@ +/* + * 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; + +import org.apache.nifi.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.authorization.exception.AuthorizerDestructionException; + +import java.util.Set; + +/** + * Provides access to Users and Groups. + * + * NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to + * make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results. + * + * Additionally, extensions need to be thread safe. + */ +public interface UserGroupProvider { + + /** + * Retrieves all users. Must be non null + * + * @return a list of users + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + Set getUsers() throws AuthorizationAccessException; + + /** + * Retrieves the user with the given identifier. + * + * @param identifier the id of the user to retrieve + * @return the user with the given id, or null if no matching user was found + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + User getUser(String identifier) throws AuthorizationAccessException; + + /** + * Retrieves the user with the given identity. + * + * @param identity the identity of the user to retrieve + * @return the user with the given identity, or null if no matching user was found + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + User getUserByIdentity(String identity) throws AuthorizationAccessException; + + /** + * Retrieves all groups. Must be non null + * + * @return a list of groups + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + Set getGroups() throws AuthorizationAccessException; + + /** + * Retrieves a Group by id. + * + * @param identifier the identifier of the Group to retrieve + * @return the Group with the given identifier, or null if no matching group was found + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + Group getGroup(String identifier) throws AuthorizationAccessException; + + /** + * Gets a user and their groups. Must be non null. If the user is not known the UserAndGroups.getUser() and + * UserAndGroups.getGroups() should return null + * + * @return the UserAndGroups for the specified identity + * @throws AuthorizationAccessException if there was an unexpected error performing the operation + */ + UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException; + + /** + * Called immediately after instance creation for implementers to perform additional setup + * + * @param initializationContext in which to initialize + */ + void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException; + + /** + * Called to configure the Authorizer. + * + * @param configurationContext at the time of configuration + * @throws AuthorizerCreationException for any issues configuring the provider + */ + void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException; + + /** + * Called immediately before instance destruction for implementers to release resources. + * + * @throws AuthorizerDestructionException If pre-destruction fails. + */ + void preDestruction() throws AuthorizerDestructionException; +} diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderInitializationContext.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderInitializationContext.java new file mode 100644 index 000000000000..d83b4b324aba --- /dev/null +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderInitializationContext.java @@ -0,0 +1,37 @@ +/* + * 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; + +/** + * Initialization content for UserGroupProviders. + */ +public interface UserGroupProviderInitializationContext { + + /** + * The identifier of the UserGroupProvider. + * + * @return The identifier + */ + String getIdentifier(); + + /** + * The lookup for accessing other configured UserGroupProviders. + * + * @return The UserGroupProvider lookup + */ + UserGroupProviderLookup getUserGroupProviderLookup(); +} diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderLookup.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderLookup.java new file mode 100644 index 000000000000..b87937488ed4 --- /dev/null +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderLookup.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** + * + */ +public interface UserGroupProviderLookup { + + /** + * Looks up the UserGroupProvider with the specified identifier + * + * @param identifier The identifier of the UserGroupProvider + * @return The UserGroupProvider + */ + UserGroupProvider getUserGroupProvider(String identifier); +} diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/exception/UninheritableAuthorizationsException.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/exception/UninheritableAuthorizationsException.java new file mode 100644 index 000000000000..f75ead8b75d6 --- /dev/null +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/exception/UninheritableAuthorizationsException.java @@ -0,0 +1,28 @@ +/* + * 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.exception; + +/** + * Represents the case when the proposed authorizations are not inheritable. + */ +public class UninheritableAuthorizationsException extends RuntimeException { + + public UninheritableAuthorizationsException(String message) { + super(message); + } + +} 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 3219ac257b9b..cd27e1061e70 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 @@ -84,6 +84,7 @@ default AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAct final Resource resource = getResource(); final AuthorizationRequest request = new AuthorizationRequest.Builder() .identity(user.getIdentity()) + .groups(user.getGroups()) .anonymous(user.isAnonymous()) .accessAttempt(false) .action(action) @@ -188,6 +189,7 @@ default void authorize(Authorizer authorizer, RequestAction action, NiFiUser use final Resource resource = getResource(); final AuthorizationRequest request = new AuthorizationRequest.Builder() .identity(user.getIdentity()) + .groups(user.getGroups()) .anonymous(user.isAnonymous()) .accessAttempt(true) .action(action) diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/user/NiFiUser.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/user/NiFiUser.java index c450bc44db90..6b8012bb2e35 100644 --- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/user/NiFiUser.java +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/user/NiFiUser.java @@ -17,6 +17,8 @@ package org.apache.nifi.authorization.user; +import java.util.Set; + /** * A representation of a NiFi user that has logged into the application */ @@ -27,6 +29,11 @@ public interface NiFiUser { */ String getIdentity(); + /** + * @return the groups that this user belongs to if this nifi is configured to load user groups, null otherwise. + */ + Set getGroups(); + /** * @return the next user in the proxied entities chain, or null if no more users exist in the chain. */ diff --git a/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java b/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java index cda2ac5ffdcc..08c8bb32b1af 100644 --- a/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java +++ b/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java @@ -16,7 +16,6 @@ */ package org.apache.nifi.authorization; -import org.apache.nifi.authorization.exception.AuthorizerCreationException; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; @@ -251,10 +250,7 @@ public void testGetFingerprint() { when(authorizer2.getAccessPolicies()).thenReturn(policies2); // compare the fingerprints - assertEquals(authorizer1.getFingerprint(), authorizer2.getFingerprint()); - - //System.out.println(authorizer1.getFingerprint()); } @Test @@ -332,211 +328,4 @@ public void testEmptyAuthorizer() { Assert.assertTrue(fingerprint.length() > 0); } - @Test(expected = AuthorizerCreationException.class) - public void testOnConfiguredWhenPoliciesWithSameResourceAndAction() { - User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build(); - - AccessPolicy policy1 = new AccessPolicy.Builder() - .identifier("policy-id-1") - .resource("resource1") - .action(RequestAction.READ) - .addUser(user1.getIdentifier()) - .build(); - - AccessPolicy policy2 = new AccessPolicy.Builder() - .identifier("policy-id-2") - .resource("resource1") - .action(RequestAction.READ) - .addUser(user1.getIdentifier()) - .build(); - - Set policies = new LinkedHashSet<>(); - policies.add(policy1); - policies.add(policy2); - - Set users = new LinkedHashSet<>(); - users.add(user1); - - AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); - AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(new HashSet<>(), users, policies); - authorizer.onConfigured(context); - } - - @Test(expected = AuthorizerCreationException.class) - public void testOnConfiguredWhenUsersWithSameIdentity() { - User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build(); - User user2 = new User.Builder().identifier("user-id-2").identity("user-1").build(); - - Set users = new LinkedHashSet<>(); - users.add(user1); - users.add(user2); - - AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); - AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(new HashSet<>(), users, new HashSet<>()); - authorizer.onConfigured(context); - } - - @Test(expected = AuthorizerCreationException.class) - public void testOnConfiguredWhenGroupsWithSameName() { - Group group1 = new Group.Builder().identifier("group-id-1").name("group-1").build(); - Group group2 = new Group.Builder().identifier("group-id-2").name("group-1").build(); - - Set groups = new LinkedHashSet<>(); - groups.add(group1); - groups.add(group2); - - AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); - AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(groups, new HashSet<>(), new HashSet<>()); - authorizer.onConfigured(context); - } - - @Test - public void testAddPoliciesWithSameResourceAndAction() { - AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); - AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(); - authorizer.onConfigured(context); - - User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build(); - authorizer.addUser(user1); - - AccessPolicy policy1 = new AccessPolicy.Builder() - .identifier("policy-id-1") - .resource("resource1") - .action(RequestAction.READ) - .addUser(user1.getIdentifier()) - .build(); - authorizer.addAccessPolicy(policy1); - - AccessPolicy policy2 = new AccessPolicy.Builder() - .identifier("policy-id-2") - .resource("resource1") - .action(RequestAction.READ) - .addUser(user1.getIdentifier()) - .build(); - - try { - authorizer.addAccessPolicy(policy2); - Assert.fail("Should have thrown exception"); - } catch (IllegalStateException e) { - - } - } - - @Test - public void testAddUsersWithSameIdentity() { - AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); - AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(); - authorizer.onConfigured(context); - - User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build(); - authorizer.addUser(user1); - - User user2 = new User.Builder().identifier("user-id-2").identity("user-1").build(); - - try { - authorizer.addUser(user2); - Assert.fail("Should have thrown exception"); - } catch (IllegalStateException e) { - - } - } - - @Test - public void testAddGroupsWithSameName() { - AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); - AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(); - authorizer.onConfigured(context); - - Group group1 = new Group.Builder().identifier("group-id-1").name("group-1").build(); - authorizer.addGroup(group1); - - Group group2 = new Group.Builder().identifier("group-id-2").name("group-1").build(); - - try { - authorizer.addGroup(group2); - Assert.fail("Should have thrown exception"); - } catch (IllegalStateException e) { - - } - } - - @Test - public void testAddUsersWithSameIdentityAsGroupName() { - AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); - AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(); - authorizer.onConfigured(context); - - Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build(); - authorizer.addGroup(group1); - - User user = new User.Builder().identifier("user-id-2").identity("abc").build(); - - try { - authorizer.addUser(user); - Assert.fail("Should have thrown exception"); - } catch (IllegalStateException e) { - - } - } - - @Test - public void testAddGroupWithSameNameAsUserIdentity() { - AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); - AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(); - authorizer.onConfigured(context); - - User user = new User.Builder().identifier("user-id-2").identity("abc").build(); - authorizer.addUser(user); - - Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build(); - try { - authorizer.addGroup(group1); - Assert.fail("Should have thrown exception"); - } catch (IllegalStateException e) { - - } - } - - @Test - public void testUpdateUserWithSameIdentity() { - AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); - AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(); - authorizer.onConfigured(context); - - User user1 = new User.Builder().identifier("user-id-1").identity("abc").build(); - authorizer.addUser(user1); - - User user2 = new User.Builder().identifier("user-id-2").identity("xyz").build(); - authorizer.addUser(user2); - - try { - User user1Updated = new User.Builder().identifier("user-id-1").identity("xyz").build(); - authorizer.updateUser(user1Updated); - Assert.fail("Should have thrown exception"); - } catch (IllegalStateException e) { - - } - } - - @Test - public void testUpdateGroupWithSameName() { - AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); - AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(); - authorizer.onConfigured(context); - - Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build(); - authorizer.addGroup(group1); - - Group group2 = new Group.Builder().identifier("group-id-2").name("xyz").build(); - authorizer.addGroup(group2); - - try { - Group group1Updated = new Group.Builder().identifier("group-id-1").name("xyz").build(); - authorizer.updateGroup(group1Updated); - Assert.fail("Should have thrown exception"); - } catch (IllegalStateException e) { - - } - } - } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml index e3deaece26ea..57181d2585c3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml @@ -65,15 +65,27 @@ org.apache.nifi - nifi-framework-core + nifi-nar-utils org.apache.nifi - nifi-nar-utils + nifi-properties + + + org.springframework + spring-beans + + + org.apache.commons + commons-lang3 org.apache.nifi - nifi-properties + nifi-framework-nar-utils + + + org.apache.nifi + nifi-framework-authorization diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderFactory.java new file mode 100644 index 000000000000..c71c982337e0 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderFactory.java @@ -0,0 +1,179 @@ +/* + * 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; + +import org.apache.nifi.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.authorization.exception.AuthorizerDestructionException; +import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException; +import org.apache.nifi.nar.NarCloseable; + +import java.util.Set; + +public final class AccessPolicyProviderFactory { + + public static AccessPolicyProvider withNarLoader(final AccessPolicyProvider baseAccessPolicyProvider) { + if (baseAccessPolicyProvider instanceof ConfigurableAccessPolicyProvider) { + final ConfigurableAccessPolicyProvider baseConfigurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) baseAccessPolicyProvider; + return new ConfigurableAccessPolicyProvider() { + @Override + public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableAccessPolicyProvider.addAccessPolicy(accessPolicy); + } + } + + @Override + public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableAccessPolicyProvider.updateAccessPolicy(accessPolicy); + } + } + + @Override + public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicy); + } + } + + @Override + public Set getAccessPolicies() throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableAccessPolicyProvider.getAccessPolicies(); + } + } + + @Override + public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableAccessPolicyProvider.getAccessPolicy(identifier); + } + } + + @Override + public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableAccessPolicyProvider.getAccessPolicy(resourceIdentifier, action); + } + } + + @Override + public UserGroupProvider getUserGroupProvider() { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableAccessPolicyProvider.getUserGroupProvider(); + } + } + + @Override + public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseConfigurableAccessPolicyProvider.inheritFingerprint(fingerprint); + } + } + + @Override + public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseConfigurableAccessPolicyProvider.checkInheritability(proposedFingerprint); + } + } + + @Override + public String getFingerprint() throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableAccessPolicyProvider.getFingerprint(); + } + } + + @Override + public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseConfigurableAccessPolicyProvider.initialize(initializationContext); + } + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseConfigurableAccessPolicyProvider.onConfigured(configurationContext); + } + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseConfigurableAccessPolicyProvider.preDestruction(); + } + } + }; + } else { + return new AccessPolicyProvider() { + @Override + public Set getAccessPolicies() throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseAccessPolicyProvider.getAccessPolicies(); + } + } + + @Override + public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseAccessPolicyProvider.getAccessPolicy(identifier); + } + } + + @Override + public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseAccessPolicyProvider.getAccessPolicy(resourceIdentifier, action); + } + } + + @Override + public UserGroupProvider getUserGroupProvider() { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseAccessPolicyProvider.getUserGroupProvider(); + } + } + + @Override + public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseAccessPolicyProvider.initialize(initializationContext); + } + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseAccessPolicyProvider.onConfigured(configurationContext); + } + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseAccessPolicyProvider.preDestruction(); + } + } + }; + } + } + + private AccessPolicyProviderFactory() {} +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerCapabilityDetection.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerCapabilityDetection.java new file mode 100644 index 000000000000..59de9088708f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerCapabilityDetection.java @@ -0,0 +1,45 @@ +/* + * 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; + +public final class AuthorizerCapabilityDetection { + + public static boolean isManagedAuthorizer(final Authorizer authorizer) { + return authorizer instanceof ManagedAuthorizer; + } + + public static boolean isConfigurableAccessPolicyProvider(final Authorizer authorizer) { + if (!isManagedAuthorizer(authorizer)) { + return false; + } + + final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer; + return managedAuthorizer.getAccessPolicyProvider() instanceof ConfigurableAccessPolicyProvider; + } + + public static boolean isConfigurableUserGroupProvider(final Authorizer authorizer) { + if (!isManagedAuthorizer(authorizer)) { + return false; + } + + final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer; + final AccessPolicyProvider accessPolicyProvider = managedAuthorizer.getAccessPolicyProvider(); + return accessPolicyProvider.getUserGroupProvider() instanceof ConfigurableUserGroupProvider; + } + + private AuthorizerCapabilityDetection() {} +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactory.java new file mode 100644 index 000000000000..660b47b5bcf7 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactory.java @@ -0,0 +1,426 @@ +/* + * 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; + +import org.apache.nifi.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.authorization.exception.AuthorizerDestructionException; +import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException; +import org.apache.nifi.nar.NarCloseable; + +import java.util.Set; + +public final class AuthorizerFactory { + + /** + * Checks if another policy exists with the same resource and action as the given policy. + * + * @param checkAccessPolicy an access policy being checked + * @return true if another access policy exists with the same resource and action, false otherwise + */ + private static boolean policyExists(final AccessPolicyProvider accessPolicyProvider, final AccessPolicy checkAccessPolicy) { + for (AccessPolicy accessPolicy : accessPolicyProvider.getAccessPolicies()) { + if (!accessPolicy.getIdentifier().equals(checkAccessPolicy.getIdentifier()) + && accessPolicy.getResource().equals(checkAccessPolicy.getResource()) + && accessPolicy.getAction().equals(checkAccessPolicy.getAction())) { + return true; + } + } + return false; + } + + /** + * Checks if another user exists with the same identity. + * + * @param identifier identity of the user + * @param identity identity of the user + * @return true if another user exists with the same identity, false otherwise + */ + private static boolean tenantExists(final UserGroupProvider userGroupProvider, final String identifier, final String identity) { + for (User user : userGroupProvider.getUsers()) { + if (!user.getIdentifier().equals(identifier) + && user.getIdentity().equals(identity)) { + return true; + } + } + + for (Group group : userGroupProvider.getGroups()) { + if (!group.getIdentifier().equals(identifier) + && group.getName().equals(identity)) { + return true; + } + } + + return false; + } + + public static Authorizer installIntegrityChecks(final Authorizer baseAuthorizer) { + if (baseAuthorizer instanceof ManagedAuthorizer) { + final ManagedAuthorizer baseManagedAuthorizer = (ManagedAuthorizer) baseAuthorizer; + return new ManagedAuthorizer() { + @Override + public String getFingerprint() throws AuthorizationAccessException { + return baseManagedAuthorizer.getFingerprint(); + } + + @Override + public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { + baseManagedAuthorizer.inheritFingerprint(fingerprint); + } + + @Override + public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { + baseManagedAuthorizer.checkInheritability(proposedFingerprint); + } + + @Override + public AccessPolicyProvider getAccessPolicyProvider() { + final AccessPolicyProvider baseAccessPolicyProvider = baseManagedAuthorizer.getAccessPolicyProvider(); + if (baseAccessPolicyProvider instanceof ConfigurableAccessPolicyProvider) { + final ConfigurableAccessPolicyProvider baseConfigurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) baseAccessPolicyProvider; + return new ConfigurableAccessPolicyProvider() { + @Override + public String getFingerprint() throws AuthorizationAccessException { + return baseConfigurableAccessPolicyProvider.getFingerprint(); + } + + @Override + public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { + baseConfigurableAccessPolicyProvider.inheritFingerprint(fingerprint); + } + + @Override + public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { + baseConfigurableAccessPolicyProvider.checkInheritability(proposedFingerprint); + } + + @Override + public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + if (policyExists(baseConfigurableAccessPolicyProvider, accessPolicy)) { + throw new IllegalStateException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction())); + } + return baseConfigurableAccessPolicyProvider.addAccessPolicy(accessPolicy); + } + + @Override + public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + return baseConfigurableAccessPolicyProvider.updateAccessPolicy(accessPolicy); + } + + @Override + public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicy); + } + + @Override + public Set getAccessPolicies() throws AuthorizationAccessException { + return baseConfigurableAccessPolicyProvider.getAccessPolicies(); + } + + @Override + public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException { + return baseConfigurableAccessPolicyProvider.getAccessPolicy(identifier); + } + + @Override + public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException { + return baseConfigurableAccessPolicyProvider.getAccessPolicy(resourceIdentifier, action); + } + + @Override + public UserGroupProvider getUserGroupProvider() { + final UserGroupProvider baseUserGroupProvider = baseConfigurableAccessPolicyProvider.getUserGroupProvider(); + if (baseUserGroupProvider instanceof ConfigurableUserGroupProvider) { + final ConfigurableUserGroupProvider baseConfigurableUserGroupProvider = (ConfigurableUserGroupProvider) baseUserGroupProvider; + return new ConfigurableUserGroupProvider() { + @Override + public String getFingerprint() throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.getFingerprint(); + } + + @Override + public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { + baseConfigurableUserGroupProvider.inheritFingerprint(fingerprint); + } + + @Override + public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { + baseConfigurableUserGroupProvider.checkInheritability(proposedFingerprint); + } + + @Override + public User addUser(User user) throws AuthorizationAccessException { + if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) { + throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity())); + } + return baseConfigurableUserGroupProvider.addUser(user); + } + + @Override + public User updateUser(User user) throws AuthorizationAccessException { + if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) { + throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity())); + } + return baseConfigurableUserGroupProvider.updateUser(user); + } + + @Override + public User deleteUser(User user) throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.deleteUser(user); + } + + @Override + public Group addGroup(Group group) throws AuthorizationAccessException { + if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) { + throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName())); + } + return baseConfigurableUserGroupProvider.addGroup(group); + } + + @Override + public Group updateGroup(Group group) throws AuthorizationAccessException { + if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) { + throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName())); + } + return baseConfigurableUserGroupProvider.updateGroup(group); + } + + @Override + public Group deleteGroup(Group group) throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.deleteGroup(group); + } + + @Override + public Set getUsers() throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.getUsers(); + } + + @Override + public User getUser(String identifier) throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.getUser(identifier); + } + + @Override + public User getUserByIdentity(String identity) throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.getUserByIdentity(identity); + } + + @Override + public Set getGroups() throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.getGroups(); + } + + @Override + public Group getGroup(String identifier) throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.getGroup(identifier); + } + + @Override + public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.getUserAndGroups(identity); + } + + @Override + public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException { + baseConfigurableUserGroupProvider.initialize(initializationContext); + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + baseConfigurableUserGroupProvider.onConfigured(configurationContext); + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + baseConfigurableUserGroupProvider.preDestruction(); + } + }; + } else { + return baseUserGroupProvider; + } + } + + @Override + public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException { + baseConfigurableAccessPolicyProvider.initialize(initializationContext); + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + baseConfigurableAccessPolicyProvider.onConfigured(configurationContext); + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + baseConfigurableAccessPolicyProvider.preDestruction(); + } + }; + } else { + return baseAccessPolicyProvider; + } + } + + @Override + public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException { + return baseManagedAuthorizer.authorize(request); + } + + @Override + public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { + baseManagedAuthorizer.initialize(initializationContext); + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + baseManagedAuthorizer.onConfigured(configurationContext); + + final AccessPolicyProvider accessPolicyProvider = baseManagedAuthorizer.getAccessPolicyProvider(); + final UserGroupProvider userGroupProvider = accessPolicyProvider.getUserGroupProvider(); + + // ensure that only one policy per resource-action exists + for (AccessPolicy accessPolicy : accessPolicyProvider.getAccessPolicies()) { + if (policyExists(accessPolicyProvider, accessPolicy)) { + throw new AuthorizerCreationException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction())); + } + } + + // ensure that only one group exists per identity + for (User user : userGroupProvider.getUsers()) { + if (tenantExists(userGroupProvider, user.getIdentifier(), user.getIdentity())) { + throw new AuthorizerCreationException(String.format("Found multiple users/user groups with identity '%s'.", user.getIdentity())); + } + } + + // ensure that only one group exists per identity + for (Group group : userGroupProvider.getGroups()) { + if (tenantExists(userGroupProvider, group.getIdentifier(), group.getName())) { + throw new AuthorizerCreationException(String.format("Found multiple users/user groups with name '%s'.", group.getName())); + } + } + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + baseManagedAuthorizer.preDestruction(); + } + }; + } else { + return baseAuthorizer; + } + } + + /** + * Decorates the base authorizer to ensure the nar context classloader is used when invoking the underlying methods. + * + * @param baseAuthorizer base authorizer + * @return authorizer + */ + public static Authorizer withNarLoader(final Authorizer baseAuthorizer) { + if (baseAuthorizer instanceof ManagedAuthorizer) { + final ManagedAuthorizer baseManagedAuthorizer = (ManagedAuthorizer) baseAuthorizer; + return new ManagedAuthorizer() { + @Override + public String getFingerprint() throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseManagedAuthorizer.getFingerprint(); + } + } + + @Override + public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseManagedAuthorizer.inheritFingerprint(fingerprint); + } + } + + @Override + public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseManagedAuthorizer.checkInheritability(proposedFingerprint); + } + } + + @Override + public AccessPolicyProvider getAccessPolicyProvider() { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseManagedAuthorizer.getAccessPolicyProvider(); + } + } + + @Override + public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseManagedAuthorizer.authorize(request); + } + } + + @Override + public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseManagedAuthorizer.initialize(initializationContext); + } + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseManagedAuthorizer.onConfigured(configurationContext); + } + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseManagedAuthorizer.preDestruction(); + } + } + }; + } else { + return new Authorizer() { + @Override + public AuthorizationResult authorize(final AuthorizationRequest request) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseAuthorizer.authorize(request); + } + } + + @Override + public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseAuthorizer.initialize(initializationContext); + } + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseAuthorizer.onConfigured(configurationContext); + } + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseAuthorizer.preDestruction(); + } + } + }; + } + } + + private AuthorizerFactory() {} +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java index b934cb845d38..9de875686109 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java @@ -25,7 +25,6 @@ import org.apache.nifi.authorization.generated.Property; import org.apache.nifi.bundle.Bundle; import org.apache.nifi.nar.ExtensionManager; -import org.apache.nifi.nar.NarCloseable; import org.apache.nifi.util.NiFiProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,12 +48,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; /** * Factory bean for loading the configured authorizer. */ -public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, AuthorizerLookup { +public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, UserGroupProviderLookup, AccessPolicyProviderLookup, AuthorizerLookup { private static final Logger logger = LoggerFactory.getLogger(AuthorizerFactoryBean.class); private static final String AUTHORIZERS_XSD = "/authorizers.xsd"; @@ -74,8 +72,19 @@ private static JAXBContext initializeJaxbContext() { private Authorizer authorizer; private NiFiProperties properties; + private final Map userGroupProviders = new HashMap<>(); + private final Map accessPolicyProviders = new HashMap<>(); private final Map authorizers = new HashMap<>(); + @Override + public UserGroupProvider getUserGroupProvider(String identifier) { + return userGroupProviders.get(identifier); + } + + @Override + public AccessPolicyProvider getAccessPolicyProvider(String identifier) { + return accessPolicyProviders.get(identifier); + } @Override public Authorizer getAuthorizer(String identifier) { @@ -98,6 +107,28 @@ public Object getObject() throws Exception { } else { final Authorizers authorizerConfiguration = loadAuthorizersConfiguration(); + // create each user group provider + for (final org.apache.nifi.authorization.generated.UserGroupProvider userGroupProvider : authorizerConfiguration.getUserGroupProvider()) { + userGroupProviders.put(userGroupProvider.getIdentifier(), createUserGroupProvider(userGroupProvider.getIdentifier(), userGroupProvider.getClazz())); + } + + // configure each user group provider + for (final org.apache.nifi.authorization.generated.UserGroupProvider provider : authorizerConfiguration.getUserGroupProvider()) { + final UserGroupProvider instance = userGroupProviders.get(provider.getIdentifier()); + instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty())); + } + + // create each access policy provider + for (final org.apache.nifi.authorization.generated.AccessPolicyProvider accessPolicyProvider : authorizerConfiguration.getAccessPolicyProvider()) { + accessPolicyProviders.put(accessPolicyProvider.getIdentifier(), createAccessPolicyProvider(accessPolicyProvider.getIdentifier(), accessPolicyProvider.getClazz())); + } + + // configure each access policy provider + for (final org.apache.nifi.authorization.generated.AccessPolicyProvider provider : authorizerConfiguration.getAccessPolicyProvider()) { + final AccessPolicyProvider instance = accessPolicyProviders.get(provider.getIdentifier()); + instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty())); + } + // create each authorizer for (final org.apache.nifi.authorization.generated.Authorizer authorizer : authorizerConfiguration.getAuthorizer()) { authorizers.put(authorizer.getIdentifier(), createAuthorizer(authorizer.getIdentifier(), authorizer.getClazz())); @@ -106,7 +137,7 @@ public Object getObject() throws Exception { // configure each authorizer for (final org.apache.nifi.authorization.generated.Authorizer provider : authorizerConfiguration.getAuthorizer()) { final Authorizer instance = authorizers.get(provider.getIdentifier()); - instance.onConfigured(loadAuthorizerConfiguration(provider)); + instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty())); } // get the authorizer instance @@ -146,6 +177,102 @@ private Authorizers loadAuthorizersConfiguration() throws Exception { } } + private UserGroupProvider createUserGroupProvider(final String identifier, final String userGroupProviderClassName) throws Exception { + // get the classloader for the specified user group provider + final List userGroupProviderBundles = ExtensionManager.getBundles(userGroupProviderClassName); + + if (userGroupProviderBundles.size() == 0) { + throw new Exception(String.format("The specified user group provider class '%s' is not known to this nifi.", userGroupProviderClassName)); + } + + if (userGroupProviderBundles.size() > 1) { + throw new Exception(String.format("Multiple bundles found for the specified user group provider class '%s', only one is allowed.", userGroupProviderClassName)); + } + + final Bundle userGroupProviderBundle = userGroupProviderBundles.get(0); + final ClassLoader userGroupProviderClassLoader = userGroupProviderBundle.getClassLoader(); + + // get the current context classloader + final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); + + final UserGroupProvider instance; + try { + // set the appropriate class loader + Thread.currentThread().setContextClassLoader(userGroupProviderClassLoader); + + // attempt to load the class + Class rawUserGroupProviderClass = Class.forName(userGroupProviderClassName, true, userGroupProviderClassLoader); + Class userGroupProviderClass = rawUserGroupProviderClass.asSubclass(UserGroupProvider.class); + + // otherwise create a new instance + Constructor constructor = userGroupProviderClass.getConstructor(); + instance = (UserGroupProvider) constructor.newInstance(); + + // method injection + performMethodInjection(instance, userGroupProviderClass); + + // field injection + performFieldInjection(instance, userGroupProviderClass); + + // call post construction lifecycle event + instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this)); + } finally { + if (currentClassLoader != null) { + Thread.currentThread().setContextClassLoader(currentClassLoader); + } + } + + return UserGroupProviderFactory.withNarLoader(instance); + } + + private AccessPolicyProvider createAccessPolicyProvider(final String identifier, final String accessPolicyProviderClassName) throws Exception { + // get the classloader for the specified access policy provider + final List accessPolicyProviderBundles = ExtensionManager.getBundles(accessPolicyProviderClassName); + + if (accessPolicyProviderBundles.size() == 0) { + throw new Exception(String.format("The specified access policy provider class '%s' is not known to this nifi.", accessPolicyProviderClassName)); + } + + if (accessPolicyProviderBundles.size() > 1) { + throw new Exception(String.format("Multiple bundles found for the specified access policy provider class '%s', only one is allowed.", accessPolicyProviderClassName)); + } + + final Bundle accessPolicyProviderBundle = accessPolicyProviderBundles.get(0); + final ClassLoader accessPolicyProviderClassLoader = accessPolicyProviderBundle.getClassLoader(); + + // get the current context classloader + final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); + + final AccessPolicyProvider instance; + try { + // set the appropriate class loader + Thread.currentThread().setContextClassLoader(accessPolicyProviderClassLoader); + + // attempt to load the class + Class rawAccessPolicyProviderClass = Class.forName(accessPolicyProviderClassName, true, accessPolicyProviderClassLoader); + Class accessPolicyClass = rawAccessPolicyProviderClass.asSubclass(AccessPolicyProvider.class); + + // otherwise create a new instance + Constructor constructor = accessPolicyClass.getConstructor(); + instance = (AccessPolicyProvider) constructor.newInstance(); + + // method injection + performMethodInjection(instance, accessPolicyClass); + + // field injection + performFieldInjection(instance, accessPolicyClass); + + // call post construction lifecycle event + instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this)); + } finally { + if (currentClassLoader != null) { + Thread.currentThread().setContextClassLoader(currentClassLoader); + } + } + + return AccessPolicyProviderFactory.withNarLoader(instance); + } + private Authorizer createAuthorizer(final String identifier, final String authorizerClassName) throws Exception { // get the classloader for the specified authorizer final List authorizerBundles = ExtensionManager.getBundles(authorizerClassName); @@ -184,26 +311,26 @@ private Authorizer createAuthorizer(final String identifier, final String author performFieldInjection(instance, authorizerClass); // call post construction lifecycle event - instance.initialize(new StandardAuthorizerInitializationContext(identifier, this)); + instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this)); } finally { if (currentClassLoader != null) { Thread.currentThread().setContextClassLoader(currentClassLoader); } } - return withNarLoader(instance); + return AuthorizerFactory.installIntegrityChecks(AuthorizerFactory.withNarLoader(instance)); } - private AuthorizerConfigurationContext loadAuthorizerConfiguration(final org.apache.nifi.authorization.generated.Authorizer authorizer) { + private AuthorizerConfigurationContext loadAuthorizerConfiguration(final String identifier, final List properties) { final Map authorizerProperties = new HashMap<>(); - for (final Property property : authorizer.getProperty()) { + for (final Property property : properties) { authorizerProperties.put(property.getName(), property.getValue()); } - return new StandardAuthorizerConfigurationContext(authorizer.getIdentifier(), authorizerProperties); + return new StandardAuthorizerConfigurationContext(identifier, authorizerProperties); } - private void performMethodInjection(final Authorizer instance, final Class authorizerClass) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + private void performMethodInjection(final Object instance, final Class authorizerClass) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { for (final Method method : authorizerClass.getMethods()) { if (method.isAnnotationPresent(AuthorizerContext.class)) { // make the method accessible @@ -235,7 +362,7 @@ private void performMethodInjection(final Authorizer instance, final Class autho } } - private void performFieldInjection(final Authorizer instance, final Class authorizerClass) throws IllegalArgumentException, IllegalAccessException { + private void performFieldInjection(final Object instance, final Class authorizerClass) throws IllegalArgumentException, IllegalAccessException { for (final Field field : authorizerClass.getDeclaredFields()) { if (field.isAnnotationPresent(AuthorizerContext.class)) { // make the method accessible @@ -291,189 +418,6 @@ public void preDestruction() throws AuthorizerDestructionException { }; } - /** - * Decorates the base authorizer to ensure the nar context classloader is used when invoking the underlying methods. - * - * @param baseAuthorizer base authorizer - * @return authorizer - */ - public Authorizer withNarLoader(final Authorizer baseAuthorizer) { - if (baseAuthorizer instanceof AbstractPolicyBasedAuthorizer) { - AbstractPolicyBasedAuthorizer policyBasedAuthorizer = (AbstractPolicyBasedAuthorizer) baseAuthorizer; - return new AbstractPolicyBasedAuthorizer() { - @Override - public Group doAddGroup(Group group) throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.addGroup(group); - } - } - - @Override - public Group getGroup(String identifier) throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.getGroup(identifier); - } - } - - @Override - public Group doUpdateGroup(Group group) throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.updateGroup(group); - } - } - - @Override - public Group deleteGroup(Group group) throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.deleteGroup(group); - } - } - - @Override - public Set getGroups() throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.getGroups(); - } - } - - @Override - public User doAddUser(User user) throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.addUser(user); - } - } - - @Override - public User getUser(String identifier) throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.getUser(identifier); - } - } - - @Override - public User getUserByIdentity(String identity) throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.getUserByIdentity(identity); - } - } - - @Override - public User doUpdateUser(User user) throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.updateUser(user); - } - } - - @Override - public User deleteUser(User user) throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.deleteUser(user); - } - } - - @Override - public Set getUsers() throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.getUsers(); - } - } - - @Override - public AccessPolicy doAddAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.addAccessPolicy(accessPolicy); - } - } - - @Override - public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.getAccessPolicy(identifier); - } - } - - @Override - public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.updateAccessPolicy(accessPolicy); - } - } - - @Override - public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.deleteAccessPolicy(accessPolicy); - } - } - - @Override - public Set getAccessPolicies() throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.getAccessPolicies(); - } - } - - @Override - public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return policyBasedAuthorizer.getUsersAndAccessPolicies(); - } - } - - @Override - public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - policyBasedAuthorizer.initialize(initializationContext); - } - } - - @Override - public void doOnConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - policyBasedAuthorizer.onConfigured(configurationContext); - } - } - - @Override - public void preDestruction() throws AuthorizerDestructionException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - baseAuthorizer.preDestruction(); - } - } - }; - } else { - return new Authorizer() { - @Override - public AuthorizationResult authorize(final AuthorizationRequest request) throws AuthorizationAccessException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - return baseAuthorizer.authorize(request); - } - } - - @Override - public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - baseAuthorizer.initialize(initializationContext); - } - } - - @Override - public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - baseAuthorizer.onConfigured(configurationContext); - } - } - - @Override - public void preDestruction() throws AuthorizerDestructionException { - try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { - baseAuthorizer.preDestruction(); - } - } - }; - } - } - @Override public Class getObjectType() { return Authorizer.class; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupProviderFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupProviderFactory.java new file mode 100644 index 000000000000..caa265fc0bfc --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupProviderFactory.java @@ -0,0 +1,228 @@ +/* + * 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; + +import org.apache.nifi.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.authorization.exception.AuthorizerDestructionException; +import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException; +import org.apache.nifi.nar.NarCloseable; + +import java.util.Set; + +public final class UserGroupProviderFactory { + + public static UserGroupProvider withNarLoader(final UserGroupProvider baseUserGroupProvider) { + if (baseUserGroupProvider instanceof ConfigurableUserGroupProvider) { + final ConfigurableUserGroupProvider baseConfigurableUserGroupProvider = (ConfigurableUserGroupProvider) baseUserGroupProvider; + return new ConfigurableUserGroupProvider() { + @Override + public User addUser(User user) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableUserGroupProvider.addUser(user); + } + } + + @Override + public User updateUser(User user) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableUserGroupProvider.updateUser(user); + } + } + + @Override + public User deleteUser(User user) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableUserGroupProvider.deleteUser(user); + } + } + + @Override + public Group addGroup(Group group) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableUserGroupProvider.addGroup(group); + } + } + + @Override + public Group updateGroup(Group group) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableUserGroupProvider.updateGroup(group); + } + } + + @Override + public Group deleteGroup(Group group) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableUserGroupProvider.deleteGroup(group); + } + } + + @Override + public Set getUsers() throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableUserGroupProvider.getUsers(); + } + } + + @Override + public User getUser(String identifier) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableUserGroupProvider.getUser(identifier); + } + } + + @Override + public User getUserByIdentity(String identity) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableUserGroupProvider.getUserByIdentity(identity); + } + } + + @Override + public Set getGroups() throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableUserGroupProvider.getGroups(); + } + } + + @Override + public Group getGroup(String identifier) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableUserGroupProvider.getGroup(identifier); + } + } + + @Override + public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableUserGroupProvider.getUserAndGroups(identity); + } + } + + @Override + public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseConfigurableUserGroupProvider.inheritFingerprint(fingerprint); + } + } + + @Override + public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseConfigurableUserGroupProvider.checkInheritability(proposedFingerprint); + } + } + + @Override + public String getFingerprint() throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseConfigurableUserGroupProvider.getFingerprint(); + } + } + + @Override + public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseConfigurableUserGroupProvider.initialize(initializationContext); + } + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseConfigurableUserGroupProvider.onConfigured(configurationContext); + } + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseConfigurableUserGroupProvider.preDestruction(); + } + } + }; + } else { + return new UserGroupProvider() { + @Override + public Set getUsers() throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseUserGroupProvider.getUsers(); + } + } + + @Override + public User getUser(String identifier) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseUserGroupProvider.getUser(identifier); + } + } + + @Override + public User getUserByIdentity(String identity) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseUserGroupProvider.getUserByIdentity(identity); + } + } + + @Override + public Set getGroups() throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseUserGroupProvider.getGroups(); + } + } + + @Override + public Group getGroup(String identifier) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseUserGroupProvider.getGroup(identifier); + } + } + + @Override + public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseUserGroupProvider.getUserAndGroups(identity); + } + } + + @Override + public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseUserGroupProvider.initialize(initializationContext); + } + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseUserGroupProvider.onConfigured(configurationContext); + } + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseUserGroupProvider.preDestruction(); + } + } + }; + } + } + + private UserGroupProviderFactory() {} +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/xsd/authorizers.xsd b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/xsd/authorizers.xsd index 4b68b0071760..46c004a4cb23 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/xsd/authorizers.xsd +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/xsd/authorizers.xsd @@ -14,7 +14,25 @@ limitations under the License. --> - + + + + + + + + + + + + + + + + + + + @@ -38,10 +56,12 @@ - + + + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/AuthorizerFactoryTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/AuthorizerFactoryTest.java new file mode 100644 index 000000000000..13d6f5a0028d --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/AuthorizerFactoryTest.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.authorization; + +import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +public class AuthorizerFactoryTest { + + @Test(expected = AuthorizerCreationException.class) + public void testOnConfiguredWhenPoliciesWithSameResourceAndAction() { + User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build(); + + AccessPolicy policy1 = new AccessPolicy.Builder() + .identifier("policy-id-1") + .resource("resource1") + .action(RequestAction.READ) + .addUser(user1.getIdentifier()) + .build(); + + AccessPolicy policy2 = new AccessPolicy.Builder() + .identifier("policy-id-2") + .resource("resource1") + .action(RequestAction.READ) + .addUser(user1.getIdentifier()) + .build(); + + Set policies = new LinkedHashSet<>(); + policies.add(policy1); + policies.add(policy2); + + Set users = new LinkedHashSet<>(); + users.add(user1); + + AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); + Authorizer authorizer = AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer(new HashSet<>(), users, policies)); + authorizer.onConfigured(context); + } + + @Test(expected = AuthorizerCreationException.class) + public void testOnConfiguredWhenUsersWithSameIdentity() { + User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build(); + User user2 = new User.Builder().identifier("user-id-2").identity("user-1").build(); + + Set users = new LinkedHashSet<>(); + users.add(user1); + users.add(user2); + + AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); + Authorizer authorizer = AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer(new HashSet<>(), users, new HashSet<>())); + authorizer.onConfigured(context); + } + + @Test(expected = AuthorizerCreationException.class) + public void testOnConfiguredWhenGroupsWithSameName() { + Group group1 = new Group.Builder().identifier("group-id-1").name("group-1").build(); + Group group2 = new Group.Builder().identifier("group-id-2").name("group-1").build(); + + Set groups = new LinkedHashSet<>(); + groups.add(group1); + groups.add(group2); + + AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); + Authorizer authorizer = AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer(groups, new HashSet<>(), new HashSet<>())); + authorizer.onConfigured(context); + } + + @Test + public void testAddPoliciesWithSameResourceAndAction() { + AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); + + final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer()); + managedAuthorizer.onConfigured(context); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider(); + final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider(); + + User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build(); + userGroupProvider.addUser(user1); + + AccessPolicy policy1 = new AccessPolicy.Builder() + .identifier("policy-id-1") + .resource("resource1") + .action(RequestAction.READ) + .addUser(user1.getIdentifier()) + .build(); + accessPolicyProvider.addAccessPolicy(policy1); + + AccessPolicy policy2 = new AccessPolicy.Builder() + .identifier("policy-id-2") + .resource("resource1") + .action(RequestAction.READ) + .addUser(user1.getIdentifier()) + .build(); + + try { + accessPolicyProvider.addAccessPolicy(policy2); + Assert.fail("Should have thrown exception"); + } catch (IllegalStateException e) { + + } + } + + @Test + public void testAddUsersWithSameIdentity() { + AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); + + final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer()); + managedAuthorizer.onConfigured(context); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider(); + final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider(); + + User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build(); + userGroupProvider.addUser(user1); + + User user2 = new User.Builder().identifier("user-id-2").identity("user-1").build(); + + try { + userGroupProvider.addUser(user2); + Assert.fail("Should have thrown exception"); + } catch (IllegalStateException e) { + + } + } + + @Test + public void testAddGroupsWithSameName() { + AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); + + final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer()); + managedAuthorizer.onConfigured(context); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider(); + final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider(); + + Group group1 = new Group.Builder().identifier("group-id-1").name("group-1").build(); + userGroupProvider.addGroup(group1); + + Group group2 = new Group.Builder().identifier("group-id-2").name("group-1").build(); + + try { + userGroupProvider.addGroup(group2); + Assert.fail("Should have thrown exception"); + } catch (IllegalStateException e) { + + } + } + + @Test + public void testAddUsersWithSameIdentityAsGroupName() { + AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); + + final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer()); + managedAuthorizer.onConfigured(context); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider(); + final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider(); + + Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build(); + userGroupProvider.addGroup(group1); + + User user = new User.Builder().identifier("user-id-2").identity("abc").build(); + + try { + userGroupProvider.addUser(user); + Assert.fail("Should have thrown exception"); + } catch (IllegalStateException e) { + + } + } + + @Test + public void testAddGroupWithSameNameAsUserIdentity() { + AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); + + final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer()); + managedAuthorizer.onConfigured(context); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider(); + final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider(); + + User user = new User.Builder().identifier("user-id-2").identity("abc").build(); + userGroupProvider.addUser(user); + + Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build(); + try { + userGroupProvider.addGroup(group1); + Assert.fail("Should have thrown exception"); + } catch (IllegalStateException e) { + + } + } + + @Test + public void testUpdateUserWithSameIdentity() { + AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); + + final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer()); + managedAuthorizer.onConfigured(context); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider(); + final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider(); + + User user1 = new User.Builder().identifier("user-id-1").identity("abc").build(); + userGroupProvider.addUser(user1); + + User user2 = new User.Builder().identifier("user-id-2").identity("xyz").build(); + userGroupProvider.addUser(user2); + + try { + User user1Updated = new User.Builder().identifier("user-id-1").identity("xyz").build(); + userGroupProvider.updateUser(user1Updated); + Assert.fail("Should have thrown exception"); + } catch (IllegalStateException e) { + + } + } + + @Test + public void testUpdateGroupWithSameName() { + AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class); + + final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer()); + managedAuthorizer.onConfigured(context); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider(); + final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider(); + + Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build(); + userGroupProvider.addGroup(group1); + + Group group2 = new Group.Builder().identifier("group-id-2").name("xyz").build(); + userGroupProvider.addGroup(group2); + + try { + Group group1Updated = new Group.Builder().identifier("group-id-1").name("xyz").build(); + userGroupProvider.updateGroup(group1Updated); + Assert.fail("Should have thrown exception"); + } catch (IllegalStateException e) { + + } + } +} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java new file mode 100644 index 000000000000..9b50b50ed42f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java @@ -0,0 +1,183 @@ +/* + * 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; + +import org.apache.nifi.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.authorization.exception.AuthorizerDestructionException; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Mock implementation of AbstractPolicyBasedAuthorizer. + */ +public class MockPolicyBasedAuthorizer extends AbstractPolicyBasedAuthorizer { + + private Set groups = new HashSet<>(); + private Set users = new HashSet<>(); + private Set policies = new HashSet<>(); + + public MockPolicyBasedAuthorizer() { + + } + + public MockPolicyBasedAuthorizer(Set groups, Set users, Set policies) { + if (groups != null) { + this.groups.addAll(groups); + } + if (users != null) { + this.users.addAll(users); + } + if (policies != null) { + this.policies.addAll(policies); + } + } + + @Override + public Group doAddGroup(Group group) throws AuthorizationAccessException { + groups.add(group); + return group; + } + + @Override + public Group getGroup(String identifier) throws AuthorizationAccessException { + return groups.stream().filter(g -> g.getIdentifier().equals(identifier)).findFirst().get(); + } + + @Override + public Group doUpdateGroup(Group group) throws AuthorizationAccessException { + deleteGroup(group); + return addGroup(group); + } + + @Override + public Group deleteGroup(Group group) throws AuthorizationAccessException { + groups.remove(group); + return group; + } + + @Override + public Set getGroups() throws AuthorizationAccessException { + return groups; + } + + @Override + public User doAddUser(User user) throws AuthorizationAccessException { + users.add(user); + return user; + } + + @Override + public User getUser(String identifier) throws AuthorizationAccessException { + return users.stream().filter(u -> u.getIdentifier().equals(identifier)).findFirst().get(); + } + + @Override + public User getUserByIdentity(String identity) throws AuthorizationAccessException { + return users.stream().filter(u -> u.getIdentity().equals(identity)).findFirst().get(); + } + + @Override + public User doUpdateUser(User user) throws AuthorizationAccessException { + deleteUser(user); + return addUser(user); + } + + @Override + public User deleteUser(User user) throws AuthorizationAccessException { + users.remove(user); + return user; + } + + @Override + public Set getUsers() throws AuthorizationAccessException { + return users; + } + + @Override + protected AccessPolicy doAddAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + policies.add(accessPolicy); + return accessPolicy; + } + + @Override + public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException { + return policies.stream().filter(p -> p.getIdentifier().equals(identifier)).findFirst().get(); + } + + @Override + public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + deleteAccessPolicy(accessPolicy); + return addAccessPolicy(accessPolicy); + } + + @Override + public AccessPolicy deleteAccessPolicy(AccessPolicy policy) throws AuthorizationAccessException { + policies.remove(policy); + return policy; + } + + @Override + public Set getAccessPolicies() throws AuthorizationAccessException { + return policies; + } + + @Override + public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException { + return new UsersAndAccessPolicies() { + @Override + public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) { + return null; + } + + @Override + public User getUser(String identity) { + return getUserByIdentity(identity); + } + + @Override + public Set getGroups(String userIdentity) { + User user = getUserByIdentity(userIdentity); + if (user == null) { + return new HashSet<>(); + } else { + return groups.stream() + .filter(g -> g.getUsers().contains(user.getIdentifier())) + .collect(Collectors.toSet()); + } + } + }; + } + + @Override + public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { + + } + + @Override + public void doOnConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java index 9e546b1fd694..fa039fefaf76 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java @@ -29,7 +29,9 @@ @XmlType(name = "flowConfiguration") public class FlowConfigurationDTO { + private Boolean supportsManagedAuthorizer; private Boolean supportsConfigurableAuthorizer; + private Boolean supportsConfigurableUsersAndGroups; private Long autoRefreshIntervalSeconds; private Date currentTime; @@ -50,6 +52,37 @@ public void setAutoRefreshIntervalSeconds(Long autoRefreshIntervalSeconds) { this.autoRefreshIntervalSeconds = autoRefreshIntervalSeconds; } + /** + * @return whether this NiFi supports a managed authorizer. Managed authorizers can visualize users, groups, + * and policies in the UI. This value is read only + */ + @ApiModelProperty( + value = "Whether this NiFi supports a managed authorizer. Managed authorizers can visualize users, groups, and policies in the UI.", + readOnly = true + ) + public Boolean getSupportsManagedAuthorizer() { + return supportsManagedAuthorizer; + } + + public void setSupportsManagedAuthorizer(Boolean supportsManagedAuthorizer) { + this.supportsManagedAuthorizer = supportsManagedAuthorizer; + } + + /** + * @return whether this NiFi supports configurable users and groups. This value is read only + */ + @ApiModelProperty( + value = "Whether this NiFi supports configurable users and groups.", + readOnly = true + ) + public Boolean getSupportsConfigurableUsersAndGroups() { + return supportsConfigurableUsersAndGroups; + } + + public void setSupportsConfigurableUsersAndGroups(Boolean supportsConfigurableUsersAndGroups) { + this.supportsConfigurableUsersAndGroups = supportsConfigurableUsersAndGroups; + } + /** * @return whether this NiFi supports a configurable authorizer. This value is read only */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessPolicyEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessPolicyEntity.java index d42f4989e8f7..19d831dbffa9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessPolicyEntity.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessPolicyEntity.java @@ -28,7 +28,7 @@ * A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to an {@link AccessPolicyDTO}. */ @XmlRootElement(name = "accessPolicyEntity") -public class AccessPolicyEntity extends ComponentEntity { +public class AccessPolicyEntity extends ComponentEntity implements Permissible { private Date generated; private AccessPolicyDTO component; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/TenantsEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/TenantsEntity.java index 49c51c3f2bca..cc97619c383a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/TenantsEntity.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/TenantsEntity.java @@ -24,7 +24,7 @@ * TenantEntity objects. */ @XmlRootElement(name = "tenantsEntity") -public class TenantsEntity { +public class TenantsEntity extends Entity { private Collection users; private Collection userGroups; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserEntity.java index 983cdfbaba00..11d2f9d4409c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserEntity.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserEntity.java @@ -24,7 +24,7 @@ * A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a UserDTO. */ @XmlRootElement(name = "userEntity") -public class UserEntity extends ComponentEntity { +public class UserEntity extends ComponentEntity implements Permissible { private UserDTO component; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupEntity.java index ea8238acfe70..d70489ca30c5 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupEntity.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupEntity.java @@ -16,14 +16,15 @@ */ package org.apache.nifi.web.api.entity; -import javax.xml.bind.annotation.XmlRootElement; import org.apache.nifi.web.api.dto.UserGroupDTO; +import javax.xml.bind.annotation.XmlRootElement; + /** * A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a UserGroupDTO. */ @XmlRootElement(name = "userGroupEntity") -public class UserGroupEntity extends ComponentEntity { +public class UserGroupEntity extends ComponentEntity implements Permissible { private UserGroupDTO component; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupsEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupsEntity.java index bdde6628bd1b..d4b4c5d8a25b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupsEntity.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupsEntity.java @@ -24,7 +24,7 @@ * UserGroupEntity objects. */ @XmlRootElement(name = "userGroupsEntity") -public class UserGroupsEntity { +public class UserGroupsEntity extends Entity { private Collection userGroups; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml index e3251a6f49b8..c4ebede7bae1 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml @@ -41,8 +41,9 @@ xjc - src/main/xsd - authorizations.xsd + + src/main/xsd/authorizations.xsd + org.apache.nifi.authorization.file.generated @@ -52,8 +53,9 @@ xjc - src/main/xsd - tenants.xsd + + src/main/xsd/tenants.xsd + org.apache.nifi.authorization.file.tenants.generated false @@ -64,8 +66,9 @@ xjc - src/main/xsd - legacy-users.xsd + + src/main/xsd/legacy-users.xsd + org.apache.nifi.user.generated false diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizationsHolder.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizationsHolder.java index e40728918d6d..0d3ea64c94dc 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizationsHolder.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizationsHolder.java @@ -19,9 +19,6 @@ import org.apache.nifi.authorization.file.generated.Authorizations; import org.apache.nifi.authorization.file.generated.Policies; -import org.apache.nifi.authorization.file.tenants.generated.Groups; -import org.apache.nifi.authorization.file.tenants.generated.Tenants; -import org.apache.nifi.authorization.file.tenants.generated.Users; import java.util.Collections; import java.util.HashMap; @@ -32,56 +29,26 @@ /** * A holder to provide atomic access to data structures. */ -public class AuthorizationsHolder implements UsersAndAccessPolicies { +public class AuthorizationsHolder { - private final Tenants tenants; private final Authorizations authorizations; private final Set allPolicies; private final Map> policiesByResource; private final Map policiesById; - private final Set allUsers; - private final Map usersById; - private final Map usersByIdentity; - - private final Set allGroups; - private final Map groupsById; - private final Map> groupsByUserIdentity; - /** - * Creates a new holder and populates all convenience data structures. + * Creates a new holder and populates all convenience authorizations data structures. * * @param authorizations the current authorizations instance */ - public AuthorizationsHolder(final Authorizations authorizations, final Tenants tenants) { + public AuthorizationsHolder(final Authorizations authorizations) { this.authorizations = authorizations; - this.tenants = tenants; - - // load all users - final Users users = tenants.getUsers(); - final Set allUsers = Collections.unmodifiableSet(createUsers(users)); - - // load all groups - final Groups groups = tenants.getGroups(); - final Set allGroups = Collections.unmodifiableSet(createGroups(groups, users)); // load all access policies final Policies policies = authorizations.getPolicies(); final Set allPolicies = Collections.unmodifiableSet(createAccessPolicies(policies)); - // create a convenience map to retrieve a user by id - final Map userByIdMap = Collections.unmodifiableMap(createUserByIdMap(allUsers)); - - // create a convenience map to retrieve a user by identity - final Map userByIdentityMap = Collections.unmodifiableMap(createUserByIdentityMap(allUsers)); - - // create a convenience map to retrieve a group by id - final Map groupByIdMap = Collections.unmodifiableMap(createGroupByIdMap(allGroups)); - - // create a convenience map to retrieve the groups for a user identity - final Map> groupsByUserIdentityMap = Collections.unmodifiableMap(createGroupsByUserIdentityMap(allGroups, allUsers)); - // create a convenience map from resource id to policies final Map> policiesByResourceMap = Collections.unmodifiableMap(createResourcePolicyMap(allPolicies)); @@ -89,13 +56,7 @@ public AuthorizationsHolder(final Authorizations authorizations, final Tenants t final Map policiesByIdMap = Collections.unmodifiableMap(createPoliciesByIdMap(allPolicies)); // set all the holders - this.allUsers = allUsers; - this.allGroups = allGroups; this.allPolicies = allPolicies; - this.usersById = userByIdMap; - this.usersByIdentity = userByIdentityMap; - this.groupsById = groupByIdMap; - this.groupsByUserIdentity = groupsByUserIdentityMap; this.policiesByResource = policiesByResourceMap; this.policiesById = policiesByIdMap; } @@ -134,9 +95,9 @@ private Set createAccessPolicies(org.apache.nifi.authorization.fil // add the appropriate request actions final String authorizationCode = policy.getAction(); - if (authorizationCode.equals(FileAuthorizer.READ_CODE)) { + if (authorizationCode.equals(FileAccessPolicyProvider.READ_CODE)) { builder.action(RequestAction.READ); - } else if (authorizationCode.equals(FileAuthorizer.WRITE_CODE)){ + } else if (authorizationCode.equals(FileAccessPolicyProvider.WRITE_CODE)){ builder.action(RequestAction.WRITE); } else { throw new IllegalStateException("Unknown Policy Action: " + authorizationCode); @@ -149,57 +110,6 @@ private Set createAccessPolicies(org.apache.nifi.authorization.fil return allPolicies; } - /** - * Creates a set of Users from the JAXB Users. - * - * @param users the JAXB Users - * @return a set of API Users matching the provided JAXB Users - */ - private Set createUsers(org.apache.nifi.authorization.file.tenants.generated.Users users) { - Set allUsers = new HashSet<>(); - if (users == null || users.getUser() == null) { - return allUsers; - } - - for (org.apache.nifi.authorization.file.tenants.generated.User user : users.getUser()) { - final User.Builder builder = new User.Builder() - .identity(user.getIdentity()) - .identifier(user.getIdentifier()); - - allUsers.add(builder.build()); - } - - return allUsers; - } - - /** - * Creates a set of Groups from the JAXB Groups. - * - * @param groups the JAXB Groups - * @return a set of API Groups matching the provided JAXB Groups - */ - private Set createGroups(org.apache.nifi.authorization.file.tenants.generated.Groups groups, - org.apache.nifi.authorization.file.tenants.generated.Users users) { - Set allGroups = new HashSet<>(); - if (groups == null || groups.getGroup() == null) { - return allGroups; - } - - for (org.apache.nifi.authorization.file.tenants.generated.Group group : groups.getGroup()) { - final Group.Builder builder = new Group.Builder() - .identifier(group.getIdentifier()) - .name(group.getName()); - - for (org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser : group.getUser()) { - builder.addUser(groupUser.getIdentifier()); - } - - allGroups.add(builder.build()); - } - - return allGroups; - } - /** * Creates a map from resource identifier to the set of policies for the given resource. * @@ -221,74 +131,6 @@ private Map> createResourcePolicyMap(final Set createUserByIdMap(final Set users) { - Map usersMap = new HashMap<>(); - for (User user : users) { - usersMap.put(user.getIdentifier(), user); - } - return usersMap; - } - - /** - * Creates a Map from user identity to User. - * - * @param users the set of all users - * @return the Map from user identity to User - */ - private Map createUserByIdentityMap(final Set users) { - Map usersMap = new HashMap<>(); - for (User user : users) { - usersMap.put(user.getIdentity(), user); - } - return usersMap; - } - - /** - * Creates a Map from group identifier to Group. - * - * @param groups the set of all groups - * @return the Map from group identifier to Group - */ - private Map createGroupByIdMap(final Set groups) { - Map groupsMap = new HashMap<>(); - for (Group group : groups) { - groupsMap.put(group.getIdentifier(), group); - } - return groupsMap; - } - - /** - * Creates a Map from user identity to the set of Groups for that identity. - * - * @param groups all groups - * @param users all users - * @return a Map from User identity to the set of Groups for that identity - */ - private Map> createGroupsByUserIdentityMap(final Set groups, final Set users) { - Map> groupsByUserIdentity = new HashMap<>(); - - for (User user : users) { - Set userGroups = new HashSet<>(); - for (Group group : groups) { - for (String groupUser : group.getUsers()) { - if (groupUser.equals(user.getIdentifier())) { - userGroups.add(group); - } - } - } - - groupsByUserIdentity.put(user.getIdentity(), userGroups); - } - - return groupsByUserIdentity; - } - /** * Creates a Map from policy identifier to AccessPolicy. * @@ -307,10 +149,6 @@ public Authorizations getAuthorizations() { return authorizations; } - public Tenants getTenants() { - return tenants; - } - public Set getAllPolicies() { return allPolicies; } @@ -323,27 +161,6 @@ public Map getPoliciesById() { return policiesById; } - public Set getAllUsers() { - return allUsers; - } - - public Map getUsersById() { - return usersById; - } - - public Map getUsersByIdentity() { - return usersByIdentity; - } - - public Set getAllGroups() { - return allGroups; - } - - public Map getGroupsById() { - return groupsById; - } - - @Override public AccessPolicy getAccessPolicy(final String resourceIdentifier, final RequestAction action) { if (resourceIdentifier == null) { throw new IllegalArgumentException("Resource Identifier cannot be null"); @@ -363,20 +180,4 @@ public AccessPolicy getAccessPolicy(final String resourceIdentifier, final Reque return null; } - @Override - public User getUser(String identity) { - if (identity == null) { - throw new IllegalArgumentException("Identity cannot be null"); - } - return usersByIdentity.get(identity); - } - - @Override - public Set getGroups(String userIdentity) { - if (userIdentity == null) { - throw new IllegalArgumentException("User Identity cannot be null"); - } - return groupsByUserIdentity.get(userIdentity); - } - } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java new file mode 100644 index 000000000000..6a6b0593a417 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java @@ -0,0 +1,948 @@ +/* + * 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; + +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.authorization.annotation.AuthorizerContext; +import org.apache.nifi.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.authorization.exception.AuthorizerDestructionException; +import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException; +import org.apache.nifi.authorization.file.generated.Authorizations; +import org.apache.nifi.authorization.file.generated.Policies; +import org.apache.nifi.authorization.file.generated.Policy; +import org.apache.nifi.authorization.resource.ResourceFactory; +import org.apache.nifi.authorization.resource.ResourceType; +import org.apache.nifi.authorization.util.IdentityMapping; +import org.apache.nifi.authorization.util.IdentityMappingUtil; +import org.apache.nifi.components.PropertyValue; +import org.apache.nifi.user.generated.Users; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.util.file.FileUtils; +import org.apache.nifi.web.api.dto.PortDTO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +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 javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvider { + + private static final Logger logger = LoggerFactory.getLogger(FileAccessPolicyProvider.class); + + private static final String AUTHORIZATIONS_XSD = "/authorizations.xsd"; + private static final String JAXB_AUTHORIZATIONS_PATH = "org.apache.nifi.authorization.file.generated"; + + private static final String USERS_XSD = "/legacy-users.xsd"; + private static final String JAXB_USERS_PATH = "org.apache.nifi.user.generated"; + + private static final JAXBContext JAXB_AUTHORIZATIONS_CONTEXT = initializeJaxbContext(JAXB_AUTHORIZATIONS_PATH); + private static final JAXBContext JAXB_USERS_CONTEXT = initializeJaxbContext(JAXB_USERS_PATH); + + /** + * Load the JAXBContext. + */ + private static JAXBContext initializeJaxbContext(final String contextPath) { + try { + return JAXBContext.newInstance(contextPath, FileAuthorizer.class.getClassLoader()); + } catch (JAXBException e) { + throw new RuntimeException("Unable to create JAXBContext."); + } + } + + private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); + private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance(); + + private static final String POLICY_ELEMENT = "policy"; + private static final String POLICY_USER_ELEMENT = "policyUser"; + private static final String POLICY_GROUP_ELEMENT = "policyGroup"; + private static final String IDENTIFIER_ATTR = "identifier"; + private static final String RESOURCE_ATTR = "resource"; + private static final String ACTIONS_ATTR = "actions"; + + static final String READ_CODE = "R"; + static final String WRITE_CODE = "W"; + + static final String PROP_NODE_IDENTITY_PREFIX = "Node Identity "; + static final String PROP_USER_GROUP_PROVIDER = "User Group Provider"; + static final String PROP_AUTHORIZATIONS_FILE = "Authorizations File"; + static final String PROP_INITIAL_ADMIN_IDENTITY = "Initial Admin Identity"; + static final Pattern NODE_IDENTITY_PATTERN = Pattern.compile(PROP_NODE_IDENTITY_PREFIX + "\\S+"); + + private Schema usersSchema; + private Schema authorizationsSchema; + private NiFiProperties properties; + private File authorizationsFile; + private File restoreAuthorizationsFile; + private String rootGroupId; + private String initialAdminIdentity; + private String legacyAuthorizedUsersFile; + private Set nodeIdentities; + private List ports = new ArrayList<>(); + private List identityMappings; + + private UserGroupProvider userGroupProvider; + private UserGroupProviderLookup userGroupProviderLookup; + private final AtomicReference authorizationsHolder = new AtomicReference<>(); + + @Override + public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException { + userGroupProviderLookup = initializationContext.getUserGroupProviderLookup(); + + try { + final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + authorizationsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(AUTHORIZATIONS_XSD)); + usersSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD)); + } catch (Exception e) { + throw new AuthorizerCreationException(e); + } + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + try { + final PropertyValue userGroupProviderIdentifier = configurationContext.getProperty(PROP_USER_GROUP_PROVIDER); + if (!userGroupProviderIdentifier.isSet()) { + throw new AuthorizerCreationException("The user group provider must be specified."); + } + + userGroupProvider = userGroupProviderLookup.getUserGroupProvider(userGroupProviderIdentifier.getValue()); + if (userGroupProvider == null) { + throw new AuthorizerCreationException("Unable to locate user group provider with identifier " + userGroupProviderIdentifier.getValue()); + } + + final PropertyValue authorizationsPath = configurationContext.getProperty(PROP_AUTHORIZATIONS_FILE); + if (StringUtils.isBlank(authorizationsPath.getValue())) { + throw new AuthorizerCreationException("The authorizations file must be specified."); + } + + // get the authorizations file and ensure it exists + authorizationsFile = new File(authorizationsPath.getValue()); + if (!authorizationsFile.exists()) { + logger.info("Creating new authorizations file at {}", new Object[] {authorizationsFile.getAbsolutePath()}); + saveAuthorizations(new Authorizations()); + } + + final File authorizationsFileDirectory = authorizationsFile.getAbsoluteFile().getParentFile(); + + // the restore directory is optional and may be null + final File restoreDirectory = properties.getRestoreDirectory(); + if (restoreDirectory != null) { + // sanity check that restore directory is a directory, creating it if necessary + FileUtils.ensureDirectoryExistAndCanAccess(restoreDirectory); + + // check that restore directory is not the same as the authorizations directory + if (authorizationsFileDirectory.getAbsolutePath().equals(restoreDirectory.getAbsolutePath())) { + throw new AuthorizerCreationException(String.format("Authorizations file directory '%s' is the same as restore directory '%s' ", + authorizationsFileDirectory.getAbsolutePath(), restoreDirectory.getAbsolutePath())); + } + + // the restore copy will have same file name, but reside in a different directory + restoreAuthorizationsFile = new File(restoreDirectory, authorizationsFile.getName()); + + try { + // sync the primary copy with the restore copy + FileUtils.syncWithRestore(authorizationsFile, restoreAuthorizationsFile, logger); + } catch (final IOException | IllegalStateException ioe) { + throw new AuthorizerCreationException(ioe); + } + } + + // extract the identity mappings from nifi.properties if any are provided + identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties)); + + // get the value of the initial admin identity + final PropertyValue initialAdminIdentityProp = configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY); + initialAdminIdentity = initialAdminIdentityProp.isSet() ? IdentityMappingUtil.mapIdentity(initialAdminIdentityProp.getValue(), identityMappings) : null; + + // get the value of the legacy authorized users file + final PropertyValue legacyAuthorizedUsersProp = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE); + legacyAuthorizedUsersFile = legacyAuthorizedUsersProp.isSet() ? legacyAuthorizedUsersProp.getValue() : null; + + // extract any node identities + nodeIdentities = new HashSet<>(); + for (Map.Entry entry : configurationContext.getProperties().entrySet()) { + Matcher matcher = NODE_IDENTITY_PATTERN.matcher(entry.getKey()); + if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) { + nodeIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), identityMappings)); + } + } + + // load the authorizations + load(); + + // if we've copied the authorizations file to a restore directory synchronize it + if (restoreAuthorizationsFile != null) { + FileUtils.copyFile(authorizationsFile, restoreAuthorizationsFile, false, false, logger); + } + + logger.info(String.format("Authorizations file loaded at %s", new Date().toString())); + } catch (IOException | AuthorizerCreationException | JAXBException | IllegalStateException | SAXException e) { + throw new AuthorizerCreationException(e); + } + } + + @Override + public UserGroupProvider getUserGroupProvider() { + return userGroupProvider; + } + + @Override + public Set getAccessPolicies() throws AuthorizationAccessException { + return authorizationsHolder.get().getAllPolicies(); + } + + @Override + public synchronized AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + if (accessPolicy == null) { + throw new IllegalArgumentException("AccessPolicy cannot be null"); + } + + // create the new JAXB Policy + final Policy policy = createJAXBPolicy(accessPolicy); + + // add the new Policy to the top-level list of policies + final AuthorizationsHolder holder = authorizationsHolder.get(); + final Authorizations authorizations = holder.getAuthorizations(); + authorizations.getPolicies().getPolicy().add(policy); + + saveAndRefreshHolder(authorizations); + + return authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier()); + } + + @Override + public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException { + if (identifier == null) { + return null; + } + + final AuthorizationsHolder holder = authorizationsHolder.get(); + return holder.getPoliciesById().get(identifier); + } + + @Override + public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException { + return authorizationsHolder.get().getAccessPolicy(resourceIdentifier, action); + } + + @Override + public synchronized AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + if (accessPolicy == null) { + throw new IllegalArgumentException("AccessPolicy cannot be null"); + } + + final AuthorizationsHolder holder = this.authorizationsHolder.get(); + final Authorizations authorizations = holder.getAuthorizations(); + + // try to find an existing Authorization that matches the policy id + Policy updatePolicy = null; + for (Policy policy : authorizations.getPolicies().getPolicy()) { + if (policy.getIdentifier().equals(accessPolicy.getIdentifier())) { + updatePolicy = policy; + break; + } + } + + // no matching Policy so return null + if (updatePolicy == null) { + return null; + } + + // update the Policy, save, reload, and return + transferUsersAndGroups(accessPolicy, updatePolicy); + saveAndRefreshHolder(authorizations); + + return this.authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier()); + } + + @Override + public synchronized AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + if (accessPolicy == null) { + throw new IllegalArgumentException("AccessPolicy cannot be null"); + } + + final AuthorizationsHolder holder = this.authorizationsHolder.get(); + final Authorizations authorizations = holder.getAuthorizations(); + + // find the matching Policy and remove it + boolean deletedPolicy = false; + Iterator policyIter = authorizations.getPolicies().getPolicy().iterator(); + while (policyIter.hasNext()) { + final Policy policy = policyIter.next(); + if (policy.getIdentifier().equals(accessPolicy.getIdentifier())) { + policyIter.remove(); + deletedPolicy = true; + break; + } + } + + // never found a matching Policy so return null + if (!deletedPolicy) { + return null; + } + + saveAndRefreshHolder(authorizations); + return accessPolicy; + } + + AuthorizationsHolder getAuthorizationsHolder() { + return authorizationsHolder.get(); + } + + @AuthorizerContext + public void setNiFiProperties(NiFiProperties properties) { + this.properties = properties; + } + + @Override + public synchronized void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { + parsePolicies(fingerprint).forEach(policy -> addAccessPolicy(policy)); + } + + @Override + public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { + try { + // ensure we can understand the proposed fingerprint + parsePolicies(proposedFingerprint); + } catch (final AuthorizationAccessException e) { + throw new UninheritableAuthorizationsException("Unable to parse the proposed fingerprint: " + e); + } + + // ensure we are in a proper state to inherit the fingerprint + if (!getAccessPolicies().isEmpty()) { + throw new UninheritableAuthorizationsException("Proposed fingerprint is not inheritable because the current access policies is not empty."); + } + } + + @Override + public String getFingerprint() throws AuthorizationAccessException { + final List policies = new ArrayList<>(getAccessPolicies()); + Collections.sort(policies, Comparator.comparing(AccessPolicy::getIdentifier)); + + XMLStreamWriter writer = null; + final StringWriter out = new StringWriter(); + try { + writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out); + writer.writeStartDocument(); + writer.writeStartElement("accessPolicies"); + + for (AccessPolicy policy : policies) { + writePolicy(writer, policy); + } + + 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(); + } + + private List parsePolicies(final String fingerprint) { + final List policies = new ArrayList<>(); + + 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(); + + // parse all the policies and add them to the current access policy provider + NodeList policyNodes = rootElement.getElementsByTagName(POLICY_ELEMENT); + for (int i = 0; i < policyNodes.getLength(); i++) { + Node policyNode = policyNodes.item(i); + policies.add(parsePolicy((Element) policyNode)); + } + } catch (SAXException | ParserConfigurationException | IOException e) { + throw new AuthorizationAccessException("Unable to parse fingerprint", e); + } + + return policies; + } + + private AccessPolicy parsePolicy(final Element element) { + final AccessPolicy.Builder builder = new AccessPolicy.Builder() + .identifier(element.getAttribute(IDENTIFIER_ATTR)) + .resource(element.getAttribute(RESOURCE_ATTR)); + + final String actions = element.getAttribute(ACTIONS_ATTR); + if (actions.equals(RequestAction.READ.name())) { + builder.action(RequestAction.READ); + } else if (actions.equals(RequestAction.WRITE.name())) { + builder.action(RequestAction.WRITE); + } else { + throw new IllegalStateException("Unknown Policy Action: " + actions); + } + + NodeList policyUsers = element.getElementsByTagName(POLICY_USER_ELEMENT); + for (int i=0; i < policyUsers.getLength(); i++) { + Element policyUserNode = (Element) policyUsers.item(i); + builder.addUser(policyUserNode.getAttribute(IDENTIFIER_ATTR)); + } + + NodeList policyGroups = element.getElementsByTagName(POLICY_GROUP_ELEMENT); + for (int i=0; i < policyGroups.getLength(); i++) { + Element policyGroupNode = (Element) policyGroups.item(i); + builder.addGroup(policyGroupNode.getAttribute(IDENTIFIER_ATTR)); + } + + return builder.build(); + } + + private void writePolicy(final XMLStreamWriter writer, final AccessPolicy policy) throws XMLStreamException { + // sort the users for the policy + List policyUsers = new ArrayList<>(policy.getUsers()); + Collections.sort(policyUsers); + + // sort the groups for this policy + List policyGroups = new ArrayList<>(policy.getGroups()); + Collections.sort(policyGroups); + + writer.writeStartElement(POLICY_ELEMENT); + writer.writeAttribute(IDENTIFIER_ATTR, policy.getIdentifier()); + writer.writeAttribute(RESOURCE_ATTR, policy.getResource()); + writer.writeAttribute(ACTIONS_ATTR, policy.getAction().name()); + + for (String policyUser : policyUsers) { + writer.writeStartElement(POLICY_USER_ELEMENT); + writer.writeAttribute(IDENTIFIER_ATTR, policyUser); + writer.writeEndElement(); + } + + for (String policyGroup : policyGroups) { + writer.writeStartElement(POLICY_GROUP_ELEMENT); + writer.writeAttribute(IDENTIFIER_ATTR, policyGroup); + writer.writeEndElement(); + } + + writer.writeEndElement(); + } + + /** + * Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up. + * + * @throws JAXBException Unable to reload the authorized users file + * @throws IOException Unable to sync file with restore + * @throws IllegalStateException Unable to sync file with restore + */ + private synchronized void load() throws JAXBException, IOException, IllegalStateException, SAXException { + // attempt to unmarshal + final Authorizations authorizations = unmarshallAuthorizations(); + if (authorizations.getPolicies() == null) { + authorizations.setPolicies(new Policies()); + } + + final AuthorizationsHolder authorizationsHolder = new AuthorizationsHolder(authorizations); + final boolean emptyAuthorizations = authorizationsHolder.getAllPolicies().isEmpty(); + final boolean hasInitialAdminIdentity = (initialAdminIdentity != null && !StringUtils.isBlank(initialAdminIdentity)); + final boolean hasLegacyAuthorizedUsers = (legacyAuthorizedUsersFile != null && !StringUtils.isBlank(legacyAuthorizedUsersFile)); + + // if we are starting fresh then we might need to populate an initial admin or convert legacy users + if (emptyAuthorizations) { + parseFlow(); + + if (hasInitialAdminIdentity && hasLegacyAuthorizedUsers) { + throw new AuthorizerCreationException("Cannot provide an Initial Admin Identity and a Legacy Authorized Users File"); + } else if (hasInitialAdminIdentity) { + logger.info("Populating authorizations for Initial Admin: " + initialAdminIdentity); + populateInitialAdmin(authorizations); + } else if (hasLegacyAuthorizedUsers) { + logger.info("Converting " + legacyAuthorizedUsersFile + " to new authorizations model"); + convertLegacyAuthorizedUsers(authorizations); + } + + populateNodes(authorizations); + + // save any changes that were made and repopulate the holder + saveAndRefreshHolder(authorizations); + } else { + this.authorizationsHolder.set(authorizationsHolder); + } + } + + private void saveAuthorizations(final Authorizations authorizations) throws JAXBException { + final Marshaller marshaller = JAXB_AUTHORIZATIONS_CONTEXT.createMarshaller(); + marshaller.setSchema(authorizationsSchema); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + marshaller.marshal(authorizations, authorizationsFile); + } + + private Authorizations unmarshallAuthorizations() throws JAXBException { + final Unmarshaller unmarshaller = JAXB_AUTHORIZATIONS_CONTEXT.createUnmarshaller(); + unmarshaller.setSchema(authorizationsSchema); + + final JAXBElement element = unmarshaller.unmarshal(new StreamSource(authorizationsFile), Authorizations.class); + return element.getValue(); + } + + /** + * Try to parse the flow configuration file to extract the root group id and port information. + * + * @throws SAXException if an error occurs creating the schema + */ + private void parseFlow() throws SAXException { + final FlowParser flowParser = new FlowParser(); + final FlowInfo flowInfo = flowParser.parse(properties.getFlowConfigurationFile()); + + if (flowInfo != null) { + rootGroupId = flowInfo.getRootGroupId(); + ports = flowInfo.getPorts() == null ? new ArrayList<>() : flowInfo.getPorts(); + } + } + + /** + * Creates the initial admin user and policies for access the flow and managing users and policies. + */ + private void populateInitialAdmin(final Authorizations authorizations) { + final User initialAdmin = userGroupProvider.getUserByIdentity(initialAdminIdentity); + if (initialAdmin == null) { + throw new AuthorizerCreationException("Unable to locate initial admin " + initialAdminIdentity + " to seed policies"); + } + + // grant the user read access to the /flow resource + addAccessPolicy(authorizations, ResourceType.Flow.getValue(), initialAdmin.getIdentifier(), READ_CODE); + + // grant the user read access to the root process group resource + if (rootGroupId != null) { + addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), READ_CODE); + addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), WRITE_CODE); + + addAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), READ_CODE); + addAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), WRITE_CODE); + } + + // grant the user write to restricted components + addAccessPolicy(authorizations, ResourceType.RestrictedComponents.getValue(), initialAdmin.getIdentifier(), WRITE_CODE); + + // grant the user read/write access to the /tenants resource + addAccessPolicy(authorizations, ResourceType.Tenant.getValue(), initialAdmin.getIdentifier(), READ_CODE); + addAccessPolicy(authorizations, ResourceType.Tenant.getValue(), initialAdmin.getIdentifier(), WRITE_CODE); + + // grant the user read/write access to the /policies resource + addAccessPolicy(authorizations, ResourceType.Policy.getValue(), initialAdmin.getIdentifier(), READ_CODE); + addAccessPolicy(authorizations, ResourceType.Policy.getValue(), initialAdmin.getIdentifier(), WRITE_CODE); + + // grant the user read/write access to the /controller resource + addAccessPolicy(authorizations, ResourceType.Controller.getValue(), initialAdmin.getIdentifier(), READ_CODE); + addAccessPolicy(authorizations, ResourceType.Controller.getValue(), initialAdmin.getIdentifier(), WRITE_CODE); + } + + /** + * Creates a user for each node and gives the nodes write permission to /proxy. + * + * @param authorizations the overall authorizations + */ + private void populateNodes(Authorizations authorizations) { + for (String nodeIdentity : nodeIdentities) { + final User node = userGroupProvider.getUserByIdentity(nodeIdentity); + if (node == null) { + throw new AuthorizerCreationException("Unable to locate node " + nodeIdentity + " to seed policies."); + } + + // grant access to the proxy resource + addAccessPolicy(authorizations, ResourceType.Proxy.getValue(), node.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, node.getIdentifier(), READ_CODE); + addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, node.getIdentifier(), WRITE_CODE); + } + } + } + + /** + * Unmarshalls an existing authorized-users.xml and converts the object model to the new model. + * + * @param authorizations the current Authorizations instance that policies will be added to + * @throws AuthorizerCreationException if the legacy authorized users file that was provided does not exist + * @throws JAXBException if the legacy authorized users file that was provided could not be unmarshalled + */ + private void convertLegacyAuthorizedUsers(final Authorizations authorizations) throws AuthorizerCreationException, JAXBException { + final File authorizedUsersFile = new File(legacyAuthorizedUsersFile); + if (!authorizedUsersFile.exists()) { + throw new AuthorizerCreationException("Legacy Authorized Users File '" + legacyAuthorizedUsersFile + "' does not exists"); + } + + final Unmarshaller unmarshaller = JAXB_USERS_CONTEXT.createUnmarshaller(); + unmarshaller.setSchema(usersSchema); + + final JAXBElement element = unmarshaller.unmarshal( + new StreamSource(authorizedUsersFile), org.apache.nifi.user.generated.Users.class); + + final org.apache.nifi.user.generated.Users users = element.getValue(); + if (users.getUser().isEmpty()) { + logger.info("Legacy Authorized Users File contained no users, nothing to convert"); + return; + } + + // get all the user DNs into a list + List userIdentities = new ArrayList<>(); + for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) { + userIdentities.add(IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings)); + } + + // sort the list and pull out the first identity + Collections.sort(userIdentities); + final String seedIdentity = userIdentities.get(0); + + // create mapping from Role to access policies + final Map> roleAccessPolicies = RoleAccessPolicy.getMappings(rootGroupId); + + final List allPolicies = new ArrayList<>(); + + for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) { + // create the identifier of the new user based on the DN + final String legacyUserDn = IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings); + final User user = userGroupProvider.getUserByIdentity(legacyUserDn); + if (user == null) { + throw new AuthorizerCreationException("Unable to locate legacy user " + legacyUserDn + " to seed policies."); + } + + // create policies based on the given role + for (org.apache.nifi.user.generated.Role jaxbRole : legacyUser.getRole()) { + Role role = Role.valueOf(jaxbRole.getName()); + Set policies = roleAccessPolicies.get(role); + + for (RoleAccessPolicy roleAccessPolicy : policies) { + + // get the matching policy, or create a new one + Policy policy = getOrCreatePolicy( + allPolicies, + seedIdentity, + roleAccessPolicy.getResource(), + roleAccessPolicy.getAction()); + + // add the user to the policy if it doesn't exist + addUserToPolicy(user.getIdentifier(), policy); + } + } + + } + + // convert any access controls on ports to the appropriate policies + for (PortDTO portDTO : ports) { + final Resource resource; + if (portDTO.getType() != null && portDTO.getType().equals("inputPort")) { + resource = ResourceFactory.getDataTransferResource(ResourceFactory.getComponentResource(ResourceType.InputPort, portDTO.getId(), portDTO.getName())); + } else { + resource = ResourceFactory.getDataTransferResource(ResourceFactory.getComponentResource(ResourceType.OutputPort, portDTO.getId(), portDTO.getName())); + } + + if (portDTO.getUserAccessControl() != null) { + for (String userAccessControl : portDTO.getUserAccessControl()) { + // need to perform the identity mapping on the access control so it matches the identities in the User objects + final String mappedUserAccessControl = IdentityMappingUtil.mapIdentity(userAccessControl, identityMappings); + final User foundUser = userGroupProvider.getUserByIdentity(mappedUserAccessControl); + + // couldn't find the user matching the access control so log a warning and skip + if (foundUser == null) { + logger.warn("Found port with user access control for {} but no user exists with this identity, skipping...", + new Object[] {mappedUserAccessControl}); + continue; + } + + // we found the user so create the appropriate policy and add the user to it + Policy policy = getOrCreatePolicy( + allPolicies, + seedIdentity, + resource.getIdentifier(), + WRITE_CODE); + + addUserToPolicy(foundUser.getIdentifier(), policy); + } + } + + if (portDTO.getGroupAccessControl() != null) { + for (String groupAccessControl : portDTO.getGroupAccessControl()) { + // find a group where the name is the groupAccessControl + Group foundGroup = null; + for (Group group : userGroupProvider.getGroups()) { + if (group.getName().equals(groupAccessControl)) { + foundGroup = group; + break; + } + } + + // couldn't find the group matching the access control so log a warning and skip + if (foundGroup == null) { + logger.warn("Found port with group access control for {} but no group exists with this name, skipping...", + new Object[] {groupAccessControl}); + continue; + } + + // we found the group so create the appropriate policy and add all the users to it + Policy policy = getOrCreatePolicy( + allPolicies, + seedIdentity, + resource.getIdentifier(), + WRITE_CODE); + + addGroupToPolicy(IdentifierUtil.getIdentifier(groupAccessControl), policy); + } + } + } + + authorizations.getPolicies().getPolicy().addAll(allPolicies); + } + + /** + * Creates and adds an access policy for the given resource, identity, and actions to the specified authorizations. + * + * @param authorizations the Authorizations instance to add the policy to + * @param resource the resource for the policy + * @param identity the identity for the policy + * @param action the action for the policy + */ + private void addAccessPolicy(final Authorizations authorizations, final String resource, final String identity, final String action) { + // first try to find an existing policy for the given resource and action + Policy foundPolicy = null; + for (Policy policy : authorizations.getPolicies().getPolicy()) { + if (policy.getResource().equals(resource) && policy.getAction().equals(action)) { + foundPolicy = policy; + break; + } + } + + if (foundPolicy == null) { + // if we didn't find an existing policy create a new one + final String uuidSeed = resource + identity + action; + final String policyIdentifier = IdentifierUtil.getIdentifier(uuidSeed); + + final AccessPolicy.Builder builder = new AccessPolicy.Builder() + .identifier(policyIdentifier) + .resource(resource) + .addUser(identity); + + if (action.equals(READ_CODE)) { + builder.action(RequestAction.READ); + } else if (action.equals(WRITE_CODE)) { + builder.action(RequestAction.WRITE); + } else { + throw new IllegalStateException("Unknown Policy Action: " + action); + } + + final AccessPolicy accessPolicy = builder.build(); + final Policy jaxbPolicy = createJAXBPolicy(accessPolicy); + authorizations.getPolicies().getPolicy().add(jaxbPolicy); + } else { + // otherwise add the user to the existing policy + Policy.User policyUser = new Policy.User(); + policyUser.setIdentifier(identity); + foundPolicy.getUser().add(policyUser); + } + } + + private Policy createJAXBPolicy(final AccessPolicy accessPolicy) { + final Policy policy = new Policy(); + policy.setIdentifier(accessPolicy.getIdentifier()); + policy.setResource(accessPolicy.getResource()); + + switch (accessPolicy.getAction()) { + case READ: + policy.setAction(READ_CODE); + break; + case WRITE: + policy.setAction(WRITE_CODE); + break; + default: + break; + } + + transferUsersAndGroups(accessPolicy, policy); + return policy; + } + + /** + * Sets the given Policy to the state of the provided AccessPolicy. Users and Groups will be cleared and + * set to match the AccessPolicy, the resource and action will be set to match the AccessPolicy. + * + * Does not set the identifier. + * + * @param accessPolicy the AccessPolicy to transfer state from + * @param policy the Policy to transfer state to + */ + private void transferUsersAndGroups(AccessPolicy accessPolicy, Policy policy) { + // add users to the policy + policy.getUser().clear(); + for (String userIdentifier : accessPolicy.getUsers()) { + Policy.User policyUser = new Policy.User(); + policyUser.setIdentifier(userIdentifier); + policy.getUser().add(policyUser); + } + + // add groups to the policy + policy.getGroup().clear(); + for (String groupIdentifier : accessPolicy.getGroups()) { + Policy.Group policyGroup = new Policy.Group(); + policyGroup.setIdentifier(groupIdentifier); + policy.getGroup().add(policyGroup); + } + } + + /** + * Adds the given user identifier to the policy if it doesn't already exist. + * + * @param userIdentifier a user identifier + * @param policy a policy to add the user to + */ + private void addUserToPolicy(final String userIdentifier, final Policy policy) { + // determine if the user already exists in the policy + boolean userExists = false; + for (Policy.User policyUser : policy.getUser()) { + if (policyUser.getIdentifier().equals(userIdentifier)) { + userExists = true; + break; + } + } + + // add the user to the policy if doesn't already exist + if (!userExists) { + Policy.User policyUser = new Policy.User(); + policyUser.setIdentifier(userIdentifier); + policy.getUser().add(policyUser); + } + } + + /** + * Adds the given group identifier to the policy if it doesn't already exist. + * + * @param groupIdentifier a group identifier + * @param policy a policy to add the user to + */ + private void addGroupToPolicy(final String groupIdentifier, final Policy policy) { + // determine if the group already exists in the policy + boolean groupExists = false; + for (Policy.Group policyGroup : policy.getGroup()) { + if (policyGroup.getIdentifier().equals(groupIdentifier)) { + groupExists = true; + break; + } + } + + // add the group to the policy if doesn't already exist + if (!groupExists) { + Policy.Group policyGroup = new Policy.Group(); + policyGroup.setIdentifier(groupIdentifier); + policy.getGroup().add(policyGroup); + } + } + + /** + * Finds the Policy matching the resource and action, or creates a new one and adds it to the list of policies. + * + * @param policies the policies to search through + * @param seedIdentity the seedIdentity to use when creating identifiers for new policies + * @param resource the resource for the policy + * @param action the action string for the police (R or RW) + * @return the matching policy or a new policy + */ + private Policy getOrCreatePolicy(final List policies, final String seedIdentity, final String resource, final String action) { + Policy foundPolicy = null; + + // try to find a policy with the same resource and actions + for (Policy policy : policies) { + if (policy.getResource().equals(resource) && policy.getAction().equals(action)) { + foundPolicy = policy; + break; + } + } + + // if a matching policy wasn't found then create one + if (foundPolicy == null) { + final String uuidSeed = resource + action + seedIdentity; + final String policyIdentifier = IdentifierUtil.getIdentifier(uuidSeed); + + foundPolicy = new Policy(); + foundPolicy.setIdentifier(policyIdentifier); + foundPolicy.setResource(resource); + foundPolicy.setAction(action); + + policies.add(foundPolicy); + } + + return foundPolicy; + } + + /** + * Saves the Authorizations instance by marshalling to a file, then re-populates the + * in-memory data structures and sets the new holder. + * + * Synchronized to ensure only one thread writes the file at a time. + * + * @param authorizations the authorizations to save and populate from + * @throws AuthorizationAccessException if an error occurs saving the authorizations + */ + private synchronized void saveAndRefreshHolder(final Authorizations authorizations) throws AuthorizationAccessException { + try { + saveAuthorizations(authorizations); + + this.authorizationsHolder.set(new AuthorizationsHolder(authorizations)); + } catch (JAXBException e) { + throw new AuthorizationAccessException("Unable to save Authorizations", e); + } + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + } +} 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 9a310a2fcc54..b64a36a27dd6 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 @@ -16,52 +16,17 @@ */ package org.apache.nifi.authorization; -import org.apache.commons.lang3.StringUtils; import org.apache.nifi.authorization.annotation.AuthorizerContext; import org.apache.nifi.authorization.exception.AuthorizationAccessException; import org.apache.nifi.authorization.exception.AuthorizerCreationException; -import org.apache.nifi.authorization.file.generated.Authorizations; -import org.apache.nifi.authorization.file.tenants.generated.Groups; -import org.apache.nifi.authorization.file.generated.Policies; -import org.apache.nifi.authorization.file.generated.Policy; -import org.apache.nifi.authorization.file.tenants.generated.Users; -import org.apache.nifi.authorization.file.tenants.generated.Tenants; -import org.apache.nifi.authorization.resource.ResourceFactory; -import org.apache.nifi.authorization.resource.ResourceType; -import org.apache.nifi.authorization.util.IdentityMapping; -import org.apache.nifi.authorization.util.IdentityMappingUtil; -import org.apache.nifi.components.PropertyValue; import org.apache.nifi.util.NiFiProperties; -import org.apache.nifi.util.file.FileUtils; -import org.apache.nifi.web.api.dto.PortDTO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.xml.sax.SAXException; -import javax.xml.XMLConstants; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; -import javax.xml.bind.Unmarshaller; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; +import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Provides authorizes requests to resources using policies persisted in a file. @@ -70,687 +35,106 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer { private static final Logger logger = LoggerFactory.getLogger(FileAuthorizer.class); - private static final String AUTHORIZATIONS_XSD = "/authorizations.xsd"; - private static final String JAXB_AUTHORIZATIONS_PATH = "org.apache.nifi.authorization.file.generated"; + private static final String FILE_USER_GROUP_PROVIDER_ID = "file-user-group-provider"; + private static final String FILE_ACCESS_POLICY_PROVIDER_ID = "file-access-policy-provider"; - private static final String TENANTS_XSD = "/tenants.xsd"; - private static final String JAXB_TENANTS_PATH = "org.apache.nifi.authorization.file.tenants.generated"; - - private static final String USERS_XSD = "/legacy-users.xsd"; - private static final String JAXB_USERS_PATH = "org.apache.nifi.user.generated"; - - private static final JAXBContext JAXB_AUTHORIZATIONS_CONTEXT = initializeJaxbContext(JAXB_AUTHORIZATIONS_PATH); - private static final JAXBContext JAXB_TENANTS_CONTEXT = initializeJaxbContext(JAXB_TENANTS_PATH); - private static final JAXBContext JAXB_USERS_CONTEXT = initializeJaxbContext(JAXB_USERS_PATH); - - /** - * Load the JAXBContext. - */ - private static JAXBContext initializeJaxbContext(final String contextPath) { - try { - return JAXBContext.newInstance(contextPath, FileAuthorizer.class.getClassLoader()); - } catch (JAXBException e) { - throw new RuntimeException("Unable to create JAXBContext."); - } - } - - static final String READ_CODE = "R"; - static final String WRITE_CODE = "W"; - - static final String PROP_AUTHORIZATIONS_FILE = "Authorizations File"; - static final String PROP_TENANTS_FILE = "Users File"; - static final String PROP_INITIAL_ADMIN_IDENTITY = "Initial Admin Identity"; static final String PROP_LEGACY_AUTHORIZED_USERS_FILE = "Legacy Authorized Users File"; - static final Pattern NODE_IDENTITY_PATTERN = Pattern.compile("Node Identity \\S+"); - private Schema usersSchema; - private Schema tenantsSchema; - private Schema authorizationsSchema; - private SchemaFactory schemaFactory; - private NiFiProperties properties; - private File tenantsFile; - private File authorizationsFile; - private File restoreAuthorizationsFile; - private File restoreTenantsFile; - private String rootGroupId; - private String initialAdminIdentity; - private String legacyAuthorizedUsersFile; - private Set nodeIdentities; - private List ports = new ArrayList<>(); - private List identityMappings; - - private final AtomicReference authorizationsHolder = new AtomicReference<>(); + private FileUserGroupProvider userGroupProvider = new FileUserGroupProvider(); + private FileAccessPolicyProvider accessPolicyProvider = new FileAccessPolicyProvider(); @Override public void initialize(final AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { - try { - schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - tenantsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(TENANTS_XSD)); - authorizationsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(AUTHORIZATIONS_XSD)); - usersSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD)); - } catch (Exception e) { - throw new AuthorizerCreationException(e); - } - } - - @Override - public void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { - try { - final PropertyValue tenantsPath = configurationContext.getProperty(PROP_TENANTS_FILE); - if (StringUtils.isBlank(tenantsPath.getValue())) { - throw new AuthorizerCreationException("The users file must be specified."); - } - - // get the tenants file and ensure it exists - tenantsFile = new File(tenantsPath.getValue()); - if (!tenantsFile.exists()) { - logger.info("Creating new users file at {}", new Object[] {tenantsFile.getAbsolutePath()}); - saveTenants(new Tenants()); - } - - final PropertyValue authorizationsPath = configurationContext.getProperty(PROP_AUTHORIZATIONS_FILE); - if (StringUtils.isBlank(authorizationsPath.getValue())) { - throw new AuthorizerCreationException("The authorizations file must be specified."); - } - - // get the authorizations file and ensure it exists - authorizationsFile = new File(authorizationsPath.getValue()); - if (!authorizationsFile.exists()) { - logger.info("Creating new authorizations file at {}", new Object[] {authorizationsFile.getAbsolutePath()}); - saveAuthorizations(new Authorizations()); - } - - final File authorizationsFileDirectory = authorizationsFile.getAbsoluteFile().getParentFile(); - final File tenantsFileDirectory = tenantsFile.getAbsoluteFile().getParentFile(); - - // the restore directory is optional and may be null - final File restoreDirectory = properties.getRestoreDirectory(); - if (restoreDirectory != null) { - // sanity check that restore directory is a directory, creating it if necessary - FileUtils.ensureDirectoryExistAndCanAccess(restoreDirectory); - - // check that restore directory is not the same as the authorizations directory - if (authorizationsFileDirectory.getAbsolutePath().equals(restoreDirectory.getAbsolutePath())) { - throw new AuthorizerCreationException(String.format("Authorizations file directory '%s' is the same as restore directory '%s' ", - authorizationsFileDirectory.getAbsolutePath(), restoreDirectory.getAbsolutePath())); - } - - // check that restore directory is not the same as the user's directory - if (tenantsFileDirectory.getAbsolutePath().equals(restoreDirectory.getAbsolutePath())) { - throw new AuthorizerCreationException(String.format("Users file directory '%s' is the same as restore directory '%s' ", - tenantsFileDirectory.getAbsolutePath(), restoreDirectory.getAbsolutePath())); - } - - // the restore copy will have same file name, but reside in a different directory - restoreAuthorizationsFile = new File(restoreDirectory, authorizationsFile.getName()); - restoreTenantsFile = new File(restoreDirectory, tenantsFile.getName()); - - try { - // sync the primary copy with the restore copy - FileUtils.syncWithRestore(authorizationsFile, restoreAuthorizationsFile, logger); - FileUtils.syncWithRestore(tenantsFile, restoreTenantsFile, logger); - } catch (final IOException | IllegalStateException ioe) { - throw new AuthorizerCreationException(ioe); - } - } - - // extract the identity mappings from nifi.properties if any are provided - identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties)); - - // get the value of the initial admin identity - final PropertyValue initialAdminIdentityProp = configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY); - initialAdminIdentity = initialAdminIdentityProp == null ? null : IdentityMappingUtil.mapIdentity(initialAdminIdentityProp.getValue(), identityMappings); - - // get the value of the legacy authorized users file - final PropertyValue legacyAuthorizedUsersProp = configurationContext.getProperty(PROP_LEGACY_AUTHORIZED_USERS_FILE); - legacyAuthorizedUsersFile = legacyAuthorizedUsersProp == null ? null : legacyAuthorizedUsersProp.getValue(); - - // extract any node identities - nodeIdentities = new HashSet<>(); - for (Map.Entry entry : configurationContext.getProperties().entrySet()) { - Matcher matcher = NODE_IDENTITY_PATTERN.matcher(entry.getKey()); - if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) { - nodeIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), identityMappings)); - } - } - - // load the authorizations - load(); - - // if we've copied the authorizations file to a restore directory synchronize it - if (restoreAuthorizationsFile != null) { - FileUtils.copyFile(authorizationsFile, restoreAuthorizationsFile, false, false, logger); - } - - // if we've copied the authorizations file to a restore directory synchronize it - if (restoreTenantsFile != null) { - FileUtils.copyFile(tenantsFile, restoreTenantsFile, false, false, logger); - } - - logger.info(String.format("Authorizations file loaded at %s", new Date().toString())); - - } catch (IOException | AuthorizerCreationException | JAXBException | IllegalStateException | SAXException e) { - throw new AuthorizerCreationException(e); - } - } - - /** - * Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up. - * - * @throws JAXBException Unable to reload the authorized users file - * @throws IOException Unable to sync file with restore - * @throws IllegalStateException Unable to sync file with restore - */ - private synchronized void load() throws JAXBException, IOException, IllegalStateException, SAXException { - // attempt to unmarshal - final Authorizations authorizations = unmarshallAuthorizations(); - if (authorizations.getPolicies() == null) { - authorizations.setPolicies(new Policies()); - } - - final Tenants tenants = unmarshallTenants(); - if (tenants.getUsers() == null) { - tenants.setUsers(new Users()); - } - if (tenants.getGroups() == null) { - tenants.setGroups(new Groups()); - } - - final AuthorizationsHolder authorizationsHolder = new AuthorizationsHolder(authorizations, tenants); - final boolean emptyAuthorizations = authorizationsHolder.getAllPolicies().isEmpty(); - final boolean hasInitialAdminIdentity = (initialAdminIdentity != null && !StringUtils.isBlank(initialAdminIdentity)); - final boolean hasLegacyAuthorizedUsers = (legacyAuthorizedUsersFile != null && !StringUtils.isBlank(legacyAuthorizedUsersFile)); - - // if we are starting fresh then we might need to populate an initial admin or convert legacy users - if (emptyAuthorizations) { - parseFlow(); - - if (hasInitialAdminIdentity && hasLegacyAuthorizedUsers) { - throw new AuthorizerCreationException("Cannot provide an Initial Admin Identity and a Legacy Authorized Users File"); - } else if (hasInitialAdminIdentity) { - logger.info("Populating authorizations for Initial Admin: " + initialAdminIdentity); - populateInitialAdmin(authorizations, tenants); - } else if (hasLegacyAuthorizedUsers) { - logger.info("Converting " + legacyAuthorizedUsersFile + " to new authorizations model"); - convertLegacyAuthorizedUsers(authorizations, tenants); - } - - populateNodes(authorizations, tenants); - - // save any changes that were made and repopulate the holder - saveAndRefreshHolder(authorizations, tenants); - } else { - this.authorizationsHolder.set(authorizationsHolder); - } - } - - private Authorizations unmarshallAuthorizations() throws JAXBException { - final Unmarshaller unmarshaller = JAXB_AUTHORIZATIONS_CONTEXT.createUnmarshaller(); - unmarshaller.setSchema(authorizationsSchema); - - final JAXBElement element = unmarshaller.unmarshal(new StreamSource(authorizationsFile), Authorizations.class); - return element.getValue(); - } - - private Tenants unmarshallTenants() throws JAXBException { - final Unmarshaller unmarshaller = JAXB_TENANTS_CONTEXT.createUnmarshaller(); - unmarshaller.setSchema(tenantsSchema); - - final JAXBElement element = unmarshaller.unmarshal(new StreamSource(tenantsFile), Tenants.class); - return element.getValue(); - } - - /** - * Try to parse the flow configuration file to extract the root group id and port information. - * - * @throws SAXException if an error occurs creating the schema - */ - private void parseFlow() throws SAXException { - final FlowParser flowParser = new FlowParser(); - final FlowInfo flowInfo = flowParser.parse(properties.getFlowConfigurationFile()); - - if (flowInfo != null) { - rootGroupId = flowInfo.getRootGroupId(); - ports = flowInfo.getPorts() == null ? new ArrayList<>() : flowInfo.getPorts(); - } - } - - /** - * Creates the initial admin user and policies for access the flow and managing users and policies. - */ - private void populateInitialAdmin(final Authorizations authorizations, Tenants tenants) { - final org.apache.nifi.authorization.file.tenants.generated.User adminUser = getOrCreateUser(tenants, initialAdminIdentity); - - // grant the user read access to the /flow resource - addAccessPolicy(authorizations, ResourceType.Flow.getValue(), adminUser.getIdentifier(), READ_CODE); - - // 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); - } - - // grant the user write to restricted components - addAccessPolicy(authorizations, ResourceType.RestrictedComponents.getValue(), adminUser.getIdentifier(), WRITE_CODE); - - // grant the user read/write access to the /tenants resource - addAccessPolicy(authorizations, ResourceType.Tenant.getValue(), adminUser.getIdentifier(), READ_CODE); - addAccessPolicy(authorizations, ResourceType.Tenant.getValue(), adminUser.getIdentifier(), WRITE_CODE); - - // grant the user read/write access to the /policies resource - addAccessPolicy(authorizations, ResourceType.Policy.getValue(), adminUser.getIdentifier(), READ_CODE); - addAccessPolicy(authorizations, ResourceType.Policy.getValue(), adminUser.getIdentifier(), WRITE_CODE); - - // grant the user read/write access to the /controller resource - addAccessPolicy(authorizations, ResourceType.Controller.getValue(), adminUser.getIdentifier(), READ_CODE); - addAccessPolicy(authorizations, ResourceType.Controller.getValue(), adminUser.getIdentifier(), WRITE_CODE); - } - - /** - * Creates a user for each node and gives the nodes write permission to /proxy. - * - * @param authorizations the overall authorizations - */ - private void populateNodes(Authorizations authorizations, Tenants tenants) { - for (String nodeIdentity : nodeIdentities) { - final org.apache.nifi.authorization.file.tenants.generated.User jaxbNodeUser = getOrCreateUser(tenants, nodeIdentity); - - // grant access to the proxy resource - 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); - } - } - } - - /** - * Unmarshalls an existing authorized-users.xml and converts the object model to the new model. - * - * @param authorizations the current Authorizations instance that policies will be added to - * @param tenants the current Tenants instance users and groups will be added to - * @throws AuthorizerCreationException if the legacy authorized users file that was provided does not exist - * @throws JAXBException if the legacy authorized users file that was provided could not be unmarshalled - */ - private void convertLegacyAuthorizedUsers(final Authorizations authorizations, final Tenants tenants) throws AuthorizerCreationException, JAXBException { - final File authorizedUsersFile = new File(legacyAuthorizedUsersFile); - if (!authorizedUsersFile.exists()) { - throw new AuthorizerCreationException("Legacy Authorized Users File '" + legacyAuthorizedUsersFile + "' does not exists"); - } - - final Unmarshaller unmarshaller = JAXB_USERS_CONTEXT.createUnmarshaller(); - unmarshaller.setSchema(usersSchema); - - final JAXBElement element = unmarshaller.unmarshal( - new StreamSource(authorizedUsersFile), org.apache.nifi.user.generated.Users.class); - - final org.apache.nifi.user.generated.Users users = element.getValue(); - if (users.getUser().isEmpty()) { - logger.info("Legacy Authorized Users File contained no users, nothing to convert"); - return; - } - - // get all the user DNs into a list - List userIdentities = new ArrayList<>(); - for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) { - userIdentities.add(IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings)); - } - - // sort the list and pull out the first identity - Collections.sort(userIdentities); - final String seedIdentity = userIdentities.get(0); - - // create mapping from Role to access policies - final Map> roleAccessPolicies = RoleAccessPolicy.getMappings(rootGroupId); - - final List allPolicies = new ArrayList<>(); - - for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) { - // create the identifier of the new user based on the DN - final String legacyUserDn = IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings); - org.apache.nifi.authorization.file.tenants.generated.User user = getOrCreateUser(tenants, legacyUserDn); - - // if there was a group name find or create the group and add the user to it - org.apache.nifi.authorization.file.tenants.generated.Group group = getOrCreateGroup(tenants, legacyUser.getGroup()); - if (group != null) { - org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User(); - groupUser.setIdentifier(user.getIdentifier()); - group.getUser().add(groupUser); + // initialize the user group provider + userGroupProvider.initialize(new UserGroupProviderInitializationContext() { + @Override + public String getIdentifier() { + return FILE_USER_GROUP_PROVIDER_ID; } - // create policies based on the given role - for (org.apache.nifi.user.generated.Role jaxbRole : legacyUser.getRole()) { - Role role = Role.valueOf(jaxbRole.getName()); - Set policies = roleAccessPolicies.get(role); - - for (RoleAccessPolicy roleAccessPolicy : policies) { - - // get the matching policy, or create a new one - Policy policy = getOrCreatePolicy( - allPolicies, - seedIdentity, - roleAccessPolicy.getResource(), - roleAccessPolicy.getAction()); - - // add the user to the policy if it doesn't exist - addUserToPolicy(user.getIdentifier(), policy); - } + @Override + public UserGroupProviderLookup getUserGroupProviderLookup() { + return (identifier) -> null; } + }); - } - - // convert any access controls on ports to the appropriate policies - for (PortDTO portDTO : ports) { - final Resource resource; - if (portDTO.getType() != null && portDTO.getType().equals("inputPort")) { - resource = ResourceFactory.getDataTransferResource(ResourceFactory.getComponentResource(ResourceType.InputPort, portDTO.getId(), portDTO.getName())); - } else { - resource = ResourceFactory.getDataTransferResource(ResourceFactory.getComponentResource(ResourceType.OutputPort, portDTO.getId(), portDTO.getName())); + // initialize the access policy provider + accessPolicyProvider.initialize(new AccessPolicyProviderInitializationContext() { + @Override + public String getIdentifier() { + return FILE_ACCESS_POLICY_PROVIDER_ID; } - if (portDTO.getUserAccessControl() != null) { - for (String userAccessControl : portDTO.getUserAccessControl()) { - // need to perform the identity mapping on the access control so it matches the identities in the User objects - final String mappedUserAccessControl = IdentityMappingUtil.mapIdentity(userAccessControl, identityMappings); - - // find a user where the identity is the userAccessControl - org.apache.nifi.authorization.file.tenants.generated.User foundUser = null; - for (org.apache.nifi.authorization.file.tenants.generated.User jaxbUser : tenants.getUsers().getUser()) { - if (jaxbUser.getIdentity().equals(mappedUserAccessControl)) { - foundUser = jaxbUser; - break; - } - } - - // couldn't find the user matching the access control so log a warning and skip - if (foundUser == null) { - logger.warn("Found port with user access control for {} but no user exists with this identity, skipping...", - new Object[] {mappedUserAccessControl}); - continue; + @Override + public UserGroupProviderLookup getUserGroupProviderLookup() { + return (identifier) -> { + if (FILE_USER_GROUP_PROVIDER_ID.equals(identifier)) { + return userGroupProvider; } - // we found the user so create the appropriate policy and add the user to it - Policy policy = getOrCreatePolicy( - allPolicies, - seedIdentity, - resource.getIdentifier(), - WRITE_CODE); - - addUserToPolicy(foundUser.getIdentifier(), policy); - } - } - - if (portDTO.getGroupAccessControl() != null) { - for (String groupAccessControl : portDTO.getGroupAccessControl()) { - // find a group where the name is the groupAccessControl - org.apache.nifi.authorization.file.tenants.generated.Group foundGroup = null; - for (org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup : tenants.getGroups().getGroup()) { - if (jaxbGroup.getName().equals(groupAccessControl)) { - foundGroup = jaxbGroup; - break; - } - } - - // couldn't find the group matching the access control so log a warning and skip - if (foundGroup == null) { - logger.warn("Found port with group access control for {} but no group exists with this name, skipping...", - new Object[] {groupAccessControl}); - continue; - } - - // we found the group so create the appropriate policy and add all the users to it - Policy policy = getOrCreatePolicy( - allPolicies, - seedIdentity, - resource.getIdentifier(), - WRITE_CODE); - - addGroupToPolicy(foundGroup.getIdentifier(), policy); - } + return null; + }; } - } - - authorizations.getPolicies().getPolicy().addAll(allPolicies); - } - /** - * Adds the given user identifier to the policy if it doesn't already exist. - * - * @param userIdentifier a user identifier - * @param policy a policy to add the user to - */ - private void addUserToPolicy(final String userIdentifier, final Policy policy) { - // determine if the user already exists in the policy - boolean userExists = false; - for (Policy.User policyUser : policy.getUser()) { - if (policyUser.getIdentifier().equals(userIdentifier)) { - userExists = true; - break; + @Override + public AccessPolicyProviderLookup getAccessPolicyProviderLookup() { + return (identifier) -> null; } - } - - // add the user to the policy if doesn't already exist - if (!userExists) { - Policy.User policyUser = new Policy.User(); - policyUser.setIdentifier(userIdentifier); - policy.getUser().add(policyUser); - } + }); } - /** - * Adds the given group identifier to the policy if it doesn't already exist. - * - * @param groupIdentifier a group identifier - * @param policy a policy to add the user to - */ - private void addGroupToPolicy(final String groupIdentifier, final Policy policy) { - // determine if the group already exists in the policy - boolean groupExists = false; - for (Policy.Group policyGroup : policy.getGroup()) { - if (policyGroup.getIdentifier().equals(groupIdentifier)) { - groupExists = true; - break; - } - } - - // add the group to the policy if doesn't already exist - if (!groupExists) { - Policy.Group policyGroup = new Policy.Group(); - policyGroup.setIdentifier(groupIdentifier); - policy.getGroup().add(policyGroup); - } - } - - /** - * Finds the User with the given identity, or creates a new one and adds it to the Tenants. - * - * @param tenants the Tenants reference - * @param userIdentity the user identity to find or create - * @return the User from Tenants with the given identity, or a new instance that was added to Tenants - */ - private org.apache.nifi.authorization.file.tenants.generated.User getOrCreateUser(final Tenants tenants, final String userIdentity) { - if (StringUtils.isBlank(userIdentity)) { - return null; - } - - org.apache.nifi.authorization.file.tenants.generated.User foundUser = null; - for (org.apache.nifi.authorization.file.tenants.generated.User user : tenants.getUsers().getUser()) { - if (user.getIdentity().equals(userIdentity)) { - foundUser = user; - break; - } - } - - if (foundUser == null) { - final String userIdentifier = UUID.nameUUIDFromBytes(userIdentity.getBytes(StandardCharsets.UTF_8)).toString(); - foundUser = new org.apache.nifi.authorization.file.tenants.generated.User(); - foundUser.setIdentifier(userIdentifier); - foundUser.setIdentity(userIdentity); - tenants.getUsers().getUser().add(foundUser); - } - - return foundUser; - } + @Override + public void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + final Map configurationProperties = configurationContext.getProperties(); - /** - * Finds the Group with the given name, or creates a new one and adds it to Tenants. - * - * @param tenants the Tenants reference - * @param groupName the name of the group to look for - * @return the Group from Tenants with the given name, or a new instance that was added to Tenants - */ - private org.apache.nifi.authorization.file.tenants.generated.Group getOrCreateGroup(final Tenants tenants, final String groupName) { - if (StringUtils.isBlank(groupName)) { - return null; + // relay the relevant config + final Map userGroupProperties = new HashMap<>(); + if (configurationProperties.containsKey(FileUserGroupProvider.PROP_TENANTS_FILE)) { + userGroupProperties.put(FileUserGroupProvider.PROP_TENANTS_FILE, configurationProperties.get(FileUserGroupProvider.PROP_TENANTS_FILE)); } - - org.apache.nifi.authorization.file.tenants.generated.Group foundGroup = null; - for (org.apache.nifi.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) { - if (group.getName().equals(groupName)) { - foundGroup = group; - break; - } + if (configurationProperties.containsKey(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)) { + userGroupProperties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, configurationProperties.get(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)); } - if (foundGroup == null) { - UUID newGroupIdentifier = UUID.nameUUIDFromBytes(groupName.getBytes(StandardCharsets.UTF_8)); - foundGroup = new org.apache.nifi.authorization.file.tenants.generated.Group(); - foundGroup.setIdentifier(newGroupIdentifier.toString()); - foundGroup.setName(groupName); - tenants.getGroups().getGroup().add(foundGroup); + // relay the relevant config + final Map accessPolicyProperties = new HashMap<>(); + accessPolicyProperties.put(FileAccessPolicyProvider.PROP_USER_GROUP_PROVIDER, FILE_USER_GROUP_PROVIDER_ID); + if (configurationProperties.containsKey(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE)) { + accessPolicyProperties.put(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE, configurationProperties.get(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE)); } - - return foundGroup; - } - - /** - * Finds the Policy matching the resource and action, or creates a new one and adds it to the list of policies. - * - * @param policies the policies to search through - * @param seedIdentity the seedIdentity to use when creating identifiers for new policies - * @param resource the resource for the policy - * @param action the action string for the police (R or RW) - * @return the matching policy or a new policy - */ - private Policy getOrCreatePolicy(final List policies, final String seedIdentity, final String resource, final String action) { - Policy foundPolicy = null; - - // try to find a policy with the same resource and actions - for (Policy policy : policies) { - if (policy.getResource().equals(resource) && policy.getAction().equals(action)) { - foundPolicy = policy; - break; - } + if (configurationProperties.containsKey(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)) { + accessPolicyProperties.put(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY, configurationProperties.get(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)); } - - // if a matching policy wasn't found then create one - if (foundPolicy == null) { - final String uuidSeed = resource + action + seedIdentity; - final UUID policyIdentifier = UUID.nameUUIDFromBytes(uuidSeed.getBytes(StandardCharsets.UTF_8)); - - foundPolicy = new Policy(); - foundPolicy.setIdentifier(policyIdentifier.toString()); - foundPolicy.setResource(resource); - foundPolicy.setAction(action); - - policies.add(foundPolicy); + if (configurationProperties.containsKey(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)) { + accessPolicyProperties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, configurationProperties.get(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)); } - return foundPolicy; - } - - /** - * Creates and adds an access policy for the given resource, identity, and actions. - * - * @param authorizations the Authorizations instance to add the policy to - * @param resource the resource for the policy - * @param identity the identity for the policy - * @param action the action for the policy - */ - private void addAccessPolicy(final Authorizations authorizations, final String resource, final String identity, final String action) { - // first try to find an existing policy for the given resource and action - Policy foundPolicy = null; - for (Policy policy : authorizations.getPolicies().getPolicy()) { - if (policy.getResource().equals(resource) && policy.getAction().equals(action)) { - foundPolicy = policy; - break; + // ensure all node identities are seeded into the user provider + configurationProperties.forEach((property, value) -> { + final Matcher matcher = FileAccessPolicyProvider.NODE_IDENTITY_PATTERN.matcher(property); + if (matcher.matches()) { + accessPolicyProperties.put(property, value); + userGroupProperties.put(property.replace(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX, FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX), value); } - } + }); - if (foundPolicy == null) { - // if we didn't find an existing policy create a new one - final String uuidSeed = resource + identity + action; - final UUID policyIdentifier = UUID.nameUUIDFromBytes(uuidSeed.getBytes(StandardCharsets.UTF_8)); - - final AccessPolicy.Builder builder = new AccessPolicy.Builder() - .identifier(policyIdentifier.toString()) - .resource(resource) - .addUser(identity); - - if (action.equals(READ_CODE)) { - builder.action(RequestAction.READ); - } else if (action.equals(WRITE_CODE)) { - builder.action(RequestAction.WRITE); - } else { - throw new IllegalStateException("Unknown Policy Action: " + action); + // ensure the initial admin is seeded into the user provider if appropriate + if (configurationProperties.containsKey(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)) { + int i = 0; + while (true) { + final String key = FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + i++; + if (!userGroupProperties.containsKey(key)) { + userGroupProperties.put(key, configurationProperties.get(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)); + break; + } } - - final AccessPolicy accessPolicy = builder.build(); - final Policy jaxbPolicy = createJAXBPolicy(accessPolicy); - authorizations.getPolicies().getPolicy().add(jaxbPolicy); - } else { - // otherwise add the user to the existing policy - Policy.User policyUser = new Policy.User(); - policyUser.setIdentifier(identity); - foundPolicy.getUser().add(policyUser); - } - } - - /** - * Saves the Authorizations instance by marshalling to a file, then re-populates the - * in-memory data structures and sets the new holder. - * - * Synchronized to ensure only one thread writes the file at a time. - * - * @param authorizations the authorizations to save and populate from - * @param tenants the tenants to save and populate from - * @throws AuthorizationAccessException if an error occurs saving the authorizations - */ - private synchronized void saveAndRefreshHolder(final Authorizations authorizations, final Tenants tenants) throws AuthorizationAccessException { - try { - saveTenants(tenants); - saveAuthorizations(authorizations); - - final AuthorizationsHolder authorizationsHolder = new AuthorizationsHolder(authorizations, tenants); - this.authorizationsHolder.set(authorizationsHolder); - } catch (JAXBException e) { - throw new AuthorizationAccessException("Unable to save Authorizations", e); } - } - private void saveAuthorizations(final Authorizations authorizations) throws JAXBException { - final Marshaller marshaller = JAXB_AUTHORIZATIONS_CONTEXT.createMarshaller(); - marshaller.setSchema(authorizationsSchema); - marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); - marshaller.marshal(authorizations, authorizationsFile); - } + // configure the user group provider + userGroupProvider.onConfigured(new StandardAuthorizerConfigurationContext(FILE_USER_GROUP_PROVIDER_ID, userGroupProperties)); - private void saveTenants(final Tenants tenants) throws JAXBException { - final Marshaller marshaller = JAXB_TENANTS_CONTEXT.createMarshaller(); - marshaller.setSchema(tenantsSchema); - marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); - marshaller.marshal(tenants, tenantsFile); - } - - - @AuthorizerContext - public void setNiFiProperties(NiFiProperties properties) { - this.properties = properties; + // configure the access policy provider + accessPolicyProvider.onConfigured(new StandardAuthorizerConfigurationContext(FILE_USER_GROUP_PROVIDER_ID, accessPolicyProperties)); } @Override @@ -762,434 +146,115 @@ public void preDestruction() { @Override public synchronized Group doAddGroup(Group group) throws AuthorizationAccessException { - if (group == null) { - throw new IllegalArgumentException("Group cannot be null"); - } - - final AuthorizationsHolder holder = this.authorizationsHolder.get(); - final Tenants tenants = holder.getTenants(); - final Authorizations authorizations = holder.getAuthorizations(); - - // determine that all users in the group exist before doing anything, throw an exception if they don't - final Set jaxbUsers = checkGroupUsers(group, tenants.getUsers().getUser()); - - // create a new JAXB Group based on the incoming Group - final org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup = new org.apache.nifi.authorization.file.tenants.generated.Group(); - jaxbGroup.setIdentifier(group.getIdentifier()); - jaxbGroup.setName(group.getName()); - - // add each user to the group - for (String groupUser : group.getUsers()) { - org.apache.nifi.authorization.file.tenants.generated.Group.User jaxbGroupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User(); - jaxbGroupUser.setIdentifier(groupUser); - jaxbGroup.getUser().add(jaxbGroupUser); - } - - tenants.getGroups().getGroup().add(jaxbGroup); - saveAndRefreshHolder(authorizations, tenants); - - return this.authorizationsHolder.get().getGroupsById().get(group.getIdentifier()); + return userGroupProvider.addGroup(group); } @Override public Group getGroup(String identifier) throws AuthorizationAccessException { - if (identifier == null) { - return null; - } - return authorizationsHolder.get().getGroupsById().get(identifier); + return userGroupProvider.getGroup(identifier); } @Override public synchronized Group doUpdateGroup(Group group) throws AuthorizationAccessException { - if (group == null) { - throw new IllegalArgumentException("Group cannot be null"); - } - - final AuthorizationsHolder holder = this.authorizationsHolder.get(); - final Tenants tenants = holder.getTenants(); - final Authorizations authorizations = holder.getAuthorizations(); - - // find the group that needs to be update - org.apache.nifi.authorization.file.tenants.generated.Group updateGroup = null; - for (org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup : tenants.getGroups().getGroup()) { - if (jaxbGroup.getIdentifier().equals(group.getIdentifier())) { - updateGroup = jaxbGroup; - break; - } - } - - // if the group wasn't found return null, otherwise update the group and save changes - if (updateGroup == null) { - return null; - } - - // reset the list of users and add each user to the group - updateGroup.getUser().clear(); - for (String groupUser : group.getUsers()) { - org.apache.nifi.authorization.file.tenants.generated.Group.User jaxbGroupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User(); - jaxbGroupUser.setIdentifier(groupUser); - updateGroup.getUser().add(jaxbGroupUser); - } - - updateGroup.setName(group.getName()); - saveAndRefreshHolder(authorizations, tenants); - - return this.authorizationsHolder.get().getGroupsById().get(group.getIdentifier()); + return userGroupProvider.updateGroup(group); } @Override public synchronized Group deleteGroup(Group group) throws AuthorizationAccessException { - final AuthorizationsHolder holder = this.authorizationsHolder.get(); - final Tenants tenants = holder.getTenants(); - final Authorizations authorizations = holder.getAuthorizations(); - - final List groups = tenants.getGroups().getGroup(); - - // for each policy iterate over the group reference and remove the group reference if it matches the group being deleted - for (Policy policy : authorizations.getPolicies().getPolicy()) { - Iterator policyGroupIter = policy.getGroup().iterator(); - while (policyGroupIter.hasNext()) { - Policy.Group policyGroup = policyGroupIter.next(); - if (policyGroup.getIdentifier().equals(group.getIdentifier())) { - policyGroupIter.remove(); - break; - } - } - } - - // now remove the actual group from the top-level list of groups - boolean removedGroup = false; - Iterator iter = groups.iterator(); - while (iter.hasNext()) { - org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup = iter.next(); - if (group.getIdentifier().equals(jaxbGroup.getIdentifier())) { - iter.remove(); - removedGroup = true; - break; - } - } - - if (removedGroup) { - saveAndRefreshHolder(authorizations, tenants); - return group; - } else { - return null; - } + return userGroupProvider.deleteGroup(group); } @Override public Set getGroups() throws AuthorizationAccessException { - return authorizationsHolder.get().getAllGroups(); - } - - private Set checkGroupUsers(final Group group, final List users) { - final Set jaxbUsers = new HashSet<>(); - for (String groupUser : group.getUsers()) { - boolean found = false; - for (org.apache.nifi.authorization.file.tenants.generated.User jaxbUser : users) { - if (jaxbUser.getIdentifier().equals(groupUser)) { - jaxbUsers.add(jaxbUser); - found = true; - break; - } - } - - if (!found) { - throw new IllegalStateException("Unable to add group because user " + groupUser + " does not exist"); - } - } - return jaxbUsers; + return userGroupProvider.getGroups(); } // ------------------ Users ------------------ @Override public synchronized User doAddUser(final User user) throws AuthorizationAccessException { - if (user == null) { - throw new IllegalArgumentException("User cannot be null"); - } - - final org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = createJAXBUser(user); - - final AuthorizationsHolder holder = this.authorizationsHolder.get(); - final Tenants tenants = holder.getTenants(); - final Authorizations authorizations = holder.getAuthorizations(); - tenants.getUsers().getUser().add(jaxbUser); - - saveAndRefreshHolder(authorizations, tenants); - - return this.authorizationsHolder.get().getUsersById().get(user.getIdentifier()); - } - - private org.apache.nifi.authorization.file.tenants.generated.User createJAXBUser(User user) { - final org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = new org.apache.nifi.authorization.file.tenants.generated.User(); - jaxbUser.setIdentifier(user.getIdentifier()); - jaxbUser.setIdentity(user.getIdentity()); - return jaxbUser; + return userGroupProvider.addUser(user); } @Override public User getUser(final String identifier) throws AuthorizationAccessException { - if (identifier == null) { - return null; - } - - final AuthorizationsHolder holder = authorizationsHolder.get(); - return holder.getUsersById().get(identifier); + return userGroupProvider.getUser(identifier); } @Override public User getUserByIdentity(final String identity) throws AuthorizationAccessException { - if (identity == null) { - return null; - } - - final AuthorizationsHolder holder = authorizationsHolder.get(); - return holder.getUsersByIdentity().get(identity); + return userGroupProvider.getUserByIdentity(identity); } @Override public synchronized User doUpdateUser(final User user) throws AuthorizationAccessException { - if (user == null) { - throw new IllegalArgumentException("User cannot be null"); - } - - final AuthorizationsHolder holder = this.authorizationsHolder.get(); - final Tenants tenants = holder.getTenants(); - final Authorizations authorizations = holder.getAuthorizations(); - - final List users = tenants.getUsers().getUser(); - - // fine the User that needs to be updated - org.apache.nifi.authorization.file.tenants.generated.User updateUser = null; - for (org.apache.nifi.authorization.file.tenants.generated.User jaxbUser : users) { - if (user.getIdentifier().equals(jaxbUser.getIdentifier())) { - updateUser = jaxbUser; - break; - } - } - - // if user wasn't found return null, otherwise update the user and save changes - if (updateUser == null) { - return null; - } else { - updateUser.setIdentity(user.getIdentity()); - saveAndRefreshHolder(authorizations, tenants); - - return this.authorizationsHolder.get().getUsersById().get(user.getIdentifier()); - } + return userGroupProvider.updateUser(user); } @Override public synchronized User deleteUser(final User user) throws AuthorizationAccessException { - if (user == null) { - throw new IllegalArgumentException("User cannot be null"); - } - - final AuthorizationsHolder holder = this.authorizationsHolder.get(); - final Tenants tenants = holder.getTenants(); - final Authorizations authorizations = holder.getAuthorizations(); - - final List users = tenants.getUsers().getUser(); - - // for each group iterate over the user references and remove the user reference if it matches the user being deleted - for (org.apache.nifi.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) { - Iterator groupUserIter = group.getUser().iterator(); - while (groupUserIter.hasNext()) { - org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser = groupUserIter.next(); - if (groupUser.getIdentifier().equals(user.getIdentifier())) { - groupUserIter.remove(); - break; - } - } - } - - // remove any references to the user being deleted from policies - for (Policy policy : authorizations.getPolicies().getPolicy()) { - Iterator policyUserIter = policy.getUser().iterator(); - while (policyUserIter.hasNext()) { - Policy.User policyUser = policyUserIter.next(); - if (policyUser.getIdentifier().equals(user.getIdentifier())) { - policyUserIter.remove(); - break; - } - } - } - - // remove the actual user if it exists - boolean removedUser = false; - Iterator iter = users.iterator(); - while (iter.hasNext()) { - org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = iter.next(); - if (user.getIdentifier().equals(jaxbUser.getIdentifier())) { - iter.remove(); - removedUser = true; - break; - } - } - - if (removedUser) { - saveAndRefreshHolder(authorizations, tenants); - return user; - } else { - return null; - } + return userGroupProvider.deleteUser(user); } @Override public Set getUsers() throws AuthorizationAccessException { - return authorizationsHolder.get().getAllUsers(); + return userGroupProvider.getUsers(); } // ------------------ AccessPolicies ------------------ @Override public synchronized AccessPolicy doAddAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException { - if (accessPolicy == null) { - throw new IllegalArgumentException("AccessPolicy cannot be null"); - } - - // create the new JAXB Policy - final Policy policy = createJAXBPolicy(accessPolicy); - - // add the new Policy to the top-level list of policies - final AuthorizationsHolder holder = this.authorizationsHolder.get(); - final Tenants tenants = holder.getTenants(); - final Authorizations authorizations = holder.getAuthorizations(); - authorizations.getPolicies().getPolicy().add(policy); - - saveAndRefreshHolder(authorizations, tenants); - - return this.authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier()); - } - - private Policy createJAXBPolicy(final AccessPolicy accessPolicy) { - final Policy policy = new Policy(); - policy.setIdentifier(accessPolicy.getIdentifier()); - policy.setResource(accessPolicy.getResource()); - - switch (accessPolicy.getAction()) { - case READ: - policy.setAction(READ_CODE); - break; - case WRITE: - policy.setAction(WRITE_CODE); - break; - default: - break; - } - - transferUsersAndGroups(accessPolicy, policy); - return policy; + return accessPolicyProvider.addAccessPolicy(accessPolicy); } @Override public AccessPolicy getAccessPolicy(final String identifier) throws AuthorizationAccessException { - if (identifier == null) { - return null; - } - - final AuthorizationsHolder holder = authorizationsHolder.get(); - return holder.getPoliciesById().get(identifier); + return accessPolicyProvider.getAccessPolicy(identifier); } @Override public synchronized AccessPolicy updateAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException { - if (accessPolicy == null) { - throw new IllegalArgumentException("AccessPolicy cannot be null"); - } - - final AuthorizationsHolder holder = this.authorizationsHolder.get(); - final Tenants tenants = holder.getTenants(); - final Authorizations authorizations = holder.getAuthorizations(); - - // try to find an existing Authorization that matches the policy id - Policy updatePolicy = null; - for (Policy policy : authorizations.getPolicies().getPolicy()) { - if (policy.getIdentifier().equals(accessPolicy.getIdentifier())) { - updatePolicy = policy; - break; - } - } - - // no matching Policy so return null - if (updatePolicy == null) { - return null; - } - - // update the Policy, save, reload, and return - transferUsersAndGroups(accessPolicy, updatePolicy); - saveAndRefreshHolder(authorizations, tenants); - - return this.authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier()); + return accessPolicyProvider.updateAccessPolicy(accessPolicy); } @Override public synchronized AccessPolicy deleteAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException { - if (accessPolicy == null) { - throw new IllegalArgumentException("AccessPolicy cannot be null"); - } - - final AuthorizationsHolder holder = this.authorizationsHolder.get(); - final Tenants tenants = holder.getTenants(); - final Authorizations authorizations = holder.getAuthorizations(); - - // find the matching Policy and remove it - boolean deletedPolicy = false; - Iterator policyIter = authorizations.getPolicies().getPolicy().iterator(); - while (policyIter.hasNext()) { - final Policy policy = policyIter.next(); - if (policy.getIdentifier().equals(accessPolicy.getIdentifier())) { - policyIter.remove(); - deletedPolicy = true; - break; - } - } - - // never found a matching Policy so return null - if (!deletedPolicy) { - return null; - } - - saveAndRefreshHolder(authorizations, tenants); - return accessPolicy; + return accessPolicyProvider.deleteAccessPolicy(accessPolicy); } @Override public Set getAccessPolicies() throws AuthorizationAccessException { - return authorizationsHolder.get().getAllPolicies(); + return accessPolicyProvider.getAccessPolicies(); } - @Override - public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException { - return authorizationsHolder.get(); + @AuthorizerContext + public void setNiFiProperties(NiFiProperties properties) { + userGroupProvider.setNiFiProperties(properties); + accessPolicyProvider.setNiFiProperties(properties); } - /** - * Sets the given Policy to the state of the provided AccessPolicy. Users and Groups will be cleared and - * set to match the AccessPolicy, the resource and action will be set to match the AccessPolicy. - * - * Does not set the identifier. - * - * @param accessPolicy the AccessPolicy to transfer state from - * @param policy the Policy to transfer state to - */ - private void transferUsersAndGroups(AccessPolicy accessPolicy, Policy policy) { - // add users to the policy - policy.getUser().clear(); - for (String userIdentifier : accessPolicy.getUsers()) { - Policy.User policyUser = new Policy.User(); - policyUser.setIdentifier(userIdentifier); - policy.getUser().add(policyUser); - } + @Override + public synchronized UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException { + final AuthorizationsHolder authorizationsHolder = accessPolicyProvider.getAuthorizationsHolder(); + final UserGroupHolder userGroupHolder = userGroupProvider.getUserGroupHolder(); - // add groups to the policy - policy.getGroup().clear(); - for (String groupIdentifier : accessPolicy.getGroups()) { - Policy.Group policyGroup = new Policy.Group(); - policyGroup.setIdentifier(groupIdentifier); - policy.getGroup().add(policyGroup); - } + return new UsersAndAccessPolicies() { + @Override + public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) { + return authorizationsHolder.getAccessPolicy(resourceIdentifier, action); + } + + @Override + public User getUser(String identity) { + return userGroupHolder.getUser(identity); + } + + @Override + public Set getGroups(String userIdentity) { + return userGroupHolder.getGroups(userIdentity); + } + }; } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java new file mode 100644 index 000000000000..59c829c84832 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java @@ -0,0 +1,820 @@ +/* + * 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; + +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.authorization.annotation.AuthorizerContext; +import org.apache.nifi.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.authorization.exception.AuthorizerDestructionException; +import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException; +import org.apache.nifi.authorization.file.tenants.generated.Groups; +import org.apache.nifi.authorization.file.tenants.generated.Tenants; +import org.apache.nifi.authorization.file.tenants.generated.Users; +import org.apache.nifi.authorization.util.IdentityMapping; +import org.apache.nifi.authorization.util.IdentityMappingUtil; +import org.apache.nifi.components.PropertyValue; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.util.file.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +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 javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FileUserGroupProvider implements ConfigurableUserGroupProvider { + + private static final Logger logger = LoggerFactory.getLogger(FileUserGroupProvider.class); + + private static final String TENANTS_XSD = "/tenants.xsd"; + private static final String JAXB_TENANTS_PATH = "org.apache.nifi.authorization.file.tenants.generated"; + + private static final String USERS_XSD = "/legacy-users.xsd"; + private static final String JAXB_USERS_PATH = "org.apache.nifi.user.generated"; + + private static final JAXBContext JAXB_TENANTS_CONTEXT = initializeJaxbContext(JAXB_TENANTS_PATH); + private static final JAXBContext JAXB_USERS_CONTEXT = initializeJaxbContext(JAXB_USERS_PATH); + + /** + * Load the JAXBContext. + */ + private static JAXBContext initializeJaxbContext(final String contextPath) { + try { + return JAXBContext.newInstance(contextPath, FileAuthorizer.class.getClassLoader()); + } catch (JAXBException e) { + throw new RuntimeException("Unable to create JAXBContext."); + } + } + + private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); + private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance(); + + private static final String USER_ELEMENT = "user"; + private static final String GROUP_USER_ELEMENT = "groupUser"; + private static final String GROUP_ELEMENT = "group"; + private static final String IDENTIFIER_ATTR = "identifier"; + private static final String IDENTITY_ATTR = "identity"; + private static final String NAME_ATTR = "name"; + + static final String PROP_INITIAL_USER_IDENTITY_PREFIX = "Initial User Identity "; + static final String PROP_TENANTS_FILE = "Users File"; + static final Pattern INITIAL_USER_IDENTITY_PATTERN = Pattern.compile(PROP_INITIAL_USER_IDENTITY_PREFIX + "\\S+"); + + private Schema usersSchema; + private Schema tenantsSchema; + private NiFiProperties properties; + private File tenantsFile; + private File restoreTenantsFile; + private String legacyAuthorizedUsersFile; + private Set initialUserIdentities; + private List identityMappings; + + private final AtomicReference userGroupHolder = new AtomicReference<>(); + + @Override + public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException { + try { + final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + tenantsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(TENANTS_XSD)); + usersSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD)); + } catch (Exception e) { + throw new AuthorizerCreationException(e); + } + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + try { + final PropertyValue tenantsPath = configurationContext.getProperty(PROP_TENANTS_FILE); + if (StringUtils.isBlank(tenantsPath.getValue())) { + throw new AuthorizerCreationException("The users file must be specified."); + } + + // get the tenants file and ensure it exists + tenantsFile = new File(tenantsPath.getValue()); + if (!tenantsFile.exists()) { + logger.info("Creating new users file at {}", new Object[] {tenantsFile.getAbsolutePath()}); + saveTenants(new Tenants()); + } + + final File tenantsFileDirectory = tenantsFile.getAbsoluteFile().getParentFile(); + + // the restore directory is optional and may be null + final File restoreDirectory = properties.getRestoreDirectory(); + if (restoreDirectory != null) { + // sanity check that restore directory is a directory, creating it if necessary + FileUtils.ensureDirectoryExistAndCanAccess(restoreDirectory); + + // check that restore directory is not the same as the user's directory + if (tenantsFileDirectory.getAbsolutePath().equals(restoreDirectory.getAbsolutePath())) { + throw new AuthorizerCreationException(String.format("Users file directory '%s' is the same as restore directory '%s' ", + tenantsFileDirectory.getAbsolutePath(), restoreDirectory.getAbsolutePath())); + } + + // the restore copy will have same file name, but reside in a different directory + restoreTenantsFile = new File(restoreDirectory, tenantsFile.getName()); + + try { + // sync the primary copy with the restore copy + FileUtils.syncWithRestore(tenantsFile, restoreTenantsFile, logger); + } catch (final IOException | IllegalStateException ioe) { + throw new AuthorizerCreationException(ioe); + } + } + + // extract the identity mappings from nifi.properties if any are provided + identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties)); + + // get the value of the legacy authorized users file + final PropertyValue legacyAuthorizedUsersProp = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE); + legacyAuthorizedUsersFile = legacyAuthorizedUsersProp.isSet() ? legacyAuthorizedUsersProp.getValue() : null; + + // extract any node identities + initialUserIdentities = new HashSet<>(); + for (Map.Entry entry : configurationContext.getProperties().entrySet()) { + Matcher matcher = INITIAL_USER_IDENTITY_PATTERN.matcher(entry.getKey()); + if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) { + initialUserIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), identityMappings)); + } + } + + load(); + + // if we've copied the authorizations file to a restore directory synchronize it + if (restoreTenantsFile != null) { + FileUtils.copyFile(tenantsFile, restoreTenantsFile, false, false, logger); + } + + logger.info(String.format("Users/Groups file loaded at %s", new Date().toString())); + } catch (IOException | AuthorizerCreationException | JAXBException | IllegalStateException | SAXException e) { + throw new AuthorizerCreationException(e); + } + } + + @Override + public Set getUsers() throws AuthorizationAccessException { + return userGroupHolder.get().getAllUsers(); + } + + @Override + public synchronized User addUser(User user) throws AuthorizationAccessException { + if (user == null) { + throw new IllegalArgumentException("User cannot be null"); + } + + final org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = createJAXBUser(user); + + final UserGroupHolder holder = userGroupHolder.get(); + final Tenants tenants = holder.getTenants(); + tenants.getUsers().getUser().add(jaxbUser); + + saveAndRefreshHolder(tenants); + + return userGroupHolder.get().getUsersById().get(user.getIdentifier()); + } + + @Override + public User getUser(String identifier) throws AuthorizationAccessException { + if (identifier == null) { + return null; + } + + final UserGroupHolder holder = userGroupHolder.get(); + return holder.getUsersById().get(identifier); + } + + @Override + public synchronized User updateUser(User user) throws AuthorizationAccessException { + if (user == null) { + throw new IllegalArgumentException("User cannot be null"); + } + + final UserGroupHolder holder = userGroupHolder.get(); + final Tenants tenants = holder.getTenants(); + + final List users = tenants.getUsers().getUser(); + + // fine the User that needs to be updated + org.apache.nifi.authorization.file.tenants.generated.User updateUser = null; + for (org.apache.nifi.authorization.file.tenants.generated.User jaxbUser : users) { + if (user.getIdentifier().equals(jaxbUser.getIdentifier())) { + updateUser = jaxbUser; + break; + } + } + + // if user wasn't found return null, otherwise update the user and save changes + if (updateUser == null) { + return null; + } else { + updateUser.setIdentity(user.getIdentity()); + saveAndRefreshHolder(tenants); + + return userGroupHolder.get().getUsersById().get(user.getIdentifier()); + } + } + + @Override + public User getUserByIdentity(String identity) throws AuthorizationAccessException { + if (identity == null) { + return null; + } + + final UserGroupHolder holder = userGroupHolder.get(); + return holder.getUsersByIdentity().get(identity); + } + + @Override + public synchronized User deleteUser(User user) throws AuthorizationAccessException { + if (user == null) { + throw new IllegalArgumentException("User cannot be null"); + } + + final UserGroupHolder holder = userGroupHolder.get(); + final Tenants tenants = holder.getTenants(); + + final List users = tenants.getUsers().getUser(); + + // for each group iterate over the user references and remove the user reference if it matches the user being deleted + for (org.apache.nifi.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) { + Iterator groupUserIter = group.getUser().iterator(); + while (groupUserIter.hasNext()) { + org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser = groupUserIter.next(); + if (groupUser.getIdentifier().equals(user.getIdentifier())) { + groupUserIter.remove(); + break; + } + } + } + + // remove the actual user if it exists + boolean removedUser = false; + Iterator iter = users.iterator(); + while (iter.hasNext()) { + org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = iter.next(); + if (user.getIdentifier().equals(jaxbUser.getIdentifier())) { + iter.remove(); + removedUser = true; + break; + } + } + + if (removedUser) { + saveAndRefreshHolder(tenants); + return user; + } else { + return null; + } + } + + @Override + public Set getGroups() throws AuthorizationAccessException { + return userGroupHolder.get().getAllGroups(); + } + + @Override + public synchronized Group addGroup(Group group) throws AuthorizationAccessException { + if (group == null) { + throw new IllegalArgumentException("Group cannot be null"); + } + + final UserGroupHolder holder = userGroupHolder.get(); + final Tenants tenants = holder.getTenants(); + + // determine that all users in the group exist before doing anything, throw an exception if they don't + checkGroupUsers(group, tenants.getUsers().getUser()); + + // create a new JAXB Group based on the incoming Group + final org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup = new org.apache.nifi.authorization.file.tenants.generated.Group(); + jaxbGroup.setIdentifier(group.getIdentifier()); + jaxbGroup.setName(group.getName()); + + // add each user to the group + for (String groupUser : group.getUsers()) { + org.apache.nifi.authorization.file.tenants.generated.Group.User jaxbGroupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User(); + jaxbGroupUser.setIdentifier(groupUser); + jaxbGroup.getUser().add(jaxbGroupUser); + } + + tenants.getGroups().getGroup().add(jaxbGroup); + saveAndRefreshHolder(tenants); + + return userGroupHolder.get().getGroupsById().get(group.getIdentifier()); + } + + @Override + public Group getGroup(String identifier) throws AuthorizationAccessException { + if (identifier == null) { + return null; + } + return userGroupHolder.get().getGroupsById().get(identifier); + } + + @Override + public UserAndGroups getUserAndGroups(final String identity) throws AuthorizationAccessException { + final UserGroupHolder holder = userGroupHolder.get(); + final User user = holder.getUser(identity); + final Set groups = holder.getGroups(identity); + + return new UserAndGroups() { + @Override + public User getUser() { + return user; + } + + @Override + public Set getGroups() { + return groups; + } + }; + } + + @Override + public synchronized Group updateGroup(Group group) throws AuthorizationAccessException { + if (group == null) { + throw new IllegalArgumentException("Group cannot be null"); + } + + final UserGroupHolder holder = userGroupHolder.get(); + final Tenants tenants = holder.getTenants(); + + // find the group that needs to be update + org.apache.nifi.authorization.file.tenants.generated.Group updateGroup = null; + for (org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup : tenants.getGroups().getGroup()) { + if (jaxbGroup.getIdentifier().equals(group.getIdentifier())) { + updateGroup = jaxbGroup; + break; + } + } + + // if the group wasn't found return null, otherwise update the group and save changes + if (updateGroup == null) { + return null; + } + + // reset the list of users and add each user to the group + updateGroup.getUser().clear(); + for (String groupUser : group.getUsers()) { + org.apache.nifi.authorization.file.tenants.generated.Group.User jaxbGroupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User(); + jaxbGroupUser.setIdentifier(groupUser); + updateGroup.getUser().add(jaxbGroupUser); + } + + updateGroup.setName(group.getName()); + saveAndRefreshHolder(tenants); + + return userGroupHolder.get().getGroupsById().get(group.getIdentifier()); + } + + @Override + public synchronized Group deleteGroup(Group group) throws AuthorizationAccessException { + final UserGroupHolder holder = userGroupHolder.get(); + final Tenants tenants = holder.getTenants(); + + final List groups = tenants.getGroups().getGroup(); + + // now remove the actual group from the top-level list of groups + boolean removedGroup = false; + Iterator iter = groups.iterator(); + while (iter.hasNext()) { + org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup = iter.next(); + if (group.getIdentifier().equals(jaxbGroup.getIdentifier())) { + iter.remove(); + removedGroup = true; + break; + } + } + + if (removedGroup) { + saveAndRefreshHolder(tenants); + return group; + } else { + return null; + } + } + + UserGroupHolder getUserGroupHolder() { + return userGroupHolder.get(); + } + + @AuthorizerContext + public void setNiFiProperties(NiFiProperties properties) { + this.properties = properties; + } + + @Override + public synchronized void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { + final UsersAndGroups usersAndGroups = parseUsersAndGroups(fingerprint); + usersAndGroups.getUsers().forEach(user -> addUser(user)); + usersAndGroups.getGroups().forEach(group -> addGroup(group)); + } + + @Override + public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException { + try { + // ensure we understand the proposed fingerprint + parseUsersAndGroups(proposedFingerprint); + } catch (final AuthorizationAccessException e) { + throw new UninheritableAuthorizationsException("Unable to parse the proposed fingerprint: " + e); + } + + final UserGroupHolder usersAndGroups = userGroupHolder.get(); + + // ensure we are in a proper state to inherit the fingerprint + if (!usersAndGroups.getAllUsers().isEmpty() || !usersAndGroups.getAllGroups().isEmpty()) { + throw new UninheritableAuthorizationsException("Proposed fingerprint is not inheritable because the current users and groups is not empty."); + } + } + + @Override + public String getFingerprint() throws AuthorizationAccessException { + final UserGroupHolder usersAndGroups = userGroupHolder.get(); + + final List users = new ArrayList<>(usersAndGroups.getAllUsers()); + Collections.sort(users, Comparator.comparing(User::getIdentifier)); + + final List groups = new ArrayList<>(usersAndGroups.getAllGroups()); + Collections.sort(groups, Comparator.comparing(Group::getIdentifier)); + + XMLStreamWriter writer = null; + final StringWriter out = new StringWriter(); + try { + writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out); + writer.writeStartDocument(); + writer.writeStartElement("tenants"); + + for (User user : users) { + writeUser(writer, user); + } + for (Group group : groups) { + writeGroup(writer, group); + } + + 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(); + } + + private UsersAndGroups parseUsersAndGroups(final String fingerprint) { + final List users = new ArrayList<>(); + final List groups = new ArrayList<>(); + + 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(); + + // parse all the users and add them to the current user group provider + NodeList userNodes = rootElement.getElementsByTagName(USER_ELEMENT); + for (int i=0; i < userNodes.getLength(); i++) { + Node userNode = userNodes.item(i); + users.add(parseUser((Element) userNode)); + } + + // parse all the groups and add them to the current user group provider + NodeList groupNodes = rootElement.getElementsByTagName(GROUP_ELEMENT); + for (int i=0; i < groupNodes.getLength(); i++) { + Node groupNode = groupNodes.item(i); + groups.add(parseGroup((Element) groupNode)); + } + } catch (SAXException | ParserConfigurationException | IOException e) { + throw new AuthorizationAccessException("Unable to parse fingerprint", e); + } + + return new UsersAndGroups(users, groups); + } + + private User parseUser(final Element element) { + final User.Builder builder = new User.Builder() + .identifier(element.getAttribute(IDENTIFIER_ATTR)) + .identity(element.getAttribute(IDENTITY_ATTR)); + + return builder.build(); + } + + private Group parseGroup(final Element element) { + final Group.Builder builder = new Group.Builder() + .identifier(element.getAttribute(IDENTIFIER_ATTR)) + .name(element.getAttribute(NAME_ATTR)); + + NodeList groupUsers = element.getElementsByTagName(GROUP_USER_ELEMENT); + for (int i=0; i < groupUsers.getLength(); i++) { + Element groupUserNode = (Element) groupUsers.item(i); + builder.addUser(groupUserNode.getAttribute(IDENTIFIER_ATTR)); + } + + return builder.build(); + } + + private void writeUser(final XMLStreamWriter writer, final User user) throws XMLStreamException { + writer.writeStartElement(USER_ELEMENT); + writer.writeAttribute(IDENTIFIER_ATTR, user.getIdentifier()); + writer.writeAttribute(IDENTITY_ATTR, user.getIdentity()); + writer.writeEndElement(); + } + + private void writeGroup(final XMLStreamWriter writer, final Group group) throws XMLStreamException { + List users = new ArrayList<>(group.getUsers()); + Collections.sort(users); + + writer.writeStartElement(GROUP_ELEMENT); + writer.writeAttribute(IDENTIFIER_ATTR, group.getIdentifier()); + writer.writeAttribute(NAME_ATTR, group.getName()); + + for (String user : users) { + writer.writeStartElement(GROUP_USER_ELEMENT); + writer.writeAttribute(IDENTIFIER_ATTR, user); + writer.writeEndElement(); + } + + writer.writeEndElement(); + } + + private org.apache.nifi.authorization.file.tenants.generated.User createJAXBUser(User user) { + final org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = new org.apache.nifi.authorization.file.tenants.generated.User(); + jaxbUser.setIdentifier(user.getIdentifier()); + jaxbUser.setIdentity(user.getIdentity()); + return jaxbUser; + } + + private Set checkGroupUsers(final Group group, final List users) { + final Set jaxbUsers = new HashSet<>(); + for (String groupUser : group.getUsers()) { + boolean found = false; + for (org.apache.nifi.authorization.file.tenants.generated.User jaxbUser : users) { + if (jaxbUser.getIdentifier().equals(groupUser)) { + jaxbUsers.add(jaxbUser); + found = true; + break; + } + } + + if (!found) { + throw new IllegalStateException("Unable to add group because user " + groupUser + " does not exist"); + } + } + return jaxbUsers; + } + + /** + * Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up. + * + * @throws JAXBException Unable to reload the authorized users file + * @throws IllegalStateException Unable to sync file with restore + * @throws SAXException Unable to unmarshall tenants + */ + private synchronized void load() throws JAXBException, IllegalStateException, SAXException { + final Tenants tenants = unmarshallTenants(); + if (tenants.getUsers() == null) { + tenants.setUsers(new Users()); + } + if (tenants.getGroups() == null) { + tenants.setGroups(new Groups()); + } + + final UserGroupHolder userGroupHolder = new UserGroupHolder(tenants); + final boolean emptyTenants = userGroupHolder.getAllUsers().isEmpty() && userGroupHolder.getAllGroups().isEmpty(); + final boolean hasLegacyAuthorizedUsers = (legacyAuthorizedUsersFile != null && !StringUtils.isBlank(legacyAuthorizedUsersFile)); + + if (emptyTenants) { + if (hasLegacyAuthorizedUsers) { + logger.info("Loading users from legacy model " + legacyAuthorizedUsersFile + " into new users file."); + convertLegacyAuthorizedUsers(tenants); + } + + populateInitialUsers(tenants); + + // save any changes that were made and repopulate the holder + saveAndRefreshHolder(tenants); + } else { + this.userGroupHolder.set(userGroupHolder); + } + } + + private void saveTenants(final Tenants tenants) throws JAXBException { + final Marshaller marshaller = JAXB_TENANTS_CONTEXT.createMarshaller(); + marshaller.setSchema(tenantsSchema); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + marshaller.marshal(tenants, tenantsFile); + } + + private Tenants unmarshallTenants() throws JAXBException { + final Unmarshaller unmarshaller = JAXB_TENANTS_CONTEXT.createUnmarshaller(); + unmarshaller.setSchema(tenantsSchema); + + final JAXBElement element = unmarshaller.unmarshal(new StreamSource(tenantsFile), Tenants.class); + return element.getValue(); + } + + private void populateInitialUsers(final Tenants tenants) { + for (String initialUserIdentity : initialUserIdentities) { + getOrCreateUser(tenants, initialUserIdentity); + } + } + + /** + * Unmarshalls an existing authorized-users.xml and converts the object model to the new model. + * + * @param tenants the current Tenants instance users and groups will be added to + * @throws AuthorizerCreationException if the legacy authorized users file that was provided does not exist + * @throws JAXBException if the legacy authorized users file that was provided could not be unmarshalled + */ + private void convertLegacyAuthorizedUsers(final Tenants tenants) throws AuthorizerCreationException, JAXBException { + final File authorizedUsersFile = new File(legacyAuthorizedUsersFile); + if (!authorizedUsersFile.exists()) { + throw new AuthorizerCreationException("Legacy Authorized Users File '" + legacyAuthorizedUsersFile + "' does not exists"); + } + + final Unmarshaller unmarshaller = JAXB_USERS_CONTEXT.createUnmarshaller(); + unmarshaller.setSchema(usersSchema); + + final JAXBElement element = unmarshaller.unmarshal( + new StreamSource(authorizedUsersFile), org.apache.nifi.user.generated.Users.class); + + final org.apache.nifi.user.generated.Users users = element.getValue(); + if (users.getUser().isEmpty()) { + logger.info("Legacy Authorized Users File contained no users, nothing to convert"); + return; + } + + for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) { + // create the identifier of the new user based on the DN + final String legacyUserDn = IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings); + org.apache.nifi.authorization.file.tenants.generated.User user = getOrCreateUser(tenants, legacyUserDn); + + // if there was a group name find or create the group and add the user to it + org.apache.nifi.authorization.file.tenants.generated.Group group = getOrCreateGroup(tenants, legacyUser.getGroup()); + if (group != null) { + org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User(); + groupUser.setIdentifier(user.getIdentifier()); + group.getUser().add(groupUser); + } + } + } + + /** + * Finds the User with the given identity, or creates a new one and adds it to the Tenants. + * + * @param tenants the Tenants reference + * @param userIdentity the user identity to find or create + * @return the User from Tenants with the given identity, or a new instance that was added to Tenants + */ + private org.apache.nifi.authorization.file.tenants.generated.User getOrCreateUser(final Tenants tenants, final String userIdentity) { + if (StringUtils.isBlank(userIdentity)) { + return null; + } + + org.apache.nifi.authorization.file.tenants.generated.User foundUser = null; + for (org.apache.nifi.authorization.file.tenants.generated.User user : tenants.getUsers().getUser()) { + if (user.getIdentity().equals(userIdentity)) { + foundUser = user; + break; + } + } + + if (foundUser == null) { + final String userIdentifier = IdentifierUtil.getIdentifier(userIdentity); + foundUser = new org.apache.nifi.authorization.file.tenants.generated.User(); + foundUser.setIdentifier(userIdentifier); + foundUser.setIdentity(userIdentity); + tenants.getUsers().getUser().add(foundUser); + } + + return foundUser; + } + + /** + * Finds the Group with the given name, or creates a new one and adds it to Tenants. + * + * @param tenants the Tenants reference + * @param groupName the name of the group to look for + * @return the Group from Tenants with the given name, or a new instance that was added to Tenants + */ + private org.apache.nifi.authorization.file.tenants.generated.Group getOrCreateGroup(final Tenants tenants, final String groupName) { + if (StringUtils.isBlank(groupName)) { + return null; + } + + org.apache.nifi.authorization.file.tenants.generated.Group foundGroup = null; + for (org.apache.nifi.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) { + if (group.getName().equals(groupName)) { + foundGroup = group; + break; + } + } + + if (foundGroup == null) { + final String newGroupIdentifier = IdentifierUtil.getIdentifier(groupName); + foundGroup = new org.apache.nifi.authorization.file.tenants.generated.Group(); + foundGroup.setIdentifier(newGroupIdentifier); + foundGroup.setName(groupName); + tenants.getGroups().getGroup().add(foundGroup); + } + + return foundGroup; + } + + /** + * Saves the Authorizations instance by marshalling to a file, then re-populates the + * in-memory data structures and sets the new holder. + * + * Synchronized to ensure only one thread writes the file at a time. + * + * @param tenants the tenants to save and populate from + * @throws AuthorizationAccessException if an error occurs saving the authorizations + */ + private synchronized void saveAndRefreshHolder(final Tenants tenants) throws AuthorizationAccessException { + try { + saveTenants(tenants); + + this.userGroupHolder.set(new UserGroupHolder(tenants)); + } catch (JAXBException e) { + throw new AuthorizationAccessException("Unable to save Authorizations", e); + } + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + } + + private static class UsersAndGroups { + final List users; + final List groups; + + public UsersAndGroups(List users, List groups) { + this.users = users; + this.groups = groups; + } + + public List getUsers() { + return users; + } + + public List getGroups() { + return groups; + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/IdentifierUtil.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/IdentifierUtil.java new file mode 100644 index 000000000000..dfd670080990 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/IdentifierUtil.java @@ -0,0 +1,35 @@ +/* + * 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; + +import org.apache.nifi.util.StringUtils; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +public final class IdentifierUtil { + + static String getIdentifier(final String seed) { + if (StringUtils.isBlank(seed)) { + return null; + } + + return UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString(); + } + + private IdentifierUtil() {} +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupHolder.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupHolder.java new file mode 100644 index 000000000000..44cedd8787d3 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupHolder.java @@ -0,0 +1,239 @@ +/* + * 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; + + +import org.apache.nifi.authorization.file.tenants.generated.Groups; +import org.apache.nifi.authorization.file.tenants.generated.Tenants; +import org.apache.nifi.authorization.file.tenants.generated.Users; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * A holder to provide atomic access to user group data structures. + */ +public class UserGroupHolder { + + private final Tenants tenants; + + private final Set allUsers; + private final Map usersById; + private final Map usersByIdentity; + + private final Set allGroups; + private final Map groupsById; + private final Map> groupsByUserIdentity; + + /** + * Creates a new holder and populates all convenience data structures. + * + * @param tenants the current tenants instance + */ + public UserGroupHolder(final Tenants tenants) { + this.tenants = tenants; + + // load all users + final Users users = tenants.getUsers(); + final Set allUsers = Collections.unmodifiableSet(createUsers(users)); + + // load all groups + final Groups groups = tenants.getGroups(); + final Set allGroups = Collections.unmodifiableSet(createGroups(groups, users)); + + // create a convenience map to retrieve a user by id + final Map userByIdMap = Collections.unmodifiableMap(createUserByIdMap(allUsers)); + + // create a convenience map to retrieve a user by identity + final Map userByIdentityMap = Collections.unmodifiableMap(createUserByIdentityMap(allUsers)); + + // create a convenience map to retrieve a group by id + final Map groupByIdMap = Collections.unmodifiableMap(createGroupByIdMap(allGroups)); + + // create a convenience map to retrieve the groups for a user identity + final Map> groupsByUserIdentityMap = Collections.unmodifiableMap(createGroupsByUserIdentityMap(allGroups, allUsers)); + + // set all the holders + this.allUsers = allUsers; + this.allGroups = allGroups; + this.usersById = userByIdMap; + this.usersByIdentity = userByIdentityMap; + this.groupsById = groupByIdMap; + this.groupsByUserIdentity = groupsByUserIdentityMap; + } + + /** + * Creates a set of Users from the JAXB Users. + * + * @param users the JAXB Users + * @return a set of API Users matching the provided JAXB Users + */ + private Set createUsers(Users users) { + Set allUsers = new HashSet<>(); + if (users == null || users.getUser() == null) { + return allUsers; + } + + for (org.apache.nifi.authorization.file.tenants.generated.User user : users.getUser()) { + final User.Builder builder = new User.Builder() + .identity(user.getIdentity()) + .identifier(user.getIdentifier()); + + allUsers.add(builder.build()); + } + + return allUsers; + } + + /** + * Creates a set of Groups from the JAXB Groups. + * + * @param groups the JAXB Groups + * @return a set of API Groups matching the provided JAXB Groups + */ + private Set createGroups(Groups groups, + Users users) { + Set allGroups = new HashSet<>(); + if (groups == null || groups.getGroup() == null) { + return allGroups; + } + + for (org.apache.nifi.authorization.file.tenants.generated.Group group : groups.getGroup()) { + final Group.Builder builder = new Group.Builder() + .identifier(group.getIdentifier()) + .name(group.getName()); + + for (org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser : group.getUser()) { + builder.addUser(groupUser.getIdentifier()); + } + + allGroups.add(builder.build()); + } + + return allGroups; + } + + /** + * Creates a Map from user identifier to User. + * + * @param users the set of all users + * @return the Map from user identifier to User + */ + private Map createUserByIdMap(final Set users) { + Map usersMap = new HashMap<>(); + for (User user : users) { + usersMap.put(user.getIdentifier(), user); + } + return usersMap; + } + + /** + * Creates a Map from user identity to User. + * + * @param users the set of all users + * @return the Map from user identity to User + */ + private Map createUserByIdentityMap(final Set users) { + Map usersMap = new HashMap<>(); + for (User user : users) { + usersMap.put(user.getIdentity(), user); + } + return usersMap; + } + + /** + * Creates a Map from group identifier to Group. + * + * @param groups the set of all groups + * @return the Map from group identifier to Group + */ + private Map createGroupByIdMap(final Set groups) { + Map groupsMap = new HashMap<>(); + for (Group group : groups) { + groupsMap.put(group.getIdentifier(), group); + } + return groupsMap; + } + + /** + * Creates a Map from user identity to the set of Groups for that identity. + * + * @param groups all groups + * @param users all users + * @return a Map from User identity to the set of Groups for that identity + */ + private Map> createGroupsByUserIdentityMap(final Set groups, final Set users) { + Map> groupsByUserIdentity = new HashMap<>(); + + for (User user : users) { + Set userGroups = new HashSet<>(); + for (Group group : groups) { + for (String groupUser : group.getUsers()) { + if (groupUser.equals(user.getIdentifier())) { + userGroups.add(group); + } + } + } + + groupsByUserIdentity.put(user.getIdentity(), userGroups); + } + + return groupsByUserIdentity; + } + + public Tenants getTenants() { + return tenants; + } + + public Set getAllUsers() { + return allUsers; + } + + public Map getUsersById() { + return usersById; + } + + public Map getUsersByIdentity() { + return usersByIdentity; + } + + public Set getAllGroups() { + return allGroups; + } + + public Map getGroupsById() { + return groupsById; + } + + public User getUser(String identity) { + if (identity == null) { + throw new IllegalArgumentException("Identity cannot be null"); + } + return usersByIdentity.get(identity); + } + + public Set getGroups(String userIdentity) { + if (userIdentity == null) { + throw new IllegalArgumentException("User Identity cannot be null"); + } + return groupsByUserIdentity.get(userIdentity); + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.AccessPolicyProvider b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.AccessPolicyProvider new file mode 100755 index 000000000000..a4e735b9ebf8 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.AccessPolicyProvider @@ -0,0 +1,15 @@ +# 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. +org.apache.nifi.authorization.FileAccessPolicyProvider diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider new file mode 100755 index 000000000000..2e1e407e86e6 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider @@ -0,0 +1,15 @@ +# 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. +org.apache.nifi.authorization.FileUserGroupProvider diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAccessPolicyProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAccessPolicyProviderTest.java new file mode 100644 index 000000000000..3c9e4c563e70 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAccessPolicyProviderTest.java @@ -0,0 +1,1050 @@ +/* + * 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; + +import org.apache.nifi.attribute.expression.language.StandardPropertyValue; +import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.authorization.resource.ResourceFactory; +import org.apache.nifi.authorization.resource.ResourceType; +import org.apache.nifi.components.PropertyValue; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.util.file.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class FileAccessPolicyProviderTest { + + private static final String EMPTY_AUTHORIZATIONS_CONCISE = + "" + + ""; + + private static final String EMPTY_TENANTS_CONCISE = + "" + + ""; + + private static final String EMPTY_AUTHORIZATIONS = + "" + + "" + + ""; + + private static final String EMPTY_TENANTS = + "" + + "" + + ""; + + private static final String BAD_SCHEMA_AUTHORIZATIONS = + "" + + "" + + ""; + + private static final String AUTHORIZATIONS = + "" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""; + + private static final String TENANTS = + "" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""; + + private static final String TENANTS_FOR_ADMIN_AND_NODES = + "" + + "" + + " " + + " " + + " " + + " " + + " " + + ""; + + // This is the root group id from the flow.xml.gz in src/test/resources + private static final String ROOT_GROUP_ID = "e530e14c-adcf-41c2-b5d6-d9a59ba8765c"; + + private NiFiProperties properties; + private FileAccessPolicyProvider accessPolicyProvider; + private FileUserGroupProvider userGroupProvider; + private File primaryAuthorizations; + private File primaryTenants; + private File restoreAuthorizations; + private File restoreTenants; + private File flow; + private File flowNoPorts; + private File flowWithDns; + + private AuthorizerConfigurationContext configurationContext; + + @Before + public void setup() throws IOException { + // primary authorizations + primaryAuthorizations = new File("target/authorizations/authorizations.xml"); + FileUtils.ensureDirectoryExistAndCanAccess(primaryAuthorizations.getParentFile()); + + // primary tenants + primaryTenants = new File("target/authorizations/users.xml"); + FileUtils.ensureDirectoryExistAndCanAccess(primaryTenants.getParentFile()); + + // restore authorizations + restoreAuthorizations = new File("target/restore/authorizations.xml"); + FileUtils.ensureDirectoryExistAndCanAccess(restoreAuthorizations.getParentFile()); + + // restore authorizations + restoreTenants = new File("target/restore/users.xml"); + FileUtils.ensureDirectoryExistAndCanAccess(restoreTenants.getParentFile()); + + flow = new File("src/test/resources/flow.xml.gz"); + FileUtils.ensureDirectoryExistAndCanAccess(flow.getParentFile()); + + flowNoPorts = new File("src/test/resources/flow-no-ports.xml.gz"); + FileUtils.ensureDirectoryExistAndCanAccess(flowNoPorts.getParentFile()); + + flowWithDns = new File("src/test/resources/flow-with-dns.xml.gz"); + FileUtils.ensureDirectoryExistAndCanAccess(flowWithDns.getParentFile()); + + properties = mock(NiFiProperties.class); + when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile()); + when(properties.getFlowConfigurationFile()).thenReturn(flow); + + userGroupProvider = new FileUserGroupProvider(); + userGroupProvider.setNiFiProperties(properties); + userGroupProvider.initialize(null); + + // this same configuration is being used for both the user group provider and the access policy provider + configurationContext = mock(AuthorizerConfigurationContext.class); + when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE))).thenReturn(new StandardPropertyValue(primaryAuthorizations.getPath(), null)); + when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_TENANTS_FILE))).thenReturn(new StandardPropertyValue(primaryTenants.getPath(), null)); + when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))).thenReturn(new StandardPropertyValue(null, null)); + when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))).thenReturn(new StandardPropertyValue(null, null)); + when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_USER_GROUP_PROVIDER))).thenReturn(new StandardPropertyValue("user-group-provider", null)); + when(configurationContext.getProperties()).then((invocation) -> { + final Map properties = new HashMap<>(); + + final PropertyValue authFile = configurationContext.getProperty(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE); + if (authFile != null) { + properties.put(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE, authFile.getValue()); + } + + final PropertyValue tenantFile = configurationContext.getProperty(FileUserGroupProvider.PROP_TENANTS_FILE); + if (tenantFile != null) { + properties.put(FileUserGroupProvider.PROP_TENANTS_FILE, tenantFile.getValue()); + } + + final PropertyValue legacyAuthFile = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE); + if (legacyAuthFile != null) { + properties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, legacyAuthFile.getValue()); + } + + final PropertyValue initialAdmin = configurationContext.getProperty(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY); + if (initialAdmin != null) { + properties.put(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY, initialAdmin.getValue()); + } + + int i = 1; + while (true) { + final String key = FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + i++; + final PropertyValue value = configurationContext.getProperty(key); + if (value == null) { + break; + } else { + properties.put(key, value.getValue()); + } + } + + i = 1; + while (true) { + final String key = FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + i++; + final PropertyValue value = configurationContext.getProperty(key); + if (value == null) { + break; + } else { + properties.put(key, value.getValue()); + } + } + + // ensure the initial admin is seeded into the user provider if appropriate + if (properties.containsKey(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)) { + i = 0; + while (true) { + final String key = FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + i++; + if (!properties.containsKey(key)) { + properties.put(key, properties.get(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)); + break; + } + } + } + + return properties; + }); + + final AccessPolicyProviderInitializationContext initializationContext = mock(AccessPolicyProviderInitializationContext.class); + when(initializationContext.getUserGroupProviderLookup()).thenReturn(new UserGroupProviderLookup() { + @Override + public UserGroupProvider getUserGroupProvider(String identifier) { + return userGroupProvider; + } + }); + + accessPolicyProvider = new FileAccessPolicyProvider(); + accessPolicyProvider.setNiFiProperties(properties); + accessPolicyProvider.initialize(initializationContext); + } + + @After + public void cleanup() throws Exception { + deleteFile(primaryAuthorizations); + deleteFile(primaryTenants); + deleteFile(restoreAuthorizations); + deleteFile(restoreTenants); + } + + @Test + public void testOnConfiguredWhenLegacyUsersFileProvidedWithOverlappingRoles() throws Exception { + when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) + .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users-multirole.xml", null)); + + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + assertNotNull(accessPolicyProvider.getAccessPolicy(ResourceType.Flow.getValue(), RequestAction.READ)); + assertNotNull(accessPolicyProvider.getAccessPolicy(ResourceType.Controller.getValue(), RequestAction.READ)); + assertNotNull(accessPolicyProvider.getAccessPolicy(ResourceType.Controller.getValue(), RequestAction.WRITE)); + assertNotNull(accessPolicyProvider.getAccessPolicy(ResourceType.System.getValue(), RequestAction.READ)); + assertNotNull(accessPolicyProvider.getAccessPolicy(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID, RequestAction.READ)); + assertNotNull(accessPolicyProvider.getAccessPolicy(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID, RequestAction.WRITE)); + } + + @Test + public void testOnConfiguredWhenLegacyUsersFileProvidedAndFlowHasNoPorts() throws Exception { + properties = mock(NiFiProperties.class); + when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile()); + when(properties.getFlowConfigurationFile()).thenReturn(flowNoPorts); + + when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) + .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users.xml", null)); + + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + boolean foundDataTransferPolicy = false; + for (AccessPolicy policy : accessPolicyProvider.getAccessPolicies()) { + if (policy.getResource().contains(ResourceType.DataTransfer.name())) { + foundDataTransferPolicy = true; + break; + } + } + + assertFalse(foundDataTransferPolicy); + } + + @Test + public void testOnConfiguredWhenLegacyUsersFileProvided() throws Exception { + when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) + .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users.xml", null)); + + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + final User user1 = userGroupProvider.getUserByIdentity("user1"); + final User user2 = userGroupProvider.getUserByIdentity("user2"); + final User user3 = userGroupProvider.getUserByIdentity("user3"); + final User user4 = userGroupProvider.getUserByIdentity("user4"); + final User user5 = userGroupProvider.getUserByIdentity("user5"); + final User user6 = userGroupProvider.getUserByIdentity("user6"); + + // verify one group got created + final Set groups = userGroupProvider.getGroups(); + final Group group1 = groups.iterator().next(); + + // verify more than one policy got created + final Set policies = accessPolicyProvider.getAccessPolicies(); + assertTrue(policies.size() > 0); + + // verify user1's policies + final Map> user1Policies = getResourceActions(policies, user1); + assertEquals(4, user1Policies.size()); + + assertTrue(user1Policies.containsKey(ResourceType.Flow.getValue())); + assertEquals(1, user1Policies.get(ResourceType.Flow.getValue()).size()); + assertTrue(user1Policies.get(ResourceType.Flow.getValue()).contains(RequestAction.READ)); + + assertTrue(user1Policies.containsKey(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID)); + assertEquals(1, user1Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).size()); + assertTrue(user1Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).contains(RequestAction.READ)); + + // verify user2's policies + final Map> user2Policies = getResourceActions(policies, user2); + assertEquals(2, user2Policies.size()); + + assertTrue(user2Policies.containsKey(ResourceType.Provenance.getValue())); + assertEquals(1, user2Policies.get(ResourceType.Provenance.getValue()).size()); + assertTrue(user2Policies.get(ResourceType.Provenance.getValue()).contains(RequestAction.READ)); + + // verify user3's policies + final Map> user3Policies = getResourceActions(policies, user3); + assertEquals(6, user3Policies.size()); + + assertTrue(user3Policies.containsKey(ResourceType.Flow.getValue())); + assertEquals(1, user3Policies.get(ResourceType.Flow.getValue()).size()); + assertTrue(user3Policies.get(ResourceType.Flow.getValue()).contains(RequestAction.READ)); + + assertTrue(user3Policies.containsKey(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID)); + assertEquals(2, user3Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).size()); + assertTrue(user3Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).contains(RequestAction.WRITE)); + + // verify user4's policies + final Map> user4Policies = getResourceActions(policies, user4); + assertEquals(6, user4Policies.size()); + + assertTrue(user4Policies.containsKey(ResourceType.Flow.getValue())); + assertEquals(1, user4Policies.get(ResourceType.Flow.getValue()).size()); + assertTrue(user4Policies.get(ResourceType.Flow.getValue()).contains(RequestAction.READ)); + + assertTrue(user4Policies.containsKey(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID)); + assertEquals(1, user4Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).size()); + assertTrue(user4Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).contains(RequestAction.READ)); + + assertTrue(user4Policies.containsKey(ResourceType.Tenant.getValue())); + assertEquals(2, user4Policies.get(ResourceType.Tenant.getValue()).size()); + assertTrue(user4Policies.get(ResourceType.Tenant.getValue()).contains(RequestAction.WRITE)); + + assertTrue(user4Policies.containsKey(ResourceType.Policy.getValue())); + assertEquals(2, user4Policies.get(ResourceType.Policy.getValue()).size()); + assertTrue(user4Policies.get(ResourceType.Policy.getValue()).contains(RequestAction.WRITE)); + + // verify user5's policies + final Map> user5Policies = getResourceActions(policies, user5); + assertEquals(2, user5Policies.size()); + + assertTrue(user5Policies.containsKey(ResourceType.Proxy.getValue())); + assertEquals(1, user5Policies.get(ResourceType.Proxy.getValue()).size()); + assertTrue(user5Policies.get(ResourceType.Proxy.getValue()).contains(RequestAction.WRITE)); + + // verify user6's policies + final Map> user6Policies = getResourceActions(policies, user6); + assertEquals(3, user6Policies.size()); + + assertTrue(user6Policies.containsKey(ResourceType.SiteToSite.getValue())); + assertEquals(1, user6Policies.get(ResourceType.SiteToSite.getValue()).size()); + assertTrue(user6Policies.get(ResourceType.SiteToSite.getValue()).contains(RequestAction.READ)); + + final Resource inputPortResource = ResourceFactory.getDataTransferResource( + ResourceFactory.getComponentResource(ResourceType.InputPort, "2f7d1606-b090-4be7-a592-a5b70fb55531", "TCP Input")); + final AccessPolicy inputPortPolicy = accessPolicyProvider.getAccessPolicy(inputPortResource.getIdentifier(), RequestAction.WRITE); + assertNotNull(inputPortPolicy); + assertEquals(1, inputPortPolicy.getUsers().size()); + assertTrue(inputPortPolicy.getUsers().contains(user6.getIdentifier())); + assertEquals(1, inputPortPolicy.getGroups().size()); + assertTrue(inputPortPolicy.getGroups().contains(group1.getIdentifier())); + + final Resource outputPortResource = ResourceFactory.getDataTransferResource( + ResourceFactory.getComponentResource(ResourceType.OutputPort, "2f7d1606-b090-4be7-a592-a5b70fb55532", "TCP Output")); + final AccessPolicy outputPortPolicy = accessPolicyProvider.getAccessPolicy(outputPortResource.getIdentifier(), RequestAction.WRITE); + assertNotNull(outputPortPolicy); + assertEquals(1, outputPortPolicy.getUsers().size()); + assertTrue(outputPortPolicy.getUsers().contains(user4.getIdentifier())); + } + + private Map> getResourceActions(final Set policies, final User user) { + Map> resourceActionMap = new HashMap<>(); + + for (AccessPolicy accessPolicy : policies) { + if (accessPolicy.getUsers().contains(user.getIdentifier())) { + Set actions = resourceActionMap.get(accessPolicy.getResource()); + if (actions == null) { + actions = new HashSet<>(); + resourceActionMap.put(accessPolicy.getResource(), actions); + } + actions.add(accessPolicy.getAction()); + } + } + + return resourceActionMap; + } + + @Test + public void testOnConfiguredWhenLegacyUsersFileProvidedWithIdentityMappings() throws Exception { + final Properties props = new Properties(); + props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$"); + props.setProperty("nifi.security.identity.mapping.value.dn1", "$1"); + + properties = getNiFiProperties(props); + when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile()); + when(properties.getFlowConfigurationFile()).thenReturn(flowWithDns); + + userGroupProvider.setNiFiProperties(properties); + accessPolicyProvider.setNiFiProperties(properties); + + when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) + .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users-with-dns.xml", null)); + + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + final User user4 = userGroupProvider.getUserByIdentity("user4"); + final User user6 = userGroupProvider.getUserByIdentity("user6"); + + // verify one group got created + final Set groups = userGroupProvider.getGroups(); + final Group group1 = groups.iterator().next(); + + final Resource inputPortResource = ResourceFactory.getDataTransferResource( + ResourceFactory.getComponentResource(ResourceType.InputPort, "2f7d1606-b090-4be7-a592-a5b70fb55531", "TCP Input")); + final AccessPolicy inputPortPolicy = accessPolicyProvider.getAccessPolicy(inputPortResource.getIdentifier(), RequestAction.WRITE); + assertNotNull(inputPortPolicy); + assertEquals(1, inputPortPolicy.getUsers().size()); + assertTrue(inputPortPolicy.getUsers().contains(user6.getIdentifier())); + assertEquals(1, inputPortPolicy.getGroups().size()); + assertTrue(inputPortPolicy.getGroups().contains(group1.getIdentifier())); + + final Resource outputPortResource = ResourceFactory.getDataTransferResource( + ResourceFactory.getComponentResource(ResourceType.OutputPort, "2f7d1606-b090-4be7-a592-a5b70fb55532", "TCP Output")); + final AccessPolicy outputPortPolicy = accessPolicyProvider.getAccessPolicy(outputPortResource.getIdentifier(), RequestAction.WRITE); + assertNotNull(outputPortPolicy); + assertEquals(1, outputPortPolicy.getUsers().size()); + assertTrue(outputPortPolicy.getUsers().contains(user4.getIdentifier())); + } + + @Test(expected = AuthorizerCreationException.class) + public void testOnConfiguredWhenBadLegacyUsersFileProvided() throws Exception { + when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) + .thenReturn(new StandardPropertyValue("src/test/resources/does-not-exist.xml", null)); + + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + accessPolicyProvider.onConfigured(configurationContext); + } + + @Test(expected = AuthorizerCreationException.class) + public void testOnConfiguredWhenInitialAdminAndLegacyUsersProvided() throws Exception { + final String adminIdentity = "admin-user"; + when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + + when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) + .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users.xml", null)); + + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + accessPolicyProvider.onConfigured(configurationContext); + } + + @Test + public void testOnConfiguredWhenInitialAdminNotProvided() throws Exception { + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + final Set policies = accessPolicyProvider.getAccessPolicies(); + assertEquals(0, policies.size()); + } + + @Test + public void testOnConfiguredWhenInitialAdminProvided() throws Exception { + final String adminIdentity = "admin-user"; + + when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + final Set users = userGroupProvider.getUsers(); + final User adminUser = users.iterator().next(); + assertEquals(adminIdentity, adminUser.getIdentity()); + + final Set policies = accessPolicyProvider.getAccessPolicies(); + assertEquals(12, policies.size()); + + final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID; + + boolean foundRootGroupPolicy = false; + for (AccessPolicy policy : policies) { + if (policy.getResource().equals(rootGroupResource)) { + foundRootGroupPolicy = true; + break; + } + } + + assertTrue(foundRootGroupPolicy); + } + + @Test + public void testOnConfiguredWhenInitialAdminProvidedAndNoFlowExists() throws Exception { + // setup NiFi properties to return a file that does not exist + properties = mock(NiFiProperties.class); + when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile()); + when(properties.getFlowConfigurationFile()).thenReturn(new File("src/test/resources/does-not-exist.xml.gz")); + + userGroupProvider.setNiFiProperties(properties); + accessPolicyProvider.setNiFiProperties(properties); + + final String adminIdentity = "admin-user"; + when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + final Set users = userGroupProvider.getUsers(); + final User adminUser = users.iterator().next(); + assertEquals(adminIdentity, adminUser.getIdentity()); + + final Set policies = accessPolicyProvider.getAccessPolicies(); + assertEquals(8, policies.size()); + + final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID; + + boolean foundRootGroupPolicy = false; + for (AccessPolicy policy : policies) { + if (policy.getResource().equals(rootGroupResource)) { + foundRootGroupPolicy = true; + break; + } + } + + assertFalse(foundRootGroupPolicy); + } + + @Test + public void testOnConfiguredWhenInitialAdminProvidedAndFlowIsNull() throws Exception { + // setup NiFi properties to return a file that does not exist + properties = mock(NiFiProperties.class); + when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile()); + when(properties.getFlowConfigurationFile()).thenReturn(null); + + userGroupProvider.setNiFiProperties(properties); + accessPolicyProvider.setNiFiProperties(properties); + + final String adminIdentity = "admin-user"; + when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + final Set users = userGroupProvider.getUsers(); + final User adminUser = users.iterator().next(); + assertEquals(adminIdentity, adminUser.getIdentity()); + + final Set policies = accessPolicyProvider.getAccessPolicies(); + assertEquals(8, policies.size()); + + final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID; + + boolean foundRootGroupPolicy = false; + for (AccessPolicy policy : policies) { + if (policy.getResource().equals(rootGroupResource)) { + foundRootGroupPolicy = true; + break; + } + } + + assertFalse(foundRootGroupPolicy); + } + + @Test + public void testOnConfiguredWhenInitialAdminProvidedWithIdentityMapping() throws Exception { + final Properties props = new Properties(); + props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$"); + props.setProperty("nifi.security.identity.mapping.value.dn1", "$1_$2_$3"); + + properties = getNiFiProperties(props); + when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile()); + when(properties.getFlowConfigurationFile()).thenReturn(flow); + + userGroupProvider.setNiFiProperties(properties); + accessPolicyProvider.setNiFiProperties(properties); + + final String adminIdentity = "CN=localhost, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US"; + when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + final Set users = userGroupProvider.getUsers(); + final User adminUser = users.iterator().next(); + assertEquals("localhost_Apache NiFi_Apache", adminUser.getIdentity()); + } + + @Test + public void testOnConfiguredWhenNodeIdentitiesProvided() throws Exception { + final String adminIdentity = "admin-user"; + final String nodeIdentity1 = "node1"; + final String nodeIdentity2 = "node2"; + + when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "1"))) + .thenReturn(new StandardPropertyValue(nodeIdentity1, null)); + when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "2"))) + .thenReturn(new StandardPropertyValue(nodeIdentity2, null)); + + when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "1"))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "2"))) + .thenReturn(new StandardPropertyValue(nodeIdentity1, null)); + when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "3"))) + .thenReturn(new StandardPropertyValue(nodeIdentity2, null)); + + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + User nodeUser1 = userGroupProvider.getUserByIdentity(nodeIdentity1); + User nodeUser2 = userGroupProvider.getUserByIdentity(nodeIdentity2); + + AccessPolicy proxyWritePolicy = accessPolicyProvider.getAccessPolicy(ResourceType.Proxy.getValue(), RequestAction.WRITE); + + assertNotNull(proxyWritePolicy); + assertTrue(proxyWritePolicy.getUsers().contains(nodeUser1.getIdentifier())); + assertTrue(proxyWritePolicy.getUsers().contains(nodeUser2.getIdentifier())); + } + + @Test + public void testOnConfiguredWhenNodeIdentitiesProvidedAndUsersAlreadyExist() throws Exception { + final String adminIdentity = "admin-user"; + final String nodeIdentity1 = "node1"; + final String nodeIdentity2 = "node2"; + + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "1"))) + .thenReturn(new StandardPropertyValue(nodeIdentity1, null)); + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "2"))) + .thenReturn(new StandardPropertyValue(nodeIdentity2, null)); + + when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "1"))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "2"))) + .thenReturn(new StandardPropertyValue(nodeIdentity1, null)); + when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "3"))) + .thenReturn(new StandardPropertyValue(nodeIdentity2, null)); + + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + writeFile(primaryTenants, TENANTS_FOR_ADMIN_AND_NODES); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + User nodeUser1 = userGroupProvider.getUserByIdentity(nodeIdentity1); + User nodeUser2 = userGroupProvider.getUserByIdentity(nodeIdentity2); + + AccessPolicy proxyWritePolicy = accessPolicyProvider.getAccessPolicy(ResourceType.Proxy.getValue(), RequestAction.WRITE); + + assertNotNull(proxyWritePolicy); + assertTrue(proxyWritePolicy.getUsers().contains(nodeUser1.getIdentifier())); + assertTrue(proxyWritePolicy.getUsers().contains(nodeUser2.getIdentifier())); + } + + + @Test + public void testOnConfiguredWhenTenantsAndAuthorizationsFileDoesNotExist() { + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + assertEquals(0, accessPolicyProvider.getAccessPolicies().size()); + } + + @Test + public void testOnConfiguredWhenAuthorizationsFileDoesNotExist() throws Exception { + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + assertEquals(0, accessPolicyProvider.getAccessPolicies().size()); + } + + @Test + public void testOnConfiguredWhenTenantsFileDoesNotExist() throws Exception { + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + assertEquals(0, accessPolicyProvider.getAccessPolicies().size()); + } + + @Test + public void testOnConfiguredWhenRestoreDoesNotExist() throws Exception { + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + assertEquals(primaryAuthorizations.length(), restoreAuthorizations.length()); + assertEquals(primaryTenants.length(), restoreTenants.length()); + } + + @Test(expected = AuthorizerCreationException.class) + public void testOnConfiguredWhenPrimaryDoesNotExist() throws Exception { + writeFile(restoreAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + } + + @Test(expected = AuthorizerCreationException.class) + public void testOnConfiguredWhenPrimaryAuthorizationsDifferentThanRestore() throws Exception { + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS); + writeFile(restoreAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + } + + @Test(expected = AuthorizerCreationException.class) + public void testOnConfiguredWithBadAuthorizationsSchema() throws Exception { + writeFile(primaryAuthorizations, BAD_SCHEMA_AUTHORIZATIONS); + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + } + + @Test + public void testGetAllUsersGroupsPolicies() throws Exception { + writeFile(primaryAuthorizations, AUTHORIZATIONS); + writeFile(primaryTenants, TENANTS); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + final Set policies = accessPolicyProvider.getAccessPolicies(); + assertEquals(2, policies.size()); + + boolean foundPolicy1 = false; + boolean foundPolicy2 = false; + + for (AccessPolicy policy : policies) { + if (policy.getIdentifier().equals("policy-1") + && policy.getResource().equals("/flow") + && policy.getAction() == RequestAction.READ + && policy.getGroups().size() == 2 + && policy.getGroups().contains("group-1") + && policy.getGroups().contains("group-2") + && policy.getUsers().size() == 1 + && policy.getUsers().contains("user-1")) { + foundPolicy1 = true; + } else if (policy.getIdentifier().equals("policy-2") + && policy.getResource().equals("/flow") + && policy.getAction() == RequestAction.WRITE + && policy.getGroups().size() == 0 + && policy.getUsers().size() == 1 + && policy.getUsers().contains("user-2")) { + foundPolicy2 = true; + } + } + + assertTrue(foundPolicy1); + assertTrue(foundPolicy2); + } + + // --------------- AccessPolicy Tests ------------------------ + + @Test + public void testAddAccessPolicy() throws Exception { + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS); + writeFile(primaryTenants, EMPTY_TENANTS); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + assertEquals(0, accessPolicyProvider.getAccessPolicies().size()); + + final AccessPolicy policy1 = new AccessPolicy.Builder() + .identifier("policy-1") + .resource("resource-1") + .addUser("user-1") + .addGroup("group-1") + .action(RequestAction.READ) + .build(); + + final AccessPolicy returnedPolicy1 = accessPolicyProvider.addAccessPolicy(policy1); + assertNotNull(returnedPolicy1); + assertEquals(policy1.getIdentifier(), returnedPolicy1.getIdentifier()); + assertEquals(policy1.getResource(), returnedPolicy1.getResource()); + assertEquals(policy1.getUsers(), returnedPolicy1.getUsers()); + assertEquals(policy1.getGroups(), returnedPolicy1.getGroups()); + assertEquals(policy1.getAction(), returnedPolicy1.getAction()); + + assertEquals(1, accessPolicyProvider.getAccessPolicies().size()); + } + + @Test + public void testAddAccessPolicyWithEmptyUsersAndGroups() throws Exception { + writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS); + writeFile(primaryTenants, EMPTY_TENANTS); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + assertEquals(0, accessPolicyProvider.getAccessPolicies().size()); + + final AccessPolicy policy1 = new AccessPolicy.Builder() + .identifier("policy-1") + .resource("resource-1") + .action(RequestAction.READ) + .build(); + + final AccessPolicy returnedPolicy1 = accessPolicyProvider.addAccessPolicy(policy1); + assertNotNull(returnedPolicy1); + assertEquals(policy1.getIdentifier(), returnedPolicy1.getIdentifier()); + assertEquals(policy1.getResource(), returnedPolicy1.getResource()); + assertEquals(policy1.getUsers(), returnedPolicy1.getUsers()); + assertEquals(policy1.getGroups(), returnedPolicy1.getGroups()); + assertEquals(policy1.getAction(), returnedPolicy1.getAction()); + + assertEquals(1, accessPolicyProvider.getAccessPolicies().size()); + } + + @Test + public void testGetAccessPolicy() throws Exception { + writeFile(primaryAuthorizations, AUTHORIZATIONS); + writeFile(primaryTenants, TENANTS); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + assertEquals(2, accessPolicyProvider.getAccessPolicies().size()); + + final AccessPolicy policy = accessPolicyProvider.getAccessPolicy("policy-1"); + assertNotNull(policy); + assertEquals("policy-1", policy.getIdentifier()); + assertEquals("/flow", policy.getResource()); + + assertEquals(RequestAction.READ, policy.getAction()); + + assertEquals(1, policy.getUsers().size()); + assertTrue(policy.getUsers().contains("user-1")); + + assertEquals(2, policy.getGroups().size()); + assertTrue(policy.getGroups().contains("group-1")); + assertTrue(policy.getGroups().contains("group-2")); + } + + @Test + public void testGetAccessPolicyWhenNotFound() throws Exception { + writeFile(primaryAuthorizations, AUTHORIZATIONS); + writeFile(primaryTenants, TENANTS); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + assertEquals(2, accessPolicyProvider.getAccessPolicies().size()); + + final AccessPolicy policy = accessPolicyProvider.getAccessPolicy("policy-X"); + assertNull(policy); + } + + @Test + public void testUpdateAccessPolicy() throws Exception { + writeFile(primaryAuthorizations, AUTHORIZATIONS); + writeFile(primaryTenants, TENANTS); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + assertEquals(2, accessPolicyProvider.getAccessPolicies().size()); + + final AccessPolicy policy = new AccessPolicy.Builder() + .identifier("policy-1") + .resource("resource-A") + .addUser("user-A") + .addGroup("group-A") + .action(RequestAction.READ) + .build(); + + final AccessPolicy updateAccessPolicy = accessPolicyProvider.updateAccessPolicy(policy); + assertNotNull(updateAccessPolicy); + assertEquals("policy-1", updateAccessPolicy.getIdentifier()); + assertEquals("/flow", updateAccessPolicy.getResource()); + + assertEquals(1, updateAccessPolicy.getUsers().size()); + assertTrue(updateAccessPolicy.getUsers().contains("user-A")); + + assertEquals(1, updateAccessPolicy.getGroups().size()); + assertTrue(updateAccessPolicy.getGroups().contains("group-A")); + + assertEquals(RequestAction.READ, updateAccessPolicy.getAction()); + } + + @Test + public void testUpdateAccessPolicyWhenResourceNotFound() throws Exception { + writeFile(primaryAuthorizations, AUTHORIZATIONS); + writeFile(primaryTenants, TENANTS); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + assertEquals(2, accessPolicyProvider.getAccessPolicies().size()); + + final AccessPolicy policy = new AccessPolicy.Builder() + .identifier("policy-XXX") + .resource("resource-A") + .addUser("user-A") + .addGroup("group-A") + .action(RequestAction.READ) + .build(); + + final AccessPolicy updateAccessPolicy = accessPolicyProvider.updateAccessPolicy(policy); + assertNull(updateAccessPolicy); + } + + @Test + public void testDeleteAccessPolicy() throws Exception { + writeFile(primaryAuthorizations, AUTHORIZATIONS); + writeFile(primaryTenants, TENANTS); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + assertEquals(2, accessPolicyProvider.getAccessPolicies().size()); + + final AccessPolicy policy = new AccessPolicy.Builder() + .identifier("policy-1") + .resource("resource-A") + .addUser("user-A") + .addGroup("group-A") + .action(RequestAction.READ) + .build(); + + final AccessPolicy deletedAccessPolicy = accessPolicyProvider.deleteAccessPolicy(policy); + assertNotNull(deletedAccessPolicy); + assertEquals(policy.getIdentifier(), deletedAccessPolicy.getIdentifier()); + + // should have one less policy, and get by policy id should return null + assertEquals(1, accessPolicyProvider.getAccessPolicies().size()); + assertNull(accessPolicyProvider.getAccessPolicy(policy.getIdentifier())); + } + + @Test + public void testDeleteAccessPolicyWhenNotFound() throws Exception { + writeFile(primaryAuthorizations, AUTHORIZATIONS); + writeFile(primaryTenants, TENANTS); + + userGroupProvider.onConfigured(configurationContext); + accessPolicyProvider.onConfigured(configurationContext); + + assertEquals(2, accessPolicyProvider.getAccessPolicies().size()); + + final AccessPolicy policy = new AccessPolicy.Builder() + .identifier("policy-XXX") + .resource("resource-A") + .addUser("user-A") + .addGroup("group-A") + .action(RequestAction.READ) + .build(); + + final AccessPolicy deletedAccessPolicy = accessPolicyProvider.deleteAccessPolicy(policy); + assertNull(deletedAccessPolicy); + } + + private static void writeFile(final File file, final String content) throws Exception { + byte[] bytes = content.getBytes(StandardCharsets.UTF_8); + try (final FileOutputStream fos = new FileOutputStream(file)) { + fos.write(bytes); + } + } + + private static boolean deleteFile(final File file) { + if (file.isDirectory()) { + FileUtils.deleteFilesInDir(file, null, null, true, true); + } + return FileUtils.deleteFile(file, null, 10); + } + + private NiFiProperties getNiFiProperties(final Properties properties) { + final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class); + when(nifiProperties.getPropertyKeys()).thenReturn(properties.stringPropertyNames()); + + when(nifiProperties.getProperty(anyString())).then(new Answer() { + @Override + public String answer(InvocationOnMock invocationOnMock) throws Throwable { + return properties.getProperty((String)invocationOnMock.getArguments()[0]); + } + }); + return nifiProperties; + } + +} 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 de76d936e6d5..79e70a0745a2 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 @@ -21,10 +21,10 @@ import org.apache.nifi.authorization.exception.AuthorizerCreationException; import org.apache.nifi.authorization.resource.ResourceFactory; import org.apache.nifi.authorization.resource.ResourceType; +import org.apache.nifi.components.PropertyValue; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.util.file.FileUtils; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; @@ -46,6 +46,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -188,8 +189,44 @@ public void setup() throws IOException { when(properties.getFlowConfigurationFile()).thenReturn(flow); configurationContext = mock(AuthorizerConfigurationContext.class); - when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_AUTHORIZATIONS_FILE))).thenReturn(new StandardPropertyValue(primaryAuthorizations.getPath(), null)); - when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_TENANTS_FILE))).thenReturn(new StandardPropertyValue(primaryTenants.getPath(), null)); + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE))).thenReturn(new StandardPropertyValue(primaryAuthorizations.getPath(), null)); + when(configurationContext.getProperty(Mockito.eq(FileUserGroupProvider.PROP_TENANTS_FILE))).thenReturn(new StandardPropertyValue(primaryTenants.getPath(), null)); + when(configurationContext.getProperties()).then((invocation) -> { + final Map properties = new HashMap<>(); + + final PropertyValue authFile = configurationContext.getProperty(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE); + if (authFile != null) { + properties.put(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE, authFile.getValue()); + } + + final PropertyValue tenantFile = configurationContext.getProperty(FileUserGroupProvider.PROP_TENANTS_FILE); + if (tenantFile != null) { + properties.put(FileUserGroupProvider.PROP_TENANTS_FILE, tenantFile.getValue()); + } + + final PropertyValue legacyAuthFile = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE); + if (legacyAuthFile != null) { + properties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, legacyAuthFile.getValue()); + } + + final PropertyValue initialAdmin = configurationContext.getProperty(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY); + if (initialAdmin != null) { + properties.put(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY, initialAdmin.getValue()); + } + + int i = 1; + while (true) { + final String key = FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + i++; + final PropertyValue value = configurationContext.getProperty(key); + if (value == null) { + break; + } else { + properties.put(key, value.getValue()); + } + } + + return properties; + }); authorizer = new FileAuthorizer(); authorizer.setNiFiProperties(properties); @@ -464,7 +501,7 @@ public void testOnConfiguredWhenBadLegacyUsersFileProvided() throws Exception { @Test(expected = AuthorizerCreationException.class) public void testOnConfiguredWhenInitialAdminAndLegacyUsersProvided() throws Exception { final String adminIdentity = "admin-user"; - when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY))) + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))) .thenReturn(new StandardPropertyValue(adminIdentity, null)); when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) @@ -492,7 +529,7 @@ public void testOnConfiguredWhenInitialAdminNotProvided() throws Exception { public void testOnConfiguredWhenInitialAdminProvided() throws Exception { final String adminIdentity = "admin-user"; - when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY))) + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))) .thenReturn(new StandardPropertyValue(adminIdentity, null)); writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); @@ -530,7 +567,7 @@ public void testOnConfiguredWhenInitialAdminProvidedAndNoFlowExists() throws Exc authorizer.setNiFiProperties(properties); final String adminIdentity = "admin-user"; - when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY))) + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))) .thenReturn(new StandardPropertyValue(adminIdentity, null)); writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); @@ -568,7 +605,7 @@ public void testOnConfiguredWhenInitialAdminProvidedAndFlowIsNull() throws Excep authorizer.setNiFiProperties(properties); final String adminIdentity = "admin-user"; - when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY))) + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))) .thenReturn(new StandardPropertyValue(adminIdentity, null)); writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); @@ -609,7 +646,7 @@ public void testOnConfiguredWhenInitialAdminProvidedWithIdentityMapping() throws authorizer.setNiFiProperties(properties); final String adminIdentity = "CN=localhost, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US"; - when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY))) + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))) .thenReturn(new StandardPropertyValue(adminIdentity, null)); writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); @@ -626,18 +663,15 @@ public void testOnConfiguredWhenInitialAdminProvidedWithIdentityMapping() throws @Test public void testOnConfiguredWhenNodeIdentitiesProvided() throws Exception { final String adminIdentity = "admin-user"; - - when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY))) - .thenReturn(new StandardPropertyValue(adminIdentity, null)); - final String nodeIdentity1 = "node1"; final String nodeIdentity2 = "node2"; - final Map props = new HashMap<>(); - props.put("Node Identity 1", nodeIdentity1); - props.put("Node Identity 2", nodeIdentity2); - - when(configurationContext.getProperties()).thenReturn(props); + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "1"))) + .thenReturn(new StandardPropertyValue(nodeIdentity1, null)); + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "2"))) + .thenReturn(new StandardPropertyValue(nodeIdentity2, null)); writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); @@ -662,18 +696,15 @@ public void testOnConfiguredWhenNodeIdentitiesProvided() throws Exception { @Test public void testOnConfiguredWhenNodeIdentitiesProvidedAndUsersAlreadyExist() throws Exception { final String adminIdentity = "admin-user"; - - when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY))) - .thenReturn(new StandardPropertyValue(adminIdentity, null)); - final String nodeIdentity1 = "node1"; final String nodeIdentity2 = "node2"; - final Map props = new HashMap<>(); - props.put("Node Identity 1", nodeIdentity1); - props.put("Node Identity 2", nodeIdentity2); - - when(configurationContext.getProperties()).thenReturn(props); + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "1"))) + .thenReturn(new StandardPropertyValue(nodeIdentity1, null)); + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "2"))) + .thenReturn(new StandardPropertyValue(nodeIdentity2, null)); writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); writeFile(primaryTenants, TENANTS_FOR_ADMIN_AND_NODES); @@ -709,17 +740,15 @@ public void testOnConfiguredWhenNodeIdentitiesProvidedWithIdentityMappings() thr authorizer.setNiFiProperties(properties); final String adminIdentity = "CN=user1, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US"; - when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY))) - .thenReturn(new StandardPropertyValue(adminIdentity, null)); - final String nodeIdentity1 = "CN=node1, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US"; final String nodeIdentity2 = "CN=node2, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US"; - final Map nodeProps = new HashMap<>(); - nodeProps.put("Node Identity 1", nodeIdentity1); - nodeProps.put("Node Identity 2", nodeIdentity2); - - when(configurationContext.getProperties()).thenReturn(nodeProps); + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "1"))) + .thenReturn(new StandardPropertyValue(nodeIdentity1, null)); + when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "2"))) + .thenReturn(new StandardPropertyValue(nodeIdentity2, null)); writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); @@ -735,9 +764,12 @@ public void testOnConfiguredWhenNodeIdentitiesProvidedWithIdentityMappings() thr assertNotNull(nodeUser2); } + @Test public void testOnConfiguredWhenTenantsAndAuthorizationsFileDoesNotExist() { authorizer.onConfigured(configurationContext); assertEquals(0, authorizer.getAccessPolicies().size()); + assertEquals(0, authorizer.getUsers().size()); + assertEquals(0, authorizer.getGroups().size()); } @Test @@ -745,6 +777,8 @@ public void testOnConfiguredWhenAuthorizationsFileDoesNotExist() throws Exceptio writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); authorizer.onConfigured(configurationContext); assertEquals(0, authorizer.getAccessPolicies().size()); + assertEquals(0, authorizer.getUsers().size()); + assertEquals(0, authorizer.getGroups().size()); } @Test @@ -752,6 +786,8 @@ public void testOnConfiguredWhenTenantsFileDoesNotExist() throws Exception { writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE); authorizer.onConfigured(configurationContext); assertEquals(0, authorizer.getAccessPolicies().size()); + assertEquals(0, authorizer.getUsers().size()); + assertEquals(0, authorizer.getGroups().size()); } @Test @@ -1007,9 +1043,6 @@ public void testDeleteUser() throws Exception { final User user = authorizer.getUser("user-1"); assertEquals("user-1", user.getIdentifier()); - final AccessPolicy policy1 = authorizer.getAccessPolicy("policy-1"); - assertTrue(policy1.getUsers().contains("user-1")); - // delete user-1 final User deletedUser = authorizer.deleteUser(user); assertNotNull(deletedUser); @@ -1018,10 +1051,6 @@ public void testDeleteUser() throws Exception { // should be one less user assertEquals(1, authorizer.getUsers().size()); assertNull(authorizer.getUser(user.getIdentifier())); - - // verify policy-1 no longer has a reference to user-1 - final AccessPolicy updatedPolicy1 = authorizer.getAccessPolicy("policy-1"); - assertFalse(updatedPolicy1.getUsers().contains("user-1")); } @Test @@ -1121,7 +1150,6 @@ public void testAddGroupWithUser() throws Exception { assertEquals(3, groups.size()); } - @Test(expected = IllegalStateException.class) public void testAddGroupWhenUserDoesNotExist() throws Exception { writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS); @@ -1170,9 +1198,6 @@ public void testDeleteGroupWhenFound() throws Exception { authorizer.onConfigured(configurationContext); assertEquals(2, authorizer.getGroups().size()); - final AccessPolicy policy1 = authorizer.getAccessPolicy("policy-1"); - assertTrue(policy1.getGroups().contains("group-1")); - // retrieve group-1 final Group group = authorizer.getGroup("group-1"); assertEquals("group-1", group.getIdentifier()); @@ -1187,10 +1212,6 @@ public void testDeleteGroupWhenFound() throws Exception { // verify we can no longer retrieve group-1 by identifier assertNull(authorizer.getGroup(group.getIdentifier())); - - // verify group-1 is no longer in policy-1 - final AccessPolicy updatedPolicy1 = authorizer.getAccessPolicy("policy-1"); - assertFalse(updatedPolicy1.getGroups().contains("group-1")); } @Test @@ -1289,9 +1310,11 @@ public void testAddAccessPolicy() throws Exception { .action(RequestAction.READ) .build(); + final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(authorizer); + final ConfigurableAccessPolicyProvider accessPolicyProviderWithChecks = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider(); try { - final AccessPolicy returnedPolicy2 = authorizer.addAccessPolicy(policy2); - Assert.fail("Should have thrown exception"); + final AccessPolicy returnedPolicy2 = accessPolicyProviderWithChecks.addAccessPolicy(policy2); + fail("Should have thrown exception"); } catch (Exception e) { } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java new file mode 100644 index 000000000000..d10d9760acb5 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java @@ -0,0 +1,698 @@ +/* + * 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; + +import org.apache.nifi.attribute.expression.language.StandardPropertyValue; +import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.components.PropertyValue; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.util.file.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class FileUserGroupProviderTest { + + private static final String EMPTY_TENANTS_CONCISE = + "" + + ""; + + private static final String EMPTY_TENANTS = + "" + + "" + + ""; + + private static final String BAD_SCHEMA_TENANTS = + "" + + "" + + ""; + + private static final String SIMPLE_TENANTS_BY_USER = + "" + + "" + + " " + + " " + + " " + + " " + + ""; + + private static final String TENANTS = + "" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""; + + private NiFiProperties properties; + private FileUserGroupProvider userGroupProvider; + private File primaryTenants; + private File restoreTenants; + + private AuthorizerConfigurationContext configurationContext; + + @Before + public void setup() throws IOException { + // primary tenants + primaryTenants = new File("target/authorizations/users.xml"); + FileUtils.ensureDirectoryExistAndCanAccess(primaryTenants.getParentFile()); + + // restore authorizations + restoreTenants = new File("target/restore/users.xml"); + FileUtils.ensureDirectoryExistAndCanAccess(restoreTenants.getParentFile()); + + properties = mock(NiFiProperties.class); + when(properties.getRestoreDirectory()).thenReturn(restoreTenants.getParentFile()); + + configurationContext = mock(AuthorizerConfigurationContext.class); + when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))).thenReturn(new StandardPropertyValue(null, null)); + when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_TENANTS_FILE))).thenReturn(new StandardPropertyValue(primaryTenants.getPath(), null)); + when(configurationContext.getProperties()).then((invocation) -> { + final Map properties = new HashMap<>(); + + final PropertyValue tenantFile = configurationContext.getProperty(FileUserGroupProvider.PROP_TENANTS_FILE); + if (tenantFile != null) { + properties.put(FileUserGroupProvider.PROP_TENANTS_FILE, tenantFile.getValue()); + } + + final PropertyValue legacyAuthFile = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE); + if (legacyAuthFile != null) { + properties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, legacyAuthFile.getValue()); + } + + int i = 1; + while (true) { + final String key = FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + i++; + final PropertyValue value = configurationContext.getProperty(key); + if (value == null) { + break; + } else { + properties.put(key, value.getValue()); + } + } + + return properties; + }); + + userGroupProvider = new FileUserGroupProvider(); + userGroupProvider.setNiFiProperties(properties); + userGroupProvider.initialize(null); + } + + @After + public void cleanup() throws Exception { + deleteFile(primaryTenants); + deleteFile(restoreTenants); + } + + @Test + public void testOnConfiguredWhenLegacyUsersFileProvided() throws Exception { + when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) + .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users.xml", null)); + + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + userGroupProvider.onConfigured(configurationContext); + + // verify all users got created correctly + final Set users = userGroupProvider.getUsers(); + assertEquals(6, users.size()); + + final User user1 = userGroupProvider.getUserByIdentity("user1"); + assertNotNull(user1); + + final User user2 = userGroupProvider.getUserByIdentity("user2"); + assertNotNull(user2); + + final User user3 = userGroupProvider.getUserByIdentity("user3"); + assertNotNull(user3); + + final User user4 = userGroupProvider.getUserByIdentity("user4"); + assertNotNull(user4); + + final User user5 = userGroupProvider.getUserByIdentity("user5"); + assertNotNull(user5); + + final User user6 = userGroupProvider.getUserByIdentity("user6"); + assertNotNull(user6); + + // verify one group got created + final Set groups = userGroupProvider.getGroups(); + assertEquals(1, groups.size()); + final Group group1 = groups.iterator().next(); + assertEquals("group1", group1.getName()); + } + + @Test + public void testOnConfiguredWhenLegacyUsersFileProvidedWithIdentityMappings() throws Exception { + final Properties props = new Properties(); + props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$"); + props.setProperty("nifi.security.identity.mapping.value.dn1", "$1"); + + properties = getNiFiProperties(props); + when(properties.getRestoreDirectory()).thenReturn(restoreTenants.getParentFile()); + userGroupProvider.setNiFiProperties(properties); + + when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) + .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users-with-dns.xml", null)); + + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + userGroupProvider.onConfigured(configurationContext); + + final User user1 = userGroupProvider.getUserByIdentity("user1"); + assertNotNull(user1); + + final User user2 = userGroupProvider.getUserByIdentity("user2"); + assertNotNull(user2); + + final User user3 = userGroupProvider.getUserByIdentity("user3"); + assertNotNull(user3); + + final User user4 = userGroupProvider.getUserByIdentity("user4"); + assertNotNull(user4); + + final User user5 = userGroupProvider.getUserByIdentity("user5"); + assertNotNull(user5); + + final User user6 = userGroupProvider.getUserByIdentity("user6"); + assertNotNull(user6); + + // verify one group got created + final Set groups = userGroupProvider.getGroups(); + assertEquals(1, groups.size()); + final Group group1 = groups.iterator().next(); + assertEquals("group1", group1.getName()); + } + + @Test(expected = AuthorizerCreationException.class) + public void testOnConfiguredWhenBadLegacyUsersFileProvided() throws Exception { + when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) + .thenReturn(new StandardPropertyValue("src/test/resources/does-not-exist.xml", null)); + + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + userGroupProvider.onConfigured(configurationContext); + } + + @Test + public void testOnConfiguredWhenInitialUsersNotProvided() throws Exception { + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + userGroupProvider.onConfigured(configurationContext); + + final Set users = userGroupProvider.getUsers(); + assertEquals(0, users.size()); + } + + @Test + public void testOnConfiguredWhenInitialUsersProvided() throws Exception { + final String adminIdentity = "admin-user"; + final String nodeIdentity1 = "node-identity-1"; + final String nodeIdentity2 = "node-identity-2"; + + when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "1"))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "2"))) + .thenReturn(new StandardPropertyValue(nodeIdentity1, null)); + when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "3"))) + .thenReturn(new StandardPropertyValue(nodeIdentity2, null)); + + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + userGroupProvider.onConfigured(configurationContext); + + final Set users = userGroupProvider.getUsers(); + assertEquals(3, users.size()); + + assertTrue(users.contains(new User.Builder().identifier(IdentifierUtil.getIdentifier(adminIdentity)).identity(adminIdentity).build())); + assertTrue(users.contains(new User.Builder().identifier(IdentifierUtil.getIdentifier(nodeIdentity1)).identity(nodeIdentity1).build())); + assertTrue(users.contains(new User.Builder().identifier(IdentifierUtil.getIdentifier(nodeIdentity2)).identity(nodeIdentity2).build())); + } + + @Test + public void testOnConfiguredWhenTenantsExistAndInitialUsersProvided() throws Exception { + final String adminIdentity = "admin-user"; + final String nodeIdentity1 = "node-identity-1"; + final String nodeIdentity2 = "node-identity-2"; + + // despite setting initial users, they will not be loaded as the tenants file is non-empty + when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "1"))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "2"))) + .thenReturn(new StandardPropertyValue(nodeIdentity1, null)); + when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "3"))) + .thenReturn(new StandardPropertyValue(nodeIdentity2, null)); + + writeFile(primaryTenants, SIMPLE_TENANTS_BY_USER); + userGroupProvider.onConfigured(configurationContext); + + final Set users = userGroupProvider.getUsers(); + assertEquals(2, users.size()); + + assertTrue(users.contains(new User.Builder().identifier("user-1").identity("user-1").build())); + assertTrue(users.contains(new User.Builder().identifier("user-2").identity("user-2").build())); + } + + @Test + public void testOnConfiguredWhenTenantsFileDoesNotExist() throws Exception { + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + userGroupProvider.onConfigured(configurationContext); + assertEquals(0, userGroupProvider.getUsers().size()); + assertEquals(0, userGroupProvider.getGroups().size()); + } + + @Test + public void testOnConfiguredWhenRestoreDoesNotExist() throws Exception { + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + userGroupProvider.onConfigured(configurationContext); + + assertEquals(primaryTenants.length(), restoreTenants.length()); + } + + @Test(expected = AuthorizerCreationException.class) + public void testOnConfiguredWhenPrimaryDoesNotExist() throws Exception { + writeFile(restoreTenants, EMPTY_TENANTS_CONCISE); + userGroupProvider.onConfigured(configurationContext); + } + + + @Test(expected = AuthorizerCreationException.class) + public void testOnConfiguredWhenPrimaryTenantsDifferentThanRestore() throws Exception { + writeFile(primaryTenants, EMPTY_TENANTS); + writeFile(restoreTenants, EMPTY_TENANTS_CONCISE); + userGroupProvider.onConfigured(configurationContext); + } + + @Test(expected = AuthorizerCreationException.class) + public void testOnConfiguredWithBadTenantsSchema() throws Exception { + writeFile(primaryTenants, BAD_SCHEMA_TENANTS); + userGroupProvider.onConfigured(configurationContext); + } + + @Test + public void testGetAllUsersGroupsPolicies() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + + final Set groups = userGroupProvider.getGroups(); + assertEquals(2, groups.size()); + + boolean foundGroup1 = false; + boolean foundGroup2 = false; + + for (Group group : groups) { + if (group.getIdentifier().equals("group-1") && group.getName().equals("group-1") + && group.getUsers().size() == 1 && group.getUsers().contains("user-1")) { + foundGroup1 = true; + } else if (group.getIdentifier().equals("group-2") && group.getName().equals("group-2") + && group.getUsers().size() == 1 && group.getUsers().contains("user-2")) { + foundGroup2 = true; + } + } + + assertTrue(foundGroup1); + assertTrue(foundGroup2); + + final Set users = userGroupProvider.getUsers(); + assertEquals(2, users.size()); + + boolean foundUser1 = false; + boolean foundUser2 = false; + + for (User user : users) { + if (user.getIdentifier().equals("user-1") && user.getIdentity().equals("user-1")) { + foundUser1 = true; + } else if (user.getIdentifier().equals("user-2") && user.getIdentity().equals("user-2")) { + foundUser2 = true; + } + } + + assertTrue(foundUser1); + assertTrue(foundUser2); + } + + // --------------- User Tests ------------------------ + + @Test + public void testAddUser() throws Exception { + writeFile(primaryTenants, EMPTY_TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(0, userGroupProvider.getUsers().size()); + + final User user = new User.Builder() + .identifier("user-1") + .identity("user-identity-1") + .build(); + + final User addedUser = userGroupProvider.addUser(user); + assertNotNull(addedUser); + assertEquals(user.getIdentifier(), addedUser.getIdentifier()); + assertEquals(user.getIdentity(), addedUser.getIdentity()); + + final Set users = userGroupProvider.getUsers(); + assertEquals(1, users.size()); + } + + @Test + public void testGetUserByIdentifierWhenFound() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(2, userGroupProvider.getUsers().size()); + + final String identifier = "user-1"; + final User user = userGroupProvider.getUser(identifier); + assertNotNull(user); + assertEquals(identifier, user.getIdentifier()); + } + + @Test + public void testGetUserByIdentifierWhenNotFound() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(2, userGroupProvider.getUsers().size()); + + final String identifier = "user-X"; + final User user = userGroupProvider.getUser(identifier); + assertNull(user); + } + + @Test + public void testGetUserByIdentityWhenFound() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(2, userGroupProvider.getUsers().size()); + + final String identity = "user-1"; + final User user = userGroupProvider.getUserByIdentity(identity); + assertNotNull(user); + assertEquals(identity, user.getIdentifier()); + } + + @Test + public void testGetUserByIdentityWhenNotFound() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(2, userGroupProvider.getUsers().size()); + + final String identity = "user-X"; + final User user = userGroupProvider.getUserByIdentity(identity); + assertNull(user); + } + + @Test + public void testDeleteUser() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(2, userGroupProvider.getUsers().size()); + + // retrieve user-1 and verify it exists + final User user = userGroupProvider.getUser("user-1"); + assertEquals("user-1", user.getIdentifier()); + + // delete user-1 + final User deletedUser = userGroupProvider.deleteUser(user); + assertNotNull(deletedUser); + assertEquals("user-1", deletedUser.getIdentifier()); + + // should be one less user + assertEquals(1, userGroupProvider.getUsers().size()); + assertNull(userGroupProvider.getUser(user.getIdentifier())); + } + + @Test + public void testDeleteUserWhenNotFound() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(2, userGroupProvider.getUsers().size()); + + //user that doesn't exist + final User user = new User.Builder().identifier("user-X").identity("user-identity-X").build(); + + // should return null and still have 2 users because nothing was deleted + final User deletedUser = userGroupProvider.deleteUser(user); + assertNull(deletedUser); + assertEquals(2, userGroupProvider.getUsers().size()); + } + + @Test + public void testUpdateUserWhenFound() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(2, userGroupProvider.getUsers().size()); + + final User user = new User.Builder() + .identifier("user-1") + .identity("new-identity") + .build(); + + final User updatedUser = userGroupProvider.updateUser(user); + assertNotNull(updatedUser); + assertEquals(user.getIdentifier(), updatedUser.getIdentifier()); + assertEquals(user.getIdentity(), updatedUser.getIdentity()); + } + + @Test + public void testUpdateUserWhenNotFound() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(2, userGroupProvider.getUsers().size()); + + final User user = new User.Builder() + .identifier("user-X") + .identity("new-identity") + .build(); + + final User updatedUser = userGroupProvider.updateUser(user); + assertNull(updatedUser); + } + + // --------------- Group Tests ------------------------ + + @Test + public void testAddGroup() throws Exception { + writeFile(primaryTenants, EMPTY_TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(0, userGroupProvider.getGroups().size()); + + final Group group = new Group.Builder() + .identifier("group-id-1") + .name("group-name-1") + .build(); + + final Group addedGroup = userGroupProvider.addGroup(group); + assertNotNull(addedGroup); + assertEquals(group.getIdentifier(), addedGroup.getIdentifier()); + assertEquals(group.getName(), addedGroup.getName()); + assertEquals(0, addedGroup.getUsers().size()); + + final Set groups = userGroupProvider.getGroups(); + assertEquals(1, groups.size()); + } + + @Test + public void testAddGroupWithUser() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(2, userGroupProvider.getGroups().size()); + + final Group group = new Group.Builder() + .identifier("group-id-XXX") + .name("group-name-XXX") + .addUser("user-1") + .build(); + + final Group addedGroup = userGroupProvider.addGroup(group); + assertNotNull(addedGroup); + assertEquals(group.getIdentifier(), addedGroup.getIdentifier()); + assertEquals(group.getName(), addedGroup.getName()); + assertEquals(1, addedGroup.getUsers().size()); + + final Set groups = userGroupProvider.getGroups(); + assertEquals(3, groups.size()); + } + + @Test(expected = IllegalStateException.class) + public void testAddGroupWhenUserDoesNotExist() throws Exception { + writeFile(primaryTenants, EMPTY_TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(0, userGroupProvider.getGroups().size()); + + final Group group = new Group.Builder() + .identifier("group-id-1") + .name("group-name-1") + .addUser("user1") + .build(); + + userGroupProvider.addGroup(group); + } + + @Test + public void testGetGroupByIdentifierWhenFound() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(2, userGroupProvider.getGroups().size()); + + final String identifier = "group-1"; + final Group group = userGroupProvider.getGroup(identifier); + assertNotNull(group); + assertEquals(identifier, group.getIdentifier()); + } + + @Test + public void testGetGroupByIdentifierWhenNotFound() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(2, userGroupProvider.getGroups().size()); + + final String identifier = "group-X"; + final Group group = userGroupProvider.getGroup(identifier); + assertNull(group); + } + + @Test + public void testDeleteGroupWhenFound() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(2, userGroupProvider.getGroups().size()); + + // retrieve group-1 + final Group group = userGroupProvider.getGroup("group-1"); + assertEquals("group-1", group.getIdentifier()); + + // delete group-1 + final Group deletedGroup = userGroupProvider.deleteGroup(group); + assertNotNull(deletedGroup); + assertEquals("group-1", deletedGroup.getIdentifier()); + + // verify there is one less overall group + assertEquals(1, userGroupProvider.getGroups().size()); + + // verify we can no longer retrieve group-1 by identifier + assertNull(userGroupProvider.getGroup(group.getIdentifier())); + } + + @Test + public void testDeleteGroupWhenNotFound() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(2, userGroupProvider.getGroups().size()); + + final Group group = new Group.Builder() + .identifier("group-id-X") + .name("group-name-X") + .build(); + + final Group deletedGroup = userGroupProvider.deleteGroup(group); + assertNull(deletedGroup); + assertEquals(2, userGroupProvider.getGroups().size()); + } + + @Test + public void testUpdateGroupWhenFound() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(2, userGroupProvider.getGroups().size()); + + // verify user-1 is in group-1 before the update + final Group groupBefore = userGroupProvider.getGroup("group-1"); + assertEquals(1, groupBefore.getUsers().size()); + assertTrue(groupBefore.getUsers().contains("user-1")); + + final Group group = new Group.Builder() + .identifier("group-1") + .name("new-name") + .addUser("user-2") + .build(); + + final Group updatedGroup = userGroupProvider.updateGroup(group); + assertEquals(group.getIdentifier(), updatedGroup.getIdentifier()); + assertEquals(group.getName(), updatedGroup.getName()); + + assertEquals(1, updatedGroup.getUsers().size()); + assertTrue(updatedGroup.getUsers().contains("user-2")); + } + + @Test + public void testUpdateGroupWhenNotFound() throws Exception { + writeFile(primaryTenants, TENANTS); + userGroupProvider.onConfigured(configurationContext); + assertEquals(2, userGroupProvider.getGroups().size()); + + final Group group = new Group.Builder() + .identifier("group-X") + .name("group-X") + .build(); + + final Group updatedGroup = userGroupProvider.updateGroup(group); + assertNull(updatedGroup); + assertEquals(2, userGroupProvider.getGroups().size()); + } + + private static void writeFile(final File file, final String content) throws Exception { + byte[] bytes = content.getBytes(StandardCharsets.UTF_8); + try (final FileOutputStream fos = new FileOutputStream(file)) { + fos.write(bytes); + } + } + + private static boolean deleteFile(final File file) { + if (file.isDirectory()) { + FileUtils.deleteFilesInDir(file, null, null, true, true); + } + return FileUtils.deleteFile(file, null, 10); + } + + private NiFiProperties getNiFiProperties(final Properties properties) { + final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class); + when(nifiProperties.getPropertyKeys()).thenReturn(properties.stringPropertyNames()); + + when(nifiProperties.getProperty(anyString())).then(new Answer() { + @Override + public String answer(InvocationOnMock invocationOnMock) throws Throwable { + return properties.getProperty((String)invocationOnMock.getArguments()[0]); + } + }); + return nifiProperties; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java index 344f49cb9109..58343e8e4d27 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java @@ -22,10 +22,15 @@ public class StandardAuthorizerInitializationContext implements AuthorizerInitializationContext { private final String identifier; + private final UserGroupProviderLookup userGroupProviderLookup; + private final AccessPolicyProviderLookup accessPolicyProviderLookup; private final AuthorizerLookup authorizerLookup; - public StandardAuthorizerInitializationContext(String identifier, AuthorizerLookup authorizerLookup) { + public StandardAuthorizerInitializationContext(String identifier, UserGroupProviderLookup userGroupProviderLookup, + AccessPolicyProviderLookup accessPolicyProviderLookup, AuthorizerLookup authorizerLookup) { this.identifier = identifier; + this.userGroupProviderLookup = userGroupProviderLookup; + this.accessPolicyProviderLookup = accessPolicyProviderLookup; this.authorizerLookup = authorizerLookup; } @@ -38,4 +43,13 @@ public AuthorizerLookup getAuthorizerLookup() { return authorizerLookup; } + @Override + public AccessPolicyProviderLookup getAccessPolicyProviderLookup() { + return accessPolicyProviderLookup; + } + + @Override + public UserGroupProviderLookup getUserGroupProviderLookup() { + return userGroupProviderLookup; + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java new file mode 100644 index 000000000000..8e726f7b6d90 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java @@ -0,0 +1,251 @@ +/* + * 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; + +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.authorization.exception.AuthorizerDestructionException; +import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException; +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; + + @Override + public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { + accessPolicyProviderLookup = initializationContext.getAccessPolicyProviderLookup(); + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + final String accessPolicyProviderKey = configurationContext.getProperty("Access Policy Provider").getValue(); + accessPolicyProvider = accessPolicyProviderLookup.getAccessPolicyProvider(accessPolicyProviderKey); + + // 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; + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java index 2a82795023fa..8c1619a9677f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java @@ -16,7 +16,11 @@ */ package org.apache.nifi.authorization.user; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collections; import java.util.Objects; +import java.util.Set; /** * An implementation of NiFiUser. @@ -24,42 +28,20 @@ public class StandardNiFiUser implements NiFiUser { public static final String ANONYMOUS_IDENTITY = "anonymous"; - public static final StandardNiFiUser ANONYMOUS = new StandardNiFiUser(ANONYMOUS_IDENTITY, null, null, true); + public static final StandardNiFiUser ANONYMOUS = new Builder().identity(ANONYMOUS_IDENTITY).anonymous(true).build(); private final String identity; + private final Set groups; private final NiFiUser chain; private final String clientAddress; private final boolean isAnonymous; - public StandardNiFiUser(String identity) { - this(identity, null, null, false); - } - - public StandardNiFiUser(String identity, String clientAddress) { - this(identity, null, clientAddress, false); - } - - public StandardNiFiUser(String identity, NiFiUser chain) { - this(identity, chain, null, false); - } - - public StandardNiFiUser(String identity, NiFiUser chain, String clientAddress) { - this(identity, chain, clientAddress, false); - } - - /** - * This constructor is private as the only instance of this class which should have {@code isAnonymous} set to true is the singleton ANONYMOUS. - * - * @param identity the identity string for the user (i.e. "Andy" or "CN=alopresto, OU=Apache NiFi") - * @param chain the proxy chain that leads to this users - * @param clientAddress the source address of the request - * @param isAnonymous true to represent the canonical "anonymous" user - */ - private StandardNiFiUser(String identity, NiFiUser chain, String clientAddress, boolean isAnonymous) { - this.identity = identity; - this.chain = chain; - this.clientAddress = clientAddress; - this.isAnonymous = isAnonymous; + private StandardNiFiUser(final Builder builder) { + this.identity = builder.identity; + this.groups = builder.groups == null ? null : Collections.unmodifiableSet(builder.groups); + this.chain = builder.chain; + this.clientAddress = builder.clientAddress; + this.isAnonymous = builder.isAnonymous; } /** @@ -70,7 +52,7 @@ private StandardNiFiUser(String identity, NiFiUser chain, String clientAddress, * @return an anonymous user instance with the identity "anonymous" */ public static StandardNiFiUser populateAnonymousUser(NiFiUser chain, String clientAddress) { - return new StandardNiFiUser(ANONYMOUS_IDENTITY, chain, clientAddress, true); + return new Builder().identity(ANONYMOUS_IDENTITY).chain(chain).clientAddress(clientAddress).anonymous(true).build(); } @Override @@ -78,6 +60,11 @@ public String getIdentity() { return identity; } + @Override + public Set getGroups() { + return groups; + } + @Override public NiFiUser getChain() { return chain; @@ -116,6 +103,87 @@ public int hashCode() { @Override public String toString() { - return String.format("identity[%s]", getIdentity()); + final String formattedGroups; + if (groups == null) { + formattedGroups = "none"; + } else { + formattedGroups = StringUtils.join(groups, ", "); + } + + return String.format("identity[%s], groups[%s]", getIdentity(), formattedGroups); + } + + /** + * Builder for a StandardNiFiUser + */ + public static class Builder { + + private String identity; + private Set groups; + private NiFiUser chain; + private String clientAddress; + private boolean isAnonymous = false; + + /** + * Sets the identity. + * + * @param identity the identity string for the user (i.e. "Andy" or "CN=alopresto, OU=Apache NiFi") + * @return the builder + */ + public Builder identity(final String identity) { + this.identity = identity; + return this; + } + + /** + * Sets the groups. + * + * @param groups the user groups + * @return the builder + */ + public Builder groups(final Set groups) { + this.groups = groups; + return this; + } + + /** + * Sets the chain. + * + * @param chain the proxy chain that leads to this users + * @return the builder + */ + public Builder chain(final NiFiUser chain) { + this.chain = chain; + return this; + } + + /** + * Sets the client address. + * + * @param clientAddress the source address of the request + * @return the builder + */ + public Builder clientAddress(final String clientAddress) { + this.clientAddress = clientAddress; + return this; + } + + /** + * Sets whether this user is the canonical "anonymous" user + * + * @param isAnonymous true to represent the canonical "anonymous" user + * @return the builder + */ + private Builder anonymous(final boolean isAnonymous) { + this.isAnonymous = isAnonymous; + return this; + } + + /** + * @return builds a StandardNiFiUser from the current state of the builder + */ + public StandardNiFiUser build() { + return new StandardNiFiUser(this); + } } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/UserGroupUtil.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/UserGroupUtil.java new file mode 100644 index 000000000000..a4dc27ecfb17 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/UserGroupUtil.java @@ -0,0 +1,54 @@ +/* + * 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.util; + +import org.apache.nifi.authorization.Authorizer; +import org.apache.nifi.authorization.Group; +import org.apache.nifi.authorization.ManagedAuthorizer; +import org.apache.nifi.authorization.UserAndGroups; +import org.apache.nifi.authorization.UserGroupProvider; + +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + +public class UserGroupUtil { + + /** + * Gets the groups for the user with the specified identity. Returns null if the authorizer is not able to load user groups. + * + * @param authorizer the authorizer to load the groups from + * @param userIdentity the user identity + * @return the listing of groups for the user + */ + public static Set getUserGroups(final Authorizer authorizer, final String userIdentity) { + if (authorizer instanceof ManagedAuthorizer) { + final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer; + final UserGroupProvider userGroupProvider = managedAuthorizer.getAccessPolicyProvider().getUserGroupProvider(); + final UserAndGroups userAndGroups = userGroupProvider.getUserAndGroups(userIdentity); + final Set userGroups = userAndGroups.getGroups(); + + if (userGroups == null || userGroups.isEmpty()) { + return Collections.EMPTY_SET; + } else { + return userAndGroups.getGroups().stream().map(group -> group.getName()).collect(Collectors.toSet()); + } + } else { + return null; + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer new file mode 100755 index 000000000000..966a28989b9c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer @@ -0,0 +1,15 @@ +# 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. +org.apache.nifi.authorization.StandardManagedAuthorizer diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java new file mode 100644 index 000000000000..a40c6f967c35 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java @@ -0,0 +1,438 @@ +/* + * 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; + + +import org.apache.nifi.attribute.expression.language.StandardPropertyValue; +import org.apache.nifi.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class StandardManagedAuthorizerTest { + + private static final String EMPTY_FINGERPRINT = "" + + "" + + "" + + "" + + ""; + + private static final String NON_EMPTY_FINGERPRINT = "" + + "" + + "" + + "<accessPolicies>" + + "<policy identifier=\"policy-id-1\" resource=\"resource2\" actions=\"READ\">" + + "<policyUser identifier=\"user-id-1\"></policyUser>" + + "<policyGroup identifier=\"group-id-1\"></policyGroup>" + + "</policy>" + + "</accessPolicies>" + + "" + + "" + + "<tenants>" + + "<user identifier=\"user-id-1\" identity=\"user-1\"></user>" + + "<group identifier=\"group-id-1\" name=\"group-1\">" + + "<groupUser identifier=\"user-id-1\"></groupUser>" + + "</group>" + + "</tenants>" + + "" + + ""; + + private static final String ACCESS_POLICY_FINGERPRINT = + "" + + "" + + "" + + "" + + "" + + ""; + + private static final String TENANT_FINGERPRINT = + "" + + "" + + "" + + "" + + "" + + ""; + + private static final Resource TEST_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return "1"; + } + + @Override + public String getName() { + return "resource1"; + } + + @Override + public String getSafeDescription() { + return "description1"; + } + }; + + @Test(expected = AuthorizerCreationException.class) + public void testNullAccessPolicyProvider() throws Exception { + getStandardManagedAuthorizer(null); + } + + @Test + public void testEmptyFingerPrint() throws Exception { + final UserGroupProvider userGroupProvider = mock(UserGroupProvider.class); + + final AccessPolicyProvider accessPolicyProvider = mock(AccessPolicyProvider.class); + when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider); + + final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider); + Assert.assertEquals(EMPTY_FINGERPRINT, managedAuthorizer.getFingerprint()); + } + + @Test + public void testNonEmptyFingerPrint() throws Exception { + final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class); + when(userGroupProvider.getFingerprint()).thenReturn(TENANT_FINGERPRINT); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class); + when(accessPolicyProvider.getFingerprint()).thenReturn(ACCESS_POLICY_FINGERPRINT); + when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider); + + final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider); + Assert.assertEquals(NON_EMPTY_FINGERPRINT, managedAuthorizer.getFingerprint()); + } + + @Test + public void testInheritEmptyFingerprint() throws Exception { + final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class); + when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider); + + final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider); + managedAuthorizer.inheritFingerprint(EMPTY_FINGERPRINT); + + verify(userGroupProvider, times(0)).inheritFingerprint(anyString()); + verify(accessPolicyProvider, times(0)).inheritFingerprint(anyString()); + } + + @Test(expected = AuthorizationAccessException.class) + public void testInheritInvalidFingerprint() throws Exception { + final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class); + when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider); + + final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider); + managedAuthorizer.inheritFingerprint("not a valid fingerprint"); + } + + @Test + public void testInheritNonEmptyFingerprint() throws Exception { + final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class); + when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider); + + final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider); + managedAuthorizer.inheritFingerprint(NON_EMPTY_FINGERPRINT); + + verify(userGroupProvider, times(1)).inheritFingerprint(TENANT_FINGERPRINT); + verify(accessPolicyProvider, times(1)).inheritFingerprint(ACCESS_POLICY_FINGERPRINT); + } + + @Test + public void testCheckInheritEmptyFingerprint() throws Exception { + final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class); + when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider); + + final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider); + managedAuthorizer.checkInheritability(EMPTY_FINGERPRINT); + + verify(userGroupProvider, times(0)).inheritFingerprint(anyString()); + verify(accessPolicyProvider, times(0)).inheritFingerprint(anyString()); + } + + @Test(expected = AuthorizationAccessException.class) + public void testCheckInheritInvalidFingerprint() throws Exception { + final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class); + when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider); + + final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider); + managedAuthorizer.checkInheritability("not a valid fingerprint"); + } + + @Test + public void testCheckInheritNonEmptyFingerprint() throws Exception { + final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class); + when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider); + + final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider); + managedAuthorizer.checkInheritability(NON_EMPTY_FINGERPRINT); + + verify(userGroupProvider, times(1)).checkInheritability(TENANT_FINGERPRINT); + verify(accessPolicyProvider, times(1)).checkInheritability(ACCESS_POLICY_FINGERPRINT); + } + + @Test + public void testAuthorizationByUser() throws Exception { + final String userIdentifier = "userIdentifier1"; + final String userIdentity = "userIdentity1"; + + final User user = new User.Builder() + .identity(userIdentity) + .identifier(userIdentifier) + .build(); + + final AccessPolicy policy = new AccessPolicy.Builder() + .identifier("1") + .resource(TEST_RESOURCE.getIdentifier()) + .addUser(userIdentifier) + .action(RequestAction.READ) + .build(); + + final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class); + when(userGroupProvider.getUserAndGroups(userIdentity)).thenReturn(new UserAndGroups() { + @Override + public User getUser() { + return user; + } + + @Override + public Set getGroups() { + return Collections.EMPTY_SET; + } + }); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class); + when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy); + when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider); + + final AuthorizationRequest request = new AuthorizationRequest.Builder() + .identity(userIdentity) + .resource(TEST_RESOURCE) + .action(RequestAction.READ) + .accessAttempt(true) + .anonymous(false) + .build(); + + final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider); + assertEquals(AuthorizationResult.approved(), managedAuthorizer.authorize(request)); + } + + @Test + public void testAuthorizationByGroup() throws Exception { + final String userIdentifier = "userIdentifier1"; + final String userIdentity = "userIdentity1"; + final String groupIdentifier = "groupIdentifier1"; + + final User user = new User.Builder() + .identity(userIdentity) + .identifier(userIdentifier) + .build(); + + final Group group = new Group.Builder() + .identifier(groupIdentifier) + .name(groupIdentifier) + .addUser(user.getIdentifier()) + .build(); + + final AccessPolicy policy = new AccessPolicy.Builder() + .identifier("1") + .resource(TEST_RESOURCE.getIdentifier()) + .addGroup(groupIdentifier) + .action(RequestAction.READ) + .build(); + + final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class); + when(userGroupProvider.getUserAndGroups(userIdentity)).thenReturn(new UserAndGroups() { + @Override + public User getUser() { + return user; + } + + @Override + public Set getGroups() { + return Stream.of(group).collect(Collectors.toSet()); + } + }); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class); + when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy); + when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider); + + final AuthorizationRequest request = new AuthorizationRequest.Builder() + .identity(userIdentity) + .resource(TEST_RESOURCE) + .action(RequestAction.READ) + .accessAttempt(true) + .anonymous(false) + .build(); + + final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider); + assertEquals(AuthorizationResult.approved(), managedAuthorizer.authorize(request)); + } + + @Test + public void testResourceNotFound() throws Exception { + final String userIdentity = "userIdentity1"; + + final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class); + when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(null); + when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider); + + final AuthorizationRequest request = new AuthorizationRequest.Builder() + .identity(userIdentity) + .resource(TEST_RESOURCE) + .action(RequestAction.READ) + .accessAttempt(true) + .anonymous(false) + .build(); + + final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider); + assertEquals(AuthorizationResult.resourceNotFound(), managedAuthorizer.authorize(request)); + } + + @Test + public void testUnauthorizedDueToUnknownUser() throws Exception { + final String userIdentifier = "userIdentifier1"; + final String userIdentity = "userIdentity1"; + final String notUser1Identity = "not userIdentity1"; + + final User user = new User.Builder() + .identity(userIdentity) + .identifier(userIdentifier) + .build(); + + final AccessPolicy policy = new AccessPolicy.Builder() + .identifier("1") + .resource(TEST_RESOURCE.getIdentifier()) + .addUser(userIdentifier) + .action(RequestAction.READ) + .build(); + + final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class); + when(userGroupProvider.getUserAndGroups(notUser1Identity)).thenReturn(new UserAndGroups() { + @Override + public User getUser() { + return null; + } + + @Override + public Set getGroups() { + return Collections.EMPTY_SET; + } + }); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class); + when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy); + when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider); + + final AuthorizationRequest request = new AuthorizationRequest.Builder() + .identity(notUser1Identity) + .resource(TEST_RESOURCE) + .action(RequestAction.READ) + .accessAttempt(true) + .anonymous(false) + .build(); + + final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider); + assertTrue(AuthorizationResult.denied().getResult().equals(managedAuthorizer.authorize(request).getResult())); + } + + @Test + public void testUnauthorizedDueToLackOfPermission() throws Exception { + final String userIdentifier = "userIdentifier1"; + final String userIdentity = "userIdentity1"; + + final User user = new User.Builder() + .identity(userIdentity) + .identifier(userIdentifier) + .build(); + + final AccessPolicy policy = new AccessPolicy.Builder() + .identifier("1") + .resource(TEST_RESOURCE.getIdentifier()) + .addUser("userIdentity2") + .action(RequestAction.READ) + .build(); + + final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class); + when(userGroupProvider.getUserAndGroups(userIdentity)).thenReturn(new UserAndGroups() { + @Override + public User getUser() { + return user; + } + + @Override + public Set getGroups() { + return Collections.EMPTY_SET; + } + }); + + final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class); + when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy); + when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider); + + final AuthorizationRequest request = new AuthorizationRequest.Builder() + .identity(userIdentity) + .resource(TEST_RESOURCE) + .action(RequestAction.READ) + .accessAttempt(true) + .anonymous(false) + .build(); + + final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider); + assertTrue(AuthorizationResult.denied().getResult().equals(managedAuthorizer.authorize(request).getResult())); + } + + private StandardManagedAuthorizer getStandardManagedAuthorizer(final AccessPolicyProvider accessPolicyProvider) { + final StandardManagedAuthorizer managedAuthorizer = new StandardManagedAuthorizer(); + + final AuthorizerConfigurationContext configurationContext = mock(AuthorizerConfigurationContext.class); + when(configurationContext.getProperty("Access Policy Provider")).thenReturn(new StandardPropertyValue("access-policy-provider", null)); + + final AccessPolicyProviderLookup accessPolicyProviderLookup = mock(AccessPolicyProviderLookup.class); + when(accessPolicyProviderLookup.getAccessPolicyProvider("access-policy-provider")).thenReturn(accessPolicyProvider); + + final AuthorizerInitializationContext initializationContext = mock(AuthorizerInitializationContext.class); + when(initializationContext.getAccessPolicyProviderLookup()).thenReturn(accessPolicyProviderLookup); + + managedAuthorizer.initialize(initializationContext); + managedAuthorizer.onConfigured(configurationContext); + + return managedAuthorizer; + } +} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java index 069bf7919d11..003835fe9273 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java @@ -16,14 +16,6 @@ */ package org.apache.nifi.authorization.resource; -import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.argThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - import org.apache.nifi.authorization.AccessDeniedException; import org.apache.nifi.authorization.AuthorizationRequest; import org.apache.nifi.authorization.AuthorizationResult; @@ -31,11 +23,19 @@ import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.user.NiFiUser; -import org.apache.nifi.authorization.user.StandardNiFiUser; +import org.apache.nifi.authorization.user.StandardNiFiUser.Builder; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentMatcher; +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + public class DataAuthorizableTest { private static final String IDENTITY_1 = "identity-1"; @@ -83,20 +83,20 @@ public void testCheckAuthorizationNullUser() { @Test(expected = AccessDeniedException.class) public void testAuthorizeUnauthorizedUser() { - final NiFiUser user = new StandardNiFiUser("unknown"); + final NiFiUser user = new Builder().identity("unknown").build(); testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null); } @Test public void testCheckAuthorizationUnauthorizedUser() { - final NiFiUser user = new StandardNiFiUser("unknown"); + final NiFiUser user = new Builder().identity("unknown").build(); final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null); assertEquals(Result.Denied, result.getResult()); } @Test public void testAuthorizedUser() { - final NiFiUser user = new StandardNiFiUser(IDENTITY_1); + final NiFiUser user = new Builder().identity(IDENTITY_1).build(); testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null); verify(testAuthorizer, times(1)).authorize(argThat(new ArgumentMatcher() { @@ -109,7 +109,7 @@ public boolean matches(Object o) { @Test public void testCheckAuthorizationUser() { - final NiFiUser user = new StandardNiFiUser(IDENTITY_1); + final NiFiUser user = new Builder().identity(IDENTITY_1).build(); final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null); assertEquals(Result.Approved, result.getResult()); @@ -123,9 +123,9 @@ public boolean matches(Object o) { @Test public void testAuthorizedUserChain() { - final NiFiUser proxy2 = new StandardNiFiUser(PROXY_2); - final NiFiUser proxy1 = new StandardNiFiUser(PROXY_1, proxy2); - final NiFiUser user = new StandardNiFiUser(IDENTITY_1, proxy1); + final NiFiUser proxy2 = new Builder().identity(PROXY_2).build(); + final NiFiUser proxy1 = new Builder().identity(PROXY_1).chain(proxy2).build(); + final NiFiUser user = new Builder().identity(IDENTITY_1).chain(proxy1).build(); testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null); verify(testAuthorizer, times(3)).authorize(any(AuthorizationRequest.class)); @@ -136,9 +136,9 @@ public void testAuthorizedUserChain() { @Test public void testCheckAuthorizationUserChain() { - final NiFiUser proxy2 = new StandardNiFiUser(PROXY_2); - final NiFiUser proxy1 = new StandardNiFiUser(PROXY_1, proxy2); - final NiFiUser user = new StandardNiFiUser(IDENTITY_1, proxy1); + final NiFiUser proxy2 = new Builder().identity(PROXY_2).build(); + final NiFiUser proxy1 = new Builder().identity(PROXY_1).chain(proxy2).build(); + final NiFiUser user = new Builder().identity(IDENTITY_1).chain(proxy1).build(); final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null); assertEquals(Result.Approved, result.getResult()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java index bbaeb26da5bb..2fc55a4c6b7e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java @@ -16,6 +16,7 @@ */ package org.apache.nifi.cluster.coordination.http; +import org.apache.nifi.cluster.coordination.http.endpoints.AccessPolicyEndpointMerger; import org.apache.nifi.cluster.coordination.http.endpoints.BulletinBoardEndpointMerger; import org.apache.nifi.cluster.coordination.http.endpoints.ComponentStateEndpointMerger; import org.apache.nifi.cluster.coordination.http.endpoints.ConnectionEndpointMerger; @@ -60,9 +61,14 @@ import org.apache.nifi.cluster.coordination.http.endpoints.ReportingTaskEndpointMerger; import org.apache.nifi.cluster.coordination.http.endpoints.ReportingTaskTypesEndpointMerger; import org.apache.nifi.cluster.coordination.http.endpoints.ReportingTasksEndpointMerger; +import org.apache.nifi.cluster.coordination.http.endpoints.SearchUsersEndpointMerger; import org.apache.nifi.cluster.coordination.http.endpoints.StatusHistoryEndpointMerger; import org.apache.nifi.cluster.coordination.http.endpoints.SystemDiagnosticsEndpointMerger; import org.apache.nifi.cluster.coordination.http.endpoints.TemplatesEndpointMerger; +import org.apache.nifi.cluster.coordination.http.endpoints.UserEndpointMerger; +import org.apache.nifi.cluster.coordination.http.endpoints.UserGroupEndpointMerger; +import org.apache.nifi.cluster.coordination.http.endpoints.UserGroupsEndpointMerger; +import org.apache.nifi.cluster.coordination.http.endpoints.UsersEndpointMerger; import org.apache.nifi.cluster.coordination.http.replication.RequestReplicator; import org.apache.nifi.cluster.manager.NodeResponse; import org.apache.nifi.stream.io.NullOutputStream; @@ -141,6 +147,12 @@ public StandardHttpResponseMapper(final NiFiProperties nifiProperties) { endpointMergers.add(new FunnelEndpointMerger()); endpointMergers.add(new FunnelsEndpointMerger()); endpointMergers.add(new ControllerEndpointMerger()); + endpointMergers.add(new UsersEndpointMerger()); + endpointMergers.add(new UserEndpointMerger()); + endpointMergers.add(new UserGroupsEndpointMerger()); + endpointMergers.add(new UserGroupEndpointMerger()); + endpointMergers.add(new AccessPolicyEndpointMerger()); + endpointMergers.add(new SearchUsersEndpointMerger()); } @Override diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMerger.java new file mode 100644 index 000000000000..4b2406a3c23a --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMerger.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.cluster.coordination.http.endpoints; + +import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger; +import org.apache.nifi.cluster.manager.AccessPolicyEntityMerger; +import org.apache.nifi.cluster.manager.NodeResponse; +import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.web.api.entity.AccessPolicyEntity; + +import java.net.URI; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +public class AccessPolicyEndpointMerger extends AbstractSingleEntityEndpoint implements EndpointResponseMerger { + public static final Pattern ACCESS_POLICIES_URI_PATTERN = Pattern.compile("/nifi-api/policies"); + public static final Pattern ACCESS_POLICY_URI_PATTERN = Pattern.compile("/nifi-api/policies/[a-f0-9\\-]{36}"); + public static final Pattern ACCESS_POLICY_LOOKUP_URI_PATTERN = Pattern.compile("/nifi-api/policies/(?:read|write)/(?:[\\w-]+?/?)+"); + private final AccessPolicyEntityMerger accessPolicyEntityMerger = new AccessPolicyEntityMerger(); + + @Override + public boolean canHandle(final URI uri, final String method) { + if (("GET".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) && (ACCESS_POLICY_URI_PATTERN.matcher(uri.getPath()).matches())) { + return true; + } else if ("GET".equalsIgnoreCase(method) && ACCESS_POLICY_LOOKUP_URI_PATTERN.matcher(uri.getPath()).matches()) { + return true; + } else if ("POST".equalsIgnoreCase(method) && ACCESS_POLICIES_URI_PATTERN.matcher(uri.getPath()).matches()) { + return true; + } + + return false; + } + + @Override + protected Class getEntityClass() { + return AccessPolicyEntity.class; + } + + @Override + protected void mergeResponses(final AccessPolicyEntity clientEntity, final Map entityMap, + final Set successfulResponses, final Set problematicResponses) { + + accessPolicyEntityMerger.merge(clientEntity, entityMap); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/SearchUsersEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/SearchUsersEndpointMerger.java new file mode 100644 index 000000000000..31996cb649f0 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/SearchUsersEndpointMerger.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.cluster.coordination.http.endpoints; + +import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger; +import org.apache.nifi.cluster.manager.NodeResponse; +import org.apache.nifi.web.api.entity.TenantEntity; +import org.apache.nifi.web.api.entity.TenantsEntity; + +import java.net.URI; +import java.util.Collection; +import java.util.Set; +import java.util.regex.Pattern; + +public class SearchUsersEndpointMerger implements EndpointResponseMerger { + public static final Pattern SEARCH_TENANTS_URI_PATTERN = Pattern.compile("/nifi-api/tenants/search-results"); + + @Override + public boolean canHandle(final URI uri, final String method) { + return "GET".equalsIgnoreCase(method) && SEARCH_TENANTS_URI_PATTERN.matcher(uri.getPath()).matches(); + } + + @Override + public final NodeResponse merge(final URI uri, final String method, final Set successfulResponses, final Set problematicResponses, final NodeResponse clientResponse) { + if (!canHandle(uri, method)) { + throw new IllegalArgumentException("Cannot use Endpoint Mapper of type " + getClass().getSimpleName() + " to map responses for URI " + uri + ", HTTP Method " + method); + } + + final TenantsEntity responseEntity = clientResponse.getClientResponse().getEntity(TenantsEntity.class); + final Collection userEntities = responseEntity.getUsers(); + final Collection userGroupEntities = responseEntity.getUserGroups(); + + for (final NodeResponse nodeResponse : successfulResponses) { + final TenantsEntity nodeResponseEntity = nodeResponse == clientResponse ? responseEntity : nodeResponse.getClientResponse().getEntity(TenantsEntity.class); + + // only retain users/groups that all nodes agree on + userEntities.retainAll(nodeResponseEntity.getUsers()); + userGroupEntities.retainAll(nodeResponseEntity.getUserGroups()); + } + + // create a new client response + return new NodeResponse(clientResponse, responseEntity); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserEndpointMerger.java new file mode 100644 index 000000000000..afac1595923b --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserEndpointMerger.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.cluster.coordination.http.endpoints; + +import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger; +import org.apache.nifi.cluster.manager.NodeResponse; +import org.apache.nifi.cluster.manager.UserEntityMerger; +import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.web.api.entity.UserEntity; + +import java.net.URI; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +public class UserEndpointMerger extends AbstractSingleEntityEndpoint implements EndpointResponseMerger { + public static final Pattern USERS_URI_PATTERN = Pattern.compile("/nifi-api/tenants/users"); + public static final Pattern USER_URI_PATTERN = Pattern.compile("/nifi-api/tenants/users/[a-f0-9\\-]{36}"); + private final UserEntityMerger userEntityMerger = new UserEntityMerger(); + + @Override + public boolean canHandle(final URI uri, final String method) { + if (("GET".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) && (USER_URI_PATTERN.matcher(uri.getPath()).matches())) { + return true; + } else if ("POST".equalsIgnoreCase(method) && USERS_URI_PATTERN.matcher(uri.getPath()).matches()) { + return true; + } + + return false; + } + + @Override + protected Class getEntityClass() { + return UserEntity.class; + } + + @Override + protected void mergeResponses(final UserEntity clientEntity, final Map entityMap, + final Set successfulResponses, final Set problematicResponses) { + + userEntityMerger.merge(clientEntity, entityMap); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupEndpointMerger.java new file mode 100644 index 000000000000..ca2d6e14ee1c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupEndpointMerger.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.cluster.coordination.http.endpoints; + +import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger; +import org.apache.nifi.cluster.manager.NodeResponse; +import org.apache.nifi.cluster.manager.UserGroupEntityMerger; +import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.web.api.entity.UserGroupEntity; + +import java.net.URI; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +public class UserGroupEndpointMerger extends AbstractSingleEntityEndpoint implements EndpointResponseMerger { + public static final Pattern USER_GROUPS_URI_PATTERN = Pattern.compile("/nifi-api/tenants/user-groups"); + public static final Pattern USER_GROUP_URI_PATTERN = Pattern.compile("/nifi-api/tenants/user-groups/[a-f0-9\\-]{36}"); + private final UserGroupEntityMerger userGroupEntityMerger = new UserGroupEntityMerger(); + + @Override + public boolean canHandle(final URI uri, final String method) { + if (("GET".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) && (USER_GROUP_URI_PATTERN.matcher(uri.getPath()).matches())) { + return true; + } else if ("POST".equalsIgnoreCase(method) && USER_GROUPS_URI_PATTERN.matcher(uri.getPath()).matches()) { + return true; + } + + return false; + } + + @Override + protected Class getEntityClass() { + return UserGroupEntity.class; + } + + @Override + protected void mergeResponses(final UserGroupEntity clientEntity, final Map entityMap, + final Set successfulResponses, final Set problematicResponses) { + + userGroupEntityMerger.merge(clientEntity, entityMap); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupsEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupsEndpointMerger.java new file mode 100644 index 000000000000..3b1881490e66 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupsEndpointMerger.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.cluster.coordination.http.endpoints; + +import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger; +import org.apache.nifi.cluster.manager.NodeResponse; +import org.apache.nifi.cluster.manager.UserGroupsEntityMerger; +import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.web.api.entity.UserGroupEntity; +import org.apache.nifi.web.api.entity.UserGroupsEntity; + +import java.net.URI; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +public class UserGroupsEndpointMerger implements EndpointResponseMerger { + public static final Pattern USER_GROUPS_URI_PATTERN = Pattern.compile("/nifi-api/tenants/user-groups"); + + @Override + public boolean canHandle(final URI uri, final String method) { + return "GET".equalsIgnoreCase(method) && USER_GROUPS_URI_PATTERN.matcher(uri.getPath()).matches(); + } + + @Override + public final NodeResponse merge(final URI uri, final String method, final Set successfulResponses, final Set problematicResponses, final NodeResponse clientResponse) { + if (!canHandle(uri, method)) { + throw new IllegalArgumentException("Cannot use Endpoint Mapper of type " + getClass().getSimpleName() + " to map responses for URI " + uri + ", HTTP Method " + method); + } + + final UserGroupsEntity responseEntity = clientResponse.getClientResponse().getEntity(UserGroupsEntity.class); + final Collection userGroupEntities = responseEntity.getUserGroups(); + + final Map> entityMap = new HashMap<>(); + for (final NodeResponse nodeResponse : successfulResponses) { + final UserGroupsEntity nodeResponseEntity = nodeResponse == clientResponse ? responseEntity : nodeResponse.getClientResponse().getEntity(UserGroupsEntity.class); + final Collection nodeUserGroupEntities = nodeResponseEntity.getUserGroups(); + + // only retain user groups that all nodes agree on + userGroupEntities.retainAll(nodeUserGroupEntities); + + for (final UserGroupEntity nodeUserGroupEntity : nodeUserGroupEntities) { + final NodeIdentifier nodeId = nodeResponse.getNodeId(); + Map innerMap = entityMap.get(nodeId); + if (innerMap == null) { + innerMap = new HashMap<>(); + entityMap.put(nodeUserGroupEntity.getId(), innerMap); + } + + innerMap.put(nodeResponse.getNodeId(), nodeUserGroupEntity); + } + } + + UserGroupsEntityMerger.mergeUserGroups(userGroupEntities, entityMap); + + // create a new client response + return new NodeResponse(clientResponse, responseEntity); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UsersEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UsersEndpointMerger.java new file mode 100644 index 000000000000..581b35960c1c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UsersEndpointMerger.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.cluster.coordination.http.endpoints; + +import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger; +import org.apache.nifi.cluster.manager.NodeResponse; +import org.apache.nifi.cluster.manager.UsersEntityMerger; +import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.web.api.entity.UserEntity; +import org.apache.nifi.web.api.entity.UsersEntity; + +import java.net.URI; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +public class UsersEndpointMerger implements EndpointResponseMerger { + public static final Pattern TENANTS_URI_PATTERN = Pattern.compile("/nifi-api/tenants/users"); + + @Override + public boolean canHandle(final URI uri, final String method) { + return "GET".equalsIgnoreCase(method) && TENANTS_URI_PATTERN.matcher(uri.getPath()).matches(); + } + + @Override + public final NodeResponse merge(final URI uri, final String method, final Set successfulResponses, final Set problematicResponses, final NodeResponse clientResponse) { + if (!canHandle(uri, method)) { + throw new IllegalArgumentException("Cannot use Endpoint Mapper of type " + getClass().getSimpleName() + " to map responses for URI " + uri + ", HTTP Method " + method); + } + + final UsersEntity responseEntity = clientResponse.getClientResponse().getEntity(UsersEntity.class); + final Collection userEntities = responseEntity.getUsers(); + + final Map> entityMap = new HashMap<>(); + for (final NodeResponse nodeResponse : successfulResponses) { + final UsersEntity nodeResponseEntity = nodeResponse == clientResponse ? responseEntity : nodeResponse.getClientResponse().getEntity(UsersEntity.class); + final Collection nodeUserEntities = nodeResponseEntity.getUsers(); + + // only retain users that all nodes agree on + userEntities.retainAll(nodeUserEntities); + + for (final UserEntity nodeUserEntity : nodeUserEntities) { + final NodeIdentifier nodeId = nodeResponse.getNodeId(); + Map innerMap = entityMap.get(nodeId); + if (innerMap == null) { + innerMap = new HashMap<>(); + entityMap.put(nodeUserEntity.getId(), innerMap); + } + + innerMap.put(nodeResponse.getNodeId(), nodeUserEntity); + } + } + + UsersEntityMerger.mergeUsers(userEntities, entityMap); + + // create a new client response + return new NodeResponse(clientResponse, responseEntity); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMerger.java new file mode 100644 index 000000000000..88a0faf70953 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMerger.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.cluster.manager; + +import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.web.api.dto.AccessPolicyDTO; +import org.apache.nifi.web.api.entity.AccessPolicyEntity; +import org.apache.nifi.web.api.entity.TenantEntity; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class AccessPolicyEntityMerger implements ComponentEntityMerger { + @Override + public void merge(AccessPolicyEntity clientEntity, Map entityMap) { + ComponentEntityMerger.super.merge(clientEntity, entityMap); + } + + /** + * Merges the AccessPolicyEntity responses. + * + * @param clientEntity the entity being returned to the client + * @param entityMap all node responses + */ + public void mergeComponents(final AccessPolicyEntity clientEntity, final Map entityMap) { + final AccessPolicyDTO clientDto = clientEntity.getComponent(); + final Map dtoMap = new HashMap<>(); + for (final Map.Entry entry : entityMap.entrySet()) { + final AccessPolicyEntity nodeAccessPolicyEntity = entry.getValue(); + final AccessPolicyDTO nodeAccessPolicyDto = nodeAccessPolicyEntity.getComponent(); + dtoMap.put(entry.getKey(), nodeAccessPolicyDto); + } + + mergeDtos(clientDto, dtoMap); + } + + private static void mergeDtos(final AccessPolicyDTO clientDto, final Map dtoMap) { + // if unauthorized for the client dto, simple return + if (clientDto == null) { + return; + } + + final Set users = new HashSet<>(clientDto.getUsers()); + final Set userGroups = new HashSet<>(clientDto.getUserGroups()); + + for (final Map.Entry nodeEntry : dtoMap.entrySet()) { + final AccessPolicyDTO nodeAccessPolicy = nodeEntry.getValue(); + + if (nodeAccessPolicy != null) { + users.retainAll(nodeAccessPolicy.getUsers()); + userGroups.retainAll(nodeAccessPolicy.getUserGroups()); + } + } + + clientDto.setUsers(users); + clientDto.setUserGroups(userGroups); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserEntityMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserEntityMerger.java new file mode 100644 index 000000000000..e1e3fcc41720 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserEntityMerger.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.cluster.manager; + +import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.web.api.dto.UserDTO; +import org.apache.nifi.web.api.entity.AccessPolicySummaryEntity; +import org.apache.nifi.web.api.entity.TenantEntity; +import org.apache.nifi.web.api.entity.UserEntity; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class UserEntityMerger implements ComponentEntityMerger { + + @Override + public void merge(UserEntity clientEntity, Map entityMap) { + ComponentEntityMerger.super.merge(clientEntity, entityMap); + } + + /** + * Merges the UserEntity responses. + * + * @param clientEntity the entity being returned to the client + * @param entityMap all node responses + */ + public void mergeComponents(final UserEntity clientEntity, final Map entityMap) { + final UserDTO clientDto = clientEntity.getComponent(); + final Map dtoMap = new HashMap<>(); + for (final Map.Entry entry : entityMap.entrySet()) { + final UserEntity nodeUserEntity = entry.getValue(); + final UserDTO nodeUserDto = nodeUserEntity.getComponent(); + dtoMap.put(entry.getKey(), nodeUserDto); + } + + mergeDtos(clientDto, dtoMap); + } + + private static void mergeDtos(final UserDTO clientDto, final Map dtoMap) { + // if unauthorized for the client dto, simple return + if (clientDto == null) { + return; + } + + final Set accessPolicyEntities = new HashSet<>(clientDto.getAccessPolicies()); + final Set tenantEntities = new HashSet<>(clientDto.getUserGroups()); + + for (final Map.Entry nodeEntry : dtoMap.entrySet()) { + final UserDTO nodeUser = nodeEntry.getValue(); + + if (nodeUser != null) { + accessPolicyEntities.retainAll(nodeUser.getAccessPolicies()); + tenantEntities.retainAll(nodeUser.getUserGroups()); + } + } + + clientDto.setAccessPolicies(accessPolicyEntities); + clientDto.setUserGroups(tenantEntities); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupEntityMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupEntityMerger.java new file mode 100644 index 000000000000..8142b4515af5 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupEntityMerger.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.cluster.manager; + +import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.web.api.dto.UserGroupDTO; +import org.apache.nifi.web.api.entity.AccessPolicyEntity; +import org.apache.nifi.web.api.entity.TenantEntity; +import org.apache.nifi.web.api.entity.UserGroupEntity; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class UserGroupEntityMerger implements ComponentEntityMerger { + @Override + public void merge(UserGroupEntity clientEntity, Map entityMap) { + ComponentEntityMerger.super.merge(clientEntity, entityMap); + } + + /** + * Merges the UserGroupEntity responses. + * + * @param clientEntity the entity being returned to the client + * @param entityMap all node responses + */ + public void mergeComponents(final UserGroupEntity clientEntity, final Map entityMap) { + final UserGroupDTO clientDto = clientEntity.getComponent(); + final Map dtoMap = new HashMap<>(); + for (final Map.Entry entry : entityMap.entrySet()) { + final UserGroupEntity nodeUserGroupEntity = entry.getValue(); + final UserGroupDTO nodeUserGroupDto = nodeUserGroupEntity.getComponent(); + dtoMap.put(entry.getKey(), nodeUserGroupDto); + } + + mergeDtos(clientDto, dtoMap); + } + + private static void mergeDtos(final UserGroupDTO clientDto, final Map dtoMap) { + // if unauthorized for the client dto, simple return + if (clientDto == null) { + return; + } + + final Set accessPolicyEntities = new HashSet<>(clientDto.getAccessPolicies()); + final Set userEntities = new HashSet<>(clientDto.getUsers()); + + for (final Map.Entry nodeEntry : dtoMap.entrySet()) { + final UserGroupDTO nodeUserGroup = nodeEntry.getValue(); + + if (nodeUserGroup != null) { + accessPolicyEntities.retainAll(nodeUserGroup.getAccessPolicies()); + userEntities.retainAll(nodeUserGroup.getUsers()); + } + } + + clientDto.setAccessPolicies(accessPolicyEntities); + clientDto.setUsers(userEntities); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupsEntityMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupsEntityMerger.java new file mode 100644 index 000000000000..6dbb0c6bd531 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupsEntityMerger.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.cluster.manager; + +import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.web.api.entity.UserGroupEntity; + +import java.util.Collection; +import java.util.Map; + +public class UserGroupsEntityMerger implements ComponentEntityMerger { + private static final UserGroupEntityMerger userGroupEntityMerger = new UserGroupEntityMerger(); + + /** + * Merges multiple UserGroupEntity responses. + * + * @param userGroupEntities entities being returned to the client + * @param entityMap all node responses + */ + public static void mergeUserGroups(final Collection userGroupEntities, final Map> entityMap) { + for (final UserGroupEntity entity : userGroupEntities) { + userGroupEntityMerger.merge(entity, entityMap.get(entity.getId())); + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UsersEntityMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UsersEntityMerger.java new file mode 100644 index 000000000000..48743368dd13 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UsersEntityMerger.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.cluster.manager; + +import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.web.api.entity.UserEntity; + +import java.util.Collection; +import java.util.Map; + +public class UsersEntityMerger implements ComponentEntityMerger { + private static final UserEntityMerger userEntityMerger = new UserEntityMerger(); + + /** + * Merges multiple UserEntity responses. + * + * @param userEntities entities being returned to the client + * @param entityMap all node responses + */ + public static void mergeUsers(final Collection userEntities, final Map> entityMap) { + for (final UserEntity entity : userEntities) { + userEntityMerger.merge(entity, entityMap.get(entity.getId())); + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMergerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMergerTest.java new file mode 100644 index 000000000000..c8a06eda0bf2 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMergerTest.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.cluster.coordination.http.endpoints; + +import org.junit.Test; + +import java.net.URI; +import java.util.UUID; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class AccessPolicyEndpointMergerTest { + + @Test + public void testCanHandle() throws Exception { + final AccessPolicyEndpointMerger merger = new AccessPolicyEndpointMerger(); + assertTrue(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies"), "POST")); + assertFalse(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies"), "GET")); + assertFalse(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies"), "PUT")); + assertTrue(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies/" + UUID.randomUUID().toString()), "PUT")); + assertFalse(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies/Read/flow"), "GET")); + assertTrue(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies/read/flow"), "GET")); + assertTrue(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies/read/processors/" + UUID.randomUUID().toString()), "GET")); + } + +} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java index d90c49b492ba..b5eff63074ce 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java @@ -27,6 +27,7 @@ import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.authorization.user.NiFiUserDetails; import org.apache.nifi.authorization.user.StandardNiFiUser; +import org.apache.nifi.authorization.user.StandardNiFiUser.Builder; import org.apache.nifi.cluster.coordination.ClusterCoordinator; import org.apache.nifi.cluster.coordination.node.NodeConnectionState; import org.apache.nifi.cluster.coordination.node.NodeConnectionStatus; @@ -157,9 +158,9 @@ public void testRequestChain() { final Entity entity = new ProcessorEntity(); // set the user - final NiFiUser proxy2 = new StandardNiFiUser(proxyIdentity2); - final NiFiUser proxy1 = new StandardNiFiUser(proxyIdentity1, proxy2); - final NiFiUser user = new StandardNiFiUser(userIdentity, proxy1); + final NiFiUser proxy2 = new Builder().identity(proxyIdentity2).build(); + final NiFiUser proxy1 = new Builder().identity(proxyIdentity1).chain(proxy2).build(); + final NiFiUser user = new Builder().identity(userIdentity).chain(proxy1).build(); final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(user)); SecurityContextHolder.getContext().setAuthentication(authentication); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMergerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMergerTest.java new file mode 100644 index 000000000000..70a941a0e311 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMergerTest.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.cluster.manager; + +import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.web.api.dto.AccessPolicyDTO; +import org.apache.nifi.web.api.dto.PermissionsDTO; +import org.apache.nifi.web.api.dto.TenantDTO; +import org.apache.nifi.web.api.entity.AccessPolicyEntity; +import org.apache.nifi.web.api.entity.TenantEntity; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +public class AccessPolicyEntityMergerTest { + + @Test + public void testMergeAccessPolicy() throws Exception { + final NodeIdentifier node1 = new NodeIdentifier("node-1", "host-1", 8080, "host-1", 19998, null, null, null, false); + final NodeIdentifier node2 = new NodeIdentifier("node-2", "host-2", 8081, "host-2", 19999, null, null, null, false); + + final PermissionsDTO permissed = new PermissionsDTO(); + permissed.setCanRead(true); + permissed.setCanWrite(true); + + final TenantDTO user1DTO = new TenantDTO(); + user1DTO.setId("user-1"); + + final TenantEntity user1Entity = new TenantEntity(); + user1Entity.setPermissions(permissed); + user1Entity.setId(user1DTO.getId()); + user1Entity.setComponent(user1DTO); + + final TenantDTO user2DTO = new TenantDTO(); + user1DTO.setId("user-2"); + + final TenantEntity user2Entity = new TenantEntity(); + user2Entity.setPermissions(permissed); + user2Entity.setId(user2DTO.getId()); + user2Entity.setComponent(user2DTO); + + final AccessPolicyDTO accessPolicy1DTO = new AccessPolicyDTO(); + accessPolicy1DTO.setId("policy-1"); + accessPolicy1DTO.setUsers(Stream.of(user1Entity, user2Entity).collect(Collectors.toSet())); + accessPolicy1DTO.setUserGroups(Stream.of(user2Entity).collect(Collectors.toSet())); + + final AccessPolicyEntity accessPolicy1Entity = new AccessPolicyEntity(); + accessPolicy1Entity.setPermissions(permissed); + accessPolicy1Entity.setId(accessPolicy1DTO.getId()); + accessPolicy1Entity.setComponent(accessPolicy1DTO); + + final AccessPolicyDTO accessPolicy2DTO = new AccessPolicyDTO(); + accessPolicy2DTO.setId("policy-2"); + accessPolicy2DTO.setUsers(Stream.of(user1Entity).collect(Collectors.toSet())); + accessPolicy2DTO.setUserGroups(Stream.of(user1Entity, user2Entity).collect(Collectors.toSet())); + + final AccessPolicyEntity accessPolicy2Entity = new AccessPolicyEntity(); + accessPolicy2Entity.setPermissions(permissed); + accessPolicy2Entity.setId(accessPolicy2DTO.getId()); + accessPolicy2Entity.setComponent(accessPolicy2DTO); + + final Map nodeMap = new HashMap<>(); + nodeMap.put(node1, accessPolicy1Entity); + nodeMap.put(node2, accessPolicy2Entity); + + final AccessPolicyEntityMerger merger = new AccessPolicyEntityMerger(); + merger.merge(accessPolicy1Entity, nodeMap); + + assertEquals(1, accessPolicy1DTO.getUserGroups().size()); + assertTrue(accessPolicy1DTO.getUsers().contains(user1Entity)); + + assertEquals(1, accessPolicy1DTO.getUserGroups().size()); + assertTrue(accessPolicy1DTO.getUserGroups().contains(user2Entity)); + } + +} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserEntityMergerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserEntityMergerTest.java new file mode 100644 index 000000000000..03db8b47867c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserEntityMergerTest.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.cluster.manager; + +import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.web.api.dto.AccessPolicySummaryDTO; +import org.apache.nifi.web.api.dto.PermissionsDTO; +import org.apache.nifi.web.api.dto.TenantDTO; +import org.apache.nifi.web.api.dto.UserDTO; +import org.apache.nifi.web.api.entity.AccessPolicySummaryEntity; +import org.apache.nifi.web.api.entity.TenantEntity; +import org.apache.nifi.web.api.entity.UserEntity; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +public class UserEntityMergerTest { + + @Test + public void testMergeAccessPolicy() throws Exception { + final NodeIdentifier node1 = new NodeIdentifier("node-1", "host-1", 8080, "host-1", 19998, null, null, null, false); + final NodeIdentifier node2 = new NodeIdentifier("node-2", "host-2", 8081, "host-2", 19999, null, null, null, false); + + final PermissionsDTO permissed = new PermissionsDTO(); + permissed.setCanRead(true); + permissed.setCanWrite(true); + + final TenantDTO userGroup1DTO = new TenantDTO(); + userGroup1DTO.setId("user-group-1"); + + final TenantEntity userGroup1Entity = new TenantEntity(); + userGroup1Entity.setPermissions(permissed); + userGroup1Entity.setId(userGroup1DTO.getId()); + userGroup1Entity.setComponent(userGroup1DTO); + + final TenantDTO userGroup2DTO = new TenantDTO(); + userGroup1DTO.setId("user-group-2"); + + final TenantEntity userGroup2Entity = new TenantEntity(); + userGroup2Entity.setPermissions(permissed); + userGroup2Entity.setId(userGroup2DTO.getId()); + userGroup2Entity.setComponent(userGroup2DTO); + + final AccessPolicySummaryDTO policy1DTO = new AccessPolicySummaryDTO(); + policy1DTO.setId("policy-1"); + + final AccessPolicySummaryEntity policy1Entity = new AccessPolicySummaryEntity(); + policy1Entity.setPermissions(permissed); + policy1Entity.setId(policy1DTO.getId()); + policy1Entity.setComponent(policy1DTO); + + final AccessPolicySummaryDTO policy2DTO = new AccessPolicySummaryDTO(); + policy2DTO.setId("policy-2"); + + final AccessPolicySummaryEntity policy2Entity = new AccessPolicySummaryEntity(); + policy2Entity.setPermissions(permissed); + policy2Entity.setId(policy2DTO.getId()); + policy2Entity.setComponent(policy2DTO); + + final UserDTO user1DTO = new UserDTO(); + user1DTO.setId("user-1"); + user1DTO.setAccessPolicies(Stream.of(policy1Entity, policy2Entity).collect(Collectors.toSet())); + user1DTO.setUserGroups(Stream.of(userGroup2Entity).collect(Collectors.toSet())); + + final UserEntity user1Entity = new UserEntity(); + user1Entity.setPermissions(permissed); + user1Entity.setId(user1DTO.getId()); + user1Entity.setComponent(user1DTO); + + final UserDTO user2DTO = new UserDTO(); + user2DTO.setId("user-2"); + user2DTO.setAccessPolicies(Stream.of(policy1Entity).collect(Collectors.toSet())); + user2DTO.setUserGroups(Stream.of(userGroup1Entity, userGroup2Entity).collect(Collectors.toSet())); + + final UserEntity user2Entity = new UserEntity(); + user2Entity.setPermissions(permissed); + user2Entity.setId(user2DTO.getId()); + user2Entity.setComponent(user2DTO); + + final Map nodeMap = new HashMap<>(); + nodeMap.put(node1, user1Entity); + nodeMap.put(node2, user2Entity); + + final UserEntityMerger merger = new UserEntityMerger(); + merger.merge(user1Entity, nodeMap); + + assertEquals(1, user1DTO.getUserGroups().size()); + assertTrue(user1DTO.getAccessPolicies().contains(policy1Entity)); + + assertEquals(1, user1DTO.getUserGroups().size()); + assertTrue(user1DTO.getUserGroups().contains(userGroup2Entity)); + } + +} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserGroupEntityMergerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserGroupEntityMergerTest.java new file mode 100644 index 000000000000..cc975cb7b14c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserGroupEntityMergerTest.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.cluster.manager; + +import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.web.api.dto.AccessPolicyDTO; +import org.apache.nifi.web.api.dto.PermissionsDTO; +import org.apache.nifi.web.api.dto.TenantDTO; +import org.apache.nifi.web.api.dto.UserGroupDTO; +import org.apache.nifi.web.api.entity.AccessPolicyEntity; +import org.apache.nifi.web.api.entity.TenantEntity; +import org.apache.nifi.web.api.entity.UserGroupEntity; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +public class UserGroupEntityMergerTest { + + @Test + public void testMergeAccessPolicy() throws Exception { + final NodeIdentifier node1 = new NodeIdentifier("node-1", "host-1", 8080, "host-1", 19998, null, null, null, false); + final NodeIdentifier node2 = new NodeIdentifier("node-2", "host-2", 8081, "host-2", 19999, null, null, null, false); + + final PermissionsDTO permissed = new PermissionsDTO(); + permissed.setCanRead(true); + permissed.setCanWrite(true); + + final TenantDTO user1DTO = new TenantDTO(); + user1DTO.setId("user-1"); + + final TenantEntity user1Entity = new TenantEntity(); + user1Entity.setPermissions(permissed); + user1Entity.setId(user1DTO.getId()); + user1Entity.setComponent(user1DTO); + + final TenantDTO user2DTO = new TenantDTO(); + user1DTO.setId("user-2"); + + final TenantEntity user2Entity = new TenantEntity(); + user2Entity.setPermissions(permissed); + user2Entity.setId(user2DTO.getId()); + user2Entity.setComponent(user2DTO); + + final AccessPolicyDTO policy1DTO = new AccessPolicyDTO(); + policy1DTO.setId("policy-1"); + + final AccessPolicyEntity policy1Entity = new AccessPolicyEntity(); + policy1Entity.setPermissions(permissed); + policy1Entity.setId(policy1DTO.getId()); + policy1Entity.setComponent(policy1DTO); + + final AccessPolicyDTO policy2DTO = new AccessPolicyDTO(); + policy2DTO.setId("policy-2"); + + final AccessPolicyEntity policy2Entity = new AccessPolicyEntity(); + policy2Entity.setPermissions(permissed); + policy2Entity.setId(policy2DTO.getId()); + policy2Entity.setComponent(policy2DTO); + + final UserGroupDTO userGroup1DTO = new UserGroupDTO(); + userGroup1DTO.setId("user-1"); + userGroup1DTO.setAccessPolicies(Stream.of(policy1Entity, policy2Entity).collect(Collectors.toSet())); + userGroup1DTO.setUsers(Stream.of(user2Entity).collect(Collectors.toSet())); + + final UserGroupEntity userGroup1Entity = new UserGroupEntity(); + userGroup1Entity.setPermissions(permissed); + userGroup1Entity.setId(userGroup1DTO.getId()); + userGroup1Entity.setComponent(userGroup1DTO); + + final UserGroupDTO userGroup2DTO = new UserGroupDTO(); + userGroup2DTO.setId("user-2"); + userGroup2DTO.setAccessPolicies(Stream.of(policy1Entity).collect(Collectors.toSet())); + userGroup2DTO.setUsers(Stream.of(user1Entity, user2Entity).collect(Collectors.toSet())); + + final UserGroupEntity userGroup2Entity = new UserGroupEntity(); + userGroup2Entity.setPermissions(permissed); + userGroup2Entity.setId(userGroup2DTO.getId()); + userGroup2Entity.setComponent(userGroup2DTO); + + final Map nodeMap = new HashMap<>(); + nodeMap.put(node1, userGroup1Entity); + nodeMap.put(node2, userGroup2Entity); + + final UserGroupEntityMerger merger = new UserGroupEntityMerger(); + merger.merge(userGroup1Entity, nodeMap); + + assertEquals(1, userGroup1DTO.getUsers().size()); + assertTrue(userGroup1DTO.getAccessPolicies().contains(policy1Entity)); + + assertEquals(1, userGroup1DTO.getUsers().size()); + assertTrue(userGroup1DTO.getUsers().contains(user2Entity)); + } + +} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/cluster/protocol/DataFlow.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/cluster/protocol/DataFlow.java index 6e2b9fef48c3..03175840f43a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/cluster/protocol/DataFlow.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/cluster/protocol/DataFlow.java @@ -32,7 +32,7 @@ public interface DataFlow { /** * @return the raw byte array of the Authorizer's fingerprint, - * null when not using a sub-class of AbstractPolicyBasedAuthorizer + * null when not using a ManagedAuthorizer */ public byte[] getAuthorizerFingerprint(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml index 150ed56330e1..aaf9ce3918e3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml @@ -188,6 +188,10 @@ 4.3.1.201605051710-r test + + org.apache.nifi + nifi-authorizer + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java index b2c16281a727..f2387c20a9d2 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java @@ -17,8 +17,9 @@ package org.apache.nifi.controller; import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer; import org.apache.nifi.authorization.Authorizer; +import org.apache.nifi.authorization.AuthorizerCapabilityDetection; +import org.apache.nifi.authorization.ManagedAuthorizer; import org.apache.nifi.bundle.Bundle; import org.apache.nifi.cluster.ConnectionException; import org.apache.nifi.cluster.coordination.ClusterCoordinator; @@ -572,8 +573,8 @@ private FlowResponseMessage handleFlowRequest(final FlowRequestMessage request) } private byte[] getAuthorizerFingerprint() { - final boolean isInternalAuthorizer = (authorizer instanceof AbstractPolicyBasedAuthorizer); - return isInternalAuthorizer ? ((AbstractPolicyBasedAuthorizer) authorizer).getFingerprint().getBytes(StandardCharsets.UTF_8) : null; + final boolean isInternalAuthorizer = AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer); + return isInternalAuthorizer ? ((ManagedAuthorizer) authorizer).getFingerprint().getBytes(StandardCharsets.UTF_8) : null; } @Override diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java index 6f1e8e1055d2..12920a6f0ec1 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java @@ -18,8 +18,10 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer; import org.apache.nifi.authorization.Authorizer; +import org.apache.nifi.authorization.AuthorizerCapabilityDetection; +import org.apache.nifi.authorization.ManagedAuthorizer; +import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException; import org.apache.nifi.bundle.BundleCoordinate; import org.apache.nifi.cluster.protocol.DataFlow; import org.apache.nifi.cluster.protocol.StandardDataFlow; @@ -224,15 +226,15 @@ public void sync(final FlowController controller, final DataFlow proposedFlow, f logger.trace("Getting Authorizer fingerprint from controller"); final byte[] existingAuthFingerprint; - final AbstractPolicyBasedAuthorizer policyBasedAuthorizer; + final ManagedAuthorizer managedAuthorizer; final Authorizer authorizer = controller.getAuthorizer(); - if (authorizer instanceof AbstractPolicyBasedAuthorizer) { - policyBasedAuthorizer = (AbstractPolicyBasedAuthorizer) authorizer; - existingAuthFingerprint = policyBasedAuthorizer.getFingerprint().getBytes(StandardCharsets.UTF_8); + if (AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) { + managedAuthorizer = (ManagedAuthorizer) authorizer; + existingAuthFingerprint = managedAuthorizer.getFingerprint().getBytes(StandardCharsets.UTF_8); } else { existingAuthFingerprint = null; - policyBasedAuthorizer = null; + managedAuthorizer = null; } final Set missingComponents = new HashSet<>(); @@ -249,7 +251,7 @@ public void sync(final FlowController controller, final DataFlow proposedFlow, f if (existingFlowEmpty) { configuration = parseFlowBytes(proposedFlow.getFlow()); if (configuration != null) { - logger.trace("Checking bunde compatibility"); + logger.trace("Checking bundle compatibility"); checkBundleCompatibility(configuration); } } else { @@ -272,7 +274,7 @@ public void sync(final FlowController controller, final DataFlow proposedFlow, f logger.trace("Checking authorizer inheritability"); - final AuthorizerInheritability authInheritability = checkAuthorizerInheritability(existingDataFlow, proposedFlow); + final AuthorizerInheritability authInheritability = checkAuthorizerInheritability(authorizer, existingDataFlow, proposedFlow); if (!authInheritability.isInheritable() && authInheritability.getReason() != null) { throw new UninheritableFlowException("Proposed Authorizer is not inheritable by the flow controller because of Authorizer differences: " + authInheritability.getReason()); } @@ -415,10 +417,10 @@ public void sync(final FlowController controller, final DataFlow proposedFlow, f } // if auths are inheritable and we have a policy based authorizer, then inherit - if (authInheritability.isInheritable() && policyBasedAuthorizer != null) { + if (authInheritability.isInheritable() && managedAuthorizer != null) { logger.trace("Inheriting authorizations"); final String proposedAuthFingerprint = new String(proposedFlow.getAuthorizerFingerprint(), StandardCharsets.UTF_8); - policyBasedAuthorizer.inheritFingerprint(proposedAuthFingerprint); + managedAuthorizer.inheritFingerprint(proposedAuthFingerprint); } logger.debug("Finished syncing flows"); @@ -1391,7 +1393,7 @@ public String checkMissingComponentsInheritability(final DataFlow existingFlow, * @param proposedFlow the proposed DataFlow * @return the AuthorizerInheritability result */ - public AuthorizerInheritability checkAuthorizerInheritability(final DataFlow existingFlow, final DataFlow proposedFlow) { + private AuthorizerInheritability checkAuthorizerInheritability(final Authorizer authorizer, final DataFlow existingFlow, final DataFlow proposedFlow) { final byte[] existing = existingFlow.getAuthorizerFingerprint(); final byte[] proposed = proposedFlow.getAuthorizerFingerprint(); @@ -1414,15 +1416,20 @@ public AuthorizerInheritability checkAuthorizerInheritability(final DataFlow exi // both are internal, but not the same if (!Arrays.equals(existing, proposed)) { - final byte[] emptyAuthBytes = AbstractPolicyBasedAuthorizer.EMPTY_FINGERPRINT.getBytes(StandardCharsets.UTF_8); + if (AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) { + final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer; - // if current is empty then we can take all the proposed authorizations - // otherwise they are both internal authorizers and don't match so we can't proceed - if (Arrays.equals(emptyAuthBytes, existing)) { - return AuthorizerInheritability.inheritable(); + try { + // if the configurations are not equal, see if the manager indicates the proposed configuration is inheritable + managedAuthorizer.checkInheritability(new String(proposed, StandardCharsets.UTF_8)); + return AuthorizerInheritability.inheritable(); + } catch (final UninheritableAuthorizationsException e) { + return AuthorizerInheritability.uninheritable(e.getMessage()); + } } else { + // should never hit since the existing is only null when authorizer is not managed return AuthorizerInheritability.uninheritable( - "Proposed Authorizations do not match current Authorizations"); + "Proposed Authorizations do not match current Authorizations and are not configured with an internal Authorizer"); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java index 14d3dccdbd93..8326889f2408 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java @@ -18,7 +18,9 @@ import org.apache.nifi.annotation.behavior.RequiresInstanceClassLoading; import org.apache.nifi.authentication.LoginIdentityProvider; +import org.apache.nifi.authorization.AccessPolicyProvider; import org.apache.nifi.authorization.Authorizer; +import org.apache.nifi.authorization.UserGroupProvider; import org.apache.nifi.bundle.Bundle; import org.apache.nifi.bundle.BundleCoordinate; import org.apache.nifi.components.ConfigurableComponent; @@ -82,6 +84,8 @@ public class ExtensionManager { definitionMap.put(ReportingTask.class, new HashSet<>()); definitionMap.put(ControllerService.class, new HashSet<>()); definitionMap.put(Authorizer.class, new HashSet<>()); + definitionMap.put(UserGroupProvider.class, new HashSet<>()); + definitionMap.put(AccessPolicyProvider.class, new HashSet<>()); definitionMap.put(LoginIdentityProvider.class, new HashSet<>()); definitionMap.put(ProvenanceRepository.class, new HashSet<>()); definitionMap.put(ComponentStatusRepository.class, new HashSet<>()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/NarThreadContextClassLoader.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/NarThreadContextClassLoader.java index e7faa0292f92..0be99dc50836 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/NarThreadContextClassLoader.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/NarThreadContextClassLoader.java @@ -17,7 +17,9 @@ package org.apache.nifi.nar; import org.apache.nifi.authentication.LoginIdentityProvider; +import org.apache.nifi.authorization.AccessPolicyProvider; import org.apache.nifi.authorization.Authorizer; +import org.apache.nifi.authorization.UserGroupProvider; import org.apache.nifi.bundle.Bundle; import org.apache.nifi.components.Validator; import org.apache.nifi.components.state.StateProvider; @@ -64,6 +66,8 @@ public class NarThreadContextClassLoader extends URLClassLoader { narSpecificClasses.add(StreamCallback.class); narSpecificClasses.add(ControllerService.class); narSpecificClasses.add(Authorizer.class); + narSpecificClasses.add(UserGroupProvider.class); + narSpecificClasses.add(AccessPolicyProvider.class); narSpecificClasses.add(LoginIdentityProvider.class); narSpecificClasses.add(ProvenanceRepository.class); narSpecificClasses.add(ComponentStatusRepository.class); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml index 7dd24de83e45..054cbdc15c3e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml @@ -141,7 +141,7 @@ - file-provider + managed-authorizer diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml index 46bf6374a293..652afae88541 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml @@ -14,13 +14,105 @@ limitations under the License. --> + + file-user-group-provider + org.apache.nifi.authorization.FileUserGroupProvider + ./conf/users.xml + + + + + + + + file-access-policy-provider + org.apache.nifi.authorization.FileAccessPolicyProvider + file-user-group-provider + ./conf/authorizations.xml + + + + + + + + + managed-authorizer + org.apache.nifi.authorization.StandardManagedAuthorizer + file-access-policy-provider + + + - + + --> \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/main/java/org/apache/nifi/remote/StandardRootGroupPort.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/main/java/org/apache/nifi/remote/StandardRootGroupPort.java index 873cd33e8810..ea79675acc40 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/main/java/org/apache/nifi/remote/StandardRootGroupPort.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/main/java/org/apache/nifi/remote/StandardRootGroupPort.java @@ -16,24 +16,6 @@ */ package org.apache.nifi.remote; -import static java.util.Objects.requireNonNull; - -import java.io.IOException; -import java.net.SocketTimeoutException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - import org.apache.nifi.authorization.AuthorizationResult; import org.apache.nifi.authorization.AuthorizationResult.Result; import org.apache.nifi.authorization.Authorizer; @@ -41,9 +23,10 @@ import org.apache.nifi.authorization.resource.Authorizable; import org.apache.nifi.authorization.resource.DataTransferAuthorizable; import org.apache.nifi.authorization.user.NiFiUser; -import org.apache.nifi.authorization.user.StandardNiFiUser; +import org.apache.nifi.authorization.user.StandardNiFiUser.Builder; import org.apache.nifi.authorization.util.IdentityMapping; import org.apache.nifi.authorization.util.IdentityMappingUtil; +import org.apache.nifi.authorization.util.UserGroupUtil; import org.apache.nifi.components.ValidationResult; import org.apache.nifi.connectable.ConnectableType; import org.apache.nifi.controller.AbstractPort; @@ -73,6 +56,24 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import static java.util.Objects.requireNonNull; + public class StandardRootGroupPort extends AbstractPort implements RootGroupPort { private static final String CATEGORY = "Site to Site"; @@ -362,8 +363,8 @@ public PortAuthorizationResult checkUserAuthorization(final String dn) { } final String identity = IdentityMappingUtil.mapIdentity(dn, identityMappings); - - return checkUserAuthorization(new StandardNiFiUser(identity)); + final Set groups = UserGroupUtil.getUserGroups(authorizer, identity); + return checkUserAuthorization(new Builder().identity(identity).groups(groups).build()); } @Override 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 dbb2aaf96dfb..36a9524cd55e 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 @@ -2559,6 +2559,7 @@ private boolean isUserAuthorized(final NiFiUser user, final RootGroupPort port) final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(ResourceFactory.getDataTransferResource(port.getResource())) .identity(user.getIdentity()) + .groups(user.getGroups()) .anonymous(user.isAnonymous()) .accessAttempt(false) .action(RequestAction.WRITE) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java index cbae778b474f..14440402823e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java @@ -17,20 +17,6 @@ package org.apache.nifi.web; import com.sun.jersey.core.util.MultivaluedMapImpl; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import javax.ws.rs.HttpMethod; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.action.Action; import org.apache.nifi.action.Component; @@ -79,6 +65,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; + /** * Implements the NiFiWebConfigurationContext interface to support a context in both standalone and clustered environments. */ @@ -110,6 +111,7 @@ private void authorizeFlowAccess(final NiFiUser user) { final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(ResourceFactory.getFlowResource()) .identity(user.getIdentity()) + .groups(user.getGroups()) .anonymous(user.isAnonymous()) .accessAttempt(true) .action(RequestAction.READ) 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 999c8322f6fb..689ce7be01b9 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 @@ -23,8 +23,8 @@ import com.wordnik.swagger.annotations.ApiResponses; import com.wordnik.swagger.annotations.Authorization; import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer; import org.apache.nifi.authorization.Authorizer; +import org.apache.nifi.authorization.AuthorizerCapabilityDetection; import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.resource.Authorizable; import org.apache.nifi.authorization.user.NiFiUserUtils; @@ -140,8 +140,8 @@ public Response getAccessPolicyForResource( ) @PathParam("resource") String rawResource) { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER); } // parse the action and resource type @@ -203,8 +203,8 @@ public Response createAccessPolicy( ) final AccessPolicyEntity requestAccessPolicyEntity) { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_POLICIES); } if (requestAccessPolicyEntity == null || requestAccessPolicyEntity.getComponent() == null) { @@ -294,8 +294,8 @@ public Response getAccessPolicy( @PathParam("id") final String id) { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER); } if (isReplicateRequest()) { @@ -356,8 +356,8 @@ public Response updateAccessPolicy( ) final AccessPolicyEntity requestAccessPolicyEntity) { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_POLICIES); } if (requestAccessPolicyEntity == null || requestAccessPolicyEntity.getComponent() == null) { @@ -454,8 +454,8 @@ public Response removeAccessPolicy( @PathParam("id") final String id) { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_POLICIES); } if (isReplicateRequest()) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java index cb87ca2101ce..0a3b0e0d8f38 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java @@ -110,6 +110,7 @@ private void authorizeController(final RequestAction action) { final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(ResourceFactory.getControllerResource()) .identity(user.getIdentity()) + .groups(user.getGroups()) .anonymous(user.isAnonymous()) .accessAttempt(true) .action(action) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/CountersResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/CountersResource.java index 3d6c3be7add0..2a2c2b9f2e79 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/CountersResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/CountersResource.java @@ -94,6 +94,7 @@ private void authorizeCounters(final RequestAction action) { final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(ResourceFactory.getCountersResource()) .identity(user.getIdentity()) + .groups(user.getGroups()) .anonymous(user.isAnonymous()) .accessAttempt(true) .action(action) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java index a380aa75e47b..14f2108cbb2e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java @@ -217,6 +217,7 @@ private void authorizeFlow() { final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(ResourceFactory.getFlowResource()) .identity(user.getIdentity()) + .groups(user.getGroups()) .anonymous(user.isAnonymous()) .accessAttempt(true) .action(RequestAction.READ) @@ -2236,6 +2237,7 @@ public Response getComponentHistory( final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(ResourceFactory.getControllerResource()) .identity(user.getIdentity()) + .groups(user.getGroups()) .anonymous(user.isAnonymous()) .accessAttempt(true) .action(RequestAction.READ) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProvenanceResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProvenanceResource.java index 10663bec0d77..2aff9ca455f7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProvenanceResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProvenanceResource.java @@ -114,6 +114,7 @@ private void authorizeProvenanceRequest() { final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(ResourceFactory.getProvenanceResource()) .identity(user.getIdentity()) + .groups(user.getGroups()) .anonymous(user.isAnonymous()) .accessAttempt(true) .action(RequestAction.READ) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ResourceResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ResourceResource.java index cd41ed9f27a2..56c62bcc5c2d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ResourceResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ResourceResource.java @@ -74,6 +74,7 @@ private void authorizeResource() { final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(ResourceFactory.getResourceResource()) .identity(user.getIdentity()) + .groups(user.getGroups()) .anonymous(user.isAnonymous()) .accessAttempt(true) .action(RequestAction.READ) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SiteToSiteResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SiteToSiteResource.java index 744a9f44dbc0..ce5b3276fbfd 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SiteToSiteResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SiteToSiteResource.java @@ -112,6 +112,7 @@ protected void authorizeSiteToSite() { final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(ResourceFactory.getSiteToSiteResource()) .identity(user.getIdentity()) + .groups(user.getGroups()) .anonymous(user.isAnonymous()) .accessAttempt(true) .action(RequestAction.READ) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SystemDiagnosticsResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SystemDiagnosticsResource.java index 04e86834c231..b000060fe7fb 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SystemDiagnosticsResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SystemDiagnosticsResource.java @@ -77,6 +77,7 @@ private void authorizeSystem() { final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(ResourceFactory.getSystemResource()) .identity(user.getIdentity()) + .groups(user.getGroups()) .anonymous(user.isAnonymous()) .accessAttempt(true) .action(RequestAction.READ) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java index ab82aceb7a67..d48900964909 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java @@ -23,8 +23,8 @@ import com.wordnik.swagger.annotations.ApiResponses; import com.wordnik.swagger.annotations.Authorization; import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer; import org.apache.nifi.authorization.Authorizer; +import org.apache.nifi.authorization.AuthorizerCapabilityDetection; import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.resource.Authorizable; import org.apache.nifi.authorization.user.NiFiUserUtils; @@ -149,8 +149,8 @@ public Response createUser( ) final UserEntity requestUserEntity) { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS); } if (requestUserEntity == null || requestUserEntity.getComponent() == null) { @@ -234,8 +234,8 @@ public Response getUser( @PathParam("id") final String id) { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER); } if (isReplicateRequest()) { @@ -284,8 +284,8 @@ public Response getUser( public Response getUsers() { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER); } if (isReplicateRequest()) { @@ -352,8 +352,8 @@ public Response updateUser( ) final UserEntity requestUserEntity) { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS); } if (requestUserEntity == null || requestUserEntity.getComponent() == null) { @@ -448,8 +448,8 @@ public Response removeUser( @PathParam("id") final String id) { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS); } if (isReplicateRequest()) { @@ -538,8 +538,8 @@ public Response createUserGroup( ) final UserGroupEntity requestUserGroupEntity) { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS); } if (requestUserGroupEntity == null || requestUserGroupEntity.getComponent() == null) { @@ -623,8 +623,8 @@ public Response getUserGroup( @PathParam("id") final String id) { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER); } if (isReplicateRequest()) { @@ -673,8 +673,8 @@ public Response getUserGroup( public Response getUserGroups() { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER); } if (isReplicateRequest()) { @@ -740,8 +740,8 @@ public Response updateUserGroup( ) final UserGroupEntity requestUserGroupEntity) { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS); } if (requestUserGroupEntity == null || requestUserGroupEntity.getComponent() == null) { @@ -836,8 +836,8 @@ public Response removeUserGroup( @PathParam("id") final String id) { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS); } if (isReplicateRequest()) { @@ -897,7 +897,7 @@ public Response removeUserGroup( @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.") } ) - public Response searchCluster( + public Response searchTenants( @ApiParam( value = "Identity to search for.", required = true @@ -905,8 +905,8 @@ public Response searchCluster( @QueryParam("q") @DefaultValue(StringUtils.EMPTY) String value) { // ensure we're running with a configurable authorizer - if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) { - throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) { + throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER); } if (isReplicateRequest()) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/AccessDeniedExceptionMapper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/AccessDeniedExceptionMapper.java index 9c5ba4dea72e..47b28d9a1a9d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/AccessDeniedExceptionMapper.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/AccessDeniedExceptionMapper.java @@ -55,7 +55,7 @@ public Response toResponse(AccessDeniedException exception) { if (user == null) { identity = ""; } else { - identity = user.getIdentity(); + identity = user.toString(); } logger.info(String.format("%s does not have permission to access the requested resource. %s Returning %s response.", identity, exception.getMessage(), status)); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java index 95b1ebcd2e0f..4b59de9a15ec 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java @@ -38,9 +38,9 @@ import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.DeprecationNotice; import org.apache.nifi.annotation.documentation.Tags; -import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer; import org.apache.nifi.authorization.AccessPolicy; import org.apache.nifi.authorization.Authorizer; +import org.apache.nifi.authorization.AuthorizerCapabilityDetection; import org.apache.nifi.authorization.Group; import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.Resource; @@ -220,7 +220,9 @@ public FlowConfigurationDTO createFlowConfigurationDto(final String autoRefreshI // get the refresh interval final long refreshInterval = FormatUtils.getTimeDuration(autoRefreshInterval, TimeUnit.SECONDS); dto.setAutoRefreshIntervalSeconds(refreshInterval); - dto.setSupportsConfigurableAuthorizer(authorizer instanceof AbstractPolicyBasedAuthorizer); + dto.setSupportsManagedAuthorizer(AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)); + dto.setSupportsConfigurableUsersAndGroups(AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)); + dto.setSupportsConfigurableAuthorizer(AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer)); final Date now = new Date(); dto.setTimeOffset(TimeZone.getDefault().getOffset(now.getTime())); 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 1d48ee01b160..853ee418c4d5 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 @@ -23,7 +23,9 @@ public interface AccessPolicyDAO { - String MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER = "This NiFi is not configured to internally manage users, groups, and policies. Please contact your system administrator."; + String MSG_NON_MANAGED_AUTHORIZER = "This NiFi is not configured to internally manage users, groups, or policies. Please contact your system administrator."; + String MSG_NON_CONFIGURABLE_POLICIES = "This NiFi is not configured to allow configurable policies. Please contact your system administrator."; + String MSG_NON_CONFIGURABLE_USERS = "This NiFi is not configured to allow configurable users and groups. Please contact your system administrator."; /** * Whether or not NiFi supports a configurable authorizer. 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 a47c0517de3c..92904702dc4a 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 @@ -16,15 +16,21 @@ */ package org.apache.nifi.web.dao.impl; -import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer; import org.apache.nifi.authorization.AccessPolicy; +import org.apache.nifi.authorization.AccessPolicyProvider; +import org.apache.nifi.authorization.AccessPolicyProviderInitializationContext; import org.apache.nifi.authorization.Authorizer; +import org.apache.nifi.authorization.AuthorizerCapabilityDetection; import org.apache.nifi.authorization.AuthorizerConfigurationContext; -import org.apache.nifi.authorization.AuthorizerInitializationContext; import org.apache.nifi.authorization.Group; +import org.apache.nifi.authorization.ManagedAuthorizer; +import org.apache.nifi.authorization.ConfigurableAccessPolicyProvider; +import org.apache.nifi.authorization.ConfigurableUserGroupProvider; import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.User; -import org.apache.nifi.authorization.UsersAndAccessPolicies; +import org.apache.nifi.authorization.UserAndGroups; +import org.apache.nifi.authorization.UserGroupProvider; +import org.apache.nifi.authorization.UserGroupProviderInitializationContext; import org.apache.nifi.authorization.exception.AuthorizationAccessException; import org.apache.nifi.authorization.exception.AuthorizerCreationException; import org.apache.nifi.authorization.exception.AuthorizerDestructionException; @@ -44,118 +50,101 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGroupDAO, UserDAO { - private final AbstractPolicyBasedAuthorizer authorizer; - private final boolean supportsConfigurableAuthorizer; + private final AccessPolicyProvider accessPolicyProvider; + private final UserGroupProvider userGroupProvider; public StandardPolicyBasedAuthorizerDAO(final Authorizer authorizer) { - if (authorizer instanceof AbstractPolicyBasedAuthorizer) { - this.authorizer = (AbstractPolicyBasedAuthorizer) authorizer; - this.supportsConfigurableAuthorizer = true; + if (AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) { + accessPolicyProvider = ((ManagedAuthorizer) authorizer).getAccessPolicyProvider(); } else { - this.authorizer = new AbstractPolicyBasedAuthorizer() { + accessPolicyProvider = new AccessPolicyProvider() { @Override - public Group doAddGroup(final Group group) throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + public Set getAccessPolicies() throws AuthorizationAccessException { + throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER); } @Override - public Group getGroup(final String identifier) throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException { + throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER); } @Override - public Group doUpdateGroup(final Group group) throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException { + throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER); } @Override - public Group deleteGroup(final Group group) throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); - } + public UserGroupProvider getUserGroupProvider() { + return new UserGroupProvider() { + @Override + public Set getUsers() throws AuthorizationAccessException { + throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER); + } - @Override - public Set getGroups() throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); - } + @Override + public User getUser(String identifier) throws AuthorizationAccessException { + throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER); + } - @Override - public User doAddUser(final User user) throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); - } + @Override + public User getUserByIdentity(String identity) throws AuthorizationAccessException { + throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER); + } - @Override - public User getUser(final String identifier) throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); - } + @Override + public Set getGroups() throws AuthorizationAccessException { + throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER); + } - @Override - public User getUserByIdentity(final String identity) throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); - } + @Override + public Group getGroup(String identifier) throws AuthorizationAccessException { + throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER); + } - @Override - public User doUpdateUser(final User user) throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); - } + @Override + public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException { + throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER); + } - @Override - public User deleteUser(final User user) throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); - } + @Override + public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException { - @Override - public Set getUsers() throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); - } + } - @Override - public AccessPolicy doAddAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); - } + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { - @Override - public AccessPolicy getAccessPolicy(final String identifier) throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); - } + } - @Override - public AccessPolicy updateAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); - } + @Override + public void preDestruction() throws AuthorizerDestructionException { - @Override - public AccessPolicy deleteAccessPolicy(final AccessPolicy policy) throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); + } + }; } @Override - public Set getAccessPolicies() throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); - } + public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException { - @Override - public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException { - throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER); } @Override - public void initialize(final AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { - } + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { - @Override - public void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { } @Override public void preDestruction() throws AuthorizerDestructionException { + } }; - this.supportsConfigurableAuthorizer = false; } + + userGroupProvider = accessPolicyProvider.getUserGroupProvider(); } private AccessPolicy findAccessPolicy(final RequestAction requestAction, final String resource) { - return authorizer.getAccessPolicies().stream() + return accessPolicyProvider.getAccessPolicies().stream() .filter(policy -> policy.getAction().equals(requestAction) && policy.getResource().equals(resource)) .findFirst() .orElse(null); @@ -163,23 +152,28 @@ private AccessPolicy findAccessPolicy(final RequestAction requestAction, final S @Override public boolean supportsConfigurableAuthorizer() { - return supportsConfigurableAuthorizer; + return accessPolicyProvider instanceof ConfigurableAccessPolicyProvider; } @Override public boolean hasAccessPolicy(final String accessPolicyId) { - return authorizer.getAccessPolicy(accessPolicyId) != null; + return accessPolicyProvider.getAccessPolicy(accessPolicyId) != null; } @Override public AccessPolicy createAccessPolicy(final AccessPolicyDTO accessPolicyDTO) { - return authorizer.addAccessPolicy(buildAccessPolicy(accessPolicyDTO.getId(), - accessPolicyDTO.getResource(), RequestAction.valueOfValue(accessPolicyDTO.getAction()), accessPolicyDTO)); + if (supportsConfigurableAuthorizer()) { + final ConfigurableAccessPolicyProvider configurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) accessPolicyProvider; + return configurableAccessPolicyProvider.addAccessPolicy(buildAccessPolicy(accessPolicyDTO.getId(), + accessPolicyDTO.getResource(), RequestAction.valueOfValue(accessPolicyDTO.getAction()), accessPolicyDTO)); + } else { + throw new IllegalStateException(MSG_NON_CONFIGURABLE_POLICIES); + } } @Override public AccessPolicy getAccessPolicy(final String accessPolicyId) { - final AccessPolicy accessPolicy = authorizer.getAccessPolicy(accessPolicyId); + final AccessPolicy accessPolicy = accessPolicyProvider.getAccessPolicy(accessPolicyId); if (accessPolicy == null) { throw new ResourceNotFoundException(String.format("Unable to find access policy with id '%s'.", accessPolicyId)); } @@ -210,14 +204,25 @@ public AccessPolicy getAccessPolicy(final RequestAction requestAction, final Aut @Override public AccessPolicy updateAccessPolicy(final AccessPolicyDTO accessPolicyDTO) { - final AccessPolicy currentAccessPolicy = getAccessPolicy(accessPolicyDTO.getId()); - return authorizer.updateAccessPolicy(buildAccessPolicy(currentAccessPolicy.getIdentifier(), - currentAccessPolicy.getResource(), currentAccessPolicy.getAction(), accessPolicyDTO)); + if (supportsConfigurableAuthorizer()) { + final ConfigurableAccessPolicyProvider configurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) accessPolicyProvider; + + final AccessPolicy currentAccessPolicy = getAccessPolicy(accessPolicyDTO.getId()); + return configurableAccessPolicyProvider.updateAccessPolicy(buildAccessPolicy(currentAccessPolicy.getIdentifier(), + currentAccessPolicy.getResource(), currentAccessPolicy.getAction(), accessPolicyDTO)); + } else { + throw new IllegalStateException(MSG_NON_CONFIGURABLE_POLICIES); + } } @Override public AccessPolicy deleteAccessPolicy(final String accessPolicyId) { - return authorizer.deleteAccessPolicy(getAccessPolicy(accessPolicyId)); + if (supportsConfigurableAuthorizer()) { + final ConfigurableAccessPolicyProvider configurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) accessPolicyProvider; + return configurableAccessPolicyProvider.deleteAccessPolicy(getAccessPolicy(accessPolicyId)); + } else { + throw new IllegalStateException(MSG_NON_CONFIGURABLE_POLICIES); + } } private AccessPolicy buildAccessPolicy(final String identifier, final String resource, final RequestAction action, final AccessPolicyDTO accessPolicyDTO) { @@ -238,17 +243,22 @@ private AccessPolicy buildAccessPolicy(final String identifier, final String res @Override public boolean hasUserGroup(final String userGroupId) { - return authorizer.getGroup(userGroupId) != null; + return userGroupProvider.getGroup(userGroupId) != null; } @Override public Group createUserGroup(final UserGroupDTO userGroupDTO) { - return authorizer.addGroup(buildUserGroup(userGroupDTO.getId(), userGroupDTO)); + if (userGroupProvider instanceof ConfigurableUserGroupProvider) { + final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider; + return configurableUserGroupProvider.addGroup(buildUserGroup(userGroupDTO.getId(), userGroupDTO)); + } else { + throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS); + } } @Override public Group getUserGroup(final String userGroupId) { - final Group userGroup = authorizer.getGroup(userGroupId); + final Group userGroup = userGroupProvider.getGroup(userGroupId); if (userGroup == null) { throw new ResourceNotFoundException(String.format("Unable to find user group with id '%s'.", userGroupId)); } @@ -257,14 +267,14 @@ public Group getUserGroup(final String userGroupId) { @Override public Set getUserGroupsForUser(String userId) { - return authorizer.getGroups().stream() + return userGroupProvider.getGroups().stream() .filter(g -> g.getUsers().contains(userId)) .collect(Collectors.toSet()); } @Override public Set getAccessPoliciesForUser(String userId) { - return authorizer.getAccessPolicies().stream() + return accessPolicyProvider.getAccessPolicies().stream() .filter(p -> { // policy contains the user if (p.getUsers().contains(userId)) { @@ -272,14 +282,14 @@ public Set getAccessPoliciesForUser(String userId) { } // policy contains a group with the user - return !p.getGroups().stream().filter(g -> authorizer.getGroup(g).getUsers().contains(userId)).collect(Collectors.toSet()).isEmpty(); + return !p.getGroups().stream().filter(g -> userGroupProvider.getGroup(g).getUsers().contains(userId)).collect(Collectors.toSet()).isEmpty(); }) .collect(Collectors.toSet()); } @Override public Set getAccessPoliciesForUserGroup(String userGroupId) { - return authorizer.getAccessPolicies().stream() + return accessPolicyProvider.getAccessPolicies().stream() .filter(p -> { // policy contains the user group return p.getGroups().contains(userGroupId); @@ -289,17 +299,46 @@ public Set getAccessPoliciesForUserGroup(String userGroupId) { @Override public Set getUserGroups() { - return authorizer.getGroups(); + return userGroupProvider.getGroups(); } @Override public Group updateUserGroup(final UserGroupDTO userGroupDTO) { - return authorizer.updateGroup(buildUserGroup(getUserGroup(userGroupDTO.getId()).getIdentifier(), userGroupDTO)); + if (userGroupProvider instanceof ConfigurableUserGroupProvider) { + final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider; + return configurableUserGroupProvider.updateGroup(buildUserGroup(getUserGroup(userGroupDTO.getId()).getIdentifier(), userGroupDTO)); + } else { + throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS); + } } @Override public Group deleteUserGroup(final String userGroupId) { - return authorizer.deleteGroup(getUserGroup(userGroupId)); + if (userGroupProvider instanceof ConfigurableUserGroupProvider) { + final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider; + + final Group group = getUserGroup(userGroupId); + final Group removedGroup = configurableUserGroupProvider.deleteGroup(group); + + // ensure the user was removed + if (removedGroup == null) { + throw new ResourceNotFoundException(String.format("Unable to find user group with id '%s'.", removedGroup)); + } + + // remove any references to the user group being deleted from policies if possible + if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) { + for (AccessPolicy policy : accessPolicyProvider.getAccessPolicies()) { + if (policy.getGroups().contains(removedGroup.getIdentifier())) { + final AccessPolicy.Builder builder = new AccessPolicy.Builder(policy).removeGroup(removedGroup.getIdentifier()); + ((ConfigurableAccessPolicyProvider) accessPolicyProvider).updateAccessPolicy(builder.build()); + } + } + } + + return removedGroup; + } else { + throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS); + } } private Group buildUserGroup(final String identifier, final UserGroupDTO userGroupDTO) { @@ -313,17 +352,22 @@ private Group buildUserGroup(final String identifier, final UserGroupDTO userGro @Override public boolean hasUser(final String userId) { - return authorizer.getUser(userId) != null; + return userGroupProvider.getUser(userId) != null; } @Override public User createUser(final UserDTO userDTO) { - return authorizer.addUser(buildUser(userDTO.getId(), userDTO)); + if (userGroupProvider instanceof ConfigurableUserGroupProvider) { + final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider; + return configurableUserGroupProvider.addUser(buildUser(userDTO.getId(), userDTO)); + } else { + throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS); + } } @Override public User getUser(final String userId) { - final User user = authorizer.getUser(userId); + final User user = userGroupProvider.getUser(userId); if (user == null) { throw new ResourceNotFoundException(String.format("Unable to find user with id '%s'.", userId)); } @@ -332,18 +376,46 @@ public User getUser(final String userId) { @Override public Set getUsers() { - return authorizer.getUsers(); + return userGroupProvider.getUsers(); } @Override public User updateUser(final UserDTO userDTO) { - return authorizer.updateUser(buildUser(getUser(userDTO.getId()).getIdentifier(), userDTO)); + if (userGroupProvider instanceof ConfigurableUserGroupProvider) { + final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider; + return configurableUserGroupProvider.updateUser(buildUser(getUser(userDTO.getId()).getIdentifier(), userDTO)); + } else { + throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS); + } } @Override public User deleteUser(final String userId) { - final User user = getUser(userId); - return authorizer.deleteUser(user); + if (userGroupProvider instanceof ConfigurableUserGroupProvider) { + final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider; + + final User user = getUser(userId); + final User removedUser = configurableUserGroupProvider.deleteUser(user); + + // ensure the user was removed + if (removedUser == null) { + throw new ResourceNotFoundException(String.format("Unable to find user with id '%s'.", userId)); + } + + // remove any references to the user being deleted from policies if possible + if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) { + for (AccessPolicy policy : accessPolicyProvider.getAccessPolicies()) { + if (policy.getUsers().contains(removedUser.getIdentifier())) { + final AccessPolicy.Builder builder = new AccessPolicy.Builder(policy).removeUser(removedUser.getIdentifier()); + ((ConfigurableAccessPolicyProvider) accessPolicyProvider).updateAccessPolicy(builder.build()); + } + } + } + + return removedUser; + } else { + throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS); + } } private User buildUser(final String identifier, final UserDTO userDTO) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/StandardNiFiServiceFacadeSpec.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/StandardNiFiServiceFacadeSpec.groovy index 29ab83a0ca5e..a830a8775584 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/StandardNiFiServiceFacadeSpec.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/StandardNiFiServiceFacadeSpec.groovy @@ -16,35 +16,51 @@ */ package org.apache.nifi.web -import org.apache.nifi.authorization.* +import org.apache.nifi.authorization.AccessDeniedException +import org.apache.nifi.authorization.AccessPolicy +import org.apache.nifi.authorization.AuthorizableLookup +import org.apache.nifi.authorization.AuthorizationResult +import org.apache.nifi.authorization.Authorizer +import org.apache.nifi.authorization.Group +import org.apache.nifi.authorization.RequestAction +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.ResourceFactory import org.apache.nifi.authorization.user.NiFiUser -import org.apache.nifi.authorization.user.StandardNiFiUser import org.apache.nifi.authorization.user.NiFiUserDetails +import org.apache.nifi.authorization.user.StandardNiFiUser import org.apache.nifi.controller.service.ControllerServiceProvider import org.apache.nifi.reporting.Bulletin import org.apache.nifi.reporting.BulletinRepository -import org.apache.nifi.reporting.ComponentType -import org.apache.nifi.web.api.dto.* +import org.apache.nifi.web.api.dto.AccessPolicyDTO +import org.apache.nifi.web.api.dto.BulletinDTO +import org.apache.nifi.web.api.dto.DtoFactory +import org.apache.nifi.web.api.dto.EntityFactory +import org.apache.nifi.web.api.dto.RevisionDTO +import org.apache.nifi.web.api.dto.UserDTO +import org.apache.nifi.web.api.dto.UserGroupDTO import org.apache.nifi.web.api.entity.BulletinEntity import org.apache.nifi.web.api.entity.UserEntity import org.apache.nifi.web.controller.ControllerFacade import org.apache.nifi.web.dao.AccessPolicyDAO import org.apache.nifi.web.dao.UserDAO import org.apache.nifi.web.dao.UserGroupDAO -import org.apache.nifi.web.revision.* +import org.apache.nifi.web.revision.DeleteRevisionTask +import org.apache.nifi.web.revision.ReadOnlyRevisionCallback +import org.apache.nifi.web.revision.RevisionClaim +import org.apache.nifi.web.revision.RevisionManager +import org.apache.nifi.web.revision.UpdateRevisionTask import org.apache.nifi.web.security.token.NiFiAuthenticationToken import org.springframework.security.core.context.SecurityContextHolder import spock.lang.Ignore import spock.lang.Specification import spock.lang.Unroll - class StandardNiFiServiceFacadeSpec extends Specification { def setup() { - final NiFiUser user = new StandardNiFiUser("nifi-user"); + final NiFiUser user = new StandardNiFiUser.Builder().identity("nifi-user").build(); final NiFiAuthenticationToken auth = new NiFiAuthenticationToken(new NiFiUserDetails(user)); SecurityContextHolder.getContext().setAuthentication(auth); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAOSpec.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAOSpec.groovy index 340f6f9bcc73..5a4cc3b76c97 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAOSpec.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAOSpec.groovy @@ -27,6 +27,14 @@ import spock.lang.Unroll class StandardPolicyBasedAuthorizerDAOSpec extends Specification { + private AbstractPolicyBasedAuthorizer mockAuthorizer() { + def authorizer = Mock AbstractPolicyBasedAuthorizer + authorizer.getAccessPolicyProvider() >> { + callRealMethod(); + } + return authorizer; + } + @Unroll def "test non-policy-based authorizer #method throws IllegalStateException"() { when: @@ -34,31 +42,57 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { then: def e = thrown(IllegalStateException) - assert e.message.equalsIgnoreCase(StandardPolicyBasedAuthorizerDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER) + assert e.message.equalsIgnoreCase(StandardPolicyBasedAuthorizerDAO.MSG_NON_MANAGED_AUTHORIZER) where: method | daoMethod - 'createAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createAccessPolicy(new AccessPolicyDTO(id: '1', resource: '/1', action: "read")) } - 'createUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createUser(new UserDTO(id: '1', identity: 'a')) } - 'createUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createUserGroup(new UserGroupDTO(id: '1', identity: 'a')) } - 'deleteAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteAccessPolicy('1') } - 'deleteUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteUser('1') } - 'deleteUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteUserGroup('1') } 'getAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).getAccessPolicy('1') } 'getUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).getUser('1') } 'getUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).getUserGroup('1') } 'hasAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).hasAccessPolicy('1') } 'hasUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).hasUser('1') } 'hasUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).hasUserGroup('1') } - 'updateAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).updateAccessPolicy(new AccessPolicyDTO(id: '1', resource: '/1', action: "read")) } + } + + @Unroll + def "test non-configurable user group provider #method throws IllegalStateException"() { + when: + daoMethod() + + then: + def e = thrown(IllegalStateException) + assert e.message.equalsIgnoreCase(StandardPolicyBasedAuthorizerDAO.MSG_NON_CONFIGURABLE_USERS) + + where: + method | daoMethod + 'createUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createUser(new UserDTO(id: '1', identity: 'a')) } + 'createUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createUserGroup(new UserGroupDTO(id: '1', identity: 'a')) } + 'deleteUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteUser('1') } + 'deleteUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteUserGroup('1') } 'updateUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).updateUser(new UserDTO(id: '1', identity: 'a')) } 'updateUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).updateUserGroup(new UserGroupDTO(id: '1', identity: 'a')) } } + @Unroll + def "test non-configurable access policy provider #method throws IllegalStateException"() { + when: + daoMethod() + + then: + def e = thrown(IllegalStateException) + assert e.message.equalsIgnoreCase(StandardPolicyBasedAuthorizerDAO.MSG_NON_CONFIGURABLE_POLICIES) + + where: + method | daoMethod + 'createAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createAccessPolicy(new AccessPolicyDTO(id: '1', resource: '/1', action: "read")) } + 'deleteAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteAccessPolicy('1') } + 'updateAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).updateAccessPolicy(new AccessPolicyDTO(id: '1', resource: '/1', action: "read")) } + } + @Unroll def "HasAccessPolicy: accessPolicy: #accessPolicy"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -79,7 +113,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "CreateAccessPolicy: accessPolicy=#accessPolicy"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) def requestDTO = new AccessPolicyDTO(id: 'policy-id-1', resource: '/fake/resource', action: "read", users: [new TenantEntity(id: 'user-id-1')] as Set, @@ -92,7 +126,6 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { noExceptionThrown() then: - 1 * authorizer.getAccessPolicies() >> accessPolicies 1 * authorizer.doAddAccessPolicy(accessPolicy) >> accessPolicy 0 * _ result?.equals accessPolicy @@ -106,7 +139,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "GetAccessPolicy: success"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -126,7 +159,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "GetAccessPolicy: failure"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -141,7 +174,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "UpdateAccessPolicy: success"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) def requestDTO = new AccessPolicyDTO(id: 'policy-id-1', resource: '/fake/resource', action: "read", users: [new TenantEntity(id: 'user-id-1')] as Set, @@ -165,7 +198,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "UpdateAccessPolicy: failure"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) def requestDTO = new AccessPolicyDTO(id: 'policy-id-1', resource: '/fake/resource', action: "read", users: [new TenantEntity(id: 'user-id-1')] as Set, @@ -183,7 +216,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "DeleteAccessPolicy: success"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -204,7 +237,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "DeleteAccessPolicy: failure"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -219,7 +252,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "HasUserGroup: userGroup=#userGroup"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -239,7 +272,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "CreateUserGroup: userGroup=#userGroup"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) def requestDTO = new UserGroupDTO(id: 'user-group-id-1', identity: 'user group identity', users: [new TenantEntity(id: 'user-id-1')] as Set) @@ -250,8 +283,6 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { noExceptionThrown() then: - 1 * authorizer.getUsers() >> users - 1 * authorizer.getGroups() >> groups 1 * authorizer.doAddGroup(userGroup) >> userGroup 0 * _ result?.equals userGroup @@ -265,7 +296,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "GetUserGroup: success"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -284,7 +315,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "GetUserGroup: failure"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -299,7 +330,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "GetUserGroups: success"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -318,7 +349,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "UpdateUserGroup: success"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) def requestDTO = new UserGroupDTO(id: 'user-group-id-1', identity: 'user group identity', users: [new TenantEntity(id: 'user-id-1')] as Set) @@ -327,8 +358,6 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { then: 1 * authorizer.getGroup(requestDTO.id) >> userGroup - 1 * authorizer.getUsers() >> users - 1 * authorizer.getGroups() >> groups 1 * authorizer.doUpdateGroup(userGroup) >> userGroup 0 * _ result?.equals(userGroup) @@ -342,7 +371,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "UpdateUserGroup: failure"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) def requestDTO = new UserGroupDTO(id: 'user-group-id-1', identity: 'user group identity', users: [new TenantEntity(id: 'user-id-1')] as Set) @@ -358,7 +387,10 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "DeleteUserGroup: success"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() + authorizer.getAccessPolicyProvider().getAccessPolicies() >> { + callRealMethod(); + } def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -367,6 +399,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { then: 1 * authorizer.getGroup('user-group-id-1') >> userGroup 1 * authorizer.deleteGroup(userGroup) >> userGroup + 1 * authorizer.getAccessPolicies() >> [] 0 * _ assert result?.equals(userGroup) @@ -378,7 +411,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "DeleteUserGroup: failure"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -393,7 +426,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "HasUser: user=#user"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -412,7 +445,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "CreateUser: user=#user"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) def requestDTO = new UserDTO(id: 'user-id-1', identity: 'user identity', userGroups: [new TenantEntity(id: 'user-group-id-1')] as Set) @@ -423,8 +456,6 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { noExceptionThrown() then: - 1 * authorizer.getUsers() >> users - 1 * authorizer.getGroups() >> groups 1 * authorizer.doAddUser(user) >> user 0 * _ result?.equals user @@ -438,7 +469,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "GetUser: success"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -457,7 +488,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "GetUser: failure"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -472,7 +503,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "GetUsers: success"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -491,7 +522,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "UpdateUser: success"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) def requestDTO = new UserDTO(id: 'user-id-1', identity: 'user identity', userGroups: [new TenantEntity(id: 'user-group-id-1')] as Set) @@ -500,8 +531,6 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { then: 1 * authorizer.getUser(requestDTO.id) >> user - 1 * authorizer.getUsers() >> users - 1 * authorizer.getGroups() >> groups 1 * authorizer.doUpdateUser(user) >> user 0 * _ result?.equals(user) @@ -515,7 +544,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "UpdateUser: failure"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) def requestDTO = new UserDTO(id: 'user-id-1', identity: 'user identity', userGroups: [new TenantEntity(id: 'user-group-id-1')] as Set) @@ -531,7 +560,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "DeleteUser: success"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: @@ -540,6 +569,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { then: 1 * authorizer.getUser('user-id-1') >> user 1 * authorizer.deleteUser(user) >> user + 1 * authorizer.getAccessPolicies() >> [] 0 * _ result?.equals(user) @@ -551,7 +581,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification { @Unroll def "DeleteUser: failure"() { given: - def authorizer = Mock AbstractPolicyBasedAuthorizer + def authorizer = mockAuthorizer() def dao = new StandardPolicyBasedAuthorizerDAO(authorizer) when: diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestRemoteProcessGroupAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestRemoteProcessGroupAuditor.java index ea7fa7d41eaa..68be1cdce7ac 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestRemoteProcessGroupAuditor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestRemoteProcessGroupAuditor.java @@ -25,7 +25,7 @@ import org.apache.nifi.admin.service.AuditService; import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.authorization.user.NiFiUserDetails; -import org.apache.nifi.authorization.user.StandardNiFiUser; +import org.apache.nifi.authorization.user.StandardNiFiUser.Builder; import org.apache.nifi.groups.RemoteProcessGroup; import org.apache.nifi.remote.RemoteGroupPort; import org.apache.nifi.remote.protocol.SiteToSiteTransportProtocol; @@ -62,7 +62,7 @@ public void setup() { final SecurityContext securityContext = SecurityContextHolder.getContext(); final Authentication authentication = mock(Authentication.class); securityContext.setAuthentication(authentication); - final NiFiUser user = new StandardNiFiUser("user-id"); + final NiFiUser user = new Builder().identity("user-id").build(); final NiFiUserDetails userDetail = new NiFiUserDetails(user); when(authentication.getPrincipal()).thenReturn(userDetail); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java index 9933d4b75fe4..05e4451d1072 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java @@ -31,7 +31,7 @@ import org.apache.nifi.authorization.resource.ResourceFactory; import org.apache.nifi.authorization.resource.ResourceType; import org.apache.nifi.authorization.user.NiFiUserDetails; -import org.apache.nifi.authorization.user.StandardNiFiUser; +import org.apache.nifi.authorization.user.StandardNiFiUser.Builder; import org.apache.nifi.controller.FlowController; import org.apache.nifi.history.History; import org.apache.nifi.history.HistoryQuery; @@ -190,7 +190,7 @@ public void testGetUnknownAction() throws Exception { @Test public void testGetActionApprovedThroughAction() throws Exception { // set the user - final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(USER_1))); + final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_1).build())); SecurityContextHolder.getContext().setAuthentication(authentication); // get the action @@ -218,7 +218,7 @@ public boolean matches(Object o) { @Test(expected = AccessDeniedException.class) public void testGetActionDeniedDespiteControllerAccess() throws Exception { // set the user - final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(USER_2))); + final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_2).build())); SecurityContextHolder.getContext().setAuthentication(authentication); try { @@ -245,7 +245,7 @@ public boolean matches(Object o) { @Test public void testGetActionApprovedThroughController() throws Exception { // set the user - final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(USER_2))); + final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_2).build())); SecurityContextHolder.getContext().setAuthentication(authentication); // get the action @@ -273,7 +273,7 @@ public boolean matches(Object o) { @Test public void testGetActionsForUser1() throws Exception { // set the user - final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(USER_1))); + final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_1).build())); SecurityContextHolder.getContext().setAuthentication(authentication); final HistoryDTO dto = serviceFacade.getActions(new HistoryQueryDTO()); @@ -292,7 +292,7 @@ public void testGetActionsForUser1() throws Exception { @Test public void testGetActionsForUser2() throws Exception { // set the user - final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(USER_2))); + final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_2).build())); SecurityContextHolder.getContext().setAuthentication(authentication); final HistoryDTO dto = serviceFacade.getActions(new HistoryQueryDTO()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/test/java/org/apache/nifi/web/revision/TestNaiveRevisionManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/test/java/org/apache/nifi/web/revision/TestNaiveRevisionManager.java index a4ac9ebdba6d..6fa1865e0f55 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/test/java/org/apache/nifi/web/revision/TestNaiveRevisionManager.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/test/java/org/apache/nifi/web/revision/TestNaiveRevisionManager.java @@ -18,19 +18,19 @@ package org.apache.nifi.web.revision; -import java.util.HashSet; -import java.util.Set; - import org.apache.nifi.authorization.user.NiFiUser; -import org.apache.nifi.authorization.user.StandardNiFiUser; +import org.apache.nifi.authorization.user.StandardNiFiUser.Builder; import org.apache.nifi.web.FlowModification; import org.apache.nifi.web.Revision; +import java.util.HashSet; +import java.util.Set; + public class TestNaiveRevisionManager { private static final String CLIENT_1 = "client-1"; private static final String COMPONENT_1 = "component-1"; - private static final NiFiUser USER_1 = new StandardNiFiUser("user-1"); + private static final NiFiUser USER_1 = new Builder().identity("user-1").build(); private RevisionUpdate components(final Revision revision) { return new StandardRevisionUpdate(null, new FlowModification(revision, null)); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java index 62d0858585b1..5636c2dc3c18 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java @@ -16,8 +16,10 @@ */ package org.apache.nifi.web.security; +import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.authorization.util.IdentityMapping; import org.apache.nifi.authorization.util.IdentityMappingUtil; +import org.apache.nifi.authorization.util.UserGroupUtil; import org.apache.nifi.util.NiFiProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,6 +27,7 @@ import java.util.Collections; import java.util.List; +import java.util.Set; /** * Base AuthenticationProvider that provides common functionality to mapping identities. @@ -34,12 +37,13 @@ public abstract class NiFiAuthenticationProvider implements AuthenticationProvid private static final Logger LOGGER = LoggerFactory.getLogger(NiFiAuthenticationProvider.class); private NiFiProperties properties; + private Authorizer authorizer; private List mappings; /** * @param properties the NiFiProperties instance */ - public NiFiAuthenticationProvider(final NiFiProperties properties) { + public NiFiAuthenticationProvider(final NiFiProperties properties, final Authorizer authorizer) { this.properties = properties; this.mappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties)); } @@ -52,4 +56,7 @@ protected String mapIdentity(final String identity) { return IdentityMappingUtil.mapIdentity(identity, mappings); } + protected Set getUserGroups(final String identity) { + return UserGroupUtil.getUserGroups(authorizer, identity); + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProvider.java index 9b33f77475b4..075720dc7acd 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProvider.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProvider.java @@ -16,9 +16,11 @@ */ package org.apache.nifi.web.security.jwt; +import io.jsonwebtoken.JwtException; +import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.authorization.user.NiFiUserDetails; -import org.apache.nifi.authorization.user.StandardNiFiUser; +import org.apache.nifi.authorization.user.StandardNiFiUser.Builder; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.security.InvalidAuthenticationException; import org.apache.nifi.web.security.NiFiAuthenticationProvider; @@ -26,8 +28,6 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; -import io.jsonwebtoken.JwtException; - /** * */ @@ -35,8 +35,8 @@ public class JwtAuthenticationProvider extends NiFiAuthenticationProvider { private final JwtService jwtService; - public JwtAuthenticationProvider(JwtService jwtService, NiFiProperties nifiProperties) { - super(nifiProperties); + public JwtAuthenticationProvider(JwtService jwtService, NiFiProperties nifiProperties, Authorizer authorizer) { + super(nifiProperties, authorizer); this.jwtService = jwtService; } @@ -46,7 +46,8 @@ public Authentication authenticate(Authentication authentication) throws Authent try { final String jwtPrincipal = jwtService.getAuthenticationFromToken(request.getToken()); - final NiFiUser user = new StandardNiFiUser(mapIdentity(jwtPrincipal), request.getClientAddress()); + final String mappedIdentity = mapIdentity(jwtPrincipal); + final NiFiUser user = new Builder().identity(mappedIdentity).groups(getUserGroups(mappedIdentity)).clientAddress(request.getClientAddress()).build(); return new NiFiAuthenticationToken(new NiFiUserDetails(user)); } catch (JwtException e) { throw new InvalidAuthenticationException(e.getMessage(), e); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationProvider.java index 1e8825d92143..f375df205c6a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationProvider.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationProvider.java @@ -16,9 +16,10 @@ */ package org.apache.nifi.web.security.otp; +import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.authorization.user.NiFiUserDetails; -import org.apache.nifi.authorization.user.StandardNiFiUser; +import org.apache.nifi.authorization.user.StandardNiFiUser.Builder; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.security.InvalidAuthenticationException; import org.apache.nifi.web.security.NiFiAuthenticationProvider; @@ -33,8 +34,8 @@ public class OtpAuthenticationProvider extends NiFiAuthenticationProvider { private OtpService otpService; - public OtpAuthenticationProvider(OtpService otpService, NiFiProperties nifiProperties) { - super(nifiProperties); + public OtpAuthenticationProvider(OtpService otpService, NiFiProperties nifiProperties, Authorizer authorizer) { + super(nifiProperties, authorizer); this.otpService = otpService; } @@ -49,7 +50,8 @@ public Authentication authenticate(Authentication authentication) throws Authent } else { otpPrincipal = otpService.getAuthenticationFromUiExtensionToken(request.getToken()); } - final NiFiUser user = new StandardNiFiUser(mapIdentity(otpPrincipal), request.getClientAddress()); + final String mappedIdentity = mapIdentity(otpPrincipal); + final NiFiUser user = new Builder().identity(mappedIdentity).groups(getUserGroups(mappedIdentity)).clientAddress(request.getClientAddress()).build(); return new NiFiAuthenticationToken(new NiFiUserDetails(user)); } catch (OtpAuthenticationException e) { throw new InvalidAuthenticationException(e.getMessage(), e); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java index b5835d0fefa9..510e1362d417 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java @@ -16,11 +16,6 @@ */ package org.apache.nifi.web.security.x509; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.authentication.AuthenticationResponse; import org.apache.nifi.authorization.AuthorizationRequest; @@ -33,6 +28,7 @@ import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.authorization.user.NiFiUserDetails; import org.apache.nifi.authorization.user.StandardNiFiUser; +import org.apache.nifi.authorization.user.StandardNiFiUser.Builder; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.security.InvalidAuthenticationException; import org.apache.nifi.web.security.NiFiAuthenticationProvider; @@ -42,6 +38,13 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + /** * */ @@ -51,7 +54,7 @@ public class X509AuthenticationProvider extends NiFiAuthenticationProvider { private Authorizer authorizer; public X509AuthenticationProvider(final X509IdentityProvider certificateIdentityProvider, final Authorizer authorizer, final NiFiProperties nifiProperties) { - super(nifiProperties); + super(nifiProperties, authorizer); this.certificateIdentityProvider = certificateIdentityProvider; this.authorizer = authorizer; } @@ -70,7 +73,7 @@ public Authentication authenticate(Authentication authentication) throws Authent if (StringUtils.isBlank(request.getProxiedEntitiesChain())) { final String mappedIdentity = mapIdentity(authenticationResponse.getIdentity()); - return new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(mappedIdentity, request.getClientAddress()))); + return new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(mappedIdentity).groups(getUserGroups(mappedIdentity)).clientAddress(request.getClientAddress()).build())); } else { // build the entire proxy chain if applicable - final List proxyChain = new ArrayList<>(ProxiedEntitiesUtils.tokenizeProxiedEntitiesChain(request.getProxiedEntitiesChain())); @@ -89,10 +92,13 @@ public Authentication authenticate(Authentication authentication) throws Authent identity = mapIdentity(identity); } + final Set groups = getUserGroups(identity); + if (chainIter.hasPrevious()) { // authorize this proxy in order to authenticate this user final AuthorizationRequest proxyAuthorizationRequest = new AuthorizationRequest.Builder() .identity(identity) + .groups(groups) .anonymous(isAnonymous) .accessAttempt(true) .action(RequestAction.WRITE) @@ -108,7 +114,7 @@ public Authentication authenticate(Authentication authentication) throws Authent // Only set the client address for user making the request because we don't know the client address of the proxies String clientAddress = (proxy == null) ? request.getClientAddress() : null; - proxy = createUser(identity, proxy, clientAddress, isAnonymous); + proxy = createUser(identity, groups, proxy, clientAddress, isAnonymous); } return new NiFiAuthenticationToken(new NiFiUserDetails(proxy)); @@ -124,11 +130,11 @@ public Authentication authenticate(Authentication authentication) throws Authent * @param isAnonymous if true, an anonymous user will be returned (identity will be ignored) * @return the populated user */ - protected static NiFiUser createUser(String identity, NiFiUser chain, String clientAddress, boolean isAnonymous) { + protected static NiFiUser createUser(String identity, Set groups, NiFiUser chain, String clientAddress, boolean isAnonymous) { if (isAnonymous) { return StandardNiFiUser.populateAnonymousUser(chain, clientAddress); } else { - return new StandardNiFiUser(identity, chain, clientAddress); + return new Builder().identity(identity).groups(groups).chain(chain).clientAddress(clientAddress).build(); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml index ff1aff167f39..369b33cc1534 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml @@ -43,7 +43,7 @@ - + @@ -53,8 +53,9 @@ - - + + + @@ -62,8 +63,9 @@ - - + + + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/NiFiAuthenticationProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/NiFiAuthenticationProviderTest.java index eb89c22c9d93..479034a077a4 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/NiFiAuthenticationProviderTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/NiFiAuthenticationProviderTest.java @@ -16,10 +16,10 @@ */ package org.apache.nifi.web.security; +import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.authorization.util.IdentityMapping; import org.apache.nifi.util.NiFiProperties; import org.junit.Test; -import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.springframework.security.core.Authentication; @@ -30,6 +30,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class NiFiAuthenticationProviderTest { @@ -169,7 +170,7 @@ public void testMapIdentityWithMultipleMatchingPatterns() { } private NiFiProperties getNiFiProperties(final Properties properties) { - final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class); + final NiFiProperties nifiProperties = mock(NiFiProperties.class); when(nifiProperties.getPropertyKeys()).thenReturn(properties.stringPropertyNames()); when(nifiProperties.getProperty(anyString())).then(new Answer() { @@ -186,7 +187,7 @@ private static class TestableNiFiAuthenticationProvider extends NiFiAuthenticati * @param properties the NiFiProperties instance */ public TestableNiFiAuthenticationProvider(NiFiProperties properties) { - super(properties); + super(properties, mock(Authorizer.class)); } @Override diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpAuthenticationProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpAuthenticationProviderTest.java index 1b5f4475dbaa..1b649e8affaf 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpAuthenticationProviderTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpAuthenticationProviderTest.java @@ -16,12 +16,12 @@ */ package org.apache.nifi.web.security.otp; +import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.authorization.user.NiFiUserDetails; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.security.token.NiFiAuthenticationToken; import org.junit.Before; import org.junit.Test; -import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -75,7 +75,7 @@ public String answer(InvocationOnMock invocation) throws Throwable { } }).when(otpService).getAuthenticationFromUiExtensionToken(anyString()); - otpAuthenticationProvider = new OtpAuthenticationProvider(otpService, Mockito.mock(NiFiProperties.class)); + otpAuthenticationProvider = new OtpAuthenticationProvider(otpService, mock(NiFiProperties.class), mock(Authorizer.class)); } @Test diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java index 43aea8645a65..70df64900dff 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java @@ -16,20 +16,6 @@ */ package org.apache.nifi.web.security.x509; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.security.Principal; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.authentication.AuthenticationResponse; import org.apache.nifi.authorization.AuthorizationRequest; @@ -45,6 +31,21 @@ import org.junit.Before; import org.junit.Test; +import java.security.Principal; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class X509AuthenticationProviderTest { private static final String INVALID_CERTIFICATE = "invalid-certificate"; @@ -190,7 +191,7 @@ public void testShouldCreateAnonymousUser() { String identity = "someone"; // Act - NiFiUser user = X509AuthenticationProvider.createUser(identity, null, null, true); + NiFiUser user = X509AuthenticationProvider.createUser(identity, null, null, null, true); // Assert assert user != null; @@ -205,7 +206,7 @@ public void testShouldCreateKnownUser() { String identity = "someone"; // Act - NiFiUser user = X509AuthenticationProvider.createUser(identity, null, null, false); + NiFiUser user = X509AuthenticationProvider.createUser(identity, null, null, null, false); // Assert assert user != null; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp index c7064f8ff7bc..13e214650ae4 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp @@ -146,15 +146,15 @@ Flow Configuration History - - + + Users - + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp index 6d97b0e7f14d..0732b3df473a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp @@ -95,8 +95,8 @@ ng-disabled="!(appCtrl.serviceProvider.graphControlsCtrl.canConfigureOrOpenDetails())">
-
 
-
+
 
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css index 0e92a9c16f75..667291da0c38 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css @@ -35,12 +35,14 @@ #delete-policy-button { margin-top: -4px; + display: none; } #new-policy-user-button { margin-top: -4px; margin-right: 5px; padding-left: 5px; + display: none; } button.policy-button { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/users.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/users.css index f380c89dbaa6..dc2a4046c2a1 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/users.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/users.css @@ -67,6 +67,7 @@ #new-user-button { float: right; padding-left: 5px; + display: none; } /* users table */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js index cef0b49b14ec..2fa8a2b9df15 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js @@ -325,7 +325,9 @@ var autoRefreshIntervalSeconds = parseInt(configDetails.autoRefreshIntervalSeconds, 10); // record whether we can configure the authorizer + nfCanvas.setManagedAuthorizer(configDetails.supportsManagedAuthorizer); nfCanvas.setConfigurableAuthorizer(configDetails.supportsConfigurableAuthorizer); + nfCanvas.setConfigurableUsersAndGroups(configDetails.supportsConfigurableUsersAndGroups); // init nfStorage nfStorage.init(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js index efa21c4b83ce..54f1d14a1331 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js @@ -1725,6 +1725,13 @@ } }, + /** + * Returns whether the authorizer is managed. + */ + isManagedAuthorizer: function () { + return nfCanvas.isManagedAuthorizer(); + }, + /** * Returns whether the authorizer is configurable. */ @@ -1732,6 +1739,13 @@ return nfCanvas.isConfigurableAuthorizer(); }, + /** + * Returns whether the authorizer support configurable users and groups. + */ + isConfigurableUsersAndGroups: function () { + return nfCanvas.isConfigurableUsersAndGroups(); + }, + /** * Set the group id. * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js index ecb126931391..018044173151 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js @@ -84,7 +84,9 @@ var groupName = null; var permissions = null; var parentGroupId = null; + var managedAuthorizer = false; var configurableAuthorizer = false; + var configurableUsersAndGroups = false; var svg = null; var canvas = null; @@ -877,6 +879,22 @@ return parentGroupId; }, + /** + * Set whether the authorizer is managed. + * + * @param bool The boolean value representing whether the authorizer is managed + */ + setManagedAuthorizer: function (bool) { + managedAuthorizer = bool; + }, + + /** + * Returns whether the authorizer is managed. + */ + isManagedAuthorizer: function () { + return managedAuthorizer; + }, + /** * Set whether the authorizer is configurable. * @@ -893,6 +911,22 @@ return configurableAuthorizer; }, + /** + * Set whether the users and groups is configurable. + * + * @param bool The boolean value representing whether the users and groups is configurable. + */ + setConfigurableUsersAndGroups: function(bool){ + configurableUsersAndGroups = bool; + }, + + /** + * Returns whether the users and groups is configurable. + */ + isConfigurableUsersAndGroups: function () { + return configurableUsersAndGroups; + }, + /** * Whether the current user can read from this group. * 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 1c16626fa847..c5e05f942e61 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 @@ -133,7 +133,7 @@ * @param {selection} selection The selection of currently selected components */ var canManagePolicies = function (selection) { - return nfCanvasUtils.isConfigurableAuthorizer() && nfCanvasUtils.canManagePolicies(selection); + return nfCanvasUtils.isManagedAuthorizer() && nfCanvasUtils.canManagePolicies(selection); }; /** diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js index ef98d38f5c78..a4fb774ff66c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js @@ -882,7 +882,7 @@ } // allow policy configuration conditionally - if (nfCanvasUtils.isConfigurableAuthorizer() && nfCommon.canAccessTenants()) { + if (nfCanvasUtils.isManagedAuthorizer() && nfCommon.canAccessTenants()) { markup += '
'; } 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 c4c3cd80f33c..c4ae1baed2a3 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 @@ -567,8 +567,11 @@ sortable: true, resizable: true, formatter: identityFormatter - }, - { + } + ]; + + if (nfCanvasUtils.isConfigurableAuthorizer()) { + usersColumns.push({ id: 'actions', name: ' ', sortable: false, @@ -576,8 +579,8 @@ formatter: actionFormatter, width: 100, maxWidth: 100 - } - ]; + }); + } var usersOptions = { forceFitColumns: true, @@ -874,20 +877,24 @@ // 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', policyEntity.permissions.canWrite === false); + if (nfCanvasUtils.isConfigurableAuthorizer()) { + // allow remove when policy is not inherited + $('#delete-policy-button').prop('disabled', policyEntity.permissions.canWrite === false); - // allow modification if allowed - $('#new-policy-user-button').prop('disabled', policyEntity.permissions.canWrite === false); + // allow modification if allowed + $('#new-policy-user-button').prop('disabled', policyEntity.permissions.canWrite === false); + } } else { $('#policy-message').append(getResourceMessage(policy.resource)); - // 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(); + if (nfCanvasUtils.isConfigurableAuthorizer()) { + // 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(); - // do not support policy deletion/modification - $('#delete-policy-button').prop('disabled', true); - $('#new-policy-user-button').prop('disabled', true); + // do not support policy deletion/modification + $('#delete-policy-button').prop('disabled', true); + $('#new-policy-user-button').prop('disabled', true); + } } // populate the table @@ -928,8 +935,10 @@ // show an appropriate message $('#policy-message').text('No component specific administrators.'); - // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide - $('#add-local-admin-message').show(); + if (nfCanvasUtils.isConfigurableAuthorizer()) { + // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide + $('#add-local-admin-message').show(); + } } } else { // reset the policy @@ -938,8 +947,10 @@ // show an appropriate message $('#policy-message').text('No component specific administrators.'); - // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide - $('#add-local-admin-message').show(); + if (nfCanvasUtils.isConfigurableAuthorizer()) { + // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide + $('#add-local-admin-message').show(); + } } deferred.resolve(); @@ -951,8 +962,10 @@ // show an appropriate message $('#policy-message').text('No component specific administrators.'); - // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide - $('#add-local-admin-message').show(); + if (nfCanvasUtils.isConfigurableAuthorizer()) { + // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide + $('#add-local-admin-message').show(); + } deferred.resolve(); } else if (xhr.status === 403) { @@ -997,8 +1010,10 @@ // 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(); + if (nfCanvasUtils.isConfigurableAuthorizer()) { + // allow option to override because we don't know if it's supported or not + $('#override-policy-message').show(); + } } deferred.resolve(); @@ -1010,8 +1025,10 @@ // 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(); + if (nfCanvasUtils.isConfigurableAuthorizer()) { + // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide + $('#new-policy-message').show(); + } deferred.resolve(); } else if (xhr.status === 403) { @@ -1183,9 +1200,11 @@ */ var resetPolicyMessage = function () { $('#policy-message').text('').empty(); - $('#new-policy-message').hide(); - $('#override-policy-message').hide(); - $('#add-local-admin-message').hide(); + if (nfCanvasUtils.isConfigurableAuthorizer()) { + $('#new-policy-message').hide(); + $('#override-policy-message').hide(); + $('#add-local-admin-message').hide(); + } }; /** @@ -1194,9 +1213,11 @@ var resetPolicy = function () { resetPolicyMessage(); - // reset button state - $('#delete-policy-button').prop('disabled', true); - $('#new-policy-user-button').prop('disabled', true); + if (nfCanvasUtils.isConfigurableAuthorizer()) { + // reset button state + $('#delete-policy-button').prop('disabled', true); + $('#new-policy-user-button').prop('disabled', true); + } // reset the current policy $('#policy-table').removeData('policy'); @@ -1229,6 +1250,11 @@ initAddTenantToPolicyDialog(); initPolicyTable(); + if (nfCanvasUtils.isConfigurableAuthorizer()) { + $('#delete-policy-button').show(); + $('#new-policy-user-button').show(); + } + $('#policy-refresh-button').on('click', function () { loadPolicy(); }); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js index 075f7128d37c..3f346f5d9fba 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js @@ -871,7 +871,7 @@ } // allow policy configuration conditionally - if (nfCanvasUtils.isConfigurableAuthorizer() && nfCommon.canAccessTenants()) { + if (nfCanvasUtils.isManagedAuthorizer() && nfCommon.canAccessTenants()) { markup += '
'; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js index c942bdb6c0cf..89c7cf2372ae 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js @@ -293,7 +293,7 @@ // allow policy configuration conditionally if embedded in if (top !== window && nfCommon.canAccessTenants()) { - if (nfCommon.isDefinedAndNotNull(parent.nf) && nfCommon.isDefinedAndNotNull(parent.nf.CanvasUtils) && parent.nf.CanvasUtils.isConfigurableAuthorizer()) { + if (nfCommon.isDefinedAndNotNull(parent.nf) && nfCommon.isDefinedAndNotNull(parent.nf.CanvasUtils) && parent.nf.CanvasUtils.isManagedAuthorizer()) { markup += '
'; } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js index 76232346616a..14c8fe766de1 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js @@ -750,7 +750,7 @@ /** * Initializes the processor list. */ - var initUsersTable = function () { + var initUsersTable = function (configurableUsersAndGroups) { // define the function for filtering the list $('#users-filter').keyup(function () { applyFilter(); @@ -797,7 +797,7 @@ var markup = ''; // ensure user can modify the user - if (nfCommon.canModifyTenants()) { + if (configurableUsersAndGroups && nfCommon.canModifyTenants()) { markup += '
'; markup += '
'; } @@ -1215,28 +1215,32 @@ }; var nfUsersTable = { - init: function () { + init: function (configurableUsersAndGroups) { initUserDialog(); initUserPoliciesDialog(); initUserPoliciesTable(); initUserDeleteDialog(); - initUsersTable(); + initUsersTable(configurableUsersAndGroups); - if (nfCommon.canModifyTenants()) { - $('#new-user-button').on('click', function () { - buildUsersList(); - buildGroupsList(); + if (configurableUsersAndGroups) { + $('#new-user-button').show(); - // show the dialog - $('#user-dialog').modal('show'); + if (nfCommon.canModifyTenants()) { + $('#new-user-button').on('click', function () { + buildUsersList(); + buildGroupsList(); - // set the focus automatically, only when adding a new user - $('#user-identity-edit-dialog').focus(); - }); + // show the dialog + $('#user-dialog').modal('show'); - $('#new-user-button').prop('disabled', false); - } else { - $('#new-user-button').prop('disabled', true); + // set the focus automatically, only when adding a new user + $('#user-identity-edit-dialog').focus(); + }); + + $('#new-user-button').prop('disabled', false); + } else { + $('#new-user-button').prop('disabled', true); + } } }, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users.js index e39e160c08cc..6b53ce87c092 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users.js @@ -66,7 +66,8 @@ urls: { banners: '../nifi-api/flow/banners', controllerAbout: '../nifi-api/flow/about', - currentUser: '../nifi-api/flow/current-user' + currentUser: '../nifi-api/flow/current-user', + flowConfig: '../nifi-api/flow/config' } }; @@ -83,6 +84,14 @@ }).fail(nfErrorHandler.handleAjaxError); }; + var getFlowConfig = function () { + return $.ajax({ + type: 'GET', + url: config.urls.flowConfig, + dataType: 'json' + }).fail(nfErrorHandler.handleAjaxError); + } + var initializeUsersPage = function () { // define mouse over event for the refresh button nfCommon.addHoverEffect('#user-refresh-button', 'button-refresh', 'button-refresh-hover').click(function () { @@ -149,9 +158,12 @@ nfClient.init(); // load the users authorities - ensureAccess().done(function () { + $.when(getFlowConfig(), ensureAccess()).done(function (configResult) { + var configResponse = configResult[0]; + var configDetails = configResponse.flowConfiguration; + // create the counters table - nfUsersTable.init(); + nfUsersTable.init(configDetails.supportsConfigurableUsersAndGroups); // load the users table nfUsersTable.loadUsersTable().done(function () { diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/TestPersistentProvenanceRepository.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/TestPersistentProvenanceRepository.java index 5eeb7dec1d31..f031710e58f5 100644 --- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/TestPersistentProvenanceRepository.java +++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/TestPersistentProvenanceRepository.java @@ -16,41 +16,6 @@ */ package org.apache.nifi.provenance; -import static org.apache.nifi.provenance.TestUtil.createFlowFile; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; - -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.Collectors; -import java.util.zip.GZIPOutputStream; - import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.core.SimpleAnalyzer; import org.apache.lucene.document.Document; @@ -90,7 +55,6 @@ import org.apache.nifi.util.file.FileUtils; import org.junit.After; import org.junit.Assert; -import static org.junit.Assume.assumeFalse; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -102,6 +66,42 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; +import java.util.zip.GZIPOutputStream; + +import static org.apache.nifi.provenance.TestUtil.createFlowFile; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; +import static org.mockito.Mockito.mock; + public class TestPersistentProvenanceRepository { @Rule @@ -2272,6 +2272,11 @@ public String getIdentity() { return "unit-test"; } + @Override + public Set getGroups() { + return Collections.EMPTY_SET; + } + @Override public NiFiUser getChain() { return null; diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/index/lucene/TestLuceneEventIndex.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/index/lucene/TestLuceneEventIndex.java index 3079b871096f..44c5402d2c7e 100644 --- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/index/lucene/TestLuceneEventIndex.java +++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/index/lucene/TestLuceneEventIndex.java @@ -16,23 +16,6 @@ */ package org.apache.nifi.provenance.index.lucene; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.Collectors; - import org.apache.nifi.authorization.AccessDeniedException; import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.events.EventReporter; @@ -56,7 +39,6 @@ import org.apache.nifi.provenance.store.ArrayListEventStore; import org.apache.nifi.provenance.store.EventStore; import org.apache.nifi.provenance.store.StorageResult; -import static org.junit.Assume.assumeFalse; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; @@ -65,6 +47,25 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; + public class TestLuceneEventIndex { private final AtomicLong idGenerator = new AtomicLong(0L); @@ -343,6 +344,11 @@ public String getIdentity() { return "unit test"; } + @Override + public Set getGroups() { + return Collections.EMPTY_SET; + } + @Override public NiFiUser getChain() { return null; diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java index 942fea4994c0..5ccf6eab4cc7 100644 --- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java +++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java @@ -26,9 +26,11 @@ import org.junit.Test; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import static org.junit.Assert.assertEquals; @@ -185,6 +187,11 @@ public String getIdentity() { return "unit-test"; } + @Override + public Set getGroups() { + return Collections.EMPTY_SET; + } + @Override public NiFiUser getChain() { return null; diff --git a/pom.xml b/pom.xml index 8794abd6e154..7985908dd469 100644 --- a/pom.xml +++ b/pom.xml @@ -1718,7 +1718,7 @@ org.codehaus.mojo jaxb2-maven-plugin - 1.6 + 2.3.1 org.apache.maven.plugins From 4f7f4f9785671889de4eb8d46d7ad18dfce8c1a9 Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Fri, 9 Jun 2017 11:15:00 -0400 Subject: [PATCH 2/2] NIFI-3653: - Adding methods to the User, Group, and AccessPolicy builder that more easily supports generating UUIDs. - Fixing typo when seeding policies during startup. - Fixing type in documentation and error messages. --- .../main/asciidoc/administration-guide.adoc | 2 +- .../nifi/authorization/AccessPolicy.java | 37 ++++++++++++++++ .../org/apache/nifi/authorization/Group.java | 37 ++++++++++++++++ .../org/apache/nifi/authorization/User.java | 37 ++++++++++++++++ .../FileAccessPolicyProvider.java | 43 +++++++++---------- .../FileUserGroupProviderTest.java | 6 +-- .../controller/StandardFlowSynchronizer.java | 2 +- .../src/main/resources/conf/authorizers.xml | 2 +- 8 files changed, 138 insertions(+), 28 deletions(-) diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc index 95697c681b4e..097f194fa8d1 100644 --- a/nifi-docs/src/main/asciidoc/administration-guide.adoc +++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc @@ -398,7 +398,7 @@ The default UserGroupProvider is the FileUserGroupProvider, however, you can dev The default AccessPolicyProvider is the FileAccessPolicyProvider, however, you can develop additional AccessPolicyProvider as extensions. The FileAccessPolicyProvider has the following properties: * User Group Provider - The identifier for an User Group Provider defined above that will be used to access users and groups for use in the managed access policies. -* Authorizations File - The file where the FileAuthorizer will store policies. +* Authorizations File - The file where the FileAccessPolicyProvider will store policies. * Initial Admin Identity - The identity of an initial admin user that will be granted access to the UI and given the ability to create additional users, groups, and policies. The value of this property could be a DN when using certificates or LDAP, or a Kerberos principal. This property will only be used when there are no other policies defined. If this property is specified then a Legacy Authorized Users File can not be specified. * Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically converted to the new authorizations model. If this property is specified then an Initial Admin Identity can not be specified, and this property will only be used when there are no other users, groups, and policies defined. * Node Identity - The identity of a NiFi cluster node. When clustered, a property for each node should be defined, so that every node knows about every other node. If not clustered these properties can be ignored. The name of each property must be unique, for example for a three node cluster: "Node Identity A", "Node Identity B", "Node Identity C" or "Node Identity 1", "Node Identity 2", "Node Identity 3" diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicy.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicy.java index 93cabb2aa381..1a7f751945db 100644 --- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicy.java +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicy.java @@ -16,10 +16,12 @@ */ package org.apache.nifi.authorization; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashSet; import java.util.Objects; import java.util.Set; +import java.util.UUID; /** * Defines a policy for a set of userIdentifiers to perform a set of actions on a given resource. @@ -173,6 +175,41 @@ public Builder identifier(final String identifier) { return this; } + /** + * Sets the identifier of the builder to a random UUID. + * + * @return the builder + * @throws IllegalStateException if this method is called when this builder was constructed from an existing Policy + */ + public Builder identifierGenerateRandom() { + if (fromPolicy) { + throw new IllegalStateException( + "Identifier can not be changed when initialized from an existing policy"); + } + + this.identifier = UUID.randomUUID().toString(); + return this; + } + + /** + * Sets the identifier of the builder with a UUID generated from the specified seed string. + * + * @return the builder + * @throws IllegalStateException if this method is called when this builder was constructed from an existing Policy + */ + public Builder identifierGenerateFromSeed(final String seed) { + if (fromPolicy) { + throw new IllegalStateException( + "Identifier can not be changed when initialized from an existing policy"); + } + if (seed == null) { + throw new IllegalArgumentException("Cannot seed the policy identifier with a null value."); + } + + this.identifier = UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString(); + return this; + } + /** * Sets the resource of the builder. * diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Group.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Group.java index 7db619a8f7fa..7908e856e003 100644 --- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Group.java +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Group.java @@ -16,10 +16,12 @@ */ package org.apache.nifi.authorization; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashSet; import java.util.Objects; import java.util.Set; +import java.util.UUID; /** * A group that users can belong to. @@ -141,6 +143,41 @@ public Builder identifier(final String identifier) { return this; } + /** + * Sets the identifier of the builder to a random UUID. + * + * @return the builder + * @throws IllegalStateException if this method is called when this builder was constructed from an existing Group + */ + public Builder identifierGenerateRandom() { + if (fromGroup) { + throw new IllegalStateException( + "Identifier can not be changed when initialized from an existing group"); + } + + this.identifier = UUID.randomUUID().toString(); + return this; + } + + /** + * Sets the identifier of the builder with a UUID generated from the specified seed string. + * + * @return the builder + * @throws IllegalStateException if this method is called when this builder was constructed from an existing Group + */ + public Builder identifierGenerateFromSeed(final String seed) { + if (fromGroup) { + throw new IllegalStateException( + "Identifier can not be changed when initialized from an existing group"); + } + if (seed == null) { + throw new IllegalArgumentException("Cannot seed the group identifier with a null value."); + } + + this.identifier = UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString(); + return this; + } + /** * Sets the name of the builder. * diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/User.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/User.java index 371241b95776..83368eaf5945 100644 --- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/User.java +++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/User.java @@ -16,7 +16,9 @@ */ package org.apache.nifi.authorization; +import java.nio.charset.StandardCharsets; import java.util.Objects; +import java.util.UUID; /** * A user to create authorization policies for. @@ -128,6 +130,41 @@ public Builder identifier(final String identifier) { return this; } + /** + * Sets the identifier of the builder to a random UUID. + * + * @return the builder + * @throws IllegalStateException if this method is called when this builder was constructed from an existing User + */ + public Builder identifierGenerateRandom() { + if (fromUser) { + throw new IllegalStateException( + "Identifier can not be changed when initialized from an existing user"); + } + + this.identifier = UUID.randomUUID().toString(); + return this; + } + + /** + * Sets the identifier of the builder with a UUID generated from the specified seed string. + * + * @return the builder + * @throws IllegalStateException if this method is called when this builder was constructed from an existing User + */ + public Builder identifierGenerateFromSeed(final String seed) { + if (fromUser) { + throw new IllegalStateException( + "Identifier can not be changed when initialized from an existing user"); + } + if (seed == null) { + throw new IllegalArgumentException("Cannot seed the user identifier with a null value."); + } + + this.identifier = UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString(); + return this; + } + /** * Sets the identity of the builder. * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java index 6a6b0593a417..653a94997eb0 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java @@ -560,31 +560,31 @@ private void populateInitialAdmin(final Authorizations authorizations) { } // grant the user read access to the /flow resource - addAccessPolicy(authorizations, ResourceType.Flow.getValue(), initialAdmin.getIdentifier(), READ_CODE); + addUserToAccessPolicy(authorizations, ResourceType.Flow.getValue(), initialAdmin.getIdentifier(), READ_CODE); // grant the user read access to the root process group resource if (rootGroupId != null) { - addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), READ_CODE); - addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), WRITE_CODE); + addUserToAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), READ_CODE); + addUserToAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), WRITE_CODE); - addAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), READ_CODE); - addAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), WRITE_CODE); + addUserToAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), READ_CODE); + addUserToAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), WRITE_CODE); } // grant the user write to restricted components - addAccessPolicy(authorizations, ResourceType.RestrictedComponents.getValue(), initialAdmin.getIdentifier(), WRITE_CODE); + addUserToAccessPolicy(authorizations, ResourceType.RestrictedComponents.getValue(), initialAdmin.getIdentifier(), WRITE_CODE); // grant the user read/write access to the /tenants resource - addAccessPolicy(authorizations, ResourceType.Tenant.getValue(), initialAdmin.getIdentifier(), READ_CODE); - addAccessPolicy(authorizations, ResourceType.Tenant.getValue(), initialAdmin.getIdentifier(), WRITE_CODE); + addUserToAccessPolicy(authorizations, ResourceType.Tenant.getValue(), initialAdmin.getIdentifier(), READ_CODE); + addUserToAccessPolicy(authorizations, ResourceType.Tenant.getValue(), initialAdmin.getIdentifier(), WRITE_CODE); // grant the user read/write access to the /policies resource - addAccessPolicy(authorizations, ResourceType.Policy.getValue(), initialAdmin.getIdentifier(), READ_CODE); - addAccessPolicy(authorizations, ResourceType.Policy.getValue(), initialAdmin.getIdentifier(), WRITE_CODE); + addUserToAccessPolicy(authorizations, ResourceType.Policy.getValue(), initialAdmin.getIdentifier(), READ_CODE); + addUserToAccessPolicy(authorizations, ResourceType.Policy.getValue(), initialAdmin.getIdentifier(), WRITE_CODE); // grant the user read/write access to the /controller resource - addAccessPolicy(authorizations, ResourceType.Controller.getValue(), initialAdmin.getIdentifier(), READ_CODE); - addAccessPolicy(authorizations, ResourceType.Controller.getValue(), initialAdmin.getIdentifier(), WRITE_CODE); + addUserToAccessPolicy(authorizations, ResourceType.Controller.getValue(), initialAdmin.getIdentifier(), READ_CODE); + addUserToAccessPolicy(authorizations, ResourceType.Controller.getValue(), initialAdmin.getIdentifier(), WRITE_CODE); } /** @@ -600,12 +600,12 @@ private void populateNodes(Authorizations authorizations) { } // grant access to the proxy resource - addAccessPolicy(authorizations, ResourceType.Proxy.getValue(), node.getIdentifier(), WRITE_CODE); + addUserToAccessPolicy(authorizations, ResourceType.Proxy.getValue(), node.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, node.getIdentifier(), READ_CODE); - addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, node.getIdentifier(), WRITE_CODE); + addUserToAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, node.getIdentifier(), READ_CODE); + addUserToAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, node.getIdentifier(), WRITE_CODE); } } } @@ -750,10 +750,10 @@ private void convertLegacyAuthorizedUsers(final Authorizations authorizations) t * * @param authorizations the Authorizations instance to add the policy to * @param resource the resource for the policy - * @param identity the identity for the policy + * @param userIdentifier the identifier for the user to add to the policy * @param action the action for the policy */ - private void addAccessPolicy(final Authorizations authorizations, final String resource, final String identity, final String action) { + private void addUserToAccessPolicy(final Authorizations authorizations, final String resource, final String userIdentifier, final String action) { // first try to find an existing policy for the given resource and action Policy foundPolicy = null; for (Policy policy : authorizations.getPolicies().getPolicy()) { @@ -765,13 +765,12 @@ private void addAccessPolicy(final Authorizations authorizations, final String r if (foundPolicy == null) { // if we didn't find an existing policy create a new one - final String uuidSeed = resource + identity + action; - final String policyIdentifier = IdentifierUtil.getIdentifier(uuidSeed); + final String uuidSeed = resource + action; final AccessPolicy.Builder builder = new AccessPolicy.Builder() - .identifier(policyIdentifier) + .identifierGenerateFromSeed(uuidSeed) .resource(resource) - .addUser(identity); + .addUser(userIdentifier); if (action.equals(READ_CODE)) { builder.action(RequestAction.READ); @@ -787,7 +786,7 @@ private void addAccessPolicy(final Authorizations authorizations, final String r } else { // otherwise add the user to the existing policy Policy.User policyUser = new Policy.User(); - policyUser.setIdentifier(identity); + policyUser.setIdentifier(userIdentifier); foundPolicy.getUser().add(policyUser); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java index d10d9760acb5..8b91f78e59dc 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java @@ -264,9 +264,9 @@ public void testOnConfiguredWhenInitialUsersProvided() throws Exception { final Set users = userGroupProvider.getUsers(); assertEquals(3, users.size()); - assertTrue(users.contains(new User.Builder().identifier(IdentifierUtil.getIdentifier(adminIdentity)).identity(adminIdentity).build())); - assertTrue(users.contains(new User.Builder().identifier(IdentifierUtil.getIdentifier(nodeIdentity1)).identity(nodeIdentity1).build())); - assertTrue(users.contains(new User.Builder().identifier(IdentifierUtil.getIdentifier(nodeIdentity2)).identity(nodeIdentity2).build())); + assertTrue(users.contains(new User.Builder().identifierGenerateFromSeed(adminIdentity).identity(adminIdentity).build())); + assertTrue(users.contains(new User.Builder().identifierGenerateFromSeed(nodeIdentity1).identity(nodeIdentity1).build())); + assertTrue(users.contains(new User.Builder().identifierGenerateFromSeed(nodeIdentity2).identity(nodeIdentity2).build())); } @Test diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java index 12920a6f0ec1..01dd35edffaa 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java @@ -1424,7 +1424,7 @@ private AuthorizerInheritability checkAuthorizerInheritability(final Authorizer managedAuthorizer.checkInheritability(new String(proposed, StandardCharsets.UTF_8)); return AuthorizerInheritability.inheritable(); } catch (final UninheritableAuthorizationsException e) { - return AuthorizerInheritability.uninheritable(e.getMessage()); + return AuthorizerInheritability.uninheritable("Proposed Authorizations do not match current Authorizations: " + e.getMessage()); } } else { // should never hit since the existing is only null when authorizer is not managed diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml index 652afae88541..247c0e8667d8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml @@ -59,7 +59,7 @@ - User Group Provider - The identifier for an User Group Provider defined above that will be used to access users and groups for use in the managed access policies. - - Authorizations File - The file where the FileAuthorizer will store policies. + - Authorizations File - The file where the FileAccessPolicyProvider will store policies. - Initial Admin Identity - The identity of an initial admin user that will be granted access to the UI and given the ability to create additional users, groups, and policies. The value of this property could be