You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2015/11/18 20:01:58 UTC
[1/6] nifi git commit: NIFI-655: - Fixed typo in error message for
unrecognized authentication strategy.
Repository: nifi
Updated Branches:
refs/heads/NIFI-655 16608aa85 -> c94d0271d
NIFI-655: - Fixed typo in error message for unrecognized authentication strategy.
Signed-off-by: Matt Gilman <ma...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/45b24a4b
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/45b24a4b
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/45b24a4b
Branch: refs/heads/NIFI-655
Commit: 45b24a4b6015e14525da0feb93d0a3dbc98180f1
Parents: 16608aa
Author: Andy LoPresto <an...@andylopresto.com>
Authored: Tue Nov 17 17:26:39 2015 -0800
Committer: Matt Gilman <ma...@gmail.com>
Committed: Wed Nov 18 08:30:44 2015 -0500
----------------------------------------------------------------------
.../src/main/java/org/apache/nifi/ldap/LdapProvider.java | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/nifi/blob/45b24a4b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java
index 3c26d9f..628fc02 100644
--- a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java
+++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java
@@ -58,6 +58,8 @@ public class LdapProvider extends AbstractLdapProvider {
// connection time out
final String rawConnectTimeout = configurationContext.getProperty("Connect Timeout");
+
+ // TODO: Refactor to utility method to remove duplicate code
if (StringUtils.isNotBlank(rawConnectTimeout)) {
try {
final Long connectTimeout = FormatUtils.getTimeDuration(rawConnectTimeout, TimeUnit.MILLISECONDS);
@@ -89,7 +91,7 @@ public class LdapProvider extends AbstractLdapProvider {
try {
authenticationStrategy = LdapAuthenticationStrategy.valueOf(rawAuthenticationStrategy);
} catch (final IllegalArgumentException iae) {
- throw new ProviderCreationException(String.format("Unrecgonized authentication strategy '%s'. Possible values are [%s]",
+ throw new ProviderCreationException(String.format("Unrecognized authentication strategy '%s'. Possible values are [%s]",
rawAuthenticationStrategy, StringUtils.join(LdapAuthenticationStrategy.values(), ", ")));
}
@@ -142,7 +144,7 @@ public class LdapProvider extends AbstractLdapProvider {
sslContext = SslContextFactory.createSslContext(rawKeystore, rawKeystorePassword.toCharArray(), rawKeystoreType,
rawTruststore, rawTruststorePassword.toCharArray(), rawTruststoreType, clientAuth, TLS);
} catch (final IllegalArgumentException iae) {
- throw new ProviderCreationException(String.format("Unrecgonized client auth '%s'", rawClientAuth));
+ throw new ProviderCreationException(String.format("Unrecognized client auth '%s'", rawClientAuth));
}
}
}
[5/6] nifi git commit: NIFI-655: - Refactoring key service to expose
the key id. - Handling client side expiration better. - Removing specialized
active directory provider and abstract ldap provider.
Posted by mc...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java
index 628fc02..cbd5ea4 100644
--- a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java
+++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java
@@ -27,59 +27,72 @@ import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.authentication.AuthenticationResponse;
+import org.apache.nifi.authentication.LoginCredentials;
+import org.apache.nifi.authentication.LoginIdentityProvider;
import org.apache.nifi.authentication.LoginIdentityProviderConfigurationContext;
+import org.apache.nifi.authentication.LoginIdentityProviderInitializationContext;
+import org.apache.nifi.authentication.exception.IdentityAccessException;
+import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException;
import org.apache.nifi.authorization.exception.ProviderCreationException;
+import org.apache.nifi.authorization.exception.ProviderDestructionException;
import org.apache.nifi.security.util.SslContextFactory;
-import org.apache.nifi.security.util.SslContextFactory.ClientAuth;
import org.apache.nifi.util.FormatUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.ldap.CommunicationException;
import org.springframework.ldap.core.support.AbstractTlsDirContextAuthenticationStrategy;
import org.springframework.ldap.core.support.DefaultTlsDirContextAuthenticationStrategy;
import org.springframework.ldap.core.support.DigestMd5DirContextAuthenticationStrategy;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.core.support.SimpleDirContextAuthenticationStrategy;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.BindAuthenticator;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.search.LdapUserSearch;
+import org.springframework.security.ldap.userdetails.LdapUserDetails;
/**
- * LDAP based implementation of a login identity provider.
+ * Abstract LDAP based implementation of a login identity provider.
*/
-public class LdapProvider extends AbstractLdapProvider {
+public class LdapProvider implements LoginIdentityProvider {
+ private static final Logger logger = LoggerFactory.getLogger(LdapProvider.class);
private static final String TLS = "TLS";
- @Override
- protected AbstractLdapAuthenticationProvider getLdapAuthenticationProvider(LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException {
- final LdapContextSource context = new LdapContextSource();
+ private AbstractLdapAuthenticationProvider provider;
+ private long expiration;
- final Map<String, Object> baseEnvironment = new HashMap<>();
-
- // connection time out
- final String rawConnectTimeout = configurationContext.getProperty("Connect Timeout");
+ @Override
+ public final void initialize(final LoginIdentityProviderInitializationContext initializationContext) throws ProviderCreationException {
+ }
- // TODO: Refactor to utility method to remove duplicate code
- if (StringUtils.isNotBlank(rawConnectTimeout)) {
- try {
- final Long connectTimeout = FormatUtils.getTimeDuration(rawConnectTimeout, TimeUnit.MILLISECONDS);
- baseEnvironment.put("com.sun.jndi.ldap.connect.timeout", connectTimeout.toString());
- } catch (final IllegalArgumentException iae) {
- throw new ProviderCreationException(String.format("The Connect Timeout '%s' is not a valid time duration", rawConnectTimeout));
- }
+ @Override
+ public final void onConfigured(final LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException {
+ final String rawExpiration = configurationContext.getProperty("Expiration Duration");
+ if (StringUtils.isBlank(rawExpiration)) {
+ throw new ProviderCreationException("The Expiration Duration must be specified.");
}
- // read time out
- final String rawReadTimeout = configurationContext.getProperty("Read Timeout");
- if (StringUtils.isNotBlank(rawReadTimeout)) {
- try {
- final Long readTimeout = FormatUtils.getTimeDuration(rawReadTimeout, TimeUnit.MILLISECONDS);
- baseEnvironment.put("com.sun.jndi.ldap.read.timeout", readTimeout.toString());
- } catch (final IllegalArgumentException iae) {
- throw new ProviderCreationException(String.format("The Read Timeout '%s' is not a valid time duration", rawReadTimeout));
- }
+ try {
+ expiration = FormatUtils.getTimeDuration(rawExpiration, TimeUnit.MILLISECONDS);
+ } catch (final IllegalArgumentException iae) {
+ throw new ProviderCreationException(String.format("The Expiration Duration '%s' is not a valid time duration", rawExpiration));
}
+ final LdapContextSource context = new LdapContextSource();
+
+ final Map<String, Object> baseEnvironment = new HashMap<>();
+
+ // connect/read time out
+ setTimeout(configurationContext, baseEnvironment, "Connect Timeout", "com.sun.jndi.ldap.connect.timeout");
+ setTimeout(configurationContext, baseEnvironment, "Read Timeout", "com.sun.jndi.ldap.read.timeout");
+
// set the base environment is necessary
if (!baseEnvironment.isEmpty()) {
context.setBaseEnvironmentProperties(baseEnvironment);
@@ -140,7 +153,7 @@ public class LdapProvider extends AbstractLdapProvider {
sslContext = SslContextFactory.createSslContext(rawKeystore, rawKeystorePassword.toCharArray(), rawKeystoreType, TLS);
} else {
try {
- final ClientAuth clientAuth = ClientAuth.valueOf(rawClientAuth);
+ final SslContextFactory.ClientAuth clientAuth = SslContextFactory.ClientAuth.valueOf(rawClientAuth);
sslContext = SslContextFactory.createSslContext(rawKeystore, rawKeystorePassword.toCharArray(), rawKeystoreType,
rawTruststore, rawTruststorePassword.toCharArray(), rawTruststoreType, clientAuth, TLS);
} catch (final IllegalArgumentException iae) {
@@ -205,7 +218,56 @@ public class LdapProvider extends AbstractLdapProvider {
}
// create the underlying provider
- final LdapAuthenticationProvider ldapAuthenticationProvider = new LdapAuthenticationProvider(authenticator);
- return ldapAuthenticationProvider;
+ provider = new LdapAuthenticationProvider(authenticator);
}
+
+ private void setTimeout(final LoginIdentityProviderConfigurationContext configurationContext,
+ final Map<String, Object> baseEnvironment,
+ final String configurationProperty,
+ final String environmentKey) {
+
+ final String rawTimeout = configurationContext.getProperty(configurationProperty);
+ if (StringUtils.isNotBlank(rawTimeout)) {
+ try {
+ final Long timeout = FormatUtils.getTimeDuration(rawTimeout, TimeUnit.MILLISECONDS);
+ baseEnvironment.put(environmentKey, timeout.toString());
+ } catch (final IllegalArgumentException iae) {
+ throw new ProviderCreationException(String.format("The %s '%s' is not a valid time duration", configurationProperty, rawTimeout));
+ }
+ }
+ }
+
+ @Override
+ public final AuthenticationResponse authenticate(final LoginCredentials credentials) throws InvalidLoginCredentialsException, IdentityAccessException {
+ if (provider == null) {
+ throw new IdentityAccessException("The LDAP authentication provider is not initialized.");
+ }
+
+ try {
+ // perform the authentication
+ final UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());
+ final Authentication authentication = provider.authenticate(token);
+
+ // attempt to get the ldap user details to get the DN
+ if (authentication.getPrincipal() instanceof LdapUserDetails) {
+ final LdapUserDetails userDetails = (LdapUserDetails) authentication.getPrincipal();
+ return new AuthenticationResponse(userDetails.getDn(), credentials.getUsername(), expiration);
+ } else {
+ return new AuthenticationResponse(authentication.getName(), credentials.getUsername(), expiration);
+ }
+ } catch (final CommunicationException | AuthenticationServiceException e) {
+ logger.error(e.getMessage());
+ if (logger.isDebugEnabled()) {
+ logger.debug(StringUtils.EMPTY, e);
+ }
+ throw new IdentityAccessException("Unable to query the configured directory server. See the logs for additional details.", e);
+ } catch (final BadCredentialsException bce) {
+ throw new InvalidLoginCredentialsException(bce.getMessage(), bce);
+ }
+ }
+
+ @Override
+ public final void preDestruction() throws ProviderDestructionException {
+ }
+
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider
index 2158cf7..b5ca1fe 100644
--- a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider
+++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider
@@ -12,5 +12,4 @@
# 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.
-org.apache.nifi.ldap.LdapProvider
-org.apache.nifi.ldap.ActiveDirectoryProvider
\ No newline at end of file
+org.apache.nifi.ldap.LdapProvider
\ No newline at end of file
[6/6] nifi git commit: NIFI-655: - Refactoring key service to expose
the key id. - Handling client side expiration better. - Removing specialized
active directory provider and abstract ldap provider.
Posted by mc...@apache.org.
NIFI-655:
- Refactoring key service to expose the key id.
- Handling client side expiration better.
- Removing specialized active directory provider and abstract ldap provider.
Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/c94d0271
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/c94d0271
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/c94d0271
Branch: refs/heads/NIFI-655
Commit: c94d0271d990df6d6cd9a1dfee7b1706f451483c
Parents: 7d04dfe
Author: Matt Gilman <ma...@gmail.com>
Authored: Wed Nov 18 14:01:45 2015 -0500
Committer: Matt Gilman <ma...@gmail.com>
Committed: Wed Nov 18 14:01:45 2015 -0500
----------------------------------------------------------------------
.../nifi/security/util/CertificateUtils.java | 31 ++---
.../java/org/apache/nifi/admin/dao/KeyDAO.java | 18 ++-
.../nifi/admin/dao/impl/StandardKeyDAO.java | 67 ++++++++--
.../apache/nifi/admin/service/KeyService.java | 8 +-
.../nifi/admin/service/action/GetKeyAction.java | 41 -------
.../admin/service/action/GetKeyByIdAction.java | 42 +++++++
.../service/action/GetKeyByIdentityAction.java | 42 +++++++
.../service/action/GetOrCreateKeyAction.java | 7 +-
.../admin/service/impl/StandardKeyService.java | 19 ++-
.../src/main/java/org/apache/nifi/key/Key.java | 69 +++++++++++
.../org/apache/nifi/web/api/AccessResource.java | 82 ++++++-------
.../InvalidAuthenticationExceptionMapper.java | 44 +++++++
.../src/main/resources/nifi-web-api-context.xml | 1 +
.../InvalidAuthenticationException.java | 35 ++++++
.../web/security/NiFiAuthenticationFilter.java | 5 +-
.../security/jwt/JwtAuthenticationFilter.java | 9 +-
.../nifi/web/security/jwt/JwtService.java | 20 ++-
.../security/x509/X509AuthenticationFilter.java | 4 +-
.../nifi/web/security/jwt/JwtServiceTest.java | 11 +-
.../webapp/js/nf/canvas/nf-canvas-header.js | 2 +-
.../src/main/webapp/js/nf/canvas/nf-canvas.js | 14 +--
.../src/main/webapp/js/nf/login/nf-login.js | 12 +-
.../src/main/webapp/js/nf/nf-common.js | 112 ++++++++++-------
.../src/main/webapp/js/nf/nf-dialog.js | 23 ++--
.../src/main/webapp/js/nf/nf-storage.js | 75 ++++++++----
.../apache/nifi/ldap/AbstractLdapProvider.java | 106 ----------------
.../nifi/ldap/ActiveDirectoryProvider.java | 51 --------
.../java/org/apache/nifi/ldap/LdapProvider.java | 122 ++++++++++++++-----
...he.nifi.authentication.LoginIdentityProvider | 3 +-
29 files changed, 627 insertions(+), 448 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
----------------------------------------------------------------------
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
index 5126933..ea3a6c6 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
+++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
@@ -87,30 +87,21 @@ public final class CertificateUtils {
*/
public static String extractUsername(String dn) {
String username = dn;
- String cn = "";
// ensure the dn is specified
if (StringUtils.isNotBlank(dn)) {
-
- // attempt to locate the cn
- if (dn.startsWith("CN=")) {
- cn = StringUtils.substringBetween(dn, "CN=", ",");
- } else if (dn.startsWith("/CN=")) {
- cn = StringUtils.substringBetween(dn, "CN=", "/");
- } else if (dn.startsWith("C=") || dn.startsWith("/C=")) {
- cn = StringUtils.substringAfter(dn, "CN=");
- } else if (dn.startsWith("/") && StringUtils.contains(dn, "CN=")) {
- cn = StringUtils.substringAfter(dn, "CN=");
- }
-
- // attempt to get the username from the cn
- if (StringUtils.isNotBlank(cn)) {
- if (cn.endsWith(")")) {
- username = StringUtils.substringBetween(cn, "(", ")");
- } else if (cn.contains(" ")) {
- username = StringUtils.substringAfterLast(cn, " ");
+ // determine the separate
+ final String separator = StringUtils.indexOfIgnoreCase(dn, "/cn=") > 0 ? "/" : ",";
+
+ // attempt to locate the cd
+ final String cnPattern = "cn=";
+ final int cnIndex = StringUtils.indexOfIgnoreCase(dn, cnPattern);
+ if (cnIndex >= 0) {
+ int separatorIndex = StringUtils.indexOf(dn, separator, cnIndex);
+ if (separatorIndex > 0) {
+ username = StringUtils.substring(dn, cnIndex + cnPattern.length(), separatorIndex);
} else {
- username = cn;
+ username = StringUtils.substring(dn, cnIndex + cnPattern.length());
}
}
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/KeyDAO.java
----------------------------------------------------------------------
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 0bc6e99..2a24e0b 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
@@ -16,18 +16,28 @@
*/
package org.apache.nifi.admin.dao;
+import org.apache.nifi.key.Key;
+
/**
* Key data access.
*/
public interface KeyDAO {
/**
- * Gets the key for the specified user identity. Returns null if no key exists for the user identity.
+ * Gets the key for the specified user identity. Returns null if no key exists for the key id.
*
- * @param identity The user identity
+ * @param id The key id
+ * @return The key or null
+ */
+ Key findKeyById(int id);
+
+ /**
+ * Gets the latest key for the specified identity. Returns null if no key exists for the user identity.
+ *
+ * @param identity The identity
* @return The key or null
*/
- String getKey(String identity);
+ Key findLatestKeyByIdentity(String identity);
/**
* Creates a key for the specified user identity.
@@ -35,5 +45,5 @@ public interface KeyDAO {
* @param identity The user identity
* @return The key
*/
- String createKey(String identity);
+ Key createKey(String identity);
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardKeyDAO.java
----------------------------------------------------------------------
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 994e95b..f4bdc1d 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
@@ -20,17 +20,23 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
+import java.sql.Statement;
import java.util.UUID;
import org.apache.nifi.admin.RepositoryUtils;
import org.apache.nifi.admin.dao.DataAccessException;
import org.apache.nifi.admin.dao.KeyDAO;
+import org.apache.nifi.key.Key;
/**
*
*/
public class StandardKeyDAO implements KeyDAO {
- private static final String SELECT_KEY_FOR_USER = "SELECT KEY "
+ private static final String SELECT_KEY_FOR_USER_BY_ID = "SELECT ID, IDENTITY, KEY "
+ + "FROM KEY "
+ + "WHERE ID = ?";
+
+ private static final String SELECT_KEY_FOR_USER_BY_IDENTITY = "SELECT ID, IDENTITY, KEY "
+ "FROM KEY "
+ "WHERE IDENTITY = ?";
@@ -47,18 +53,49 @@ public class StandardKeyDAO implements KeyDAO {
}
@Override
- public String getKey(String identity) {
+ public Key findKeyById(int id) {
+ Key key = null;
+
+ PreparedStatement statement = null;
+ ResultSet rs = null;
+ try {
+ // add each authority for the specified user
+ statement = connection.prepareStatement(SELECT_KEY_FOR_USER_BY_ID);
+ statement.setInt(1, id);
+
+ // execute the query
+ rs = statement.executeQuery();
+
+ // if the key was found, add it
+ if (rs.next()) {
+ key = new Key();
+ key.setId(rs.getInt("ID"));
+ key.setIdentity(rs.getString("IDENTITY"));
+ key.setKey(rs.getString("KEY"));
+ }
+ } catch (SQLException sqle) {
+ throw new DataAccessException(sqle);
+ } finally {
+ RepositoryUtils.closeQuietly(rs);
+ RepositoryUtils.closeQuietly(statement);
+ }
+
+ return key;
+ }
+
+ @Override
+ public Key findLatestKeyByIdentity(String identity) {
if (identity == null) {
throw new IllegalArgumentException("Specified identity cannot be null.");
}
- String key = null;
+ Key key = null;
PreparedStatement statement = null;
ResultSet rs = null;
try {
// add each authority for the specified user
- statement = connection.prepareStatement(SELECT_KEY_FOR_USER);
+ statement = connection.prepareStatement(SELECT_KEY_FOR_USER_BY_IDENTITY);
statement.setString(1, identity);
// execute the query
@@ -66,7 +103,10 @@ public class StandardKeyDAO implements KeyDAO {
// if the key was found, add it
if (rs.next()) {
- key = rs.getString("KEY");
+ key = new Key();
+ key.setId(rs.getInt("ID"));
+ key.setIdentity(rs.getString("IDENTITY"));
+ key.setKey(rs.getString("KEY"));
}
} catch (SQLException sqle) {
throw new DataAccessException(sqle);
@@ -79,20 +119,27 @@ public class StandardKeyDAO implements KeyDAO {
}
@Override
- public String createKey(final String identity) {
+ public Key createKey(final String identity) {
PreparedStatement statement = null;
ResultSet rs = null;
try {
- final String key = UUID.randomUUID().toString();
+ final String keyValue = UUID.randomUUID().toString();
// add each authority for the specified user
- statement = connection.prepareStatement(INSERT_KEY);
+ statement = connection.prepareStatement(INSERT_KEY, Statement.RETURN_GENERATED_KEYS);
statement.setString(1, identity);
- statement.setString(2, key);
+ statement.setString(2, keyValue);
// insert the key
int updateCount = statement.executeUpdate();
- if (updateCount == 1) {
+ rs = statement.getGeneratedKeys();
+
+ // verify the results
+ if (updateCount == 1 && rs.next()) {
+ final Key key = new Key();
+ key.setId(rs.getInt(1));
+ key.setIdentity(identity);
+ key.setKey(keyValue);
return key;
} else {
throw new DataAccessException("Unable to add key for user.");
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/KeyService.java
----------------------------------------------------------------------
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 9346625..ae64c41 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
@@ -16,6 +16,8 @@
*/
package org.apache.nifi.admin.service;
+import org.apache.nifi.key.Key;
+
/**
* Supports retrieving and issues keys for signing user tokens.
*/
@@ -24,10 +26,10 @@ public interface KeyService {
/**
* Gets a key for the specified user identity. Returns null if the user has not had a key issued
*
- * @param identity The user identity
+ * @param id The key id
* @return The key or null
*/
- String getKey(String identity);
+ Key getKey(int id);
/**
* Gets a key for the specified user identity. If a key does not exist, one will be created.
@@ -36,5 +38,5 @@ public interface KeyService {
* @return The key
* @throws AdministrationException if it failed to get/create the key
*/
- String getOrCreateKey(String identity);
+ Key getOrCreateKey(String identity);
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyAction.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyAction.java
deleted file mode 100644
index f12b1ef..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyAction.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.admin.service.action;
-
-import org.apache.nifi.admin.dao.DAOFactory;
-import org.apache.nifi.authorization.AuthorityProvider;
-
-import org.apache.nifi.admin.dao.KeyDAO;
-
-/**
- * Gets a key for the specified user identity.
- */
-public class GetKeyAction implements AdministrationAction<String> {
-
- private final String identity;
-
- public GetKeyAction(String identity) {
- this.identity = identity;
- }
-
- @Override
- public String execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) {
- final KeyDAO keyDao = daoFactory.getKeyDAO();
- return keyDao.getKey(identity);
- }
-
-}
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdAction.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdAction.java
new file mode 100644
index 0000000..8763b9d
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdAction.java
@@ -0,0 +1,42 @@
+/*
+ * 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.admin.service.action;
+
+import org.apache.nifi.admin.dao.DAOFactory;
+import org.apache.nifi.authorization.AuthorityProvider;
+
+import org.apache.nifi.admin.dao.KeyDAO;
+import org.apache.nifi.key.Key;
+
+/**
+ * Gets a key for the specified key id.
+ */
+public class GetKeyByIdAction implements AdministrationAction<Key> {
+
+ private final int id;
+
+ public GetKeyByIdAction(int id) {
+ this.id = id;
+ }
+
+ @Override
+ public Key execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) {
+ final KeyDAO keyDao = daoFactory.getKeyDAO();
+ return keyDao.findKeyById(id);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdentityAction.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdentityAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdentityAction.java
new file mode 100644
index 0000000..9bcb0b3
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdentityAction.java
@@ -0,0 +1,42 @@
+/*
+ * 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.admin.service.action;
+
+import org.apache.nifi.admin.dao.DAOFactory;
+import org.apache.nifi.authorization.AuthorityProvider;
+
+import org.apache.nifi.admin.dao.KeyDAO;
+import org.apache.nifi.key.Key;
+
+/**
+ * Gets a key for the specified key id.
+ */
+public class GetKeyByIdentityAction implements AdministrationAction<Key> {
+
+ private final String identity;
+
+ public GetKeyByIdentityAction(String identity) {
+ this.identity = identity;
+ }
+
+ @Override
+ public Key execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) {
+ final KeyDAO keyDao = daoFactory.getKeyDAO();
+ return keyDao.findLatestKeyByIdentity(identity);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetOrCreateKeyAction.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetOrCreateKeyAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetOrCreateKeyAction.java
index 209cbcd..bb85b6f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetOrCreateKeyAction.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetOrCreateKeyAction.java
@@ -20,11 +20,12 @@ import org.apache.nifi.admin.dao.DAOFactory;
import org.apache.nifi.authorization.AuthorityProvider;
import org.apache.nifi.admin.dao.KeyDAO;
+import org.apache.nifi.key.Key;
/**
* Gets a key for the specified user identity.
*/
-public class GetOrCreateKeyAction implements AdministrationAction<String> {
+public class GetOrCreateKeyAction implements AdministrationAction<Key> {
private final String identity;
@@ -33,10 +34,10 @@ public class GetOrCreateKeyAction implements AdministrationAction<String> {
}
@Override
- public String execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) {
+ public Key execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) {
final KeyDAO keyDao = daoFactory.getKeyDAO();
- String key = keyDao.getKey(identity);
+ Key key = keyDao.findLatestKeyByIdentity(identity);
if (key == null) {
key = keyDao.createKey(identity);
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java
----------------------------------------------------------------------
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 7dff9d8..ca0a124 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.GetKeyAction;
+import org.apache.nifi.admin.service.action.GetKeyByIdAction;
import org.apache.nifi.admin.service.action.GetOrCreateKeyAction;
import org.apache.nifi.admin.service.transaction.Transaction;
import org.apache.nifi.admin.service.transaction.TransactionBuilder;
@@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.locks.ReentrantReadWriteLock;
+import org.apache.nifi.key.Key;
/**
*
@@ -44,19 +45,17 @@ public class StandardKeyService implements KeyService {
private TransactionBuilder transactionBuilder;
@Override
- public String getKey(String identity) {
- // TODO: Change this service to look up by "key ID" instead of identity
- // TODO: Change the return type to a Key POJO to support key rotation
+ public Key getKey(int id) {
Transaction transaction = null;
- String key = null;
+ Key key = null;
readLock.lock();
try {
// start the transaction
transaction = transactionBuilder.start();
- // seed the accounts
- GetKeyAction addActions = new GetKeyAction(identity);
+ // get the key
+ GetKeyByIdAction addActions = new GetKeyByIdAction(id);
key = transaction.execute(addActions);
// commit the transaction
@@ -76,11 +75,9 @@ public class StandardKeyService implements KeyService {
}
@Override
- public String getOrCreateKey(String identity) {
- // TODO: Change this service to look up by "key ID" instead of identity
- // TODO: Change the return type to a Key POJO to support key rotation
+ public Key getOrCreateKey(String identity) {
Transaction transaction = null;
- String key = null;
+ Key key = null;
writeLock.lock();
try {
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/key/Key.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..b7158c2
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/key/Key.java
@@ -0,0 +1,69 @@
+/*
+ * 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.key;
+
+import java.io.Serializable;
+
+/**
+ * An signing key for a NiFi user.
+ */
+public class Key implements Serializable {
+
+ private int id;
+ private String identity;
+ private String key;
+
+ /**
+ * The key id.
+ *
+ * @return the id
+ */
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ /**
+ * The identity of the user this key is associated with.
+ *
+ * @return the identity
+ */
+ public String getIdentity() {
+ return identity;
+ }
+
+ public void setIdentity(String identity) {
+ this.identity = identity;
+ }
+
+ /**
+ * The signing key.
+ *
+ * @return the signing key
+ */
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
----------------------------------------------------------------------
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 2e1c44e..27d7d29 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
@@ -55,6 +55,7 @@ import org.apache.nifi.web.api.dto.RevisionDTO;
import org.apache.nifi.web.api.entity.AccessStatusEntity;
import org.apache.nifi.web.api.entity.AccessConfigurationEntity;
import org.apache.nifi.web.api.request.ClientIdParameter;
+import org.apache.nifi.web.security.InvalidAuthenticationException;
import org.apache.nifi.web.security.ProxiedEntitiesUtils;
import org.apache.nifi.web.security.UntrustedProxyException;
import org.apache.nifi.web.security.jwt.JwtService;
@@ -185,55 +186,52 @@ public class AccessResource extends ApplicationResource {
accessStatus.setStatus(AccessStatusDTO.Status.UNKNOWN.name());
accessStatus.setMessage("No credentials supplied, unknown user.");
} else {
- // Extract the Base64 encoded token from the Authorization header
- final String token = StringUtils.substringAfterLast(authorization, " ");
-
try {
+ // Extract the Base64 encoded token from the Authorization header
+ final String token = StringUtils.substringAfterLast(authorization, " ");
final String principal = jwtService.getAuthenticationFromToken(token);
- // ensure we have something we can work with (certificate or credentials)
- if (principal == null) {
- throw new IllegalArgumentException("The specific token is not valid.");
- } else {
- // set the user identity
- accessStatus.setIdentity(principal);
- accessStatus.setUsername(CertificateUtils.extractUsername(principal));
+ // set the user identity
+ accessStatus.setIdentity(principal);
+ accessStatus.setUsername(CertificateUtils.extractUsername(principal));
- // without a certificate, this is not a proxied request
- final List<String> chain = Arrays.asList(principal);
+ // without a certificate, this is not a proxied request
+ final List<String> chain = Arrays.asList(principal);
- // check authorization for this user
- checkAuthorization(chain);
+ // check authorization for this user
+ checkAuthorization(chain);
- // no issues with authorization
- accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
- accessStatus.setMessage("Account is active and authorized");
- }
+ // no issues with authorization
+ accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
+ accessStatus.setMessage("Account is active and authorized");
} catch (JwtException e) {
- // TODO: Handle the exception from a failed JWT verification
- throw new AccessDeniedException("The JWT could not be verified", e);
+ throw new InvalidAuthenticationException(e.getMessage(), e);
}
}
} else {
- final AuthenticationResponse authenticationResponse = certificateIdentityProvider.authenticate(certificates);
-
- // get the proxy chain and ensure its populated
- final List<String> proxyChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(httpServletRequest, authenticationResponse.getIdentity());
- if (proxyChain.isEmpty()) {
- logger.error(String.format("Unable to parse the proxy chain %s from the incoming request.", authenticationResponse.getIdentity()));
- throw new IllegalArgumentException("Unable to determine the user from the incoming request.");
- }
+ try {
+ final AuthenticationResponse authenticationResponse = certificateIdentityProvider.authenticate(certificates);
+
+ // get the proxy chain and ensure its populated
+ final List<String> proxyChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(httpServletRequest, authenticationResponse.getIdentity());
+ if (proxyChain.isEmpty()) {
+ logger.error(String.format("Unable to parse the proxy chain %s from the incoming request.", authenticationResponse.getIdentity()));
+ throw new IllegalArgumentException("Unable to determine the user from the incoming request.");
+ }
- // ensure the proxy chain is authorized
- checkAuthorization(proxyChain);
+ // set the user identity
+ accessStatus.setIdentity(proxyChain.get(0));
+ accessStatus.setUsername(CertificateUtils.extractUsername(proxyChain.get(0)));
- // set the user identity
- accessStatus.setIdentity(proxyChain.get(0));
- accessStatus.setUsername(CertificateUtils.extractUsername(proxyChain.get(0)));
+ // ensure the proxy chain is authorized
+ checkAuthorization(proxyChain);
- // no issues with authorization
- accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
- accessStatus.setMessage("Account is active and authorized");
+ // no issues with authorization
+ accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
+ accessStatus.setMessage("Account is active and authorized");
+ } catch (final IllegalArgumentException iae) {
+ throw new InvalidAuthenticationException(iae.getMessage(), iae);
+ }
}
} catch (final UsernameNotFoundException unfe) {
accessStatus.setStatus(AccessStatusDTO.Status.UNREGISTERED.name());
@@ -323,20 +321,20 @@ public class AccessResource extends ApplicationResource {
final AuthenticationResponse authenticationResponse = loginIdentityProvider.authenticate(new LoginCredentials(username, password));
final long maxExpiration = TimeUnit.MILLISECONDS.convert(12, TimeUnit.HOURS);
final long minExpiration = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
-
+
long expiration = authenticationResponse.getExpiration();
if (expiration > maxExpiration) {
expiration = maxExpiration;
-
- logger.warn(String.format("Max token expiration exceeded. Setting expiration to %s from %s for %s", expiration,
+
+ logger.warn(String.format("Max token expiration exceeded. Setting expiration to %s from %s for %s", expiration,
authenticationResponse.getExpiration(), authenticationResponse.getIdentity()));
} else if (expiration < minExpiration) {
expiration = minExpiration;
-
- logger.warn(String.format("Min token expiration not met. Setting expiration to %s from %s for %s", expiration,
+
+ logger.warn(String.format("Min token expiration not met. Setting expiration to %s from %s for %s", expiration,
authenticationResponse.getExpiration(), authenticationResponse.getIdentity()));
}
-
+
// create the authentication token
// TODO: Some Spring beans return "" for getClass().getSimpleName(). Using getName() temporarily
loginAuthenticationToken = new LoginAuthenticationToken(authenticationResponse.getIdentity(), expiration, loginIdentityProvider.getClass().getName());
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/InvalidAuthenticationExceptionMapper.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/InvalidAuthenticationExceptionMapper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/InvalidAuthenticationExceptionMapper.java
new file mode 100644
index 0000000..14d5139
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/InvalidAuthenticationExceptionMapper.java
@@ -0,0 +1,44 @@
+/*
+ * 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.api.config;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.web.security.InvalidAuthenticationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Maps access denied exceptions into a client response.
+ */
+@Provider
+public class InvalidAuthenticationExceptionMapper implements ExceptionMapper<InvalidAuthenticationException> {
+
+ private static final Logger logger = LoggerFactory.getLogger(InvalidAuthenticationExceptionMapper.class);
+
+ @Override
+ public Response toResponse(InvalidAuthenticationException exception) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(StringUtils.EMPTY, exception);
+ }
+
+ return Response.status(Response.Status.UNAUTHORIZED).entity(exception.getMessage()).type("text/plain").build();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
index 73929d8..9f3d2f5 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
@@ -255,6 +255,7 @@
<!-- exception mapping -->
<bean class="org.apache.nifi.web.api.config.AccessDeniedExceptionMapper" scope="singleton"/>
+ <bean class="org.apache.nifi.web.api.config.InvalidAuthenticationExceptionMapper" scope="singleton"/>
<bean class="org.apache.nifi.web.api.config.AuthenticationCredentialsNotFoundExceptionMapper" scope="singleton"/>
<bean class="org.apache.nifi.web.api.config.AccountNotFoundExceptionMapper" scope="singleton"/>
<bean class="org.apache.nifi.web.api.config.AdministrationExceptionMapper" scope="singleton"/>
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/InvalidAuthenticationException.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/InvalidAuthenticationException.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/InvalidAuthenticationException.java
new file mode 100644
index 0000000..1065152
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/InvalidAuthenticationException.java
@@ -0,0 +1,35 @@
+/*
+ * 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.core.AuthenticationException;
+
+/**
+ * Thrown if the authentication of a given request is invalid. For instance,
+ * an expired certificate or token.
+ */
+public class InvalidAuthenticationException extends AuthenticationException {
+
+ public InvalidAuthenticationException(String msg) {
+ super(msg);
+ }
+
+ public InvalidAuthenticationException(String msg, Throwable t) {
+ super(msg, t);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
index ec34ace..f09d610 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
@@ -36,7 +36,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
-import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -134,8 +133,8 @@ public abstract class NiFiAuthenticationFilter implements Filter {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
out.println("Access is denied.");
}
- } else if (ae instanceof BadCredentialsException) {
- response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ } else if (ae instanceof InvalidAuthenticationException) {
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
out.println(ae.getMessage());
} else if (ae instanceof AccountStatusException) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
----------------------------------------------------------------------
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 dea5bba..20675fb 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
@@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
+import org.apache.nifi.web.security.InvalidAuthenticationException;
/**
*/
@@ -49,7 +50,6 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
// TODO: Refactor request header extraction logic to shared utility as it is duplicated in AccessResource
// get the principal out of the user token
- // look for an authorization token
final String authorization = request.getHeader(AUTHORIZATION);
// if there is no authorization header, we don't know the user
@@ -61,9 +61,6 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
try {
final String jwtPrincipal = jwtService.getAuthenticationFromToken(token);
- if (jwtPrincipal == null) {
- return null;
- }
if (isNewAccountRequest(request)) {
return new NewAccountAuthenticationRequestToken(new NewAccountRequest(Arrays.asList(jwtPrincipal), getJustification(request)));
@@ -71,9 +68,7 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
return new NiFiAuthenticationRequestToken(Arrays.asList(jwtPrincipal));
}
} catch (JwtException e) {
- // TODO: Is this the correct way to handle an unverified token?
- logger.error("Could not verify JWT", e);
- return null;
+ throw new InvalidAuthenticationException(e.getMessage(), e);
}
}
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
----------------------------------------------------------------------
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 f006e5b..9635354 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
@@ -35,6 +35,7 @@ import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
+import org.apache.nifi.key.Key;
/**
*
@@ -88,17 +89,16 @@ public class JwtService {
public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
final String identity = claims.getSubject();
- // TODO: Currently the kid field is identical to identity, but will be a unique key ID when key rotation is implemented
- final String keyId = claims.get(KEY_ID_CLAIM, String.class);
- // The key is unique per identity and should be retrieved from the key service
- final String key = keyService.getKey(keyId);
+ // Get the key based on the key id in the claims
+ final Integer keyId = claims.get(KEY_ID_CLAIM, Integer.class);
+ final Key key = keyService.getKey(keyId);
// Ensure we were able to find a key that was previously issued by this key service for this user
- if (key == null) {
+ if (key == null || key.getKey() == null) {
throw new UnsupportedJwtException("Unable to determine signing key for " + identity + " [kid: " + keyId + "]");
}
- return key.getBytes(StandardCharsets.UTF_8);
+ return key.getKey().getBytes(StandardCharsets.UTF_8);
}
}).parseClaimsJws(base64EncodedToken);
} catch (final MalformedJwtException | UnsupportedJwtException | SignatureException | ExpiredJwtException | IllegalArgumentException | AdministrationException e) {
@@ -137,21 +137,19 @@ public class JwtService {
try {
// Get/create the key for this user
- final String key = keyService.getOrCreateKey(identity);
- final byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
+ final Key key = keyService.getOrCreateKey(identity);
+ final byte[] keyBytes = key.getKey().getBytes(StandardCharsets.UTF_8);
logger.trace("Generating JWT for " + authenticationToken);
// TODO: Implement "jti" claim with nonce to prevent replay attacks and allow blacklisting of revoked tokens
- // TODO: Change kid field to key ID when KeyService is refactored
-
// Build the token
return Jwts.builder().setSubject(identity)
.setIssuer(authenticationToken.getIssuer())
.setAudience(authenticationToken.getIssuer())
.claim(USERNAME_CLAIM, username)
- .claim(KEY_ID_CLAIM, identity)
+ .claim(KEY_ID_CLAIM, key.getId())
.setExpiration(expiration.getTime())
.setIssuedAt(Calendar.getInstance().getTime())
.signWith(SIGNATURE_ALGORITHM, keyBytes).compact();
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java
index e626f74..dd7d47e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java
@@ -21,6 +21,7 @@ import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.nifi.authentication.AuthenticationResponse;
+import org.apache.nifi.web.security.InvalidAuthenticationException;
import org.apache.nifi.web.security.NiFiAuthenticationFilter;
import org.apache.nifi.web.security.ProxiedEntitiesUtils;
import org.apache.nifi.web.security.token.NewAccountAuthenticationRequestToken;
@@ -28,7 +29,6 @@ import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken;
import org.apache.nifi.web.security.user.NewAccountRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.security.authentication.BadCredentialsException;
/**
* Custom X509 filter that will inspect the HTTP headers for a proxied user before extracting the user details from the client certificate.
@@ -58,7 +58,7 @@ public class X509AuthenticationFilter extends NiFiAuthenticationFilter {
try {
authenticationResponse = certificateIdentityProvider.authenticate(certificates);
} catch (final IllegalArgumentException iae) {
- throw new BadCredentialsException(iae.getMessage(), iae);
+ throw new InvalidAuthenticationException(iae.getMessage(), iae);
}
final List<String> proxyChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(request, authenticationResponse.getIdentity());
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
----------------------------------------------------------------------
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 c9107b9..ea3e122 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
@@ -22,10 +22,12 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.LinkedHashMap;
import java.util.Map;
+import org.apache.nifi.key.Key;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.when;
/**
@@ -117,9 +119,14 @@ public class JwtServiceTest {
@Before
public void setUp() throws Exception {
+ final Key key = new Key();
+ key.setId(0);
+ key.setIdentity(HMAC_SECRET);
+ key.setKey(HMAC_SECRET);
+
mockKeyService = Mockito.mock(KeyService.class);
- when(mockKeyService.getKey(anyString())).thenReturn(HMAC_SECRET);
- when(mockKeyService.getOrCreateKey(anyString())).thenReturn(HMAC_SECRET);
+ when(mockKeyService.getKey(anyInt())).thenReturn(key);
+ when(mockKeyService.getOrCreateKey(anyString())).thenReturn(key);
jwtService = new JwtService(mockKeyService);
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
index eab5297..7d63534 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
@@ -142,7 +142,7 @@ nf.CanvasHeader = (function () {
});
// show the login link if supported and user is currently anonymous
- var isAnonymous = $('#current-user').text() === nf.Canvas.ANONYMOUS_USER_TEXT;
+ var isAnonymous = $('#current-user').text() === nf.Common.ANONYMOUS_USER_TEXT;
if (supportsLogin === true && isAnonymous) {
// login link
$('#login-link').click(function () {
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
index 09a8212..c9498fe 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
@@ -936,7 +936,6 @@ nf.Canvas = (function () {
};
return {
- ANONYMOUS_USER_TEXT: 'Anonymous user',
CANVAS_OFFSET: 0,
/**
* Determines if the current broswer supports SVG.
@@ -1056,17 +1055,8 @@ nf.Canvas = (function () {
$('#logout-link-container').show();
}
} else {
- // alert user's of anonymous access
- $('#anonymous-user-alert').show().qtip($.extend({}, nf.Common.config.tooltipConfig, {
- content: 'You are accessing with limited authority. Log in or request an account to access with additional authority granted to you by an administrator.',
- position: {
- my: 'top right',
- at: 'bottom left'
- }
- }));
-
- // render the anonymous user text
- $('#current-user').text(nf.Canvas.ANONYMOUS_USER_TEXT).show();
+ // set the anonymous user label
+ nf.Common.setAnonymousUserLabel();
}
deferred.resolve();
}).fail(function (xhr, status, error) {
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
index 89db6ce..bd39a44 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
@@ -96,8 +96,10 @@ nf.Login = (function () {
'password': $('#password').val()
}
}).done(function (jwt) {
- // store the jwt and reload the page
- nf.Storage.setItem('jwt', jwt, nf.Common.getJwtExpiration(jwt));
+ // get the payload and store the token with the appropirate expiration
+ var token = nf.Common.getJwtPayload(jwt);
+ var expiration = parseInt(token['exp'], 10) * nf.Common.MILLIS_PER_SECOND;
+ nf.Storage.setItem('jwt', jwt, expiration);
// check to see if they actually have access now
$.ajax({
@@ -112,8 +114,7 @@ nf.Login = (function () {
nf.Common.scheduleTokenRefresh();
// show the user
- var user = nf.Common.getJwtSubject(jwt);
- $('#nifi-user-submit-justification').text(user);
+ $('#nifi-user-submit-justification').text(token['preferred_username']);
// show the registration form
initializeNiFiRegistration();
@@ -133,8 +134,7 @@ nf.Login = (function () {
nf.Common.scheduleTokenRefresh();
// show the user
- var user = nf.Common.getJwtSubject(jwt);
- $('#nifi-user-submit-justification').text(user);
+ $('#nifi-user-submit-justification').text(token['preferred_username']);
if (xhr.status === 401) {
initializeNiFiRegistration();
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
index 3be6b91..9202819 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
@@ -54,9 +54,17 @@ $(document).ready(function () {
// include jwt when possible
$.ajaxSetup({
'beforeSend': function(xhr) {
+ var hadToken = nf.Storage.hasItem('jwt');
+
+ // get the token to include in all requests
var token = nf.Storage.getItem('jwt');
- if (token) {
+ if (token !== null) {
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
+ } else {
+ // if the current user was logged in with a token and the token just expired, reload
+ if (hadToken === true) {
+ return false;
+ }
}
}
});
@@ -83,6 +91,8 @@ nf.Common = (function () {
var tokenRefreshInterval = null;
return {
+ ANONYMOUS_USER_TEXT: 'Anonymous user',
+
config: {
sensitiveText: 'Sensitive value set',
tooltipConfig: {
@@ -100,9 +110,6 @@ nf.Common = (function () {
at: 'top right',
my: 'bottom left'
}
- },
- urls: {
- token: '../nifi-api/access/token'
}
},
@@ -148,7 +155,7 @@ nf.Common = (function () {
}
// set the interval to one hour
- var interval = nf.Common.MILLIS_PER_HOUR;
+ var interval = 10 * nf.Common.MILLIS_PER_MINUTE;
var checkExpiration = function () {
var expiration = nf.Storage.getItemExpiration('jwt');
@@ -161,13 +168,16 @@ nf.Common = (function () {
// get the time remainging plus a little bonus time to reload the token
var timeRemaining = expirationDate.valueOf() - now.valueOf() - nf.Common.MILLIS_PER_MINUTE;
if (timeRemaining < interval) {
- // if the token will expire before the next interval minus some bonus time, refresh now
- $.ajax({
- type: 'POST',
- url: nf.Common.config.urls.token
- }).done(function (jwt) {
- nf.Storage.setItem('jwt', jwt, nf.Common.getJwtExpiration(jwt));
- });
+ if ($('#current-user').text() !== nf.Common.ANONYMOUS_USER_TEXT && !$('#anonymous-user-alert').is(':visible')) {
+ // if the token will expire before the next interval minus some bonus time, notify the user to re-login
+ $('#anonymous-user-alert').show().qtip($.extend({}, nf.Common.config.tooltipConfig, {
+ content: 'Your session will expire soon. Please log in again to avoid being automatically logged out.',
+ position: {
+ my: 'top right',
+ at: 'bottom left'
+ }
+ }));
+ }
}
}
};
@@ -180,56 +190,46 @@ nf.Common = (function () {
},
/**
- * Extracts the subject from the specified jwt. If the jwt is not as expected
- * an empty string is returned.
- *
- * @param {string} jwt
- * @returns {string}
+ * Sets the anonymous user label.
*/
- getJwtSubject: function (jwt) {
- if (nf.Common.isDefinedAndNotNull(jwt)) {
- var segments = jwt.split(/\./);
- if (segments.length !== 3) {
- return '';
- }
-
- var rawPayload = $.base64.atob(segments[1]);
- var payload = JSON.parse(rawPayload);
-
- if (nf.Common.isDefinedAndNotNull(payload['preferred_username'])) {
- return payload['preferred_username'];
- } else {
- '';
- }
+ setAnonymousUserLabel: function () {
+ var anonymousUserAlert = $('#anonymous-user-alert');
+ if (anonymousUserAlert.data('qtip')) {
+ anonymousUserAlert.qtip('api').destroy(true);
}
+
+ // alert user's of anonymous access
+ anonymousUserAlert.show().qtip($.extend({}, nf.Common.config.tooltipConfig, {
+ content: 'You are accessing with limited authority. Log in or request an account to access with additional authority granted to you by an administrator.',
+ position: {
+ my: 'top right',
+ at: 'bottom left'
+ }
+ }));
- return '';
+ // render the anonymous user text
+ $('#current-user').text(nf.Common.ANONYMOUS_USER_TEXT).show();
},
/**
- * Extracts the expiration from the specified jwt. If the jwt is not as expected
- * a null value is returned.
+ * Extracts the subject from the specified jwt. If the jwt is not as expected
+ * an empty string is returned.
*
* @param {string} jwt
- * @returns {integer}
+ * @returns {string}
*/
- getJwtExpiration: function (jwt) {
+ getJwtPayload: function (jwt) {
if (nf.Common.isDefinedAndNotNull(jwt)) {
var segments = jwt.split(/\./);
if (segments.length !== 3) {
- return null;
+ return '';
}
var rawPayload = $.base64.atob(segments[1]);
var payload = JSON.parse(rawPayload);
- if (nf.Common.isDefinedAndNotNull(payload['exp'])) {
- try {
- // jwt exp is in seconds
- return parseInt(payload['exp'], 10) * nf.Common.MILLIS_PER_SECOND;
- } catch (e) {
- return null;
- }
+ if (nf.Common.isDefinedAndNotNull(payload)) {
+ return payload;
} else {
return null;
}
@@ -313,6 +313,28 @@ nf.Common = (function () {
* @argument {string} error The error
*/
handleAjaxError: function (xhr, status, error) {
+ if (status === 'canceled') {
+ if ($('#splash').is(':visible')) {
+ $('#message-title').text('Session Expired');
+ $('#message-content').text('Your session has expired. Please reload to log in again.');
+
+ // show the error pane
+ $('#message-pane').show();
+
+ // close the canvas
+ nf.Common.closeCanvas();
+ } else {
+ nf.Dialog.showOkDialog({
+ dialogContent: 'Your session has expired. Please press Ok to log in again.',
+ overlayBackground: false,
+ okHandler: function () {
+ window.location = '/nifi';
+ }
+ });
+ }
+ return;
+ }
+
// if an error occurs while the splash screen is visible close the canvas show the error message
if ($('#splash').is(':visible')) {
if (xhr.status === 401) {
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js
index 2d1183f..d305e4d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js
@@ -23,15 +23,6 @@ $(document).ready(function () {
// configure the ok dialog
$('#nf-ok-dialog').modal({
- buttons: [{
- buttonText: 'Ok',
- handler: {
- click: function () {
- // close the dialog
- $('#nf-ok-dialog').modal('hide');
- }
- }
- }],
handler: {
close: function () {
// clear the content
@@ -77,6 +68,20 @@ nf.Dialog = (function () {
var content = $('<p></p>').append(options.dialogContent);
$('#nf-ok-dialog-content').append(content);
+ // update the button model
+ $('#nf-ok-dialog').modal('setButtonModel', [{
+ buttonText: 'Ok',
+ handler: {
+ click: function () {
+ // close the dialog
+ $('#nf-ok-dialog').modal('hide');
+ if (typeof options.okHandler === 'function') {
+ options.okHandler.call(this);
+ }
+ }
+ }
+ }]);
+
// show the dialog
$('#nf-ok-dialog').modal('setHeaderText', options.headerText).modal('setOverlayBackground', options.overlayBackground).modal('show');
},
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js
index cdb2e9e..7b0c3f6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js
@@ -42,32 +42,18 @@ nf.Storage = (function () {
};
/**
- * If the item at key is not expired, the value of field is returned. Otherwise, null.
+ * Gets an enty for the key. The entry expiration is not checked.
*
* @param {string} key
- * @param {string} field
- * @return {object} the value
*/
- var getEntryField = function (key, field) {
+ var getEntry = function (key) {
try {
// parse the entry
var entry = JSON.parse(localStorage.getItem(key));
// ensure the entry and item are present
if (nf.Common.isDefinedAndNotNull(entry)) {
-
- // if the entry is expired, drop it and return null
- if (checkExpiration(entry)) {
- nf.Storage.removeItem(key);
- return null;
- }
-
- // if the entry has the specified field return its value
- if (nf.Common.isDefinedAndNotNull(entry[field])) {
- return entry[field];
- } else {
- return null;
- }
+ return entry;
} else {
return null;
}
@@ -75,7 +61,7 @@ nf.Storage = (function () {
return null;
}
};
-
+
return {
/**
* Initializes the storage. Items will be persisted for two days. Once the scripts runs
@@ -86,11 +72,9 @@ nf.Storage = (function () {
try {
// get the next item
var key = localStorage.key(i);
- var entry = JSON.parse(localStorage.getItem(key));
-
- if (checkExpiration(entry)) {
- nf.Storage.removeItem(key);
- }
+
+ // attempt to get the item which will expire if necessary
+ nf.Storage.getItem(key);
} catch (e) {
}
}
@@ -118,6 +102,17 @@ nf.Storage = (function () {
},
/**
+ * Returns whether there is an entry for this key. This will not check the expiration. If
+ * the entry is expired, it will return null on a subsequent getItem invocation.
+ *
+ * @param {string} key
+ * @returns {boolean}
+ */
+ hasItem: function (key) {
+ return getEntry(key) !== null;
+ },
+
+ /**
* Gets the item with the specified key. If an item with this key does
* not exist, null is returned. If an item exists but cannot be parsed
* or is malformed/unrecognized, null is returned.
@@ -125,18 +120,44 @@ nf.Storage = (function () {
* @param {type} key
*/
getItem: function (key) {
- return getEntryField(key, 'item');
+ var entry = getEntry(key);
+ if (entry === null) {
+ return null;
+ }
+
+ // if the entry is expired, drop it and return null
+ if (checkExpiration(entry)) {
+ nf.Storage.removeItem(key);
+ return null;
+ }
+
+ // if the entry has the specified field return its value
+ if (nf.Common.isDefinedAndNotNull(entry['item'])) {
+ return entry['item'];
+ } else {
+ return null;
+ }
},
/**
- * Gets the expiration for the specified item. If the item does not exists our could
- * not be parsed, returns null.
+ * Gets the expiration for the specified item. This will not check the expiration. If
+ * the entry is expired, it will return null on a subsequent getItem invocation.
*
* @param {string} key
* @returns {integer}
*/
getItemExpiration: function (key) {
- return getEntryField(key, 'expires');
+ var entry = getEntry(key);
+ if (entry === null) {
+ return null;
+ }
+
+ // if the entry has the specified field return its value
+ if (nf.Common.isDefinedAndNotNull(entry['expires'])) {
+ return entry['expires'];
+ } else {
+ return null;
+ }
},
/**
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/AbstractLdapProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/AbstractLdapProvider.java b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/AbstractLdapProvider.java
deleted file mode 100644
index 64c48bf..0000000
--- a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/AbstractLdapProvider.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.ldap;
-
-import java.util.concurrent.TimeUnit;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.authentication.AuthenticationResponse;
-import org.apache.nifi.authentication.LoginCredentials;
-import org.apache.nifi.authentication.LoginIdentityProvider;
-import org.apache.nifi.authentication.LoginIdentityProviderConfigurationContext;
-import org.apache.nifi.authentication.LoginIdentityProviderInitializationContext;
-import org.apache.nifi.authentication.exception.IdentityAccessException;
-import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException;
-import org.apache.nifi.authorization.exception.ProviderCreationException;
-import org.apache.nifi.authorization.exception.ProviderDestructionException;
-import org.apache.nifi.util.FormatUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.ldap.CommunicationException;
-import org.springframework.security.authentication.AuthenticationServiceException;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
-import org.springframework.security.ldap.userdetails.LdapUserDetails;
-
-/**
- * Abstract LDAP based implementation of a login identity provider.
- */
-public abstract class AbstractLdapProvider implements LoginIdentityProvider {
-
- private static final Logger logger = LoggerFactory.getLogger(AbstractLdapProvider.class);
-
- private AbstractLdapAuthenticationProvider provider;
- private long expiration;
-
- @Override
- public final void initialize(final LoginIdentityProviderInitializationContext initializationContext) throws ProviderCreationException {
- }
-
- @Override
- public final void onConfigured(final LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException {
- final String rawExpiration = configurationContext.getProperty("Expiration Duration");
- if (StringUtils.isBlank(rawExpiration)) {
- throw new ProviderCreationException("The Expiration Duration must be specified.");
- }
-
- try {
- expiration = FormatUtils.getTimeDuration(rawExpiration, TimeUnit.MILLISECONDS);
- } catch (final IllegalArgumentException iae) {
- throw new ProviderCreationException(String.format("The Expiration Duration '%s' is not a valid time duration", rawExpiration));
- }
-
- provider = getLdapAuthenticationProvider(configurationContext);
- }
-
- protected abstract AbstractLdapAuthenticationProvider getLdapAuthenticationProvider(LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException;
-
- @Override
- public final AuthenticationResponse authenticate(final LoginCredentials credentials) throws InvalidLoginCredentialsException, IdentityAccessException {
- if (provider == null) {
- throw new IdentityAccessException("The LDAP authentication provider is not initialized.");
- }
-
- try {
- // perform the authentication
- final UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());
- final Authentication authentication = provider.authenticate(token);
-
- // attempt to get the ldap user details to get the DN
- if (authentication.getPrincipal() instanceof LdapUserDetails) {
- final LdapUserDetails userDetails = (LdapUserDetails) authentication.getPrincipal();
- return new AuthenticationResponse(userDetails.getDn(), credentials.getUsername(), expiration);
- } else {
- return new AuthenticationResponse(authentication.getName(), credentials.getUsername(), expiration);
- }
- } catch (final CommunicationException | AuthenticationServiceException e) {
- logger.error(e.getMessage());
- if (logger.isDebugEnabled()) {
- logger.debug(StringUtils.EMPTY, e);
- }
- throw new IdentityAccessException("Unable to query the configured directory server. See the logs for additional details.", e);
- } catch (final BadCredentialsException bce) {
- throw new InvalidLoginCredentialsException(bce.getMessage(), bce);
- }
- }
-
- @Override
- public final void preDestruction() throws ProviderDestructionException {
- }
-
-}
http://git-wip-us.apache.org/repos/asf/nifi/blob/c94d0271/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ActiveDirectoryProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ActiveDirectoryProvider.java b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ActiveDirectoryProvider.java
deleted file mode 100644
index e43c1ee..0000000
--- a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ActiveDirectoryProvider.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.ldap;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.authentication.LoginIdentityProviderConfigurationContext;
-import org.apache.nifi.authorization.exception.ProviderCreationException;
-import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
-import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
-
-/**
- * Active Directory based implementation of a login identity provider.
- */
-public class ActiveDirectoryProvider extends AbstractLdapProvider {
-
- @Override
- protected AbstractLdapAuthenticationProvider getLdapAuthenticationProvider(LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException {
-
- final String url = configurationContext.getProperty("Url");
- if (StringUtils.isBlank(url)) {
- throw new ProviderCreationException("The Active Directory 'Url' must be specified.");
- }
-
- final String domain = configurationContext.getProperty("Domain");
- final String userSearchBase = configurationContext.getProperty("User Search Base");
-
- final ActiveDirectoryLdapAuthenticationProvider activeDirectoryAuthenticationProvider
- = new ActiveDirectoryLdapAuthenticationProvider(StringUtils.isBlank(domain) ? null : domain, url, StringUtils.isBlank(userSearchBase) ? null : userSearchBase);
-
- final String userSearchFilter = configurationContext.getProperty("User Search Filter");
- if (StringUtils.isNotBlank(userSearchFilter)) {
- activeDirectoryAuthenticationProvider.setSearchFilter(userSearchFilter);
- }
-
- return activeDirectoryAuthenticationProvider;
- }
-}
[3/6] nifi git commit: NIFI-655. - Added issuer field to
LoginAuthenticationToken. - Updated AccessResource to pass identity provider
class name when creating LoginAuthenticationTokens. - Began refactoring JWT
logic from request parsing logic in JwtServi
Posted by mc...@apache.org.
NIFI-655. - Added issuer field to LoginAuthenticationToken. - Updated AccessResource to pass identity provider class name when creating LoginAuthenticationTokens. - Began refactoring JWT logic from request parsing logic in JwtService. - Added unit tests for JWT logic.
Signed-off-by: Matt Gilman <ma...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/3bc11e13
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/3bc11e13
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/3bc11e13
Branch: refs/heads/NIFI-655
Commit: 3bc11e13d7f7dd4242881cde03c9b2ff6fc7da02
Parents: caeede5
Author: Andy LoPresto <an...@andylopresto.com>
Authored: Tue Nov 17 18:22:39 2015 -0800
Committer: Matt Gilman <ma...@gmail.com>
Committed: Wed Nov 18 08:31:23 2015 -0500
----------------------------------------------------------------------
.../org/apache/nifi/web/api/AccessResource.java | 6 +-
.../nifi/web/security/jwt/JwtService.java | 100 ++++++--
.../token/LoginAuthenticationToken.java | 64 ++++-
.../nifi/web/security/jwt/JwtServiceTest.java | 254 +++++++++++++++++++
4 files changed, 395 insertions(+), 29 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/nifi/blob/3bc11e13/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
----------------------------------------------------------------------
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 8f4cd5a..b4778ad 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
@@ -191,7 +191,7 @@ public class AccessResource extends ApplicationResource {
final String principal = jwtService.getAuthentication(httpServletRequest);
// TODO - catch jwt exception?
- // ensure we have something we can work with (certificate or crendentials)
+ // ensure we have something we can work with (certificate or credentials)
if (principal == null) {
throw new IllegalArgumentException("The specific token is not valid.");
} else {
@@ -334,7 +334,7 @@ public class AccessResource extends ApplicationResource {
}
// create the authentication token
- loginAuthenticationToken = new LoginAuthenticationToken(authenticationResponse.getIdentity(), expiration);
+ loginAuthenticationToken = new LoginAuthenticationToken(authenticationResponse.getIdentity(), expiration, loginIdentityProvider.getClass().getSimpleName());
} catch (final InvalidLoginCredentialsException ilce) {
throw new IllegalArgumentException("The supplied username and password are not valid.", ilce);
} catch (final IdentityAccessException iae) {
@@ -355,7 +355,7 @@ public class AccessResource extends ApplicationResource {
authorizeProxyIfNecessary(proxyChain);
// create the authentication token
- loginAuthenticationToken = new LoginAuthenticationToken(proxyChain.get(0), authenticationResponse.getExpiration());
+ loginAuthenticationToken = new LoginAuthenticationToken(proxyChain.get(0), authenticationResponse.getExpiration(), certificateIdentityProvider.getClass().getSimpleName());
}
// generate JWT for response
http://git-wip-us.apache.org/repos/asf/nifi/blob/3bc11e13/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
----------------------------------------------------------------------
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 25e52d6..acbbcfe 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
@@ -20,25 +20,30 @@ import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwsHeader;
+import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.SigningKeyResolverAdapter;
import io.jsonwebtoken.UnsupportedJwtException;
-import java.nio.charset.StandardCharsets;
-import java.util.Calendar;
-import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.admin.service.AdministrationException;
import org.apache.nifi.admin.service.KeyService;
import org.apache.nifi.web.security.token.LoginAuthenticationToken;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import java.nio.charset.StandardCharsets;
+import java.util.Calendar;
/**
*
*/
public class JwtService {
+ private static final org.slf4j.Logger logger = LoggerFactory.getLogger(JwtService.class);
+ private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
private final static String AUTHORIZATION = "Authorization";
private final KeyService keyService;
@@ -54,51 +59,104 @@ public class JwtService {
* @return The user identifier from the token
*/
public String getAuthentication(final HttpServletRequest request) {
+ // TODO: Refactor request token extraction out of this service
// extract/verify token from incoming request
final String authorization = request.getHeader(AUTHORIZATION);
- final String token = StringUtils.substringAfterLast(authorization, " ");
+ final String base64EncodedToken = StringUtils.substringAfterLast(authorization, " ");
+
+ return getAuthenticationFromToken(base64EncodedToken);
+ }
+
+ public String getAuthenticationFromToken(final String base64EncodedToken) {
+ // The library representations of the JWT should be kept internal to this service.
+ try {
+ final Jws<Claims> jws = parseTokenFromBase64EncodedString(base64EncodedToken);
+ return jws.getBody().getSubject();
+ } catch (JwtException e) {
+ logger.debug("The Base64 encoded JWT: " + base64EncodedToken);
+ final String errorMessage = "There was an error parsing the Base64-encoded JWT";
+ logger.error(errorMessage, e);
+ throw new JwtException(errorMessage, e);
+ }
+ }
+ private Jws<Claims> parseTokenFromBase64EncodedString(final String base64EncodedToken) throws JwtException {
try {
- final Jws<Claims> jwt = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
+ // TODO: Check algorithm for validity
+ // TODO: Ensure signature verification occurs
+ return Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
@Override
public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
final String identity = claims.getSubject();
+
+ // The key is unique per identity and should be retrieved from the key service
final String key = keyService.getKey(identity);
- // ensure we were able to find a key that was previously issued by this key service for this user
+ // Ensure we were able to find a key that was previously issued by this key service for this user
if (key == null) {
throw new UnsupportedJwtException("Unable to determine signing key for " + identity);
}
return key.getBytes(StandardCharsets.UTF_8);
}
- }).parseClaimsJws(token);
- return jwt.getBody().getSubject();
+ }).parseClaimsJws(base64EncodedToken);
} catch (final MalformedJwtException | UnsupportedJwtException | SignatureException | ExpiredJwtException | IllegalArgumentException | AdministrationException e) {
- return null;
+ // TODO: Exercise all exceptions to ensure none leak key material to logs
+ final String errorMessage = "There was an error parsing the Base64-encoded JWT";
+ logger.error(errorMessage, e);
+ throw new JwtException(errorMessage, e);
}
}
/**
* Generates a signed JWT token from the provided (Spring Security) login authentication token.
*
- * @param authenticationToken the authentication token
+ * @param authenticationToken
* @return a signed JWT containing the user identity and the identity provider, Base64-encoded
+ * @throws JwtException
*/
- public String generateSignedToken(final LoginAuthenticationToken authenticationToken) {
- // set expiration to one day from now
- final Calendar calendar = Calendar.getInstance();
- calendar.setTimeInMillis(calendar.getTimeInMillis() + authenticationToken.getExpiration());
+ public String generateSignedToken(final LoginAuthenticationToken authenticationToken) throws JwtException {
+ if (authenticationToken == null) {
+ throw new IllegalArgumentException("Cannot generate a JWT for a null authentication token");
+ }
+
+ // Set expiration from the token
+ final Calendar expiration = Calendar.getInstance();
+ expiration.setTimeInMillis(authenticationToken.getExpiration());
- // create a token the specified authentication
- final String identity = authenticationToken.getPrincipal().toString();
+ final Object principal = authenticationToken.getPrincipal();
+ if (principal == null || StringUtils.isEmpty(principal.toString())) {
+ final String errorMessage = "Cannot generate a JWT for a token with an empty identity issued by " + authenticationToken.getIssuer();
+ logger.error(errorMessage);
+ throw new JwtException(errorMessage);
+ }
+
+ // Create a JWT with the specified authentication
+ final String identity = principal.toString();
final String username = authenticationToken.getName();
- // get/create the key for this user
- final String key = keyService.getOrCreateKey(identity);
- final byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
+ try {
+ // Get/create the key for this user
+ final String key = keyService.getOrCreateKey(identity);
+ final byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
+
+ logger.trace("Generating JWT for " + authenticationToken);
- // build the token
- return Jwts.builder().setSubject(identity).claim("preferred_username", username).setExpiration(calendar.getTime()).signWith(SignatureAlgorithm.HS512, keyBytes).compact();
+ // TODO: Implement "jti" claim with nonce to prevent replay attacks and allow blacklisting of revoked tokens
+
+ // Build the token
+ return Jwts.builder().setSubject(identity)
+ .setIssuer(authenticationToken.getIssuer())
+ .setAudience(authenticationToken.getIssuer())
+ .claim("preferred_username", username)
+ .setExpiration(expiration.getTime())
+ .setIssuedAt(Calendar.getInstance().getTime())
+ .signWith(SIGNATURE_ALGORITHM, keyBytes).compact();
+ } catch (NullPointerException | AdministrationException e) {
+ // TODO: Remove exception handling and pass through
+ final String errorMessage = "Could not retrieve the signing key for JWT";
+ logger.error(errorMessage, e);
+ throw new JwtException(errorMessage, e);
+ }
}
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/3bc11e13/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
index 0bb0932..a06ee89 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
@@ -19,25 +19,46 @@ package org.apache.nifi.web.security.token;
import org.apache.nifi.security.util.CertificateUtils;
import org.springframework.security.authentication.AbstractAuthenticationToken;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+
/**
- * This is an Authentication Token for logging in. Once a user is authenticated, they can be issues an ID token.
+ * This is an Authentication Token for logging in. Once a user is authenticated, they can be issued an ID token.
*/
public class LoginAuthenticationToken extends AbstractAuthenticationToken {
private final String identity;
private final String username;
private final long expiration;
+ private final String issuer;
- public LoginAuthenticationToken(final String identity, final long expiration) {
- this(identity, null, expiration);
+ /**
+ * Creates a representation of the authentication token for a user.
+ *
+ * @param identity The unique identifier for this user
+ * @param expiration The relative time to expiration in milliseconds
+ * @param issuer The IdentityProvider implementation that generated this token
+ */
+ public LoginAuthenticationToken(final String identity, final long expiration, final String issuer) {
+ this(identity, null, expiration, issuer);
}
- public LoginAuthenticationToken(final String identity, final String username, final long expiration) {
+ /**
+ * Creates a representation of the authentication token for a user.
+ *
+ * @param identity The unique identifier for this user (cannot be null or empty)
+ * @param username The preferred username for this user
+ * @param expiration The relative time to expiration in milliseconds
+ * @param issuer The IdentityProvider implementation that generated this token
+ */
+ public LoginAuthenticationToken(final String identity, final String username, final long expiration, final String issuer) {
super(null);
setAuthenticated(true);
this.identity = identity;
this.username = username;
- this.expiration = expiration;
+ this.issuer = issuer;
+ Calendar now = Calendar.getInstance();
+ this.expiration = now.getTimeInMillis() + expiration;
}
@Override
@@ -50,10 +71,19 @@ public class LoginAuthenticationToken extends AbstractAuthenticationToken {
return identity;
}
+ /**
+ * Returns the expiration instant in milliseconds. This value is an absolute point in time (i.e. Nov 16, 2015 11:30:00.000 GMT), not a relative time (i.e. 60 minutes). It is calculated by adding the relative expiration from the constructor to the timestamp at object creation.
+ *
+ * @return the expiration in millis
+ */
public long getExpiration() {
return expiration;
}
+ public String getIssuer() {
+ return issuer;
+ }
+
@Override
public String getName() {
if (username == null) {
@@ -64,4 +94,28 @@ public class LoginAuthenticationToken extends AbstractAuthenticationToken {
}
}
+ @Override
+ public String toString() {
+ Calendar expirationTime = Calendar.getInstance();
+ expirationTime.setTimeInMillis(getExpiration());
+ long remainingTime = expirationTime.getTimeInMillis() - Calendar.getInstance().getTimeInMillis();
+
+ SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss.SSS");
+ dateFormat.setTimeZone(expirationTime.getTimeZone());
+ String expirationTimeString = dateFormat.format(expirationTime.getTime());
+
+ return new StringBuilder("LoginAuthenticationToken for ")
+ .append(getName())
+ .append(" issued by ")
+ .append(getIssuer())
+ .append(" expiring at ")
+ .append(expirationTimeString)
+ .append(" [")
+ .append(getExpiration())
+ .append(" ms, ")
+ .append(remainingTime)
+ .append(" ms remaining]")
+ .toString();
+ }
+
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/3bc11e13/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..a7e763e
--- /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/JwtServiceTest.java
@@ -0,0 +1,254 @@
+package org.apache.nifi.web.security.jwt;
+
+import io.jsonwebtoken.JwtException;
+import org.apache.commons.codec.CharEncoding;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.nifi.admin.service.AdministrationException;
+import org.apache.nifi.admin.service.KeyService;
+import org.apache.nifi.web.security.token.LoginAuthenticationToken;
+import org.codehaus.jettison.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+/**
+ * Created by alopresto on 11/11/15.
+ */
+public class JwtServiceTest {
+
+ private static final Logger logger = LoggerFactory.getLogger(JwtServiceTest.class);
+
+ /**
+ * These constant strings were generated using the tool at http://jwt.io
+ */
+
+ private static final String VALID_SIGNED_TOKEN = "";
+ private static final String INVALID_SIGNED_TOKEN = "";
+ private static final String VALID_UNSIGNED_TOKEN = "";
+ private static final String INVALID_UNSIGNED_TOKEN = "";
+ private static final String VALID_MALSIGNED_TOKEN = "";
+ private static final String INVALID_MALSIGNED_TOKEN = "";
+
+ private static final String DEFAULT_HEADER = "{\"alg\":\"HS256\"}";
+
+ private static final String TOKEN_DELIMITER = ".";
+
+ private static final String HMAC_SECRET = "test_hmac_shared_secret";
+
+ private KeyService mockKeyService;
+
+ // Class under test
+ private JwtService jwtService;
+
+
+ private String generateHS256Token(String rawHeader, String rawPayload, boolean isValid, boolean isSigned) {
+ return generateHS256Token(rawHeader, rawPayload, HMAC_SECRET, isValid, isSigned);
+ }
+
+ private String generateHS256Token(String rawHeader, String rawPayload, String hmacSecret, boolean isValid, boolean isSigned) {
+ try {
+ logger.info("Generating token for " + rawHeader + " + " + rawPayload);
+
+ String base64Header = Base64.encodeBase64URLSafeString(rawHeader.getBytes(CharEncoding.UTF_8));
+ String base64Payload = Base64.encodeBase64URLSafeString(rawPayload.getBytes(CharEncoding.UTF_8));
+ // TODO: Support valid/invalid manipulation
+
+ final String body = base64Header + TOKEN_DELIMITER + base64Payload;
+
+ String signature = generateHMAC(hmacSecret, body);
+
+ return body + TOKEN_DELIMITER + signature;
+ } catch (NoSuchAlgorithmException | InvalidKeyException | UnsupportedEncodingException e) {
+ final String errorMessage = "Could not generate the token";
+ logger.error(errorMessage, e);
+ fail(errorMessage);
+ return null;
+ }
+ }
+
+ private String generateHMAC(String hmacSecret, String body) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
+ Mac hmacSHA256 = Mac.getInstance("HmacSHA256");
+ SecretKeySpec secret_key = new SecretKeySpec(hmacSecret.getBytes("UTF-8"), "HmacSHA256");
+ hmacSHA256.init(secret_key);
+ return Base64.encodeBase64URLSafeString(hmacSHA256.doFinal(body.getBytes("UTF-8")));
+ }
+
+
+ @Before
+ public void setUp() throws Exception {
+ mockKeyService = Mockito.mock(KeyService.class);
+ when(mockKeyService.getOrCreateKey(anyString())).thenReturn(HMAC_SECRET);
+ jwtService = new JwtService(mockKeyService);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+
+ }
+
+ @Test
+ public void testShouldGetAuthenticationForValidToken() throws Exception {
+
+ }
+
+ @Test
+ public void testShouldNotGetAuthenticationForInvalidToken() throws Exception {
+ // Arrange
+ String token = INVALID_SIGNED_TOKEN;
+
+ String header = "{" +
+ " \"alg\":\"HS256\"" +
+ "}";
+ String payload = "{" +
+ " \"sub\":\"alopresto\"," +
+ " \"preferred_username\":\"alopresto\"," +
+ " \"exp\":2895419760" +
+ "}";
+
+ // Act
+ logger.info("Test token: " + generateHS256Token(header, payload, true, true));
+
+
+ // Assert
+
+
+ }
+
+ @Test
+ public void testShouldNotGetAuthenticationForEmptyToken() throws Exception {
+
+ }
+
+ @Test
+ public void testShouldNotGetAuthenticationForUnsignedToken() throws Exception {
+
+ }
+
+ @Test
+ public void testShouldNotGetAuthenticationForTokenWithWrongAlgorithm() throws Exception {
+
+ }
+
+ @Test
+ public void testShouldGenerateSignedToken() throws Exception {
+ // Arrange
+
+ // Token expires in 60 seconds
+ final int EXPIRATION_MILLIS = 60000;
+ LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken("alopresto", EXPIRATION_MILLIS, "MockIdentityProvider");
+ logger.debug("Generating token for " + loginAuthenticationToken);
+
+ final String EXPECTED_HEADER = DEFAULT_HEADER;
+
+ // Convert the expiration time from ms to s
+ final long TOKEN_EXPIRATION_SEC = (long) (loginAuthenticationToken.getExpiration() / 1000.0);
+
+ // Act
+ String token = jwtService.generateSignedToken(loginAuthenticationToken);
+ logger.debug("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
+ final String DECODED_PAYLOAD = new String(Base64.decodeBase64(token.split("\\.")[1].getBytes()));
+ final long ISSUED_AT_SEC = Long.valueOf(DECODED_PAYLOAD.substring(DECODED_PAYLOAD.lastIndexOf(":") + 1, 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
+ Map<String, Object> claims = new LinkedHashMap<>();
+ claims.put("sub", "alopresto");
+ claims.put("iss", "MockIdentityProvider");
+ claims.put("aud", "MockIdentityProvider");
+ claims.put("preferred_username", "alopresto");
+ claims.put("exp", TOKEN_EXPIRATION_SEC);
+ claims.put("iat", ISSUED_AT_SEC);
+ logger.trace("JSON Object to String: " + new JSONObject(claims).toString());
+
+ 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);
+
+ // Assert
+ assertEquals("JWT token", EXPECTED_TOKEN_STRING, token);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testShouldNotGenerateTokenWithNullAuthenticationToken() throws Exception {
+ // Arrange
+ LoginAuthenticationToken nullLoginAuthenticationToken = null;
+ logger.debug("Generating token for " + nullLoginAuthenticationToken);
+
+ // Act
+ jwtService.generateSignedToken(nullLoginAuthenticationToken);
+
+ // Assert
+
+ // Should throw exception
+ }
+
+ @Test(expected = JwtException.class)
+ public void testShouldNotGenerateTokenWithEmptyIdentity() throws Exception {
+ // Arrange
+ final int EXPIRATION_MILLIS = 60000;
+ LoginAuthenticationToken emptyIdentityLoginAuthenticationToken = new LoginAuthenticationToken("", EXPIRATION_MILLIS, "MockIdentityProvider");
+ logger.debug("Generating token for " + emptyIdentityLoginAuthenticationToken);
+
+ // Act
+ jwtService.generateSignedToken(emptyIdentityLoginAuthenticationToken);
+
+ // Assert
+
+ // Should throw exception
+ }
+
+ @Test(expected = JwtException.class)
+ public void testShouldNotGenerateTokenWithNullIdentity() throws Exception {
+ // Arrange
+ final int EXPIRATION_MILLIS = 60000;
+ LoginAuthenticationToken nullIdentityLoginAuthenticationToken = new LoginAuthenticationToken(null, EXPIRATION_MILLIS, "MockIdentityProvider");
+ logger.debug("Generating token for " + nullIdentityLoginAuthenticationToken);
+
+ // Act
+ jwtService.generateSignedToken(nullIdentityLoginAuthenticationToken);
+
+ // Assert
+
+ // Should throw exception
+ }
+
+ @Test(expected = JwtException.class)
+ public void testShouldNotGenerateTokenWithMissingKey() throws Exception {
+ // Arrange
+ final int EXPIRATION_MILLIS = 60000;
+ LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken("alopresto", EXPIRATION_MILLIS, "MockIdentityProvider");
+ logger.debug("Generating token for " + loginAuthenticationToken);
+
+ // Set up the bad key service
+ KeyService missingKeyService = Mockito.mock(KeyService.class);
+ when(missingKeyService.getOrCreateKey(anyString())).thenThrow(new AdministrationException("Could not find a key for that user"));
+ jwtService = new JwtService(missingKeyService);
+
+ // Act
+ jwtService.generateSignedToken(loginAuthenticationToken);
+
+ // Assert
+
+ // Should throw exception
+ }
+}
\ No newline at end of file
[4/6] nifi git commit: NIFI-655. - Changed issuer field to use FQ
class name because some classes return an empty string for getSimpleName(). -
Finished refactoring JWT logic from request parsing logic in JwtService. -
Updated AccessResource and JwtAuthe
Posted by mc...@apache.org.
NIFI-655. - Changed issuer field to use FQ class name because some classes return an empty string for getSimpleName(). - Finished refactoring JWT logic from request parsing logic in JwtService. - Updated AccessResource and JwtAuthenticationFilter to call new JwtService methods decoupled from request header parsing. - Added extensive unit tests for JWT logic.
Signed-off-by: Matt Gilman <ma...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/7d04dfea
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/7d04dfea
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/7d04dfea
Branch: refs/heads/NIFI-655
Commit: 7d04dfeac012307201e84dd00883dd6a79e6e2cc
Parents: 3bc11e1
Author: Andy LoPresto <an...@andylopresto.com>
Authored: Wed Nov 18 00:45:38 2015 -0800
Committer: Matt Gilman <ma...@gmail.com>
Committed: Wed Nov 18 08:31:39 2015 -0500
----------------------------------------------------------------------
.../admin/service/impl/StandardKeyService.java | 10 +-
.../org/apache/nifi/web/api/AccessResource.java | 54 +++---
.../security/jwt/JwtAuthenticationFilter.java | 44 ++++-
.../nifi/web/security/jwt/JwtService.java | 64 +++----
.../nifi/web/security/jwt/JwtServiceTest.java | 182 ++++++++++++++++---
5 files changed, 264 insertions(+), 90 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d04dfea/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java
----------------------------------------------------------------------
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 1b2f8c9..7dff9d8 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
@@ -18,6 +18,9 @@ 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.GetKeyAction;
+import org.apache.nifi.admin.service.action.GetOrCreateKeyAction;
import org.apache.nifi.admin.service.transaction.Transaction;
import org.apache.nifi.admin.service.transaction.TransactionBuilder;
import org.apache.nifi.admin.service.transaction.TransactionException;
@@ -26,9 +29,6 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.locks.ReentrantReadWriteLock;
-import org.apache.nifi.admin.service.KeyService;
-import org.apache.nifi.admin.service.action.GetKeyAction;
-import org.apache.nifi.admin.service.action.GetOrCreateKeyAction;
/**
*
@@ -45,6 +45,8 @@ public class StandardKeyService implements KeyService {
@Override
public String getKey(String identity) {
+ // TODO: Change this service to look up by "key ID" instead of identity
+ // TODO: Change the return type to a Key POJO to support key rotation
Transaction transaction = null;
String key = null;
@@ -75,6 +77,8 @@ public class StandardKeyService implements KeyService {
@Override
public String getOrCreateKey(String identity) {
+ // TODO: Change this service to look up by "key ID" instead of identity
+ // TODO: Change the return type to a Key POJO to support key rotation
Transaction transaction = null;
String key = null;
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d04dfea/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
----------------------------------------------------------------------
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 b4778ad..2e1c44e 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
@@ -23,6 +23,7 @@ import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import io.jsonwebtoken.JwtException;
import org.apache.nifi.util.NiFiProperties;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
@@ -184,30 +185,33 @@ public class AccessResource extends ApplicationResource {
accessStatus.setStatus(AccessStatusDTO.Status.UNKNOWN.name());
accessStatus.setMessage("No credentials supplied, unknown user.");
} else {
- // TODO - use this token with the JWT service
+ // Extract the Base64 encoded token from the Authorization header
final String token = StringUtils.substringAfterLast(authorization, " ");
- // TODO - do not call this method of the jwt service
- final String principal = jwtService.getAuthentication(httpServletRequest);
-
- // TODO - catch jwt exception?
- // ensure we have something we can work with (certificate or credentials)
- if (principal == null) {
- throw new IllegalArgumentException("The specific token is not valid.");
- } else {
- // set the user identity
- accessStatus.setIdentity(principal);
- accessStatus.setUsername(CertificateUtils.extractUsername(principal));
-
- // without a certificate, this is not a proxied request
- final List<String> chain = Arrays.asList(principal);
-
- // check authorization for this user
- checkAuthorization(chain);
-
- // no issues with authorization
- accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
- accessStatus.setMessage("Account is active and authorized");
+ try {
+ final String principal = jwtService.getAuthenticationFromToken(token);
+
+ // ensure we have something we can work with (certificate or credentials)
+ if (principal == null) {
+ throw new IllegalArgumentException("The specific token is not valid.");
+ } else {
+ // set the user identity
+ accessStatus.setIdentity(principal);
+ accessStatus.setUsername(CertificateUtils.extractUsername(principal));
+
+ // without a certificate, this is not a proxied request
+ final List<String> chain = Arrays.asList(principal);
+
+ // check authorization for this user
+ checkAuthorization(chain);
+
+ // no issues with authorization
+ accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
+ accessStatus.setMessage("Account is active and authorized");
+ }
+ } catch (JwtException e) {
+ // TODO: Handle the exception from a failed JWT verification
+ throw new AccessDeniedException("The JWT could not be verified", e);
}
}
} else {
@@ -334,7 +338,8 @@ public class AccessResource extends ApplicationResource {
}
// create the authentication token
- loginAuthenticationToken = new LoginAuthenticationToken(authenticationResponse.getIdentity(), expiration, loginIdentityProvider.getClass().getSimpleName());
+ // TODO: Some Spring beans return "" for getClass().getSimpleName(). Using getName() temporarily
+ loginAuthenticationToken = new LoginAuthenticationToken(authenticationResponse.getIdentity(), expiration, loginIdentityProvider.getClass().getName());
} catch (final InvalidLoginCredentialsException ilce) {
throw new IllegalArgumentException("The supplied username and password are not valid.", ilce);
} catch (final IdentityAccessException iae) {
@@ -355,7 +360,8 @@ public class AccessResource extends ApplicationResource {
authorizeProxyIfNecessary(proxyChain);
// create the authentication token
- loginAuthenticationToken = new LoginAuthenticationToken(proxyChain.get(0), authenticationResponse.getExpiration(), certificateIdentityProvider.getClass().getSimpleName());
+ // TODO: Some Spring beans return "" for getClass().getSimpleName(). Using getName() temporarily
+ loginAuthenticationToken = new LoginAuthenticationToken(proxyChain.get(0), authenticationResponse.getExpiration(), certificateIdentityProvider.getClass().getName());
}
// generate JWT for response
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d04dfea/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
----------------------------------------------------------------------
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 5a84e93..dea5bba 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
@@ -16,9 +16,8 @@
*/
package org.apache.nifi.web.security.jwt;
-import java.util.Arrays;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import io.jsonwebtoken.JwtException;
+import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.web.security.NiFiAuthenticationFilter;
import org.apache.nifi.web.security.token.NewAccountAuthenticationRequestToken;
import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken;
@@ -26,12 +25,18 @@ import org.apache.nifi.web.security.user.NewAccountRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Arrays;
+
/**
*/
public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
+ private static final String AUTHORIZATION = "Authorization";
+
private JwtService jwtService;
@Override
@@ -41,16 +46,35 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
return null;
}
+ // TODO: Refactor request header extraction logic to shared utility as it is duplicated in AccessResource
+
// get the principal out of the user token
- final String jwtPrincipal = jwtService.getAuthentication(request);
- if (jwtPrincipal == null) {
- return null;
- }
+ // look for an authorization token
+ final String authorization = request.getHeader(AUTHORIZATION);
- if (isNewAccountRequest(request)) {
- return new NewAccountAuthenticationRequestToken(new NewAccountRequest(Arrays.asList(jwtPrincipal), getJustification(request)));
+ // if there is no authorization header, we don't know the user
+ if (authorization == null) {
+ return null;
} else {
- return new NiFiAuthenticationRequestToken(Arrays.asList(jwtPrincipal));
+ // Extract the Base64 encoded token from the Authorization header
+ final String token = StringUtils.substringAfterLast(authorization, " ");
+
+ try {
+ final String jwtPrincipal = jwtService.getAuthenticationFromToken(token);
+ if (jwtPrincipal == null) {
+ return null;
+ }
+
+ if (isNewAccountRequest(request)) {
+ return new NewAccountAuthenticationRequestToken(new NewAccountRequest(Arrays.asList(jwtPrincipal), getJustification(request)));
+ } else {
+ return new NiFiAuthenticationRequestToken(Arrays.asList(jwtPrincipal));
+ }
+ } catch (JwtException e) {
+ // TODO: Is this the correct way to handle an unverified token?
+ logger.error("Could not verify JWT", e);
+ return null;
+ }
}
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d04dfea/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
----------------------------------------------------------------------
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 acbbcfe..f006e5b 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
@@ -33,7 +33,6 @@ import org.apache.nifi.admin.service.KeyService;
import org.apache.nifi.web.security.token.LoginAuthenticationToken;
import org.slf4j.LoggerFactory;
-import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
@@ -44,7 +43,8 @@ public class JwtService {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(JwtService.class);
private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
- private final static String AUTHORIZATION = "Authorization";
+ private static final String KEY_ID_CLAIM = "kid";
+ private static final String USERNAME_CLAIM = "preferred_username";
private final KeyService keyService;
@@ -52,49 +52,50 @@ public class JwtService {
this.keyService = keyService;
}
- /**
- * Gets the Authentication by extracting a JWT token from the specified request.
- *
- * @param request Request to extract the token from
- * @return The user identifier from the token
- */
- public String getAuthentication(final HttpServletRequest request) {
- // TODO: Refactor request token extraction out of this service
- // extract/verify token from incoming request
- final String authorization = request.getHeader(AUTHORIZATION);
- final String base64EncodedToken = StringUtils.substringAfterLast(authorization, " ");
-
- return getAuthenticationFromToken(base64EncodedToken);
- }
-
- public String getAuthenticationFromToken(final String base64EncodedToken) {
+ public String getAuthenticationFromToken(final String base64EncodedToken) throws JwtException {
// The library representations of the JWT should be kept internal to this service.
try {
final Jws<Claims> jws = parseTokenFromBase64EncodedString(base64EncodedToken);
+
+ if (jws == null) {
+ throw new JwtException("Unable to parse token");
+ }
+
+ // Additional validation that subject is present
+ if (StringUtils.isEmpty(jws.getBody().getSubject())) {
+ throw new JwtException("No subject available in token");
+ }
+
+ // TODO: Validate issuer against active registry?
+ if (StringUtils.isEmpty(jws.getBody().getIssuer())) {
+ // TODO: Remove after testing
+// logger.info("Decoded JWT payload: " + jws.toString());
+ throw new JwtException("No issuer available in token");
+ }
return jws.getBody().getSubject();
} catch (JwtException e) {
logger.debug("The Base64 encoded JWT: " + base64EncodedToken);
- final String errorMessage = "There was an error parsing the Base64-encoded JWT";
+ final String errorMessage = "There was an error validating the JWT";
logger.error(errorMessage, e);
- throw new JwtException(errorMessage, e);
+ throw e;
}
}
private Jws<Claims> parseTokenFromBase64EncodedString(final String base64EncodedToken) throws JwtException {
try {
- // TODO: Check algorithm for validity
- // TODO: Ensure signature verification occurs
return Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
@Override
public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
final String identity = claims.getSubject();
+ // TODO: Currently the kid field is identical to identity, but will be a unique key ID when key rotation is implemented
+ final String keyId = claims.get(KEY_ID_CLAIM, String.class);
// The key is unique per identity and should be retrieved from the key service
- final String key = keyService.getKey(identity);
+ final String key = keyService.getKey(keyId);
// Ensure we were able to find a key that was previously issued by this key service for this user
if (key == null) {
- throw new UnsupportedJwtException("Unable to determine signing key for " + identity);
+ throw new UnsupportedJwtException("Unable to determine signing key for " + identity + " [kid: " + keyId + "]");
}
return key.getBytes(StandardCharsets.UTF_8);
@@ -102,8 +103,7 @@ public class JwtService {
}).parseClaimsJws(base64EncodedToken);
} catch (final MalformedJwtException | UnsupportedJwtException | SignatureException | ExpiredJwtException | IllegalArgumentException | AdministrationException e) {
// TODO: Exercise all exceptions to ensure none leak key material to logs
- final String errorMessage = "There was an error parsing the Base64-encoded JWT";
- logger.error(errorMessage, e);
+ final String errorMessage = "There was an error validating the JWT";
throw new JwtException(errorMessage, e);
}
}
@@ -111,9 +111,9 @@ public class JwtService {
/**
* Generates a signed JWT token from the provided (Spring Security) login authentication token.
*
- * @param authenticationToken
+ * @param authenticationToken an instance of the Spring Security token after login credentials have been verified against the respective information source
* @return a signed JWT containing the user identity and the identity provider, Base64-encoded
- * @throws JwtException
+ * @throws JwtException if there is a problem generating the signed token
*/
public String generateSignedToken(final LoginAuthenticationToken authenticationToken) throws JwtException {
if (authenticationToken == null) {
@@ -144,17 +144,19 @@ public class JwtService {
// TODO: Implement "jti" claim with nonce to prevent replay attacks and allow blacklisting of revoked tokens
+ // TODO: Change kid field to key ID when KeyService is refactored
+
// Build the token
return Jwts.builder().setSubject(identity)
.setIssuer(authenticationToken.getIssuer())
.setAudience(authenticationToken.getIssuer())
- .claim("preferred_username", username)
+ .claim(USERNAME_CLAIM, username)
+ .claim(KEY_ID_CLAIM, identity)
.setExpiration(expiration.getTime())
.setIssuedAt(Calendar.getInstance().getTime())
.signWith(SIGNATURE_ALGORITHM, keyBytes).compact();
} catch (NullPointerException | AdministrationException e) {
- // TODO: Remove exception handling and pass through
- final String errorMessage = "Could not retrieve the signing key for JWT";
+ final String errorMessage = "Could not retrieve the signing key for JWT for " + identity;
logger.error(errorMessage, e);
throw new JwtException(errorMessage, e);
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d04dfea/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
----------------------------------------------------------------------
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 a7e763e..c9107b9 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
@@ -9,6 +9,7 @@ import org.apache.nifi.web.security.token.LoginAuthenticationToken;
import org.codehaus.jettison.json.JSONObject;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
import org.slf4j.Logger;
@@ -38,14 +39,38 @@ public class JwtServiceTest {
* These constant strings were generated using the tool at http://jwt.io
*/
- private static final String VALID_SIGNED_TOKEN = "";
- private static final String INVALID_SIGNED_TOKEN = "";
- private static final String VALID_UNSIGNED_TOKEN = "";
- private static final String INVALID_UNSIGNED_TOKEN = "";
- private static final String VALID_MALSIGNED_TOKEN = "";
- private static final String INVALID_MALSIGNED_TOKEN = "";
+ private static final String VALID_SIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbG9wcmVzdG8iLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.8fUgH9jLvE1essgrcoV8OCyDhXvSXUH_1xqeqDqWycU";
+
+ // This token has an empty subject field
+ private static final String INVALID_SIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.jcjRBLtDzREmdjkJf3xry-ucyCmSRygBaP-HCWBkwlI";
+
+ private static final String VALID_UNSIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbG9wcmVzdG8iLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9";
+
+ // This token has an empty subject field
+ private static final String INVALID_UNSIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9";
+
+ // Algorithm field is "none"
+ private static final String VALID_MALSIGNED_TOKEN = "eyJhbGciOiJub25lIn0.eyJzdWIiOiJhbG9wcmVzdG8iLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoxNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.mPO_wMNMl_zjMNevhNvUoXbSJ9Kx6jAe5OxDIAzKQbI";
+
+ // Algorithm field is "none" and no signature is present
+ private static final String VALID_MALSIGNED_NO_SIG_TOKEN = "eyJhbGciOiJub25lIn0.eyJzdWIiOiJhbG9wcmVzdG8iLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.";
+
+ // This token has an empty subject field
+ private static final String INVALID_MALSIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoxNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.WAwmUY4KHKV2oARNodkqDkbZsfRXGZfD2Ccy64GX9QF";
+
+ private static final String EXPIRED_SIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoxNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.y3M1TlzXZ80cVTkfcNxaHpq6aAlM1y2HGCZWEOcvmSU";
+
+ // Subject is "mgilman" but signed with "alopresto" key
+ private static final String IMPOSTER_SIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtZ2lsbWFuIiwiaXNzIjoiTW9ja0lkZW50aXR5UHJvdmlkZXIiLCJhdWQiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsInByZWZlcnJlZF91c2VybmFtZSI6ImFsb3ByZXN0byIsImtpZCI6ImFsb3ByZXN0byIsImV4cCI6MjQ0NzgwODc2MSwiaWF0IjoxNDQ3ODA4NzAxfQ.l-9nHmYTEMgLshX8qCEqbc2O4BH_GYBVQIFkUKsJvLA";
+
+ // Issuer field is set to unknown provider
+ private static final String UNKNOWN_ISSUER_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbG9wcmVzdG8iLCJpc3MiOiJVbmtub3duSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.SAd9tyNwSaijWet9wvAWSNmpxmPSK4XQuLx7h3ARqBo";
+
+ // Issuer field is absent
+ private static final String NO_ISSUER_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbG9wcmVzdG8iLCJhdWQiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsInByZWZlcnJlZF91c2VybmFtZSI6ImFsb3ByZXN0byIsImtpZCI6ImFsb3ByZXN0byIsImV4cCI6MjQ0NzgwODc2MSwiaWF0IjoxNDQ3ODA4NzAxfQ.Hdha7K69sz6224vidvuZ6A6UdGLdZ_U1egS0txuVXAk";
private static final String DEFAULT_HEADER = "{\"alg\":\"HS256\"}";
+ private static final String DEFAULT_IDENTITY = "alopresto";
private static final String TOKEN_DELIMITER = ".";
@@ -93,6 +118,7 @@ public class JwtServiceTest {
@Before
public void setUp() throws Exception {
mockKeyService = Mockito.mock(KeyService.class);
+ when(mockKeyService.getKey(anyString())).thenReturn(HMAC_SECRET);
when(mockKeyService.getOrCreateKey(anyString())).thenReturn(HMAC_SECRET);
jwtService = new JwtService(mockKeyService);
}
@@ -104,45 +130,156 @@ public class JwtServiceTest {
@Test
public void testShouldGetAuthenticationForValidToken() throws Exception {
+ // Arrange
+ String token = VALID_SIGNED_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+ // Assert
+ assertEquals("Identity", DEFAULT_IDENTITY, identity);
}
- @Test
+ @Test(expected = JwtException.class)
public void testShouldNotGetAuthenticationForInvalidToken() throws Exception {
// Arrange
String token = INVALID_SIGNED_TOKEN;
- String header = "{" +
- " \"alg\":\"HS256\"" +
- "}";
- String payload = "{" +
- " \"sub\":\"alopresto\"," +
- " \"preferred_username\":\"alopresto\"," +
- " \"exp\":2895419760" +
- "}";
-
// Act
- logger.info("Test token: " + generateHS256Token(header, payload, true, true));
-
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
// Assert
-
+ // Should fail
}
- @Test
+ @Test(expected = JwtException.class)
public void testShouldNotGetAuthenticationForEmptyToken() throws Exception {
+ // Arrange
+ String token = "";
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+ // Assert
+
+ // Should fail
}
- @Test
+ @Test(expected = JwtException.class)
public void testShouldNotGetAuthenticationForUnsignedToken() throws Exception {
+ // Arrange
+ String token = VALID_UNSIGNED_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+ // Should fail
}
- @Test
+ @Test(expected = JwtException.class)
+ public void testShouldNotGetAuthenticationForMalsignedToken() throws Exception {
+ // Arrange
+ String token = VALID_MALSIGNED_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+
+ // Should fail
+ }
+
+ @Test(expected = JwtException.class)
public void testShouldNotGetAuthenticationForTokenWithWrongAlgorithm() throws Exception {
+ // Arrange
+ String token = VALID_MALSIGNED_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+
+ // Should fail
+ }
+
+ @Test(expected = JwtException.class)
+ public void testShouldNotGetAuthenticationForTokenWithWrongAlgorithmAndNoSignature() throws Exception {
+ // Arrange
+ String token = VALID_MALSIGNED_NO_SIG_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+
+ // Should fail
+ }
+
+ @Ignore("Not yet implemented")
+ @Test(expected = JwtException.class)
+ public void testShouldNotGetAuthenticationForTokenFromUnknownIdentityProvider() throws Exception {
+ // Arrange
+ String token = UNKNOWN_ISSUER_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+
+ // Should fail
+ }
+
+ @Test(expected = JwtException.class)
+ public void testShouldNotGetAuthenticationForTokenFromEmptyIdentityProvider() throws Exception {
+ // Arrange
+ String token = NO_ISSUER_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+
+ // Should fail
+ }
+
+ @Test(expected = JwtException.class)
+ public void testShouldNotGetAuthenticationForExpiredToken() throws Exception {
+ // Arrange
+ String token = EXPIRED_SIGNED_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+
+ // Should fail
+ }
+
+ @Test(expected = JwtException.class)
+ public void testShouldNotGetAuthenticationForImposterToken() throws Exception {
+ // Arrange
+ String token = IMPOSTER_SIGNED_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+ // Should fail
}
@Test
@@ -176,6 +313,7 @@ public class JwtServiceTest {
claims.put("iss", "MockIdentityProvider");
claims.put("aud", "MockIdentityProvider");
claims.put("preferred_username", "alopresto");
+ claims.put("kid", "alopresto");
claims.put("exp", TOKEN_EXPIRATION_SEC);
claims.put("iat", ISSUED_AT_SEC);
logger.trace("JSON Object to String: " + new JSONObject(claims).toString());
[2/6] nifi git commit: NIFI-655. - Added logback-test.xml
configuration resource for nifi-web-security.
Posted by mc...@apache.org.
NIFI-655. - Added logback-test.xml configuration resource for nifi-web-security.
Signed-off-by: Matt Gilman <ma...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/caeede57
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/caeede57
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/caeede57
Branch: refs/heads/NIFI-655
Commit: caeede5773ac75061049a1c792a7b15188cae098
Parents: 45b24a4
Author: Andy LoPresto <an...@andylopresto.com>
Authored: Tue Nov 17 17:27:31 2015 -0800
Committer: Matt Gilman <ma...@gmail.com>
Committed: Wed Nov 18 08:31:08 2015 -0500
----------------------------------------------------------------------
.../src/test/resources/logback-test.xml | 36 ++++++++++++++++++++
1 file changed, 36 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/nifi/blob/caeede57/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/resources/logback-test.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/resources/logback-test.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..fe5d27e
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/resources/logback-test.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+
+<configuration>
+ <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <pattern>%-4r [%t] %-5p %c - %m%n</pattern>
+ </encoder>
+ </appender>
+
+ <appender name="FILE" class="ch.qos.logback.core.FileAppender">
+ <file>./target/log</file>
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <pattern>%date %level [%thread] %logger{40} %msg%n</pattern>
+ </encoder>
+ </appender>
+
+
+ <logger name="org.apache.nifi" level="TRACE"/>
+ <root level="INFO">
+ <appender-ref ref="CONSOLE"/>
+ </root>
+</configuration>