This is an automated email from the ASF dual-hosted git repository.
kdoran pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nifi-registry.git
The following commit(s) were added to refs/heads/master by this push:
new 4825565 NIFIREG-358 Refactoring proxy authorization to be part of
Authorizables
4825565 is described below
commit 4825565e48b935e07c3b6039b16e3144f150fa20
Author: Bryan Bende <[email protected]>
AuthorDate: Fri Feb 7 12:22:37 2020 -0500
NIFIREG-358 Refactoring proxy authorization to be part of Authorizables
NIFIREG-358 Catching UntrustedProxyException when asking for authorized
resources since it would be considered unauthorized
This closes #258.
Signed-off-by: Kevin Doran <[email protected]>
---
.../security/authorization/AuthorizerFactory.java | 12 -
.../authorization/FrameworkAuthorizer.java | 189 ----------
.../authorization/FrameworkManagedAuthorizer.java | 54 ---
.../authorization/StandardAuthorizableLookup.java | 78 +++-
.../authorization/UntrustedProxyException.java | 29 ++
.../authorization/resource/Authorizable.java | 14 -
.../resource/ProxyChainAuthorizable.java | 145 ++++++++
.../resource/PublicCheckingAuthorizable.java | 107 ++++++
.../registry/service/AuthorizationService.java | 3 +-
.../service/AuthorizationServiceSpec.groovy | 3 +-
.../authorization/TestFrameworkAuthorizer.java | 278 --------------
.../TestStandardAuthorizableLookup.java | 404 +++++++++++++++++++++
.../authorization/AuthorizationRequest.java | 5 +
.../web/mapper/UntrustedProxyExceptionMapper.java | 48 +++
.../ResourceAuthorizationFilterSpec.groovy | 4 +-
15 files changed, 814 insertions(+), 559 deletions(-)
diff --git
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
index 959e29e..f69ac3c 100644
---
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
+++
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
@@ -248,12 +248,8 @@ public class AuthorizerFactory implements
UserGroupProviderLookup, AccessPolicyP
try (final ExtensionCloseable extClosable =
ExtensionCloseable.withClassLoader(authorizerClassLoader)) {
authorizer.onConfigured(authorizerConfigurationContext);
}
-
- // wrap the integrity checked Authorizer with the
FrameworkAuthorizer
- authorizer = createFrameworkAuthorizer(authorizer);
}
-
} catch (AuthorizerFactoryException e) {
throw e;
} catch (Exception e) {
@@ -427,14 +423,6 @@ public class AuthorizerFactory implements
UserGroupProviderLookup, AccessPolicyP
return instance;
}
- private Authorizer createFrameworkAuthorizer(final Authorizer
baseAuthorizer) {
- if (baseAuthorizer instanceof ManagedAuthorizer) {
- return new FrameworkManagedAuthorizer((ManagedAuthorizer)
baseAuthorizer, registryService);
- } else {
- return new FrameworkAuthorizer(baseAuthorizer, registryService);
- }
- }
-
private void performMethodInjection(final Object instance, final Class
authorizerClass) throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
for (final Method method : authorizerClass.getMethods()) {
if (method.isAnnotationPresent(AuthorizerContext.class)) {
diff --git
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/FrameworkAuthorizer.java
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/FrameworkAuthorizer.java
deleted file mode 100644
index 08fb8f0..0000000
---
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/FrameworkAuthorizer.java
+++ /dev/null
@@ -1,189 +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;
-
-import org.apache.nifi.registry.bucket.Bucket;
-import org.apache.nifi.registry.exception.ResourceNotFoundException;
-import
org.apache.nifi.registry.security.authorization.exception.AccessDeniedException;
-import
org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
-import org.apache.nifi.registry.security.authorization.resource.Authorizable;
-import
org.apache.nifi.registry.security.authorization.resource.ResourceFactory;
-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.StandardNiFiUser;
-import
org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
-import
org.apache.nifi.registry.security.exception.SecurityProviderDestructionException;
-import org.apache.nifi.registry.service.RegistryService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Wraps an Authorizer and adds framework level logic for authorizing proxies,
public resources, and anything else
- * that needs to be done on top of the regular Authorizer.
- */
-public class FrameworkAuthorizer implements Authorizer {
-
- public static Logger LOGGER =
LoggerFactory.getLogger(FrameworkAuthorizer.class);
-
- private static final Authorizable PROXY_AUTHORIZABLE = new Authorizable() {
- @Override
- public Authorizable getParentAuthorizable() {
- return null;
- }
-
- @Override
- public Resource getResource() {
- return ResourceFactory.getProxyResource();
- }
- };
-
- private final Authorizer wrappedAuthorizer;
- private final RegistryService registryService;
-
- public FrameworkAuthorizer(final Authorizer wrappedAuthorizer, final
RegistryService registryService) {
- this.wrappedAuthorizer = Objects.requireNonNull(wrappedAuthorizer);
- this.registryService = Objects.requireNonNull(registryService);
- }
-
- @Override
- public void initialize(final AuthorizerInitializationContext
initializationContext) throws SecurityProviderCreationException {
- wrappedAuthorizer.initialize(initializationContext);
- }
-
- @Override
- public void onConfigured(final AuthorizerConfigurationContext
configurationContext) throws SecurityProviderCreationException {
- wrappedAuthorizer.onConfigured(configurationContext);
- }
-
- @Override
- public AuthorizationResult authorize(final AuthorizationRequest request)
throws AuthorizationAccessException {
- final Resource resource = request.getResource();
- final RequestAction requestAction = request.getAction();
-
- /**
- * If the request is for a resource that has been made public and
action is READ, then it should automatically be authorized.
- *
- * This needs to be checked before the proxy authorizations b/c access
to a public resource should always be allowed.
- */
-
- final boolean allowPublicAccess = isPublicAccessAllowed(resource,
requestAction);
- if (allowPublicAccess) {
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("Authorizing access to public resource '{}'", new
Object[]{resource.getIdentifier()});
- }
- return AuthorizationResult.approved();
- }
-
- /**
- * Deny an anonymous user access to anything else, they should only
have access to publicly readable resources checked above
- */
-
- if (request.isAnonymous()) {
- return AuthorizationResult.denied("Anonymous access is not
authorized");
- }
-
- /*
- * If the request has a proxy chain, ensure each identity in the chain
is an authorized proxy for the given action.
- *
- * The action comes from the original request. For example, if user1 is
proxied by proxy1, and it is a WRITE request
- * to /buckets/12345, then we need to determine if proxy1 is authorized
to proxy WRITE requests.
- */
-
- final List<String> proxyChainIdentities = request.getProxyIdentities();
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("Found {} proxy identities", new
Object[]{proxyChainIdentities.size()});
- }
-
- for (final String proxyIdentity : proxyChainIdentities) {
- final NiFiUser proxyNiFiUser = createProxyNiFiUser(proxyIdentity);
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("Authorizing proxy [{}] for {}", new
Object[]{proxyIdentity, requestAction});
- }
-
- try {
- PROXY_AUTHORIZABLE.authorize(wrappedAuthorizer, requestAction,
proxyNiFiUser);
- } catch (final AccessDeniedException e) {
- final String actionString = requestAction.toString();
- return AuthorizationResult.denied(String.format("Untrusted
proxy [%s] for %s operation.", proxyIdentity, actionString));
- }
- }
-
- /**
- * All other authorization decisions need to be delegated to the
original wrapped Authorizer.
- */
-
- return wrappedAuthorizer.authorize(request);
- }
-
- /**
- * Determines if the given Resource is considered public for the action
being performed.
- *
- * @param resource a Resource being authorized
- * @param action the action being performed
- * @return true if the resource is public for the given action, false
otherwise
- */
- private boolean isPublicAccessAllowed(final Resource resource, final
RequestAction action) {
- if (resource == null || action == null) {
- return false;
- }
-
- final String resourceIdentifier = resource.getIdentifier();
- if (resourceIdentifier == null ||
!resourceIdentifier.startsWith(ResourceType.Bucket.getValue() + "/")) {
- return false;
- }
-
- final int lastSlashIndex = resourceIdentifier.lastIndexOf("/");
- if (lastSlashIndex < 0 || lastSlashIndex >=
resourceIdentifier.length() - 1) {
- return false;
- }
-
- final String bucketId = resourceIdentifier.substring(lastSlashIndex +
1);
- try {
- final Bucket bucket = registryService.getBucket(bucketId);
- return bucket.isAllowPublicRead() && action == RequestAction.READ;
- } catch (ResourceNotFoundException rnfe) {
- // if not found then we can't determine public access, so return
false to delegate to regular authorizer
- LOGGER.debug("Cannot determine public access, bucket not found
with id [{}]", new Object[]{bucketId});
- return false;
- } catch (Exception e) {
- LOGGER.error("Error checking public access to bucket with id
[{}]", new Object[]{bucketId}, e);
- return false;
- }
- }
-
- /**
- * Creates a NiFiUser for the given proxy identity.
- *
- * This is only intended to be used for authorizing the given proxy
identity against the /proxy resource, so we
- * don't need to populate the rest of the info on this user.
- *
- * @param proxyIdentity the proxy identity
- * @return the NiFiUser
- */
- private NiFiUser createProxyNiFiUser(final String proxyIdentity) {
- return new StandardNiFiUser.Builder().identity(proxyIdentity).build();
- }
-
- @Override
- public void preDestruction() throws SecurityProviderDestructionException {
- wrappedAuthorizer.preDestruction();
- }
-
-}
diff --git
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/FrameworkManagedAuthorizer.java
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/FrameworkManagedAuthorizer.java
deleted file mode 100644
index 478482e..0000000
---
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/FrameworkManagedAuthorizer.java
+++ /dev/null
@@ -1,54 +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;
-
-import
org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
-import
org.apache.nifi.registry.security.authorization.exception.UninheritableAuthorizationsException;
-import org.apache.nifi.registry.service.RegistryService;
-
-/**
- * Similar to FrameworkAuthorizer, but specifically for wrapping a
ManagedAuthorizer.
- */
-public class FrameworkManagedAuthorizer extends FrameworkAuthorizer implements
ManagedAuthorizer {
-
- private final ManagedAuthorizer wrappedManagedAuthorizer;
-
- public FrameworkManagedAuthorizer(final ManagedAuthorizer
wrappedManagedAuthorizer, final RegistryService registryService) {
- super(wrappedManagedAuthorizer, registryService);
- this.wrappedManagedAuthorizer = wrappedManagedAuthorizer;
- }
-
- @Override
- public String getFingerprint() throws AuthorizationAccessException {
- return wrappedManagedAuthorizer.getFingerprint();
- }
-
- @Override
- public void inheritFingerprint(final String fingerprint) throws
AuthorizationAccessException {
- wrappedManagedAuthorizer.inheritFingerprint(fingerprint);
- }
-
- @Override
- public void checkInheritability(final String proposedFingerprint) throws
AuthorizationAccessException, UninheritableAuthorizationsException {
- wrappedManagedAuthorizer.checkInheritability(proposedFingerprint);
- }
-
- @Override
- public AccessPolicyProvider getAccessPolicyProvider() {
- return wrappedManagedAuthorizer.getAccessPolicyProvider();
- }
-}
diff --git
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/StandardAuthorizableLookup.java
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/StandardAuthorizableLookup.java
index 18c2a52..6f68ebe 100644
---
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/StandardAuthorizableLookup.java
+++
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/StandardAuthorizableLookup.java
@@ -17,15 +17,22 @@
package org.apache.nifi.registry.security.authorization;
import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.bucket.Bucket;
import org.apache.nifi.registry.exception.ResourceNotFoundException;
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.ProxyChainAuthorizable;
+import
org.apache.nifi.registry.security.authorization.resource.PublicCheckingAuthorizable;
import
org.apache.nifi.registry.security.authorization.resource.ResourceFactory;
import org.apache.nifi.registry.security.authorization.resource.ResourceType;
+import org.apache.nifi.registry.service.RegistryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
+import java.util.Objects;
+
@Component
public class StandardAuthorizableLookup implements AuthorizableLookup {
@@ -103,14 +110,21 @@ public class StandardAuthorizableLookup implements
AuthorizableLookup {
}
};
+ private final RegistryService registryService;
+
+ @Autowired
+ public StandardAuthorizableLookup(final RegistryService registryService) {
+ this.registryService = Objects.requireNonNull(registryService);
+ }
+
@Override
public Authorizable getActuatorAuthorizable() {
- return ACTUATOR_AUTHORIZABLE;
+ return new ProxyChainAuthorizable(ACTUATOR_AUTHORIZABLE,
PROXY_AUTHORIZABLE, this::isPublicAccessAllowed);
}
@Override
public Authorizable getSwaggerAuthorizable() {
- return SWAGGER_AUTHORIZABLE;
+ return new ProxyChainAuthorizable(SWAGGER_AUTHORIZABLE,
PROXY_AUTHORIZABLE, this::isPublicAccessAllowed);
}
@Override
@@ -120,34 +134,42 @@ public class StandardAuthorizableLookup implements
AuthorizableLookup {
@Override
public Authorizable getTenantsAuthorizable() {
- return TENANTS_AUTHORIZABLE;
+ return new ProxyChainAuthorizable(TENANTS_AUTHORIZABLE,
PROXY_AUTHORIZABLE, this::isPublicAccessAllowed);
}
@Override
public Authorizable getPoliciesAuthorizable() {
- return POLICIES_AUTHORIZABLE;
+ return new ProxyChainAuthorizable(POLICIES_AUTHORIZABLE,
PROXY_AUTHORIZABLE, this::isPublicAccessAllowed);
}
@Override
public Authorizable getBucketsAuthorizable() {
- return BUCKETS_AUTHORIZABLE;
+ return new ProxyChainAuthorizable(BUCKETS_AUTHORIZABLE,
PROXY_AUTHORIZABLE, this::isPublicAccessAllowed);
}
@Override
public Authorizable getBucketAuthorizable(String bucketIdentifier) {
- // Note - this returns a special Authorizable type that inherits
permissions from the parent Authorizable
- return new InheritingAuthorizable() {
+ // Note - this creates a special Authorizable type that inherits
permissions from the parent Authorizable
+ final Authorizable inheritingAuthorizable = new
InheritingAuthorizable() {
@Override
public Authorizable getParentAuthorizable() {
- return getBucketsAuthorizable();
+ // Use the unwrapped buckets authorizable here so that we
don't reauthorize the proxy chain
+ return BUCKETS_AUTHORIZABLE;
}
@Override
public Resource getResource() {
return ResourceFactory.getBucketResource(bucketIdentifier,
"Bucket with ID " + bucketIdentifier);
}
+
};
+
+ // Wrap the inheriting Authorizable with logic that first checks if
public access is allowed, if not then delegates to the inheriting Authorizable
+ final Authorizable publicCheckingAuthorizable = new
PublicCheckingAuthorizable(inheritingAuthorizable, this::isPublicAccessAllowed);
+
+ // Return ProxyChainAuthorizable -> public checking Authorizable ->
inheriting Authorizable
+ return new ProxyChainAuthorizable(publicCheckingAuthorizable,
PROXY_AUTHORIZABLE, this::isPublicAccessAllowed);
}
@Override
@@ -217,4 +239,44 @@ public class StandardAuthorizableLookup implements
AuthorizableLookup {
return authorizable;
}
+ /**
+ * Determines if the given Resource is considered public for the action
being performed.
+ *
+ * @param resource a Resource being authorized
+ * @param action the action being performed
+ * @return true if the resource is public for the given action, false
otherwise
+ */
+ private boolean isPublicAccessAllowed(final Resource resource, final
RequestAction action) {
+ if (resource == null || action == null) {
+ return false;
+ }
+
+ if (action != RequestAction.READ) {
+ return false;
+ }
+
+ final String resourceIdentifier = resource.getIdentifier();
+ if (resourceIdentifier == null ||
!resourceIdentifier.startsWith(ResourceType.Bucket.getValue() + "/")) {
+ return false;
+ }
+
+ final int lastSlashIndex = resourceIdentifier.lastIndexOf("/");
+ if (lastSlashIndex < 0 || lastSlashIndex >=
resourceIdentifier.length() - 1) {
+ return false;
+ }
+
+ final String bucketId = resourceIdentifier.substring(lastSlashIndex +
1);
+ try {
+ final Bucket bucket = registryService.getBucket(bucketId);
+ return bucket.isAllowPublicRead();
+ } catch (ResourceNotFoundException rnfe) {
+ // if not found then we can't determine public access, so return
false to delegate to regular authorizer
+ logger.debug("Cannot determine public access, bucket not found
with id [{}]", new Object[]{bucketId});
+ return false;
+ } catch (Exception e) {
+ logger.error("Error checking public access to bucket with id
[{}]", new Object[]{bucketId}, e);
+ return false;
+ }
+ }
+
}
diff --git
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/UntrustedProxyException.java
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/UntrustedProxyException.java
new file mode 100644
index 0000000..fbf1580
--- /dev/null
+++
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/UntrustedProxyException.java
@@ -0,0 +1,29 @@
+/*
+ * 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 class UntrustedProxyException extends RuntimeException {
+
+ public UntrustedProxyException(String message) {
+ super(message);
+ }
+
+ public UntrustedProxyException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/Authorizable.java
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/Authorizable.java
index 04cb469..c461965 100644
---
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/Authorizable.java
+++
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/Authorizable.java
@@ -27,9 +27,7 @@ import
org.apache.nifi.registry.security.authorization.UserContextKeys;
import
org.apache.nifi.registry.security.authorization.exception.AccessDeniedException;
import org.apache.nifi.registry.security.authorization.user.NiFiUser;
-import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
public interface Authorizable {
@@ -95,10 +93,6 @@ public interface Authorizable {
userContext = null;
}
- // Note: We don't include the proxy identities here since this is not
a direct attempt to access the resource and
- // we just want to determine if the end user is authorized. The proxy
identities will be authorized when calling
- // Authorizable.authorize() during a direct access attempt for a
resource.
-
final Resource resource = getResource();
final Resource requestedResource = getRequestedResource();
final AuthorizationRequest request = new AuthorizationRequest.Builder()
@@ -211,18 +205,10 @@ public interface Authorizable {
userContext = null;
}
- final List<String> proxyChain = new ArrayList<>();
- NiFiUser proxyUser = user.getChain();
- while (proxyUser != null) {
- proxyChain.add(proxyUser.getIdentity());
- proxyUser = proxyUser.getChain();
- }
-
final Resource resource = getResource();
final Resource requestedResource = getRequestedResource();
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.identity(user.getIdentity())
- .proxyIdentities(proxyChain)
.groups(user.getGroups())
.anonymous(user.isAnonymous())
.accessAttempt(true)
diff --git
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ProxyChainAuthorizable.java
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ProxyChainAuthorizable.java
new file mode 100644
index 0000000..aec8d76
--- /dev/null
+++
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ProxyChainAuthorizable.java
@@ -0,0 +1,145 @@
+/*
+ * 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.resource;
+
+import org.apache.nifi.registry.security.authorization.AuthorizationResult;
+import org.apache.nifi.registry.security.authorization.Authorizer;
+import org.apache.nifi.registry.security.authorization.RequestAction;
+import org.apache.nifi.registry.security.authorization.Resource;
+import org.apache.nifi.registry.security.authorization.UntrustedProxyException;
+import
org.apache.nifi.registry.security.authorization.exception.AccessDeniedException;
+import org.apache.nifi.registry.security.authorization.user.NiFiUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.BiFunction;
+
+/**
+ * Authorizable that wraps another Authorizable and applies logic for
authorizing the proxy chain, unless the resource
+ * allows public access, which then skips authorizing the proxy chain.
+ */
+public class ProxyChainAuthorizable implements Authorizable {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(ProxyChainAuthorizable.class);
+
+ private final Authorizable wrappedAuthorizable;
+ private final Authorizable proxyAuthorizable;
+ private final BiFunction<Resource,RequestAction,Boolean>
publicResourceCheck;
+
+ public ProxyChainAuthorizable(final Authorizable wrappedAuthorizable,
+ final Authorizable proxyAuthorizable,
+ final
BiFunction<Resource,RequestAction,Boolean> publicResourceCheck) {
+ this.wrappedAuthorizable = Objects.requireNonNull(wrappedAuthorizable);
+ this.proxyAuthorizable = Objects.requireNonNull(proxyAuthorizable);
+ this.publicResourceCheck = Objects.requireNonNull(publicResourceCheck);
+ }
+
+ @Override
+ public Authorizable getParentAuthorizable() {
+ if (wrappedAuthorizable.getParentAuthorizable() == null) {
+ return null;
+ } else {
+ final Authorizable parentAuthorizable =
wrappedAuthorizable.getParentAuthorizable();
+ return new ProxyChainAuthorizable(parentAuthorizable,
proxyAuthorizable, publicResourceCheck);
+ }
+ }
+
+ @Override
+ public Resource getResource() {
+ return wrappedAuthorizable.getResource();
+ }
+
+ @Override
+ public AuthorizationResult checkAuthorization(final Authorizer authorizer,
final RequestAction action, final NiFiUser user,
+ final Map<String, String>
resourceContext) {
+ final Resource requestResource =
wrappedAuthorizable.getRequestedResource();
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Requested resource is {}", new
Object[]{requestResource.getIdentifier()});
+ }
+
+ // if public access is allowed then we want to skip proxy
authorization so just return
+ final Boolean isPublicAccessAllowed =
publicResourceCheck.apply(requestResource, action);
+ if (isPublicAccessAllowed) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Proxy chain will not be checked, public access
is allowed for {} on {}",
+ new Object[]{action.toString(),
requestResource.getIdentifier()});
+ }
+ return AuthorizationResult.approved();
+ }
+
+ // otherwise public access is not allowed so check the proxy chain for
the given action
+ NiFiUser proxyUser = user.getChain();
+ while (proxyUser != null) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Checking proxy [{}] for {}", new
Object[]{proxyUser, action});
+ }
+
+ // if the proxy is denied then break out of the loop and return a
denied result
+ final AuthorizationResult proxyAuthorizationResult =
proxyAuthorizable.checkAuthorization(authorizer, action, proxyUser);
+ if (proxyAuthorizationResult.getResult() ==
AuthorizationResult.Result.Denied) {
+ final String deniedMessage = String.format("Untrusted proxy
[%s] for %s operation.", proxyUser.getIdentity(), action.toString());
+ return AuthorizationResult.denied(deniedMessage);
+ }
+
+ proxyUser = proxyUser.getChain();
+ }
+
+ // at this point the proxy chain was approved so continue to check the
original Authorizable
+ return wrappedAuthorizable.checkAuthorization(authorizer, action,
user, resourceContext);
+ }
+
+ @Override
+ public void authorize(final Authorizer authorizer, final RequestAction
action, final NiFiUser user,
+ final Map<String, String> resourceContext) throws
AccessDeniedException {
+ final Resource requestResource =
wrappedAuthorizable.getRequestedResource();
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Requested resource is {}", new
Object[]{requestResource.getIdentifier()});
+ }
+
+ // if public access is allowed then we want to skip proxy
authorization so just return
+ final Boolean isPublicAccessAllowed =
publicResourceCheck.apply(requestResource, action);
+ if (isPublicAccessAllowed) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Proxy chain will not be authorized, public
access is allowed for {} on {}",
+ new Object[]{action.toString(),
requestResource.getIdentifier()});
+ }
+ return;
+ }
+
+ // otherwise public access is not allowed so authorize proxy chain for
the given action
+ NiFiUser proxyUser = user.getChain();
+ while (proxyUser != null) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Authorizing proxy [{}] for {}", new
Object[]{proxyUser, action});
+ }
+
+ try {
+ proxyAuthorizable.authorize(authorizer, action, proxyUser);
+ } catch (final AccessDeniedException e) {
+ final String actionString = action.toString();
+ throw new UntrustedProxyException(String.format("Untrusted
proxy [%s] for %s operation.", proxyUser.getIdentity(), actionString));
+ }
+ proxyUser = proxyUser.getChain();
+ }
+
+ // at this point the proxy chain was authorized so continue to
authorize the original Authorizable
+ wrappedAuthorizable.authorize(authorizer, action, user,
resourceContext);
+ }
+
+}
diff --git
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/PublicCheckingAuthorizable.java
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/PublicCheckingAuthorizable.java
new file mode 100644
index 0000000..7cacb59
--- /dev/null
+++
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/PublicCheckingAuthorizable.java
@@ -0,0 +1,107 @@
+/*
+ * 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.resource;
+
+import org.apache.nifi.registry.security.authorization.AuthorizationResult;
+import org.apache.nifi.registry.security.authorization.Authorizer;
+import org.apache.nifi.registry.security.authorization.RequestAction;
+import org.apache.nifi.registry.security.authorization.Resource;
+import
org.apache.nifi.registry.security.authorization.exception.AccessDeniedException;
+import org.apache.nifi.registry.security.authorization.user.NiFiUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.BiFunction;
+
+/**
+ * Authorizable that first checks if public access is allowed for the resource
and action. If it is then it short-circuits
+ * and returns approved, otherwise it continues and delegates to the wrapped
Authorizable.
+ */
+public class PublicCheckingAuthorizable implements Authorizable {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(PublicCheckingAuthorizable.class);
+
+ private final Authorizable wrappedAuthorizable;
+ private final BiFunction<Resource, RequestAction,Boolean>
publicResourceCheck;
+
+ public PublicCheckingAuthorizable(final Authorizable wrappedAuthorizable,
+ final
BiFunction<Resource,RequestAction,Boolean> publicResourceCheck) {
+ this.wrappedAuthorizable = Objects.requireNonNull(wrappedAuthorizable);
+ this.publicResourceCheck = Objects.requireNonNull(publicResourceCheck);
+ }
+
+ @Override
+ public Authorizable getParentAuthorizable() {
+ return wrappedAuthorizable.getParentAuthorizable();
+ }
+
+ @Override
+ public Resource getResource() {
+ return wrappedAuthorizable.getResource();
+ }
+
+ @Override
+ public AuthorizationResult checkAuthorization(final Authorizer authorizer,
final RequestAction action, final NiFiUser user,
+ final Map<String, String>
resourceContext) {
+ final Resource resource = getResource();
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Requested resource is {}", new
Object[]{resource.getIdentifier()});
+ }
+
+ // if public access is allowed then return approved
+ final Boolean isPublicAccessAllowed =
publicResourceCheck.apply(resource, action);
+ if(isPublicAccessAllowed) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Public access is allowed for {}", new
Object[]{resource.getIdentifier()});
+ }
+ return AuthorizationResult.approved();
+ }
+
+ // otherwise delegate to the original inheriting authorizable
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Delegating to inheriting authorizable for {}", new
Object[]{resource.getIdentifier()});
+ }
+ return wrappedAuthorizable.checkAuthorization(authorizer, action,
user, resourceContext);
+ }
+
+ @Override
+ public void authorize(final Authorizer authorizer, final RequestAction
action, final NiFiUser user,
+ final Map<String, String> resourceContext) throws
AccessDeniedException {
+ final Resource resource = getResource();
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Requested resource is {}", new
Object[]{resource.getIdentifier()});
+ }
+
+ // if public access is allowed then skip authorization and return
+ final Boolean isPublicAccessAllowed =
publicResourceCheck.apply(resource, action);
+ if(isPublicAccessAllowed) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Public access is allowed for {}", new
Object[]{resource.getIdentifier()});
+ }
+ return;
+ }
+
+ // otherwise delegate to the original authorizable
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Delegating to inheriting authorizable for {}", new
Object[]{resource.getIdentifier()});
+ }
+
+ wrappedAuthorizable.authorize(authorizer, action, user,
resourceContext);
+ }
+}
diff --git
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java
index 12f39b6..503f27c 100644
---
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java
+++
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/AuthorizationService.java
@@ -39,6 +39,7 @@ import
org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProv
import org.apache.nifi.registry.security.authorization.Group;
import org.apache.nifi.registry.security.authorization.ManagedAuthorizer;
import org.apache.nifi.registry.security.authorization.RequestAction;
+import org.apache.nifi.registry.security.authorization.UntrustedProxyException;
import org.apache.nifi.registry.security.authorization.UserAndGroups;
import org.apache.nifi.registry.security.authorization.UserGroupProvider;
import
org.apache.nifi.registry.security.authorization.UserGroupProviderInitializationContext;
@@ -487,7 +488,7 @@ public class AuthorizationService {
.getAuthorizableByResource(resource.getIdentifier())
.authorize(authorizer, actionType,
NiFiUserUtils.getNiFiUser());
return true;
- } catch (AccessDeniedException e) {
+ } catch (AccessDeniedException |
UntrustedProxyException e) {
return false;
}
})
diff --git
a/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy
b/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy
index 33b9f40..8035bd8 100644
---
a/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy
+++
b/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy
@@ -41,8 +41,7 @@ class AuthorizationServiceSpec extends Specification {
def setup() {
accessPolicyProvider.getUserGroupProvider() >> userGroupProvider
def standardAuthorizer = new
StandardManagedAuthorizer(accessPolicyProvider, userGroupProvider)
- def frameworkAuthorizer = new
FrameworkManagedAuthorizer(standardAuthorizer, registryService)
- authorizationService = new AuthorizationService(authorizableLookup,
frameworkAuthorizer, registryService)
+ authorizationService = new AuthorizationService(authorizableLookup,
standardAuthorizer, registryService)
}
// ----- User tests -------------------------------------------------------
diff --git
a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/TestFrameworkAuthorizer.java
b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/TestFrameworkAuthorizer.java
deleted file mode 100644
index 2cc03f8..0000000
---
a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/TestFrameworkAuthorizer.java
+++ /dev/null
@@ -1,278 +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;
-
-import org.apache.nifi.registry.bucket.Bucket;
-import
org.apache.nifi.registry.security.authorization.resource.ResourceFactory;
-import org.apache.nifi.registry.service.RegistryService;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentMatcher;
-
-import java.util.Arrays;
-import java.util.UUID;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.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 TestFrameworkAuthorizer {
-
- private Authorizer frameworkAuthorizer;
- private Authorizer wrappedAuthorizer;
- private RegistryService registryService;
-
- private Bucket bucketPublic;
- private Bucket bucketNotPublic;
-
- @Before
- public void setup() {
- wrappedAuthorizer = mock(Authorizer.class);
- registryService = mock(RegistryService.class);
- frameworkAuthorizer = new FrameworkAuthorizer(wrappedAuthorizer,
registryService);
-
- bucketPublic = new Bucket();
- bucketPublic.setIdentifier(UUID.randomUUID().toString());
- bucketPublic.setName("Public Bucket");
- bucketPublic.setAllowPublicRead(true);
-
- bucketNotPublic = new Bucket();
- bucketNotPublic.setIdentifier(UUID.randomUUID().toString());
- bucketNotPublic.setName("Non Public Bucket");
- bucketNotPublic.setAllowPublicRead(false);
-
-
when(registryService.getBucket(bucketPublic.getIdentifier())).thenReturn(bucketPublic);
-
when(registryService.getBucket(bucketNotPublic.getIdentifier())).thenReturn(bucketNotPublic);
- }
-
- @Test
- public void testReadPublicBucketWhenAnonymous() {
- final Resource resource =
ResourceFactory.getBucketResource(bucketPublic.getIdentifier(),
bucketPublic.getName());
-
- final AuthorizationRequest request = new AuthorizationRequest.Builder()
- .resource(resource)
- .requestedResource(resource)
- .action(RequestAction.READ)
- .accessAttempt(true)
- .identity("anonymous")
- .anonymous(true)
- .build();
-
- final AuthorizationResult result =
frameworkAuthorizer.authorize(request);
- assertNotNull(result);
- assertEquals(AuthorizationResult.Result.Approved, result.getResult());
-
- // should never make it to wrapped authorizer
- verify(wrappedAuthorizer,
times(0)).authorize(any(AuthorizationRequest.class));
- }
-
- @Test
- public void testReadNonPublicBucketWhenAnonymous() {
- final Resource resource =
ResourceFactory.getBucketResource(bucketNotPublic.getIdentifier(),
bucketNotPublic.getName());
-
- final AuthorizationRequest request = new AuthorizationRequest.Builder()
- .resource(resource)
- .requestedResource(resource)
- .action(RequestAction.READ)
- .accessAttempt(true)
- .identity("anonymous")
- .anonymous(true)
- .build();
-
- final AuthorizationResult result =
frameworkAuthorizer.authorize(request);
- assertNotNull(result);
- assertEquals(AuthorizationResult.Result.Denied, result.getResult());
-
- // should be denied before making it to the wrapped authorizer since
the user is anonymous
- verify(wrappedAuthorizer,
times(0)).authorize(any(AuthorizationRequest.class));
- }
-
- @Test
- public void testWritePublicBucketWhenAnonymous() {
- final Resource resource =
ResourceFactory.getBucketResource(bucketPublic.getIdentifier(),
bucketPublic.getName());
-
- final AuthorizationRequest request = new AuthorizationRequest.Builder()
- .resource(resource)
- .requestedResource(resource)
- .action(RequestAction.WRITE)
- .accessAttempt(true)
- .identity("anonymous")
- .anonymous(true)
- .build();
-
- final AuthorizationResult result =
frameworkAuthorizer.authorize(request);
- assertNotNull(result);
- assertEquals(AuthorizationResult.Result.Denied, result.getResult());
-
- // should be denied before making it to wrapped authorizer since
request is anonymous
- verify(wrappedAuthorizer,
times(0)).authorize(any(AuthorizationRequest.class));
- }
-
- @Test
- public void testReadPublicBucketWhenNotAnonymous() {
- final Resource resource =
ResourceFactory.getBucketResource(bucketPublic.getIdentifier(),
bucketPublic.getName());
-
- final AuthorizationRequest request = new AuthorizationRequest.Builder()
- .resource(resource)
- .requestedResource(resource)
- .action(RequestAction.READ)
- .accessAttempt(true)
- .identity("user1")
- .anonymous(false)
- .proxyIdentities(Arrays.asList("proxy1", "proxy2"))
- .build();
-
- final AuthorizationResult result =
frameworkAuthorizer.authorize(request);
- assertNotNull(result);
- assertEquals(AuthorizationResult.Result.Approved, result.getResult());
-
- // should never make it to wrapped authorizer
- verify(wrappedAuthorizer,
times(0)).authorize(any(AuthorizationRequest.class));
- }
-
- @Test
- public void testReadNonPublicBucketWhenNotAnonymousAndAuthorizedProxies() {
- final Resource resource =
ResourceFactory.getBucketResource(bucketNotPublic.getIdentifier(),
bucketNotPublic.getName());
-
- final AuthorizationRequest request = new AuthorizationRequest.Builder()
- .resource(resource)
- .requestedResource(resource)
- .action(RequestAction.READ)
- .accessAttempt(true)
- .identity("user1")
- .anonymous(false)
- .proxyIdentities(Arrays.asList("proxy1", "proxy2"))
- .build();
-
- // since the bucket is not public it will fall through to the wrapped
authorizer
- when(wrappedAuthorizer.authorize(any(AuthorizationRequest.class)))
- .thenReturn(AuthorizationResult.approved());
-
- final AuthorizationResult result =
frameworkAuthorizer.authorize(request);
- assertNotNull(result);
- assertEquals(AuthorizationResult.Result.Approved, result.getResult());
-
- // should make 3 calls to the wrapped authorizer to authorize user1,
proxy1, proxy2
- verify(wrappedAuthorizer,
times(3)).authorize(any(AuthorizationRequest.class));
- }
-
- @Test
- public void testReadNonPublicBucketWhenNotAnonymousAndUnauthorizedProxy() {
- final Resource resource =
ResourceFactory.getBucketResource(bucketNotPublic.getIdentifier(),
bucketNotPublic.getName());
-
- final AuthorizationRequest request = new AuthorizationRequest.Builder()
- .resource(resource)
- .requestedResource(resource)
- .action(RequestAction.READ)
- .accessAttempt(true)
- .identity("user1")
- .anonymous(false)
- .proxyIdentities(Arrays.asList("proxy1", "proxy2"))
- .build();
-
- // since the bucket is not public and the user is not anonymous, it
will continue to proxy authorization
-
- // simulate the first proxy being authorized for READ actions
- final AuthorizationRequestMatcher proxy1Matcher = new
AuthorizationRequestMatcher(
- "proxy1", ResourceFactory.getProxyResource(),
request.getAction());
-
when(wrappedAuthorizer.authorize(argThat(proxy1Matcher))).thenReturn(AuthorizationResult.approved());
-
- // simulate the second proxy being unauthorized for READ actions
- final AuthorizationRequestMatcher proxy2Matcher = new
AuthorizationRequestMatcher(
- "proxy2", ResourceFactory.getProxyResource(),
request.getAction());
-
when(wrappedAuthorizer.authorize(argThat(proxy2Matcher))).thenReturn(AuthorizationResult.denied("denied"));
-
- final AuthorizationResult result =
frameworkAuthorizer.authorize(request);
- assertNotNull(result);
- assertEquals(AuthorizationResult.Result.Denied, result.getResult());
-
- // should make 2 calls to the wrapped authorizer for the two proxies
- verify(wrappedAuthorizer,
times(2)).authorize(any(AuthorizationRequest.class));
- }
-
- @Test
- public void
testReadNonPublicBucketWhenNotAnonymousAndUnauthorizedEndUser() {
- final Resource resource =
ResourceFactory.getBucketResource(bucketNotPublic.getIdentifier(),
bucketNotPublic.getName());
-
- final AuthorizationRequest request = new AuthorizationRequest.Builder()
- .resource(resource)
- .requestedResource(resource)
- .action(RequestAction.READ)
- .accessAttempt(true)
- .identity("user1")
- .anonymous(false)
- .proxyIdentities(Arrays.asList("proxy1", "proxy2"))
- .build();
-
- // since the bucket is not public and the user is not anonymous, it
will continue to proxy authorization
-
- // simulate the first proxy being authorized for READ actions
- final AuthorizationRequestMatcher proxy1Matcher = new
AuthorizationRequestMatcher(
- "proxy1", ResourceFactory.getProxyResource(),
request.getAction());
-
when(wrappedAuthorizer.authorize(argThat(proxy1Matcher))).thenReturn(AuthorizationResult.approved());
-
- // simulate the second proxy being authorized for READ actions
- final AuthorizationRequestMatcher proxy2Matcher = new
AuthorizationRequestMatcher(
- "proxy2", ResourceFactory.getProxyResource(),
request.getAction());
-
when(wrappedAuthorizer.authorize(argThat(proxy2Matcher))).thenReturn(AuthorizationResult.approved());
-
- // simulate the end user being unauthorized for READ actions
- final AuthorizationRequestMatcher user1Matcher = new
AuthorizationRequestMatcher(
- "user1", resource, request.getAction());
-
when(wrappedAuthorizer.authorize(argThat(user1Matcher))).thenReturn(AuthorizationResult.denied("denied"));
-
- final AuthorizationResult result =
frameworkAuthorizer.authorize(request);
- assertNotNull(result);
- assertEquals(AuthorizationResult.Result.Denied, result.getResult());
-
- // should make 3 calls to the wrapped authorizer for the two proxies
and end user
- verify(wrappedAuthorizer,
times(3)).authorize(any(AuthorizationRequest.class));
- }
-
-
- /**
- * Matcher for matching Authorization requests.
- */
- private static class AuthorizationRequestMatcher implements
ArgumentMatcher<AuthorizationRequest> {
-
- private final String identity;
- private final Resource resource;
- private final RequestAction action;
-
- public AuthorizationRequestMatcher(final String identity, final
Resource resource, final RequestAction action) {
- this.identity = identity;
- this.resource = resource;
- this.action = action;
- }
-
- @Override
- public boolean matches(final AuthorizationRequest request) {
- if (request == null) {
- return false;
- }
-
- return identity.equals(request.getIdentity())
- &&
resource.getIdentifier().equals(request.getResource().getIdentifier())
- && action == request.getAction();
- }
- }
-}
diff --git
a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/TestStandardAuthorizableLookup.java
b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/TestStandardAuthorizableLookup.java
new file mode 100644
index 0000000..2804ac7
--- /dev/null
+++
b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/TestStandardAuthorizableLookup.java
@@ -0,0 +1,404 @@
+/*
+ * 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.bucket.Bucket;
+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.ResourceFactory;
+import org.apache.nifi.registry.security.authorization.user.NiFiUser;
+import org.apache.nifi.registry.security.authorization.user.StandardNiFiUser;
+import org.apache.nifi.registry.service.RegistryService;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.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 TestStandardAuthorizableLookup {
+
+ private static final NiFiUser USER_NO_PROXY_CHAIN = new
StandardNiFiUser.Builder()
+ .identity("user1")
+ .build();
+
+ private static final NiFiUser USER_WITH_PROXY_CHAIN = new
StandardNiFiUser.Builder()
+ .identity("user1")
+ .chain(new StandardNiFiUser.Builder().identity("CN=localhost,
OU=NIFI").build())
+ .build();
+
+ private Authorizer authorizer;
+ private RegistryService registryService;
+ private AuthorizableLookup authorizableLookup;
+
+ private Bucket bucketPublic;
+ private Bucket bucketNotPublic;
+
+ @Before
+ public void setup() {
+ authorizer = mock(Authorizer.class);
+ registryService = mock(RegistryService.class);
+ authorizableLookup = new StandardAuthorizableLookup(registryService);
+
+ bucketPublic = new Bucket();
+ bucketPublic.setIdentifier(UUID.randomUUID().toString());
+ bucketPublic.setName("Public Bucket");
+ bucketPublic.setAllowPublicRead(true);
+
+ bucketNotPublic = new Bucket();
+ bucketNotPublic.setIdentifier(UUID.randomUUID().toString());
+ bucketNotPublic.setName("Non Public Bucket");
+ bucketNotPublic.setAllowPublicRead(false);
+
+
when(registryService.getBucket(bucketPublic.getIdentifier())).thenReturn(bucketPublic);
+
when(registryService.getBucket(bucketNotPublic.getIdentifier())).thenReturn(bucketNotPublic);
+ }
+
+ // Test check method for Bucket Authorizable
+
+ @Test
+ public void testCheckReadPublicBucketWithNoProxyChain() {
+ final Authorizable bucketAuthorizable =
authorizableLookup.getBucketAuthorizable(bucketPublic.getIdentifier());
+ final AuthorizationResult result =
bucketAuthorizable.checkAuthorization(authorizer, RequestAction.READ,
USER_NO_PROXY_CHAIN);
+ assertNotNull(result);
+ assertEquals(AuthorizationResult.Result.Approved, result.getResult());
+
+ // Should never call authorizer because resource is public
+ verify(authorizer,
times(0)).authorize(any(AuthorizationRequest.class));
+ }
+
+ @Test
+ public void testCheckReadPublicBucketWithProxyChain() {
+ final Authorizable bucketAuthorizable =
authorizableLookup.getBucketAuthorizable(bucketPublic.getIdentifier());
+ final AuthorizationResult result =
bucketAuthorizable.checkAuthorization(authorizer, RequestAction.READ,
USER_WITH_PROXY_CHAIN);
+ assertNotNull(result);
+ assertEquals(AuthorizationResult.Result.Approved, result.getResult());
+
+ // Should never call authorizer because resource is public
+ verify(authorizer,
times(0)).authorize(any(AuthorizationRequest.class));
+ }
+
+ @Test
+ public void
testCheckWritePublicBucketWithUnauthorizedUserAndNoProxyChain() {
+ final RequestAction action = RequestAction.WRITE;
+
+ // first request will be to the specific bucket
+ final AuthorizationRequest expectedBucketAuthorizationRequest =
getBucketAuthorizationRequest(
+ bucketPublic.getIdentifier(), action, USER_NO_PROXY_CHAIN);
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedBucketAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.denied());
+
+ // second request will go to parent of /buckets
+ final AuthorizationRequest expectedBucketsAuthorizationRequest =
getBucketsAuthorizationRequest(action, USER_NO_PROXY_CHAIN);
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedBucketsAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.denied());
+
+ // should reach authorizer and return denied
+ final Authorizable bucketAuthorizable =
authorizableLookup.getBucketAuthorizable(bucketPublic.getIdentifier());
+ final AuthorizationResult result =
bucketAuthorizable.checkAuthorization(authorizer, action, USER_NO_PROXY_CHAIN);
+ assertNotNull(result);
+ assertEquals(AuthorizationResult.Result.Denied, result.getResult());
+
+ // Should call authorizer twice for specific bucket and top-level
/buckets
+ verify(authorizer,
times(2)).authorize(any(AuthorizationRequest.class));
+ }
+
+ @Test
+ public void testCheckWritePublicBucketWithUnauthorizedProxyChain() {
+ final RequestAction action = RequestAction.WRITE;
+
+ // first request will be to authorize the proxy
+ final AuthorizationRequest expectedProxyAuthorizationRequest =
getProxyAuthorizationRequest(action, USER_WITH_PROXY_CHAIN.getChain());
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedProxyAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.denied());
+
+ // the authorization of the proxy chain should return denied
+ final Authorizable bucketAuthorizable =
authorizableLookup.getBucketAuthorizable(bucketPublic.getIdentifier());
+ final AuthorizationResult result =
bucketAuthorizable.checkAuthorization(authorizer, action,
USER_WITH_PROXY_CHAIN);
+ assertNotNull(result);
+ assertEquals(AuthorizationResult.Result.Denied, result.getResult());
+
+ // Should never call authorizer once for /proxy and then return denied
+ verify(authorizer,
times(1)).authorize(any(AuthorizationRequest.class));
+ }
+
+ @Test
+ public void
testCheckWritePublicBucketWithUnauthorizedUserAndAuthorizedProxyChain() {
+ final NiFiUser user = USER_WITH_PROXY_CHAIN;
+ final RequestAction action = RequestAction.WRITE;
+
+ // first request will be to authorize the proxy
+ final AuthorizationRequest expectedProxyAuthorizationRequest =
getProxyAuthorizationRequest(action, user.getChain());
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedProxyAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.approved());
+
+ // second request will be to the specific bucket
+ final AuthorizationRequest expectedBucketAuthorizationRequest =
getBucketAuthorizationRequest(
+ bucketPublic.getIdentifier(), action, user);
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedBucketAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.denied());
+
+ // third request will go to parent of /buckets
+ final AuthorizationRequest expectedBucketsAuthorizationRequest =
getBucketsAuthorizationRequest(action, user);
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedBucketsAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.denied());
+
+ // the authorization of the proxy chain should return denied
+ final Authorizable bucketAuthorizable =
authorizableLookup.getBucketAuthorizable(bucketPublic.getIdentifier());
+ final AuthorizationResult result =
bucketAuthorizable.checkAuthorization(authorizer, action, user);
+ assertNotNull(result);
+ assertEquals(AuthorizationResult.Result.Denied, result.getResult());
+
+ // Should call authorizer three time for /proxy, /bucket/{id}, and
/buckets
+ verify(authorizer,
times(3)).authorize(any(AuthorizationRequest.class));
+ }
+
+ @Test
+ public void
testCheckWritePublicBucketWithAuthorizedUserAndAuthorizedProxyChain() {
+ final NiFiUser user = USER_WITH_PROXY_CHAIN;
+ final RequestAction action = RequestAction.WRITE;
+
+ // first request will be to authorize the proxy
+ final AuthorizationRequest expectedProxyAuthorizationRequest =
getProxyAuthorizationRequest(action, user.getChain());
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedProxyAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.approved());
+
+ // second request will be to the specific bucket
+ final AuthorizationRequest expectedBucketAuthorizationRequest =
getBucketAuthorizationRequest(
+ bucketPublic.getIdentifier(), action, user);
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedBucketAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.approved());
+
+ // the authorization should all return approved
+ final Authorizable bucketAuthorizable =
authorizableLookup.getBucketAuthorizable(bucketPublic.getIdentifier());
+ final AuthorizationResult result =
bucketAuthorizable.checkAuthorization(authorizer, action, user);
+ assertNotNull(result);
+ assertEquals(AuthorizationResult.Result.Approved, result.getResult());
+
+ // Should call authorizer two times for /proxy and /bucket/{id}
+ verify(authorizer,
times(2)).authorize(any(AuthorizationRequest.class));
+ }
+
+ // Test authorize method for Bucket Authorizable
+
+ @Test
+ public void testAuthorizeReadPublicBucketWithNoProxyChain() {
+ final Authorizable bucketAuthorizable =
authorizableLookup.getBucketAuthorizable(bucketPublic.getIdentifier());
+ bucketAuthorizable.authorize(authorizer, RequestAction.READ,
USER_NO_PROXY_CHAIN);
+
+ // Should never call authorizer because resource is public
+ verify(authorizer,
times(0)).authorize(any(AuthorizationRequest.class));
+ }
+
+ @Test
+ public void testAuthorizeReadPublicBucketWithProxyChain() {
+ final Authorizable bucketAuthorizable =
authorizableLookup.getBucketAuthorizable(bucketPublic.getIdentifier());
+ bucketAuthorizable.authorize(authorizer, RequestAction.READ,
USER_WITH_PROXY_CHAIN);
+
+ // Should never call authorizer because resource is public
+ verify(authorizer,
times(0)).authorize(any(AuthorizationRequest.class));
+ }
+
+ @Test
+ public void
testAuthorizeWritePublicBucketWithUnauthorizedUserAndNoProxyChain() {
+ final RequestAction action = RequestAction.WRITE;
+
+ // first request will be to the specific bucket
+ final AuthorizationRequest expectedBucketAuthorizationRequest =
getBucketAuthorizationRequest(
+ bucketPublic.getIdentifier(), action, USER_NO_PROXY_CHAIN);
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedBucketAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.denied());
+
+ // second request will go to parent of /buckets
+ final AuthorizationRequest expectedBucketsAuthorizationRequest =
getBucketsAuthorizationRequest(action, USER_NO_PROXY_CHAIN);
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedBucketsAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.denied());
+
+ // should reach authorizer and throw access denied
+ final Authorizable bucketAuthorizable =
authorizableLookup.getBucketAuthorizable(bucketPublic.getIdentifier());
+ try {
+ bucketAuthorizable.authorize(authorizer, action,
USER_NO_PROXY_CHAIN);
+ Assert.fail("Should have thrown exception");
+ } catch (AccessDeniedException e) {
+ // Should never call authorizer twice for specific bucket and
top-level /buckets
+ verify(authorizer,
times(2)).authorize(any(AuthorizationRequest.class));
+ }
+ }
+
+ @Test
+ public void testAuthorizeWritePublicBucketWithUnauthorizedProxyChain() {
+ final RequestAction action = RequestAction.WRITE;
+
+ // first request will be to authorize the proxy
+ final AuthorizationRequest expectedProxyAuthorizationRequest =
getProxyAuthorizationRequest(action, USER_WITH_PROXY_CHAIN.getChain());
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedProxyAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.denied());
+
+ // the authorization of the proxy chain should throw
UntrustedProxyException
+ final Authorizable bucketAuthorizable =
authorizableLookup.getBucketAuthorizable(bucketPublic.getIdentifier());
+ try {
+ bucketAuthorizable.authorize(authorizer, action,
USER_WITH_PROXY_CHAIN);
+ Assert.fail("Should have thrown exception");
+ } catch (UntrustedProxyException e) {
+ // Should call authorizer once for /proxy and then throw exception
+ verify(authorizer,
times(1)).authorize(any(AuthorizationRequest.class));
+ }
+ }
+
+ @Test
+ public void
testAuthorizeWritePublicBucketWithUnauthorizedUserAndAuthorizedProxyChain() {
+ final NiFiUser user = USER_WITH_PROXY_CHAIN;
+ final RequestAction action = RequestAction.WRITE;
+
+ // first request will be to authorize the proxy
+ final AuthorizationRequest expectedProxyAuthorizationRequest =
getProxyAuthorizationRequest(action, user.getChain());
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedProxyAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.approved());
+
+ // second request will be to the specific bucket
+ final AuthorizationRequest expectedBucketAuthorizationRequest =
getBucketAuthorizationRequest(
+ bucketPublic.getIdentifier(), action, user);
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedBucketAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.denied());
+
+ // third request will go to parent of /buckets
+ final AuthorizationRequest expectedBucketsAuthorizationRequest =
getBucketsAuthorizationRequest(action, user);
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedBucketsAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.denied());
+
+ // the authorization of the proxy chain should throw
UntrustedProxyException
+ final Authorizable bucketAuthorizable =
authorizableLookup.getBucketAuthorizable(bucketPublic.getIdentifier());
+ try {
+ bucketAuthorizable.authorize(authorizer, action, user);
+ Assert.fail("Should have thrown exception");
+ } catch (AccessDeniedException e) {
+ // Should call authorizer three times for /proxy, /bucket/{id},
and /buckets
+ verify(authorizer,
times(3)).authorize(any(AuthorizationRequest.class));
+ }
+ }
+
+ @Test
+ public void
testAuthorizeWritePublicBucketWithAuthorizedUserAndAuthorizedProxyChain() {
+ final NiFiUser user = USER_WITH_PROXY_CHAIN;
+ final RequestAction action = RequestAction.WRITE;
+
+ // first request will be to authorize the proxy
+ final AuthorizationRequest expectedProxyAuthorizationRequest =
getProxyAuthorizationRequest(action, user.getChain());
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedProxyAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.approved());
+
+ // second request will be to the specific bucket
+ final AuthorizationRequest expectedBucketAuthorizationRequest =
getBucketAuthorizationRequest(
+ bucketPublic.getIdentifier(), action, user);
+
+ when(authorizer.authorize(argThat(new
AuthorizationRequestMatcher(expectedBucketAuthorizationRequest))))
+ .thenReturn(AuthorizationResult.approved());
+
+ // the authorization should all return approved so no exception
+ final Authorizable bucketAuthorizable =
authorizableLookup.getBucketAuthorizable(bucketPublic.getIdentifier());
+ bucketAuthorizable.authorize(authorizer, action, user);
+
+ // Should call authorizer two times for /proxy and /bucket/{id}
+ verify(authorizer,
times(2)).authorize(any(AuthorizationRequest.class));
+ }
+
+ private AuthorizationRequest getBucketAuthorizationRequest(final String
bucketIdentifier, final RequestAction action, final NiFiUser user) {
+ return new AuthorizationRequest.Builder()
+ .resource(ResourceFactory.getBucketResource(bucketIdentifier,
bucketIdentifier))
+ .action(action)
+ .identity(user.getIdentity())
+ .accessAttempt(true)
+ .anonymous(false)
+ .build();
+ }
+
+ private AuthorizationRequest getBucketsAuthorizationRequest(final
RequestAction action, final NiFiUser user) {
+ return new AuthorizationRequest.Builder()
+ .resource(ResourceFactory.getBucketsResource())
+ .action(action)
+ .identity(user.getIdentity())
+ .accessAttempt(true)
+ .anonymous(false)
+ .build();
+ }
+
+ private AuthorizationRequest getProxyAuthorizationRequest(final
RequestAction action, final NiFiUser user) {
+ return new AuthorizationRequest.Builder()
+ .resource(ResourceFactory.getProxyResource())
+ .action(action)
+ .identity(user.getIdentity())
+ .accessAttempt(true)
+ .anonymous(false)
+ .build();
+ }
+
+ /**
+ * ArugmentMatcher for AuthorizationRequest.
+ */
+ private static class AuthorizationRequestMatcher implements
ArgumentMatcher<AuthorizationRequest> {
+
+ private final AuthorizationRequest expectedAuthorizationRequest;
+
+ public AuthorizationRequestMatcher(final AuthorizationRequest
expectedAuthorizationRequest) {
+ this.expectedAuthorizationRequest = expectedAuthorizationRequest;
+ }
+
+ @Override
+ public boolean matches(final AuthorizationRequest
authorizationRequest) {
+ if (authorizationRequest == null) {
+ return false;
+ }
+
+ final String requestResourceId =
authorizationRequest.getResource().getIdentifier();
+ final String expectedResourceId =
expectedAuthorizationRequest.getResource().getIdentifier();
+
+ final String requestAction =
authorizationRequest.getAction().toString();
+ final String expectedAction =
expectedAuthorizationRequest.getAction().toString();
+
+ final String requestUserIdentity =
authorizationRequest.getIdentity();
+ final String expectedUserIdentity =
authorizationRequest.getIdentity();
+
+ return requestResourceId.equals(expectedResourceId)
+ && requestAction.equals(expectedAction)
+ && requestUserIdentity.equals(expectedUserIdentity);
+ }
+ }
+}
diff --git
a/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizationRequest.java
b/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizationRequest.java
index 56b7b45..3e832fa 100644
---
a/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizationRequest.java
+++
b/nifi-registry-core/nifi-registry-security-api/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizationRequest.java
@@ -109,6 +109,8 @@ public class AuthorizationRequest {
* The identities in the proxy chain for the request. Will be empty if the
request was not proxied.
*
* @return The identities in the proxy chain
+ *
+ * @deprecated no longer populated
*/
public List<String> getProxyIdentities() {
return proxyIdentities;
@@ -210,6 +212,9 @@ public class AuthorizationRequest {
return this;
}
+ /**
+ * @deprecated no longer populated by the framework
+ */
public Builder proxyIdentities(final List<String> proxyIdentities) {
this.proxyIdentities = proxyIdentities;
return this;
diff --git
a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/UntrustedProxyExceptionMapper.java
b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/UntrustedProxyExceptionMapper.java
new file mode 100644
index 0000000..453dbd5
--- /dev/null
+++
b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/mapper/UntrustedProxyExceptionMapper.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.mapper;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.security.authorization.UntrustedProxyException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Maps an UntrustedProxyException to a FORBIDDEN response.
+ */
+@Component
+@Provider
+public class UntrustedProxyExceptionMapper implements
ExceptionMapper<UntrustedProxyException> {
+
+ private static Logger LOGGER =
LoggerFactory.getLogger(UntrustedProxyException.class);
+
+ @Override
+ public Response toResponse(final UntrustedProxyException exception) {
+ LOGGER.info("{}. Returning {} response.", exception,
Response.Status.FORBIDDEN);
+ LOGGER.debug(StringUtils.EMPTY, exception);
+
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity(exception.getMessage())
+ .type("text/plain")
+ .build();
+ }
+}
diff --git
a/nifi-registry-core/nifi-registry-web-api/src/test/groovy/org/apache/nifi/registry/security/authorization/ResourceAuthorizationFilterSpec.groovy
b/nifi-registry-core/nifi-registry-web-api/src/test/groovy/org/apache/nifi/registry/security/authorization/ResourceAuthorizationFilterSpec.groovy
index e27dfbe..806f73c 100644
---
a/nifi-registry-core/nifi-registry-web-api/src/test/groovy/org/apache/nifi/registry/security/authorization/ResourceAuthorizationFilterSpec.groovy
+++
b/nifi-registry-core/nifi-registry-web-api/src/test/groovy/org/apache/nifi/registry/security/authorization/ResourceAuthorizationFilterSpec.groovy
@@ -20,6 +20,7 @@ import
org.apache.nifi.registry.security.authorization.exception.AccessDeniedExc
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.service.RegistryService
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
@@ -34,7 +35,8 @@ import javax.servlet.http.HttpServletResponse
class ResourceAuthorizationFilterSpec extends Specification {
- AuthorizableLookup authorizableLookup = new StandardAuthorizableLookup()
+ RegistryService registryService = Mock(RegistryService)
+ AuthorizableLookup authorizableLookup = new
StandardAuthorizableLookup(registryService)
AuthorizationService mockAuthorizationService = Mock(AuthorizationService)
FilterChain mockFilterChain = Mock(FilterChain)
ResourceAuthorizationFilter.Builder resourceAuthorizationFilterBuilder