NIFIREG-212 Separating proxy into Read, Write, and Delete so some proxies can be set to read-only
This closes #194.
Signed-off-by: Kevin Doran <kdoran@apache.org>
diff --git a/nifi-registry-core/nifi-registry-docs/src/main/asciidoc/administration-guide.adoc b/nifi-registry-core/nifi-registry-docs/src/main/asciidoc/administration-guide.adoc
index 2193734..75c6f15 100644
--- a/nifi-registry-core/nifi-registry-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-registry-core/nifi-registry-docs/src/main/asciidoc/administration-guide.adoc
@@ -686,10 +686,18 @@
| Allows users to delete policies
| `resource="/policies" action="D"`
-| Can Proxy Requests
-| Allows users to proxy requests
+| Can Proxy Requests (Read)
+| Allows users to proxy read requests (GET)
+| `resource="/proxy" action="R"`
+
+| Can Proxy Requests (Write)
+| Allows users to proxy write requests (POST, PUT, PATCH)
| `resource="/proxy" action="W"`
+| Can Proxy Requests (Delete)
+| Allows users to proxy delete requests (DELETE)
+| `resource="/proxy" action="D"`
+
| View Swagger
| Allows users to access the self-hosted Swagger UI
| `resource="/swagger" action="R"`
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAccessPolicyProvider.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAccessPolicyProvider.java
index c3434c4..5eb1874 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAccessPolicyProvider.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileAccessPolicyProvider.java
@@ -131,14 +131,18 @@
new ResourceActionPair("/swagger", READ_CODE),
new ResourceActionPair("/swagger", WRITE_CODE),
new ResourceActionPair("/swagger", DELETE_CODE),
- new ResourceActionPair("/proxy", WRITE_CODE)
+ new ResourceActionPair("/proxy", READ_CODE),
+ new ResourceActionPair("/proxy", WRITE_CODE),
+ new ResourceActionPair("/proxy", DELETE_CODE)
};
/* TODO - move this somewhere into nifi-registry-security-framework so it can be applied to any ConfigurableAccessPolicyProvider
* (and also gets us away from requiring magic strings here) */
private static final ResourceActionPair[] NIFI_ACCESS_POLICIES = {
new ResourceActionPair("/buckets", READ_CODE),
- new ResourceActionPair("/proxy", WRITE_CODE)
+ new ResourceActionPair("/proxy", READ_CODE),
+ new ResourceActionPair("/proxy", WRITE_CODE),
+ new ResourceActionPair("/proxy", DELETE_CODE)
};
static final String PROP_NIFI_IDENTITY_PREFIX = "NiFi Identity ";
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509AuthenticationRequestDetails.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509AuthenticationRequestDetails.java
new file mode 100644
index 0000000..aa24cd6
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509AuthenticationRequestDetails.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.web.security.authentication.x509;
+
+import java.util.Objects;
+
+public class X509AuthenticationRequestDetails {
+
+ private final String proxiedEntitiesChain;
+
+ private final String httpMethod;
+
+ public X509AuthenticationRequestDetails(final String proxiedEntitiesChain, final String httpMethod) {
+ this.proxiedEntitiesChain = proxiedEntitiesChain;
+ this.httpMethod = Objects.requireNonNull(httpMethod);
+ }
+
+ public String getProxiedEntitiesChain() {
+ return proxiedEntitiesChain;
+ }
+
+ public String getHttpMethod() {
+ return httpMethod;
+ }
+
+}
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 aefdd5b..9d724ac 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
@@ -35,6 +35,9 @@
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 java.util.List;
import java.util.ListIterator;
@@ -42,6 +45,8 @@
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() {
@@ -63,12 +68,16 @@
AuthenticationRequestToken requestToken,
AuthenticationResponse response) {
- AuthenticationRequest authenticationRequest = requestToken.getAuthenticationRequest();
+ final AuthenticationRequest authenticationRequest = requestToken.getAuthenticationRequest();
- String proxiedEntitiesChain = authenticationRequest.getDetails() != null
- ? (String)authenticationRequest.getDetails()
- : null;
+ final Object requestDetails = authenticationRequest.getDetails();
+ if (requestDetails == null || !(requestDetails instanceof X509AuthenticationRequestDetails)) {
+ throw new IllegalStateException("Invalid request details specified");
+ }
+ final X509AuthenticationRequestDetails x509RequestDetails = (X509AuthenticationRequestDetails) authenticationRequest.getDetails();
+
+ final String proxiedEntitiesChain = x509RequestDetails.getProxiedEntitiesChain();
if (StringUtils.isBlank(proxiedEntitiesChain)) {
return super.buildAuthenticatedToken(requestToken, response);
}
@@ -77,6 +86,10 @@
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(); ) {
@@ -97,16 +110,47 @@
proxy = createUser(identity, groups, proxy, clientAddress, isAnonymous);
if (chainIter.hasPrevious()) {
- try {
- PROXY_AUTHORIZABLE.authorize(authorizer, RequestAction.WRITE, proxy);
- } catch (final AccessDeniedException e) {
- throw new UntrustedProxyException(String.format("Untrusted proxy [%s].", identity));
+ switch (httpMethod) {
+ case POST:
+ case PUT:
+ case PATCH:
+ authorizeWrite(proxy);
+ break;
+ case DELETE:
+ authorizeDelete(proxy);
+ break;
+ default:
+ authorizeRead(proxy);
+ break;
}
}
}
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()));
+ }
}
/**
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java
index 2a1856e..fc74f66 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java
@@ -113,9 +113,10 @@
final String principal = certificatePrincipal.toString();
// extract the proxiedEntitiesChain header value from the servletRequest
- String proxiedEntitiesChainHeader = servletRequest.getHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN);
+ final String proxiedEntitiesChainHeader = servletRequest.getHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN);
+ final X509AuthenticationRequestDetails details = new X509AuthenticationRequestDetails(proxiedEntitiesChainHeader, servletRequest.getMethod());
- return new AuthenticationRequest(principal, certificates[0], proxiedEntitiesChainHeader);
+ return new AuthenticationRequest(principal, certificates[0], details);
}
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureFileIT.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureFileIT.java
index 67cb2e2..095d258 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureFileIT.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureFileIT.java
@@ -65,7 +65,7 @@
"\"buckets\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
"\"tenants\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
"\"policies\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
- "\"proxy\":{\"canRead\":false,\"canWrite\":true,\"canDelete\":false}}" +
+ "\"proxy\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}}" +
"}";
// When: the /access endpoint is queried
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureKerberosIT.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureKerberosIT.java
index 8d8ea97..de87fcf 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureKerberosIT.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureKerberosIT.java
@@ -195,7 +195,7 @@
"\"buckets\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
"\"tenants\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
"\"policies\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
- "\"proxy\":{\"canRead\":false,\"canWrite\":true,\"canDelete\":false}}" +
+ "\"proxy\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}}" +
"}";
// When: the /access endpoint is queried using a JWT for the kerberos user
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 543ea87..11d7b33 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
@@ -256,7 +256,7 @@
"\"buckets\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
"\"tenants\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
"\"policies\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
- "\"proxy\":{\"canRead\":false,\"canWrite\":true,\"canDelete\":false}}" +
+ "\"proxy\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}}" +
"}";
// When: the /access endpoint is queried using a JWT for the nifiadmin LDAP user
@@ -284,7 +284,7 @@
"\"buckets\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
"\"tenants\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
"\"policies\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
- "\"proxy\":{\"canRead\":false,\"canWrite\":true,\"canDelete\":false}}}," +
+ "\"proxy\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}}}," +
"{\"identity\":\"euler\",\"userGroups\":[{\"identity\":\"mathematicians\"}],\"accessPolicies\":[],\"configurable\":false}," +
"{\"identity\":\"euclid\",\"userGroups\":[{\"identity\":\"mathematicians\"}],\"accessPolicies\":[],\"configurable\":false}," +
"{\"identity\":\"boyle\",\"userGroups\":[{\"identity\":\"chemists\"}],\"accessPolicies\":[],\"configurable\":false}," +
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java
index cb14b90..62aa098 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java
@@ -97,7 +97,7 @@
Assert.assertEquals(fullAccess, currentUser.getResourcePermissions().getBuckets());
Assert.assertEquals(fullAccess, currentUser.getResourcePermissions().getTenants());
Assert.assertEquals(fullAccess, currentUser.getResourcePermissions().getPolicies());
- Assert.assertEquals(new Permissions().withCanWrite(true), currentUser.getResourcePermissions().getProxy());
+ Assert.assertEquals(new Permissions().withCanWrite(true).withCanRead(true).withCanDelete(true), currentUser.getResourcePermissions().getProxy());
}
@Test
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-group/nf-registry-manage-group.html b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-group/nf-registry-manage-group.html
index 8317ee2..31eade4 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-group/nf-registry-manage-group.html
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-group/nf-registry-manage-group.html
@@ -128,12 +128,32 @@
</mat-checkbox>
</div>
<mat-checkbox [disabled]="!canEditSpecialPrivileges()"
- [checked]="nfRegistryService.group.resourcePermissions.proxy.canWrite"
+ [checked]="nfRegistryService.group.resourcePermissions.proxy.canRead && nfRegistryService.group.resourcePermissions.proxy.canWrite && nfRegistryService.group.resourcePermissions.proxy.canDelete"
(change)="toggleGroupManageProxyPrivileges($event)">
<span class="description">Can proxy user requests<i
matTooltip="Allow a connected system (e.g., NiFi) to process requests of authorized users of that system."
class="pad-left-sm fa fa-question-circle-o help-icon"></i></span>
</mat-checkbox>
+ <div flex fxLayout="row" fxLayoutAlign="space-around center">
+ <mat-checkbox class="pad-left-md"
+ [disabled]="!canEditSpecialPrivileges()"
+ [(checked)]="nfRegistryService.group.resourcePermissions.proxy.canRead"
+ (change)="toggleGroupManageProxyPrivileges($event, 'read')">
+ <span class="description">Read</span>
+ </mat-checkbox>
+ <mat-checkbox class="pad-left-md"
+ [disabled]="!canEditSpecialPrivileges()"
+ [(checked)]="nfRegistryService.group.resourcePermissions.proxy.canWrite"
+ (change)="toggleGroupManageProxyPrivileges($event, 'write')">
+ <span class="description">Write</span>
+ </mat-checkbox>
+ <mat-checkbox class="pad-left-md"
+ [disabled]="!canEditSpecialPrivileges()"
+ [(checked)]="nfRegistryService.group.resourcePermissions.proxy.canDelete"
+ (change)="toggleGroupManageProxyPrivileges($event, 'delete')">
+ <span class="description">Delete</span>
+ </mat-checkbox>
+ </div>
</div>
<mat-button-toggle-group name="nifi-registry-manage-group-perspective" class="pad-left-md tab-toggle-group">
<mat-button-toggle [checked]="manageGroupPerspective === 'membership'"
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-user/nf-registry-manage-user.html b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-user/nf-registry-manage-user.html
index 409dbd0..2b0a505 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-user/nf-registry-manage-user.html
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-user/nf-registry-manage-user.html
@@ -134,12 +134,32 @@
</div>
<mat-checkbox
[disabled]="!canEditSpecialPrivileges()"
- [checked]="nfRegistryService.user.resourcePermissions.proxy.canWrite"
+ [checked]="nfRegistryService.user.resourcePermissions.proxy.canRead && nfRegistryService.user.resourcePermissions.proxy.canWrite && nfRegistryService.user.resourcePermissions.proxy.canDelete"
(change)="toggleUserManageProxyPrivileges($event)">
<span class="description">Can proxy user requests<i
matTooltip="Allow a connected system (e.g., NiFi) to process requests of authorized users of that system."
class="pad-left-sm fa fa-question-circle-o help-icon"></i></span>
</mat-checkbox>
+ <div flex fxLayout="row" fxLayoutAlign="space-around center">
+ <mat-checkbox class="pad-left-md"
+ [disabled]="!canEditSpecialPrivileges()"
+ [(checked)]="nfRegistryService.user.resourcePermissions.proxy.canRead"
+ (change)="toggleUserManageProxyPrivileges($event, 'read')">
+ <span class="description">Read</span>
+ </mat-checkbox>
+ <mat-checkbox class="pad-left-md"
+ [disabled]="!canEditSpecialPrivileges()"
+ [(checked)]="nfRegistryService.user.resourcePermissions.proxy.canWrite"
+ (change)="toggleUserManageProxyPrivileges($event, 'write')">
+ <span class="description">Write</span>
+ </mat-checkbox>
+ <mat-checkbox class="pad-left-md"
+ [disabled]="!canEditSpecialPrivileges()"
+ [(checked)]="nfRegistryService.user.resourcePermissions.proxy.canDelete"
+ (change)="toggleUserManageProxyPrivileges($event, 'delete')">
+ <span class="description">Delete</span>
+ </mat-checkbox>
+ </div>
</div>
<mat-button-toggle-group name="nifi-registry-manage-user-perspective" class="pad-left-md tab-toggle-group">
<mat-button-toggle [checked]="manageUserPerspective === 'membership'"
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 5395acd..060c8dd 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
@@ -194,7 +194,7 @@
// model for proxy privileges
this.PROXY_PRIVS = {
- '/proxy': ['write']
+ '/proxy': ['read', 'write', 'delete']
};
//<editor-fold desc="application state objects">