Repository: nifi-registry Updated Branches: refs/heads/master c6aaca673 -> 1755d8400
NIFIREG-134 Enable SpringBoot Actuator endpoints - Configures Jersey as a filter (previously was a servlet) that forwards requests to /actuator/* so they can be handled by Actuator - Adds a ResourceAuthorizationFilter that performs authorization in the filter chain, and configures it to gate access to /actuator/* - Adds test cases for ResourceAuthorizationFilter This closes #97. Signed-off-by: Bryan Bende <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/nifi-registry/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi-registry/commit/1755d840 Tree: http://git-wip-us.apache.org/repos/asf/nifi-registry/tree/1755d840 Diff: http://git-wip-us.apache.org/repos/asf/nifi-registry/diff/1755d840 Branch: refs/heads/master Commit: 1755d840052645c916916438649566fc04eeaf2e Parents: c6aaca6 Author: Kevin Doran <[email protected]> Authored: Thu Jan 25 09:14:53 2018 -0500 Committer: Bryan Bende <[email protected]> Committed: Mon Mar 12 11:19:22 2018 -0400 ---------------------------------------------------------------------- .../authorization/AuthorizableLookup.java | 9 + .../security/authorization/AuthorizeAccess.java | 21 -- .../StandardAuthorizableLookup.java | 65 ++++-- .../file/FileAccessPolicyProvider.java | 3 + .../authorization/resource/ResourceFactory.java | 71 +++--- .../authorization/resource/ResourceType.java | 37 +++- .../registry/service/AuthorizationService.java | 16 +- .../service/AuthorizationServiceSpec.groovy | 16 +- nifi-registry-web-api/pom.xml | 18 ++ .../registry/NiFiRegistryApiApplication.java | 21 +- .../web/NiFiRegistryResourceConfig.java | 4 + .../registry/web/api/AccessPolicyResource.java | 12 +- .../api/AuthorizableApplicationResource.java | 14 +- .../registry/web/api/BucketFlowResource.java | 6 +- .../nifi/registry/web/api/BucketResource.java | 13 +- .../nifi/registry/web/api/FlowResource.java | 10 +- .../nifi/registry/web/api/ItemResource.java | 6 +- .../nifi/registry/web/api/TenantResource.java | 16 +- .../security/NiFiRegistrySecurityConfig.java | 29 ++- .../IdentityAuthenticationFilter.java | 148 ------------- .../security/authentication/IdentityFilter.java | 4 +- .../HttpMethodAuthorizationRules.java | 48 ++++ .../ResourceAuthorizationFilter.java | 218 +++++++++++++++++++ .../StandardHttpMethodAuthorizationRules.java | 40 ++++ .../ResourceAuthorizationFilterSpec.groovy | 170 +++++++++++++++ .../nifi/registry/web/api/SecureFileIT.java | 1 + 26 files changed, 713 insertions(+), 303 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizableLookup.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizableLookup.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizableLookup.java index 2ba7227..1842199 100644 --- a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizableLookup.java +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizableLookup.java @@ -21,6 +21,13 @@ import org.apache.nifi.registry.security.authorization.resource.Authorizable; public interface AuthorizableLookup { /** + * Get the authorizable for /actuator. + * + * @return authorizable + */ + Authorizable getActuatorAuthorizable(); + + /** * Get the authorizable for /proxy. * * @return authorizable @@ -59,6 +66,8 @@ public interface AuthorizableLookup { /** * Get the authorizable of the specified resource. + * If the resource is authorized by its base/top-level + * resource type, the authorizable for the base type will be returned. * * @param resource resource * @return authorizable http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizeAccess.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizeAccess.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizeAccess.java deleted file mode 100644 index 94dc3be..0000000 --- a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizeAccess.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.nifi.registry.security.authorization; - -public interface AuthorizeAccess { - void authorize(AuthorizableLookup lookup); -} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/StandardAuthorizableLookup.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/StandardAuthorizableLookup.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/StandardAuthorizableLookup.java index 00d318a..50b7185 100644 --- a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/StandardAuthorizableLookup.java +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/StandardAuthorizableLookup.java @@ -22,11 +22,15 @@ import org.apache.nifi.registry.security.authorization.resource.Authorizable; import org.apache.nifi.registry.security.authorization.resource.InheritingAuthorizable; import org.apache.nifi.registry.security.authorization.resource.ResourceFactory; import org.apache.nifi.registry.security.authorization.resource.ResourceType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Component public class StandardAuthorizableLookup implements AuthorizableLookup { + private static final Logger logger = LoggerFactory.getLogger(StandardAuthorizableLookup.class); + private static final Authorizable TENANTS_AUTHORIZABLE = new Authorizable() { @Override public Authorizable getParentAuthorizable() { @@ -75,6 +79,23 @@ public class StandardAuthorizableLookup implements AuthorizableLookup { } }; + private static final Authorizable ACTUATOR_AUTHORIZABLE = new Authorizable() { + @Override + public Authorizable getParentAuthorizable() { + return null; + } + + @Override + public Resource getResource() { + return ResourceFactory.getActuatorResource(); + } + }; + + @Override + public Authorizable getActuatorAuthorizable() { + return ACTUATOR_AUTHORIZABLE; + } + @Override public Authorizable getProxyAuthorizable() { return PROXY_AUTHORIZABLE; @@ -114,12 +135,7 @@ public class StandardAuthorizableLookup implements AuthorizableLookup { @Override public Authorizable getAuthorizableByResource(String resource) { - ResourceType resourceType = null; - for (ResourceType type : ResourceType.values()) { - if (resource.equals(type.getValue()) || resource.startsWith(type.getValue() + "/")) { - resourceType = type; - } - } + ResourceType resourceType = ResourceType.mapFullResourcePathToResourceType(resource); if (resourceType == null) { throw new ResourceNotFoundException("Unrecognized resource: " + resource); @@ -129,21 +145,10 @@ public class StandardAuthorizableLookup implements AuthorizableLookup { } private Authorizable getAuthorizableByResource(final ResourceType resourceType, final String resource) { - final String childResourceId = StringUtils.substringAfter(resource, resourceType.getValue()); - if (childResourceId.startsWith("/")) { - return getAuthorizableByChildResource(resourceType, childResourceId.substring(1)); - } else { - return getAuthorizableByResource(resourceType); - } - } - - private Authorizable getAuthorizableByResource(final ResourceType resourceType) { Authorizable authorizable = null; switch (resourceType) { - case Bucket: - authorizable = getBucketsAuthorizable(); - break; + /* Access to these resources are always authorized by the top-level resource */ case Policy: authorizable = getPoliciesAuthorizable(); break; @@ -152,10 +157,24 @@ public class StandardAuthorizableLookup implements AuthorizableLookup { break; case Proxy: authorizable = getProxyAuthorizable(); + break; + case Actuator: + authorizable = getActuatorAuthorizable(); + break; + + /* Access to buckets can be authorized by the top-level /buckets resource or an individual /buckets/{id} resource */ + case Bucket: + final String childResourceId = StringUtils.substringAfter(resource, resourceType.getValue()); + if (childResourceId.startsWith("/")) { + authorizable = getAuthorizableByChildResource(resourceType, childResourceId); + } else { + authorizable = getBucketsAuthorizable(); + } } if (authorizable == null) { - throw new IllegalArgumentException("An unexpected type of resource in this policy " + resourceType.getValue()); + logger.debug("Could not determine the Authorizable for resource type='{}', path='{}', ", resourceType.getValue(), resource); + throw new IllegalArgumentException("This an unexpected type of authorizable resource: " + resourceType.getValue()); } return authorizable; @@ -165,8 +184,12 @@ public class StandardAuthorizableLookup implements AuthorizableLookup { Authorizable authorizable; switch (baseResourceType) { case Bucket: - authorizable = getBucketAuthorizable(childResourceId); - break; + String[] childResourcePathParts = childResourceId.split("/"); + if (childResourcePathParts.length >= 1) { + final String bucketId = childResourcePathParts[1]; + authorizable = getBucketAuthorizable(bucketId); + break; + } default: throw new IllegalArgumentException("Unexpected lookup for child resource authorizable for base resource type " + baseResourceType.getValue()); } http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAccessPolicyProvider.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAccessPolicyProvider.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAccessPolicyProvider.java index e4a03f3..83ceb50 100644 --- a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAccessPolicyProvider.java +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAccessPolicyProvider.java @@ -125,6 +125,9 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide new ResourceActionPair("/buckets", READ_CODE), new ResourceActionPair("/buckets", WRITE_CODE), new ResourceActionPair("/buckets", DELETE_CODE), + new ResourceActionPair("/actuator", READ_CODE), + new ResourceActionPair("/actuator", WRITE_CODE), + new ResourceActionPair("/actuator", DELETE_CODE), new ResourceActionPair("/proxy", WRITE_CODE) }; http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceFactory.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceFactory.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceFactory.java index b81b873..845e719 100644 --- a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceFactory.java +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceFactory.java @@ -39,77 +39,85 @@ public final class ResourceFactory { } }; - - private final static Resource POLICY_RESOURCE = new Resource() { + private final static Resource PROXY_RESOURCE = new Resource() { @Override public String getIdentifier() { - return ResourceType.Policy.getValue(); + return ResourceType.Proxy.getValue(); } @Override public String getName() { - return "Policies for "; + return "Proxy User Requests"; } @Override public String getSafeDescription() { - return "the policies for "; + return "proxy requests on behalf of users"; } }; - private final static Resource PROXY_RESOURCE = new Resource() { + private final static Resource TENANTS_RESOURCE = new Resource() { @Override public String getIdentifier() { - return ResourceType.Proxy.getValue(); + return ResourceType.Tenant.getValue(); } @Override public String getName() { - return "Proxy User Requests"; + return "Tenants"; } @Override public String getSafeDescription() { - return "proxy requests on behalf of users"; + return "users/user groups"; } }; - private final static Resource TENANTS_RESOURCE = new Resource() { + private final static Resource POLICIES_RESOURCE = new Resource() { + @Override public String getIdentifier() { - return ResourceType.Tenant.getValue(); + return ResourceType.Policy.getValue(); } @Override public String getName() { - return "Tenants"; + return "Access Policies"; } @Override public String getSafeDescription() { - return "users/user groups"; + return "policies"; } }; - private final static Resource POLICIES_RESOURCE = new Resource() { - + private final static Resource ACTUATOR_RESOURCE = new Resource() { @Override public String getIdentifier() { - return "/policies"; + return ResourceType.Actuator.getValue(); } @Override public String getName() { - return "Access Policies"; + return "Actuator"; } @Override public String getSafeDescription() { - return "policies"; + return "actuator"; } }; /** + * Gets the Resource for actuator system management endpoints. + * + * @return The resource for actuator system management endpoints. + */ + public static Resource getActuatorResource() { + return ACTUATOR_RESOURCE; + } + + /** * Gets the Resource for proxying a user request. * * @return The resource for proxying a user request @@ -152,33 +160,6 @@ public final class ResourceFactory { } /** - * Gets a Resource for accessing a resources's policies. - * - * @param resource The resource being accessed - * @return The resource - */ - public static Resource getPolicyResource(final Resource resource) { - Objects.requireNonNull(resource, "The resource type must be specified."); - - return new Resource() { - @Override - public String getIdentifier() { - return String.format("%s%s", POLICY_RESOURCE.getIdentifier(), resource.getIdentifier()); - } - - @Override - public String getName() { - return POLICY_RESOURCE.getName() + resource.getName(); - } - - @Override - public String getSafeDescription() { - return POLICY_RESOURCE.getSafeDescription() + resource.getSafeDescription(); - } - }; - } - - /** * Get a Resource object for any object that has a base type and an identifier, ie: * /buckets/{uuid} * http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceType.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceType.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceType.java index 7274b56..00358ff 100644 --- a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceType.java +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceType.java @@ -20,7 +20,8 @@ public enum ResourceType { Bucket("/buckets"), Policy("/policies"), Proxy("/proxy"), - Tenant("/tenants"); + Tenant("/tenants"), + Actuator("/actuator"); final String value; @@ -48,4 +49,38 @@ public enum ResourceType { return type; } + + /** + * Map an arbitrary resource path to its base resource type. The base resource type is + * what the resource path starts with. + * + * The resourcePath arg is expected to be a string of the format: + * + * {ResourceTypeValue}/arbitrary/sub-resource/path + * + * For example: + * /buckets -> ResourceType.Bucket + * /buckets/bucketId -> ResourceType.Bucket + * /policies/read/buckets -> ResourceType.Policy + * + * @param resourcePath the path component of a URI (not including the context path) + * @return the base resource type + */ + public static ResourceType mapFullResourcePathToResourceType(final String resourcePath) { + if (resourcePath == null) { + throw new IllegalArgumentException("Resource path must not be null"); + } + + ResourceType type = null; + + for (final ResourceType rt : values()) { + final String rtValue = rt.getValue(); + if(resourcePath.equals(rtValue) || resourcePath.startsWith(rtValue + "/")) { + type = rt; + break; + } + } + + return type; + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java index 219885c..12f6f26 100644 --- a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java @@ -29,7 +29,6 @@ import org.apache.nifi.registry.bucket.Bucket; import org.apache.nifi.registry.security.authorization.AccessPolicyProvider; import org.apache.nifi.registry.security.authorization.AccessPolicyProviderInitializationContext; import org.apache.nifi.registry.security.authorization.AuthorizableLookup; -import org.apache.nifi.registry.security.authorization.AuthorizeAccess; import org.apache.nifi.registry.security.authorization.Authorizer; import org.apache.nifi.registry.security.authorization.AuthorizerCapabilityDetection; import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext; @@ -100,8 +99,16 @@ public class AuthorizationService { // ---------------------- Authorization methods ------------------------------------- - public void authorizeAccess(final AuthorizeAccess authorizeAccess) { - authorizeAccess.authorize(authorizableLookup); + public AuthorizableLookup getAuthorizableLookup() { + return authorizableLookup; + } + + public Authorizer getAuthorizer() { + return authorizer; + } + + public void authorize(Authorizable authorizable, RequestAction action) throws AccessDeniedException { + authorizable.authorize(authorizer, action, NiFiUserUtils.getNiFiUser()); } // ---------------------- Permissions methods --------------------------------------- @@ -520,6 +527,9 @@ public class AuthorizationService { if (includeFilter == null || includeFilter.equals(ResourceType.Proxy)) { resources.add(ResourceFactory.getProxyResource()); } + if (includeFilter == null || includeFilter.equals(ResourceType.Actuator)) { + resources.add(ResourceFactory.getActuatorResource()); + } if (includeFilter == null || includeFilter.equals(ResourceType.Bucket)) { resources.add(ResourceFactory.getBucketsResource()); // add all buckets http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy b/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy index ead72d4..8bde97a 100644 --- a/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy +++ b/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy @@ -536,14 +536,15 @@ class AuthorizationServiceSpec extends Specification { then: resources != null - resources.size() == 6 + resources.size() == 7 def sortedResources = resources.sort{it.identifier} - sortedResources[0].identifier == "/buckets" - sortedResources[1].identifier == "/buckets/b1" - sortedResources[2].identifier == "/buckets/b2" - sortedResources[3].identifier == "/policies" - sortedResources[4].identifier == "/proxy" - sortedResources[5].identifier == "/tenants" + sortedResources[0].identifier == "/actuator" + sortedResources[1].identifier == "/buckets" + sortedResources[2].identifier == "/buckets/b1" + sortedResources[3].identifier == "/buckets/b2" + sortedResources[4].identifier == "/policies" + sortedResources[5].identifier == "/proxy" + sortedResources[6].identifier == "/tenants" } @@ -575,6 +576,7 @@ class AuthorizationServiceSpec extends Specification { def denied = Mock(Authorizable) denied.authorize(_, _, _) >> { throw new AccessDeniedException("") } + authorizableLookup.getAuthorizableByResource("/actuator") >> denied authorizableLookup.getAuthorizableByResource("/buckets") >> authorized authorizableLookup.getAuthorizableByResource("/buckets/b1") >> authorized authorizableLookup.getAuthorizableByResource("/buckets/b2") >> denied http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/pom.xml ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/pom.xml b/nifi-registry-web-api/pom.xml index 797f31e..e991836 100644 --- a/nifi-registry-web-api/pom.xml +++ b/nifi-registry-web-api/pom.xml @@ -240,5 +240,23 @@ <version>3.2.1</version> <scope>test</scope> </dependency> + <dependency> + <groupId>org.spockframework</groupId> + <artifactId>spock-core</artifactId> + <version>1.0-groovy-2.4</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.codehaus.groovy</groupId> + <artifactId>groovy-all</artifactId> + <version>2.4.12</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>cglib</groupId> + <artifactId>cglib-nodep</artifactId> + <version>2.2.2</version> + <scope>test</scope> + </dependency> </dependencies> </project> http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/NiFiRegistryApiApplication.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/NiFiRegistryApiApplication.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/NiFiRegistryApiApplication.java index 29c6480..256efb4 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/NiFiRegistryApiApplication.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/NiFiRegistryApiApplication.java @@ -18,9 +18,11 @@ package org.apache.nifi.registry; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import java.util.Properties; + /** * Main class for starting the NiFi Registry Web API as a Spring Boot application. * @@ -31,12 +33,27 @@ import org.springframework.boot.web.servlet.support.SpringBootServletInitializer * * WebMvcAutoConfiguration is excluded because our web app is using Jersey in place of SpringMVC */ -@SpringBootApplication(exclude = WebMvcAutoConfiguration.class) +@SpringBootApplication public class NiFiRegistryApiApplication extends SpringBootServletInitializer { public static final String NIFI_REGISTRY_PROPERTIES_ATTRIBUTE = "nifi-registry.properties"; public static final String NIFI_REGISTRY_MASTER_KEY_ATTRIBUTE = "nifi-registry.key"; + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + final Properties defaultProperties = new Properties(); + + // Enable Actuator Endpoints + defaultProperties.setProperty("management.endpoints.web.expose", "*"); + + // Run Jersey as a filter instead of a servlet so that requests can be forwarded to other handlers (e.g., actuator) + defaultProperties.setProperty("spring.jersey.type", "filter"); + + return application + .sources(NiFiRegistryApiApplication.class) + .properties(defaultProperties); + } + public static void main(String[] args) { SpringApplication.run(NiFiRegistryApiApplication.class, args); } http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java index 878ec90..097fc47 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java @@ -26,6 +26,7 @@ import org.apache.nifi.registry.web.api.TenantResource; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.ServerProperties; import org.glassfish.jersey.server.filter.HttpMethodOverrideFilter; +import org.glassfish.jersey.servlet.ServletProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration; @@ -67,6 +68,9 @@ public class NiFiRegistryResourceConfig extends ResourceConfig { // if this value needs to be changed, kerberos authentication needs to move to filter chain // so it can directly set the HttpServletResponse instead of indirectly through a JAX-RS Response property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, true); + + // configure jersey to ignore resource paths for actuator + property(ServletProperties.FILTER_STATIC_CONTENT_REGEX, "/actuator.*"); } } http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java index 9950bb3..ac84b2f 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java @@ -32,7 +32,6 @@ import org.apache.nifi.registry.security.authorization.Authorizer; import org.apache.nifi.registry.security.authorization.AuthorizerCapabilityDetection; import org.apache.nifi.registry.security.authorization.RequestAction; import org.apache.nifi.registry.security.authorization.resource.Authorizable; -import org.apache.nifi.registry.security.authorization.user.NiFiUserUtils; import org.apache.nifi.registry.service.AuthorizationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,11 +68,14 @@ public class AccessPolicyResource extends AuthorizableApplicationResource { private static final Logger logger = LoggerFactory.getLogger(AccessPolicyResource.class); + private Authorizer authorizer; + @Autowired public AccessPolicyResource( Authorizer authorizer, AuthorizationService authorizationService) { - super(authorizer, authorizationService); + super(authorizationService); + this.authorizer = authorizer; } /** @@ -392,10 +394,8 @@ public class AccessPolicyResource extends AuthorizableApplicationResource { } private void authorizeAccess(RequestAction actionType) { - authorizationService.authorizeAccess(lookup -> { - final Authorizable policiesAuthorizable = lookup.getPoliciesAuthorizable(); - policiesAuthorizable.authorize(authorizer, actionType, NiFiUserUtils.getNiFiUser()); - }); + final Authorizable policiesAuthorizable = authorizableLookup.getPoliciesAuthorizable(); + authorizationService.authorize(policiesAuthorizable, actionType); } private String generateAccessPolicyUri(final AccessPolicySummary accessPolicy) { http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AuthorizableApplicationResource.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AuthorizableApplicationResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AuthorizableApplicationResource.java index d0be42c..8f983cf 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AuthorizableApplicationResource.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AuthorizableApplicationResource.java @@ -18,11 +18,10 @@ package org.apache.nifi.registry.web.api; import org.apache.nifi.registry.authorization.Resource; import org.apache.nifi.registry.bucket.BucketItem; -import org.apache.nifi.registry.security.authorization.Authorizer; +import org.apache.nifi.registry.security.authorization.AuthorizableLookup; import org.apache.nifi.registry.security.authorization.RequestAction; import org.apache.nifi.registry.security.authorization.resource.Authorizable; import org.apache.nifi.registry.security.authorization.resource.ResourceType; -import org.apache.nifi.registry.security.authorization.user.NiFiUserUtils; import org.apache.nifi.registry.service.AuthorizationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,20 +35,17 @@ public class AuthorizableApplicationResource extends ApplicationResource { private static final Logger logger = LoggerFactory.getLogger(AuthorizableApplicationResource.class); protected final AuthorizationService authorizationService; - protected final Authorizer authorizer; + protected final AuthorizableLookup authorizableLookup; protected AuthorizableApplicationResource( - Authorizer authorizer, AuthorizationService authorizationService) { - this.authorizer = authorizer; this.authorizationService = authorizationService; + this.authorizableLookup = authorizationService.getAuthorizableLookup(); } protected void authorizeBucketAccess(RequestAction actionType, String bucketIdentifier) { - authorizationService.authorizeAccess(lookup -> { - final Authorizable bucketAccessPolicy = lookup.getBucketAuthorizable(bucketIdentifier); - bucketAccessPolicy.authorize(authorizer, actionType, NiFiUserUtils.getNiFiUser()); - }); + final Authorizable bucketAuthorizable = authorizableLookup.getBucketAuthorizable(bucketIdentifier); + authorizationService.authorize(bucketAuthorizable, actionType); } protected void authorizeBucketItemAccess(RequestAction actionType, BucketItem bucketItem) { http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketFlowResource.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketFlowResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketFlowResource.java index 788f318..a53d6e2 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketFlowResource.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketFlowResource.java @@ -31,7 +31,6 @@ import org.apache.nifi.registry.exception.ResourceNotFoundException; import org.apache.nifi.registry.flow.VersionedFlow; import org.apache.nifi.registry.flow.VersionedFlowSnapshot; import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata; -import org.apache.nifi.registry.security.authorization.Authorizer; import org.apache.nifi.registry.security.authorization.RequestAction; import org.apache.nifi.registry.security.authorization.user.NiFiUserUtils; import org.apache.nifi.registry.service.AuthorizationService; @@ -78,9 +77,8 @@ public class BucketFlowResource extends AuthorizableApplicationResource { final RegistryService registryService, final LinkService linkService, final PermissionsService permissionsService, - final AuthorizationService authorizationService, - final Authorizer authorizer) { - super(authorizer, authorizationService); + final AuthorizationService authorizationService) { + super(authorizationService); this.registryService = registryService; this.linkService = linkService; this.permissionsService =permissionsService; http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketResource.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketResource.java index f94117d..5f75e80 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketResource.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/BucketResource.java @@ -28,11 +28,9 @@ import org.apache.commons.lang3.StringUtils; import org.apache.nifi.registry.bucket.Bucket; import org.apache.nifi.registry.bucket.BucketItem; import org.apache.nifi.registry.field.Fields; -import org.apache.nifi.registry.security.authorization.Authorizer; import org.apache.nifi.registry.security.authorization.RequestAction; import org.apache.nifi.registry.security.authorization.exception.AccessDeniedException; import org.apache.nifi.registry.security.authorization.resource.Authorizable; -import org.apache.nifi.registry.security.authorization.user.NiFiUserUtils; import org.apache.nifi.registry.service.AuthorizationService; import org.apache.nifi.registry.service.RegistryService; import org.apache.nifi.registry.web.link.LinkService; @@ -85,9 +83,8 @@ public class BucketResource extends AuthorizableApplicationResource { final RegistryService registryService, final LinkService linkService, final PermissionsService permissionsService, - final AuthorizationService authorizationService, - final Authorizer authorizer) { - super(authorizer, authorizationService); + final AuthorizationService authorizationService) { + super(authorizationService); this.registryService = registryService; this.linkService = linkService; this.permissionsService = permissionsService; @@ -278,10 +275,8 @@ public class BucketResource extends AuthorizableApplicationResource { } private void authorizeAccess(RequestAction actionType) throws AccessDeniedException { - authorizationService.authorizeAccess(lookup -> { - final Authorizable bucketsAuthorizable = lookup.getBucketsAuthorizable(); - bucketsAuthorizable.authorize(authorizer, actionType, NiFiUserUtils.getNiFiUser()); - }); + final Authorizable bucketsAuthorizable = authorizableLookup.getBucketsAuthorizable(); + authorizationService.authorize(bucketsAuthorizable, actionType); } } http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java index 4ea059e..c9deb11 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java @@ -20,8 +20,6 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.Authorization; import org.apache.nifi.registry.field.Fields; -import org.apache.nifi.registry.security.authorization.Authorizer; -import org.apache.nifi.registry.service.AuthorizationService; import org.apache.nifi.registry.service.RegistryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -41,16 +39,12 @@ import java.util.Set; description = "Gets metadata about flows.", authorizations = { @Authorization("Authorization") } ) -public class FlowResource extends AuthorizableApplicationResource { +public class FlowResource extends ApplicationResource { private final RegistryService registryService; @Autowired - public FlowResource( - final RegistryService registryService, - final AuthorizationService authorizationService, - final Authorizer authorizer) { - super(authorizer, authorizationService); + public FlowResource(final RegistryService registryService) { this.registryService = registryService; } http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ItemResource.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ItemResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ItemResource.java index 645f34d..e7ae6be 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ItemResource.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ItemResource.java @@ -26,7 +26,6 @@ import io.swagger.annotations.Extension; import io.swagger.annotations.ExtensionProperty; import org.apache.nifi.registry.bucket.BucketItem; import org.apache.nifi.registry.field.Fields; -import org.apache.nifi.registry.security.authorization.Authorizer; import org.apache.nifi.registry.security.authorization.RequestAction; import org.apache.nifi.registry.service.AuthorizationService; import org.apache.nifi.registry.service.RegistryService; @@ -74,9 +73,8 @@ public class ItemResource extends AuthorizableApplicationResource { final RegistryService registryService, final LinkService linkService, final PermissionsService permissionsService, - final AuthorizationService authorizationService, - final Authorizer authorizer) { - super(authorizer, authorizationService); + final AuthorizationService authorizationService) { + super(authorizationService); this.registryService = registryService; this.linkService = linkService; this.permissionsService = permissionsService; http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/TenantResource.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/TenantResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/TenantResource.java index 2fc2e97..d00038d 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/TenantResource.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/TenantResource.java @@ -32,7 +32,6 @@ import org.apache.nifi.registry.security.authorization.Authorizer; import org.apache.nifi.registry.security.authorization.AuthorizerCapabilityDetection; import org.apache.nifi.registry.security.authorization.RequestAction; import org.apache.nifi.registry.security.authorization.resource.Authorizable; -import org.apache.nifi.registry.security.authorization.user.NiFiUserUtils; import org.apache.nifi.registry.service.AuthorizationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,11 +67,12 @@ public class TenantResource extends AuthorizableApplicationResource { private static final Logger logger = LoggerFactory.getLogger(TenantResource.class); + private Authorizer authorizer; + @Autowired - public TenantResource( - Authorizer authorizer, - AuthorizationService authorizationService) { - super(authorizer, authorizationService); + public TenantResource(AuthorizationService authorizationService) { + super(authorizationService); + authorizer = authorizationService.getAuthorizer(); } @@ -563,10 +563,8 @@ public class TenantResource extends AuthorizableApplicationResource { } private void authorizeAccess(RequestAction actionType) { - authorizationService.authorizeAccess(lookup -> { - final Authorizable tenantsAuthorizable = lookup.getTenantsAuthorizable(); - tenantsAuthorizable.authorize(authorizer, actionType, NiFiUserUtils.getNiFiUser()); - }); + final Authorizable tenantsAuthorizable = authorizableLookup.getTenantsAuthorizable(); + authorizationService.authorize(tenantsAuthorizable, actionType); } private String generateUserUri(final User user) { http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java index 9a5d18b..1d484c1 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java @@ -17,7 +17,8 @@ package org.apache.nifi.registry.web.security; import org.apache.nifi.registry.properties.NiFiRegistryProperties; -import org.apache.nifi.registry.security.authorization.Authorizer; +import org.apache.nifi.registry.security.authorization.resource.ResourceType; +import org.apache.nifi.registry.service.AuthorizationService; import org.apache.nifi.registry.web.security.authentication.AnonymousIdentityFilter; import org.apache.nifi.registry.web.security.authentication.IdentityAuthenticationProvider; import org.apache.nifi.registry.web.security.authentication.IdentityFilter; @@ -25,6 +26,7 @@ import org.apache.nifi.registry.web.security.authentication.exception.UntrustedP import org.apache.nifi.registry.web.security.authentication.jwt.JwtIdentityProvider; import org.apache.nifi.registry.web.security.authentication.x509.X509IdentityAuthenticationProvider; import org.apache.nifi.registry.web.security.authentication.x509.X509IdentityProvider; +import org.apache.nifi.registry.web.security.authorization.ResourceAuthorizationFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -38,6 +40,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; import javax.servlet.ServletException; @@ -59,7 +62,7 @@ public class NiFiRegistrySecurityConfig extends WebSecurityConfigurerAdapter { private NiFiRegistryProperties properties; @Autowired - private Authorizer authorizer; + private AuthorizationService authorizationService; private AnonymousIdentityFilter anonymousAuthenticationFilter = new AnonymousIdentityFilter(); @@ -73,6 +76,8 @@ public class NiFiRegistrySecurityConfig extends WebSecurityConfigurerAdapter { private IdentityFilter jwtAuthenticationFilter; private IdentityAuthenticationProvider jwtAuthenticationProvider; + private ResourceAuthorizationFilter resourceAuthorizationFilter; + public NiFiRegistrySecurityConfig() { super(true); // disable defaults } @@ -112,6 +117,12 @@ public class NiFiRegistrySecurityConfig extends WebSecurityConfigurerAdapter { // is detected earlier in the Spring filter chain. http.anonymous().authenticationFilter(anonymousAuthenticationFilter); } + + // After Spring Security filter chain is complete (so authentication is done), + // but before the Jersey application endpoints get the request, + // insert the ResourceAuthorizationFilter to do its authorization checks + http.addFilterAfter(resourceAuthorizationFilter(), FilterSecurityInterceptor.class); + } @Override @@ -130,7 +141,7 @@ public class NiFiRegistrySecurityConfig extends WebSecurityConfigurerAdapter { private IdentityAuthenticationProvider x509AuthenticationProvider() { if (x509AuthenticationProvider == null) { - x509AuthenticationProvider = new X509IdentityAuthenticationProvider(properties, authorizer, x509IdentityProvider); + x509AuthenticationProvider = new X509IdentityAuthenticationProvider(properties, authorizationService.getAuthorizer(), x509IdentityProvider); } return x509AuthenticationProvider; } @@ -144,11 +155,21 @@ public class NiFiRegistrySecurityConfig extends WebSecurityConfigurerAdapter { private IdentityAuthenticationProvider jwtAuthenticationProvider() { if (jwtAuthenticationProvider == null) { - jwtAuthenticationProvider = new IdentityAuthenticationProvider(properties, authorizer, jwtIdentityProvider); + jwtAuthenticationProvider = new IdentityAuthenticationProvider(properties, authorizationService.getAuthorizer(), jwtIdentityProvider); } return jwtAuthenticationProvider; } + private ResourceAuthorizationFilter resourceAuthorizationFilter() { + if (resourceAuthorizationFilter == null) { + resourceAuthorizationFilter = ResourceAuthorizationFilter.builder() + .setAuthorizationService(authorizationService) + .addResourceType(ResourceType.Actuator) + .build(); + } + return resourceAuthorizationFilter; + } + private AuthenticationEntryPoint http401AuthenticationEntryPoint() { // This gets used for both secured and unsecured configurations. It will be called by Spring Security if a request makes it through the filter chain without being authenticated. // For unsecured, this should never be reached because the custom AnonymousAuthenticationFilter should always populate a fully-authenticated anonymous user http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityAuthenticationFilter.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityAuthenticationFilter.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityAuthenticationFilter.java deleted file mode 100644 index 8367eec..0000000 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityAuthenticationFilter.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.nifi.registry.web.security.authentication; - -import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.registry.security.authentication.AuthenticationRequest; -import org.apache.nifi.registry.security.authentication.IdentityProvider; -import org.apache.nifi.registry.security.authorization.user.NiFiUserUtils; -import org.apache.nifi.registry.security.util.ProxiedEntitiesUtils; -import org.apache.nifi.registry.web.security.authentication.exception.InvalidAuthenticationException; -import org.apache.nifi.registry.web.security.authentication.exception.UntrustedProxyException; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; - -/** - * Note: This class is deprecated and is being considered for complete removal in favor of using {@link IdentityFilter}. - * It is remaining in place for the time being until the pattern of authentication implemented by {@link IdentityFilter} - * has been more thoroughly vetted in real use. - */ -@Deprecated -public class IdentityAuthenticationFilter extends AbstractAuthenticationProcessingFilter { - - private static final RequestMatcher requiresAuthenticationRequestMatcher = new RequestMatcher() { - @Override - public boolean matches(HttpServletRequest httpServletRequest) { - return NiFiUserUtils.getNiFiUser() == null; - } - }; - - private final IdentityProvider identityProvider; - - public IdentityAuthenticationFilter(IdentityProvider identityProvider, AuthenticationManager authenticationManager, String defaultFilterProcessesUrl) { - super(defaultFilterProcessesUrl); - super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultFilterProcessesUrl)); // Authentication will only be initiated for the request url matching this pattern - setAuthenticationManager(authenticationManager); - this.identityProvider = identityProvider; - } - - public IdentityAuthenticationFilter(IdentityProvider identityProvider, AuthenticationManager authenticationManager) { - super(requiresAuthenticationRequestMatcher); - setAuthenticationManager(authenticationManager); - this.identityProvider = identityProvider; - } - - @Override - public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException { - - // Only require authentication from an identity provider if the NiFi registry is running securely. - if (!httpServletRequest.isSecure()) { - throw new InvalidAuthenticationException("Authentication of user identity claim is only avaialble when running a securely."); - } - - AuthenticationRequest authenticationRequest = identityProvider.extractCredentials(httpServletRequest); - if (authenticationRequest == null) { - throw new InvalidAuthenticationException("User credentials not found in httpServletRequest by " + identityProvider.getClass().getSimpleName()); - } - Authentication authentication = new AuthenticationRequestToken(authenticationRequest, identityProvider.getClass(), httpServletRequest.getRemoteAddr()); - Authentication authenticationResult = getAuthenticationManager().authenticate(authentication); // See IdentityAuthenticationProvider for authentication impl. - if (authenticationResult == null) { - throw new InvalidAuthenticationException("User credentials not authenticated by " + identityProvider.getClass().getSimpleName()); - } - - return authenticationResult; - // Super class will invoke successfulAuthentication() or unsuccessfulAuthentication() depending on the outcome of the authentication attempt - } - - @Override - protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { - - logger.info("Authentication success for " + authResult); - - SecurityContextHolder.getContext().setAuthentication(authResult); - if (StringUtils.isNotBlank(request.getHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN))) { - response.setHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_ACCEPTED, Boolean.TRUE.toString()); - } - - // continue the filter chain, which now holds a NiFiUser in the SecurityContext's authentication - chain.doFilter(request, response); - } - - @Override - protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { - this.logger.debug("Authentication request failed: " + failed.toString(), failed); - - SecurityContextHolder.clearContext(); - this.logger.debug("Updated SecurityContextHolder to contain null Authentication"); - - // populate the response - if (StringUtils.isNotBlank(request.getHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN))) { - response.setHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_DETAILS, failed.getMessage()); - } - - // set the response status - response.setContentType("text/plain"); - - // write the response message - PrintWriter out = response.getWriter(); - - // use the type of authentication exception to determine the response code - if (failed instanceof InvalidAuthenticationException) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - out.println(failed.getMessage()); - } else if (failed instanceof UntrustedProxyException) { // thrown in X509IdentityProviderAuthenticationProvider - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - out.println(failed.getMessage()); - } else if (failed instanceof AuthenticationServiceException) { - logger.error(String.format("Unable to authorize: %s", failed.getMessage()), failed); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - out.println(String.format("Unable to authorize: %s", failed.getMessage())); - } else { - logger.error(String.format("Unable to authorize: %s", failed.getMessage()), failed); - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - out.println("Access is denied."); - } - - // log the failure - logger.warn(String.format("Rejecting access to web api: %s", failed.getMessage())); - logger.debug(StringUtils.EMPTY, failed); - } - -} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityFilter.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityFilter.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityFilter.java index 40c2662..cd5e2bf 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityFilter.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/IdentityFilter.java @@ -64,8 +64,8 @@ public class IdentityFilter extends GenericFilterBean { } if (credentialsAlreadyPresent()) { - logger.debug("Credentials already extracted for {}, skipping credentials extraction filter for {}", - SecurityContextHolder.getContext().getAuthentication().getPrincipal(), + logger.debug("Credentials already extracted for [{}], skipping credentials extraction filter using {}", + SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString(), identityProvider.getClass().getSimpleName()); filterChain.doFilter(servletRequest, servletResponse); return; http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/HttpMethodAuthorizationRules.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/HttpMethodAuthorizationRules.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/HttpMethodAuthorizationRules.java new file mode 100644 index 0000000..c940359 --- /dev/null +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/HttpMethodAuthorizationRules.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.web.security.authorization; + +import org.apache.nifi.registry.security.authorization.RequestAction; +import org.springframework.http.HttpMethod; + +public interface HttpMethodAuthorizationRules { + + default boolean requiresAuthorization(HttpMethod httpMethod) { + return true; + } + + default RequestAction mapHttpMethodToAction(HttpMethod httpMethod) { + + switch (httpMethod) { + case TRACE: + case OPTIONS: + case HEAD: + case GET: + return RequestAction.READ; + case POST: + case PUT: + case PATCH: + return RequestAction.WRITE; + case DELETE: + return RequestAction.DELETE; + default: + throw new IllegalArgumentException("Unknown http method: " + httpMethod); + } + + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/ResourceAuthorizationFilter.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/ResourceAuthorizationFilter.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/ResourceAuthorizationFilter.java new file mode 100644 index 0000000..6e551e1 --- /dev/null +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/ResourceAuthorizationFilter.java @@ -0,0 +1,218 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.web.security.authorization; + +import org.apache.nifi.registry.security.authorization.AuthorizableLookup; +import org.apache.nifi.registry.security.authorization.RequestAction; +import org.apache.nifi.registry.security.authorization.exception.AccessDeniedException; +import org.apache.nifi.registry.security.authorization.resource.Authorizable; +import org.apache.nifi.registry.security.authorization.resource.ResourceType; +import org.apache.nifi.registry.security.authorization.user.NiFiUser; +import org.apache.nifi.registry.security.authorization.user.NiFiUserUtils; +import org.apache.nifi.registry.service.AuthorizationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpMethod; +import org.springframework.web.filter.GenericFilterBean; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * This filter is designed to perform a resource authorization check in the Spring Security filter chain. + * + * It authorizes the current authenticated user for the {@link RequestAction} (based on the HttpMethod) requested + * on the {@link ResourceType} (based on the URI path). + * + * This filter is designed to be place after any authentication and before any application endpoints. + * + * This filter can be used in place of or in addition to authorization checks that occur in the application + * downstream of this filter. + * + * To configure this filter, provide an {@link AuthorizationService} that will be used to perform the authorization + * check, as well as a set of rules that control which resource and HTTP methods are handled by this filter. + * + * Any (ResourceType, HttpMethod) pair that is not configured to require authorization by this filter will be + * allowed to proceed in the filter chain without an authorization check. + * + * Any (ResourceType, HttpMethod) pair that is configured to require authorization by this filter will map + * the HttpMethod to a NiFi Registry RequestAction (configurable when creating this filter), and the + * (Resource Authorizable, RequestAction) pair will be sent to the AuthorizationService, which will use the + * configured Authorizer to authorize the current user for the action on the requested resource. + */ +public class ResourceAuthorizationFilter extends GenericFilterBean { + + private static final Logger logger = LoggerFactory.getLogger(ResourceAuthorizationFilter.class); + + private Map<ResourceType, HttpMethodAuthorizationRules> resourceTypeAuthorizationRules; + private AuthorizationService authorizationService; + private AuthorizableLookup authorizableLookup; + + ResourceAuthorizationFilter(Builder builder) { + if (builder.getAuthorizationService() == null || builder.getResourceTypeAuthorizationRules() == null) { + throw new IllegalArgumentException("Builder is missing one or more required fields [authorizationService, resourceTypeAuthorizationRules]."); + } + this.resourceTypeAuthorizationRules = builder.getResourceTypeAuthorizationRules(); + this.authorizationService = builder.getAuthorizationService(); + this.authorizableLookup = this.authorizationService.getAuthorizableLookup(); + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + + HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; + HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; + + boolean authorizationCheckIsRequired = false; + String resourcePath = null; + RequestAction action = null; + + // Only require authorization if the NiFi Registry is running securely. + if (servletRequest.isSecure()) { + + // Only require authorization for resources for which this filter has been configured + resourcePath = httpServletRequest.getServletPath(); + if (resourcePath != null) { + final ResourceType resourceType = ResourceType.mapFullResourcePathToResourceType(resourcePath); + final HttpMethodAuthorizationRules authorizationRules = resourceTypeAuthorizationRules.get(resourceType); + if (authorizationRules != null) { + final String httpMethodStr = httpServletRequest.getMethod().toUpperCase(); + HttpMethod httpMethod = HttpMethod.resolve(httpMethodStr); + + // Only require authorization for HTTP methods included in this resource type's rule set + if (httpMethod != null && authorizationRules.requiresAuthorization(httpMethod)) { + authorizationCheckIsRequired = true; + action = authorizationRules.mapHttpMethodToAction(httpMethod); + } + } + } + } + + if (!authorizationCheckIsRequired) { + forwardRequestWithoutAuthorizationCheck(httpServletRequest, httpServletResponse, filterChain); + return; + } + + // Perform authorization check + try { + authorizeAccess(resourcePath, action); + successfulAuthorization(httpServletRequest, httpServletResponse, filterChain); + } catch (Exception e) { + logger.debug("Exception occurred while performing authorization check.", e); + failedAuthorization(httpServletRequest, httpServletResponse, filterChain, e); + } + } + + private boolean userIsAuthenticated() { + NiFiUser user = NiFiUserUtils.getNiFiUser(); + return (user != null && !user.isAnonymous()); + } + + private void authorizeAccess(String path, RequestAction action) throws AccessDeniedException { + + if (path == null || action == null) { + throw new IllegalArgumentException("Authorization is required, but a required input [resource, action] is absent."); + } + + Authorizable authorizable = authorizableLookup.getAuthorizableByResource(path); + + if (authorizable == null) { + throw new IllegalStateException("Resource Authorization Filter configured for non-authorizable resource: " + path); + } + + // throws AccessDeniedException if current user is not authorized to perform requested action on resource + authorizationService.authorize(authorizable, action); + } + + private void forwardRequestWithoutAuthorizationCheck(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { + logger.debug("Request filter authorization check is not required for this HTTP Method on this resource. " + + "Allowing request to proceed. An additional authorization check might be performed downstream of this filter."); + chain.doFilter(req, res); + } + + private void successfulAuthorization(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { + logger.debug("Request filter authorization check passed. Allowing request to proceed."); + chain.doFilter(req, res); + } + + private void failedAuthorization(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Exception failure) throws IOException, ServletException { + logger.debug("Request filter authorization check failed. Blocking access."); + + NiFiUser user = NiFiUserUtils.getNiFiUser(); + final String identity = (user != null) ? user.toString() : "<no user found>"; + final int status = !userIsAuthenticated() ? HttpServletResponse.SC_UNAUTHORIZED : HttpServletResponse.SC_FORBIDDEN; + + logger.info("{} does not have permission to perform this action on the requested resource. {} Returning {} response.", identity, failure.getMessage(), status); + logger.debug("", failure); + + if (!response.isCommitted()) { + response.setStatus(status); + response.setContentType("text/plain"); + response.getWriter().println(String.format("Access is denied due to: %s Contact the system administrator.", failure.getLocalizedMessage())); + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private AuthorizationService authorizationService; + final private Map<ResourceType, HttpMethodAuthorizationRules> resourceTypeAuthorizationRules; + + // create via ResourceAuthorizationFilter.builder() + private Builder() { + this.resourceTypeAuthorizationRules = new HashMap<>(); + } + + public AuthorizationService getAuthorizationService() { + return authorizationService; + } + + public Builder setAuthorizationService(AuthorizationService authorizationService) { + this.authorizationService = authorizationService; + return this; + } + + public Map<ResourceType, HttpMethodAuthorizationRules> getResourceTypeAuthorizationRules() { + return resourceTypeAuthorizationRules; + } + + public Builder addResourceType(ResourceType resourceType) { + this.resourceTypeAuthorizationRules.put(resourceType, new HttpMethodAuthorizationRules() {}); + return this; + } + + public Builder addResourceType(ResourceType resourceType, HttpMethodAuthorizationRules authorizationRules) { + this.resourceTypeAuthorizationRules.put(resourceType, authorizationRules); + return this; + } + + public ResourceAuthorizationFilter build() { + return new ResourceAuthorizationFilter(this); + } + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/StandardHttpMethodAuthorizationRules.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/StandardHttpMethodAuthorizationRules.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/StandardHttpMethodAuthorizationRules.java new file mode 100644 index 0000000..daa5a37 --- /dev/null +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/StandardHttpMethodAuthorizationRules.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.registry.web.security.authorization; + +import org.springframework.http.HttpMethod; + +import java.util.EnumSet; +import java.util.Set; + +public class StandardHttpMethodAuthorizationRules implements HttpMethodAuthorizationRules { + + final private Set<HttpMethod> methodsRequiringAuthorization; + + public StandardHttpMethodAuthorizationRules() { + this(EnumSet.allOf(HttpMethod.class)); + } + + public StandardHttpMethodAuthorizationRules(Set<HttpMethod> methodsRequiringAuthorization) { + this.methodsRequiringAuthorization = methodsRequiringAuthorization; + } + + @Override + public boolean requiresAuthorization(HttpMethod httpMethod) { + return methodsRequiringAuthorization.contains(httpMethod); + } +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/test/groovy/org/apache/nifi/registry/security/authorization/ResourceAuthorizationFilterSpec.groovy ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/test/groovy/org/apache/nifi/registry/security/authorization/ResourceAuthorizationFilterSpec.groovy b/nifi-registry-web-api/src/test/groovy/org/apache/nifi/registry/security/authorization/ResourceAuthorizationFilterSpec.groovy new file mode 100644 index 0000000..e27dfbe --- /dev/null +++ b/nifi-registry-web-api/src/test/groovy/org/apache/nifi/registry/security/authorization/ResourceAuthorizationFilterSpec.groovy @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.security.authorization + +import org.apache.nifi.registry.security.authorization.exception.AccessDeniedException +import org.apache.nifi.registry.security.authorization.resource.Authorizable +import org.apache.nifi.registry.security.authorization.resource.ResourceType +import org.apache.nifi.registry.service.AuthorizationService +import org.apache.nifi.registry.web.security.authorization.HttpMethodAuthorizationRules +import org.apache.nifi.registry.web.security.authorization.ResourceAuthorizationFilter +import org.apache.nifi.registry.web.security.authorization.StandardHttpMethodAuthorizationRules +import org.springframework.http.HttpMethod +import org.springframework.mock.web.MockHttpServletRequest +import org.springframework.mock.web.MockHttpServletResponse +import spock.lang.Specification + +import javax.servlet.FilterChain +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse + +class ResourceAuthorizationFilterSpec extends Specification { + + AuthorizableLookup authorizableLookup = new StandardAuthorizableLookup() + AuthorizationService mockAuthorizationService = Mock(AuthorizationService) + FilterChain mockFilterChain = Mock(FilterChain) + ResourceAuthorizationFilter.Builder resourceAuthorizationFilterBuilder + + // runs before every feature method + def setup() { + mockAuthorizationService.getAuthorizableLookup() >> authorizableLookup + resourceAuthorizationFilterBuilder = ResourceAuthorizationFilter.builder().setAuthorizationService(mockAuthorizationService) + } + + // runs after every feature method + def cleanup() { + //mockAuthorizationService = null + //mockFilterChain = null + resourceAuthorizationFilterBuilder = null + } + + // runs before the first feature method + def setupSpec() {} + + // runs after the last feature method + def cleanupSpec() {} + + + def "unsecured requests are allowed without an authorization check"() { + + setup: + def resourceAuthorizationFilter = resourceAuthorizationFilterBuilder.addResourceType(ResourceType.Actuator).build() + def httpServletRequest = createUnsecuredRequest() + def httpServletResponse = createResponse() + + when: "doFilter() is called" + resourceAuthorizationFilter.doFilter(httpServletRequest, httpServletResponse, mockFilterChain) + + then: "response is forwarded without authorization check" + 0 * mockAuthorizationService._ + 1 * mockFilterChain.doFilter(_ as HttpServletRequest, _ as HttpServletResponse) + + } + + + def "secure requests to an unguarded resource are allowed without an authorization check"() { + + setup: + def resourceAuthorizationFilter = resourceAuthorizationFilterBuilder.addResourceType(ResourceType.Actuator).build() + def httpServletRequest = createSecureRequest(HttpMethod.POST, ResourceType.Bucket) + def httpServletResponse = createResponse() + + when: "doFilter() is called" + resourceAuthorizationFilter.doFilter(httpServletRequest, httpServletResponse, mockFilterChain) + + then: "response is forwarded without authorization check" + 0 * mockAuthorizationService._ + 1 * mockFilterChain.doFilter(_ as HttpServletRequest, _ as HttpServletResponse) + + } + + + def "secure requests to an unguarded HTTP method are allowed without an authorization check"() { + + setup: + HttpMethodAuthorizationRules rules = new StandardHttpMethodAuthorizationRules(EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE)) + def resourceAuthorizationFilter = resourceAuthorizationFilterBuilder.addResourceType(ResourceType.Actuator, rules).build() + def httpServletRequest = createSecureRequest(HttpMethod.GET, ResourceType.Actuator) + def httpServletResponse = createResponse() + + when: "doFilter() is called" + resourceAuthorizationFilter.doFilter(httpServletRequest, httpServletResponse, mockFilterChain) + + then: "response is forwarded without authorization check" + 0 * mockAuthorizationService._ + 1 * mockFilterChain.doFilter(_ as HttpServletRequest, _ as HttpServletResponse) + + } + + + def "secure requests matching resource configuration rules perform authorization check"() { + + setup: + // Stubbing setup for mockAuthorizationService is done in the then block as we are also verifying interactions with mock + def resourceAuthorizationFilter = resourceAuthorizationFilterBuilder.addResourceType(ResourceType.Actuator).build() + def authorizedRequest = createSecureRequest(HttpMethod.GET, ResourceType.Actuator) + def unauthorizedRequest = createSecureRequest(HttpMethod.POST, ResourceType.Actuator) + def httpServletResponse = createResponse() + + + when: "doFilter() is called with an authorized request" + resourceAuthorizationFilter.doFilter(authorizedRequest, httpServletResponse, mockFilterChain) + + then: "response is forwarded after authorization check" + 1 * mockAuthorizationService.authorize(_ as Authorizable, RequestAction.READ) >> { allowAccess() } + 1 * mockFilterChain.doFilter(_ as HttpServletRequest, _ as HttpServletResponse) + + + when: "doFilter() is called with an unauthorized request" + resourceAuthorizationFilter.doFilter(unauthorizedRequest, httpServletResponse, mockFilterChain) + + then: "authorization check is performed and response is not forwarded" + 1 * mockAuthorizationService.authorize(_ as Authorizable, RequestAction.WRITE) >> { denyAccess() } + 0 * mockFilterChain.doFilter(*_) + + } + + static private HttpServletRequest createUnsecuredRequest() { + HttpServletRequest req = new MockHttpServletRequest() + req.setScheme("http") + req.setSecure(false) + return req + } + + static private HttpServletRequest createSecureRequest(HttpMethod httpMethod, ResourceType resourceType) { + HttpServletRequest req = new MockHttpServletRequest() + req.setMethod(httpMethod.name()) + req.setScheme("https") + req.setServletPath(resourceType.getValue()) + req.setSecure(true) + return req + } + + static private HttpServletResponse createResponse() { + HttpServletResponse res = new MockHttpServletResponse() + return res + } + + static private void allowAccess() { + // Do nothing (no thrown exception indicates access is allowed + } + + static private void denyAccess() { + throw new AccessDeniedException("This is an expected AccessDeniedException.") + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/1755d840/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureFileIT.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureFileIT.java b/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureFileIT.java index 0ffdb0d..29cd215 100644 --- a/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureFileIT.java +++ b/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureFileIT.java @@ -85,6 +85,7 @@ public class SecureFileIT extends IntegrationTestBase { // Given: an empty registry returns these resources String expected = "[" + + "{\"identifier\":\"/actuator\",\"name\":\"Actuator\"}," + "{\"identifier\":\"/policies\",\"name\":\"Access Policies\"}," + "{\"identifier\":\"/tenants\",\"name\":\"Tenants\"}," + "{\"identifier\":\"/proxy\",\"name\":\"Proxy User Requests\"}," +
