You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by bb...@apache.org on 2016/02/19 19:22:18 UTC
[2/2] nifi git commit: NIFI-1497: - Introducing a one time use
password service for use in query parameters when accessing UI extensions and
downloading resources. - Using one time use tokens when accessing ui
extensions and downloading resources. - Ensu
NIFI-1497: - Introducing a one time use password service for use in query parameters when accessing UI extensions and downloading resources. - Using one time use tokens when accessing ui extensions and downloading resources. - Ensuring appropriate roles when accessing component details through the web context for custom UIs. - Addressing typo in class name. - Ensuring appropriate roles when accessing content through the content access. - Code clean up. - Refactoring some basic scripts for accessing JWT tokens so UI extensions can reuse common functionality.
Signed-off-by: Bryan Bende <bb...@apache.org>
Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/a8edab2e
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/a8edab2e
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/a8edab2e
Branch: refs/heads/master
Commit: a8edab2e7955b175c5cbd2a3266fb51fa9712aee
Parents: 86ab442
Author: Matt Gilman <ma...@gmail.com>
Authored: Fri Feb 19 10:50:31 2016 -0500
Committer: Bryan Bende <bb...@apache.org>
Committed: Fri Feb 19 10:54:53 2016 -0500
----------------------------------------------------------------------
.../web/NiFiWebApiSecurityConfiguration.java | 35 ++-
.../nifi/web/StandardNiFiContentAccess.java | 70 ++++--
.../StandardNiFiWebConfigurationContext.java | 4 +
.../apache/nifi/web/StandardNiFiWebContext.java | 4 +
.../org/apache/nifi/web/api/AccessResource.java | 122 ++++++++++-
.../src/main/resources/nifi-web-api-context.xml | 1 +
.../accesscontrol/AccessTokenEndpointTest.java | 2 +-
.../nifi/web/ContentViewerController.java | 40 +++-
.../src/main/webapp/WEB-INF/jsp/header.jsp | 64 +++++-
.../src/main/webapp/css/main.css | 8 +
.../web/security/NiFiAuthenticationFilter.java | 6 +-
.../security/NiFiAuthenticationProvider.java | 10 +-
.../anonymous/NiFiAnonymousUserFilter.java | 7 +-
.../authorization/NiFiAuthorizationService.java | 6 +-
.../security/jwt/JwtAuthenticationFilter.java | 6 +-
.../otp/OtpAuthenticationException.java | 34 +++
.../security/otp/OtpAuthenticationFilter.java | 96 ++++++++
.../nifi/web/security/otp/OtpService.java | 219 +++++++++++++++++++
.../NewAccountAuthorizationRequestToken.java | 2 +-
.../token/NiFiAuthorizationRequestToken.java | 54 +++++
.../token/NiFiAuthortizationRequestToken.java | 54 -----
.../security/token/OtpAuthenticationToken.java | 56 +++++
.../security/x509/X509AuthenticationFilter.java | 6 +-
.../resources/nifi-web-security-context.xml | 5 +-
.../NiFiAuthorizationServiceTest.java | 6 +-
.../otp/OtpAuthenticationFilterTest.java | 210 ++++++++++++++++++
.../nifi/web/security/otp/OtpServiceTest.java | 149 +++++++++++++
.../nifi-framework/nifi-web/nifi-web-ui/pom.xml | 14 ++
.../resources/filters/bulletin-board.properties | 1 +
.../main/resources/filters/canvas.properties | 1 +
.../main/resources/filters/cluster.properties | 1 +
.../main/resources/filters/counters.properties | 1 +
.../main/resources/filters/history.properties | 1 +
.../src/main/resources/filters/login.properties | 1 +
.../resources/filters/provenance.properties | 1 +
.../main/resources/filters/summary.properties | 1 +
.../main/resources/filters/templates.properties | 1 +
.../src/main/resources/filters/users.properties | 1 +
.../main/webapp/js/nf/canvas/nf-canvas-utils.js | 2 +-
.../main/webapp/js/nf/canvas/nf-custom-ui.js | 42 ++--
.../webapp/js/nf/canvas/nf-queue-listing.js | 136 +++++++++---
.../src/main/webapp/js/nf/nf-ajax-setup.js | 39 ++++
.../src/main/webapp/js/nf/nf-common.js | 41 ++--
.../src/main/webapp/js/nf/nf-storage.js | 25 ++-
.../js/nf/provenance/nf-provenance-table.js | 136 +++++++++---
.../js/nf/templates/nf-templates-table.js | 33 ++-
.../update/attributes/api/RuleResource.java | 22 +-
.../src/main/webapp/WEB-INF/jsp/worksheet.jsp | 3 +
48 files changed, 1550 insertions(+), 229 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
index 1488aba..64ecc5f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
@@ -24,7 +24,9 @@ import org.apache.nifi.web.security.anonymous.NiFiAnonymousUserFilter;
import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter;
import org.apache.nifi.web.security.jwt.JwtService;
import org.apache.nifi.web.security.node.NodeAuthorizedUserFilter;
-import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
+import org.apache.nifi.web.security.otp.OtpAuthenticationFilter;
+import org.apache.nifi.web.security.otp.OtpService;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
import org.apache.nifi.web.security.x509.X509AuthenticationFilter;
import org.apache.nifi.web.security.x509.X509CertificateExtractor;
import org.apache.nifi.web.security.x509.X509IdentityProvider;
@@ -54,12 +56,14 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
private UserService userService;
private AuthenticationUserDetailsService userDetailsService;
private JwtService jwtService;
+ private OtpService otpService;
private X509CertificateExtractor certificateExtractor;
private X509IdentityProvider certificateIdentityProvider;
private LoginIdentityProvider loginIdentityProvider;
private NodeAuthorizedUserFilter nodeAuthorizedUserFilter;
private JwtAuthenticationFilter jwtAuthenticationFilter;
+ private OtpAuthenticationFilter otpAuthenticationFilter;
private X509AuthenticationFilter x509AuthenticationFilter;
private NiFiAnonymousUserFilter anonymousAuthenticationFilter;
@@ -69,9 +73,12 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
@Override
public void configure(WebSecurity webSecurity) throws Exception {
+ // ignore the access endpoints for obtaining the access config, the access token
+ // granting, and access status for a given user (note: we are not ignoring the
+ // the /access/download-token and /access/ui-extension-token endpoints
webSecurity
.ignoring()
- .antMatchers("/access/**");
+ .antMatchers("/access", "/access/config", "/access/token");
}
@Override
@@ -95,6 +102,9 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
// jwt
http.addFilterAfter(jwtFilterBean(), AnonymousAuthenticationFilter.class);
+
+ // otp
+ http.addFilterAfter(otpFilterBean(), AnonymousAuthenticationFilter.class);
}
@Bean
@@ -135,6 +145,20 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
return jwtAuthenticationFilter;
}
+ public OtpAuthenticationFilter otpFilterBean() throws Exception {
+ if (otpAuthenticationFilter == null) {
+ otpAuthenticationFilter = new OtpAuthenticationFilter();
+ otpAuthenticationFilter.setProperties(properties);
+ otpAuthenticationFilter.setAuthenticationManager(authenticationManager());
+
+ // only consider the tokens when configured for login
+ if (loginIdentityProvider != null) {
+ otpAuthenticationFilter.setOtpService(otpService);
+ }
+ }
+ return otpAuthenticationFilter;
+ }
+
@Bean
public X509AuthenticationFilter x509FilterBean() throws Exception {
if (x509AuthenticationFilter == null) {
@@ -157,7 +181,7 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
}
@Autowired
- public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthortizationRequestToken> userDetailsService) {
+ public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthorizationRequestToken> userDetailsService) {
this.userDetailsService = userDetailsService;
}
@@ -177,6 +201,11 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
}
@Autowired
+ public void setOtpService(OtpService otpService) {
+ this.otpService = otpService;
+ }
+
+ @Autowired
public void setLoginIdentityProvider(LoginIdentityProvider loginIdentityProvider) {
this.loginIdentityProvider = loginIdentityProvider;
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java
index f994c52..afaf3ed 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java
@@ -17,19 +17,10 @@
package org.apache.nifi.web;
import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.core.util.MultivaluedMapImpl;
-import java.io.Serializable;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import javax.ws.rs.HttpMethod;
-import javax.ws.rs.core.MultivaluedMap;
import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.authorization.Authority;
import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.cluster.manager.exception.UnknownNodeException;
import org.apache.nifi.cluster.manager.impl.WebClusterManager;
@@ -38,13 +29,26 @@ import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.controller.repository.claim.ContentDirection;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.user.NiFiUserDetails;
+import org.apache.nifi.web.security.user.NiFiUserUtils;
import org.apache.nifi.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.MultivaluedMap;
+import java.io.Serializable;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
/**
*
*/
@@ -54,17 +58,16 @@ public class StandardNiFiContentAccess implements ContentAccess {
public static final String CLIENT_ID_PARAM = "clientId";
private static final Pattern FLOWFILE_CONTENT_URI_PATTERN = Pattern
- .compile("/controller/process-groups/((?:root)|(?:[a-f0-9\\-]{36}))/connections/([a-f0-9\\-]{36})/flowfiles/([a-f0-9\\-]{36})/content");
+ .compile("/controller/process-groups/((?:root)|(?:[a-f0-9\\-]{36}))/connections/([a-f0-9\\-]{36})/flowfiles/([a-f0-9\\-]{36})/content.*");
private static final Pattern PROVENANCE_CONTENT_URI_PATTERN = Pattern
- .compile("/controller/provenance/events/([0-9]+)/content/((?:input)|(?:output))");
+ .compile("/controller/provenance/events/([0-9]+)/content/((?:input)|(?:output)).*");
private NiFiProperties properties;
private NiFiServiceFacade serviceFacade;
private WebClusterManager clusterManager;
@Override
- @PreAuthorize("hasRole('ROLE_PROVENANCE')")
public DownloadableContent getContent(final ContentRequestContext request) {
// if clustered, send request to cluster manager
if (properties.isClusterManager()) {
@@ -99,6 +102,11 @@ public class StandardNiFiContentAccess implements ContentAccess {
}
}
+ // ensure we were able to detect the cluster node id
+ if (request.getClusterNodeId() == null) {
+ throw new IllegalArgumentException("Unable to determine the which node has the content.");
+ }
+
// get the target node and ensure it exists
final Node targetNode = clusterManager.getNode(request.getClusterNodeId());
if (targetNode == null) {
@@ -113,6 +121,16 @@ public class StandardNiFiContentAccess implements ContentAccess {
final ClientResponse clientResponse = nodeResponse.getClientResponse();
final MultivaluedMap<String, String> responseHeaders = clientResponse.getHeaders();
+ // ensure an appropriate response
+ if (Status.NOT_FOUND.getStatusCode() == clientResponse.getStatusInfo().getStatusCode()) {
+ throw new ResourceNotFoundException(clientResponse.getEntity(String.class));
+ } else if (Status.FORBIDDEN.getStatusCode() == clientResponse.getStatusInfo().getStatusCode()
+ || Status.UNAUTHORIZED.getStatusCode() == clientResponse.getStatusInfo().getStatusCode()) {
+ throw new AccessDeniedException(clientResponse.getEntity(String.class));
+ } else if (Status.OK.getStatusCode() != clientResponse.getStatusInfo().getStatusCode()) {
+ throw new IllegalStateException(clientResponse.getEntity(String.class));
+ }
+
// get the file name
final String contentDisposition = responseHeaders.getFirst("Content-Disposition");
final String filename = StringUtils.substringBetween(contentDisposition, "filename=\"", "\"");
@@ -140,7 +158,7 @@ public class StandardNiFiContentAccess implements ContentAccess {
final String connectionId = flowFileMatcher.group(2);
final String flowfileId = flowFileMatcher.group(3);
- return serviceFacade.getContent(groupId, connectionId, flowfileId, dataUri);
+ return getFlowFileContent(groupId, connectionId, flowfileId, dataUri);
}
// provenance event content
@@ -150,7 +168,7 @@ public class StandardNiFiContentAccess implements ContentAccess {
final Long eventId = Long.parseLong(provenanceMatcher.group(1));
final ContentDirection direction = ContentDirection.valueOf(provenanceMatcher.group(2).toUpperCase());
- return serviceFacade.getContent(eventId, dataUri, direction);
+ return getProvenanceEventContent(eventId, dataUri, direction);
} catch (final IllegalArgumentException iae) {
throw new IllegalArgumentException("The specified data reference URI is not valid.");
}
@@ -161,6 +179,24 @@ public class StandardNiFiContentAccess implements ContentAccess {
}
}
+ private DownloadableContent getFlowFileContent(final String groupId, final String connectionId, final String flowfileId, final String dataUri) {
+ // ensure the user is authorized as DFM - not checking with @PreAuthorized annotation as aspect not trigger on call within a class
+ if (!NiFiUserUtils.getAuthorities().contains(Authority.ROLE_DFM.toString())) {
+ throw new AccessDeniedException("Access is denied.");
+ }
+
+ return serviceFacade.getContent(groupId, connectionId, flowfileId, dataUri);
+ }
+
+ private DownloadableContent getProvenanceEventContent(final Long eventId, final String dataUri, final ContentDirection direction) {
+ // ensure the user is authorized as Provenance - not checking with @PreAuthorized annotation as aspect not trigger on call within a class
+ if (!NiFiUserUtils.getAuthorities().contains(Authority.ROLE_PROVENANCE.toString())) {
+ throw new AccessDeniedException("Access is denied.");
+ }
+
+ return serviceFacade.getContent(eventId, dataUri, direction);
+ }
+
public void setProperties(NiFiProperties properties) {
this.properties = properties;
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
index 9502bf2..cae1175 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
@@ -86,6 +86,7 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
private AuditService auditService;
@Override
+ @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
public ControllerService getControllerService(String serviceIdentifier) {
return controllerServiceLookup.getControllerService(serviceIdentifier);
}
@@ -157,6 +158,7 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
}
@Override
+ @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
public String getCurrentUserDn() {
String userIdentity = NiFiUser.ANONYMOUS_USER_IDENTITY;
@@ -169,6 +171,7 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
}
@Override
+ @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
public String getCurrentUserName() {
String userName = NiFiUser.ANONYMOUS_USER_IDENTITY;
@@ -181,6 +184,7 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
}
@Override
+ @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
public ComponentDetails getComponentDetails(final NiFiWebRequestContext requestContext) throws ResourceNotFoundException, ClusterRequestException {
final String id = requestContext.getId();
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java
index 7e9e77e..9667ad6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java
@@ -81,6 +81,7 @@ public class StandardNiFiWebContext implements NiFiWebContext {
private AuditService auditService;
@Override
+ @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
public ControllerService getControllerService(String serviceIdentifier) {
return controllerServiceLookup.getControllerService(serviceIdentifier);
}
@@ -128,6 +129,7 @@ public class StandardNiFiWebContext implements NiFiWebContext {
}
@Override
+ @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
public String getCurrentUserDn() {
String userIdentity = NiFiUser.ANONYMOUS_USER_IDENTITY;
@@ -140,6 +142,7 @@ public class StandardNiFiWebContext implements NiFiWebContext {
}
@Override
+ @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
public String getCurrentUserName() {
String userName = NiFiUser.ANONYMOUS_USER_IDENTITY;
@@ -152,6 +155,7 @@ public class StandardNiFiWebContext implements NiFiWebContext {
}
@Override
+ @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
public ProcessorInfo getProcessor(final NiFiWebContextConfig config) throws ResourceNotFoundException, ClusterRequestException {
final Revision revision = config.getRevision();
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/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 7bf9690..24536fc 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
@@ -24,6 +24,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import io.jsonwebtoken.JwtException;
+import org.apache.nifi.user.NiFiUser;
import org.apache.nifi.util.NiFiProperties;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
@@ -60,8 +61,11 @@ import org.apache.nifi.web.security.ProxiedEntitiesUtils;
import org.apache.nifi.web.security.UntrustedProxyException;
import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter;
import org.apache.nifi.web.security.jwt.JwtService;
+import org.apache.nifi.web.security.otp.OtpService;
import org.apache.nifi.web.security.token.LoginAuthenticationToken;
-import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
+import org.apache.nifi.web.security.token.OtpAuthenticationToken;
+import org.apache.nifi.web.security.user.NiFiUserUtils;
import org.apache.nifi.web.security.x509.X509CertificateExtractor;
import org.apache.nifi.web.security.x509.X509IdentityProvider;
import org.slf4j.Logger;
@@ -92,8 +96,9 @@ public class AccessResource extends ApplicationResource {
private X509CertificateExtractor certificateExtractor;
private X509IdentityProvider certificateIdentityProvider;
private JwtService jwtService;
+ private OtpService otpService;
- private AuthenticationUserDetailsService<NiFiAuthortizationRequestToken> userDetailsService;
+ private AuthenticationUserDetailsService<NiFiAuthorizationRequestToken> userDetailsService;
/**
* Retrieves the access configuration for this NiFi.
@@ -285,7 +290,107 @@ public class AccessResource extends ApplicationResource {
* @throws AuthenticationException if the proxy chain is not authorized
*/
private UserDetails checkAuthorization(final List<String> proxyChain) throws AuthenticationException {
- return userDetailsService.loadUserDetails(new NiFiAuthortizationRequestToken(proxyChain));
+ return userDetailsService.loadUserDetails(new NiFiAuthorizationRequestToken(proxyChain));
+ }
+
+ /**
+ * Creates a single use access token for downloading FlowFile content.
+ *
+ * @param httpServletRequest the servlet request
+ * @return A token (string)
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ @Produces(MediaType.TEXT_PLAIN)
+ @Path("/download-token")
+ @ApiOperation(
+ value = "Creates a single use access token for downloading FlowFile content.",
+ notes = "The token returned is a base64 encoded string. It is valid for a single request up to five minutes from being issued. " +
+ "It is used as a query parameter name 'access_token'.",
+ response = String.class
+ )
+ @ApiResponses(
+ value = {
+ @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+ @ApiResponse(code = 409, message = "Unable to create the download token because NiFi is not in the appropriate state. " +
+ "(i.e. may not have any tokens to grant or be configured to support username/password login)"),
+ @ApiResponse(code = 500, message = "Unable to create download token because an unexpected error occurred.")
+ }
+ )
+ public Response createDownloadToken(@Context HttpServletRequest httpServletRequest) {
+ // only support access tokens when communicating over HTTPS
+ if (!httpServletRequest.isSecure()) {
+ throw new IllegalStateException("Download tokens are only issued over HTTPS.");
+ }
+
+ // if not configuration for login, don't consider credentials
+ if (loginIdentityProvider == null) {
+ throw new IllegalStateException("Download tokens not supported by this NiFi.");
+ }
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ if (user == null) {
+ throw new AccessDeniedException("Unable to determine user details.");
+ }
+
+ final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(user.getIdentity());
+
+ // generate otp for response
+ final String token = otpService.generateDownloadToken(authenticationToken);
+
+ // build the response
+ final URI uri = URI.create(generateResourceUri("access", "download-token"));
+ return generateCreatedResponse(uri, token).build();
+ }
+
+ /**
+ * Creates a single use access token for accessing a NiFi UI extension.
+ *
+ * @param httpServletRequest the servlet request
+ * @return A token (string)
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ @Produces(MediaType.TEXT_PLAIN)
+ @Path("/ui-extension-token")
+ @ApiOperation(
+ value = "Creates a single use access token for accessing a NiFi UI extension.",
+ notes = "The token returned is a base64 encoded string. It is valid for a single request up to five minutes from being issued. " +
+ "It is used as a query parameter name 'access_token'.",
+ response = String.class
+ )
+ @ApiResponses(
+ value = {
+ @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+ @ApiResponse(code = 409, message = "Unable to create the download token because NiFi is not in the appropriate state. " +
+ "(i.e. may not have any tokens to grant or be configured to support username/password login)"),
+ @ApiResponse(code = 500, message = "Unable to create download token because an unexpected error occurred.")
+ }
+ )
+ public Response createUiExtensionToken(@Context HttpServletRequest httpServletRequest) {
+ // only support access tokens when communicating over HTTPS
+ if (!httpServletRequest.isSecure()) {
+ throw new IllegalStateException("UI extension access tokens are only issued over HTTPS.");
+ }
+
+ // if not configuration for login, don't consider credentials
+ if (loginIdentityProvider == null) {
+ throw new IllegalStateException("UI extension access tokens not supported by this NiFi.");
+ }
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ if (user == null) {
+ throw new AccessDeniedException("Unable to determine user details.");
+ }
+
+ final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(user.getIdentity());
+
+ // generate otp for response
+ final String token = otpService.generateUiExtensionToken(authenticationToken);
+
+ // build the response
+ final URI uri = URI.create(generateResourceUri("access", "ui-extension-token"));
+ return generateCreatedResponse(uri, token).build();
}
/**
@@ -302,6 +407,9 @@ public class AccessResource extends ApplicationResource {
@Path("/token")
@ApiOperation(
value = "Creates a token for accessing the REST API via username/password",
+ notes = "The token returned is formatted as a JSON Web Token (JWT). The token is base64 encoded and comprised of three parts. The header, " +
+ "the body, and the signature. The expiration of the token is a contained within the body. The token can be used in the Authorization header " +
+ "in the format 'Authorization: Bearer <token>'.",
response = String.class
)
@ApiResponses(
@@ -399,7 +507,7 @@ public class AccessResource extends ApplicationResource {
private void authorizeProxyIfNecessary(final List<String> proxyChain) throws AuthenticationException {
if (proxyChain.size() > 1) {
try {
- userDetailsService.loadUserDetails(new NiFiAuthortizationRequestToken(proxyChain));
+ userDetailsService.loadUserDetails(new NiFiAuthorizationRequestToken(proxyChain));
} catch (final UsernameNotFoundException unfe) {
// if a username not found exception was thrown, the proxies were authorized and now
// we can issue a new token to the end user which they will use to identify themselves
@@ -427,6 +535,10 @@ public class AccessResource extends ApplicationResource {
this.jwtService = jwtService;
}
+ public void setOtpService(OtpService otpService) {
+ this.otpService = otpService;
+ }
+
public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) {
this.certificateExtractor = certificateExtractor;
}
@@ -435,7 +547,7 @@ public class AccessResource extends ApplicationResource {
this.certificateIdentityProvider = certificateIdentityProvider;
}
- public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthortizationRequestToken> userDetailsService) {
+ public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthorizationRequestToken> userDetailsService) {
this.userDetailsService = userDetailsService;
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/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 b38bca5..c9326b2 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
@@ -259,6 +259,7 @@
<property name="certificateIdentityProvider" ref="certificateIdentityProvider"/>
<property name="loginIdentityProvider" ref="loginIdentityProvider"/>
<property name="jwtService" ref="jwtService"/>
+ <property name="otpService" ref="otpService"/>
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
index 82fe73a..fe48490 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
@@ -204,7 +204,7 @@ public class AccessTokenEndpointTest {
* @throws Exception ex
*/
@Test
- public void testUnkownUser() throws Exception {
+ public void testUnknownUser() throws Exception {
String url = BASE_URL + "/access/token";
ClientResponse response = TOKEN_USER.testCreateToken(url, "not a real user", "not a real password");
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java
index a6f6aec..9b053f0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java
@@ -21,6 +21,7 @@ import com.ibm.icu.text.CharsetMatch;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.URI;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@@ -71,7 +72,19 @@ public class ContentViewerController extends HttpServlet {
final ServletContext servletContext = request.getServletContext();
final ContentAccess contentAccess = (ContentAccess) servletContext.getAttribute("nifi-content-access");
- final ContentRequestContext contentRequest = getContentRequest(request);
+ final ContentRequestContext contentRequest;
+ try {
+ contentRequest = getContentRequest(request);
+ } catch (final Exception e) {
+ request.setAttribute("title", "Error");
+ request.setAttribute("messages", "Unable to interpret content request.");
+
+ // forward to the error page
+ final ServletContext viewerContext = servletContext.getContext("/nifi");
+ viewerContext.getRequestDispatcher("/message").forward(request, response);
+ return;
+ }
+
if (contentRequest.getDataUri() == null) {
request.setAttribute("title", "Error");
request.setAttribute("messages", "The data reference must be specified.");
@@ -274,26 +287,41 @@ public class ContentViewerController extends HttpServlet {
* @return Get the content request context based on the specified request
*/
private ContentRequestContext getContentRequest(final HttpServletRequest request) {
+ final String ref = request.getParameter("ref");
+ final String clientId = request.getParameter("clientId");
+ final String proxiedEntitiesChain = request.getHeader("X-ProxiedEntitiesChain");
+
+ final URI refUri = URI.create(ref);
+ final String query = refUri.getQuery();
+ final String[] queryParameters = query.split("&");
+
+ String rawClusterNodeId = null;
+ for (int i = 0; i < queryParameters.length; i++) {
+ if (queryParameters[0].startsWith("clusterNodeId=")) {
+ rawClusterNodeId = StringUtils.substringAfterLast(queryParameters[0], "clusterNodeId=");
+ }
+ }
+ final String clusterNodeId = rawClusterNodeId;
+
return new ContentRequestContext() {
@Override
public String getDataUri() {
- return request.getParameter("ref");
+ return ref;
}
@Override
public String getClusterNodeId() {
- final String ref = request.getParameter("ref");
- return StringUtils.substringAfterLast(ref, "clusterNodeId=");
+ return clusterNodeId;
}
@Override
public String getClientId() {
- return request.getParameter("clientId");
+ return clientId;
}
@Override
public String getProxiedEntitiesChain() {
- return request.getHeader("X-ProxiedEntitiesChain");
+ return proxiedEntitiesChain;
}
};
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/WEB-INF/jsp/header.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/WEB-INF/jsp/header.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/WEB-INF/jsp/header.jsp
index 0f6fae3..c000284 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/WEB-INF/jsp/header.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/WEB-INF/jsp/header.jsp
@@ -25,14 +25,37 @@
<link href="../nifi/css/message-pane.css" rel="stylesheet" type="text/css" />
<link href="../nifi/css/message-page.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="../nifi/js/jquery/combo/jquery.combo.css" type="text/css" />
+ <link rel="stylesheet" href="../nifi/js/jquery/modal/jquery.modal.css" type="text/css" />
<link rel="stylesheet" href="../nifi/css/reset.css" type="text/css" />
<script type="text/javascript" src="../nifi/js/jquery/jquery-2.1.1.min.js"></script>
+ <script type="text/javascript" src="../nifi/js/jquery/jquery.center.js"></script>
<script type="text/javascript" src="../nifi/js/jquery/combo/jquery.combo.js"></script>
+ <script type="text/javascript" src="../nifi/js/jquery/modal/jquery.modal.js"></script>
<script type="text/javascript" src="../nifi/js/nf/nf-namespace.js"></script>
+ <script type="text/javascript" src="../nifi/js/nf/nf-storage.js"></script>
+ <script type="text/javascript" src="../nifi/js/nf/nf-ajax-setup.js"></script>
<script type="text/javascript" src="../nifi/js/nf/nf-universal-capture.js"></script>
<script type="text/javascript">
var $$ = $.noConflict(true);
$$(document).ready(function () {
+ // initialize the dialog
+ $$('#content-viewer-message-dialog').modal({
+ overlayBackground: false,
+ buttons: [{
+ buttonText: 'Ok',
+ handler: {
+ click: function () {
+ $$('#content-viewer-message-dialog').modal('hide');
+ }
+ }
+ }],
+ handler: {
+ close: function () {
+ $$('#content-viewer-message').text('');
+ }
+ }
+ });
+
var url = $$('#requestUrl').text();
var ref = $$('#ref').text();
@@ -74,12 +97,40 @@
currentLocation = option.value;
return;
}
-
+
// if the selection has changesd, reload the page
if (currentLocation !== option.value) {
- window.location.href = url + '?' + $$.param($$.extend({
- mode: option.value
- }, params));
+ // get an access token if necessary
+ var getAccessToken = $.Deferred(function (deferred) {
+ if (nf.Storage.hasItem('jwt')) {
+ $.ajax({
+ type: 'POST',
+ url: '../nifi-api/access/ui-extension-token'
+ }).done(function (token) {
+ deferred.resolve(token);
+ }).fail(function () {
+ $$('#content-viewer-message').text('Unable to generate a token to view the content.');
+ $$('#content-viewer-message-dialog').modal('show');
+ deferred.reject();
+ })
+ } else {
+ deferred.resolve('');
+ }
+ }).promise();
+
+ // reload as appropriate
+ getAccessToken.done(function (uiExtensionToken) {
+ var contentParameter = {
+ mode: option.value
+ };
+
+ // include the download token if applicable
+ if (typeof uiExtensionToken !== 'undefined' && uiExtensionToken !== null && $$.trim(uiExtensionToken) !== '') {
+ contentParameter['access_token'] = uiExtensionToken;
+ }
+
+ window.location.href = url + '?' + $$.param($$.extend(contentParameter, params));
+ });
}
}
});
@@ -91,6 +142,11 @@
<span id="clusterNodeId" class="hidden"><%= request.getParameter("clusterNodeId") == null ? "" : org.apache.nifi.util.EscapeUtils.escapeHtml(request.getParameter("clusterNodeId")) %></span>
<span id="mode" class="hidden"><%= request.getParameter("mode") == null ? "" : org.apache.nifi.util.EscapeUtils.escapeHtml(request.getParameter("mode")) %></span>
<span id="requestUrl" class="hidden"><%= org.apache.nifi.util.EscapeUtils.escapeHtml(request.getRequestURL().toString()) %></span>
+ <div id="content-viewer-message-dialog">
+ <div class="dialog-content" style="margin-top: -20px;">
+ <div id="content-viewer-message"></div>
+ </div>
+ </div>
<div id="view-as-label">View as</div>
<div id="view-as" class="pointer button-normal"></div>
<div id="content-filename"><span class="content-label">filename</span><%= request.getAttribute("filename") == null ? "" : org.apache.nifi.util.EscapeUtils.escapeHtml(request.getAttribute("filename").toString()) %></div>
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/css/main.css
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/css/main.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/css/main.css
index fe68b62..6e83c76 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/css/main.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/webapp/css/main.css
@@ -19,6 +19,14 @@
padding: 0;
}
+#content-viewer-message-dialog {
+ z-index: 1302;
+ display: none;
+ height: 225px;
+ width: 375px;
+ font-size: 62.5%
+}
+
#view-as-label {
position: absolute;
top: 72px;
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/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 d63f01e..0520ac8 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
@@ -27,7 +27,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.user.NiFiUser;
import org.apache.nifi.util.NiFiProperties;
-import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
import org.apache.nifi.web.security.user.NiFiUserUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -82,7 +82,7 @@ public abstract class NiFiAuthenticationFilter extends GenericFilterBean {
private void authenticate(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws IOException, ServletException {
String dnChain = null;
try {
- final NiFiAuthortizationRequestToken authenticated = attemptAuthentication(request);
+ final NiFiAuthorizationRequestToken authenticated = attemptAuthentication(request);
if (authenticated != null) {
dnChain = ProxiedEntitiesUtils.formatProxyDn(StringUtils.join(authenticated.getChain(), "><"));
@@ -125,7 +125,7 @@ public abstract class NiFiAuthenticationFilter extends GenericFilterBean {
* @return The NiFiAutorizationRequestToken used to later authorized the client
* @throws InvalidAuthenticationException If the request contained an authentication attempt, but could not authenticate
*/
- public abstract NiFiAuthortizationRequestToken attemptAuthentication(HttpServletRequest request);
+ public abstract NiFiAuthorizationRequestToken attemptAuthentication(HttpServletRequest request);
protected void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
if (log.isDebugEnabled()) {
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.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/NiFiAuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
index 0887901..e51a26e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
@@ -18,7 +18,7 @@ package org.apache.nifi.web.security;
import org.apache.nifi.web.security.token.NewAccountAuthorizationRequestToken;
import org.apache.nifi.web.security.token.NewAccountAuthorizationToken;
-import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
import org.apache.nifi.web.security.token.NiFiAuthorizationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
@@ -32,15 +32,15 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
*/
public class NiFiAuthenticationProvider implements AuthenticationProvider {
- private final AuthenticationUserDetailsService<NiFiAuthortizationRequestToken> userDetailsService;
+ private final AuthenticationUserDetailsService<NiFiAuthorizationRequestToken> userDetailsService;
- public NiFiAuthenticationProvider(final AuthenticationUserDetailsService<NiFiAuthortizationRequestToken> userDetailsService) {
+ public NiFiAuthenticationProvider(final AuthenticationUserDetailsService<NiFiAuthorizationRequestToken> userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
- final NiFiAuthortizationRequestToken request = (NiFiAuthortizationRequestToken) authentication;
+ final NiFiAuthorizationRequestToken request = (NiFiAuthorizationRequestToken) authentication;
try {
// defer to the nifi user details service to authorize the user
@@ -67,7 +67,7 @@ public class NiFiAuthenticationProvider implements AuthenticationProvider {
@Override
public boolean supports(Class<?> authentication) {
- return NiFiAuthortizationRequestToken.class.isAssignableFrom(authentication);
+ return NiFiAuthorizationRequestToken.class.isAssignableFrom(authentication);
}
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.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/anonymous/NiFiAnonymousUserFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java
index e67ed2c..05c5fb8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java
@@ -58,8 +58,11 @@ public class NiFiAnonymousUserFilter extends AnonymousAuthenticationFilter {
user.getAuthorities().addAll(EnumSet.allOf(Authority.class));
}
- // only create an authentication token if the anonymous user has some authorities
- if (!user.getAuthorities().isEmpty()) {
+ // only create an authentication token if the anonymous user has some authorities or they are accessing a ui
+ // extension. ui extensions have run this security filter but we shouldn't require authentication/authorization
+ // when accessing static resources like images, js, and css. authentication/authorization is required when
+ // interacting with nifi however and that will be verified in the NiFiWebContext or NiFiWebConfigurationContext
+ if (!user.getAuthorities().isEmpty() || !request.getContextPath().startsWith("/nifi-api")) {
NiFiUserDetails userDetails = new NiFiUserDetails(user);
// get the granted authorities
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.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/authorization/NiFiAuthorizationService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java
index 75c01bf..dd87cfa 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java
@@ -30,7 +30,7 @@ import org.apache.nifi.user.NiFiUser;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.UntrustedProxyException;
import org.apache.nifi.web.security.user.NiFiUserDetails;
-import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
@@ -44,7 +44,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
* UserDetailsService that will verify user identity and grant user authorities.
*/
-public class NiFiAuthorizationService implements AuthenticationUserDetailsService<NiFiAuthortizationRequestToken> {
+public class NiFiAuthorizationService implements AuthenticationUserDetailsService<NiFiAuthorizationRequestToken> {
private static final Logger logger = LoggerFactory.getLogger(NiFiAuthorizationService.class);
@@ -63,7 +63,7 @@ public class NiFiAuthorizationService implements AuthenticationUserDetailsServic
* @throws org.springframework.dao.DataAccessException ex
*/
@Override
- public synchronized UserDetails loadUserDetails(NiFiAuthortizationRequestToken request) throws UsernameNotFoundException, DataAccessException {
+ public synchronized UserDetails loadUserDetails(NiFiAuthorizationRequestToken request) throws UsernameNotFoundException, DataAccessException {
NiFiUserDetails userDetails = null;
final List<String> chain = new ArrayList<>(request.getChain());
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/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 faf3cde..e13fcea 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
@@ -20,7 +20,7 @@ import io.jsonwebtoken.JwtException;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.web.security.NiFiAuthenticationFilter;
import org.apache.nifi.web.security.token.NewAccountAuthorizationRequestToken;
-import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
import org.apache.nifi.web.security.user.NewAccountRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,7 +40,7 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
private JwtService jwtService;
@Override
- public NiFiAuthortizationRequestToken attemptAuthentication(final HttpServletRequest request) {
+ public NiFiAuthorizationRequestToken attemptAuthentication(final HttpServletRequest request) {
// only suppport jwt login when running securely
if (!request.isSecure()) {
return null;
@@ -68,7 +68,7 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
if (isNewAccountRequest(request)) {
return new NewAccountAuthorizationRequestToken(new NewAccountRequest(Arrays.asList(jwtPrincipal), getJustification(request)));
} else {
- return new NiFiAuthortizationRequestToken(Arrays.asList(jwtPrincipal));
+ return new NiFiAuthorizationRequestToken(Arrays.asList(jwtPrincipal));
}
} catch (JwtException e) {
throw new InvalidAuthenticationException(e.getMessage(), e);
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationException.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/otp/OtpAuthenticationException.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationException.java
new file mode 100644
index 0000000..ee2d573
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationException.java
@@ -0,0 +1,34 @@
+/*
+ * 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.otp;
+
+import org.springframework.security.core.AuthenticationException;
+
+/**
+ * Thrown if a one time use token fails authentication.
+ */
+public class OtpAuthenticationException extends AuthenticationException {
+
+ public OtpAuthenticationException(String msg) {
+ super(msg);
+ }
+
+ public OtpAuthenticationException(String msg, Throwable t) {
+ super(msg, t);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationFilter.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/otp/OtpAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationFilter.java
new file mode 100644
index 0000000..9c44298
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationFilter.java
@@ -0,0 +1,96 @@
+/*
+ * 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.otp;
+
+import org.apache.nifi.web.security.InvalidAuthenticationException;
+import org.apache.nifi.web.security.NiFiAuthenticationFilter;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Arrays;
+import java.util.regex.Pattern;
+
+/**
+ */
+public class OtpAuthenticationFilter extends NiFiAuthenticationFilter {
+
+ private static final Logger logger = LoggerFactory.getLogger(OtpAuthenticationFilter.class);
+
+ private static final Pattern PROVENANCE_DOWNLOAD_PATTERN =
+ Pattern.compile("/controller/provenance/events/[0-9]+/content/(?:(?:output)|(?:input))");
+ private static final Pattern QUEUE_DOWNLOAD_PATTERN =
+ Pattern.compile("/controller/process-groups/(?:(?:root)|(?:[a-f0-9\\-]{36}))/connections/[a-f0-9\\-]{36}/flowfiles/[a-f0-9\\-]{36}/content");
+ private static final Pattern TEMPLATE_DOWNLOAD_PATTERN =
+ Pattern.compile("/controller/templates/[a-f0-9\\-]{36}");
+
+ protected static final String ACCESS_TOKEN = "access_token";
+
+ private OtpService otpService;
+
+ @Override
+ public NiFiAuthorizationRequestToken attemptAuthentication(final HttpServletRequest request) {
+ // only support otp login when running securely
+ if (!request.isSecure()) {
+ return null;
+ }
+
+ // get the accessToken out of the query string
+ final String accessToken = request.getParameter(ACCESS_TOKEN);
+
+ // if there is no authorization header, we don't know the user
+ if (accessToken == null) {
+ return null;
+ } else {
+ if (otpService == null) {
+ throw new InvalidAuthenticationException("NiFi is not configured to support username/password logins.");
+ }
+
+ try {
+ String identity = null;
+ if (request.getContextPath().equals("/nifi-api")) {
+ if (isDownloadRequest(request.getPathInfo())) {
+ // handle download requests
+ identity = otpService.getAuthenticationFromDownloadToken(accessToken);
+ }
+ } else {
+ // handle requests to other context paths (other UI extensions)
+ identity = otpService.getAuthenticationFromUiExtensionToken(accessToken);
+ }
+
+ // the path is a support path for otp tokens
+ if (identity == null) {
+ return null;
+ }
+
+ return new NiFiAuthorizationRequestToken(Arrays.asList(identity));
+ } catch (final OtpAuthenticationException oae) {
+ throw new InvalidAuthenticationException(oae.getMessage(), oae);
+ }
+ }
+ }
+
+ private boolean isDownloadRequest(final String pathInfo) {
+ return PROVENANCE_DOWNLOAD_PATTERN.matcher(pathInfo).matches() || QUEUE_DOWNLOAD_PATTERN.matcher(pathInfo).matches() || TEMPLATE_DOWNLOAD_PATTERN.matcher(pathInfo).matches();
+ }
+
+ public void setOtpService(OtpService otpService) {
+ this.otpService = otpService;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpService.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/otp/OtpService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpService.java
new file mode 100644
index 0000000..cd5a90c
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpService.java
@@ -0,0 +1,219 @@
+/*
+ * 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.otp;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.nifi.web.security.token.OtpAuthenticationToken;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * OtpService is a service for generating and verifying one time password tokens.
+ */
+public class OtpService {
+
+ private static final Logger logger = LoggerFactory.getLogger(OtpService.class);
+
+ private static final String HMAC_SHA256 = "HmacSHA256";
+
+ // protected for testing purposes
+ protected static final int MAX_CACHE_SOFT_LIMIT = 100;
+
+ private final Cache<CacheKey, String> downloadTokenCache;
+ private final Cache<CacheKey, String> uiExtensionCache;
+
+ /**
+ * Creates a new OtpService with an expiration of 5 minutes.
+ */
+ public OtpService() {
+ this(5, TimeUnit.MINUTES);
+ }
+
+ /**
+ * Creates a new OtpService.
+ *
+ * @param duration The expiration duration
+ * @param units The expiration units
+ * @throws NullPointerException If units is null
+ * @throws IllegalArgumentException If duration is negative
+ */
+ public OtpService(final int duration, final TimeUnit units) {
+ downloadTokenCache = CacheBuilder.newBuilder().expireAfterWrite(duration, units).build();
+ uiExtensionCache = CacheBuilder.newBuilder().expireAfterWrite(duration, units).build();
+ }
+
+ /**
+ * Generates a download token for the specified authentication.
+ *
+ * @param authenticationToken The authentication
+ * @return The one time use download token
+ */
+ public String generateDownloadToken(final OtpAuthenticationToken authenticationToken) {
+ return generateToken(downloadTokenCache.asMap(), authenticationToken);
+ }
+
+ /**
+ * Gets the authenticated identity from the specified one time use download token. This method will not return null.
+ *
+ * @param token The one time use download token
+ * @return The authenticated identity
+ * @throws OtpAuthenticationException When the specified token does not correspond to an authenticated identity
+ */
+ public String getAuthenticationFromDownloadToken(final String token) throws OtpAuthenticationException {
+ return getAuthenticationFromToken(downloadTokenCache.asMap(), token);
+ }
+
+ /**
+ * Generates a UI extension token for the specified authentication.
+ *
+ * @param authenticationToken The authentication
+ * @return The one time use UI extension token
+ */
+ public String generateUiExtensionToken(final OtpAuthenticationToken authenticationToken) {
+ return generateToken(uiExtensionCache.asMap(), authenticationToken);
+ }
+
+ /**
+ * Gets the authenticated identity from the specified one time use UI extension token. This method will not return null.
+ *
+ * @param token The one time use UI extension token
+ * @return The authenticated identity
+ * @throws OtpAuthenticationException When the specified token does not correspond to an authenticated identity
+ */
+ public String getAuthenticationFromUiExtensionToken(final String token) throws OtpAuthenticationException {
+ return getAuthenticationFromToken(uiExtensionCache.asMap(), token);
+ }
+
+ /**
+ * Generates a token and stores it in the specified cache.
+ *
+ * @param cache The cache
+ * @param authenticationToken The authentication
+ * @return The one time use token
+ */
+ private String generateToken(final ConcurrentMap<CacheKey, String> cache, final OtpAuthenticationToken authenticationToken) {
+ if (cache.size() >= MAX_CACHE_SOFT_LIMIT) {
+ throw new IllegalStateException("The maximum number of single use tokens have been issued.");
+ }
+
+ // hash the authentication and build a cache key
+ final CacheKey cacheKey = new CacheKey(hash(authenticationToken));
+
+ // store the token unless the token is already stored which should not update it's original timestamp
+ cache.putIfAbsent(cacheKey, authenticationToken.getName());
+
+ // return the token
+ return cacheKey.getToken();
+ }
+
+ /**
+ * Gets the corresponding authentication for the specified one time use token. The specified token will be removed.
+ *
+ * @param cache The cache
+ * @param token The one time use token
+ * @return The authenticated identity
+ */
+ private String getAuthenticationFromToken(final ConcurrentMap<CacheKey, String> cache, final String token) throws OtpAuthenticationException {
+ final String authenticatedUser = cache.remove(new CacheKey(token));
+ if (authenticatedUser == null) {
+ throw new OtpAuthenticationException("Unable to validate the access token.");
+ }
+
+ return authenticatedUser;
+ }
+
+ /**
+ * Hashes the specified authentication token. The resulting value will be used as the one time use token.
+ *
+ * @param authenticationToken the authentication token
+ * @return the one time use token
+ */
+ private String hash(final OtpAuthenticationToken authenticationToken) {
+ try {
+ // input is the user identity and timestamp
+ final String input = authenticationToken.getName() + "-" + System.nanoTime();
+
+ // create the secret using secure random
+ final SecureRandom secureRandom = new SecureRandom();
+ final byte[] randomBytes = new byte[32];
+ secureRandom.nextBytes(randomBytes);
+ final SecretKeySpec secret = new SecretKeySpec(randomBytes, HMAC_SHA256); // 256 bit
+
+ // hash the input
+ final Mac hmacSha256 = Mac.getInstance(HMAC_SHA256);
+ hmacSha256.init(secret);
+ final byte[] output = hmacSha256.doFinal(input.getBytes(StandardCharsets.UTF_8));
+
+ // return the result as a base 64 string
+ return Base64.encodeBase64URLSafeString(output);
+ } catch (final NoSuchAlgorithmException | InvalidKeyException e) {
+ final String errorMessage = "There was an error generating the OTP";
+ logger.error(errorMessage, e);
+ throw new IllegalStateException("Unable to generate single use token.");
+ }
+ }
+
+ /**
+ * Key for the cache. Necessary to override the default String.equals() to utilize MessageDigest.isEquals() to prevent timing attacks.
+ */
+ private static class CacheKey {
+ final String token;
+
+ public CacheKey(String token) {
+ this.token = token;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final CacheKey otherCacheKey = (CacheKey) o;
+ return MessageDigest.isEqual(token.getBytes(StandardCharsets.UTF_8), otherCacheKey.token.getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public int hashCode() {
+ return token.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "CacheKey{token ending in '..." + token.substring(token.length() - 6) + "'}";
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthorizationRequestToken.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/NewAccountAuthorizationRequestToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthorizationRequestToken.java
index 35c371d..693d420 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthorizationRequestToken.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthorizationRequestToken.java
@@ -21,7 +21,7 @@ import org.apache.nifi.web.security.user.NewAccountRequest;
/**
* An authentication token that is used as an authorization request when submitting a new account.
*/
-public class NewAccountAuthorizationRequestToken extends NiFiAuthortizationRequestToken {
+public class NewAccountAuthorizationRequestToken extends NiFiAuthorizationRequestToken {
final NewAccountRequest newAccountRequest;
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthorizationRequestToken.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/NiFiAuthorizationRequestToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthorizationRequestToken.java
new file mode 100644
index 0000000..c20aaf3
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthorizationRequestToken.java
@@ -0,0 +1,54 @@
+/*
+ * 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.token;
+
+import java.util.Collections;
+import java.util.List;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+
+/**
+ * An authentication token that is used as an authorization request. The request has already been authenticated and is now going to be authorized.
+ * The request chain is specified during creation and is used authorize the user(s).
+ */
+public class NiFiAuthorizationRequestToken extends AbstractAuthenticationToken {
+
+ private final List<String> chain;
+
+ public NiFiAuthorizationRequestToken(final List<String> chain) {
+ super(null);
+ this.chain = chain;
+ }
+
+ @Override
+ public Object getCredentials() {
+ return null;
+ }
+
+ @Override
+ public Object getPrincipal() {
+ return chain;
+ }
+
+ public List<String> getChain() {
+ return Collections.unmodifiableList(chain);
+ }
+
+ @Override
+ public final void setAuthenticated(boolean authenticated) {
+ throw new IllegalArgumentException("Cannot change the authenticated state.");
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthortizationRequestToken.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/NiFiAuthortizationRequestToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthortizationRequestToken.java
deleted file mode 100644
index a1459a4..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthortizationRequestToken.java
+++ /dev/null
@@ -1,54 +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.web.security.token;
-
-import java.util.Collections;
-import java.util.List;
-import org.springframework.security.authentication.AbstractAuthenticationToken;
-
-/**
- * An authentication token that is used as an authorization request. The request has already been authenticated and is now going to be authorized.
- * The request chain is specified during creation and is used authorize the user(s).
- */
-public class NiFiAuthortizationRequestToken extends AbstractAuthenticationToken {
-
- private final List<String> chain;
-
- public NiFiAuthortizationRequestToken(final List<String> chain) {
- super(null);
- this.chain = chain;
- }
-
- @Override
- public Object getCredentials() {
- return null;
- }
-
- @Override
- public Object getPrincipal() {
- return chain;
- }
-
- public List<String> getChain() {
- return Collections.unmodifiableList(chain);
- }
-
- @Override
- public final void setAuthenticated(boolean authenticated) {
- throw new IllegalArgumentException("Cannot change the authenticated state.");
- }
-}
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/OtpAuthenticationToken.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/OtpAuthenticationToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/OtpAuthenticationToken.java
new file mode 100644
index 0000000..fc51912
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/OtpAuthenticationToken.java
@@ -0,0 +1,56 @@
+/*
+ * 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.token;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+
+/**
+ * This is an Authentication Token for logging in. Once a user is authenticated, they can be issued an ID token.
+ */
+public class OtpAuthenticationToken extends AbstractAuthenticationToken {
+
+ private final String identity;
+
+ /**
+ * Creates a representation of the otp authentication token for a user.
+ *
+ * @param identity The unique identifier for this user
+ */
+ public OtpAuthenticationToken(final String identity) {
+ super(null);
+ setAuthenticated(true);
+ this.identity = identity;
+ }
+
+ @Override
+ public Object getCredentials() {
+ return null;
+ }
+
+ @Override
+ public Object getPrincipal() {
+ return identity;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("OtpAuthenticationToken for ")
+ .append(getName())
+ .toString();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/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 2c792f6..019a53c 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
@@ -24,7 +24,7 @@ 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.NewAccountAuthorizationRequestToken;
-import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
+import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
import org.apache.nifi.web.security.user.NewAccountRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,7 +40,7 @@ public class X509AuthenticationFilter extends NiFiAuthenticationFilter {
private X509IdentityProvider certificateIdentityProvider;
@Override
- public NiFiAuthortizationRequestToken attemptAuthentication(final HttpServletRequest request) {
+ public NiFiAuthorizationRequestToken attemptAuthentication(final HttpServletRequest request) {
// only suppport x509 login when running securely
if (!request.isSecure()) {
return null;
@@ -64,7 +64,7 @@ public class X509AuthenticationFilter extends NiFiAuthenticationFilter {
if (isNewAccountRequest(request)) {
return new NewAccountAuthorizationRequestToken(new NewAccountRequest(proxyChain, getJustification(request)));
} else {
- return new NiFiAuthortizationRequestToken(proxyChain);
+ return new NiFiAuthorizationRequestToken(proxyChain);
}
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/a8edab2e/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
index d079293..44a93af 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
@@ -49,7 +49,10 @@
<bean id="jwtService" class="org.apache.nifi.web.security.jwt.JwtService">
<constructor-arg ref="userService"/>
</bean>
-
+
+ <!-- otp service -->
+ <bean id="otpService" class="org.apache.nifi.web.security.otp.OtpService"/>
+
<!-- login identity provider -->
<bean id="loginIdentityProvider" class="org.apache.nifi.web.security.spring.LoginIdentityProviderFactoryBean">
<property name="properties" ref="nifiProperties"/>