NIFI-2003 Creating abstract authentication provider and incorporating into existing providers

NIFI-2201 Add support for seeding cluster nodes in authorizations.xml
- Passing client address along in user context on authorization requests
- This closes #628
This commit is contained in:
Bryan Bende 2016-06-16 11:51:00 -04:00 committed by Matt Gilman
parent 4b9df7d1e2
commit ba763b95e8
41 changed files with 915 additions and 146 deletions

View File

@ -105,31 +105,33 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
final UsersAndAccessPolicies usersAndAccessPolicies = getUsersAndAccessPolicies();
final String resourceIdentifier = request.getResource().getIdentifier();
final Set<AccessPolicy> policies = usersAndAccessPolicies.getAccessPolicies(resourceIdentifier);
if (policies == null || policies.isEmpty()) {
final AccessPolicy policy = usersAndAccessPolicies.getAccessPolicy(resourceIdentifier, request.getAction());
if (policy == null) {
return AuthorizationResult.resourceNotFound();
}
final User user = usersAndAccessPolicies.getUser(request.getIdentity());
if (user == null) {
return AuthorizationResult.denied("Unknown user with identity " + request.getIdentity());
}
final Set<Group> userGroups = usersAndAccessPolicies.getGroups(user.getIdentity());
for (AccessPolicy policy : policies) {
final boolean containsUser = policy.getUsers().contains(user.getIdentifier());
if (policy.getAction() == request.getAction() && (containsUser || containsGroup(userGroups, policy)) ) {
return AuthorizationResult.approved();
}
if (policy.getUsers().contains(user.getIdentifier()) || containsGroup(userGroups, policy)) {
return AuthorizationResult.approved();
}
return AuthorizationResult.denied();
}
private boolean containsGroup(Set<Group> userGroups, final AccessPolicy policy) {
/**
* Determines if the policy contains one of the user's groups.
*
* @param userGroups the set of the user's groups
* @param policy the policy
* @return true if one of the Groups in userGroups is contained in the policy
*/
private boolean containsGroup(final Set<Group> userGroups, final AccessPolicy policy) {
if (userGroups.isEmpty() || policy.getGroups().isEmpty()) {
return false;
}

View File

@ -0,0 +1,26 @@
/*
* 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.authorization;
/**
* Constants for keys that can be passed in the AuthorizationRequest user context Map.
*/
public enum UserContextKeys {
CLIENT_ADDRESS;
}

View File

@ -25,12 +25,13 @@ import java.util.Set;
public interface UsersAndAccessPolicies {
/**
* Retrieves the set of access policies for a given resource.
* Retrieves the set of access policies for a given resource and action.
*
* @param resourceIdentifier the resource identifier to retrieve policies for
* @return the set of access policies for the given resource
* @param action the action to retrieve policies for
* @return the access policy for the given resource and action
*/
public Set<AccessPolicy> getAccessPolicies(final String resourceIdentifier);
public AccessPolicy getAccessPolicy(final String resourceIdentifier, final RequestAction action);
/**
* Retrieves a user by an identity string.

View File

@ -23,9 +23,11 @@ import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
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;
public interface Authorizable {
@ -67,7 +69,13 @@ public interface Authorizable {
* @return is authorized
*/
default AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) {
// TODO - include user details context
final Map<String,String> userContext;
if (user.getClientAddress() != null && !user.getClientAddress().trim().isEmpty()) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
// build the request
final AuthorizationRequest request = new AuthorizationRequest.Builder()
@ -77,6 +85,7 @@ public interface Authorizable {
.action(action)
.resource(getResource())
.resourceContext(resourceContext)
.userContext(userContext)
.build();
// perform the authorization
@ -119,7 +128,13 @@ public interface Authorizable {
* @param resourceContext resource context
*/
default void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) throws AccessDeniedException {
// TODO - include user details context
final Map<String,String> userContext;
if (user.getClientAddress() != null && !user.getClientAddress().trim().isEmpty()) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.identity(user.getIdentity())
@ -128,6 +143,7 @@ public interface Authorizable {
.action(action)
.resource(getResource())
.resourceContext(resourceContext)
.userContext(userContext)
.build();
final AuthorizationResult result = authorizer.authorize(request);

View File

@ -36,4 +36,10 @@ public interface NiFiUser {
* @return <code>true</code> if the user is the unauthenticated Anonymous user
*/
boolean isAnonymous();
/**
* @return the address of the client that made the request which created this user
*/
String getClientAddress();
}

View File

@ -54,15 +54,14 @@ public class TestAbstractPolicyBasedAuthorizer {
final String userIdentifier = "userIdentifier1";
final String userIdentity = "userIdentity1";
final Set<AccessPolicy> policiesForResource = new HashSet<>();
policiesForResource.add(new AccessPolicy.Builder()
final AccessPolicy policy = new AccessPolicy.Builder()
.identifier("1")
.resource(TEST_RESOURCE.getIdentifier())
.addUser(userIdentifier)
.action(RequestAction.READ)
.build());
.build();
when(usersAndAccessPolicies.getAccessPolicies(TEST_RESOURCE.getIdentifier())).thenReturn(policiesForResource);
when(usersAndAccessPolicies.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
final User user = new User.Builder()
.identity(userIdentity)
@ -92,15 +91,14 @@ public class TestAbstractPolicyBasedAuthorizer {
final String userIdentity = "userIdentity1";
final String groupIdentifier = "groupIdentifier1";
final Set<AccessPolicy> policiesForResource = new HashSet<>();
policiesForResource.add(new AccessPolicy.Builder()
final AccessPolicy policy = new AccessPolicy.Builder()
.identifier("1")
.resource(TEST_RESOURCE.getIdentifier())
.addGroup(groupIdentifier)
.action(RequestAction.READ)
.build());
.build();
when(usersAndAccessPolicies.getAccessPolicies(TEST_RESOURCE.getIdentifier())).thenReturn(policiesForResource);
when(usersAndAccessPolicies.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
final User user = new User.Builder()
.identity(userIdentity)
@ -137,15 +135,14 @@ public class TestAbstractPolicyBasedAuthorizer {
final String userIdentifier = "userIdentifier1";
final String userIdentity = "userIdentity1";
final Set<AccessPolicy> policiesForResource = new HashSet<>();
policiesForResource.add(new AccessPolicy.Builder()
final AccessPolicy policy = new AccessPolicy.Builder()
.identifier("1")
.resource(TEST_RESOURCE.getIdentifier())
.addUser("NOT_USER_1")
.action(RequestAction.READ)
.build());
.build();
when(usersAndAccessPolicies.getAccessPolicies(TEST_RESOURCE.getIdentifier())).thenReturn(policiesForResource);
when(usersAndAccessPolicies.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
final User user = new User.Builder()
.identity(userIdentity)
@ -171,7 +168,7 @@ public class TestAbstractPolicyBasedAuthorizer {
UsersAndAccessPolicies usersAndAccessPolicies = Mockito.mock(UsersAndAccessPolicies.class);
when(authorizer.getUsersAndAccessPolicies()).thenReturn(usersAndAccessPolicies);
when(usersAndAccessPolicies.getAccessPolicies(TEST_RESOURCE.getIdentifier())).thenReturn(new HashSet<>());
when(usersAndAccessPolicies.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(null);
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.identity("userIdentity")

View File

@ -135,6 +135,8 @@ public class NiFiProperties extends Properties {
public static final String SECURITY_CLUSTER_AUTHORITY_PROVIDER_THREADS = "nifi.security.cluster.authority.provider.threads";
public static final String SECURITY_OCSP_RESPONDER_URL = "nifi.security.ocsp.responder.url";
public static final String SECURITY_OCSP_RESPONDER_CERTIFICATE = "nifi.security.ocsp.responder.certificate";
public static final String SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX = "nifi.security.identity.mapping.pattern.";
public static final String SECURITY_IDENTITY_MAPPING_VALUE_PREFIX = "nifi.security.identity.mapping.value.";
// web properties
public static final String WEB_WAR_DIR = "nifi.web.war.directory";

View File

@ -337,11 +337,23 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
}
@Override
public Set<AccessPolicy> getAccessPolicies(String resourceIdentifier) {
public AccessPolicy getAccessPolicy(final String resourceIdentifier, final RequestAction action) {
if (resourceIdentifier == null) {
throw new IllegalArgumentException("Resource Identifier cannot be null");
}
return policiesByResource.get(resourceIdentifier);
final Set<AccessPolicy> resourcePolicies = policiesByResource.get(resourceIdentifier);
if (resourcePolicies == null) {
return null;
}
for (AccessPolicy accessPolicy : resourcePolicies) {
if (accessPolicy.getAction() == action) {
return accessPolicy;
}
}
return null;
}
@Override

View File

@ -66,6 +66,8 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
/**
@ -103,6 +105,7 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
static final String PROP_AUTHORIZATIONS_FILE = "Authorizations File";
static final String PROP_INITIAL_ADMIN_IDENTITY = "Initial Admin Identity";
static final String PROP_LEGACY_AUTHORIZED_USERS_FILE = "Legacy Authorized Users File";
static final Pattern NODE_IDENTITY_PATTERN = Pattern.compile("Node Identity \\S+");
private Schema flowSchema;
private Schema usersSchema;
@ -114,6 +117,7 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
private String rootGroupId;
private String initialAdminIdentity;
private String legacyAuthorizedUsersFile;
private Set<String> nodeIdentities;
private final AtomicReference<AuthorizationsHolder> authorizationsHolder = new AtomicReference<>();
@ -169,12 +173,23 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
}
}
// get the value of the initial admin identity
final PropertyValue initialAdminIdentityProp = configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY);
initialAdminIdentity = initialAdminIdentityProp == null ? null : initialAdminIdentityProp.getValue();
// get the value of the legacy authorized users file
final PropertyValue legacyAuthorizedUsersProp = configurationContext.getProperty(PROP_LEGACY_AUTHORIZED_USERS_FILE);
legacyAuthorizedUsersFile = legacyAuthorizedUsersProp == null ? null : legacyAuthorizedUsersProp.getValue();
// extract any node identities
nodeIdentities = new HashSet<>();
for (Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
Matcher matcher = NODE_IDENTITY_PATTERN.matcher(entry.getKey());
if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
nodeIdentities.add(entry.getValue());
}
}
// load the authorizations
load();
@ -235,6 +250,8 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
convertLegacyAuthorizedUsers(authorizations);
}
populateNodes(authorizations);
// save any changes that were made and repopulate the holder
saveAndRefreshHolder(authorizations);
} else {
@ -337,6 +354,38 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
addAccessPolicy(authorizations, ResourceType.Policy.getValue(), adminUser.getIdentifier(), WRITE_CODE);
}
/**
* Creates a user for each node and gives the nodes write permission to /proxy.
*
* @param authorizations the overall authorizations
*/
private void populateNodes(Authorizations authorizations) {
for (String nodeIdentity : nodeIdentities) {
// see if we have an existing user for the given node identity
org.apache.nifi.authorization.file.generated.User jaxbNodeUser = null;
for (org.apache.nifi.authorization.file.generated.User user : authorizations.getUsers().getUser()) {
if (user.getIdentity().equals(nodeIdentity)) {
jaxbNodeUser = user;
break;
}
}
// if we didn't find an existing user then create a new one
if (jaxbNodeUser == null) {
// generate an identifier and add a User with the given identifier and identity
final UUID nodeIdentifier = UUID.nameUUIDFromBytes(nodeIdentity.getBytes(StandardCharsets.UTF_8));
final User nodeUser = new User.Builder().identifier(nodeIdentifier.toString()).identity(nodeIdentity).build();
jaxbNodeUser = createJAXBUser(nodeUser);
authorizations.getUsers().getUser().add(jaxbNodeUser);
}
// grant access to the proxy resource
addAccessPolicy(authorizations, ResourceType.Proxy.getValue(), jaxbNodeUser.getIdentifier(), READ_CODE);
addAccessPolicy(authorizations, ResourceType.Proxy.getValue(), jaxbNodeUser.getIdentifier(), WRITE_CODE);
}
}
/**
* Unmarshalls an existing authorized-users.xml and converts the object model to the new model.
*
@ -509,25 +558,42 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
* @param action the action for the policy
*/
private void addAccessPolicy(final Authorizations authorizations, final String resource, final String identity, final String action) {
final String uuidSeed = resource + identity;
final UUID policyIdentifier = UUID.nameUUIDFromBytes(uuidSeed.getBytes(StandardCharsets.UTF_8));
final AccessPolicy.Builder builder = new AccessPolicy.Builder()
.identifier(policyIdentifier.toString())
.resource(resource)
.addUser(identity);
if (action.equals(READ_CODE)) {
builder.action(RequestAction.READ);
} else if (action.equals(WRITE_CODE)) {
builder.action(RequestAction.WRITE);
} else {
throw new IllegalStateException("Unknown Policy Action: " + action);
// first try to find an existing policy for the given resource and action
Policy foundPolicy = null;
for (Policy policy : authorizations.getPolicies().getPolicy()) {
if (policy.getResource().equals(resource) && policy.getAction().equals(action)) {
foundPolicy = policy;
break;
}
}
final AccessPolicy accessPolicy = builder.build();
final Policy jaxbPolicy = createJAXBPolicy(accessPolicy);
authorizations.getPolicies().getPolicy().add(jaxbPolicy);
if (foundPolicy == null) {
// if we didn't find an existing policy create a new one
final String uuidSeed = resource + identity + action;
final UUID policyIdentifier = UUID.nameUUIDFromBytes(uuidSeed.getBytes(StandardCharsets.UTF_8));
final AccessPolicy.Builder builder = new AccessPolicy.Builder()
.identifier(policyIdentifier.toString())
.resource(resource)
.addUser(identity);
if (action.equals(READ_CODE)) {
builder.action(RequestAction.READ);
} else if (action.equals(WRITE_CODE)) {
builder.action(RequestAction.WRITE);
} else {
throw new IllegalStateException("Unknown Policy Action: " + action);
}
final AccessPolicy accessPolicy = builder.build();
final Policy jaxbPolicy = createJAXBPolicy(accessPolicy);
authorizations.getPolicies().getPolicy().add(jaxbPolicy);
} else {
// otherwise add the user to the existing policy
Policy.User policyUser = new Policy.User();
policyUser.setIdentifier(identity);
foundPolicy.getUser().add(policyUser);
}
}
/**

View File

@ -157,10 +157,12 @@ public class FileAuthorizerTest {
assertEquals(1, users.size());
UsersAndAccessPolicies usersAndAccessPolicies = authorizer.getUsersAndAccessPolicies();
assertEquals(1, usersAndAccessPolicies.getAccessPolicies(ResourceType.Flow.getValue()).size());
assertEquals(2, usersAndAccessPolicies.getAccessPolicies(ResourceType.Controller.getValue()).size());
assertEquals(1, usersAndAccessPolicies.getAccessPolicies(ResourceType.System.getValue()).size());
assertEquals(2, usersAndAccessPolicies.getAccessPolicies(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).size());
assertNotNull(usersAndAccessPolicies.getAccessPolicy(ResourceType.Flow.getValue(), RequestAction.READ));
assertNotNull(usersAndAccessPolicies.getAccessPolicy(ResourceType.Controller.getValue(), RequestAction.READ));
assertNotNull(usersAndAccessPolicies.getAccessPolicy(ResourceType.Controller.getValue(), RequestAction.WRITE));
assertNotNull(usersAndAccessPolicies.getAccessPolicy(ResourceType.System.getValue(), RequestAction.READ));
assertNotNull(usersAndAccessPolicies.getAccessPolicy(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID, RequestAction.READ));
assertNotNull(usersAndAccessPolicies.getAccessPolicy(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID, RequestAction.WRITE));
}
@Test
@ -340,7 +342,7 @@ public class FileAuthorizerTest {
assertEquals(adminIdentity, adminUser.getIdentity());
final Set<AccessPolicy> policies = authorizer.getAccessPolicies();
assertEquals(4, policies.size());
assertEquals(7, policies.size());
final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID;
@ -377,7 +379,7 @@ public class FileAuthorizerTest {
assertEquals(adminIdentity, adminUser.getIdentity());
final Set<AccessPolicy> policies = authorizer.getAccessPolicies();
assertEquals(3, policies.size());
assertEquals(5, policies.size());
final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID;
@ -414,7 +416,7 @@ public class FileAuthorizerTest {
assertEquals(adminIdentity, adminUser.getIdentity());
final Set<AccessPolicy> policies = authorizer.getAccessPolicies();
assertEquals(3, policies.size());
assertEquals(5, policies.size());
final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID;
@ -429,6 +431,46 @@ public class FileAuthorizerTest {
assertFalse(foundRootGroupPolicy);
}
@Test
public void testOnConfiguredWhenNodeIdentitiesProvided() throws Exception {
final String adminIdentity = "admin-user";
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
.thenReturn(new StandardPropertyValue(adminIdentity, null));
final String nodeIdentity1 = "node1";
final String nodeIdentity2 = "node2";
final Map<String,String> props = new HashMap<>();
props.put("Node Identity 1", nodeIdentity1);
props.put("Node Identity 2", nodeIdentity2);
when(configurationContext.getProperties()).thenReturn(props);
writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE);
authorizer.onConfigured(configurationContext);
User adminUser = authorizer.getUserByIdentity(adminIdentity);
assertNotNull(adminUser);
User nodeUser1 = authorizer.getUserByIdentity(nodeIdentity1);
assertNotNull(nodeUser1);
User nodeUser2 = authorizer.getUserByIdentity(nodeIdentity2);
assertNotNull(nodeUser2);
AccessPolicy proxyReadPolicy = authorizer.getUsersAndAccessPolicies().getAccessPolicy(ResourceType.Proxy.getValue(), RequestAction.READ);
AccessPolicy proxyWritePolicy = authorizer.getUsersAndAccessPolicies().getAccessPolicy(ResourceType.Proxy.getValue(), RequestAction.WRITE);
assertNotNull(proxyReadPolicy);
assertTrue(proxyReadPolicy.getUsers().contains(nodeUser1.getIdentifier()));
assertTrue(proxyReadPolicy.getUsers().contains(nodeUser2.getIdentifier()));
assertNotNull(proxyWritePolicy);
assertTrue(proxyWritePolicy.getUsers().contains(nodeUser1.getIdentifier()));
assertTrue(proxyWritePolicy.getUsers().contains(nodeUser2.getIdentifier()));
}
@Test
public void testOnConfiguredWhenAuthorizationsFileDoesNotExist() {
authorizer.onConfigured(configurationContext);

View File

@ -27,14 +27,24 @@ public class StandardNiFiUser implements NiFiUser {
private final String identity;
private final NiFiUser chain;
private final String clientAddress;
public StandardNiFiUser(String identity) {
this(identity, null);
this(identity, null, null);
}
public StandardNiFiUser(String identity, String clientAddress) {
this(identity, null, clientAddress);
}
public StandardNiFiUser(String identity, NiFiUser chain) {
this(identity, chain, null);
}
public StandardNiFiUser(String identity, NiFiUser chain, String clientAddress) {
this.identity = identity;
this.chain = chain;
this.clientAddress = clientAddress;
}
@ -53,6 +63,11 @@ public class StandardNiFiUser implements NiFiUser {
return this == ANONYMOUS;
}
@Override
public String getClientAddress() {
return clientAddress;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {

View File

@ -16,6 +16,7 @@
*/
package org.apache.nifi.controller;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.AccessDeniedException;
import org.apache.nifi.authorization.AuthorizationRequest;
import org.apache.nifi.authorization.AuthorizationResult;
@ -23,6 +24,7 @@ import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.Resource;
import org.apache.nifi.authorization.UserContextKeys;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.resource.ResourceType;
@ -41,7 +43,9 @@ import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.TemplateDTO;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class Template implements Authorizable {
@ -168,7 +172,13 @@ public class Template implements Authorizable {
}
private AuthorizationResult checkAuthorization(final Authorizer authorizer, final RequestAction action, final boolean accessAttempt, final NiFiUser user) {
// TODO - include user details context
final Map<String,String> userContext;
if (!StringUtils.isBlank(user.getClientAddress())) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
// build the request
final AuthorizationRequest request = new AuthorizationRequest.Builder()
@ -177,6 +187,7 @@ public class Template implements Authorizable {
.accessAttempt(accessAttempt)
.action(action)
.resource(getResource())
.userContext(userContext)
.build();
// perform the authorization

View File

@ -14,59 +14,10 @@
limitations under the License.
-->
<!--
This file lists all authorizations for this NiFi instance. Refer to the properties file and authorizers.xml for configuration details.
Available resources:
/flow - READ - allows user/entity to load the UI and see the flow structure
- WRITE - NA
/resource - READ - allows user/entity to retrieve the available resources
- WRITE - NA
/system - READ - allows user/entity to retrieve system level diagnostics (CPU load, disk utilization, etc)
- WRITE - NA
/controller - READ - allows user/entity to retrieve configuration details for the controller (controller bulletins, thread pool, reporting tasks, etc)
- WRITE - allows user/entity to modify configuration details for the controller
/provenance - READ - allows user/entity to perform provenance requests. results will be filtered based on access to provenance data per component
- WRITE - NA
/token - READ - NA
- WRITE - allows user/entity to create a token for access the REST API
/site-to-site - READ - allows user/entity to retrieve configuration details for performing site to site data transfers with this NiFi
- WRITE - NA
/proxy - READ - NA
- WRITE - allows user/entity to create a proxy request on behalf of another user
/process-groups/{id} - READ - allows user/entity to retrieve configuration details for the process group and all descendant components without explicit access policies
- WRITE - allows user/entity to create/update/delete configuration details for the process group and all descendant components without explicit access policies
/processors/{id} - READ - allows user/entity to retrieve configuration details for the processor overriding any inherited authorizations from an ancestor process group
- WRITE - allows user/entity to update/delete the processor overriding any inherited authorizations from an ancestor process group
/input-ports/{id} - READ - allows user/entity to retrieve configuration details for the input port overriding any inherited authorizations from an ancestor process group
- WRITE - allows user/entity to update/delete the input port overriding any inherited authorizations from an ancestor process group
/output-ports/{id} - READ - allows user/entity to retrieve configuration details for the output port overriding any inherited authorizations from an ancestor process group
- WRITE - allows user/entity to update/delete the output port overriding any inherited authorizations from an ancestor process group
/labels/{id} - READ - allows user/entity to retrieve configuration details for the label overriding any inherited authorizations from an ancestor process group
- WRITE - allows user/entity to update/delete the label overriding any inherited authorizations from an ancestor process group
/connections/{id} - READ - allows user/entity to retrieve configuration details for the connection overriding any inherited authorizations from an ancestor process group
- WRITE - allows user/entity to update/delete the label overriding any inherited authorizations from an ancestor process group
/remote-process-groups/{id} - READ - allows user/entity to retrieve configuration details for the remote process group overriding any inherited authorizations from an ancestor process group
- WRITE - allows user/entity to update/delete the remote process group overriding any inherited authorizations from an ancestor process group
/templates/{id} - READ - allows user/entity to retrieve configuration details for the template overriding any inherited authorizations from an ancestor process group
- WRITE - allows user/entity to create/update/delete the template overriding any inherited authorizations from an ancestor process group
/controller-services/{id} - READ - allows user/entity to retrieve configuration details for the controller service overriding any inherited authorizations from an ancestor process group
- WRITE - allows user/entity to update/delete the controller service overriding any inherited authorizations from an ancestor process group
/reporting-tasks/{id} - READ - allows user/entity to retrieve configuration details for the reporting tasks overriding any inherited authorizations from the controller
- WRITE - allows user/entity to create/update/delete the reporting tasks overriding any inherited authorizations from the controller
/{type}/{id}/provenance - READ - allows user/entity to view provenance data from the underlying component
- WRITE - NA
This file should not be manually edited. Authorizations should only be created through the NiFi UI, and during
initial setup of a new instance. Refer to authorizers.xml for properties that allow seeding a new installation
with initial authorizations.
-->
<authorizations>
<!--
<users>
<user identifier="1" identity="" />
</users>
<policies>
<policy identifier="1" resource="/flow" action="RW">
<user identifier="1" />
</policy>
</policies>
-->
</authorizations>

View File

@ -19,11 +19,34 @@
must be specified in the nifi.properties file.
-->
<authorizers>
<!--
The FileAuthorizer is NiFi's provided authorizer and has the following properties:
- Authorizations File - The file where the FileAuthorizer will store authorizations.
- Initial Admin Identity - The identity of an initial admin user that will be granted access to the UI and
given the ability to create additional users, groups, and policies. The value of this property could be
a DN when using certificates or LDAP, or a Kerberos principal. This property will only be used when there
are no other users, groups, and policies defined. If this property is specified then a Legacy Authorized
Users File can not be specified.
- Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically
converted to the new authorizations model. If this property is specified then an Initial Admin Identity can
not be specified, and this property will only be used when there are no other users, groups, and policies defined.
- Node Identity [unique key] - The identity of a NiFi cluster node. When clustered, a property for each node
should be defined, so that every node knows about every other node. If not clustered these properties can be ignored.
-->
<authorizer>
<identifier>file-provider</identifier>
<class>org.apache.nifi.authorization.FileAuthorizer</class>
<property name="Authorizations File">./conf/authorizations.xml</property>
<property name="Initial Admin Identity"></property>
<property name="Legacy Authorized Users File"></property>
<!--
<property name="Node Identity 1"></property>
<property name="Node Identity 2"></property>
-->
</authorizer>
</authorizers>

View File

@ -144,6 +144,16 @@ nifi.security.user.login.identity.provider=${nifi.security.user.login.identity.p
nifi.security.ocsp.responder.url=${nifi.security.ocsp.responder.url}
nifi.security.ocsp.responder.certificate=${nifi.security.ocsp.responder.certificate}
# Identity Mapping Properties #
# These properties allow normalizing user identities such that identities coming from different identity providers
# (certificates, LDAP, Kerberos) can be treated the same internally in NiFi. The following example demonstrates normalizing
# DNs from certificates and principals from Kerberos into a common identity string:
#
# nifi.security.identity.mapping.pattern.dn=^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$
# nifi.security.identity.mapping.value.dn=$1@$2
# nifi.security.identity.mapping.pattern.kerb=^(.*?)/instance@(.*?)$
# nifi.security.identity.mapping.value.kerb=$1@$2
# cluster common properties (all nodes must have same values) #
nifi.cluster.protocol.heartbeat.interval=${nifi.cluster.protocol.heartbeat.interval}
nifi.cluster.protocol.is.secure=${nifi.cluster.protocol.is.secure}

View File

@ -48,6 +48,7 @@ import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.UserContextKeys;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.user.NiFiUser;
@ -97,12 +98,21 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
private void authorizeFlowAccess(final NiFiUser user) {
// authorize access
serviceFacade.authorizeAccess(lookup -> {
final Map<String,String> userContext;
if (!StringUtils.isBlank(user.getClientAddress())) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getFlowResource())
.identity(user.getIdentity())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(RequestAction.READ)
.userContext(userContext)
.build();
final AuthorizationResult result = authorizer.authorize(request);

View File

@ -34,6 +34,7 @@ import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.UserContextKeys;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserDetails;
@ -77,6 +78,8 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
@ -108,12 +111,21 @@ public class AccessResource extends ApplicationResource {
* Authorizes access to the flow.
*/
private boolean hasFlowAccess(final NiFiUser user) {
final Map<String,String> userContext;
if (!StringUtils.isBlank(user.getClientAddress())) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getFlowResource())
.identity(user.getIdentity())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(RequestAction.READ)
.userContext(userContext)
.build();
final AuthorizationResult result = authorizer.authorize(request);
@ -198,7 +210,7 @@ public class AccessResource extends ApplicationResource {
// Extract the Base64 encoded token from the Authorization header
final String token = StringUtils.substringAfterLast(authorization, " ");
final JwtAuthenticationRequestToken jwtRequest = new JwtAuthenticationRequestToken(token);
final JwtAuthenticationRequestToken jwtRequest = new JwtAuthenticationRequestToken(token, httpServletRequest.getRemoteAddr());
final NiFiAuthenticationToken authenticationResponse = (NiFiAuthenticationToken) jwtAuthenticationProvider.authenticate(jwtRequest);
final NiFiUser nifiUser = ((NiFiUserDetails) authenticationResponse.getDetails()).getNiFiUser();
@ -215,7 +227,7 @@ public class AccessResource extends ApplicationResource {
} else {
try {
final X509AuthenticationRequestToken x509Request = new X509AuthenticationRequestToken(
httpServletRequest.getHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN), principalExtractor, certificates);
httpServletRequest.getHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN), principalExtractor, certificates, httpServletRequest.getRemoteAddr());
final NiFiAuthenticationToken authenticationResponse = (NiFiAuthenticationToken) x509AuthenticationProvider.authenticate(x509Request);
final NiFiUser nifiUser = ((NiFiUserDetails) authenticationResponse.getDetails()).getNiFiUser();

View File

@ -30,6 +30,7 @@ import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.UserContextKeys;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
@ -66,6 +67,8 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
/**
* RESTful endpoint for managing a Flow Controller.
@ -92,12 +95,21 @@ public class ControllerResource extends ApplicationResource {
private void authorizeController(final RequestAction action) {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
final Map<String,String> userContext;
if (!StringUtils.isBlank(user.getClientAddress())) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getControllerResource())
.identity(user.getIdentity())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(action)
.userContext(userContext)
.build();
final AuthorizationResult result = authorizer.authorize(request);

View File

@ -17,6 +17,8 @@
package org.apache.nifi.web.api;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
@ -40,6 +42,7 @@ import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.UserContextKeys;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
@ -84,12 +87,21 @@ public class CountersResource extends ApplicationResource {
private void authorizeCounters(final RequestAction action) {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
final Map<String,String> userContext;
if (!StringUtils.isBlank(user.getClientAddress())) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getCountersResource())
.identity(user.getIdentity())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(action)
.userContext(userContext)
.build();
final AuthorizationResult result = authorizer.authorize(request);

View File

@ -49,6 +49,7 @@ import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.Resource;
import org.apache.nifi.authorization.UserContextKeys;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.user.NiFiUser;
@ -210,12 +211,21 @@ public class FlowResource extends ApplicationResource {
private void authorizeFlow(final RequestAction action) {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
final Map<String,String> userContext;
if (!StringUtils.isBlank(user.getClientAddress())) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getFlowResource())
.identity(user.getIdentity())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(action)
.userContext(userContext)
.build();
final AuthorizationResult result = authorizer.authorize(request);
@ -228,12 +238,21 @@ public class FlowResource extends ApplicationResource {
private boolean isAuthorized(final RequestAction action, final Resource resource) {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
final Map<String,String> userContext;
if (!StringUtils.isBlank(user.getClientAddress())) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(resource)
.identity(user.getIdentity())
.anonymous(user.isAnonymous())
.accessAttempt(false)
.action(action)
.userContext(userContext)
.build();
final AuthorizationResult result = authorizer.authorize(request);

View File

@ -29,6 +29,7 @@ import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.UserContextKeys;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
@ -92,12 +93,21 @@ public class ProvenanceResource extends ApplicationResource {
private void authorizeProvenanceRequest() {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
final Map<String,String> userContext;
if (!StringUtils.isBlank(user.getClientAddress())) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getProvenanceResource())
.identity(user.getIdentity())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(RequestAction.READ)
.userContext(userContext)
.build();
final AuthorizationResult result = authorizer.authorize(request);

View File

@ -16,7 +16,9 @@
*/
package org.apache.nifi.web.api;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
@ -33,6 +35,7 @@ import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.UserContextKeys;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
@ -62,12 +65,21 @@ public class ResourceResource extends ApplicationResource {
private void authorizeResource() {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
final Map<String,String> userContext;
if (!StringUtils.isBlank(user.getClientAddress())) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getResourceResource())
.identity(user.getIdentity())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(RequestAction.READ)
.userContext(userContext)
.build();
final AuthorizationResult result = authorizer.authorize(request);

View File

@ -17,6 +17,8 @@
package org.apache.nifi.web.api;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.Consumes;
@ -36,6 +38,7 @@ import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.UserContextKeys;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
@ -68,12 +71,21 @@ public class SystemDiagnosticsResource extends ApplicationResource {
private void authorizeSystem() {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
final Map<String,String> userContext;
if (!StringUtils.isBlank(user.getClientAddress())) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getSystemResource())
.identity(user.getIdentity())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(RequestAction.READ)
.userContext(userContext)
.build();
final AuthorizationResult result = authorizer.authorize(request);

View File

@ -1210,7 +1210,8 @@ public class ControllerFacade implements Authorizable {
final List<String> dnChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(user);
for (final String identity : dnChain) {
final Authorizable eventAuthorizable = flowController.createProvenanceAuthorizable(componentId);
final NiFiUser chainUser = new StandardNiFiUser(identity) {
final String clientAddress = user.getIdentity().equals(identity) ? user.getClientAddress() : null;
final NiFiUser chainUser = new StandardNiFiUser(identity, clientAddress) {
@Override
public boolean isAnonymous() {
// allow current user to drive anonymous flag as anonymous users are never chained... supports single user case
@ -1243,7 +1244,8 @@ public class ControllerFacade implements Authorizable {
final List<String> dnChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(user);
for (final String identity : dnChain) {
final Authorizable eventAuthorizable = flowController.createProvenanceAuthorizable(componentId);
final NiFiUser chainUser = new StandardNiFiUser(identity) {
final String clientAddress = user.getIdentity().equals(identity) ? user.getClientAddress() : null;
final NiFiUser chainUser = new StandardNiFiUser(identity, clientAddress) {
@Override
public boolean isAnonymous() {
// allow current user to drive anonymous flag as anonymous users are never chained... supports single user case

View File

@ -16,12 +16,14 @@
*/
package org.apache.nifi.web.dao.impl;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.AccessDeniedException;
import org.apache.nifi.authorization.AuthorizationRequest;
import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.UserContextKeys;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.connectable.Connectable;
@ -57,6 +59,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -598,6 +601,14 @@ public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO
final List<String> dnChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(user);
dnChain.forEach(identity -> {
// build the request
final Map<String,String> userContext;
if (!StringUtils.isBlank(user.getClientAddress())) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.identity(identity)
.anonymous(user.isAnonymous())
@ -605,6 +616,7 @@ public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO
.action(RequestAction.WRITE)
.resource(connection.getResource())
.resourceContext(attributes)
.userContext(userContext)
.build();
// perform the authorization

View File

@ -0,0 +1,185 @@
/*
* 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.web.security;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Base AuthenticationProvider that provides common functionality to mapping identities.
*/
public abstract class NiFiAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(NiFiAuthenticationProvider.class);
private static final Pattern backReferencePattern = Pattern.compile("\\$(\\d+)");
private NiFiProperties properties;
private List<IdentityMapping> mappings;
/**
* @param properties the NiFiProperties instance
*/
public NiFiAuthenticationProvider(final NiFiProperties properties) {
this.properties = properties;
this.mappings = Collections.unmodifiableList(getIdentityMappings(properties));
}
/**
* Builds the identity mappings from NiFiProperties.
*
* @param properties the NiFiProperties instance
* @return a list of identity mappings
*/
private List<IdentityMapping> getIdentityMappings(final NiFiProperties properties) {
final List<IdentityMapping> mappings = new ArrayList<>();
// go through each property
for (String propertyName : properties.stringPropertyNames()) {
if (StringUtils.startsWith(propertyName, NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX)) {
final String key = StringUtils.substringAfter(propertyName, NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX);
final String identityPattern = properties.getProperty(propertyName);
if (StringUtils.isBlank(identityPattern)) {
LOGGER.warn("Identity Mapping property {} was found, but was empty", new Object[]{propertyName});
continue;
}
final String identityValueProperty = NiFiProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX + key;
final String identityValue = properties.getProperty(identityValueProperty);
if (StringUtils.isBlank(identityValue)) {
LOGGER.warn("Identity Mapping property {} was found, but corresponding value {} was not found",
new Object[]{propertyName, identityValueProperty});
continue;
}
final IdentityMapping identityMapping = new IdentityMapping(key, Pattern.compile(identityPattern), identityValue);
mappings.add(identityMapping);
LOGGER.debug("Found Identity Mapping with key = {}, pattern = {}, value = {}",
new Object[] {key, identityPattern, identityValue});
}
}
// sort the list by the key so users can control the ordering in nifi.properties
Collections.sort(mappings, new Comparator<IdentityMapping>() {
@Override
public int compare(IdentityMapping m1, IdentityMapping m2) {
return m1.getKey().compareTo(m2.getKey());
}
});
return mappings;
}
public List<IdentityMapping> getMappings() {
return mappings;
}
protected String mapIdentity(final String identity) {
for (IdentityMapping mapping : mappings) {
Matcher m = mapping.getPattern().matcher(identity);
if (m.matches()) {
final String pattern = mapping.getPattern().pattern();
final String replacementValue = escapeLiteralBackReferences(mapping.getReplacementValue(), m.groupCount());
return identity.replaceAll(pattern, replacementValue);
}
}
return identity;
}
// If we find a back reference that is not valid, then we will treat it as a literal string. For example, if we have 3 capturing
// groups and the Replacement Value has the value is "I owe $8 to him", then we want to treat the $8 as a literal "$8", rather
// than attempting to use it as a back reference.
private static String escapeLiteralBackReferences(final String unescaped, final int numCapturingGroups) {
if (numCapturingGroups == 0) {
return unescaped;
}
String value = unescaped;
final Matcher backRefMatcher = backReferencePattern.matcher(value);
while (backRefMatcher.find()) {
final String backRefNum = backRefMatcher.group(1);
if (backRefNum.startsWith("0")) {
continue;
}
final int originalBackRefIndex = Integer.parseInt(backRefNum);
int backRefIndex = originalBackRefIndex;
// if we have a replacement value like $123, and we have less than 123 capturing groups, then
// we want to truncate the 3 and use capturing group 12; if we have less than 12 capturing groups,
// then we want to truncate the 2 and use capturing group 1; if we don't have a capturing group then
// we want to truncate the 1 and get 0.
while (backRefIndex > numCapturingGroups && backRefIndex >= 10) {
backRefIndex /= 10;
}
if (backRefIndex > numCapturingGroups) {
final StringBuilder sb = new StringBuilder(value.length() + 1);
final int groupStart = backRefMatcher.start(1);
sb.append(value.substring(0, groupStart - 1));
sb.append("\\");
sb.append(value.substring(groupStart - 1));
value = sb.toString();
}
}
return value;
}
/**
* Holder to pass around the key, pattern, and replacement from an identity mapping in NiFiProperties.
*/
public static final class IdentityMapping {
private final String key;
private final Pattern pattern;
private final String replacementValue;
public IdentityMapping(String key, Pattern pattern, String replacementValue) {
this.key = key;
this.pattern = pattern;
this.replacementValue = replacementValue;
}
public String getKey() {
return key;
}
public Pattern getPattern() {
return pattern;
}
public String getReplacementValue() {
return replacementValue;
}
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.web.security;
import org.springframework.security.authentication.AbstractAuthenticationToken;
/**
* Base class for authentication request tokens in NiFI.
*/
public abstract class NiFiAuthenticationRequestToken extends AbstractAuthenticationToken {
private final String clientAddress;
/**
* @param clientAddress The address of the client making the request
*/
public NiFiAuthenticationRequestToken(final String clientAddress) {
super(null);
setAuthenticated(false);
this.clientAddress = clientAddress;
}
public String getClientAddress() {
return clientAddress;
}
}

View File

@ -51,7 +51,7 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
} else {
// Extract the Base64 encoded token from the Authorization header
final String token = StringUtils.substringAfterLast(authorization, " ");
return new JwtAuthenticationRequestToken(token);
return new JwtAuthenticationRequestToken(token, request.getRemoteAddr());
}
}
}

View File

@ -19,9 +19,10 @@ package org.apache.nifi.web.security.jwt;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserDetails;
import org.apache.nifi.authorization.user.StandardNiFiUser;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.InvalidAuthenticationException;
import org.apache.nifi.web.security.NiFiAuthenticationProvider;
import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@ -30,11 +31,12 @@ import io.jsonwebtoken.JwtException;
/**
*
*/
public class JwtAuthenticationProvider implements AuthenticationProvider {
public class JwtAuthenticationProvider extends NiFiAuthenticationProvider {
private final JwtService jwtService;
public JwtAuthenticationProvider(JwtService jwtService) {
public JwtAuthenticationProvider(JwtService jwtService, NiFiProperties nifiProperties) {
super(nifiProperties);
this.jwtService = jwtService;
}
@ -44,7 +46,7 @@ public class JwtAuthenticationProvider implements AuthenticationProvider {
try {
final String jwtPrincipal = jwtService.getAuthenticationFromToken(request.getToken());
final NiFiUser user = new StandardNiFiUser(jwtPrincipal);
final NiFiUser user = new StandardNiFiUser(mapIdentity(jwtPrincipal), request.getClientAddress());
return new NiFiAuthenticationToken(new NiFiUserDetails(user));
} catch (JwtException e) {
throw new InvalidAuthenticationException(e.getMessage(), e);

View File

@ -16,12 +16,12 @@
*/
package org.apache.nifi.web.security.jwt;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.apache.nifi.web.security.NiFiAuthenticationRequestToken;
/**
* This is an authentication request with a given JWT token.
*/
public class JwtAuthenticationRequestToken extends AbstractAuthenticationToken {
public class JwtAuthenticationRequestToken extends NiFiAuthenticationRequestToken {
private final String token;
@ -29,9 +29,10 @@ public class JwtAuthenticationRequestToken extends AbstractAuthenticationToken {
* Creates a representation of the jwt authentication request for a user.
*
* @param token The unique token for this user
* @param clientAddress the address of the client making the request
*/
public JwtAuthenticationRequestToken(final String token) {
super(null);
public JwtAuthenticationRequestToken(final String token, final String clientAddress) {
super(clientAddress);
setAuthenticated(false);
this.token = token;
}

View File

@ -56,11 +56,11 @@ public class OtpAuthenticationFilter extends NiFiAuthenticationFilter {
if (request.getContextPath().equals("/nifi-api")) {
if (isDownloadRequest(request.getPathInfo())) {
// handle download requests
return new OtpAuthenticationRequestToken(accessToken, true);
return new OtpAuthenticationRequestToken(accessToken, true, request.getRemoteAddr());
}
} else {
// handle requests to other context paths (other UI extensions)
return new OtpAuthenticationRequestToken(accessToken, false);
return new OtpAuthenticationRequestToken(accessToken, false, request.getRemoteAddr());
}
// the path is a support path for otp tokens

View File

@ -19,20 +19,22 @@ package org.apache.nifi.web.security.otp;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserDetails;
import org.apache.nifi.authorization.user.StandardNiFiUser;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.InvalidAuthenticationException;
import org.apache.nifi.web.security.NiFiAuthenticationProvider;
import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
/**
*
*/
public class OtpAuthenticationProvider implements AuthenticationProvider {
public class OtpAuthenticationProvider extends NiFiAuthenticationProvider {
private OtpService otpService;
public OtpAuthenticationProvider(OtpService otpService) {
public OtpAuthenticationProvider(OtpService otpService, NiFiProperties nifiProperties) {
super(nifiProperties);
this.otpService = otpService;
}
@ -47,7 +49,7 @@ public class OtpAuthenticationProvider implements AuthenticationProvider {
} else {
otpPrincipal = otpService.getAuthenticationFromUiExtensionToken(request.getToken());
}
final NiFiUser user = new StandardNiFiUser(otpPrincipal);
final NiFiUser user = new StandardNiFiUser(mapIdentity(otpPrincipal), request.getClientAddress());
return new NiFiAuthenticationToken(new NiFiUserDetails(user));
} catch (OtpAuthenticationException e) {
throw new InvalidAuthenticationException(e.getMessage(), e);

View File

@ -16,12 +16,12 @@
*/
package org.apache.nifi.web.security.otp;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.apache.nifi.web.security.NiFiAuthenticationRequestToken;
/**
* This is an authentication request with a given OTP token.
*/
public class OtpAuthenticationRequestToken extends AbstractAuthenticationToken {
public class OtpAuthenticationRequestToken extends NiFiAuthenticationRequestToken {
private final String token;
private final boolean isDownloadToken;
@ -30,9 +30,11 @@ public class OtpAuthenticationRequestToken extends AbstractAuthenticationToken {
* Creates a representation of the otp authentication request for a user.
*
* @param token The unique token for this user
* @param isDownloadToken Whether or not this represents a download token
* @param clientAddress the address of the client making the request
*/
public OtpAuthenticationRequestToken(final String token, final boolean isDownloadToken) {
super(null);
public OtpAuthenticationRequestToken(final String token, final boolean isDownloadToken, final String clientAddress) {
super(clientAddress);
setAuthenticated(false);
this.token = token;
this.isDownloadToken = isDownloadToken;

View File

@ -49,7 +49,7 @@ public class X509AuthenticationFilter extends NiFiAuthenticationFilter {
return null;
}
return new X509AuthenticationRequestToken(request.getHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN), principalExtractor, certificates);
return new X509AuthenticationRequestToken(request.getHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN), principalExtractor, certificates, request.getRemoteAddr());
}
/* setters */

View File

@ -23,31 +23,36 @@ import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.UserContextKeys;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserDetails;
import org.apache.nifi.authorization.user.StandardNiFiUser;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.InvalidAuthenticationException;
import org.apache.nifi.web.security.NiFiAuthenticationProvider;
import org.apache.nifi.web.security.ProxiedEntitiesUtils;
import org.apache.nifi.web.security.UntrustedProxyException;
import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
/**
*
*/
public class X509AuthenticationProvider implements AuthenticationProvider {
public class X509AuthenticationProvider extends NiFiAuthenticationProvider {
private X509IdentityProvider certificateIdentityProvider;
private Authorizer authorizer;
public X509AuthenticationProvider(final X509IdentityProvider certificateIdentityProvider, final Authorizer authorizer) {
public X509AuthenticationProvider(final X509IdentityProvider certificateIdentityProvider, final Authorizer authorizer, final NiFiProperties nifiProperties) {
super(nifiProperties);
this.certificateIdentityProvider = certificateIdentityProvider;
this.authorizer = authorizer;
}
@ -65,7 +70,8 @@ public class X509AuthenticationProvider implements AuthenticationProvider {
}
if (StringUtils.isBlank(request.getProxiedEntitiesChain())) {
return new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(authenticationResponse.getIdentity())));
final String mappedIdentity = mapIdentity(authenticationResponse.getIdentity());
return new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(mappedIdentity, request.getClientAddress())));
} else {
// build the entire proxy chain if applicable - <end-user><proxy1><proxy2>
final List<String> proxyChain = new ArrayList<>(ProxiedEntitiesUtils.tokenizeProxiedEntitiesChain(request.getProxiedEntitiesChain()));
@ -74,7 +80,7 @@ public class X509AuthenticationProvider implements AuthenticationProvider {
// add the chain as appropriate to each proxy
NiFiUser proxy = null;
for (final ListIterator<String> chainIter = proxyChain.listIterator(proxyChain.size()); chainIter.hasPrevious();) {
final String identity = chainIter.previous();
final String identity = mapIdentity(chainIter.previous());
if (chainIter.hasPrevious()) {
// authorize this proxy in order to authenticate this user
@ -84,6 +90,7 @@ public class X509AuthenticationProvider implements AuthenticationProvider {
.accessAttempt(true)
.action(RequestAction.WRITE)
.resource(ResourceFactory.getProxyResource())
.userContext(proxy == null ? getUserContext(request) : null) // only set the context for the real user
.build();
final AuthorizationResult proxyAuthorizationResult = authorizer.authorize(proxyAuthorizationRequest);
@ -92,13 +99,29 @@ public class X509AuthenticationProvider implements AuthenticationProvider {
}
}
proxy = new StandardNiFiUser(chainIter.previous(), proxy);
// only set the client address for user making the request, we don't know the client address of the proxies
if (proxy == null) {
proxy = new StandardNiFiUser(identity, proxy, request.getClientAddress());
} else {
proxy = new StandardNiFiUser(identity, proxy, null);
}
}
return new NiFiAuthenticationToken(new NiFiUserDetails(proxy));
}
}
private Map<String,String> getUserContext(final X509AuthenticationRequestToken request) {
final Map<String,String> userContext;
if (!StringUtils.isBlank(request.getClientAddress())) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), request.getClientAddress());
} else {
userContext = null;
}
return userContext;
}
@Override
public boolean supports(Class<?> authentication) {
return X509AuthenticationRequestToken.class.isAssignableFrom(authentication);

View File

@ -17,7 +17,7 @@
package org.apache.nifi.web.security.x509;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.apache.nifi.web.security.NiFiAuthenticationRequestToken;
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
import java.security.cert.X509Certificate;
@ -25,7 +25,7 @@ import java.security.cert.X509Certificate;
/**
* This is an authentication request with a given JWT token.
*/
public class X509AuthenticationRequestToken extends AbstractAuthenticationToken {
public class X509AuthenticationRequestToken extends NiFiAuthenticationRequestToken {
private final String proxiedEntitiesChain;
private final X509PrincipalExtractor principalExtractor;
@ -37,8 +37,8 @@ public class X509AuthenticationRequestToken extends AbstractAuthenticationToken
* @param proxiedEntitiesChain The http servlet request
* @param certificates The certificate chain
*/
public X509AuthenticationRequestToken(final String proxiedEntitiesChain, final X509PrincipalExtractor principalExtractor, final X509Certificate[] certificates) {
super(null);
public X509AuthenticationRequestToken(final String proxiedEntitiesChain, final X509PrincipalExtractor principalExtractor, final X509Certificate[] certificates, final String clientAddress) {
super(clientAddress);
setAuthenticated(false);
this.proxiedEntitiesChain = proxiedEntitiesChain;
this.principalExtractor = principalExtractor;

View File

@ -43,6 +43,7 @@
<bean id="x509AuthenticationProvider" class="org.apache.nifi.web.security.x509.X509AuthenticationProvider">
<constructor-arg ref="certificateIdentityProvider" index="0"/>
<constructor-arg ref="authorizer" index="1"/>
<constructor-arg ref="nifiProperties"/>
</bean>
<!-- jwt service -->
@ -53,6 +54,7 @@
<!-- jwt authentication provider -->
<bean id="jwtAuthenticationProvider" class="org.apache.nifi.web.security.jwt.JwtAuthenticationProvider">
<constructor-arg ref="jwtService"/>
<constructor-arg ref="nifiProperties"/>
</bean>
<!-- otp service -->
@ -61,6 +63,7 @@
<!-- otp authentication provider -->
<bean id="otpAuthenticationProvider" class="org.apache.nifi.web.security.otp.OtpAuthenticationProvider">
<constructor-arg ref="otpService"/>
<constructor-arg ref="nifiProperties"/>
</bean>
<!-- Kerberos service -->

View File

@ -0,0 +1,203 @@
/*
* 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.web.security;
import org.apache.nifi.util.NiFiProperties;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import java.util.List;
import java.util.Properties;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.when;
public class NiFiAuthenticationProviderTest {
@Test
public void testValidPropertiesProvided() {
final String pattern = "^cn=(.*?),dc=(.*?),dc=(.*?)$";
final String value = "$1@$2.$3";
Properties properties = new Properties();
properties.setProperty("nifi.security.identity.mapping.pattern.dn", pattern);
properties.setProperty("nifi.security.identity.mapping.value.dn", value);
final NiFiProperties nifiProperties = getNiFiProperties(properties);
TestableNiFiAuthenticationProvider provider = new TestableNiFiAuthenticationProvider(nifiProperties);
List<NiFiAuthenticationProvider.IdentityMapping> mappings = provider.getMappings();
assertEquals(1, mappings.size());
assertEquals("dn", mappings.get(0).getKey());
assertEquals(pattern, mappings.get(0).getPattern().pattern());
assertEquals(value, mappings.get(0).getReplacementValue());
}
@Test
public void testNoMappings() {
Properties properties = new Properties();
final NiFiProperties nifiProperties = getNiFiProperties(properties);
TestableNiFiAuthenticationProvider provider = new TestableNiFiAuthenticationProvider(nifiProperties);
List<NiFiAuthenticationProvider.IdentityMapping> mappings = provider.getMappings();
assertEquals(0, mappings.size());
final String identity = "john";
assertEquals(identity, provider.mapIdentity(identity));
}
@Test
public void testPatternPropertyWithNoValue() {
Properties properties = new Properties();
properties.setProperty("nifi.security.identity.mapping.pattern.dn", "");
properties.setProperty("nifi.security.identity.mapping.value.dn", "value");
final NiFiProperties nifiProperties = getNiFiProperties(properties);
TestableNiFiAuthenticationProvider provider = new TestableNiFiAuthenticationProvider(nifiProperties);
List<NiFiAuthenticationProvider.IdentityMapping> mappings = provider.getMappings();
assertEquals(0, mappings.size());
}
@Test
public void testPatternPropertyWithNoCorrespondingValueProperty() {
Properties properties = new Properties();
properties.setProperty("nifi.security.identity.mapping.pattern.dn", "");
final NiFiProperties nifiProperties = getNiFiProperties(properties);
TestableNiFiAuthenticationProvider provider = new TestableNiFiAuthenticationProvider(nifiProperties);
List<NiFiAuthenticationProvider.IdentityMapping> mappings = provider.getMappings();
assertEquals(0, mappings.size());
}
@Test
public void testMultipleMappings() {
Properties properties = new Properties();
properties.setProperty("nifi.security.identity.mapping.pattern.1", "pattern1");
properties.setProperty("nifi.security.identity.mapping.value.1", "value1");
properties.setProperty("nifi.security.identity.mapping.pattern.2", "pattern2");
properties.setProperty("nifi.security.identity.mapping.value.2", "value2");
properties.setProperty("nifi.security.identity.mapping.pattern.3", "pattern3");
properties.setProperty("nifi.security.identity.mapping.value.3", "value3");
final NiFiProperties nifiProperties = getNiFiProperties(properties);
TestableNiFiAuthenticationProvider provider = new TestableNiFiAuthenticationProvider(nifiProperties);
List<NiFiAuthenticationProvider.IdentityMapping> mappings = provider.getMappings();
assertEquals(3, mappings.size());
}
@Test
public void testMapIdentityWithSingleMapping() {
final Properties properties = new Properties();
properties.setProperty("nifi.security.identity.mapping.pattern.dn", "^cn=(.*?),dc=(.*?),dc=(.*?)$");
properties.setProperty("nifi.security.identity.mapping.value.dn", "$1@$2.$3");
final NiFiProperties nifiProperties = getNiFiProperties(properties);
final TestableNiFiAuthenticationProvider provider = new TestableNiFiAuthenticationProvider(nifiProperties);
final String identity = "cn=jsmith,dc=aaa,dc=bbb";
final String mappedIdentity = provider.mapIdentity(identity);
assertEquals("jsmith@aaa.bbb", mappedIdentity);
}
@Test
public void testMapIdentityWithIncorrectGroupReference() {
final Properties properties = new Properties();
properties.setProperty("nifi.security.identity.mapping.pattern.dn", "^cn=(.*?),dc=(.*?),dc=(.*?)$");
properties.setProperty("nifi.security.identity.mapping.value.dn", "$1@$2.$4");
final NiFiProperties nifiProperties = getNiFiProperties(properties);
final TestableNiFiAuthenticationProvider provider = new TestableNiFiAuthenticationProvider(nifiProperties);
final String identity = "cn=jsmith,dc=aaa,dc=bbb";
final String mappedIdentity = provider.mapIdentity(identity);
assertEquals("jsmith@aaa.$4", mappedIdentity);
}
@Test
public void testMapIdentityWithNoGroupReference() {
final Properties properties = new Properties();
properties.setProperty("nifi.security.identity.mapping.pattern.dn", "^cn=(.*?),dc=(.*?),dc=(.*?)$");
properties.setProperty("nifi.security.identity.mapping.value.dn", "this makes no sense");
final NiFiProperties nifiProperties = getNiFiProperties(properties);
final TestableNiFiAuthenticationProvider provider = new TestableNiFiAuthenticationProvider(nifiProperties);
final String identity = "cn=jsmith,dc=aaa,dc=bbb";
final String mappedIdentity = provider.mapIdentity(identity);
assertEquals("this makes no sense", mappedIdentity);
}
@Test
public void testMapIdentityWithMultipleMatchingPatterns() {
// create two pattern properties that are the same, but the value properties are different
final Properties properties = new Properties();
properties.setProperty("nifi.security.identity.mapping.pattern.dn2", "^cn=(.*?),dc=(.*?),dc=(.*?)$");
properties.setProperty("nifi.security.identity.mapping.value.dn2", "$1_$2_$3");
properties.setProperty("nifi.security.identity.mapping.pattern.dn1", "^cn=(.*?),dc=(.*?),dc=(.*?)$");
properties.setProperty("nifi.security.identity.mapping.value.dn1", "$1 $2 $3");
final NiFiProperties nifiProperties = getNiFiProperties(properties);
final TestableNiFiAuthenticationProvider provider = new TestableNiFiAuthenticationProvider(nifiProperties);
// the mapping should always use dn1 because it is sorted
final String identity = "cn=jsmith,dc=aaa,dc=bbb";
final String mappedIdentity = provider.mapIdentity(identity);
assertEquals("jsmith aaa bbb", mappedIdentity);
}
private NiFiProperties getNiFiProperties(final Properties properties) {
final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class);
when(nifiProperties.stringPropertyNames()).thenReturn(properties.stringPropertyNames());
when(nifiProperties.getProperty(anyString())).then(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocationOnMock) throws Throwable {
return properties.getProperty((String)invocationOnMock.getArguments()[0]);
}
});
return nifiProperties;
}
private static class TestableNiFiAuthenticationProvider extends NiFiAuthenticationProvider {
/**
* @param properties the NiFiProperties instance
*/
public TestableNiFiAuthenticationProvider(NiFiProperties properties) {
super(properties);
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return false;
}
}
}

View File

@ -17,9 +17,11 @@
package org.apache.nifi.web.security.otp;
import org.apache.nifi.authorization.user.NiFiUserDetails;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@ -41,6 +43,7 @@ public class OtpAuthenticationProviderTest {
private OtpService otpService;
private OtpAuthenticationProvider otpAuthenticationProvider;
private NiFiProperties nifiProperties;
@Before
public void setUp() throws Exception {
@ -72,12 +75,12 @@ public class OtpAuthenticationProviderTest {
}
}).when(otpService).getAuthenticationFromUiExtensionToken(anyString());
otpAuthenticationProvider = new OtpAuthenticationProvider(otpService);
otpAuthenticationProvider = new OtpAuthenticationProvider(otpService, Mockito.mock(NiFiProperties.class));
}
@Test
public void testUiExtensionPath() throws Exception {
final OtpAuthenticationRequestToken request = new OtpAuthenticationRequestToken(UI_EXTENSION_TOKEN, false);
final OtpAuthenticationRequestToken request = new OtpAuthenticationRequestToken(UI_EXTENSION_TOKEN, false, null);
final NiFiAuthenticationToken result = (NiFiAuthenticationToken) otpAuthenticationProvider.authenticate(request);
final NiFiUserDetails details = (NiFiUserDetails) result.getPrincipal();
@ -89,7 +92,7 @@ public class OtpAuthenticationProviderTest {
@Test
public void testDownload() throws Exception {
final OtpAuthenticationRequestToken request = new OtpAuthenticationRequestToken(DOWNLOAD_TOKEN, true);
final OtpAuthenticationRequestToken request = new OtpAuthenticationRequestToken(DOWNLOAD_TOKEN, true, null);
final NiFiAuthenticationToken result = (NiFiAuthenticationToken) otpAuthenticationProvider.authenticate(request);
final NiFiUserDetails details = (NiFiUserDetails) result.getPrincipal();

View File

@ -1743,6 +1743,12 @@ public class TestPersistentProvenanceRepository {
public boolean isAnonymous() {
return false;
}
@Override
public String getClientAddress() {
return null;
}
};
}

View File

@ -201,6 +201,11 @@ public class TestVolatileProvenanceRepository {
public boolean isAnonymous() {
return false;
}
@Override
public String getClientAddress() {
return null;
}
};
}
}