This is an automated email from the ASF dual-hosted git repository.
alopresto pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new d846a74 NIFI-7568 - Applied Kerberos mappings to authentication
requests. Kerberos mappings should now be applied correctly in H2 database for
username/password based login. Added tests. Logout now deletes signing key by
key ID rather than identity. Validate token expiration now uses mapped identity
instead, which allows logging of the mapped identity. Updated delete key to
expect only 0 or 1 keys deleted.
d846a74 is described below
commit d846a74730edb4d142e0a3051c60e9fec33f1f09
Author: Nathan Gough <[email protected]>
AuthorDate: Mon Jun 29 19:18:22 2020 -0400
NIFI-7568 - Applied Kerberos mappings to authentication requests. Kerberos
mappings should now be applied correctly in H2 database for username/password
based login. Added tests.
Logout now deletes signing key by key ID rather than identity.
Validate token expiration now uses mapped identity instead, which allows
logging of the mapped identity.
Updated delete key to expect only 0 or 1 keys deleted.
This closes #4416.
Signed-off-by: Andy LoPresto <[email protected]>
---
.../java/org/apache/nifi/admin/dao/KeyDAO.java | 6 +-
.../apache/nifi/admin/dao/impl/StandardKeyDAO.java | 8 +-
.../org/apache/nifi/admin/service/KeyService.java | 4 +-
...{DeleteKeysAction.java => DeleteKeyAction.java} | 17 +-
.../admin/service/impl/StandardKeyService.java | 26 +--
.../src/main/java/org/apache/nifi/key/Key.java | 9 +
.../org/apache/nifi/web/api/AccessResource.java | 19 +-
.../accesscontrol/ITAccessTokenEndpoint.java | 9 +-
.../nifi/integration/util/NiFiTestAuthorizer.java | 4 +-
.../util/NiFiTestLoginIdentityProvider.java | 1 +
.../apache/nifi/integration/util/NiFiTestUser.java | 8 +
.../nifi-mapped-identities.properties | 144 +++++++++++++++
.../web/security/jwt/JwtAuthenticationFilter.java | 2 +-
.../apache/nifi/web/security/jwt/JwtService.java | 27 ++-
.../jwt/JwtAuthenticationProviderTest.java | 132 ++++++++++++++
.../nifi/web/security/jwt/JwtServiceTest.java | 202 ++++++++++++++++++---
.../nifi/web/security/jwt/TestKeyService.java | 73 ++++++++
17 files changed, 613 insertions(+), 78 deletions(-)
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/KeyDAO.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/KeyDAO.java
index 9626445..3cfaf2f 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/KeyDAO.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/KeyDAO.java
@@ -48,9 +48,9 @@ public interface KeyDAO {
Key createKey(String identity);
/**
- * Deletes all keys for the specified user identity.
+ * Deletes a key using the key ID.
*
- * @param identity The user identity
+ * @param keyId The key ID
*/
- void deleteKeys(String identity);
+ Integer deleteKey(Integer keyId);
}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardKeyDAO.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardKeyDAO.java
index 44d9716..28d090d 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardKeyDAO.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardKeyDAO.java
@@ -47,7 +47,7 @@ public class StandardKeyDAO implements KeyDAO {
+ ")";
private static final String DELETE_KEYS = "DELETE FROM KEY "
- + "WHERE IDENTITY = ?";
+ + "WHERE ID = ?";
private final Connection connection;
@@ -156,13 +156,13 @@ public class StandardKeyDAO implements KeyDAO {
}
@Override
- public void deleteKeys(String identity) {
+ public Integer deleteKey(Integer keyId) {
PreparedStatement statement = null;
try {
// add each authority for the specified user
statement = connection.prepareStatement(DELETE_KEYS);
- statement.setString(1, identity);
- statement.executeUpdate();
+ statement.setInt(1, keyId);
+ return statement.executeUpdate();
} catch (SQLException sqle) {
throw new DataAccessException(sqle);
} catch (DataAccessException dae) {
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/KeyService.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/KeyService.java
index 4543475..5ac10cb 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/KeyService.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/KeyService.java
@@ -43,7 +43,7 @@ public interface KeyService {
/**
* Deletes keys for the specified identity.
*
- * @param identity The user identity
+ * @param keyId The user's key ID
*/
- void deleteKey(String identity);
+ void deleteKey(Integer keyId);
}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DeleteKeysAction.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DeleteKeyAction.java
similarity index 72%
rename from
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DeleteKeysAction.java
rename to
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DeleteKeyAction.java
index 6b8a2d5..c72d2c3 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DeleteKeysAction.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DeleteKeyAction.java
@@ -23,23 +23,22 @@ import org.apache.nifi.admin.dao.KeyDAO;
/**
*
*/
-public class DeleteKeysAction implements AdministrationAction<Void> {
+public class DeleteKeyAction implements AdministrationAction<Integer> {
- private final String identity;
+ private final Integer keyId;
/**
- * Creates a new transactions for deleting keys for specified user.
+ * Creates a new transactions for deleting keys for a specified user based
on their keyId.
*
- * @param identity user identity
+ * @param keyId user identity
*/
- public DeleteKeysAction(String identity) {
- this.identity = identity;
+ public DeleteKeyAction(Integer keyId) {
+ this.keyId = keyId;
}
@Override
- public Void execute(DAOFactory daoFactory) throws DataAccessException {
+ public Integer execute(DAOFactory daoFactory) throws DataAccessException {
final KeyDAO keyDao = daoFactory.getKeyDAO();
- keyDao.deleteKeys(identity);
- return null;
+ return keyDao.deleteKey(keyId);
}
}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java
index 7a7f62d..8f4198a 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java
@@ -19,7 +19,7 @@ package org.apache.nifi.admin.service.impl;
import org.apache.nifi.admin.dao.DataAccessException;
import org.apache.nifi.admin.service.AdministrationException;
import org.apache.nifi.admin.service.KeyService;
-import org.apache.nifi.admin.service.action.DeleteKeysAction;
+import org.apache.nifi.admin.service.action.DeleteKeyAction;
import org.apache.nifi.admin.service.action.GetKeyByIdAction;
import org.apache.nifi.admin.service.action.GetOrCreateKeyAction;
import org.apache.nifi.admin.service.transaction.Transaction;
@@ -35,7 +35,7 @@ import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
- *
+ * This key service manages user JWT signing keys in the H2 user database.
*/
public class StandardKeyService implements KeyService {
@@ -51,7 +51,7 @@ public class StandardKeyService implements KeyService {
@Override
public Key getKey(int id) {
Transaction transaction = null;
- Key key = null;
+ Key key;
readLock.lock();
try {
@@ -81,7 +81,7 @@ public class StandardKeyService implements KeyService {
@Override
public Key getOrCreateKey(String identity) {
Transaction transaction = null;
- Key key = null;
+ Key key;
writeLock.lock();
try {
@@ -109,7 +109,7 @@ public class StandardKeyService implements KeyService {
}
@Override
- public void deleteKey(String identity) {
+ public void deleteKey(Integer keyId) {
Transaction transaction = null;
writeLock.lock();
@@ -118,11 +118,16 @@ public class StandardKeyService implements KeyService {
transaction = transactionBuilder.start();
// delete the keys
- DeleteKeysAction deleteKeys = new DeleteKeysAction(identity);
- transaction.execute(deleteKeys);
-
- // commit the transaction
- transaction.commit();
+ DeleteKeyAction deleteKey = new DeleteKeyAction(keyId);
+ Integer rowsDeleted = transaction.execute(deleteKey);
+
+ // commit the transaction if we found one and only one matching
keyId/user identity
+ if (rowsDeleted == 1) {
+ transaction.commit();
+ } else {
+ rollback(transaction);
+ throw new AdministrationException("Unable to find user key for
key ID " + keyId + " to remove token.");
+ }
} catch (TransactionException | DataAccessException te) {
rollback(transaction);
throw new AdministrationException(te);
@@ -157,5 +162,4 @@ public class StandardKeyService implements KeyService {
public void setProperties(NiFiProperties properties) {
this.properties = properties;
}
-
}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/key/Key.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/key/Key.java
index 9ce7a9a..ce1c6d5 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/key/Key.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/key/Key.java
@@ -66,4 +66,13 @@ public class Key implements Serializable {
this.key = key;
}
+ public Key(int id, String identity, String key) {
+ this.id = id;
+ this.identity = identity;
+ this.key = key;
+ }
+
+ public Key() {
+ }
+
}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
index 0e3e70f..5ee98cd 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
@@ -60,6 +60,7 @@ import org.apache.nifi.authorization.AccessDeniedException;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserDetails;
import org.apache.nifi.authorization.user.NiFiUserUtils;
+import org.apache.nifi.authorization.util.IdentityMappingUtil;
import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.web.api.dto.AccessConfigurationDTO;
import org.apache.nifi.web.api.dto.AccessStatusDTO;
@@ -659,11 +660,12 @@ public class AccessResource extends ApplicationResource {
final String expirationFromProperties =
properties.getKerberosAuthenticationExpiration();
long expiration =
FormatUtils.getTimeDuration(expirationFromProperties, TimeUnit.MILLISECONDS);
- final String identity = authentication.getName();
- expiration = validateTokenExpiration(expiration, identity);
+ final String rawIdentity = authentication.getName();
+ String mappedIdentity =
IdentityMappingUtil.mapIdentity(rawIdentity,
IdentityMappingUtil.getIdentityMappings(properties));
+ expiration = validateTokenExpiration(expiration,
mappedIdentity);
// create the authentication token
- final LoginAuthenticationToken loginAuthenticationToken = new
LoginAuthenticationToken(identity, expiration, "KerberosService");
+ final LoginAuthenticationToken loginAuthenticationToken = new
LoginAuthenticationToken(mappedIdentity, expiration, "KerberosService");
// generate JWT for response
final String token =
jwtService.generateSignedToken(loginAuthenticationToken);
@@ -729,10 +731,12 @@ public class AccessResource extends ApplicationResource {
try {
// attempt to authenticate
final AuthenticationResponse authenticationResponse =
loginIdentityProvider.authenticate(new LoginCredentials(username, password));
- long expiration =
validateTokenExpiration(authenticationResponse.getExpiration(),
authenticationResponse.getIdentity());
+ final String rawIdentity = authenticationResponse.getIdentity();
+ String mappedIdentity =
IdentityMappingUtil.mapIdentity(rawIdentity,
IdentityMappingUtil.getIdentityMappings(properties));
+ long expiration =
validateTokenExpiration(authenticationResponse.getExpiration(), mappedIdentity);
// create the authentication token
- loginAuthenticationToken = new
LoginAuthenticationToken(authenticationResponse.getIdentity(), expiration,
authenticationResponse.getIssuer());
+ loginAuthenticationToken = new
LoginAuthenticationToken(mappedIdentity, expiration,
authenticationResponse.getIssuer());
} catch (final InvalidLoginCredentialsException ilce) {
throw new IllegalArgumentException("The supplied username and
password are not valid.", ilce);
} catch (final IdentityAccessException iae) {
@@ -769,10 +773,11 @@ public class AccessResource extends ApplicationResource {
String userIdentity = NiFiUserUtils.getNiFiUserIdentity();
- if(userIdentity != null && !userIdentity.isEmpty()) {
+ if (userIdentity != null && !userIdentity.isEmpty()) {
try {
logger.info("Logging out user " + userIdentity);
- jwtService.logOut(userIdentity);
+
jwtService.logOutUsingAuthHeader(httpServletRequest.getHeader(JwtAuthenticationFilter.AUTHORIZATION));
+ logger.info("Successfully logged out user" + userIdentity);
return generateOkResponse().build();
} catch (final JwtException e) {
logger.error("Logout of user " + userIdentity + " failed due
to: " + e.getMessage());
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java
index 9f1ae29..a97a744 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java
@@ -44,13 +44,13 @@ public class ITAccessTokenEndpoint {
private static OneWaySslAccessControlHelper helper;
- private final String user = "unregistered-user@nifi";
+ private final String user = "[email protected]";
private final String password = "password";
private static final String CLIENT_ID = "token-endpoint-id";
@BeforeClass
public static void setup() throws Exception {
- helper = new OneWaySslAccessControlHelper();
+ helper = new
OneWaySslAccessControlHelper("src/test/resources/access-control/nifi-mapped-identities.properties");
}
// -----------
@@ -92,7 +92,7 @@ public class ITAccessTokenEndpoint {
public void testCreateProcessorUsingToken() throws Exception {
String url = helper.getBaseUrl() + "/access/token";
- Response response = helper.getUser().testCreateToken(url, "user@nifi",
"whatever");
+ Response response = helper.getUser().testCreateToken(url, user,
password);
// ensure the request is successful
Assert.assertEquals(201, response.getStatus());
@@ -154,7 +154,7 @@ public class ITAccessTokenEndpoint {
Response response = helper.getUser().testCreateToken(url, "user@nifi",
"not a real password");
- // ensure the request is successful
+ // ensure the request is not successful
Assert.assertEquals(400, response.getStatus());
}
@@ -262,7 +262,6 @@ public class ITAccessTokenEndpoint {
// verify unregistered
Assert.assertEquals("ACTIVE", accessStatus.getStatus());
-
// log out
response = helper.getUser().testDeleteWithHeaders(logoutUrl, headers);
Assert.assertEquals(200, response.getStatus());
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java
index 3d56591..956eeec 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java
@@ -44,7 +44,7 @@ public class NiFiTestAuthorizer implements Authorizer {
public static final String PRIVILEGED_USER_DN = "privileged@nifi";
public static final String EXECUTED_CODE_USER_DN = "executecode@nifi";
- public static final String TOKEN_USER = "user@nifi";
+ public static final String MAPPED_TOKEN_USER = "nifiadmin";
/**
* Creates a new FileAuthorizationProvider.
@@ -83,7 +83,7 @@ public class NiFiTestAuthorizer implements Authorizer {
}
// allow the token user
- if (TOKEN_USER.equals(request.getIdentity())) {
+ if (MAPPED_TOKEN_USER.equals(request.getIdentity())) {
return AuthorizationResult.approved();
}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestLoginIdentityProvider.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestLoginIdentityProvider.java
index 508a0d1..a244c19 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestLoginIdentityProvider.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestLoginIdentityProvider.java
@@ -43,6 +43,7 @@ public class NiFiTestLoginIdentityProvider implements
LoginIdentityProvider {
users = new HashMap<>();
users.put("user@nifi", "whatever");
users.put("unregistered-user@nifi", "password");
+ users.put("[email protected]", "password");
}
private void checkUser(final String user, final String password) {
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java
index 72d59ba..c99895a 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java
@@ -17,6 +17,8 @@
package org.apache.nifi.integration.util;
import org.apache.nifi.web.security.ProxiedEntitiesUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
@@ -25,6 +27,7 @@ import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.Response;
+import java.util.Arrays;
import java.util.Map;
/**
@@ -34,8 +37,11 @@ public class NiFiTestUser {
private final Client client;
private final String proxyDn;
+ private final Logger logger;
public NiFiTestUser(Client client, String proxyDn) {
+ logger = LoggerFactory.getLogger(NiFiTestUser.class);
+
this.client = client;
if (proxyDn != null) {
this.proxyDn = ProxiedEntitiesUtils.formatProxyDn(proxyDn);
@@ -156,6 +162,8 @@ public class NiFiTestUser {
}
}
+ logger.info("POST Request to URL: " + url + " with headers: " +
Arrays.toString(headers.entrySet().toArray()));
+
// perform the request
return resourceBuilder.post(Entity.json(entity));
}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi-mapped-identities.properties
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi-mapped-identities.properties
new file mode 100644
index 0000000..858ea8e
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi-mapped-identities.properties
@@ -0,0 +1,144 @@
+# 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.
+
+
+
+# Core Properties #
+nifi.flow.configuration.file=target/test-classes/access-control/flow.xml.gz
+nifi.flow.configuration.archive.dir=target/archive
+nifi.flowcontroller.autoResumeState=true
+nifi.flowcontroller.graceful.shutdown.period=10 sec
+nifi.flowservice.writedelay.interval=2 sec
+
+# Mapped Identities #
+nifi.security.identity.mapping.pattern.kerb=^(.*?)@(.*?)$
+nifi.security.identity.mapping.value.kerb=$1
+
+nifi.authorizer.configuration.file=target/test-classes/access-control/authorizers.xml
+nifi.login.identity.provider.configuration.file=target/test-classes/access-control/login-identity-providers.xml
+nifi.templates.directory=target/test-classes/access-control/templates
+nifi.ui.banner.text=TEST BANNER
+nifi.ui.autorefresh.interval=30 sec
+nifi.nar.library.directory=target/test-classes/access-control/lib
+nifi.nar.working.directory=target/test-classes/access-control/nar
+
+nifi.state.management.configuration.file=target/test-classes/access-control/state-management.xml
+nifi.state.management.embedded.zookeeper.start=false
+nifi.state.management.embedded.zookeeper.properties=
+nifi.state.management.embedded.zookeeper.max.instances=3
+nifi.state.management.provider.local=local-provider
+nifi.state.management.provider.cluster=
+
+# H2 Settings
+nifi.database.directory=target/test-classes/database_repository
+nifi.h2.url.append=;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE
+
+# FlowFile Repository
+nifi.provenance.repository.implementation=org.apache.nifi.provenance.VolatileProvenanceRepository
+nifi.flowfile.repository.directory=target/test-classes/flowfile_repository
+nifi.flowfile.repository.partitions=256
+nifi.flowfile.repository.checkpoint.interval=2 mins
+nifi.queue.swap.threshold=20000
+nifi.swap.storage.directory=target/test-classes/flowfile_repository/swap
+nifi.swap.in.period=5 sec
+nifi.swap.in.threads=1
+nifi.swap.out.period=5 sec
+nifi.swap.out.threads=4
+
+# Content Repository
+nifi.content.claim.max.appendable.size=10 MB
+nifi.content.claim.max.flow.files=100
+nifi.content.repository.directory.default=target/test-classes/content_repository
+nifi.content.repository.archive.enabled=false
+
+# Provenance Repository Properties
+nifi.provenance.repository.directory.default=./target/provenance_repository
+nifi.provenance.repository.query.threads=2
+nifi.provenance.repository.max.storage.time=24 hours
+nifi.provenance.repository.max.storage.size=1 GB
+nifi.provenance.repository.rollover.time=30 secs
+nifi.provenance.repository.rollover.size=100 MB
+
+# Component Status Repository
+nifi.components.status.repository.implementation=org.apache.nifi.controller.status.history.VolatileComponentStatusRepository
+nifi.components.status.repository.buffer.size=288
+nifi.components.status.snapshot.frequency=10 secs
+
+# Site to Site properties
+#For testing purposes. Default value should actually be empty!
+nifi.remote.input.host=
+nifi.remote.input.socket.port=
+nifi.remote.input.secure=false
+
+# web properties #
+nifi.web.war.directory=target/test-classes/lib
+nifi.web.http.host=
+nifi.web.http.port=
+nifi.web.https.host=
+nifi.web.https.port=8443
+nifi.web.jetty.working.directory=target/test-classes/access-control/jetty
+
+# security properties #
+nifi.sensitive.props.key=REPLACE_ME
+nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
+nifi.sensitive.props.provider=BC
+
+nifi.security.keystore=target/test-classes/access-control/keystore.jks
+nifi.security.keystoreType=JKS
+nifi.security.keystorePasswd=passwordpassword
+nifi.security.keyPasswd=
+nifi.security.truststore=target/test-classes/access-control/truststore.jks
+nifi.security.truststoreType=JKS
+nifi.security.truststorePasswd=passwordpassword
+nifi.security.user.login.identity.provider=test-provider
+nifi.security.user.authorizer=test-provider
+
+# cluster common properties (cluster manager and nodes must have same values) #
+nifi.cluster.protocol.heartbeat.interval=5 sec
+nifi.cluster.protocol.is.secure=false
+nifi.cluster.protocol.socket.timeout=30 sec
+nifi.cluster.protocol.connection.handshake.timeout=45 sec
+# if multicast is used, then nifi.cluster.protocol.multicast.xxx properties
must be configured #
+nifi.cluster.protocol.use.multicast=false
+nifi.cluster.protocol.multicast.address=
+nifi.cluster.protocol.multicast.port=
+nifi.cluster.protocol.multicast.service.broadcast.delay=500 ms
+nifi.cluster.protocol.multicast.service.locator.attempts=3
+nifi.cluster.protocol.multicast.service.locator.attempts.delay=1 sec
+
+# cluster node properties (only configure for cluster nodes) #
+nifi.cluster.is.node=false
+nifi.cluster.node.address=
+nifi.cluster.node.protocol.port=
+nifi.cluster.node.protocol.threads=2
+# if multicast is not used, nifi.cluster.node.unicast.xxx must have same
values as nifi.cluster.manager.xxx #
+nifi.cluster.node.unicast.manager.address=
+nifi.cluster.node.unicast.manager.protocol.port=
+nifi.cluster.node.unicast.manager.authority.provider.port=
+
+# cluster manager properties (only configure for cluster manager) #
+nifi.cluster.is.manager=false
+nifi.cluster.manager.address=
+nifi.cluster.manager.protocol.port=
+nifi.cluster.manager.authority.provider.port=
+nifi.cluster.manager.authority.provider.threads=10
+nifi.cluster.manager.node.firewall.file=
+nifi.cluster.manager.node.event.history.size=10
+nifi.cluster.manager.node.api.connection.timeout=30 sec
+nifi.cluster.manager.node.api.read.timeout=30 sec
+nifi.cluster.manager.node.api.request.threads=10
+nifi.cluster.manager.flow.retrieval.delay=5 sec
+nifi.cluster.manager.protocol.threads=10
+nifi.cluster.manager.safemode.duration=0 sec
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
index 0d21a8a..9a25ff3 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
@@ -63,7 +63,7 @@ public class JwtAuthenticationFilter extends
NiFiAuthenticationFilter {
return matcher.matches();
}
- private String getTokenFromHeader(String authenticationHeader) {
+ public static String getTokenFromHeader(String authenticationHeader) {
Matcher matcher = tokenPattern.matcher(authenticationHeader);
if(matcher.matches()) {
return matcher.group(1);
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
index ecbfc67..9569631 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
@@ -169,16 +169,33 @@ public class JwtService {
}
}
- public void logOut(String userIdentity) {
- if (userIdentity == null || userIdentity.isEmpty()) {
- throw new JwtException("Log out failed: The user identity was not
present in the request token to log out user.");
+ /**
+ * Log out the authenticated user using the 'kid' (Key ID) claim from the
base64 encoded JWT
+ *
+ * @param token a signed, base64 encoded, JSON Web Token in form
HEADER.PAYLOAD.SIGNATURE
+ * @throws JwtException if there is a problem with the token input
+ * @throws Exception if there is an issue logging the user out
+ */
+ public void logOut(String token) {
+ Jws<Claims> claims = parseTokenFromBase64EncodedString(token);
+
+ // Get the key ID from the claims
+ final Integer keyId = claims.getBody().get(KEY_ID_CLAIM,
Integer.class);
+
+ if (keyId == null) {
+ throw new JwtException("The key claim (kid) was not present in the
request token to log out user.");
}
try {
- keyService.deleteKey(userIdentity);
+ keyService.deleteKey(keyId);
} catch (Exception e) {
- logger.error("Unable to log out user: " + userIdentity + ". Failed
to remove their token from database.");
+ logger.error("The key with key ID: " + keyId + " failed to be
removed from the user database.");
throw e;
}
}
+
+ public void logOutUsingAuthHeader(String authorizationHeader) {
+ String base64EncodedToken =
JwtAuthenticationFilter.getTokenFromHeader(authorizationHeader);
+ logOut(base64EncodedToken);
+ }
}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProviderTest.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProviderTest.java
new file mode 100644
index 0000000..8a1dbd4
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProviderTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.jwt;
+
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.user.NiFiUserDetails;
+import org.apache.nifi.properties.StandardNiFiProperties;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.InvalidAuthenticationException;
+import org.apache.nifi.web.security.token.LoginAuthenticationToken;
+import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.util.Properties;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+public class JwtAuthenticationProviderTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private final static int EXPIRATION_MILLIS = 60000;
+ private final static String CLIENT_ADDRESS = "127.0.0.1";
+ private final static String ADMIN_IDENTITY = "nifiadmin";
+ private final static String REALMED_ADMIN_KERBEROS_IDENTITY =
"[email protected]";
+
+ private final static String UNKNOWN_TOKEN = "eyJhbGciOiJIUzI1NiJ9" +
+ ".eyJzdWIiOiJ1bmtub3duX3Rva2VuIiwiaXNzIjoiS2VyYmVyb3NQcm9" +
+ "2aWRlciIsImF1ZCI6IktlcmJlcm9zUHJvdmlkZXIiLCJwcmVmZXJyZWR" +
+ "fdXNlcm5hbWUiOiJ1bmtub3duX3Rva2VuIiwia2lkIjoxLCJleHAiOjE" +
+ "2OTI0NTQ2NjcsImlhdCI6MTU5MjQxMTQ2N30.PpOGx3Ul5ydokOOuzKd" +
+ "aRKv1kxy6Q4jGy7rBPU8PqxY";
+
+ private NiFiProperties properties;
+
+
+ private JwtService jwtService;
+ private JwtAuthenticationProvider jwtAuthenticationProvider;
+
+ @Before
+ public void setUp() throws Exception {
+ TestKeyService keyService = new TestKeyService();
+ jwtService = new JwtService(keyService);
+
+ // Set up Kerberos identity mappings
+ Properties props = new Properties();
+ props.put(properties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX,
"^(.*?)@(.*?)$");
+ props.put(properties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX, "$1");
+ properties = new StandardNiFiProperties(props);
+
+ jwtAuthenticationProvider = new JwtAuthenticationProvider(jwtService,
properties, mock(Authorizer.class));
+ }
+
+ @Test
+ public void testAdminIdentityAndTokenIsValid() throws Exception {
+ // Arrange
+ LoginAuthenticationToken loginAuthenticationToken =
+ new LoginAuthenticationToken(ADMIN_IDENTITY,
+ EXPIRATION_MILLIS,
+ "MockIdentityProvider");
+ String token =
jwtService.generateSignedToken(loginAuthenticationToken);
+ final JwtAuthenticationRequestToken request = new
JwtAuthenticationRequestToken(token, CLIENT_ADDRESS);
+
+ // Act
+ final NiFiAuthenticationToken result = (NiFiAuthenticationToken)
jwtAuthenticationProvider.authenticate(request);
+ final NiFiUserDetails details = (NiFiUserDetails)
result.getPrincipal();
+
+ // Assert
+ assertEquals(ADMIN_IDENTITY, details.getUsername());
+ }
+
+ @Test
+ public void testKerberosRealmedIdentityAndTokenIsValid() throws Exception {
+ // Arrange
+ LoginAuthenticationToken loginAuthenticationToken =
+ new LoginAuthenticationToken(REALMED_ADMIN_KERBEROS_IDENTITY,
+ EXPIRATION_MILLIS,
+ "MockIdentityProvider");
+ String token =
jwtService.generateSignedToken(loginAuthenticationToken);
+ final JwtAuthenticationRequestToken request = new
JwtAuthenticationRequestToken(token, CLIENT_ADDRESS);
+
+ // Act
+ final NiFiAuthenticationToken result = (NiFiAuthenticationToken)
jwtAuthenticationProvider.authenticate(request);
+ final NiFiUserDetails details = (NiFiUserDetails)
result.getPrincipal();
+
+ // Assert
+ // Check we now have the mapped identity
+ assertEquals(ADMIN_IDENTITY, details.getUsername());
+ }
+
+ @Test
+ public void testFailToAuthenticateWithUnknownToken() throws Exception {
+ // Arrange
+ expectedException.expect(InvalidAuthenticationException.class);
+ expectedException.expectMessage("Unable to validate the access
token.");
+
+ // Generate a token with a known token
+ LoginAuthenticationToken loginAuthenticationToken =
+ new LoginAuthenticationToken(ADMIN_IDENTITY,
+ EXPIRATION_MILLIS,
+ "MockIdentityProvider");
+ jwtService.generateSignedToken(loginAuthenticationToken);
+
+ // Act
+ // Try to authenticate with an unknown token
+ final JwtAuthenticationRequestToken request = new
JwtAuthenticationRequestToken(UNKNOWN_TOKEN, CLIENT_ADDRESS);
+ final NiFiAuthenticationToken result = (NiFiAuthenticationToken)
jwtAuthenticationProvider.authenticate(request);
+
+ // Assert
+ // Expect exception
+ }
+
+}
\ No newline at end of file
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
index 0727ccf..70f9a6d 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
@@ -16,6 +16,8 @@
*/
package org.apache.nifi.web.security.jwt;
+import static
org.apache.nifi.util.NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX;
+import static
org.apache.nifi.util.NiFiProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -28,8 +30,11 @@ import io.jsonwebtoken.JwtException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Properties;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
@@ -37,7 +42,10 @@ import org.apache.nifi.admin.service.AdministrationException;
import org.apache.nifi.admin.service.KeyService;
import org.apache.nifi.authorization.user.NiFiUserDetails;
import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.util.IdentityMapping;
+import org.apache.nifi.authorization.util.IdentityMappingUtil;
import org.apache.nifi.key.Key;
+import org.apache.nifi.properties.StandardNiFiProperties;
import org.apache.nifi.web.security.token.LoginAuthenticationToken;
import org.codehaus.jettison.json.JSONObject;
import org.junit.After;
@@ -58,6 +66,9 @@ public class JwtServiceTest {
private static final Logger logger =
LoggerFactory.getLogger(JwtServiceTest.class);
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
/**
* These constant strings were generated using the tool at http://jwt.io
*/
@@ -132,17 +143,30 @@ public class JwtServiceTest {
+ "6MSwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.6kDjDanA"
+ "g0NQDb3C8FmgbBAYDoIfMAEkF4WMVALsbJA";
+ private static final String KERBEROS_PROVIDER_TOKEN =
"eyJhbGciOiJIUzI1NiJ9" +
+
".eyJzdWIiOiJuaWZpYWRtaW5AbmlmaS5hcGFjaGUub3JnIiwiaXNzIjoiS2VyYmVyb" +
+
"3NQcm92aWRlciIsImF1ZCI6IktlcmJlcm9zUHJvdmlkZXIiLCJwcmVmZXJyZWRfdXN" +
+
"lcm5hbWUiOiJuaWZpYWRtaW5AbmlmaS5hcGFjaGUub3JnIiwia2lkIjo2LCJleHAiO" +
+
"jE2OTI0NTQ2NjcsImlhdCI6MTU5MjQxMTQ2N30.Mmnx6ssdjQ5_5VVRiyPWU60Oegc" +
+ "NdhWezaKKNK48Mew";
+
private static final String DEFAULT_HEADER = "{\"alg\":\"HS256\"}";
private static final String DEFAULT_IDENTITY = "alopresto";
+ private static final String REALMED_KERBEROS_IDENTITY =
"[email protected]";
+ private static final String KERBEROS_IDENTITY = "nifiadmin";
private static final String TOKEN_DELIMITER = ".";
private static final String HMAC_SECRET = "test_hmac_shared_secret";
+ private static List<IdentityMapping> identityMappings;
+
private KeyService mockKeyService;
+ private KeyService testKeyService;
// Class under test
private JwtService jwtService;
+ private JwtService jwtServiceUsingTestKeyService;
public static String generateHS256Token(String rawHeader, String
rawPayload, boolean isValid, boolean isSigned) {
return generateHS256Token(rawHeader, rawPayload, HMAC_SECRET, isValid,
isSigned);
@@ -189,7 +213,7 @@ public class JwtServiceTest {
Key answerKey = key;
@Override
public Key answer(InvocationOnMock invocation) throws Throwable {
-
if(invocation.getMethod().equals(KeyService.class.getMethod("deleteKey",
String.class))) {
+
if(invocation.getMethod().equals(KeyService.class.getMethod("deleteKey",
Integer.class))) {
answerKey = null;
}
return answerKey;
@@ -210,8 +234,15 @@ public class JwtServiceTest {
mockKeyService = mock(KeyService.class);
when(mockKeyService.getKey(anyInt())).thenAnswer(keyAnswer);
when(mockKeyService.getOrCreateKey(anyString())).thenReturn(key);
- doAnswer(keyAnswer).when(mockKeyService).deleteKey(anyString());
+ doAnswer(keyAnswer).when(mockKeyService).deleteKey(anyInt());
+
jwtService = new JwtService(mockKeyService);
+ jwtServiceUsingTestKeyService = new JwtService(new TestKeyService());
+
+ Properties props = new Properties();
+ props.setProperty(SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX+"kerb",
"^(.*?)@(.*?)$");
+ props.setProperty(SECURITY_IDENTITY_MAPPING_VALUE_PREFIX+"kerb", "$1");
+ identityMappings = IdentityMappingUtil.getIdentityMappings(new
StandardNiFiProperties(props));
}
@After
@@ -226,12 +257,25 @@ public class JwtServiceTest {
// Act
String identity = jwtService.getAuthenticationFromToken(token);
- logger.debug("Extracted identity: " + identity);
+ logger.info("Extracted identity: " + identity);
// Assert
assertEquals("Identity", DEFAULT_IDENTITY, identity);
}
+ @Test
+ public void testShouldGetAuthenticationForValidKerberosToken() throws
Exception {
+ // Arrange
+ String token = KERBEROS_PROVIDER_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.info("Extracted identity: " + identity);
+
+ // Assert
+ assertEquals("Identity", REALMED_KERBEROS_IDENTITY, identity);
+ }
+
@Test(expected = JwtException.class)
public void testShouldNotGetAuthenticationForInvalidToken() throws
Exception {
// Arrange
@@ -239,7 +283,7 @@ public class JwtServiceTest {
// Act
String identity = jwtService.getAuthenticationFromToken(token);
- logger.debug("Extracted identity: " + identity);
+ logger.info("Extracted identity: " + identity);
// Assert
// Should fail
@@ -252,7 +296,7 @@ public class JwtServiceTest {
// Act
String identity = jwtService.getAuthenticationFromToken(token);
- logger.debug("Extracted identity: " + identity);
+ logger.info("Extracted identity: " + identity);
// Assert
// Should fail
@@ -265,7 +309,7 @@ public class JwtServiceTest {
// Act
String identity = jwtService.getAuthenticationFromToken(token);
- logger.debug("Extracted identity: " + identity);
+ logger.info("Extracted identity: " + identity);
// Assert
// Should fail
@@ -278,7 +322,7 @@ public class JwtServiceTest {
// Act
String identity = jwtService.getAuthenticationFromToken(token);
- logger.debug("Extracted identity: " + identity);
+ logger.info("Extracted identity: " + identity);
// Assert
// Should fail
@@ -291,7 +335,7 @@ public class JwtServiceTest {
// Act
String identity = jwtService.getAuthenticationFromToken(token);
- logger.debug("Extracted identity: " + identity);
+ logger.info("Extracted identity: " + identity);
// Assert
// Should fail
@@ -304,7 +348,7 @@ public class JwtServiceTest {
// Act
String identity = jwtService.getAuthenticationFromToken(token);
- logger.debug("Extracted identity: " + identity);
+ logger.info("Extracted identity: " + identity);
// Assert
// Should fail
@@ -318,7 +362,7 @@ public class JwtServiceTest {
// Act
String identity = jwtService.getAuthenticationFromToken(token);
- logger.debug("Extracted identity: " + identity);
+ logger.info("Extracted identity: " + identity);
// Assert
// Should fail
@@ -331,7 +375,7 @@ public class JwtServiceTest {
// Act
String identity = jwtService.getAuthenticationFromToken(token);
- logger.debug("Extracted identity: " + identity);
+ logger.info("Extracted identity: " + identity);
// Assert
// Should fail
@@ -344,7 +388,7 @@ public class JwtServiceTest {
// Act
String identity = jwtService.getAuthenticationFromToken(token);
- logger.debug("Extracted identity: " + identity);
+ logger.info("Extracted identity: " + identity);
// Assert
// Should fail
@@ -357,7 +401,7 @@ public class JwtServiceTest {
// Act
String identity = jwtService.getAuthenticationFromToken(token);
- logger.debug("Extracted identity: " + identity);
+ logger.info("Extracted identity: " + identity);
// Assert
// Should fail
@@ -372,7 +416,7 @@ public class JwtServiceTest {
LoginAuthenticationToken loginAuthenticationToken = new
LoginAuthenticationToken("alopresto",
EXPIRATION_MILLIS,
"MockIdentityProvider");
- logger.debug("Generating token for " + loginAuthenticationToken);
+ logger.info("Generating token for " + loginAuthenticationToken);
final String EXPECTED_HEADER = DEFAULT_HEADER;
@@ -381,7 +425,7 @@ public class JwtServiceTest {
// Act
String token =
jwtService.generateSignedToken(loginAuthenticationToken);
- logger.debug("Generated JWT: " + token);
+ logger.info("Generated JWT: " + token);
// Run after the SUT generates the token to ensure the same issued at
time
// Split the token, decode the middle section, and form a new String
@@ -390,7 +434,7 @@ public class JwtServiceTest {
DECODED_PAYLOAD.length() - 1));
logger.trace("Actual token was issued at " + ISSUED_AT_SEC);
- // Always use LinkedHashMap to enforce order of the keys because the
signature depends on order
+ // Always use LinkedHashMap to enforce order of the signingKeys
because the signature depends on order
Map<String, Object> claims = new LinkedHashMap<>();
claims.put("sub", "alopresto");
claims.put("iss", "MockIdentityProvider");
@@ -403,7 +447,7 @@ public class JwtServiceTest {
final String EXPECTED_PAYLOAD = new JSONObject(claims).toString();
final String EXPECTED_TOKEN_STRING =
generateHS256Token(EXPECTED_HEADER, EXPECTED_PAYLOAD, true, true);
- logger.debug("Expected JWT: " + EXPECTED_TOKEN_STRING);
+ logger.info("Expected JWT: " + EXPECTED_TOKEN_STRING);
// Assert
assertEquals("JWT token", EXPECTED_TOKEN_STRING, token);
@@ -413,7 +457,7 @@ public class JwtServiceTest {
public void testShouldNotGenerateTokenWithNullAuthenticationToken() throws
Exception {
// Arrange
LoginAuthenticationToken nullLoginAuthenticationToken = null;
- logger.debug("Generating token for " + nullLoginAuthenticationToken);
+ logger.info("Generating token for " + nullLoginAuthenticationToken);
// Act
jwtService.generateSignedToken(nullLoginAuthenticationToken);
@@ -428,7 +472,7 @@ public class JwtServiceTest {
final int EXPIRATION_MILLIS = 60000;
LoginAuthenticationToken emptyIdentityLoginAuthenticationToken = new
LoginAuthenticationToken("",
EXPIRATION_MILLIS, "MockIdentityProvider");
- logger.debug("Generating token for " +
emptyIdentityLoginAuthenticationToken);
+ logger.info("Generating token for " +
emptyIdentityLoginAuthenticationToken);
// Act
jwtService.generateSignedToken(emptyIdentityLoginAuthenticationToken);
@@ -443,7 +487,7 @@ public class JwtServiceTest {
final int EXPIRATION_MILLIS = 60000;
LoginAuthenticationToken nullIdentityLoginAuthenticationToken = new
LoginAuthenticationToken(null,
EXPIRATION_MILLIS, "MockIdentityProvider");
- logger.debug("Generating token for " +
nullIdentityLoginAuthenticationToken);
+ logger.info("Generating token for " +
nullIdentityLoginAuthenticationToken);
// Act
jwtService.generateSignedToken(nullIdentityLoginAuthenticationToken);
@@ -459,7 +503,7 @@ public class JwtServiceTest {
LoginAuthenticationToken loginAuthenticationToken = new
LoginAuthenticationToken(DEFAULT_IDENTITY,
EXPIRATION_MILLIS,
"MockIdentityProvider");
- logger.debug("Generating token for " + loginAuthenticationToken);
+ logger.info("Generating token for " + loginAuthenticationToken);
// Set up the bad key service
KeyService missingKeyService = mock(KeyService.class);
@@ -474,9 +518,6 @@ public class JwtServiceTest {
// Should throw exception
}
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
@Test
public void testShouldLogOutUser() throws Exception {
// Arrange
@@ -488,16 +529,51 @@ public class JwtServiceTest {
LoginAuthenticationToken loginAuthenticationToken = new
LoginAuthenticationToken(DEFAULT_IDENTITY,
EXPIRATION_MILLIS,
"MockIdentityProvider");
- logger.debug("Generating token for " + loginAuthenticationToken);
+ logger.info("Generating token for " + loginAuthenticationToken);
// Act
String token =
jwtService.generateSignedToken(loginAuthenticationToken);
- logger.debug("Generated JWT: " + token);
+ logger.info("Generated JWT: " + token);
+ logger.info("Validating token...");
String authID = jwtService.getAuthenticationFromToken(token);
assertEquals(DEFAULT_IDENTITY, authID);
- logger.debug("Logging out user: " + DEFAULT_IDENTITY);
+ logger.info("Token was valid");
+ logger.info("Logging out user: " + authID);
jwtService.logOut(token);
- logger.debug("Logged out user: " + DEFAULT_IDENTITY);
+ logger.info("Logged out user: " + authID);
+ logger.info("Checking that token is now invalid...");
+ jwtService.getAuthenticationFromToken(token);
+
+ // Assert
+ // Should throw exception when user is not found
+ }
+
+ @Test
+ public void testShouldLogOutUserUsingAuthHeader() throws Exception {
+ // Arrange
+ expectedException.expect(JwtException.class);
+ expectedException.expectMessage("Unable to validate the access
token.");
+
+ // Token expires in 60 seconds
+ final int EXPIRATION_MILLIS = 60000;
+ LoginAuthenticationToken loginAuthenticationToken = new
LoginAuthenticationToken(DEFAULT_IDENTITY,
+ EXPIRATION_MILLIS,
+ "MockIdentityProvider");
+ logger.info("Generating token for " + loginAuthenticationToken);
+
+ // Act
+ String token =
jwtService.generateSignedToken(loginAuthenticationToken);
+
+ logger.info("Generated JWT: " + token);
+ logger.info("Validating token...");
+ String authID = jwtService.getAuthenticationFromToken(token);
+ assertEquals(DEFAULT_IDENTITY, authID);
+ logger.info("Token was valid");
+ logger.info("Logging out user: " + authID);
+ String header = "Bearer " + token;
+ jwtService.logOutUsingAuthHeader(header);
+ logger.info("Logged out user: " + authID);
+ logger.info("Checking that token is now invalid...");
jwtService.getAuthenticationFromToken(token);
// Assert
@@ -508,7 +584,7 @@ public class JwtServiceTest {
public void testLogoutWhenAuthTokenIsEmptyShouldThrowError() throws
Exception {
// Arrange
expectedException.expect(JwtException.class);
- expectedException.expectMessage("Log out failed: The user identity was
not present in the request token to log out user.");
+ expectedException.expectMessage("Unable to validate the access
token.");
// Act
jwtService.logOut(null);
@@ -517,4 +593,72 @@ public class JwtServiceTest {
// Should throw exception when authorization header is null
}
+ @Test
+ public void testShouldLogOutKerberosUser() throws Exception {
+ // Arrange
+
+ expectedException.expect(JwtException.class);
+ expectedException.expectMessage("Unable to validate the access
token.");
+
+ // Token expires in 60 seconds
+ final int EXPIRATION_MILLIS = 60000;
+ LoginAuthenticationToken loginAuthenticationToken = new
LoginAuthenticationToken(KERBEROS_IDENTITY,
+ EXPIRATION_MILLIS,
+ "MockIdentityProvider");
+ logger.info("Generating token for " + loginAuthenticationToken);
+
+ // Act
+ String token =
jwtServiceUsingTestKeyService.generateSignedToken(loginAuthenticationToken);
+ logger.info("Generated JWT: " + token);
+ logger.info("Validating token...");
+ String authID =
jwtServiceUsingTestKeyService.getAuthenticationFromToken(token);
+ logger.info("Token was valid, unmapped user identity was: " + authID);
+ assertEquals(KERBEROS_IDENTITY, authID);
+ logger.info("Using identity mappings " +
Arrays.toString(identityMappings.toArray()) + " to map identity: " + authID);
+ String mappedIdentity = IdentityMappingUtil.mapIdentity(authID,
identityMappings);
+ logger.info("Logging out user with mapped identity: " +
mappedIdentity);
+ jwtServiceUsingTestKeyService.logOut(mappedIdentity);
+ logger.info("Logged out user with mapped identity: " + mappedIdentity);
+ logger.info("Checking that token for " + mappedIdentity + " is now
invalid...");
+ jwtServiceUsingTestKeyService.getAuthenticationFromToken(token);
+
+ // Assert
+ // Should throw exception when user is not found
+ }
+
+ @Test
+ public void testShouldLogOutRealmedKerberosUser() throws Exception {
+ // Arrange
+
+ expectedException.expect(JwtException.class);
+ expectedException.expectMessage("Unable to validate the access
token.");
+
+ // Token expires in 60 seconds
+ final int EXPIRATION_MILLIS = 60000;
+ // map the kerberos identity before we create our token, just as is
done in AccessResource
+ final String mappedIdentity =
IdentityMappingUtil.mapIdentity(REALMED_KERBEROS_IDENTITY, identityMappings);
+
+ LoginAuthenticationToken loginAuthenticationToken = new
LoginAuthenticationToken(mappedIdentity,
+ EXPIRATION_MILLIS,
+ "MockIdentityProvider");
+ logger.info("Generating token for " + loginAuthenticationToken);
+
+ // Act
+ String token =
jwtServiceUsingTestKeyService.generateSignedToken(loginAuthenticationToken);
+ logger.info("Generated JWT: " + token);
+ logger.info("Validating token...");
+ String authID =
jwtServiceUsingTestKeyService.getAuthenticationFromToken(token);
+ logger.info("Token was valid, unmapped user identity was: " + authID);
+ assertEquals(KERBEROS_IDENTITY, authID);
+ logger.info("Using identity mappings " +
Arrays.toString(identityMappings.toArray()) + " to map identity: " + authID);
+ logger.info("Logging out user with mapped identity: " + authID);
+ jwtServiceUsingTestKeyService.logOut(authID);
+ logger.info("Logged out user with mapped identity: " + authID);
+ logger.info("Checking that token for " + authID + " is now
invalid...");
+ jwtServiceUsingTestKeyService.getAuthenticationFromToken(token);
+
+ // Assert
+ // Should throw exception when user is not found
+ }
+
}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/TestKeyService.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/TestKeyService.java
new file mode 100644
index 0000000..4d2f415
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/TestKeyService.java
@@ -0,0 +1,73 @@
+/*
+ * 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.jwt;
+
+import org.apache.nifi.admin.service.KeyService;
+import org.apache.nifi.key.Key;
+import org.h2.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+public class TestKeyService implements KeyService {
+
+ ArrayList<Key> signingKeys = new ArrayList<Key>();
+
+ public TestKeyService() {
+
+ }
+
+ @Override
+ public Key getKey(int id) {
+ Key key = null;
+ for(Key k : signingKeys) {
+ if(k.getId() == id) {
+ key = k;
+ }
+ }
+ return key;
+ }
+
+ @Override
+ public Key getOrCreateKey(String identity) {
+ for(Key k : signingKeys) {
+ if(StringUtils.equals(k.getIdentity(), identity)) {
+ return k;
+ }
+ }
+
+ Key key = generateKey(identity);
+ signingKeys.add(key);
+ return key;
+ }
+
+ @Override
+ public void deleteKey(Integer keyId) {
+ Key keyToRemove = null;
+ for(Key k : signingKeys) {
+ if(k.getId() == keyId) {
+ keyToRemove = k;
+ }
+ }
+ signingKeys.remove(keyToRemove);
+ }
+
+ private Key generateKey(String identity) {
+ Integer keyId = signingKeys.size();
+ return new Key(keyId, identity, UUID.randomUUID().toString());
+ }
+}