NIFI-2695: - Providing more granular and meaningful authorization error messages.

This closes #1309.

Signed-off-by: Bryan Bende <bbende@apache.org>
This commit is contained in:
Matt Gilman 2016-12-08 11:29:32 -05:00 committed by Bryan Bende
parent 41189b055f
commit b1c9f0e764
No known key found for this signature in database
GPG Key ID: A0DDA9ED50711C39
29 changed files with 400 additions and 100 deletions

View File

@ -158,8 +158,7 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
return AuthorizationResult.approved();
}
return AuthorizationResult.denied();
return AuthorizationResult.denied(request.getExplanationSupplier().get());
}
/**

View File

@ -20,12 +20,15 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
/**
* Represents an authorization request for a given user/entity performing an action against a resource within some userContext.
*/
public class AuthorizationRequest {
public static final String DEFAULT_EXPLANATION = "Unable to perform the desired action.";
private final Resource resource;
private final String identity;
private final RequestAction action;
@ -33,6 +36,7 @@ public class AuthorizationRequest {
private final boolean isAnonymous;
private final Map<String, String> userContext;
private final Map<String, String> resourceContext;
private final Supplier<String> explanationSupplier;
private AuthorizationRequest(final Builder builder) {
Objects.requireNonNull(builder.resource, "The resource is required when creating an authorization request");
@ -47,6 +51,16 @@ public class AuthorizationRequest {
this.isAnonymous = builder.isAnonymous;
this.userContext = builder.userContext == null ? null : Collections.unmodifiableMap(builder.userContext);
this.resourceContext = builder.resourceContext == null ? null : Collections.unmodifiableMap(builder.resourceContext);
this.explanationSupplier = () -> {
final String explanation = builder.explanationSupplier.get();
// ensure the specified supplier returns non null
if (explanation == null) {
return DEFAULT_EXPLANATION;
} else {
return explanation;
}
};
}
/**
@ -112,6 +126,15 @@ public class AuthorizationRequest {
return resourceContext;
}
/**
* A supplier for the explanation if access is denied. Non null.
*
* @return The explanation supplier if access is denied
*/
public Supplier<String> getExplanationSupplier() {
return explanationSupplier;
}
/**
* AuthorizationRequest builder.
*/
@ -124,6 +147,7 @@ public class AuthorizationRequest {
private RequestAction action;
private Map<String, String> userContext;
private Map<String, String> resourceContext;
private Supplier<String> explanationSupplier = () -> DEFAULT_EXPLANATION;
public Builder resource(final Resource resource) {
this.resource = resource;
@ -164,6 +188,13 @@ public class AuthorizationRequest {
return this;
}
public Builder explanationSupplier(final Supplier<String> explanationSupplier) {
if (explanationSupplier != null) {
this.explanationSupplier = explanationSupplier;
}
return this;
}
public AuthorizationRequest build() {
return new AuthorizationRequest(this);
}

View File

@ -28,7 +28,7 @@ public class AuthorizationResult {
}
private static final AuthorizationResult APPROVED = new AuthorizationResult(Result.Approved, null);
private static final AuthorizationResult RESOURCE_NOT_FOUND = new AuthorizationResult(Result.ResourceNotFound, null);
private static final AuthorizationResult RESOURCE_NOT_FOUND = new AuthorizationResult(Result.ResourceNotFound, "Not authorized for the requested resource.");
private final Result result;
private final String explanation;
@ -44,6 +44,10 @@ public class AuthorizationResult {
throw new IllegalArgumentException("An explanation is required when the authorization request is denied.");
}
if (Result.ResourceNotFound.equals(result) && explanation == null) {
throw new IllegalArgumentException("An explanation is required when the authorization request is resource not found.");
}
this.result = result;
this.explanation = explanation;
}
@ -83,7 +87,7 @@ public class AuthorizationResult {
* @return a new denied AuthorizationResult
*/
public static AuthorizationResult denied() {
return denied("Access is denied");
return denied(AuthorizationRequest.DEFAULT_EXPLANATION);
}
/**

View File

@ -34,4 +34,11 @@ public interface Resource {
* @return name of this resource
*/
String getName();
/**
* The description of this resource that may be safely used in messages to the client.
*
* @return safe description
*/
String getSafeDescription();
}

View File

@ -26,8 +26,8 @@ import org.apache.nifi.authorization.Resource;
import org.apache.nifi.authorization.UserContextKeys;
import org.apache.nifi.authorization.user.NiFiUser;
import java.util.Map;
import java.util.HashMap;
import java.util.Map;
public interface Authorizable {
@ -70,7 +70,7 @@ public interface Authorizable {
*/
default AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) {
if (user == null) {
return AuthorizationResult.denied("Unknown user");
return AuthorizationResult.denied("Unknown user.");
}
final Map<String,String> userContext;
@ -81,15 +81,28 @@ public interface Authorizable {
userContext = null;
}
// build the request
final Resource resource = getResource();
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.identity(user.getIdentity())
.anonymous(user.isAnonymous())
.accessAttempt(false)
.action(action)
.resource(getResource())
.resource(resource)
.resourceContext(resourceContext)
.userContext(userContext)
.explanationSupplier(() -> {
// build the safe explanation
final StringBuilder safeDescription = new StringBuilder("Unable to ");
if (RequestAction.READ.equals(action)) {
safeDescription.append("view ");
} else {
safeDescription.append("modify ");
}
safeDescription.append(resource.getSafeDescription()).append(".");
return safeDescription.toString();
})
.build();
// perform the authorization
@ -99,9 +112,37 @@ public interface Authorizable {
if (Result.ResourceNotFound.equals(result.getResult())) {
final Authorizable parent = getParentAuthorizable();
if (parent == null) {
return AuthorizationResult.denied();
return AuthorizationResult.denied("No applicable policies could be found.");
} else {
return parent.checkAuthorization(authorizer, action, user, resourceContext);
// create a custom authorizable to override the safe description but still defer to the parent authorizable
final Authorizable parentProxy = new Authorizable() {
@Override
public Authorizable getParentAuthorizable() {
return parent.getParentAuthorizable();
}
@Override
public Resource getResource() {
final Resource parentResource = parent.getResource();
return new Resource() {
@Override
public String getIdentifier() {
return parentResource.getIdentifier();
}
@Override
public String getName() {
return parentResource.getName();
}
@Override
public String getSafeDescription() {
return resource.getSafeDescription();
}
};
}
};
return parentProxy.checkAuthorization(authorizer, action, user, resourceContext);
}
} else {
return result;
@ -133,7 +174,7 @@ public interface Authorizable {
*/
default void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) throws AccessDeniedException {
if (user == null) {
throw new AccessDeniedException("Unknown user");
throw new AccessDeniedException("Unknown user.");
}
final Map<String,String> userContext;
@ -144,23 +185,65 @@ public interface Authorizable {
userContext = null;
}
final Resource resource = getResource();
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.identity(user.getIdentity())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(action)
.resource(getResource())
.resource(resource)
.resourceContext(resourceContext)
.userContext(userContext)
.explanationSupplier(() -> {
// build the safe explanation
final StringBuilder safeDescription = new StringBuilder("Unable to ");
if (RequestAction.READ.equals(action)) {
safeDescription.append("view ");
} else {
safeDescription.append("modify ");
}
safeDescription.append(resource.getSafeDescription()).append(".");
return safeDescription.toString();
})
.build();
final AuthorizationResult result = authorizer.authorize(request);
if (Result.ResourceNotFound.equals(result.getResult())) {
final Authorizable parent = getParentAuthorizable();
if (parent == null) {
throw new AccessDeniedException("Access is denied");
throw new AccessDeniedException("No applicable policies could be found.");
} else {
parent.authorize(authorizer, action, user, resourceContext);
// create a custom authorizable to override the safe description but still defer to the parent authorizable
final Authorizable parentProxy = new Authorizable() {
@Override
public Authorizable getParentAuthorizable() {
return parent.getParentAuthorizable();
}
@Override
public Resource getResource() {
final Resource parentResource = parent.getResource();
return new Resource() {
@Override
public String getIdentifier() {
return parentResource.getIdentifier();
}
@Override
public String getName() {
return parentResource.getName();
}
@Override
public String getSafeDescription() {
return resource.getSafeDescription();
}
};
}
};
parentProxy.authorize(authorizer, action, user, resourceContext);
}
} else if (Result.Denied.equals(result.getResult())) {
throw new AccessDeniedException(result.getExplanation());

View File

@ -16,7 +16,6 @@
*/
package org.apache.nifi.authorization;
import org.apache.nifi.authorization.MockPolicyBasedAuthorizer;
import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.junit.Assert;
import org.junit.Test;
@ -42,6 +41,11 @@ public class TestAbstractPolicyBasedAuthorizer {
public String getName() {
return "resource1";
}
@Override
public String getSafeDescription() {
return "description1";
}
};
@Test

View File

@ -447,8 +447,12 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
// convert any access controls on ports to the appropriate policies
for (PortDTO portDTO : ports) {
final boolean isInputPort = portDTO.getType() != null && portDTO.getType().equals("inputPort");
final Resource resource = ResourceFactory.getDataTransferResource(isInputPort, portDTO.getId(), portDTO.getName());
final Resource resource;
if (portDTO.getType() != null && portDTO.getType().equals("inputPort")) {
resource = ResourceFactory.getDataTransferResource(ResourceFactory.getComponentResource(ResourceType.InputPort, portDTO.getId(), portDTO.getName()));
} else {
resource = ResourceFactory.getDataTransferResource(ResourceFactory.getComponentResource(ResourceType.OutputPort, portDTO.getId(), portDTO.getName()));
}
if (portDTO.getUserAccessControl() != null) {
for (String userAccessControl : portDTO.getUserAccessControl()) {

View File

@ -358,7 +358,8 @@ public class FileAuthorizerTest {
assertEquals(2, user6Policies.get(ResourceType.SiteToSite.getValue()).size());
assertTrue(user6Policies.get(ResourceType.SiteToSite.getValue()).contains(RequestAction.WRITE));
final Resource inputPortResource = ResourceFactory.getDataTransferResource(true, "2f7d1606-b090-4be7-a592-a5b70fb55531", "TCP Input");
final Resource inputPortResource = ResourceFactory.getDataTransferResource(
ResourceFactory.getComponentResource(ResourceType.InputPort, "2f7d1606-b090-4be7-a592-a5b70fb55531", "TCP Input"));
final AccessPolicy inputPortPolicy = authorizer.getUsersAndAccessPolicies().getAccessPolicy(inputPortResource.getIdentifier(), RequestAction.WRITE);
assertNotNull(inputPortPolicy);
assertEquals(1, inputPortPolicy.getUsers().size());
@ -366,7 +367,8 @@ public class FileAuthorizerTest {
assertEquals(1, inputPortPolicy.getGroups().size());
assertTrue(inputPortPolicy.getGroups().contains(group1.getIdentifier()));
final Resource outputPortResource = ResourceFactory.getDataTransferResource(false, "2f7d1606-b090-4be7-a592-a5b70fb55532", "TCP Output");
final Resource outputPortResource = ResourceFactory.getDataTransferResource(
ResourceFactory.getComponentResource(ResourceType.OutputPort, "2f7d1606-b090-4be7-a592-a5b70fb55532", "TCP Output"));
final AccessPolicy outputPortPolicy = authorizer.getUsersAndAccessPolicies().getAccessPolicy(outputPortResource.getIdentifier(), RequestAction.WRITE);
assertNotNull(outputPortPolicy);
assertEquals(1, outputPortPolicy.getUsers().size());
@ -432,7 +434,8 @@ public class FileAuthorizerTest {
final Group group1 = groups.iterator().next();
assertEquals("group1", group1.getName());
final Resource inputPortResource = ResourceFactory.getDataTransferResource(true, "2f7d1606-b090-4be7-a592-a5b70fb55531", "TCP Input");
final Resource inputPortResource = ResourceFactory.getDataTransferResource(
ResourceFactory.getComponentResource(ResourceType.InputPort, "2f7d1606-b090-4be7-a592-a5b70fb55531", "TCP Input"));
final AccessPolicy inputPortPolicy = authorizer.getUsersAndAccessPolicies().getAccessPolicy(inputPortResource.getIdentifier(), RequestAction.WRITE);
assertNotNull(inputPortPolicy);
assertEquals(1, inputPortPolicy.getUsers().size());
@ -440,7 +443,8 @@ public class FileAuthorizerTest {
assertEquals(1, inputPortPolicy.getGroups().size());
assertTrue(inputPortPolicy.getGroups().contains(group1.getIdentifier()));
final Resource outputPortResource = ResourceFactory.getDataTransferResource(false, "2f7d1606-b090-4be7-a592-a5b70fb55532", "TCP Output");
final Resource outputPortResource = ResourceFactory.getDataTransferResource(
ResourceFactory.getComponentResource(ResourceType.OutputPort, "2f7d1606-b090-4be7-a592-a5b70fb55532", "TCP Output"));
final AccessPolicy outputPortPolicy = authorizer.getUsersAndAccessPolicies().getAccessPolicy(outputPortResource.getIdentifier(), RequestAction.WRITE);
assertNotNull(outputPortPolicy);
assertEquals(1, outputPortPolicy.getUsers().size());

View File

@ -89,7 +89,7 @@ public class AccessPolicyAuthorizable implements Authorizable, EnforcePolicyPerm
@Override
public AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) {
if (user == null) {
throw new AccessDeniedException("Unknown user");
throw new AccessDeniedException("Unknown user.");
}
final AuthorizationResult resourceResult = Authorizable.super.checkAuthorization(authorizer, action, user, resourceContext);
@ -105,7 +105,7 @@ public class AccessPolicyAuthorizable implements Authorizable, EnforcePolicyPerm
@Override
public void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) throws AccessDeniedException {
if (user == null) {
throw new AccessDeniedException("Unknown user");
throw new AccessDeniedException("Unknown user.");
}
try {

View File

@ -62,7 +62,7 @@ public class DataAuthorizable implements Authorizable, EnforcePolicyPermissionsT
@Override
public AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) {
if (user == null) {
return AuthorizationResult.denied("Unknown user");
return AuthorizationResult.denied("Unknown user.");
}
AuthorizationResult result = null;
@ -100,7 +100,7 @@ public class DataAuthorizable implements Authorizable, EnforcePolicyPermissionsT
@Override
public void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) throws AccessDeniedException {
if (user == null) {
throw new AccessDeniedException("Unknown user");
throw new AccessDeniedException("Unknown user.");
}
// calculate the dn chain

View File

@ -32,6 +32,11 @@ public final class ResourceFactory {
public String getName() {
return "Controller";
}
@Override
public String getSafeDescription() {
return "the controller";
}
};
private final static Resource FLOW_RESOURCE = new Resource() {
@ -44,6 +49,11 @@ public final class ResourceFactory {
public String getName() {
return "NiFi Flow";
}
@Override
public String getSafeDescription() {
return "the user interface";
}
};
private final static Resource POLICY_RESOURCE = new Resource() {
@ -54,7 +64,12 @@ public final class ResourceFactory {
@Override
public String getName() {
return "Policy";
return "Policies for ";
}
@Override
public String getSafeDescription() {
return "the policies for ";
}
};
@ -68,6 +83,11 @@ public final class ResourceFactory {
public String getName() {
return "Counters";
}
@Override
public String getSafeDescription() {
return "counters";
}
};
private final static Resource PROVENANCE_RESOURCE = new Resource() {
@ -80,6 +100,11 @@ public final class ResourceFactory {
public String getName() {
return "Provenance";
}
@Override
public String getSafeDescription() {
return "provenance";
}
};
private final static Resource DATA_RESOURCE = new Resource() {
@ -90,7 +115,12 @@ public final class ResourceFactory {
@Override
public String getName() {
return "Data";
return "Data for ";
}
@Override
public String getSafeDescription() {
return "the data for ";
}
};
@ -104,6 +134,11 @@ public final class ResourceFactory {
public String getName() {
return "Proxy User Requests";
}
@Override
public String getSafeDescription() {
return "proxy requests on behalf of users";
}
};
private final static Resource RESOURCE_RESOURCE = new Resource() {
@ -116,6 +151,11 @@ public final class ResourceFactory {
public String getName() {
return "NiFi Resources";
}
@Override
public String getSafeDescription() {
return "resources";
}
};
private final static Resource SITE_TO_SITE_RESOURCE = new Resource() {
@ -128,6 +168,11 @@ public final class ResourceFactory {
public String getName() {
return "Site to Site";
}
@Override
public String getSafeDescription() {
return "site-to-site details";
}
};
private final static Resource SYSTEM_RESOURCE = new Resource() {
@ -140,6 +185,11 @@ public final class ResourceFactory {
public String getName() {
return "System";
}
@Override
public String getSafeDescription() {
return "system diagnostics";
}
};
private final static Resource RESTRICTED_COMPONENTS_RESOURCE = new Resource() {
@ -152,6 +202,11 @@ public final class ResourceFactory {
public String getName() {
return "Restricted Components";
}
@Override
public String getSafeDescription() {
return "restricted components";
}
};
private final static Resource TENANT_RESOURCE = new Resource() {
@ -164,6 +219,11 @@ public final class ResourceFactory {
public String getName() {
return "Tenant";
}
@Override
public String getSafeDescription() {
return "users/user groups";
}
};
private final static Resource POLICIES_RESOURCE = new Resource() {
@ -177,6 +237,11 @@ public final class ResourceFactory {
public String getName() {
return "Access Policies";
}
@Override
public String getSafeDescription() {
return "policies";
}
};
/**
@ -271,30 +336,6 @@ public final class ResourceFactory {
return TENANT_RESOURCE;
}
/**
* Gets a Resource for performing transferring data to a port.
*
* @param isInputPort Whether this port is an input port or an output port
* @param identifier The identifier of the component being accessed
* @param name The name of the component being accessed
* @return The resource
*/
public static Resource getDataTransferResource(final boolean isInputPort, final String identifier, final String name) {
Objects.requireNonNull(identifier, "The component identifier must be specified.");
return new Resource() {
@Override
public String getIdentifier() {
return String.format("%s/%s/%s", ResourceType.DataTransfer.getValue(), isInputPort ? "input-ports" : "output-ports", identifier);
}
@Override
public String getName() {
return name;
}
};
}
/**
* Gets a Resource for performing transferring data to a port.
*
@ -314,6 +355,11 @@ public final class ResourceFactory {
public String getName() {
return "Transfer data to " + resource.getName();
}
@Override
public String getSafeDescription() {
return "data transfers to " + resource.getSafeDescription();
}
};
}
@ -342,7 +388,12 @@ public final class ResourceFactory {
@Override
public String getName() {
return "Policies for " + resource.getName();
return POLICY_RESOURCE.getName() + resource.getName();
}
@Override
public String getSafeDescription() {
return POLICY_RESOURCE.getSafeDescription() + resource.getSafeDescription();
}
};
}
@ -369,6 +420,47 @@ public final class ResourceFactory {
public String getName() {
return name;
}
@Override
public String getSafeDescription() {
final String componentType;
switch (resourceType) {
case ControllerService:
componentType = "Controller Service";
break;
case ProcessGroup:
componentType = "Process Group";
break;
case Template:
componentType = "Template";
break;
case Funnel:
componentType = "Funnel";
break;
case InputPort:
componentType = "Input Port";
break;
case OutputPort:
componentType = "Output Port";
break;
case Processor:
componentType = "Processor";
break;
case RemoteProcessGroup:
componentType = "Remote Process Group";
break;
case ReportingTask:
componentType = "Reporting Task";
break;
case Label:
componentType = "Label";
break;
default:
componentType = "Component";
break;
}
return componentType + " with ID " + identifier;
}
};
}
@ -387,7 +479,12 @@ public final class ResourceFactory {
@Override
public String getName() {
return "Data for " + resource.getName();
return DATA_RESOURCE.getName() + resource.getName();
}
@Override
public String getSafeDescription() {
return DATA_RESOURCE.getSafeDescription() + resource.getSafeDescription();
}
};
}

View File

@ -136,6 +136,11 @@ public final class StandardConnection implements Connection {
return name;
}
@Override
public String getSafeDescription() {
return "Connection " + StandardConnection.this.getIdentifier();
}
};
}
@ -172,7 +177,7 @@ public final class StandardConnection implements Connection {
@Override
public AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) {
if (user == null) {
return AuthorizationResult.denied("Unknown user");
return AuthorizationResult.denied("Unknown user.");
}
// check the source
@ -188,7 +193,7 @@ public final class StandardConnection implements Connection {
@Override
public void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) throws AccessDeniedException {
if (user == null) {
throw new AccessDeniedException("Unknown user");
throw new AccessDeniedException("Unknown user.");
}
getSourceAuthorizable().authorize(authorizer, action, user, resourceContext);

View File

@ -963,6 +963,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
public String getName() {
return resourceIdentifier;
}
@Override
public String getSafeDescription() {
return "User " + userId;
}
},
() -> userDAO.deleteUser(userId),
false, // no user specific policies to remove
@ -993,6 +998,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
public String getName() {
return resourceIdentifier;
}
@Override
public String getSafeDescription() {
return "User Group " + userGroupId;
}
},
() -> userGroupDAO.deleteUserGroup(userGroupId),
false, // no user group specific policies to remove
@ -1020,6 +1030,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
public String getName() {
return accessPolicy.getResource();
}
@Override
public String getSafeDescription() {
return "Policy " + accessPolicyId;
}
},
() -> accessPolicyDAO.deleteAccessPolicy(accessPolicyId),
false, // no need to clean up any policies as it's already been removed above
@ -2514,6 +2529,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
.accessAttempt(false)
.action(RequestAction.WRITE)
.userContext(userContext)
.explanationSupplier(() -> "Unable to retrieve port details.")
.build();
final AuthorizationResult result = authorizer.authorize(request);
@ -2680,6 +2696,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
public String getName() {
return resource;
}
@Override
public String getSafeDescription() {
return "Policy " + resource;
}
};
}
};
@ -3100,7 +3121,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
return entityFactory.createStatusHistoryEntity(dto, permissions);
}
private boolean authorizeAction(final Action action) {
private AuthorizationResult authorizeAction(final Action action) {
final String sourceId = action.getSourceId();
final Component type = action.getSourceType();
@ -3149,12 +3170,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
}
} catch (final ResourceNotFoundException e) {
// if the underlying component is gone, disallow
return false;
return AuthorizationResult.denied("The component of this action is no longer in the data flow.");
}
// perform the authorization
final AuthorizationResult result = authorizable.checkAuthorization(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
return Result.Approved.equals(result.getResult());
return authorizable.checkAuthorization(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
}
@Override
@ -3178,7 +3198,8 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
if (history.getActions() != null) {
final List<ActionEntity> actionEntities = new ArrayList<>();
for (final Action action : history.getActions()) {
actionEntities.add(entityFactory.createActionEntity(dtoFactory.createActionDto(action), authorizeAction(action)));
final AuthorizationResult result = authorizeAction(action);
actionEntities.add(entityFactory.createActionEntity(dtoFactory.createActionDto(action), Result.Approved.equals(result.getResult())));
}
historyDto.setActions(actionEntities);
}
@ -3197,9 +3218,10 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
throw new ResourceNotFoundException(String.format("Unable to find action with id '%s'.", actionId));
}
final boolean authorized = authorizeAction(action);
final AuthorizationResult result = authorizeAction(action);
final boolean authorized = Result.Approved.equals(result.getResult());
if (!authorized) {
throw new AccessDeniedException("Access is denied.");
throw new AccessDeniedException(result.getExplanation());
}
// return the action

View File

@ -115,12 +115,12 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
.accessAttempt(true)
.action(RequestAction.READ)
.userContext(userContext)
.explanationSupplier(() -> "Unable to view the user interface.")
.build();
final AuthorizationResult result = authorizer.authorize(request);
if (!Result.Approved.equals(result.getResult())) {
final String message = StringUtils.isNotBlank(result.getExplanation()) ? result.getExplanation() : "Access is denied";
throw new AccessDeniedException(message);
throw new AccessDeniedException(result.getExplanation());
}
});
}

View File

@ -252,7 +252,7 @@ public class AccessResource extends ApplicationResource {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
if (user == null) {
throw new AccessDeniedException("Unable to determine user details.");
throw new AccessDeniedException("No user authenticated in the request.");
}
final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(user.getIdentity());
@ -297,7 +297,7 @@ public class AccessResource extends ApplicationResource {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
if (user == null) {
throw new AccessDeniedException("Unable to determine user details.");
throw new AccessDeniedException("No user authenticated in the request.");
}
final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(user.getIdentity());

View File

@ -112,12 +112,23 @@ public class ControllerResource extends ApplicationResource {
.accessAttempt(true)
.action(action)
.userContext(userContext)
.explanationSupplier(() -> {
final StringBuilder explanation = new StringBuilder("Unable to ");
if (RequestAction.READ.equals(action)) {
explanation.append("view ");
} else {
explanation.append("modify ");
}
explanation.append("the controller.");
return explanation.toString();
})
.build();
final AuthorizationResult result = authorizer.authorize(request);
if (!Result.Approved.equals(result.getResult())) {
final String message = StringUtils.isNotBlank(result.getExplanation()) ? result.getExplanation() : "Access is denied";
throw new AccessDeniedException(message);
throw new AccessDeniedException(result.getExplanation());
}
}

View File

@ -98,12 +98,23 @@ public class CountersResource extends ApplicationResource {
.accessAttempt(true)
.action(action)
.userContext(userContext)
.explanationSupplier(() -> {
final StringBuilder explanation = new StringBuilder("Unable to ");
if (RequestAction.READ.equals(action)) {
explanation.append("view ");
} else {
explanation.append("modify ");
}
explanation.append("counters.");
return explanation.toString();
})
.build();
final AuthorizationResult result = authorizer.authorize(request);
if (!Result.Approved.equals(result.getResult())) {
final String message = StringUtils.isNotBlank(result.getExplanation()) ? result.getExplanation() : "Access is denied";
throw new AccessDeniedException(message);
throw new AccessDeniedException(result.getExplanation());
}
}

View File

@ -218,12 +218,12 @@ public class FlowResource extends ApplicationResource {
.accessAttempt(true)
.action(RequestAction.READ)
.userContext(userContext)
.explanationSupplier(() -> "Unable to view the user interface.")
.build();
final AuthorizationResult result = authorizer.authorize(request);
if (!Result.Approved.equals(result.getResult())) {
final String message = StringUtils.isNotBlank(result.getExplanation()) ? result.getExplanation() : "Access is denied";
throw new AccessDeniedException(message);
throw new AccessDeniedException(result.getExplanation());
}
}

View File

@ -108,12 +108,12 @@ public class ProvenanceResource extends ApplicationResource {
.accessAttempt(true)
.action(RequestAction.READ)
.userContext(userContext)
.explanationSupplier(() -> "Unable to query provenance.")
.build();
final AuthorizationResult result = authorizer.authorize(request);
if (!Result.Approved.equals(result.getResult())) {
final String message = StringUtils.isNotBlank(result.getExplanation()) ? result.getExplanation() : "Access is denied";
throw new AccessDeniedException(message);
throw new AccessDeniedException(result.getExplanation());
}
}

View File

@ -78,12 +78,12 @@ public class ResourceResource extends ApplicationResource {
.accessAttempt(true)
.action(RequestAction.READ)
.userContext(userContext)
.explanationSupplier(() -> "Unable to retrieve resources.")
.build();
final AuthorizationResult result = authorizer.authorize(request);
if (!Result.Approved.equals(result.getResult())) {
final String message = StringUtils.isNotBlank(result.getExplanation()) ? result.getExplanation() : "Access is denied";
throw new AccessDeniedException(message);
throw new AccessDeniedException(result.getExplanation());
}
}

View File

@ -116,12 +116,12 @@ public class SiteToSiteResource extends ApplicationResource {
.accessAttempt(true)
.action(RequestAction.READ)
.userContext(userContext)
.explanationSupplier(() -> "Unable to retrieve site to site details.")
.build();
final AuthorizationResult result = authorizer.authorize(request);
if (!Result.Approved.equals(result.getResult())) {
final String message = StringUtils.isNotBlank(result.getExplanation()) ? result.getExplanation() : "Access is denied";
throw new AccessDeniedException(message);
throw new AccessDeniedException(result.getExplanation());
}
}

View File

@ -81,12 +81,12 @@ public class SystemDiagnosticsResource extends ApplicationResource {
.accessAttempt(true)
.action(RequestAction.READ)
.userContext(userContext)
.explanationSupplier(() -> "Unable to view system diagnostics.")
.build();
final AuthorizationResult result = authorizer.authorize(request);
if (!Result.Approved.equals(result.getResult())) {
final String message = StringUtils.isNotBlank(result.getExplanation()) ? result.getExplanation() : "Access is denied";
throw new AccessDeniedException(message);
throw new AccessDeniedException(result.getExplanation());
}
}

View File

@ -58,13 +58,16 @@ public class AccessDeniedExceptionMapper implements ExceptionMapper<AccessDenied
identity = user.getIdentity();
}
logger.info(String.format("%s does not have permission to access the requested resource. Returning %s response.", identity, status));
logger.info(String.format("%s does not have permission to access the requested resource. %s Returning %s response.", identity, exception.getMessage(), status));
if (logger.isDebugEnabled()) {
logger.debug(StringUtils.EMPTY, exception);
}
return Response.status(status).entity("Unable to perform the desired action due to insufficient permissions. Contact the system administrator.").type("text/plain").build();
return Response.status(status)
.entity(String.format("%s Contact the system administrator.", exception.getMessage()))
.type("text/plain")
.build();
}
}

View File

@ -1241,7 +1241,7 @@ public class ControllerFacade implements Authorizable {
private AuthorizationResult checkAuthorizationForReplay(final ProvenanceEventRecord event) {
// if the connection id isn't specified, then the replay wouldn't be available anyways and we have nothing to authorize against so deny it`
if (event.getSourceQueueIdentifier() == null) {
return AuthorizationResult.denied();
return AuthorizationResult.denied("The connection id in the provenance event is unknown.");
}
final NiFiUser user = NiFiUserUtils.getNiFiUser();
@ -1272,7 +1272,7 @@ public class ControllerFacade implements Authorizable {
private void authorizeReplay(final ProvenanceEventRecord event) {
// if the connection id isn't specified, then the replay wouldn't be available anyways and we have nothing to authorize against so deny it`
if (event.getSourceQueueIdentifier() == null) {
throw new AccessDeniedException("The connection id is unknown.");
throw new AccessDeniedException("The connection id in the provenance event is unknown.");
}
final NiFiUser user = NiFiUserUtils.getNiFiUser();

View File

@ -514,7 +514,7 @@ nf.Common = (function () {
if (xhr.status === 401) {
$('#message-title').text('Unauthorized');
} else if (xhr.status === 403) {
$('#message-title').text('Access Denied');
$('#message-title').text('Insufficient Permissions');
} else if (xhr.status === 409) {
$('#message-title').text('Invalid State');
} else {
@ -535,12 +535,17 @@ nf.Common = (function () {
return;
}
// status code 400, 403, 404, and 409 are expected response codes for common errors.
if (xhr.status === 400 || xhr.status === 403 || xhr.status === 404 || xhr.status === 409 || xhr.status === 503) {
// status code 400, 404, and 409 are expected response codes for common errors.
if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409 || xhr.status === 503) {
nf.Dialog.showOkDialog({
headerText: 'Error',
dialogContent: nf.Common.escapeHtml(xhr.responseText)
});
} else if (xhr.status === 403) {
nf.Dialog.showOkDialog({
headerText: 'Insufficient Permissions',
dialogContent: nf.Common.escapeHtml(xhr.responseText)
});
} else {
if (xhr.status < 99 || xhr.status === 12007 || xhr.status === 12029) {
var content = 'Please ensure the application is running and check the logs for any errors.';

View File

@ -2384,14 +2384,14 @@ public class PersistentProvenanceRepository implements ProvenanceRepository {
}
if (user == null) {
throw new AccessDeniedException("Cannot retrieve Provenance Lineage Submission because no user id was provided");
throw new AccessDeniedException("Cannot retrieve Provenance Lineage Submission because no user id was provided in the lineage request.");
}
if (userId == null || userId.equals(user.getIdentity())) {
return submission;
}
throw new AccessDeniedException("Cannot retrieve Provenance Lineage Submission because " + user.getIdentity() + " is not the user who submitted the request");
throw new AccessDeniedException("Cannot retrieve Provenance Lineage Submission because " + user.getIdentity() + " is not the user who submitted the request.");
}
@Override
@ -2405,14 +2405,14 @@ public class PersistentProvenanceRepository implements ProvenanceRepository {
}
if (user == null) {
throw new AccessDeniedException("Cannot retrieve Provenance Query Submission because no user id was provided");
throw new AccessDeniedException("Cannot retrieve Provenance Query Submission because no user id was provided in the provenance request.");
}
if (userId == null || userId.equals(user.getIdentity())) {
return submission;
}
throw new AccessDeniedException("Cannot retrieve Provenance Query Submission because " + user.getIdentity() + " is not the user who submitted the request");
throw new AccessDeniedException("Cannot retrieve Provenance Query Submission because " + user.getIdentity() + " is not the user who submitted the request.");
}
@Override

View File

@ -460,14 +460,14 @@ public class VolatileProvenanceRepository implements ProvenanceRepository {
}
if (user == null) {
throw new AccessDeniedException("Cannot retrieve Provenance Query Submission because no user id was provided");
throw new AccessDeniedException("Cannot retrieve Provenance Query Submission because no user id was provided in the provenance request.");
}
if (userId == null || userId.equals(user.getIdentity())) {
return submission;
}
throw new AccessDeniedException("Cannot retrieve Provenance Query Submission because " + user.getIdentity() + " is not the user who submitted the request");
throw new AccessDeniedException("Cannot retrieve Provenance Query Submission because " + user.getIdentity() + " is not the user who submitted the request.");
}
public Lineage computeLineage(final String flowFileUUID, final NiFiUser user) throws IOException {
@ -520,14 +520,14 @@ public class VolatileProvenanceRepository implements ProvenanceRepository {
}
if (user == null) {
throw new AccessDeniedException("Cannot retrieve Provenance Query Submission because no user id was provided");
throw new AccessDeniedException("Cannot retrieve Provenance Lineage Submission because no user id was provided in the lineage request.");
}
if (userId == null || userId.equals(user.getIdentity())) {
return submission;
}
throw new AccessDeniedException("Cannot retrieve Provenance Query Submission because " + user.getIdentity() + " is not the user who submitted the request");
throw new AccessDeniedException("Cannot retrieve Provenance Lineage Submission because " + user.getIdentity() + " is not the user who submitted the request.");
}
public Lineage expandSpawnEventParents(String identifier) throws IOException {

View File

@ -179,13 +179,13 @@ public class RangerNiFiAuthorizer implements Authorizer {
final boolean doesPolicyExist = nifiPlugin.doesPolicyExist(request.getResource().getIdentifier());
if (doesPolicyExist) {
// a policy does exist for the resource so we were really denied access here
final String reason = result == null ? null : result.getReason();
if (reason == null) {
return AuthorizationResult.denied();
} else {
return AuthorizationResult.denied(result.getReason());
if (reason != null) {
logger.debug(String.format("Unable to authorize %s due to %s", identity, reason));
}
// a policy does exist for the resource so we were really denied access here
return AuthorizationResult.denied(request.getExplanationSupplier().get());
} else {
// a policy doesn't exist so return resource not found so NiFi can work back up the resource hierarchy
return AuthorizationResult.resourceNotFound();

View File

@ -467,6 +467,11 @@ public class TestRangerNiFiAuthorizer {
public String getName() {
return "/system";
}
@Override
public String getSafeDescription() {
return "system";
}
})
.action(RequestAction.WRITE)
.identity("admin")
@ -526,6 +531,11 @@ public class TestRangerNiFiAuthorizer {
public String getName() {
return name;
}
@Override
public String getSafeDescription() {
return name;
}
}
/**