NIFIREG-206 Support anonymous access to public resources
- Enabled anonymous user filter in all modes
- Added supportsLogin flag to CurrentUser
- Moved proxy authorization into FrameworkAuthorizer
- Added public read flag to bucket table and updated services
- Added logic for public buckets into framework authorizer and authorization service
- Added unit tests for FrameworkAuthorizer
- Updated front-end to display login link and allow anonymous user to reach login page
- Added close button to login form
- Added public checkbox to create bucket page
- Addressing review feedback
- Ensuring new checkbox is disabled when running unsecurely
- Removing unused code
This closes #197.
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/authorization/CurrentUser.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/authorization/CurrentUser.java
index 1bd4d4e..af135ec 100644
--- a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/authorization/CurrentUser.java
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/authorization/CurrentUser.java
@@ -24,6 +24,7 @@
private String identity;
private boolean anonymous;
+ private boolean loginSupported;
private ResourcePermissions resourcePermissions;
@ApiModelProperty(value = "The identity of the current user", readOnly = true)
@@ -44,6 +45,15 @@
this.anonymous = anonymous;
}
+ @ApiModelProperty(value = "Indicates if the NiFi instance supports logging in")
+ public boolean isLoginSupported() {
+ return loginSupported;
+ }
+
+ public void setLoginSupported(boolean loginSupported) {
+ this.loginSupported = loginSupported;
+ }
+
@ApiModelProperty(value = "The access that the current user has to top level resources", readOnly = true)
public ResourcePermissions getResourcePermissions() {
return resourcePermissions;
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/bucket/Bucket.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/bucket/Bucket.java
index 6837a6d..9cd82e0 100644
--- a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/bucket/Bucket.java
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/bucket/Bucket.java
@@ -43,6 +43,8 @@
private Boolean allowBundleRedeploy;
+ private Boolean allowPublicRead;
+
private Permissions permissions;
@ApiModelProperty(value = "An ID to uniquely identify this object.", readOnly = true)
@@ -90,6 +92,15 @@
this.allowBundleRedeploy = allowBundleRedeploy;
}
+ @ApiModelProperty("Indicates if this bucket allows read access to unauthenticated anonymous users")
+ public Boolean isAllowPublicRead() {
+ return allowPublicRead;
+ }
+
+ public void setAllowPublicRead(final Boolean allowPublicRead) {
+ this.allowPublicRead = allowPublicRead;
+ }
+
@ApiModelProperty(value = "The access that the current user has to this bucket.", readOnly = true)
public Permissions getPermissions() {
return permissions;
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseMetadataService.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseMetadataService.java
index 3f75439..63a4bd1 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseMetadataService.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseMetadataService.java
@@ -76,13 +76,14 @@
@Override
public BucketEntity createBucket(final BucketEntity b) {
- final String sql = "INSERT INTO BUCKET (ID, NAME, DESCRIPTION, CREATED, ALLOW_EXTENSION_BUNDLE_REDEPLOY) VALUES (?, ?, ?, ?, ?)";
+ final String sql = "INSERT INTO BUCKET (ID, NAME, DESCRIPTION, CREATED, ALLOW_EXTENSION_BUNDLE_REDEPLOY, ALLOW_PUBLIC_READ) VALUES (?, ?, ?, ?, ?, ?)";
jdbcTemplate.update(sql,
b.getId(),
b.getName(),
b.getDescription(),
b.getCreated(),
- b.isAllowExtensionBundleRedeploy() ? 1 : 0);
+ b.isAllowExtensionBundleRedeploy() ? 1 : 0,
+ b.isAllowPublicRead() ? 1 : 0);
return b;
}
@@ -104,8 +105,20 @@
@Override
public BucketEntity updateBucket(final BucketEntity bucket) {
- final String sql = "UPDATE BUCKET SET name = ?, description = ?, allow_extension_bundle_redeploy = ? WHERE id = ?";
- jdbcTemplate.update(sql, bucket.getName(), bucket.getDescription(), bucket.isAllowExtensionBundleRedeploy() ? 1 : 0, bucket.getId());
+ final String sql = "UPDATE BUCKET SET " +
+ "name = ?, " +
+ "description = ?, " +
+ "allow_extension_bundle_redeploy = ?, " +
+ "allow_public_read = ? " +
+ "WHERE id = ?";
+
+ jdbcTemplate.update(sql,
+ bucket.getName(),
+ bucket.getDescription(),
+ bucket.isAllowExtensionBundleRedeploy() ? 1 : 0,
+ bucket.isAllowPublicRead() ? 1 : 0,
+ bucket.getId());
+
return bucket;
}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketEntity.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketEntity.java
index fe980d0..b6bf12b 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketEntity.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketEntity.java
@@ -31,6 +31,8 @@
private boolean allowExtensionBundleRedeploy;
+ private boolean allowPublicRead;
+
public String getId() {
return id;
@@ -72,6 +74,14 @@
this.allowExtensionBundleRedeploy = allowExtensionBundleRedeploy;
}
+ public boolean isAllowPublicRead() {
+ return allowPublicRead;
+ }
+
+ public void setAllowPublicRead(boolean allowPublicRead) {
+ this.allowPublicRead = allowPublicRead;
+ }
+
@Override
public int hashCode() {
return Objects.hashCode(this.id);
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/BucketEntityRowMapper.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/BucketEntityRowMapper.java
index 5ef5c60..5bda0a8 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/BucketEntityRowMapper.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/BucketEntityRowMapper.java
@@ -34,6 +34,7 @@
b.setDescription(rs.getString("DESCRIPTION"));
b.setCreated(rs.getTimestamp("CREATED"));
b.setAllowExtensionBundleRedeploy(rs.getInt("ALLOW_EXTENSION_BUNDLE_REDEPLOY") == 0 ? false : true);
+ b.setAllowPublicRead(rs.getInt("ALLOW_PUBLIC_READ") == 0 ? false : true);
return b;
}
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 43862fe..916892b 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
@@ -31,6 +31,7 @@
import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
import org.apache.nifi.registry.security.exception.SecurityProviderDestructionException;
import org.apache.nifi.registry.security.util.XmlUtils;
+import org.apache.nifi.registry.service.RegistryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
@@ -88,6 +89,7 @@
private final NiFiRegistryProperties properties;
private final ExtensionManager extensionManager;
private final SensitivePropertyProvider sensitivePropertyProvider;
+ private final RegistryService registryService;
private Authorizer authorizer;
private final Map<String, UserGroupProvider> userGroupProviders = new HashMap<>();
@@ -98,11 +100,13 @@
public AuthorizerFactory(
final NiFiRegistryProperties properties,
final ExtensionManager extensionManager,
- @Nullable final SensitivePropertyProvider sensitivePropertyProvider) {
+ @Nullable final SensitivePropertyProvider sensitivePropertyProvider,
+ final RegistryService registryService) {
this.properties = properties;
this.extensionManager = extensionManager;
this.sensitivePropertyProvider = sensitivePropertyProvider;
+ this.registryService = registryService;
if (this.properties == null) {
throw new IllegalStateException("NiFiRegistryProperties cannot be null");
@@ -111,6 +115,10 @@
if (this.extensionManager == null) {
throw new IllegalStateException("ExtensionManager cannot be null");
}
+
+ if (this.registryService == null) {
+ throw new IllegalStateException("RegistryService cannot be null");
+ }
}
/***** UserGroupProviderLookup *****/
@@ -354,10 +362,22 @@
// call post construction lifecycle event
instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this));
- return installIntegrityChecks(instance);
+ // wrap the instance Authorizer with checks to ensure integrity of data
+ final Authorizer integrityCheckAuthorizer = installIntegrityChecks(instance);
+
+ // wrap the integrity checked Authorizer with the FrameworkAuthorizer
+ return createFrameworkAuthorizer(integrityCheckAuthorizer);
}
- private void performMethodInjection(final Object instance, final Class authorizerClass) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+ 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)) {
// make the method accessible
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
new file mode 100644
index 0000000..08fb8f0
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/FrameworkAuthorizer.java
@@ -0,0 +1,189 @@
+/*
+ * 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
new file mode 100644
index 0000000..478482e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/FrameworkManagedAuthorizer.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.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/resource/Authorizable.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/Authorizable.java
index d08467e..04cb469 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
@@ -16,18 +16,20 @@
*/
package org.apache.nifi.registry.security.authorization.resource;
-import org.apache.nifi.registry.security.authorization.AuthorizationResult.Result;
-import org.apache.nifi.registry.security.authorization.exception.AccessDeniedException;
-import org.apache.nifi.registry.security.authorization.user.NiFiUser;
import org.apache.nifi.registry.security.authorization.AuthorizationAuditor;
import org.apache.nifi.registry.security.authorization.AuthorizationRequest;
import org.apache.nifi.registry.security.authorization.AuthorizationResult;
+import org.apache.nifi.registry.security.authorization.AuthorizationResult.Result;
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.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 {
@@ -93,6 +95,10 @@
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()
@@ -205,10 +211,18 @@
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/service/RegistryService.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java
index 26057ae..8df44e8 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java
@@ -160,6 +160,10 @@
bucket.setAllowBundleRedeploy(false);
}
+ if (bucket.isAllowPublicRead() == null) {
+ bucket.setAllowPublicRead(false);
+ }
+
validate(bucket, "Cannot create Bucket");
writeLock.lock();
@@ -282,6 +286,10 @@
existingBucketById.setAllowExtensionBundleRedeploy(bucket.isAllowBundleRedeploy());
}
+ if (bucket.isAllowPublicRead() != null) {
+ existingBucketById.setAllowPublicRead(bucket.isAllowPublicRead());
+ }
+
// perform the actual update
final BucketEntity updatedBucket = metadataService.updateBucket(existingBucketById);
return BucketMappings.map(updatedBucket);
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/mapper/BucketMappings.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/mapper/BucketMappings.java
index 22020a6..542cc1b 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/mapper/BucketMappings.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/mapper/BucketMappings.java
@@ -33,6 +33,7 @@
bucketEntity.setDescription(bucket.getDescription());
bucketEntity.setCreated(new Date(bucket.getCreatedTimestamp()));
bucketEntity.setAllowExtensionBundleRedeploy(bucket.isAllowBundleRedeploy());
+ bucketEntity.setAllowPublicRead(bucket.isAllowPublicRead());
return bucketEntity;
}
@@ -43,6 +44,7 @@
bucket.setDescription(bucketEntity.getDescription());
bucket.setCreatedTimestamp(bucketEntity.getCreated().getTime());
bucket.setAllowBundleRedeploy(bucketEntity.isAllowExtensionBundleRedeploy());
+ bucket.setAllowPublicRead(bucketEntity.isAllowPublicRead());
return bucket;
}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/default/V5__AddBucketPublicFlags.sql b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/default/V5__AddBucketPublicFlags.sql
new file mode 100644
index 0000000..ef7478b
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/default/V5__AddBucketPublicFlags.sql
@@ -0,0 +1,16 @@
+-- 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.
+
+ALTER TABLE BUCKET ADD ALLOW_PUBLIC_READ INT NOT NULL DEFAULT (0);
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/mysql/V5__AddBucketPublicFlags.sql b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/mysql/V5__AddBucketPublicFlags.sql
new file mode 100644
index 0000000..9ba1b80
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/mysql/V5__AddBucketPublicFlags.sql
@@ -0,0 +1,16 @@
+-- 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.
+
+ALTER TABLE BUCKET ADD ALLOW_PUBLIC_READ INT NOT NULL DEFAULT 0;
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/security/authorization/AuthorizerFactorySpec.groovy b/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/security/authorization/AuthorizerFactorySpec.groovy
index 932d483..34dfe6b 100644
--- a/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/security/authorization/AuthorizerFactorySpec.groovy
+++ b/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/security/authorization/AuthorizerFactorySpec.groovy
@@ -18,15 +18,15 @@
import org.apache.nifi.registry.extension.ExtensionManager
import org.apache.nifi.registry.properties.NiFiRegistryProperties
-import org.apache.nifi.registry.security.authorization.file.FileAccessPolicyProvider
-import org.apache.nifi.registry.security.authorization.file.FileUserGroupProvider
import org.apache.nifi.registry.security.authorization.resource.ResourceFactory
+import org.apache.nifi.registry.service.RegistryService
import spock.lang.Specification
class AuthorizerFactorySpec extends Specification {
def mockProperties = Mock(NiFiRegistryProperties)
def mockExtensionManager = Mock(ExtensionManager)
+ def mockRegistryService = Mock(RegistryService)
AuthorizerFactory authorizerFactory
@@ -35,7 +35,7 @@
mockExtensionManager.getExtensionClassLoader(_) >> this.getClass().getClassLoader()
mockProperties.getPropertyKeys() >> new HashSet<String>() // Called by IdentityMappingUtil.getIdentityMappings()
- authorizerFactory = new AuthorizerFactory(mockProperties, mockExtensionManager, null)
+ authorizerFactory = new AuthorizerFactory(mockProperties, mockExtensionManager, null, mockRegistryService)
}
// runs after every feature method
@@ -88,7 +88,7 @@
when: "a bad configuration is provided and getAuthorizer() is called"
setMockPropsAuthorizersConfig(authorizersConfigFile, selectedAuthorizer)
- authorizerFactory = new AuthorizerFactory(mockProperties, mockExtensionManager, null)
+ authorizerFactory = new AuthorizerFactory(mockProperties, mockExtensionManager, null, mockRegistryService)
authorizerFactory.getAuthorizer()
then: "expect an exception"
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 4646c2f..f47e7ce 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
@@ -20,13 +20,8 @@
import org.apache.nifi.registry.authorization.User
import org.apache.nifi.registry.authorization.UserGroup
import org.apache.nifi.registry.bucket.Bucket
+import org.apache.nifi.registry.security.authorization.*
import org.apache.nifi.registry.security.authorization.AccessPolicy as AuthAccessPolicy
-import org.apache.nifi.registry.security.authorization.AuthorizableLookup
-import org.apache.nifi.registry.security.authorization.ConfigurableAccessPolicyProvider
-import org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProvider
-import org.apache.nifi.registry.security.authorization.Group
-import org.apache.nifi.registry.security.authorization.RequestAction
-import org.apache.nifi.registry.security.authorization.StandardManagedAuthorizer
import org.apache.nifi.registry.security.authorization.User as AuthUser
import org.apache.nifi.registry.security.authorization.exception.AccessDeniedException
import org.apache.nifi.registry.security.authorization.resource.Authorizable
@@ -44,8 +39,9 @@
def setup() {
accessPolicyProvider.getUserGroupProvider() >> userGroupProvider
- def authorizer = new StandardManagedAuthorizer(accessPolicyProvider, userGroupProvider)
- authorizationService = new AuthorizationService(authorizableLookup, authorizer, registryService)
+ def standardAuthorizer = new StandardManagedAuthorizer(accessPolicyProvider, userGroupProvider)
+ def frameworkAuthorizer = new FrameworkManagedAuthorizer(standardAuthorizer, registryService)
+ authorizationService = new AuthorizationService(authorizableLookup, frameworkAuthorizer, registryService)
}
// ----- User tests -------------------------------------------------------
@@ -566,21 +562,31 @@
"b1": [
"name": "Bucket #1",
"description": "An initial bucket for testing",
- "createdTimestamp": 1
+ "createdTimestamp": 1,
+ "allowPublicRead" : false
],
"b2": [
"name": "Bucket #2",
"description": "A second bucket for testing",
- "createdTimestamp": 2
+ "createdTimestamp": 2,
+ "allowPublicRead" : true
],
+ "b3": [
+ "name": "Bucket #3",
+ "description": "A third bucket for testing",
+ "createdTimestamp": 3,
+ "allowPublicRead" : false
+ ]
]
def mapBucket = {
String id -> new Bucket([
identifier: id,
name: buckets[id]["name"] as String,
- description: buckets[id]["description"] as String]) }
+ description: buckets[id]["description"] as String,
+ allowPublicRead: buckets[id]["allowPublicRead"]
+ ]) }
- registryService.getBuckets() >> {[ mapBucket("b1"), mapBucket("b2") ]}
+ registryService.getBuckets() >> {[ mapBucket("b1"), mapBucket("b2"), mapBucket("b3") ]}
def authorized = Mock(Authorizable)
authorized.authorize(_, _, _) >> { return }
@@ -590,7 +596,8 @@
authorizableLookup.getAuthorizableByResource("/actuator") >> denied
authorizableLookup.getAuthorizableByResource("/buckets") >> authorized
authorizableLookup.getAuthorizableByResource("/buckets/b1") >> authorized
- authorizableLookup.getAuthorizableByResource("/buckets/b2") >> denied
+ authorizableLookup.getAuthorizableByResource("/buckets/b2") >> authorized
+ authorizableLookup.getAuthorizableByResource("/buckets/b3") >> denied
authorizableLookup.getAuthorizableByResource("/policies") >> authorized
authorizableLookup.getAuthorizableByResource("/proxy") >> denied
authorizableLookup.getAuthorizableByResource("/swagger") >> denied
@@ -602,12 +609,13 @@
then:
resources != null
- resources.size() == 4
+ resources.size() == 5
def sortedResources = resources.sort{it.identifier}
sortedResources[0].identifier == "/buckets"
sortedResources[1].identifier == "/buckets/b1"
- sortedResources[2].identifier == "/policies"
- sortedResources[3].identifier == "/tenants"
+ sortedResources[2].identifier == "/buckets/b2"
+ sortedResources[3].identifier == "/policies"
+ sortedResources[4].identifier == "/tenants"
when:
@@ -615,11 +623,11 @@
then:
filteredResources != null
- filteredResources.size() == 2
+ filteredResources.size() == 3
def sortedFilteredResources = filteredResources.sort{it.identifier}
sortedFilteredResources[0].identifier == "/buckets"
sortedFilteredResources[1].identifier == "/buckets/b1"
-
+ sortedFilteredResources[2].identifier == "/buckets/b2"
}
}
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java
index 027bd3a..8c54152 100644
--- a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java
@@ -105,6 +105,7 @@
final BucketEntity bucket = metadataService.getBucketById("1");
assertNotNull(bucket);
assertFalse(bucket.isAllowExtensionBundleRedeploy());
+ assertFalse(bucket.isAllowPublicRead());
final String updatedName = bucket.getName() + " UPDATED";
final String updatedDesc = bucket.getDescription() + "DESC";
@@ -112,6 +113,7 @@
bucket.setName(updatedName);
bucket.setDescription(updatedDesc);
bucket.setAllowExtensionBundleRedeploy(true);
+ bucket.setAllowPublicRead(true);
metadataService.updateBucket(bucket);
@@ -120,6 +122,7 @@
assertEquals(updatedName, updatedBucket.getName());
assertEquals(updatedDesc, updatedBucket.getDescription());
assertTrue(updatedBucket.isAllowExtensionBundleRedeploy());
+ assertTrue(updatedBucket.isAllowPublicRead());
}
@Test
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
new file mode 100644
index 0000000..2cc03f8
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/TestFrameworkAuthorizer.java
@@ -0,0 +1,278 @@
+/*
+ * 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-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 1eb99f9..56b7b45 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
@@ -18,6 +18,7 @@
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -33,6 +34,7 @@
private final Resource resource;
private final Resource requestedResource;
private final String identity;
+ private final List<String> proxyIdentities;
private final Set<String> groups;
private final RequestAction action;
private final boolean isAccessAttempt;
@@ -49,6 +51,7 @@
this.resource = builder.resource;
this.identity = builder.identity;
+ this.proxyIdentities = builder.proxyIdentities == null ? Collections.emptyList() : Collections.unmodifiableList(builder.proxyIdentities);
this.groups = builder.groups == null ? null : Collections.unmodifiableSet(builder.groups);
this.action = builder.action;
this.isAccessAttempt = builder.isAccessAttempt;
@@ -103,6 +106,15 @@
}
/**
+ * 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
+ */
+ public List<String> getProxyIdentities() {
+ return proxyIdentities;
+ }
+
+ /**
* The groups the user making this request belongs to. May be null if this NiFi is not configured to load user
* groups or empty if the user has no groups
*
@@ -174,6 +186,7 @@
private Resource resource;
private Resource requestedResource;
private String identity;
+ private List<String> proxyIdentities;
private Set<String> groups;
private Boolean isAnonymous;
private Boolean isAccessAttempt;
@@ -197,6 +210,11 @@
return this;
}
+ public Builder proxyIdentities(final List<String> proxyIdentities) {
+ this.proxyIdentities = proxyIdentities;
+ return this;
+ }
+
public Builder groups(final Set<String> groups) {
this.groups = groups;
return this;
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java
index 2cf3116..30374f0 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java
@@ -124,6 +124,7 @@
}
final CurrentUser currentUser = authorizationService.getCurrentUser();
+ currentUser.setLoginSupported(httpServletRequest.isSecure() && identityProvider != null);
return generateOkResponse(currentUser).build();
}
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java
index a2a6ea9..20a6e0d 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/NiFiRegistrySecurityConfig.java
@@ -22,7 +22,6 @@
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;
-import org.apache.nifi.registry.web.security.authentication.exception.UntrustedProxyException;
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;
@@ -110,13 +109,9 @@
// otp
// todo, if needed one-time password auth filter goes here
- if (properties.getSslPort() == null) {
- // If we are running an unsecured NiFi Registry server, add an
- // anonymous authentication filter that will populate the
- // authenticated, anonymous user if no other user identity
- // is detected earlier in the Spring filter chain.
- http.anonymous().authenticationFilter(anonymousAuthenticationFilter);
- }
+ // add an anonymous authentication filter that will populate the authenticated,
+ // anonymous user if no other user identity 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,
@@ -182,20 +177,9 @@
AuthenticationException authenticationException)
throws IOException, ServletException {
- final int status;
-
- // See X509IdentityAuthenticationProvider.buildAuthenticatedToken(...)
- if (authenticationException instanceof UntrustedProxyException) {
- // return a 403 response
- status = HttpServletResponse.SC_FORBIDDEN;
- logger.info("Identity in proxy chain not trusted to act as a proxy: {} Returning 403 response.", authenticationException.toString());
-
- } else {
- // return a 401 response
- status = HttpServletResponse.SC_UNAUTHORIZED;
- logger.info("Client could not be authenticated due to: {} Returning 401 response.", authenticationException.toString());
- }
-
+ // return a 401 response
+ final int status = HttpServletResponse.SC_UNAUTHORIZED;
+ logger.info("Client could not be authenticated due to: {} Returning 401 response.", authenticationException.toString());
logger.debug("", authenticationException);
if (!response.isCommitted()) {
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/UntrustedProxyException.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/UntrustedProxyException.java
deleted file mode 100644
index 82570a3..0000000
--- a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/exception/UntrustedProxyException.java
+++ /dev/null
@@ -1,31 +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.exception;
-
-import org.springframework.security.core.AuthenticationException;
-
-public class UntrustedProxyException extends AuthenticationException {
-
- public UntrustedProxyException(String msg) {
- super(msg);
- }
-
- public UntrustedProxyException(String msg, Throwable t) {
- super(msg, t);
- }
-
-}
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java
index 9d724ac..6571177 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java
@@ -19,25 +19,16 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.registry.properties.NiFiRegistryProperties;
import org.apache.nifi.registry.security.authentication.AuthenticationRequest;
-import org.apache.nifi.registry.web.security.authentication.AuthenticationRequestToken;
import org.apache.nifi.registry.security.authentication.AuthenticationResponse;
import org.apache.nifi.registry.security.authentication.IdentityProvider;
-import org.apache.nifi.registry.web.security.authentication.IdentityAuthenticationProvider;
-import org.apache.nifi.registry.web.security.authentication.AuthenticationSuccessToken;
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.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.NiFiUserDetails;
import org.apache.nifi.registry.security.authorization.user.StandardNiFiUser;
import org.apache.nifi.registry.security.util.ProxiedEntitiesUtils;
-import org.apache.nifi.registry.web.security.authentication.exception.UntrustedProxyException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpMethod;
+import org.apache.nifi.registry.web.security.authentication.AuthenticationRequestToken;
+import org.apache.nifi.registry.web.security.authentication.AuthenticationSuccessToken;
+import org.apache.nifi.registry.web.security.authentication.IdentityAuthenticationProvider;
import java.util.List;
import java.util.ListIterator;
@@ -45,20 +36,6 @@
public class X509IdentityAuthenticationProvider extends IdentityAuthenticationProvider {
- private static final Logger LOGGER = LoggerFactory.getLogger(X509IdentityAuthenticationProvider.class);
-
- private static final Authorizable PROXY_AUTHORIZABLE = new Authorizable() {
- @Override
- public Authorizable getParentAuthorizable() {
- return null;
- }
-
- @Override
- public Resource getResource() {
- return ResourceFactory.getProxyResource();
- }
- };
-
public X509IdentityAuthenticationProvider(NiFiRegistryProperties properties, Authorizer authorizer, IdentityProvider identityProvider) {
super(properties, authorizer, identityProvider);
}
@@ -86,10 +63,6 @@
final List<String> proxyChain = ProxiedEntitiesUtils.tokenizeProxiedEntitiesChain(proxiedEntitiesChain);
proxyChain.add(response.getIdentity());
- final String httpMethodStr = x509RequestDetails.getHttpMethod().toUpperCase();
- final HttpMethod httpMethod = HttpMethod.resolve(httpMethodStr);
- LOGGER.debug("HTTP method is {}", new Object[]{httpMethod});
-
// add the chain as appropriate to each proxy
NiFiUser proxy = null;
for (final ListIterator<String> chainIter = proxyChain.listIterator(proxyChain.size()); chainIter.hasPrevious(); ) {
@@ -108,51 +81,13 @@
// Only set the client address for client making the request because we don't know the clientAddress of the proxied entities
String clientAddress = (proxy == null) ? requestToken.getClientAddress() : null;
proxy = createUser(identity, groups, proxy, clientAddress, isAnonymous);
-
- if (chainIter.hasPrevious()) {
- switch (httpMethod) {
- case POST:
- case PUT:
- case PATCH:
- authorizeWrite(proxy);
- break;
- case DELETE:
- authorizeDelete(proxy);
- break;
- default:
- authorizeRead(proxy);
- break;
- }
- }
}
+ // Defer authorization of proxy until later in FrameworkAuthorizer
+
return new AuthenticationSuccessToken(new NiFiUserDetails(proxy));
}
- private void authorizeRead(final NiFiUser proxy) {
- try {
- PROXY_AUTHORIZABLE.authorize(authorizer, RequestAction.READ, proxy);
- } catch (final AccessDeniedException e) {
- throw new UntrustedProxyException(String.format("Untrusted proxy for read operation [%s].", proxy.getIdentity()));
- }
- }
-
- private void authorizeWrite(final NiFiUser proxy) {
- try {
- PROXY_AUTHORIZABLE.authorize(authorizer, RequestAction.WRITE, proxy);
- } catch (final AccessDeniedException e) {
- throw new UntrustedProxyException(String.format("Untrusted proxy for write operation [%s].", proxy.getIdentity()));
- }
- }
-
- private void authorizeDelete(final NiFiUser proxy) {
- try {
- PROXY_AUTHORIZABLE.authorize(authorizer, RequestAction.DELETE, proxy);
- } catch (final AccessDeniedException e) {
- throw new UntrustedProxyException(String.format("Untrusted proxy for delete operation [%s].", proxy.getIdentity()));
- }
- }
-
/**
* Returns a regular user populated with the provided values, or if the user should be anonymous, a well-formed instance of the anonymous user with the provided values.
*
@@ -170,6 +105,4 @@
}
}
-
-
}
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
index 11d7b33..c6172a1 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
@@ -32,6 +32,7 @@
import org.apache.nifi.registry.security.authorization.AuthorizerFactory;
import org.apache.nifi.registry.security.crypto.BootstrapFileCryptoKeyProvider;
import org.apache.nifi.registry.security.crypto.CryptoKeyProvider;
+import org.apache.nifi.registry.service.RegistryService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -94,9 +95,9 @@
@Primary
@Bean
@DependsOn({"directoryServer"}) // Can't load LdapUserGroupProvider until the embedded LDAP server, which creates the "directoryServer" bean, is running
- public static Authorizer getAuthorizer(@Autowired NiFiRegistryProperties properties, ExtensionManager extensionManager) throws Exception {
+ public static Authorizer getAuthorizer(@Autowired NiFiRegistryProperties properties, ExtensionManager extensionManager, RegistryService registryService) throws Exception {
if (authorizerFactory == null) {
- authorizerFactory = new AuthorizerFactory(properties, extensionManager, sensitivePropertyProvider());
+ authorizerFactory = new AuthorizerFactory(properties, extensionManager, sensitivePropertyProvider(), registryService);
}
return authorizerFactory.getAuthorizer();
}
@@ -231,7 +232,18 @@
@Test
public void testGetCurrentUserFailsForAnonymous() throws Exception {
- // Given: the client is connected to an unsecured NiFi Registry
+ // Given: the client is connected to an secured NiFi Registry
+ final String expectedJson = "{" +
+ "\"anonymous\":true," +
+ "\"identity\":\"anonymous\"," +
+ "\"loginSupported\":true," +
+ "\"resourcePermissions\":{" +
+ "\"anyTopLevelResource\":{\"canDelete\":false,\"canRead\":false,\"canWrite\":false}," +
+ "\"buckets\":{\"canDelete\":false,\"canRead\":false,\"canWrite\":false}," +
+ "\"policies\":{\"canDelete\":false,\"canRead\":false,\"canWrite\":false}," +
+ "\"proxy\":{\"canDelete\":false,\"canRead\":false,\"canWrite\":false}," +
+ "\"tenants\":{\"canDelete\":false,\"canRead\":false,\"canWrite\":false}}" +
+ "}";
// When: the /access endpoint is queried with no credentials
final Response response = client
@@ -240,8 +252,10 @@
.get(Response.class);
// Then: the server returns a 200 OK with the expected current user
- assertEquals(401, response.getStatus());
+ assertEquals(200, response.getStatus());
+ final String actualJson = response.readEntity(String.class);
+ JSONAssert.assertEquals(expectedJson, actualJson, false);
}
@Test
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/dialogs/create-bucket/nf-registry-create-bucket.html b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/dialogs/create-bucket/nf-registry-create-bucket.html
index 32ee01a..19f7663 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/dialogs/create-bucket/nf-registry-create-bucket.html
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/dialogs/create-bucket/nf-registry-create-bucket.html
@@ -28,6 +28,10 @@
<input #newBucketInput matInput floatPlaceholder="always" placeholder="Bucket Name">
</mat-input-container>
</div>
+ <mat-checkbox #newBucketPublicReadCheckbox [disabled]="protocol === 'http:'" >
+ Make publicly visible<i matTooltip="Allows read access to items in this bucket by unauthenticated users. Overrides any specific policies granting read access."
+ class="pad-left-sm fa fa-question-circle-o help-icon"></i>
+ </mat-checkbox>
<mat-checkbox [(ngModel)]="keepDialogOpen">
Keep this dialog open after creating bucket
</mat-checkbox>
@@ -38,8 +42,8 @@
i18n="Cancel creation of new bucket|A button for cancelling the creation of a new bucket in the registry.@@nf-admin-workflow-cancel-create-bucket-button">
Cancel
</button>
- <button [disabled]="newBucketInput.value.length === 0" class="push-left-sm" data-automation-id="create-new-bucket-button" (click)="createBucket(newBucketInput)" color="fds-primary" mat-raised-button
- i18n="Create new bucket button|A button for creating a new bucket in the registry.@@nf-admin-workflow-create-bucket-button">
+ <button [disabled]="newBucketInput.value.length === 0" class="push-left-sm" data-automation-id="create-new-bucket-button" (click)="createBucket(newBucketInput,newBucketPublicReadCheckbox)"
+ color="fds-primary" mat-raised-button i18n="Create new bucket button|A button for creating a new bucket in the registry.@@nf-admin-workflow-create-bucket-button">
Create
</button>
</div>
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/dialogs/create-bucket/nf-registry-create-bucket.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/dialogs/create-bucket/nf-registry-create-bucket.js
index abaaa59..f529285 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/dialogs/create-bucket/nf-registry-create-bucket.js
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/dialogs/create-bucket/nf-registry-create-bucket.js
@@ -38,6 +38,7 @@
this.dialogRef = matDialogRef;
// local state
this.keepDialogOpen = false;
+ this.protocol = location.protocol;
}
NfRegistryCreateBucket.prototype = {
@@ -46,11 +47,12 @@
/**
* Create a new bucket.
*
- * @param newBucketInput The newBucketInput element.
+ * @param newBucketInput The newBucketInput element.
+ * @param newBucketPublicReadCheckbox The newBucketPublicReadCheckbox element.
*/
- createBucket: function (newBucketInput) {
+ createBucket: function (newBucketInput, newBucketPublicReadCheckbox) {
var self = this;
- this.nfRegistryApi.createBucket(newBucketInput.value).subscribe(function (bucket) {
+ this.nfRegistryApi.createBucket(newBucketInput.value, newBucketPublicReadCheckbox.checked).subscribe(function (bucket) {
if (!bucket.error) {
self.nfRegistryService.buckets.push(bucket);
self.nfRegistryService.filterBuckets();
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/dialogs/create-bucket/nf-registry-create-bucket.spec.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/dialogs/create-bucket/nf-registry-create-bucket.spec.js
index 3cb59d7..5b14455 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/dialogs/create-bucket/nf-registry-create-bucket.spec.js
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/dialogs/create-bucket/nf-registry-create-bucket.spec.js
@@ -46,7 +46,7 @@
it('should create a new bucket and close the dialog', function () {
// The function to test
- comp.createBucket({value: 'NewBucket'});
+ comp.createBucket({value: 'NewBucket'}, {checked: false});
//assertions
expect(comp).toBeDefined();
@@ -61,7 +61,7 @@
comp.keepDialogOpen = true;
// The function to test
- comp.createBucket({value: 'NewBucket'});
+ comp.createBucket({value: 'NewBucket'}, {checked: false});
//assertions
expect(comp).toBeDefined();
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.html b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.html
index 9d3cdbe..126aaa7 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.html
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.html
@@ -42,6 +42,15 @@
Save
</button>
</div>
+ <div class="pad-left-md pad-right-md pad-bottom-sm">
+ <mat-checkbox #bundlePublicReadCheckbox
+ [disabled]="!nfRegistryService.currentUser.resourcePermissions.buckets.canWrite || protocol === 'http:'"
+ [(ngModel)]="allowPublicRead"
+ (change)="toggleBucketPublicRead($event)">
+ Make publicly visible<i matTooltip="Allows read access to items in this bucket by unauthenticated users. Overrides any specific policies granting read access."
+ class="pad-left-sm fa fa-question-circle-o help-icon"></i>
+ </mat-checkbox>
+ </div>
<div class="pad-top-md pad-left-md pad-right-md">
<div class="pad-bottom-sm">
<span class="md-card-title">Bundle Settings</span>
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.js
index 1d6a71b..367129c 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.js
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.js
@@ -63,6 +63,7 @@
this.userPermsSearchTerms = [];
this.bucketname = '';
this.allowBundleRedeploy = false;
+ this.allowPublicRead = false;
this.bucketPolicies = [];
this.userPerms = {};
this.groupPerms = {};
@@ -80,6 +81,7 @@
this.dialogService = fdsDialogService;
this.snackBarService = fdsSnackBarService;
this.dataTableService = tdDataTableService;
+ this.protocol = location.protocol;
}
NfRegistryManageBucket.prototype = {
@@ -104,6 +106,7 @@
self.nfRegistryService.bucket = bucket;
self.bucketname = bucket.name;
self.allowBundleRedeploy = bucket.allowBundleRedeploy;
+ self.allowPublicRead = bucket.allowPublicRead;
if (!self.nfRegistryService.currentUser.anonymous) {
if (!response[1].status || response[1].status === 200) {
var policies = response[1];
@@ -163,6 +166,7 @@
self.nfRegistryService.bucket = response;
self.bucketname = response.name;
self.allowBundleRedeploy = response.allowBundleRedeploy;
+ self.allowPublicRead = response.allowPublicRead;
if (dialogResult) {
if (dialogResult.userOrGroup.type === 'user') {
@@ -201,6 +205,7 @@
self.nfRegistryService.bucket = response;
self.bucketname = response.name;
self.allowBundleRedeploy = response.allowBundleRedeploy;
+ self.allowPublicRead = response.allowPublicRead;
if (dialogResult) {
if (dialogResult.userOrGroup.type === 'user') {
@@ -424,6 +429,8 @@
} else if (response.status === 409) {
self.bucketname = self.nfRegistryService.bucket.name;
self.allowBundleRedeploy = self.nfRegistryService.bucket.allowBundleRedeploy;
+ self.allowPublicRead = self.nfRegistryService.bucket.allowPublicRead;
+
self.dialogService.openConfirm({
title: 'Error',
message: 'This bucket already exists. Please enter a different identity/bucket name.',
@@ -433,6 +440,8 @@
} else if (response.status === 400) {
self.bucketname = self.nfRegistryService.bucket.name;
self.allowBundleRedeploy = self.nfRegistryService.bucket.allowBundleRedeploy;
+ self.allowPublicRead = self.nfRegistryService.bucket.allowPublicRead;
+
self.dialogService.openConfirm({
title: 'Error',
message: response.error,
@@ -478,6 +487,40 @@
},
/**
+ * Update allowPublicRead flag.
+ *
+ * @param the checkbox change event
+ */
+ toggleBucketPublicRead: function (event) {
+ var self = this;
+ this.nfRegistryApi.updateBucket({
+ 'identifier': this.nfRegistryService.bucket.identifier,
+ 'allowPublicRead': event.checked,
+ }).subscribe(function (response) {
+ if (!response.status || response.status === 200) {
+ self.nfRegistryService.bucket = response;
+ self.snackBarService.openCoaster({
+ title: 'Success',
+ message: 'Bucket settings have been updated.',
+ verticalPosition: 'bottom',
+ horizontalPosition: 'right',
+ icon: 'fa fa-check-circle-o',
+ color: '#1EB475',
+ duration: 3000
+ });
+ } else if (response.status === 400) {
+ self.allowPublicRead = !event.checked;
+ self.dialogService.openConfirm({
+ title: 'Error',
+ message: response.error,
+ acceptButton: 'Ok',
+ acceptButtonColor: 'fds-warn'
+ });
+ }
+ });
+ },
+
+ /**
* Determine if bucket policies can be edited.
* @returns {boolean}
*/
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/login/dialogs/nf-registry-user-login.html b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/login/dialogs/nf-registry-user-login.html
index 343b319..986e7b3 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/login/dialogs/nf-registry-user-login.html
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/login/dialogs/nf-registry-user-login.html
@@ -18,6 +18,9 @@
<div id="nifi-registry-user-login-dialog">
<div class="pad-bottom-md" fxLayout="row" fxLayoutAlign="space-between center">
<span class="md-card-title">Log In</span>
+ <button mat-icon-button (click)="cancel()">
+ <mat-icon color="primary">close</mat-icon>
+ </button>
</div>
<div fxLayout="column" fxLayoutAlign="space-between start" class="pad-bottom-md">
<div class="pad-bottom-md fill-available-width">
@@ -33,13 +36,13 @@
</div>
<div fxLayout="row">
<span fxFlex></span>
- <button (click)="usernameInput.value='';passwordInput.value=''" color="fds-regular" mat-raised-button
- i18n="Clear log in form|A button for clearing the login form.@@nf-clear-user-login-button">
- Clear
+ <button (click)="cancel()" color="fds-regular" mat-raised-button
+ i18n="Cancel log in|A button for cancelling the login form.@@nf-cancel-user-login-button">
+ Cancel
</button>
<button [disabled]="usernameInput.value.length === 0 || passwordInput.value.length === 0" class="push-left-sm" (click)="login(usernameInput, passwordInput)" color="fds-primary" mat-raised-button
i18n="Log in|A button for attempting to authenticate with the registry.@@nf-user-login-button">
Log In
</button>
</div>
-</div>
\ No newline at end of file
+</div>
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/login/dialogs/nf-registry-user-login.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/login/dialogs/nf-registry-user-login.js
index 6412e65..d4da84c 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/login/dialogs/nf-registry-user-login.js
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/login/dialogs/nf-registry-user-login.js
@@ -20,6 +20,7 @@
import NfRegistryApi from 'services/nf-registry.api';
import { MatDialogRef } from '@angular/material';
import { NfRegistryLoginAuthGuard } from 'services/nf-registry.auth-guard.service';
+import { Router } from '@angular/router';
/**
* NfRegistryUserLogin constructor.
@@ -28,13 +29,15 @@
* @param nfRegistryService The nf-registry.service module.
* @param matDialogRef The angular material dialog ref.
* @param nfRegistryLoginAuthGuard The login auth guard.
+ * @param router The angular router module.
* @constructor
*/
-function NfRegistryUserLogin(nfRegistryApi, nfRegistryService, matDialogRef, nfRegistryLoginAuthGuard) {
+function NfRegistryUserLogin(nfRegistryApi, nfRegistryService, matDialogRef, nfRegistryLoginAuthGuard, router) {
this.nfRegistryService = nfRegistryService;
this.nfRegistryApi = nfRegistryApi;
this.dialogRef = matDialogRef;
this.nfRegistryLoginAuthGuard = nfRegistryLoginAuthGuard;
+ this.router = router;
}
NfRegistryUserLogin.prototype = {
@@ -56,6 +59,12 @@
self.nfRegistryLoginAuthGuard.checkLogin(self.nfRegistryService.redirectUrl);
}
});
+ },
+
+ cancel: function () {
+ var self = this;
+ self.dialogRef.close();
+ self.router.navigateByUrl('/nifi-registry');
}
};
@@ -69,7 +78,8 @@
NfRegistryApi,
NfRegistryService,
MatDialogRef,
- NfRegistryLoginAuthGuard
+ NfRegistryLoginAuthGuard,
+ Router
];
export default NfRegistryUserLogin;
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.html b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.html
index ac4e569..9d1e849 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.html
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.html
@@ -74,6 +74,7 @@
<div *ngIf="nfRegistryService.currentUser.identity && nfRegistryService.perspective !== 'login' && nfRegistryService.perspective !== 'not-found'" fxLayout="column" fxLayoutAlign="space-around end" class="push-right-sm">
<div id="current-user" matTooltip="{{nfRegistryService.currentUser.identity}}">{{nfRegistryService.currentUser.identity}}</div>
<a id="logout-link-container" *ngIf="nfRegistryService.currentUser.canLogout" class="link" (click)="logout()">logout</a>
+ <a id="logout-link-container" *ngIf="!nfRegistryService.currentUser.canLogout && nfRegistryService.currentUser.anonymous && nfRegistryService.currentUser.loginSupported" class="link" (click)="login()">login</a>
</div>
<div id="nifi-registry-documentation" *ngIf="nfRegistryService.perspective !== 'login'" class="pad-right-sm">
<a matTooltip="Help" href="{{nfRegistryService.documentation.link}}" target="_blank"><i class="fa fa-question-circle help-icon" aria-hidden="true"></i></a>
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.js
index bea77d0..24702cf 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.js
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.js
@@ -75,6 +75,13 @@
delete this.nfRegistryService.currentUser.anonymous;
this.nfStorage.removeItem('jwt');
this.router.navigateByUrl('/nifi-registry/login');
+ },
+
+ /**
+ * Navigate to login route.
+ */
+ login: function () {
+ this.router.navigateByUrl('/nifi-registry/login');
}
};
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js
index 28c79c1..2d2da8e 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js
@@ -155,9 +155,9 @@
* @param {string} name The name of the bucket.
* @returns {*}
*/
- createBucket: function (name) {
+ createBucket: function (name, allowPublicRead) {
var self = this;
- return this.http.post('/nifi-registry-api/buckets', {'name': name}, headers)
+ return this.http.post('/nifi-registry-api/buckets', {'name': name, 'allowPublicRead': allowPublicRead}, headers)
.map(function (response) {
return response;
})
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.spec.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.spec.js
index 2036eae..adf88ff 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.spec.js
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.spec.js
@@ -578,10 +578,13 @@
nfRegistryApi.updateBucket({
'identifier': '2f7f9e54-dc09-4ceb-aa58-9fe581319cdc',
'name': 'Bucket #1',
- 'allowBundleRedeploy': false
+ 'allowBundleRedeploy': true,
+ 'allowPublicRead': true
}).subscribe(function (response) {
expect(response[0].identifier).toEqual('2f7f9e54-dc09-4ceb-aa58-9fe581319cdc');
expect(response[0].name).toEqual('Bucket #1');
+ expect(response[0].allowBundleRedeploy).toEqual(true);
+ expect(response[0].allowPublicRead).toEqual(true);
});
// the request it made
req = httpMock.expectOne('/nifi-registry-api/buckets/2f7f9e54-dc09-4ceb-aa58-9fe581319cdc');
@@ -590,7 +593,9 @@
// Next, fulfill the request by transmitting a response.
req.flush([{
'identifier': '2f7f9e54-dc09-4ceb-aa58-9fe581319cdc',
- 'name': 'Bucket #1'
+ 'name': 'Bucket #1',
+ 'allowBundleRedeploy': true,
+ 'allowPublicRead': true
}]);
// Finally, assert that there are no outstanding requests.
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.auth-guard.service.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.auth-guard.service.js
index cc94c60..f003e80 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.auth-guard.service.js
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.auth-guard.service.js
@@ -96,8 +96,8 @@
});
self.router.navigateByUrl('/nifi-registry/explorer');
}
- } else {
- // registry security not configured, redirect to workflow perspective
+ } else if (location.protocol === 'http:') {
+ // user is anonymous and we are NOT secure, redirect to workflow perspective
self.dialogService.openConfirm({
title: 'Not Applicable',
message: 'User administration is not configured for this registry.',
@@ -105,6 +105,15 @@
acceptButtonColor: 'fds-warn'
});
self.router.navigateByUrl('/nifi-registry/administration/workflow');
+ } else {
+ // user is anonymous and we are secure so don't allow the url, navigate to the main page
+ self.dialogService.openConfirm({
+ title: 'Access denied',
+ message: 'Please contact your system administrator.',
+ acceptButton: 'Ok',
+ acceptButtonColor: 'fds-warn'
+ });
+ self.router.navigateByUrl('/nifi-registry/explorer');
}
}
});
@@ -155,7 +164,7 @@
checkLogin: function (url) {
var self = this;
- if (this.nfRegistryService.currentUser.resourcePermissions.buckets.canRead || this.nfRegistryService.currentUser.anonymous) { return true; }
+ if (this.nfRegistryService.currentUser.resourcePermissions.buckets.canRead) { return true; }
// Store the attempted URL for redirecting
this.nfRegistryService.redirectUrl = url;
@@ -197,9 +206,18 @@
});
self.router.navigateByUrl('/nifi-registry/administration/users');
}
- } else {
- // registry security not configured, allow access to workflow perspective
+ } else if (location.protocol === 'http:') {
+ // user is anonymous and we are NOT secure so allow the url
self.router.navigateByUrl(url);
+ } else {
+ // user is anonymous and we are secure so don't allow the url, navigate to the main page
+ self.dialogService.openConfirm({
+ title: 'Access denied',
+ message: 'Please contact your system administrator.',
+ acceptButton: 'Ok',
+ acceptButtonColor: 'fds-warn'
+ });
+ self.router.navigateByUrl('/nifi-registry/explorer');
}
}
});
@@ -260,7 +278,7 @@
}
self.nfRegistryService.currentUser.canActivateResourcesAuthGuard = true;
self.router.navigateByUrl(self.nfRegistryService.redirectUrl);
- } else if (self.nfRegistryService.currentUser.anonymous) {
+ } else if (self.nfRegistryService.currentUser.anonymous && !self.nfRegistryService.currentUser.loginSupported) {
self.router.navigateByUrl('/nifi-registry');
} else {
self.nfRegistryService.currentUser.anonymous = true;
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js
index 060c8dd..33c41cc 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js
@@ -206,6 +206,7 @@
this.breadCrumbState = 'out';
this.explorerViewType = '';
this.currentUser = {
+ loginSupported: false,
resourcePermissions: {
anyTopLevelResource: {
canRead: false,