mirror of https://github.com/apache/nifi.git
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 <bbende@apache.org>
This commit is contained in:
parent
86ab4428f0
commit
a8edab2e79
|
@ -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.JwtAuthenticationFilter;
|
||||||
import org.apache.nifi.web.security.jwt.JwtService;
|
import org.apache.nifi.web.security.jwt.JwtService;
|
||||||
import org.apache.nifi.web.security.node.NodeAuthorizedUserFilter;
|
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.X509AuthenticationFilter;
|
||||||
import org.apache.nifi.web.security.x509.X509CertificateExtractor;
|
import org.apache.nifi.web.security.x509.X509CertificateExtractor;
|
||||||
import org.apache.nifi.web.security.x509.X509IdentityProvider;
|
import org.apache.nifi.web.security.x509.X509IdentityProvider;
|
||||||
|
@ -54,12 +56,14 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
private AuthenticationUserDetailsService userDetailsService;
|
private AuthenticationUserDetailsService userDetailsService;
|
||||||
private JwtService jwtService;
|
private JwtService jwtService;
|
||||||
|
private OtpService otpService;
|
||||||
private X509CertificateExtractor certificateExtractor;
|
private X509CertificateExtractor certificateExtractor;
|
||||||
private X509IdentityProvider certificateIdentityProvider;
|
private X509IdentityProvider certificateIdentityProvider;
|
||||||
private LoginIdentityProvider loginIdentityProvider;
|
private LoginIdentityProvider loginIdentityProvider;
|
||||||
|
|
||||||
private NodeAuthorizedUserFilter nodeAuthorizedUserFilter;
|
private NodeAuthorizedUserFilter nodeAuthorizedUserFilter;
|
||||||
private JwtAuthenticationFilter jwtAuthenticationFilter;
|
private JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||||
|
private OtpAuthenticationFilter otpAuthenticationFilter;
|
||||||
private X509AuthenticationFilter x509AuthenticationFilter;
|
private X509AuthenticationFilter x509AuthenticationFilter;
|
||||||
private NiFiAnonymousUserFilter anonymousAuthenticationFilter;
|
private NiFiAnonymousUserFilter anonymousAuthenticationFilter;
|
||||||
|
|
||||||
|
@ -69,9 +73,12 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(WebSecurity webSecurity) throws Exception {
|
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
|
webSecurity
|
||||||
.ignoring()
|
.ignoring()
|
||||||
.antMatchers("/access/**");
|
.antMatchers("/access", "/access/config", "/access/token");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -95,6 +102,9 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
|
||||||
|
|
||||||
// jwt
|
// jwt
|
||||||
http.addFilterAfter(jwtFilterBean(), AnonymousAuthenticationFilter.class);
|
http.addFilterAfter(jwtFilterBean(), AnonymousAuthenticationFilter.class);
|
||||||
|
|
||||||
|
// otp
|
||||||
|
http.addFilterAfter(otpFilterBean(), AnonymousAuthenticationFilter.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
@ -135,6 +145,20 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
|
||||||
return jwtAuthenticationFilter;
|
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
|
@Bean
|
||||||
public X509AuthenticationFilter x509FilterBean() throws Exception {
|
public X509AuthenticationFilter x509FilterBean() throws Exception {
|
||||||
if (x509AuthenticationFilter == null) {
|
if (x509AuthenticationFilter == null) {
|
||||||
|
@ -157,7 +181,7 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthortizationRequestToken> userDetailsService) {
|
public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthorizationRequestToken> userDetailsService) {
|
||||||
this.userDetailsService = userDetailsService;
|
this.userDetailsService = userDetailsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,6 +200,11 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
|
||||||
this.jwtService = jwtService;
|
this.jwtService = jwtService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setOtpService(OtpService otpService) {
|
||||||
|
this.otpService = otpService;
|
||||||
|
}
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public void setLoginIdentityProvider(LoginIdentityProvider loginIdentityProvider) {
|
public void setLoginIdentityProvider(LoginIdentityProvider loginIdentityProvider) {
|
||||||
this.loginIdentityProvider = loginIdentityProvider;
|
this.loginIdentityProvider = loginIdentityProvider;
|
||||||
|
|
|
@ -17,7 +17,28 @@
|
||||||
package org.apache.nifi.web;
|
package org.apache.nifi.web;
|
||||||
|
|
||||||
import com.sun.jersey.api.client.ClientResponse;
|
import com.sun.jersey.api.client.ClientResponse;
|
||||||
|
import com.sun.jersey.api.client.ClientResponse.Status;
|
||||||
import com.sun.jersey.core.util.MultivaluedMapImpl;
|
import com.sun.jersey.core.util.MultivaluedMapImpl;
|
||||||
|
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;
|
||||||
|
import org.apache.nifi.cluster.node.Node;
|
||||||
|
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.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.io.Serializable;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
@ -27,23 +48,6 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
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.cluster.manager.NodeResponse;
|
|
||||||
import org.apache.nifi.cluster.manager.exception.UnknownNodeException;
|
|
||||||
import org.apache.nifi.cluster.manager.impl.WebClusterManager;
|
|
||||||
import org.apache.nifi.cluster.node.Node;
|
|
||||||
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.util.WebUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -54,17 +58,16 @@ public class StandardNiFiContentAccess implements ContentAccess {
|
||||||
public static final String CLIENT_ID_PARAM = "clientId";
|
public static final String CLIENT_ID_PARAM = "clientId";
|
||||||
|
|
||||||
private static final Pattern FLOWFILE_CONTENT_URI_PATTERN = Pattern
|
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
|
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 NiFiProperties properties;
|
||||||
private NiFiServiceFacade serviceFacade;
|
private NiFiServiceFacade serviceFacade;
|
||||||
private WebClusterManager clusterManager;
|
private WebClusterManager clusterManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@PreAuthorize("hasRole('ROLE_PROVENANCE')")
|
|
||||||
public DownloadableContent getContent(final ContentRequestContext request) {
|
public DownloadableContent getContent(final ContentRequestContext request) {
|
||||||
// if clustered, send request to cluster manager
|
// if clustered, send request to cluster manager
|
||||||
if (properties.isClusterManager()) {
|
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
|
// get the target node and ensure it exists
|
||||||
final Node targetNode = clusterManager.getNode(request.getClusterNodeId());
|
final Node targetNode = clusterManager.getNode(request.getClusterNodeId());
|
||||||
if (targetNode == null) {
|
if (targetNode == null) {
|
||||||
|
@ -113,6 +121,16 @@ public class StandardNiFiContentAccess implements ContentAccess {
|
||||||
final ClientResponse clientResponse = nodeResponse.getClientResponse();
|
final ClientResponse clientResponse = nodeResponse.getClientResponse();
|
||||||
final MultivaluedMap<String, String> responseHeaders = clientResponse.getHeaders();
|
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
|
// get the file name
|
||||||
final String contentDisposition = responseHeaders.getFirst("Content-Disposition");
|
final String contentDisposition = responseHeaders.getFirst("Content-Disposition");
|
||||||
final String filename = StringUtils.substringBetween(contentDisposition, "filename=\"", "\"");
|
final String filename = StringUtils.substringBetween(contentDisposition, "filename=\"", "\"");
|
||||||
|
@ -140,7 +158,7 @@ public class StandardNiFiContentAccess implements ContentAccess {
|
||||||
final String connectionId = flowFileMatcher.group(2);
|
final String connectionId = flowFileMatcher.group(2);
|
||||||
final String flowfileId = flowFileMatcher.group(3);
|
final String flowfileId = flowFileMatcher.group(3);
|
||||||
|
|
||||||
return serviceFacade.getContent(groupId, connectionId, flowfileId, dataUri);
|
return getFlowFileContent(groupId, connectionId, flowfileId, dataUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
// provenance event content
|
// provenance event content
|
||||||
|
@ -150,7 +168,7 @@ public class StandardNiFiContentAccess implements ContentAccess {
|
||||||
final Long eventId = Long.parseLong(provenanceMatcher.group(1));
|
final Long eventId = Long.parseLong(provenanceMatcher.group(1));
|
||||||
final ContentDirection direction = ContentDirection.valueOf(provenanceMatcher.group(2).toUpperCase());
|
final ContentDirection direction = ContentDirection.valueOf(provenanceMatcher.group(2).toUpperCase());
|
||||||
|
|
||||||
return serviceFacade.getContent(eventId, dataUri, direction);
|
return getProvenanceEventContent(eventId, dataUri, direction);
|
||||||
} catch (final IllegalArgumentException iae) {
|
} catch (final IllegalArgumentException iae) {
|
||||||
throw new IllegalArgumentException("The specified data reference URI is not valid.");
|
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) {
|
public void setProperties(NiFiProperties properties) {
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,6 +86,7 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
|
||||||
private AuditService auditService;
|
private AuditService auditService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
|
||||||
public ControllerService getControllerService(String serviceIdentifier) {
|
public ControllerService getControllerService(String serviceIdentifier) {
|
||||||
return controllerServiceLookup.getControllerService(serviceIdentifier);
|
return controllerServiceLookup.getControllerService(serviceIdentifier);
|
||||||
}
|
}
|
||||||
|
@ -157,6 +158,7 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
|
||||||
public String getCurrentUserDn() {
|
public String getCurrentUserDn() {
|
||||||
String userIdentity = NiFiUser.ANONYMOUS_USER_IDENTITY;
|
String userIdentity = NiFiUser.ANONYMOUS_USER_IDENTITY;
|
||||||
|
|
||||||
|
@ -169,6 +171,7 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
|
||||||
public String getCurrentUserName() {
|
public String getCurrentUserName() {
|
||||||
String userName = NiFiUser.ANONYMOUS_USER_IDENTITY;
|
String userName = NiFiUser.ANONYMOUS_USER_IDENTITY;
|
||||||
|
|
||||||
|
@ -181,6 +184,7 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
|
||||||
public ComponentDetails getComponentDetails(final NiFiWebRequestContext requestContext) throws ResourceNotFoundException, ClusterRequestException {
|
public ComponentDetails getComponentDetails(final NiFiWebRequestContext requestContext) throws ResourceNotFoundException, ClusterRequestException {
|
||||||
final String id = requestContext.getId();
|
final String id = requestContext.getId();
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,7 @@ public class StandardNiFiWebContext implements NiFiWebContext {
|
||||||
private AuditService auditService;
|
private AuditService auditService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
|
||||||
public ControllerService getControllerService(String serviceIdentifier) {
|
public ControllerService getControllerService(String serviceIdentifier) {
|
||||||
return controllerServiceLookup.getControllerService(serviceIdentifier);
|
return controllerServiceLookup.getControllerService(serviceIdentifier);
|
||||||
}
|
}
|
||||||
|
@ -128,6 +129,7 @@ public class StandardNiFiWebContext implements NiFiWebContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
|
||||||
public String getCurrentUserDn() {
|
public String getCurrentUserDn() {
|
||||||
String userIdentity = NiFiUser.ANONYMOUS_USER_IDENTITY;
|
String userIdentity = NiFiUser.ANONYMOUS_USER_IDENTITY;
|
||||||
|
|
||||||
|
@ -140,6 +142,7 @@ public class StandardNiFiWebContext implements NiFiWebContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
|
||||||
public String getCurrentUserName() {
|
public String getCurrentUserName() {
|
||||||
String userName = NiFiUser.ANONYMOUS_USER_IDENTITY;
|
String userName = NiFiUser.ANONYMOUS_USER_IDENTITY;
|
||||||
|
|
||||||
|
@ -152,6 +155,7 @@ public class StandardNiFiWebContext implements NiFiWebContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
|
||||||
public ProcessorInfo getProcessor(final NiFiWebContextConfig config) throws ResourceNotFoundException, ClusterRequestException {
|
public ProcessorInfo getProcessor(final NiFiWebContextConfig config) throws ResourceNotFoundException, ClusterRequestException {
|
||||||
|
|
||||||
final Revision revision = config.getRevision();
|
final Revision revision = config.getRevision();
|
||||||
|
|
|
@ -24,6 +24,7 @@ import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
import io.jsonwebtoken.JwtException;
|
import io.jsonwebtoken.JwtException;
|
||||||
|
import org.apache.nifi.user.NiFiUser;
|
||||||
import org.apache.nifi.util.NiFiProperties;
|
import org.apache.nifi.util.NiFiProperties;
|
||||||
import com.wordnik.swagger.annotations.Api;
|
import com.wordnik.swagger.annotations.Api;
|
||||||
import com.wordnik.swagger.annotations.ApiOperation;
|
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.UntrustedProxyException;
|
||||||
import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter;
|
import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter;
|
||||||
import org.apache.nifi.web.security.jwt.JwtService;
|
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.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.X509CertificateExtractor;
|
||||||
import org.apache.nifi.web.security.x509.X509IdentityProvider;
|
import org.apache.nifi.web.security.x509.X509IdentityProvider;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -92,8 +96,9 @@ public class AccessResource extends ApplicationResource {
|
||||||
private X509CertificateExtractor certificateExtractor;
|
private X509CertificateExtractor certificateExtractor;
|
||||||
private X509IdentityProvider certificateIdentityProvider;
|
private X509IdentityProvider certificateIdentityProvider;
|
||||||
private JwtService jwtService;
|
private JwtService jwtService;
|
||||||
|
private OtpService otpService;
|
||||||
|
|
||||||
private AuthenticationUserDetailsService<NiFiAuthortizationRequestToken> userDetailsService;
|
private AuthenticationUserDetailsService<NiFiAuthorizationRequestToken> userDetailsService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the access configuration for this NiFi.
|
* 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
|
* @throws AuthenticationException if the proxy chain is not authorized
|
||||||
*/
|
*/
|
||||||
private UserDetails checkAuthorization(final List<String> proxyChain) throws AuthenticationException {
|
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")
|
@Path("/token")
|
||||||
@ApiOperation(
|
@ApiOperation(
|
||||||
value = "Creates a token for accessing the REST API via username/password",
|
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
|
response = String.class
|
||||||
)
|
)
|
||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
|
@ -399,7 +507,7 @@ public class AccessResource extends ApplicationResource {
|
||||||
private void authorizeProxyIfNecessary(final List<String> proxyChain) throws AuthenticationException {
|
private void authorizeProxyIfNecessary(final List<String> proxyChain) throws AuthenticationException {
|
||||||
if (proxyChain.size() > 1) {
|
if (proxyChain.size() > 1) {
|
||||||
try {
|
try {
|
||||||
userDetailsService.loadUserDetails(new NiFiAuthortizationRequestToken(proxyChain));
|
userDetailsService.loadUserDetails(new NiFiAuthorizationRequestToken(proxyChain));
|
||||||
} catch (final UsernameNotFoundException unfe) {
|
} catch (final UsernameNotFoundException unfe) {
|
||||||
// if a username not found exception was thrown, the proxies were authorized and now
|
// 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
|
// 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;
|
this.jwtService = jwtService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOtpService(OtpService otpService) {
|
||||||
|
this.otpService = otpService;
|
||||||
|
}
|
||||||
|
|
||||||
public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) {
|
public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) {
|
||||||
this.certificateExtractor = certificateExtractor;
|
this.certificateExtractor = certificateExtractor;
|
||||||
}
|
}
|
||||||
|
@ -435,7 +547,7 @@ public class AccessResource extends ApplicationResource {
|
||||||
this.certificateIdentityProvider = certificateIdentityProvider;
|
this.certificateIdentityProvider = certificateIdentityProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthortizationRequestToken> userDetailsService) {
|
public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthorizationRequestToken> userDetailsService) {
|
||||||
this.userDetailsService = userDetailsService;
|
this.userDetailsService = userDetailsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -259,6 +259,7 @@
|
||||||
<property name="certificateIdentityProvider" ref="certificateIdentityProvider"/>
|
<property name="certificateIdentityProvider" ref="certificateIdentityProvider"/>
|
||||||
<property name="loginIdentityProvider" ref="loginIdentityProvider"/>
|
<property name="loginIdentityProvider" ref="loginIdentityProvider"/>
|
||||||
<property name="jwtService" ref="jwtService"/>
|
<property name="jwtService" ref="jwtService"/>
|
||||||
|
<property name="otpService" ref="otpService"/>
|
||||||
<property name="userDetailsService" ref="userDetailsService"/>
|
<property name="userDetailsService" ref="userDetailsService"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
|
|
@ -204,7 +204,7 @@ public class AccessTokenEndpointTest {
|
||||||
* @throws Exception ex
|
* @throws Exception ex
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUnkownUser() throws Exception {
|
public void testUnknownUser() throws Exception {
|
||||||
String url = BASE_URL + "/access/token";
|
String url = BASE_URL + "/access/token";
|
||||||
|
|
||||||
ClientResponse response = TOKEN_USER.testCreateToken(url, "not a real user", "not a real password");
|
ClientResponse response = TOKEN_USER.testCreateToken(url, "not a real user", "not a real password");
|
||||||
|
|
|
@ -21,6 +21,7 @@ import com.ibm.icu.text.CharsetMatch;
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
|
@ -71,7 +72,19 @@ public class ContentViewerController extends HttpServlet {
|
||||||
final ServletContext servletContext = request.getServletContext();
|
final ServletContext servletContext = request.getServletContext();
|
||||||
final ContentAccess contentAccess = (ContentAccess) servletContext.getAttribute("nifi-content-access");
|
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) {
|
if (contentRequest.getDataUri() == null) {
|
||||||
request.setAttribute("title", "Error");
|
request.setAttribute("title", "Error");
|
||||||
request.setAttribute("messages", "The data reference must be specified.");
|
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
|
* @return Get the content request context based on the specified request
|
||||||
*/
|
*/
|
||||||
private ContentRequestContext getContentRequest(final HttpServletRequest 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() {
|
return new ContentRequestContext() {
|
||||||
@Override
|
@Override
|
||||||
public String getDataUri() {
|
public String getDataUri() {
|
||||||
return request.getParameter("ref");
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getClusterNodeId() {
|
public String getClusterNodeId() {
|
||||||
final String ref = request.getParameter("ref");
|
return clusterNodeId;
|
||||||
return StringUtils.substringAfterLast(ref, "clusterNodeId=");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getClientId() {
|
public String getClientId() {
|
||||||
return request.getParameter("clientId");
|
return clientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getProxiedEntitiesChain() {
|
public String getProxiedEntitiesChain() {
|
||||||
return request.getHeader("X-ProxiedEntitiesChain");
|
return proxiedEntitiesChain;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,14 +25,37 @@
|
||||||
<link href="../nifi/css/message-pane.css" rel="stylesheet" type="text/css" />
|
<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 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/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" />
|
<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-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/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-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" src="../nifi/js/nf/nf-universal-capture.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var $$ = $.noConflict(true);
|
var $$ = $.noConflict(true);
|
||||||
$$(document).ready(function () {
|
$$(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 url = $$('#requestUrl').text();
|
||||||
var ref = $$('#ref').text();
|
var ref = $$('#ref').text();
|
||||||
|
|
||||||
|
@ -77,9 +100,37 @@
|
||||||
|
|
||||||
// if the selection has changesd, reload the page
|
// if the selection has changesd, reload the page
|
||||||
if (currentLocation !== option.value) {
|
if (currentLocation !== option.value) {
|
||||||
window.location.href = url + '?' + $$.param($$.extend({
|
// 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
|
mode: option.value
|
||||||
}, params));
|
};
|
||||||
|
|
||||||
|
// 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="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="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>
|
<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-label">View as</div>
|
||||||
<div id="view-as" class="pointer button-normal"></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>
|
<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>
|
||||||
|
|
|
@ -19,6 +19,14 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#content-viewer-message-dialog {
|
||||||
|
z-index: 1302;
|
||||||
|
display: none;
|
||||||
|
height: 225px;
|
||||||
|
width: 375px;
|
||||||
|
font-size: 62.5%
|
||||||
|
}
|
||||||
|
|
||||||
#view-as-label {
|
#view-as-label {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 72px;
|
top: 72px;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.nifi.user.NiFiUser;
|
import org.apache.nifi.user.NiFiUser;
|
||||||
import org.apache.nifi.util.NiFiProperties;
|
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.apache.nifi.web.security.user.NiFiUserUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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 {
|
private void authenticate(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws IOException, ServletException {
|
||||||
String dnChain = null;
|
String dnChain = null;
|
||||||
try {
|
try {
|
||||||
final NiFiAuthortizationRequestToken authenticated = attemptAuthentication(request);
|
final NiFiAuthorizationRequestToken authenticated = attemptAuthentication(request);
|
||||||
if (authenticated != null) {
|
if (authenticated != null) {
|
||||||
dnChain = ProxiedEntitiesUtils.formatProxyDn(StringUtils.join(authenticated.getChain(), "><"));
|
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
|
* @return The NiFiAutorizationRequestToken used to later authorized the client
|
||||||
* @throws InvalidAuthenticationException If the request contained an authentication attempt, but could not authenticate
|
* @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) {
|
protected void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
|
|
|
@ -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.NewAccountAuthorizationRequestToken;
|
||||||
import org.apache.nifi.web.security.token.NewAccountAuthorizationToken;
|
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.apache.nifi.web.security.token.NiFiAuthorizationToken;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
|
@ -32,15 +32,15 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
*/
|
*/
|
||||||
public class NiFiAuthenticationProvider implements AuthenticationProvider {
|
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;
|
this.userDetailsService = userDetailsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
final NiFiAuthortizationRequestToken request = (NiFiAuthortizationRequestToken) authentication;
|
final NiFiAuthorizationRequestToken request = (NiFiAuthorizationRequestToken) authentication;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// defer to the nifi user details service to authorize the user
|
// defer to the nifi user details service to authorize the user
|
||||||
|
@ -67,7 +67,7 @@ public class NiFiAuthenticationProvider implements AuthenticationProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supports(Class<?> authentication) {
|
public boolean supports(Class<?> authentication) {
|
||||||
return NiFiAuthortizationRequestToken.class.isAssignableFrom(authentication);
|
return NiFiAuthorizationRequestToken.class.isAssignableFrom(authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,8 +58,11 @@ public class NiFiAnonymousUserFilter extends AnonymousAuthenticationFilter {
|
||||||
user.getAuthorities().addAll(EnumSet.allOf(Authority.class));
|
user.getAuthorities().addAll(EnumSet.allOf(Authority.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
// only create an authentication token if the anonymous user has some authorities
|
// only create an authentication token if the anonymous user has some authorities or they are accessing a ui
|
||||||
if (!user.getAuthorities().isEmpty()) {
|
// 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);
|
NiFiUserDetails userDetails = new NiFiUserDetails(user);
|
||||||
|
|
||||||
// get the granted authorities
|
// get the granted authorities
|
||||||
|
|
|
@ -30,7 +30,7 @@ import org.apache.nifi.user.NiFiUser;
|
||||||
import org.apache.nifi.util.NiFiProperties;
|
import org.apache.nifi.util.NiFiProperties;
|
||||||
import org.apache.nifi.web.security.UntrustedProxyException;
|
import org.apache.nifi.web.security.UntrustedProxyException;
|
||||||
import org.apache.nifi.web.security.user.NiFiUserDetails;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.dao.DataAccessException;
|
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.
|
* 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);
|
private static final Logger logger = LoggerFactory.getLogger(NiFiAuthorizationService.class);
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ public class NiFiAuthorizationService implements AuthenticationUserDetailsServic
|
||||||
* @throws org.springframework.dao.DataAccessException ex
|
* @throws org.springframework.dao.DataAccessException ex
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public synchronized UserDetails loadUserDetails(NiFiAuthortizationRequestToken request) throws UsernameNotFoundException, DataAccessException {
|
public synchronized UserDetails loadUserDetails(NiFiAuthorizationRequestToken request) throws UsernameNotFoundException, DataAccessException {
|
||||||
NiFiUserDetails userDetails = null;
|
NiFiUserDetails userDetails = null;
|
||||||
final List<String> chain = new ArrayList<>(request.getChain());
|
final List<String> chain = new ArrayList<>(request.getChain());
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import io.jsonwebtoken.JwtException;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.nifi.web.security.NiFiAuthenticationFilter;
|
import org.apache.nifi.web.security.NiFiAuthenticationFilter;
|
||||||
import org.apache.nifi.web.security.token.NewAccountAuthorizationRequestToken;
|
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.apache.nifi.web.security.user.NewAccountRequest;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -40,7 +40,7 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
|
||||||
private JwtService jwtService;
|
private JwtService jwtService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NiFiAuthortizationRequestToken attemptAuthentication(final HttpServletRequest request) {
|
public NiFiAuthorizationRequestToken attemptAuthentication(final HttpServletRequest request) {
|
||||||
// only suppport jwt login when running securely
|
// only suppport jwt login when running securely
|
||||||
if (!request.isSecure()) {
|
if (!request.isSecure()) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -68,7 +68,7 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
|
||||||
if (isNewAccountRequest(request)) {
|
if (isNewAccountRequest(request)) {
|
||||||
return new NewAccountAuthorizationRequestToken(new NewAccountRequest(Arrays.asList(jwtPrincipal), getJustification(request)));
|
return new NewAccountAuthorizationRequestToken(new NewAccountRequest(Arrays.asList(jwtPrincipal), getJustification(request)));
|
||||||
} else {
|
} else {
|
||||||
return new NiFiAuthortizationRequestToken(Arrays.asList(jwtPrincipal));
|
return new NiFiAuthorizationRequestToken(Arrays.asList(jwtPrincipal));
|
||||||
}
|
}
|
||||||
} catch (JwtException e) {
|
} catch (JwtException e) {
|
||||||
throw new InvalidAuthenticationException(e.getMessage(), e);
|
throw new InvalidAuthenticationException(e.getMessage(), e);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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) + "'}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
* 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;
|
final NewAccountRequest newAccountRequest;
|
||||||
|
|
||||||
|
|
|
@ -24,11 +24,11 @@ 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.
|
* 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).
|
* The request chain is specified during creation and is used authorize the user(s).
|
||||||
*/
|
*/
|
||||||
public class NiFiAuthortizationRequestToken extends AbstractAuthenticationToken {
|
public class NiFiAuthorizationRequestToken extends AbstractAuthenticationToken {
|
||||||
|
|
||||||
private final List<String> chain;
|
private final List<String> chain;
|
||||||
|
|
||||||
public NiFiAuthortizationRequestToken(final List<String> chain) {
|
public NiFiAuthorizationRequestToken(final List<String> chain) {
|
||||||
super(null);
|
super(null);
|
||||||
this.chain = chain;
|
this.chain = chain;
|
||||||
}
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ import org.apache.nifi.web.security.InvalidAuthenticationException;
|
||||||
import org.apache.nifi.web.security.NiFiAuthenticationFilter;
|
import org.apache.nifi.web.security.NiFiAuthenticationFilter;
|
||||||
import org.apache.nifi.web.security.ProxiedEntitiesUtils;
|
import org.apache.nifi.web.security.ProxiedEntitiesUtils;
|
||||||
import org.apache.nifi.web.security.token.NewAccountAuthorizationRequestToken;
|
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.apache.nifi.web.security.user.NewAccountRequest;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -40,7 +40,7 @@ public class X509AuthenticationFilter extends NiFiAuthenticationFilter {
|
||||||
private X509IdentityProvider certificateIdentityProvider;
|
private X509IdentityProvider certificateIdentityProvider;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NiFiAuthortizationRequestToken attemptAuthentication(final HttpServletRequest request) {
|
public NiFiAuthorizationRequestToken attemptAuthentication(final HttpServletRequest request) {
|
||||||
// only suppport x509 login when running securely
|
// only suppport x509 login when running securely
|
||||||
if (!request.isSecure()) {
|
if (!request.isSecure()) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -64,7 +64,7 @@ public class X509AuthenticationFilter extends NiFiAuthenticationFilter {
|
||||||
if (isNewAccountRequest(request)) {
|
if (isNewAccountRequest(request)) {
|
||||||
return new NewAccountAuthorizationRequestToken(new NewAccountRequest(proxyChain, getJustification(request)));
|
return new NewAccountAuthorizationRequestToken(new NewAccountRequest(proxyChain, getJustification(request)));
|
||||||
} else {
|
} else {
|
||||||
return new NiFiAuthortizationRequestToken(proxyChain);
|
return new NiFiAuthorizationRequestToken(proxyChain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,9 @@
|
||||||
<constructor-arg ref="userService"/>
|
<constructor-arg ref="userService"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<!-- otp service -->
|
||||||
|
<bean id="otpService" class="org.apache.nifi.web.security.otp.OtpService"/>
|
||||||
|
|
||||||
<!-- login identity provider -->
|
<!-- login identity provider -->
|
||||||
<bean id="loginIdentityProvider" class="org.apache.nifi.web.security.spring.LoginIdentityProviderFactoryBean">
|
<bean id="loginIdentityProvider" class="org.apache.nifi.web.security.spring.LoginIdentityProviderFactoryBean">
|
||||||
<property name="properties" ref="nifiProperties"/>
|
<property name="properties" ref="nifiProperties"/>
|
||||||
|
|
|
@ -26,7 +26,7 @@ import org.apache.nifi.authorization.Authority;
|
||||||
import org.apache.nifi.user.NiFiUser;
|
import org.apache.nifi.user.NiFiUser;
|
||||||
import org.apache.nifi.util.NiFiProperties;
|
import org.apache.nifi.util.NiFiProperties;
|
||||||
import org.apache.nifi.web.security.UntrustedProxyException;
|
import org.apache.nifi.web.security.UntrustedProxyException;
|
||||||
import org.apache.nifi.web.security.token.NiFiAuthortizationRequestToken;
|
import org.apache.nifi.web.security.token.NiFiAuthorizationRequestToken;
|
||||||
import org.apache.nifi.web.security.user.NiFiUserDetails;
|
import org.apache.nifi.web.security.user.NiFiUserDetails;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -104,8 +104,8 @@ public class NiFiAuthorizationServiceTest {
|
||||||
authorizationService.setUserService(userService);
|
authorizationService.setUserService(userService);
|
||||||
}
|
}
|
||||||
|
|
||||||
private NiFiAuthortizationRequestToken createRequestAuthentication(final String... identities) {
|
private NiFiAuthorizationRequestToken createRequestAuthentication(final String... identities) {
|
||||||
return new NiFiAuthortizationRequestToken(Arrays.asList(identities));
|
return new NiFiAuthorizationRequestToken(Arrays.asList(identities));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
/*
|
||||||
|
* 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.token.NiFiAuthorizationRequestToken;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.mockito.Matchers.anyString;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class OtpAuthenticationFilterTest {
|
||||||
|
|
||||||
|
private final static String UI_EXTENSION_AUTHENTICATED_USER = "ui-extension-token-authenticated-user";
|
||||||
|
private final static String UI_EXTENSION_TOKEN = "ui-extension-token";
|
||||||
|
|
||||||
|
private final static String DOWNLOAD_AUTHENTICATED_USER = "download-token-authenticated-user";
|
||||||
|
private final static String DOWNLOAD_TOKEN = "download-token";
|
||||||
|
|
||||||
|
private OtpService otpService;
|
||||||
|
private OtpAuthenticationFilter otpAuthenticationFilter;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
otpService = mock(OtpService.class);
|
||||||
|
doAnswer(new Answer<String>() {
|
||||||
|
@Override
|
||||||
|
public String answer(InvocationOnMock invocation) throws Throwable {
|
||||||
|
Object[] args = invocation.getArguments();
|
||||||
|
String downloadToken = (String) args[0];
|
||||||
|
|
||||||
|
if (DOWNLOAD_TOKEN.equals(downloadToken)) {
|
||||||
|
return DOWNLOAD_AUTHENTICATED_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new OtpAuthenticationException("Invalid token");
|
||||||
|
}
|
||||||
|
}).when(otpService).getAuthenticationFromDownloadToken(anyString());
|
||||||
|
doAnswer(new Answer<String>() {
|
||||||
|
@Override
|
||||||
|
public String answer(InvocationOnMock invocation) throws Throwable {
|
||||||
|
Object[] args = invocation.getArguments();
|
||||||
|
String uiExtensionToken = (String) args[0];
|
||||||
|
|
||||||
|
if (UI_EXTENSION_TOKEN.equals(uiExtensionToken)) {
|
||||||
|
return UI_EXTENSION_AUTHENTICATED_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new OtpAuthenticationException("Invalid token");
|
||||||
|
}
|
||||||
|
}).when(otpService).getAuthenticationFromUiExtensionToken(anyString());
|
||||||
|
|
||||||
|
otpAuthenticationFilter = new OtpAuthenticationFilter();
|
||||||
|
otpAuthenticationFilter.setOtpService(otpService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsecureHttp() throws Exception {
|
||||||
|
final HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.isSecure()).thenReturn(false);
|
||||||
|
assertNull(otpAuthenticationFilter.attemptAuthentication(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoAccessToken() throws Exception {
|
||||||
|
final HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.isSecure()).thenReturn(true);
|
||||||
|
when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn(null);
|
||||||
|
assertNull(otpAuthenticationFilter.attemptAuthentication(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidAuthenticationException.class)
|
||||||
|
public void testTokenSupportDisabled() throws Exception {
|
||||||
|
final HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.isSecure()).thenReturn(true);
|
||||||
|
when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn("my-access-token");
|
||||||
|
|
||||||
|
final OtpAuthenticationFilter noTokenAuthenticationFilter = new OtpAuthenticationFilter();
|
||||||
|
noTokenAuthenticationFilter.attemptAuthentication(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnsupportedDownloadPath() throws Exception {
|
||||||
|
final HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.isSecure()).thenReturn(true);
|
||||||
|
when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn("my-access-token");
|
||||||
|
when(request.getContextPath()).thenReturn("/nifi-api");
|
||||||
|
when(request.getPathInfo()).thenReturn("/controller/config");
|
||||||
|
|
||||||
|
assertNull(otpAuthenticationFilter.attemptAuthentication(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUiExtensionPath() throws Exception {
|
||||||
|
final HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.isSecure()).thenReturn(true);
|
||||||
|
when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn(UI_EXTENSION_TOKEN);
|
||||||
|
when(request.getContextPath()).thenReturn("/nifi-update-attribute-ui");
|
||||||
|
|
||||||
|
final NiFiAuthorizationRequestToken result = otpAuthenticationFilter.attemptAuthentication(request);
|
||||||
|
final List<String> chain = result.getChain();
|
||||||
|
assertEquals(1, chain.size());
|
||||||
|
assertEquals(UI_EXTENSION_AUTHENTICATED_USER, chain.get(0));
|
||||||
|
|
||||||
|
verify(otpService, times(1)).getAuthenticationFromUiExtensionToken(UI_EXTENSION_TOKEN);
|
||||||
|
verify(otpService, never()).getAuthenticationFromDownloadToken(anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProvenanceInputContentDownload() throws Exception {
|
||||||
|
final HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.isSecure()).thenReturn(true);
|
||||||
|
when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn(DOWNLOAD_TOKEN);
|
||||||
|
when(request.getContextPath()).thenReturn("/nifi-api");
|
||||||
|
when(request.getPathInfo()).thenReturn("/controller/provenance/events/0/content/input");
|
||||||
|
|
||||||
|
final NiFiAuthorizationRequestToken result = otpAuthenticationFilter.attemptAuthentication(request);
|
||||||
|
final List<String> chain = result.getChain();
|
||||||
|
assertEquals(1, chain.size());
|
||||||
|
assertEquals(DOWNLOAD_AUTHENTICATED_USER, chain.get(0));
|
||||||
|
|
||||||
|
verify(otpService, never()).getAuthenticationFromUiExtensionToken(anyString());
|
||||||
|
verify(otpService, times(1)).getAuthenticationFromDownloadToken(DOWNLOAD_TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProvenanceOutputContentDownload() throws Exception {
|
||||||
|
final HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.isSecure()).thenReturn(true);
|
||||||
|
when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn(DOWNLOAD_TOKEN);
|
||||||
|
when(request.getContextPath()).thenReturn("/nifi-api");
|
||||||
|
when(request.getPathInfo()).thenReturn("/controller/provenance/events/0/content/output");
|
||||||
|
|
||||||
|
final NiFiAuthorizationRequestToken result = otpAuthenticationFilter.attemptAuthentication(request);
|
||||||
|
final List<String> chain = result.getChain();
|
||||||
|
assertEquals(1, chain.size());
|
||||||
|
assertEquals(DOWNLOAD_AUTHENTICATED_USER, chain.get(0));
|
||||||
|
|
||||||
|
verify(otpService, never()).getAuthenticationFromUiExtensionToken(anyString());
|
||||||
|
verify(otpService, times(1)).getAuthenticationFromDownloadToken(DOWNLOAD_TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFlowFileContentDownload() throws Exception {
|
||||||
|
final String uuid = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
final HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.isSecure()).thenReturn(true);
|
||||||
|
when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn(DOWNLOAD_TOKEN);
|
||||||
|
when(request.getContextPath()).thenReturn("/nifi-api");
|
||||||
|
when(request.getPathInfo()).thenReturn(String.format("/controller/process-groups/root/connections/%s/flowfiles/%s/content", uuid, uuid));
|
||||||
|
|
||||||
|
final NiFiAuthorizationRequestToken result = otpAuthenticationFilter.attemptAuthentication(request);
|
||||||
|
final List<String> chain = result.getChain();
|
||||||
|
assertEquals(1, chain.size());
|
||||||
|
assertEquals(DOWNLOAD_AUTHENTICATED_USER, chain.get(0));
|
||||||
|
|
||||||
|
verify(otpService, never()).getAuthenticationFromUiExtensionToken(anyString());
|
||||||
|
verify(otpService, times(1)).getAuthenticationFromDownloadToken(DOWNLOAD_TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTemplateDownload() throws Exception {
|
||||||
|
final String uuid = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
final HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.isSecure()).thenReturn(true);
|
||||||
|
when(request.getParameter(OtpAuthenticationFilter.ACCESS_TOKEN)).thenReturn(DOWNLOAD_TOKEN);
|
||||||
|
when(request.getContextPath()).thenReturn("/nifi-api");
|
||||||
|
when(request.getPathInfo()).thenReturn(String.format("/controller/templates/%s", uuid));
|
||||||
|
|
||||||
|
final NiFiAuthorizationRequestToken result = otpAuthenticationFilter.attemptAuthentication(request);
|
||||||
|
final List<String> chain = result.getChain();
|
||||||
|
assertEquals(1, chain.size());
|
||||||
|
assertEquals(DOWNLOAD_AUTHENTICATED_USER, chain.get(0));
|
||||||
|
|
||||||
|
verify(otpService, never()).getAuthenticationFromUiExtensionToken(anyString());
|
||||||
|
verify(otpService, times(1)).getAuthenticationFromDownloadToken(DOWNLOAD_TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
/*
|
||||||
|
* 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.token.OtpAuthenticationToken;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
public class OtpServiceTest {
|
||||||
|
|
||||||
|
private final static String USER_1 = "user-identity-1";
|
||||||
|
|
||||||
|
private OtpService otpService;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
otpService = new OtpService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAuthenticationForValidDownloadToken() throws Exception {
|
||||||
|
final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(USER_1);
|
||||||
|
final String downloadToken = otpService.generateDownloadToken(authenticationToken);
|
||||||
|
final String authenticatedUser = otpService.getAuthenticationFromDownloadToken(downloadToken);
|
||||||
|
|
||||||
|
assertNotNull(authenticatedUser);
|
||||||
|
assertEquals(USER_1, authenticatedUser);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// ensure the token is no longer valid
|
||||||
|
otpService.getAuthenticationFromDownloadToken(downloadToken);
|
||||||
|
fail();
|
||||||
|
} catch (final OtpAuthenticationException oae) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAuthenticationForValidUiExtensionToken() throws Exception {
|
||||||
|
final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(USER_1);
|
||||||
|
final String uiExtensionToken = otpService.generateUiExtensionToken(authenticationToken);
|
||||||
|
final String authenticatedUser = otpService.getAuthenticationFromUiExtensionToken(uiExtensionToken);
|
||||||
|
|
||||||
|
assertNotNull(authenticatedUser);
|
||||||
|
assertEquals(USER_1, authenticatedUser);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// ensure the token is no longer valid
|
||||||
|
otpService.getAuthenticationFromUiExtensionToken(uiExtensionToken);
|
||||||
|
fail();
|
||||||
|
} catch (final OtpAuthenticationException oae) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = OtpAuthenticationException.class)
|
||||||
|
public void testGetNonExistentDownloadToken() throws Exception {
|
||||||
|
otpService.getAuthenticationFromDownloadToken("Not a real download token");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = OtpAuthenticationException.class)
|
||||||
|
public void testGetNonExistentUiExtensionToken() throws Exception {
|
||||||
|
otpService.getAuthenticationFromUiExtensionToken("Not a real ui extension token");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void testMaxDownloadTokenLimit() throws Exception {
|
||||||
|
// ensure we'll try to loop past the limit
|
||||||
|
for (int i = 1; i < OtpService.MAX_CACHE_SOFT_LIMIT + 10; i++) {
|
||||||
|
try {
|
||||||
|
final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken("user-identity-" + i);
|
||||||
|
otpService.generateDownloadToken(authenticationToken);
|
||||||
|
} catch (final IllegalStateException iae) {
|
||||||
|
// ensure we failed when we've past the limit
|
||||||
|
assertEquals(OtpService.MAX_CACHE_SOFT_LIMIT + 1, i);
|
||||||
|
throw iae;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void testMaxUiExtensionTokenLimit() throws Exception {
|
||||||
|
// ensure we'll try to loop past the limit
|
||||||
|
for (int i = 1; i < OtpService.MAX_CACHE_SOFT_LIMIT + 10; i++) {
|
||||||
|
try {
|
||||||
|
final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken("user-identity-" + i);
|
||||||
|
otpService.generateUiExtensionToken(authenticationToken);
|
||||||
|
} catch (final IllegalStateException iae) {
|
||||||
|
// ensure we failed when we've past the limit
|
||||||
|
assertEquals(OtpService.MAX_CACHE_SOFT_LIMIT + 1, i);
|
||||||
|
throw iae;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = NullPointerException.class)
|
||||||
|
public void testNullTimeUnits() throws Exception {
|
||||||
|
new OtpService(0, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testNegativeExpiration() throws Exception {
|
||||||
|
new OtpService(-1, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = OtpAuthenticationException.class)
|
||||||
|
public void testUiExtensionTokenExpiration() throws Exception {
|
||||||
|
final OtpService otpServiceWithTightExpiration = new OtpService(2, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(USER_1);
|
||||||
|
final String downloadToken = otpServiceWithTightExpiration.generateUiExtensionToken(authenticationToken);
|
||||||
|
|
||||||
|
// sleep for 4 seconds which should sufficiently expire the valid token
|
||||||
|
Thread.sleep(4 * 1000);
|
||||||
|
|
||||||
|
// attempt to get the token now that its expired
|
||||||
|
otpServiceWithTightExpiration.getAuthenticationFromUiExtensionToken(downloadToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = OtpAuthenticationException.class)
|
||||||
|
public void testDownloadTokenExpiration() throws Exception {
|
||||||
|
final OtpService otpServiceWithTightExpiration = new OtpService(2, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
final OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(USER_1);
|
||||||
|
final String downloadToken = otpServiceWithTightExpiration.generateDownloadToken(authenticationToken);
|
||||||
|
|
||||||
|
// sleep for 4 seconds which should sufficiently expire the valid token
|
||||||
|
Thread.sleep(4 * 1000);
|
||||||
|
|
||||||
|
// attempt to get the token now that its expired
|
||||||
|
otpServiceWithTightExpiration.getAuthenticationFromDownloadToken(downloadToken);
|
||||||
|
}
|
||||||
|
}
|
|
@ -280,6 +280,7 @@
|
||||||
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-shell.js</include>
|
<include>${staging.dir}/js/nf/nf-shell.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
||||||
|
<include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
|
||||||
<include>${staging.dir}/js/nf/canvas/nf-snippet.js</include>
|
<include>${staging.dir}/js/nf/canvas/nf-snippet.js</include>
|
||||||
<include>${staging.dir}/js/nf/canvas/nf-canvas-toolbox.js</include>
|
<include>${staging.dir}/js/nf/canvas/nf-canvas-toolbox.js</include>
|
||||||
<include>${staging.dir}/js/nf/canvas/nf-custom-ui.js</include>
|
<include>${staging.dir}/js/nf/canvas/nf-custom-ui.js</include>
|
||||||
|
@ -336,6 +337,7 @@
|
||||||
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
||||||
|
<include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
|
||||||
<include>${staging.dir}/js/nf/history/nf-history.js</include>
|
<include>${staging.dir}/js/nf/history/nf-history.js</include>
|
||||||
<include>${staging.dir}/js/nf/history/nf-history-table.js</include>
|
<include>${staging.dir}/js/nf/history/nf-history-table.js</include>
|
||||||
<include>${staging.dir}/js/nf/history/nf-history-model.js</include>
|
<include>${staging.dir}/js/nf/history/nf-history-model.js</include>
|
||||||
|
@ -350,6 +352,7 @@
|
||||||
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
||||||
|
<include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
|
||||||
<include>${staging.dir}/js/nf/provenance/nf-provenance.js</include>
|
<include>${staging.dir}/js/nf/provenance/nf-provenance.js</include>
|
||||||
<include>${staging.dir}/js/nf/provenance/nf-provenance-table.js</include>
|
<include>${staging.dir}/js/nf/provenance/nf-provenance-table.js</include>
|
||||||
</includes>
|
</includes>
|
||||||
|
@ -363,6 +366,7 @@
|
||||||
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
||||||
|
<include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-processor-details.js</include>
|
<include>${staging.dir}/js/nf/nf-processor-details.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-connection-details.js</include>
|
<include>${staging.dir}/js/nf/nf-connection-details.js</include>
|
||||||
<include>${staging.dir}/js/nf/summary/nf-summary.js</include>
|
<include>${staging.dir}/js/nf/summary/nf-summary.js</include>
|
||||||
|
@ -379,6 +383,7 @@
|
||||||
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
||||||
|
<include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
|
||||||
<include>${staging.dir}/js/nf/counters/nf-counters.js</include>
|
<include>${staging.dir}/js/nf/counters/nf-counters.js</include>
|
||||||
<include>${staging.dir}/js/nf/counters/nf-counters-table.js</include>
|
<include>${staging.dir}/js/nf/counters/nf-counters-table.js</include>
|
||||||
</includes>
|
</includes>
|
||||||
|
@ -392,6 +397,7 @@
|
||||||
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
||||||
|
<include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
|
||||||
<include>${staging.dir}/js/nf/templates/nf-templates.js</include>
|
<include>${staging.dir}/js/nf/templates/nf-templates.js</include>
|
||||||
<include>${staging.dir}/js/nf/templates/nf-templates-table.js</include>
|
<include>${staging.dir}/js/nf/templates/nf-templates-table.js</include>
|
||||||
</includes>
|
</includes>
|
||||||
|
@ -405,6 +411,7 @@
|
||||||
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
||||||
|
<include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
|
||||||
<include>${staging.dir}/js/nf/cluster/nf-cluster.js</include>
|
<include>${staging.dir}/js/nf/cluster/nf-cluster.js</include>
|
||||||
<include>${staging.dir}/js/nf/cluster/nf-cluster-table.js</include>
|
<include>${staging.dir}/js/nf/cluster/nf-cluster-table.js</include>
|
||||||
</includes>
|
</includes>
|
||||||
|
@ -418,6 +425,7 @@
|
||||||
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
||||||
|
<include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
|
||||||
<include>${staging.dir}/js/nf/users/nf-users.js</include>
|
<include>${staging.dir}/js/nf/users/nf-users.js</include>
|
||||||
<include>${staging.dir}/js/nf/users/nf-users-table.js</include>
|
<include>${staging.dir}/js/nf/users/nf-users-table.js</include>
|
||||||
</includes>
|
</includes>
|
||||||
|
@ -431,6 +439,7 @@
|
||||||
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
||||||
|
<include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
|
||||||
<include>${staging.dir}/js/nf/bulletin-board/nf-bulletin-board.js</include>
|
<include>${staging.dir}/js/nf/bulletin-board/nf-bulletin-board.js</include>
|
||||||
</includes>
|
</includes>
|
||||||
</aggregation>
|
</aggregation>
|
||||||
|
@ -443,6 +452,7 @@
|
||||||
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
<include>${staging.dir}/js/nf/nf-universal-capture.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
<include>${staging.dir}/js/nf/nf-dialog.js</include>
|
||||||
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
<include>${staging.dir}/js/nf/nf-storage.js</include>
|
||||||
|
<include>${staging.dir}/js/nf/nf-ajax-setup.js</include>
|
||||||
<include>${staging.dir}/js/nf/login/nf-login.js</include>
|
<include>${staging.dir}/js/nf/login/nf-login.js</include>
|
||||||
</includes>
|
</includes>
|
||||||
</aggregation>
|
</aggregation>
|
||||||
|
@ -622,6 +632,10 @@
|
||||||
js/nf/nf-namespace.js.gz,
|
js/nf/nf-namespace.js.gz,
|
||||||
js/nf/nf-universal-capture.js,
|
js/nf/nf-universal-capture.js,
|
||||||
js/nf/nf-universal-capture.js.gz,
|
js/nf/nf-universal-capture.js.gz,
|
||||||
|
js/nf/nf-storage.js,
|
||||||
|
js/nf/nf-storage.js.gz,
|
||||||
|
js/nf/nf-ajax-setup.js,
|
||||||
|
js/nf/nf-ajax-setup.js.gz,
|
||||||
js/nf/nf-status-history.js,
|
js/nf/nf-status-history.js,
|
||||||
js/nf/nf-status-history.js.gz,
|
js/nf/nf-status-history.js.gz,
|
||||||
js/nf/canvas/nf-canvas-all.js,
|
js/nf/canvas/nf-canvas-all.js,
|
||||||
|
|
|
@ -18,6 +18,7 @@ nf.bulletin.board.script.tags=<script type="text/javascript" src="js/nf/nf-names
|
||||||
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
||||||
|
<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/bulletin-board/nf-bulletin-board.js?${project.version}"></script>
|
<script type="text/javascript" src="js/nf/bulletin-board/nf-bulletin-board.js?${project.version}"></script>
|
||||||
nf.bulletin.board.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
|
nf.bulletin.board.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
|
||||||
<link rel="stylesheet" href="css/main.css?${project.version}" type="text/css" />\n\
|
<link rel="stylesheet" href="css/main.css?${project.version}" type="text/css" />\n\
|
||||||
|
|
|
@ -21,6 +21,7 @@ nf.canvas.script.tags=<script type="text/javascript" src="js/nf/nf-namespace.js?
|
||||||
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-shell.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-shell.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
||||||
|
<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/canvas/nf-snippet.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/canvas/nf-snippet.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/canvas/nf-queue-listing.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/canvas/nf-queue-listing.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/canvas/nf-component-state.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/canvas/nf-component-state.js?${project.version}"></script>\n\
|
||||||
|
|
|
@ -18,6 +18,7 @@ nf.cluster.script.tags=<script type="text/javascript" src="js/nf/nf-namespace.js
|
||||||
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
||||||
|
<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/cluster/nf-cluster.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/cluster/nf-cluster.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/cluster/nf-cluster-table.js?${project.version}"></script>
|
<script type="text/javascript" src="js/nf/cluster/nf-cluster-table.js?${project.version}"></script>
|
||||||
nf.cluster.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
|
nf.cluster.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
|
||||||
|
|
|
@ -18,6 +18,7 @@ nf.counters.script.tags=<script type="text/javascript" src="js/nf/nf-namespace.j
|
||||||
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
||||||
|
<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/counters/nf-counters.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/counters/nf-counters.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/counters/nf-counters-table.js?${project.version}"></script>
|
<script type="text/javascript" src="js/nf/counters/nf-counters-table.js?${project.version}"></script>
|
||||||
nf.counters.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
|
nf.counters.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
|
||||||
|
|
|
@ -18,6 +18,7 @@ nf.history.script.tags=<script type="text/javascript" src="js/nf/nf-namespace.js
|
||||||
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
||||||
|
<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/history/nf-history.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/history/nf-history.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/history/nf-history-table.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/history/nf-history-table.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/history/nf-history-model.js?${project.version}"></script>
|
<script type="text/javascript" src="js/nf/history/nf-history-model.js?${project.version}"></script>
|
||||||
|
|
|
@ -17,6 +17,7 @@ nf.login.script.tags=<script type="text/javascript" src="js/nf/nf-common.js?${pr
|
||||||
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
||||||
|
<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/login/nf-login.js?${project.version}"></script>
|
<script type="text/javascript" src="js/nf/login/nf-login.js?${project.version}"></script>
|
||||||
nf.login.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
|
nf.login.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
|
||||||
<link rel="stylesheet" href="css/main.css?${project.version}" type="text/css" />\n\
|
<link rel="stylesheet" href="css/main.css?${project.version}" type="text/css" />\n\
|
||||||
|
|
|
@ -18,6 +18,7 @@ nf.provenance.script.tags=<script type="text/javascript" src="js/nf/nf-namespace
|
||||||
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
||||||
|
<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/provenance/nf-provenance.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/provenance/nf-provenance.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/provenance/nf-provenance-table.js?${project.version}"></script>
|
<script type="text/javascript" src="js/nf/provenance/nf-provenance-table.js?${project.version}"></script>
|
||||||
nf.provenance.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
|
nf.provenance.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
|
||||||
|
|
|
@ -18,6 +18,7 @@ nf.summary.script.tags=<script type="text/javascript" src="js/nf/nf-namespace.js
|
||||||
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
||||||
|
<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-processor-details.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-processor-details.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-connection-details.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-connection-details.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/summary/nf-summary.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/summary/nf-summary.js?${project.version}"></script>\n\
|
||||||
|
|
|
@ -18,6 +18,7 @@ nf.templates.script.tags=<script type="text/javascript" src="js/nf/nf-namespace.
|
||||||
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
||||||
|
<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/templates/nf-templates.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/templates/nf-templates.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/templates/nf-templates-table.js?${project.version}"></script>
|
<script type="text/javascript" src="js/nf/templates/nf-templates-table.js?${project.version}"></script>
|
||||||
nf.templates.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
|
nf.templates.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
|
||||||
|
|
|
@ -18,6 +18,7 @@ nf.users.script.tags=<script type="text/javascript" src="js/nf/nf-namespace.js?$
|
||||||
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-universal-capture.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-dialog.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\
|
||||||
|
<script type="text/javascript" src="js/nf/nf-ajax-setup.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/users/nf-users.js?${project.version}"></script>\n\
|
<script type="text/javascript" src="js/nf/users/nf-users.js?${project.version}"></script>\n\
|
||||||
<script type="text/javascript" src="js/nf/users/nf-users-table.js?${project.version}"></script>
|
<script type="text/javascript" src="js/nf/users/nf-users-table.js?${project.version}"></script>
|
||||||
nf.users.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
|
nf.users.style.tags=<link rel="stylesheet" href="css/reset.css?${project.version}" type="text/css" />\n\
|
||||||
|
|
|
@ -26,7 +26,8 @@ nf.CustomUi = {
|
||||||
* @argument {boolean} editable Whether the custom ui should support editing
|
* @argument {boolean} editable Whether the custom ui should support editing
|
||||||
*/
|
*/
|
||||||
showCustomUi: function (id, uri, editable) {
|
showCustomUi: function (id, uri, editable) {
|
||||||
|
return $.Deferred(function (deferred) {
|
||||||
|
nf.Common.getAccessToken('../nifi-api/access/ui-extension-token').done(function (uiExtensionToken) {
|
||||||
// record the processor id
|
// record the processor id
|
||||||
$('#shell-close-button');
|
$('#shell-close-button');
|
||||||
|
|
||||||
|
@ -41,7 +42,22 @@ nf.CustomUi = {
|
||||||
'editable': editable
|
'editable': editable
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// conditionally include the ui extension token
|
||||||
|
if (!nf.Common.isBlank(uiExtensionToken)) {
|
||||||
|
customUiParams['access_token'] = uiExtensionToken;
|
||||||
|
}
|
||||||
|
|
||||||
// show the shell
|
// show the shell
|
||||||
return nf.Shell.showPage('..' + uri + '?' + $.param(customUiParams), false);
|
nf.Shell.showPage('..' + uri + '?' + $.param(customUiParams), false).done(function () {
|
||||||
|
deferred.resolve();
|
||||||
|
});
|
||||||
|
}).fail(function () {
|
||||||
|
nf.Dialog.showOkDialog({
|
||||||
|
dialogContent: 'Unable to generate access token for accessing the advanced configuration dialog.',
|
||||||
|
overlayBackground: false
|
||||||
|
});
|
||||||
|
deferred.resolve();
|
||||||
|
});
|
||||||
|
}).promise();
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -22,6 +22,16 @@
|
||||||
*/
|
*/
|
||||||
nf.QueueListing = (function () {
|
nf.QueueListing = (function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration object used to hold a number of configuration items.
|
||||||
|
*/
|
||||||
|
var config = {
|
||||||
|
urls: {
|
||||||
|
uiExtensionToken: '../nifi-api/access/ui-extension-token',
|
||||||
|
downloadToken: '../nifi-api/access/download-token'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the listing request status dialog.
|
* Initializes the listing request status dialog.
|
||||||
*/
|
*/
|
||||||
|
@ -57,15 +67,33 @@ nf.QueueListing = (function () {
|
||||||
var downloadContent = function () {
|
var downloadContent = function () {
|
||||||
var dataUri = $('#flowfile-uri').text() + '/content';
|
var dataUri = $('#flowfile-uri').text() + '/content';
|
||||||
|
|
||||||
|
// perform the request once we've received a token
|
||||||
|
nf.Common.getAccessToken(config.urls.downloadToken).done(function (downloadToken) {
|
||||||
|
var parameters = {};
|
||||||
|
|
||||||
|
// conditionally include the ui extension token
|
||||||
|
if (!nf.Common.isBlank(downloadToken)) {
|
||||||
|
parameters['access_token'] = downloadToken;
|
||||||
|
}
|
||||||
|
|
||||||
// conditionally include the cluster node id
|
// conditionally include the cluster node id
|
||||||
var clusterNodeId = $('#flowfile-cluster-node-id').text();
|
var clusterNodeId = $('#flowfile-cluster-node-id').text();
|
||||||
if (!nf.Common.isBlank(clusterNodeId)) {
|
if (!nf.Common.isBlank(clusterNodeId)) {
|
||||||
window.open(dataUri + '?' + $.param({
|
parameters['clusterNodeId'] = clusterNodeId;
|
||||||
'clusterNodeId': clusterNodeId
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
window.open(dataUri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// open the url
|
||||||
|
if ($.isEmptyObject(parameters)) {
|
||||||
|
window.open(dataUri);
|
||||||
|
} else {
|
||||||
|
window.open(dataUri + '?' + $.param(parameters));
|
||||||
|
}
|
||||||
|
}).fail(function () {
|
||||||
|
nf.Dialog.showOkDialog({
|
||||||
|
dialogContent: 'Unable to generate access token for downloading content.',
|
||||||
|
overlayBackground: false
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,14 +102,54 @@ nf.QueueListing = (function () {
|
||||||
var viewContent = function () {
|
var viewContent = function () {
|
||||||
var dataUri = $('#flowfile-uri').text() + '/content';
|
var dataUri = $('#flowfile-uri').text() + '/content';
|
||||||
|
|
||||||
|
// generate tokens as necessary
|
||||||
|
var getAccessTokens = $.Deferred(function (deferred) {
|
||||||
|
if (nf.Storage.hasItem('jwt')) {
|
||||||
|
// generate a token for the ui extension and another for the callback
|
||||||
|
var uiExtensionToken = $.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: config.urls.uiExtensionToken
|
||||||
|
});
|
||||||
|
var downloadToken = $.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: config.urls.downloadToken
|
||||||
|
});
|
||||||
|
|
||||||
|
// wait for each token
|
||||||
|
$.when(uiExtensionToken, downloadToken).done(function (uiExtensionTokenResult, downloadTokenResult) {
|
||||||
|
var uiExtensionToken = uiExtensionTokenResult[0];
|
||||||
|
var downloadToken = downloadTokenResult[0];
|
||||||
|
deferred.resolve(uiExtensionToken, downloadToken);
|
||||||
|
}).fail(function () {
|
||||||
|
nf.Dialog.showOkDialog({
|
||||||
|
dialogContent: 'Unable to generate access token for viewing content.',
|
||||||
|
overlayBackground: false
|
||||||
|
});
|
||||||
|
deferred.reject();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
deferred.resolve('', '');
|
||||||
|
}
|
||||||
|
}).promise();
|
||||||
|
|
||||||
|
// perform the request after we've received the tokens
|
||||||
|
getAccessTokens.done(function (uiExtensionToken, downloadToken) {
|
||||||
|
var dataUriParameters = {};
|
||||||
|
|
||||||
// conditionally include the cluster node id
|
// conditionally include the cluster node id
|
||||||
var clusterNodeId = $('#flowfile-cluster-node-id').text();
|
var clusterNodeId = $('#flowfile-cluster-node-id').text();
|
||||||
if (!nf.Common.isBlank(clusterNodeId)) {
|
if (!nf.Common.isBlank(clusterNodeId)) {
|
||||||
var parameters = {
|
dataUriParameters['clusterNodeId'] = clusterNodeId;
|
||||||
'clusterNodeId': clusterNodeId
|
}
|
||||||
};
|
|
||||||
|
|
||||||
dataUri = dataUri + '?' + $.param(parameters);
|
// include the download token if applicable
|
||||||
|
if (!nf.Common.isBlank(downloadToken)) {
|
||||||
|
dataUriParameters['access_token'] = downloadToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// include parameters if necessary
|
||||||
|
if ($.isEmptyObject(dataUriParameters) === false) {
|
||||||
|
dataUri = dataUri + '?' + $.param(dataUriParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
// open the content viewer
|
// open the content viewer
|
||||||
|
@ -96,10 +164,18 @@ nf.QueueListing = (function () {
|
||||||
contentViewerUrl += '&';
|
contentViewerUrl += '&';
|
||||||
}
|
}
|
||||||
|
|
||||||
// open the content viewer
|
var contentViewerParameters = {
|
||||||
window.open(contentViewerUrl + $.param({
|
|
||||||
'ref': dataUri
|
'ref': dataUri
|
||||||
}));
|
};
|
||||||
|
|
||||||
|
// include the download token if applicable
|
||||||
|
if (!nf.Common.isBlank(uiExtensionToken)) {
|
||||||
|
contentViewerParameters['access_token'] = uiExtensionToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// open the content viewer
|
||||||
|
window.open(contentViewerUrl + $.param(contentViewerParameters));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs ajax setup for use within NiFi.
|
||||||
|
*/
|
||||||
|
$(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 !== null) {
|
||||||
|
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
|
||||||
|
} else {
|
||||||
|
// if the current user was logged in with a token and the token just expired, cancel the request
|
||||||
|
if (hadToken === true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -51,24 +51,6 @@ $(document).ready(function () {
|
||||||
$('div.loading-container').removeClass('ajax-loading');
|
$('div.loading-container').removeClass('ajax-loading');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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 !== 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// initialize the tooltips
|
// initialize the tooltips
|
||||||
$('img.setting-icon').qtip(nf.Common.config.tooltipConfig);
|
$('img.setting-icon').qtip(nf.Common.config.tooltipConfig);
|
||||||
|
|
||||||
|
@ -767,6 +749,29 @@ nf.Common = (function () {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an access token from the specified url.
|
||||||
|
*
|
||||||
|
* @param accessTokenUrl The access token
|
||||||
|
* @returns the access token as a deferred
|
||||||
|
*/
|
||||||
|
getAccessToken: function (accessTokenUrl) {
|
||||||
|
return $.Deferred(function (deferred) {
|
||||||
|
if (nf.Storage.hasItem('jwt')) {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: accessTokenUrl
|
||||||
|
}).done(function (token) {
|
||||||
|
deferred.resolve(token);
|
||||||
|
}).fail(function () {
|
||||||
|
deferred.reject();
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
deferred.resolve('');
|
||||||
|
}
|
||||||
|
}).promise();
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants for time duration formatting.
|
* Constants for time duration formatting.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -20,7 +20,20 @@
|
||||||
nf.Storage = (function () {
|
nf.Storage = (function () {
|
||||||
|
|
||||||
// Store items for two days before being eligible for removal.
|
// Store items for two days before being eligible for removal.
|
||||||
var TWO_DAYS = nf.Common.MILLIS_PER_DAY * 2;
|
var MILLIS_PER_DAY = 86400000;
|
||||||
|
var TWO_DAYS = MILLIS_PER_DAY * 2;
|
||||||
|
|
||||||
|
var isUndefined = function (obj) {
|
||||||
|
return typeof obj === 'undefined';
|
||||||
|
};
|
||||||
|
|
||||||
|
var isNull = function (obj) {
|
||||||
|
return obj === null;
|
||||||
|
};
|
||||||
|
|
||||||
|
var isDefinedAndNotNull = function (obj) {
|
||||||
|
return !isUndefined(obj) && !isNull(obj);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the expiration for the specified entry.
|
* Checks the expiration for the specified entry.
|
||||||
|
@ -29,7 +42,7 @@ nf.Storage = (function () {
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
var checkExpiration = function (entry) {
|
var checkExpiration = function (entry) {
|
||||||
if (nf.Common.isDefinedAndNotNull(entry.expires)) {
|
if (isDefinedAndNotNull(entry.expires)) {
|
||||||
// get the expiration
|
// get the expiration
|
||||||
var expires = new Date(entry.expires);
|
var expires = new Date(entry.expires);
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
|
@ -52,7 +65,7 @@ nf.Storage = (function () {
|
||||||
var entry = JSON.parse(localStorage.getItem(key));
|
var entry = JSON.parse(localStorage.getItem(key));
|
||||||
|
|
||||||
// ensure the entry and item are present
|
// ensure the entry and item are present
|
||||||
if (nf.Common.isDefinedAndNotNull(entry)) {
|
if (isDefinedAndNotNull(entry)) {
|
||||||
return entry;
|
return entry;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
@ -89,7 +102,7 @@ nf.Storage = (function () {
|
||||||
*/
|
*/
|
||||||
setItem: function (key, item, expires) {
|
setItem: function (key, item, expires) {
|
||||||
// calculate the expiration
|
// calculate the expiration
|
||||||
expires = nf.Common.isDefinedAndNotNull(expires) ? expires : new Date().valueOf() + TWO_DAYS;
|
expires = isDefinedAndNotNull(expires) ? expires : new Date().valueOf() + TWO_DAYS;
|
||||||
|
|
||||||
// create the entry
|
// create the entry
|
||||||
var entry = {
|
var entry = {
|
||||||
|
@ -132,7 +145,7 @@ nf.Storage = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the entry has the specified field return its value
|
// if the entry has the specified field return its value
|
||||||
if (nf.Common.isDefinedAndNotNull(entry['item'])) {
|
if (isDefinedAndNotNull(entry['item'])) {
|
||||||
return entry['item'];
|
return entry['item'];
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
@ -153,7 +166,7 @@ nf.Storage = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the entry has the specified field return its value
|
// if the entry has the specified field return its value
|
||||||
if (nf.Common.isDefinedAndNotNull(entry['expires'])) {
|
if (isDefinedAndNotNull(entry['expires'])) {
|
||||||
return entry['expires'];
|
return entry['expires'];
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -37,7 +37,9 @@ nf.ProvenanceTable = (function () {
|
||||||
provenance: '../nifi-api/controller/provenance',
|
provenance: '../nifi-api/controller/provenance',
|
||||||
cluster: '../nifi-api/cluster',
|
cluster: '../nifi-api/cluster',
|
||||||
d3Script: 'js/d3/d3.min.js',
|
d3Script: 'js/d3/d3.min.js',
|
||||||
lineageScript: 'js/nf/provenance/nf-provenance-lineage.js'
|
lineageScript: 'js/nf/provenance/nf-provenance-lineage.js',
|
||||||
|
uiExtensionToken: '../nifi-api/access/ui-extension-token',
|
||||||
|
downloadToken: '../nifi-api/access/download-token'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -78,17 +80,35 @@ nf.ProvenanceTable = (function () {
|
||||||
var eventId = $('#provenance-event-id').text();
|
var eventId = $('#provenance-event-id').text();
|
||||||
|
|
||||||
// build the url
|
// build the url
|
||||||
var url = config.urls.provenance + '/events/' + encodeURIComponent(eventId) + '/content/' + encodeURIComponent(direction);
|
var dataUri = config.urls.provenance + '/events/' + encodeURIComponent(eventId) + '/content/' + encodeURIComponent(direction);
|
||||||
|
|
||||||
|
// perform the request once we've received a token
|
||||||
|
nf.Common.getAccessToken(config.urls.downloadToken).done(function (downloadToken) {
|
||||||
|
var parameters = {};
|
||||||
|
|
||||||
|
// conditionally include the ui extension token
|
||||||
|
if (!nf.Common.isBlank(downloadToken)) {
|
||||||
|
parameters['access_token'] = downloadToken;
|
||||||
|
}
|
||||||
|
|
||||||
// conditionally include the cluster node id
|
// conditionally include the cluster node id
|
||||||
var clusterNodeId = $('#provenance-event-cluster-node-id').text();
|
var clusterNodeId = $('#provenance-event-cluster-node-id').text();
|
||||||
if (!nf.Common.isBlank(clusterNodeId)) {
|
if (!nf.Common.isBlank(clusterNodeId)) {
|
||||||
window.open(url + '?' + $.param({
|
parameters['clusterNodeId'] = clusterNodeId;
|
||||||
'clusterNodeId': clusterNodeId
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
window.open(url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// open the url
|
||||||
|
if ($.isEmptyObject(parameters)) {
|
||||||
|
window.open(dataUri);
|
||||||
|
} else {
|
||||||
|
window.open(dataUri + '?' + $.param(parameters));
|
||||||
|
}
|
||||||
|
}).fail(function () {
|
||||||
|
nf.Dialog.showOkDialog({
|
||||||
|
dialogContent: 'Unable to generate access token for downloading content.',
|
||||||
|
overlayBackground: false
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,14 +123,54 @@ nf.ProvenanceTable = (function () {
|
||||||
// build the uri to the data
|
// build the uri to the data
|
||||||
var dataUri = controllerUri + '/provenance/events/' + encodeURIComponent(eventId) + '/content/' + encodeURIComponent(direction);
|
var dataUri = controllerUri + '/provenance/events/' + encodeURIComponent(eventId) + '/content/' + encodeURIComponent(direction);
|
||||||
|
|
||||||
|
// generate tokens as necessary
|
||||||
|
var getAccessTokens = $.Deferred(function (deferred) {
|
||||||
|
if (nf.Storage.hasItem('jwt')) {
|
||||||
|
// generate a token for the ui extension and another for the callback
|
||||||
|
var uiExtensionToken = $.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: config.urls.uiExtensionToken
|
||||||
|
});
|
||||||
|
var downloadToken = $.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: config.urls.downloadToken
|
||||||
|
});
|
||||||
|
|
||||||
|
// wait for each token
|
||||||
|
$.when(uiExtensionToken, downloadToken).done(function (uiExtensionTokenResult, downloadTokenResult) {
|
||||||
|
var uiExtensionToken = uiExtensionTokenResult[0];
|
||||||
|
var downloadToken = downloadTokenResult[0];
|
||||||
|
deferred.resolve(uiExtensionToken, downloadToken);
|
||||||
|
}).fail(function () {
|
||||||
|
nf.Dialog.showOkDialog({
|
||||||
|
dialogContent: 'Unable to generate access token for viewing content.',
|
||||||
|
overlayBackground: false
|
||||||
|
});
|
||||||
|
deferred.reject();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
deferred.resolve('', '');
|
||||||
|
}
|
||||||
|
}).promise();
|
||||||
|
|
||||||
|
// perform the request after we've received the tokens
|
||||||
|
getAccessTokens.done(function (uiExtensionToken, downloadToken) {
|
||||||
|
var dataUriParameters = {};
|
||||||
|
|
||||||
// conditionally include the cluster node id
|
// conditionally include the cluster node id
|
||||||
var clusterNodeId = $('#provenance-event-cluster-node-id').text();
|
var clusterNodeId = $('#provenance-event-cluster-node-id').text();
|
||||||
if (!nf.Common.isBlank(clusterNodeId)) {
|
if (!nf.Common.isBlank(clusterNodeId)) {
|
||||||
var parameters = {
|
dataUriParameters['clusterNodeId'] = clusterNodeId;
|
||||||
'clusterNodeId': clusterNodeId
|
}
|
||||||
};
|
|
||||||
|
|
||||||
dataUri = dataUri + '?' + $.param(parameters);
|
// include the download token if applicable
|
||||||
|
if (!nf.Common.isBlank(downloadToken)) {
|
||||||
|
dataUriParameters['access_token'] = downloadToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// include parameters if necessary
|
||||||
|
if ($.isEmptyObject(dataUriParameters) === false) {
|
||||||
|
dataUri = dataUri + '?' + $.param(dataUriParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
// open the content viewer
|
// open the content viewer
|
||||||
|
@ -125,10 +185,18 @@ nf.ProvenanceTable = (function () {
|
||||||
contentViewerUrl += '&';
|
contentViewerUrl += '&';
|
||||||
}
|
}
|
||||||
|
|
||||||
// open the content viewer
|
var contentViewerParameters = {
|
||||||
window.open(contentViewerUrl + $.param({
|
|
||||||
'ref': dataUri
|
'ref': dataUri
|
||||||
}));
|
};
|
||||||
|
|
||||||
|
// include the download token if applicable
|
||||||
|
if (!nf.Common.isBlank(uiExtensionToken)) {
|
||||||
|
contentViewerParameters['access_token'] = uiExtensionToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// open the content viewer
|
||||||
|
window.open(contentViewerUrl + $.param(contentViewerParameters));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -28,7 +28,8 @@ nf.TemplatesTable = (function () {
|
||||||
filterList: 'templates-filter-list'
|
filterList: 'templates-filter-list'
|
||||||
},
|
},
|
||||||
urls: {
|
urls: {
|
||||||
templates: '../nifi-api/controller/templates'
|
templates: '../nifi-api/controller/templates',
|
||||||
|
downloadToken: '../nifi-api/access/download-token'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -150,6 +151,34 @@ nf.TemplatesTable = (function () {
|
||||||
return item[args.property].search(filterExp) >= 0;
|
return item[args.property].search(filterExp) >= 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads the specified template.
|
||||||
|
*
|
||||||
|
* @param {object} template The template
|
||||||
|
*/
|
||||||
|
var downloadTemplate = function (template) {
|
||||||
|
nf.Common.getAccessToken(config.urls.downloadToken).done(function (downloadToken) {
|
||||||
|
var parameters = {};
|
||||||
|
|
||||||
|
// conditionally include the download token
|
||||||
|
if (!nf.Common.isBlank(downloadToken)) {
|
||||||
|
parameters['access_token'] = downloadToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// open the url
|
||||||
|
if ($.isEmptyObject(parameters)) {
|
||||||
|
window.open(config.urls.templates + '/' + encodeURIComponent(template.id));
|
||||||
|
} else {
|
||||||
|
window.open(config.urls.templates + '/' + encodeURIComponent(template.id) + '?' + $.param(parameters));
|
||||||
|
}
|
||||||
|
}).fail(function () {
|
||||||
|
nf.Dialog.showOkDialog({
|
||||||
|
dialogContent: 'Unable to generate access token for downloading content.',
|
||||||
|
overlayBackground: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* Initializes the templates list.
|
* Initializes the templates list.
|
||||||
|
@ -262,7 +291,7 @@ nf.TemplatesTable = (function () {
|
||||||
// determine the desired action
|
// determine the desired action
|
||||||
if (templatesGrid.getColumns()[args.cell].id === 'actions') {
|
if (templatesGrid.getColumns()[args.cell].id === 'actions') {
|
||||||
if (target.hasClass('export-template')) {
|
if (target.hasClass('export-template')) {
|
||||||
window.open(config.urls.templates + '/' + encodeURIComponent(item.id));
|
downloadTemplate(item);
|
||||||
} else if (target.hasClass('prompt-to-delete-template')) {
|
} else if (target.hasClass('prompt-to-delete-template')) {
|
||||||
promptToDeleteTemplate(item);
|
promptToDeleteTemplate(item);
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,7 +148,7 @@ public class RuleResource {
|
||||||
try {
|
try {
|
||||||
criteria.reorder(requestEntity.getRuleOrder());
|
criteria.reorder(requestEntity.getRuleOrder());
|
||||||
} catch (final IllegalArgumentException iae) {
|
} catch (final IllegalArgumentException iae) {
|
||||||
throw new WebApplicationException(badRequest(iae.getMessage()));
|
throw new WebApplicationException(iae, badRequest(iae.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ public class RuleResource {
|
||||||
try {
|
try {
|
||||||
criteria.setFlowFilePolicy(FlowFilePolicy.valueOf(requestEntity.getFlowFilePolicy()));
|
criteria.setFlowFilePolicy(FlowFilePolicy.valueOf(requestEntity.getFlowFilePolicy()));
|
||||||
} catch (final IllegalArgumentException iae) {
|
} catch (final IllegalArgumentException iae) {
|
||||||
throw new WebApplicationException(badRequest("The specified matching strategy is unknown: " + requestEntity.getFlowFilePolicy()));
|
throw new WebApplicationException(iae, badRequest("The specified matching strategy is unknown: " + requestEntity.getFlowFilePolicy()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +227,7 @@ public class RuleResource {
|
||||||
rule = factory.createRule(ruleDto);
|
rule = factory.createRule(ruleDto);
|
||||||
rule.setId(uuid);
|
rule.setId(uuid);
|
||||||
} catch (final IllegalArgumentException iae) {
|
} catch (final IllegalArgumentException iae) {
|
||||||
throw new WebApplicationException(badRequest(iae.getMessage()));
|
throw new WebApplicationException(iae, badRequest(iae.getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the rule
|
// add the rule
|
||||||
|
@ -268,7 +268,7 @@ public class RuleResource {
|
||||||
condition = factory.createCondition(requestEntity.getCondition());
|
condition = factory.createCondition(requestEntity.getCondition());
|
||||||
condition.setId(uuid);
|
condition.setId(uuid);
|
||||||
} catch (final IllegalArgumentException iae) {
|
} catch (final IllegalArgumentException iae) {
|
||||||
throw new WebApplicationException(badRequest(iae.getMessage()));
|
throw new WebApplicationException(iae, badRequest(iae.getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// build the response
|
// build the response
|
||||||
|
@ -302,7 +302,7 @@ public class RuleResource {
|
||||||
action = factory.createAction(requestEntity.getAction());
|
action = factory.createAction(requestEntity.getAction());
|
||||||
action.setId(uuid);
|
action.setId(uuid);
|
||||||
} catch (final IllegalArgumentException iae) {
|
} catch (final IllegalArgumentException iae) {
|
||||||
throw new WebApplicationException(badRequest(iae.getMessage()));
|
throw new WebApplicationException(iae, badRequest(iae.getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// build the response
|
// build the response
|
||||||
|
@ -522,7 +522,7 @@ public class RuleResource {
|
||||||
rule.setConditions(conditions);
|
rule.setConditions(conditions);
|
||||||
rule.setActions(actions);
|
rule.setActions(actions);
|
||||||
} catch (final IllegalArgumentException iae) {
|
} catch (final IllegalArgumentException iae) {
|
||||||
throw new WebApplicationException(badRequest(iae.getMessage()));
|
throw new WebApplicationException(iae, badRequest(iae.getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the new rule if application
|
// add the new rule if application
|
||||||
|
@ -598,11 +598,11 @@ public class RuleResource {
|
||||||
// load the processor configuration
|
// load the processor configuration
|
||||||
processorDetails = configurationContext.getComponentDetails(requestContext);
|
processorDetails = configurationContext.getComponentDetails(requestContext);
|
||||||
} catch (final InvalidRevisionException ire) {
|
} catch (final InvalidRevisionException ire) {
|
||||||
throw new WebApplicationException(invalidRevision(ire.getMessage()));
|
throw new WebApplicationException(ire, invalidRevision(ire.getMessage()));
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
final String message = String.format("Unable to get UpdateAttribute[id=%s] criteria: %s", requestContext.getId(), e);
|
final String message = String.format("Unable to get UpdateAttribute[id=%s] criteria: %s", requestContext.getId(), e);
|
||||||
logger.error(message, e);
|
logger.error(message, e);
|
||||||
throw new WebApplicationException(error(message));
|
throw new WebApplicationException(e, error(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
Criteria criteria = null;
|
Criteria criteria = null;
|
||||||
|
@ -612,7 +612,7 @@ public class RuleResource {
|
||||||
} catch (final IllegalArgumentException iae) {
|
} catch (final IllegalArgumentException iae) {
|
||||||
final String message = String.format("Unable to deserialize existing rules for UpdateAttribute[id=%s]. Deserialization error: %s", requestContext.getId(), iae);
|
final String message = String.format("Unable to deserialize existing rules for UpdateAttribute[id=%s]. Deserialization error: %s", requestContext.getId(), iae);
|
||||||
logger.error(message, iae);
|
logger.error(message, iae);
|
||||||
throw new WebApplicationException(error(message));
|
throw new WebApplicationException(iae, error(message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ensure the criteria isn't null
|
// ensure the criteria isn't null
|
||||||
|
@ -634,11 +634,11 @@ public class RuleResource {
|
||||||
// save the annotation data
|
// save the annotation data
|
||||||
configurationContext.setAnnotationData(requestContext, annotationData);
|
configurationContext.setAnnotationData(requestContext, annotationData);
|
||||||
} catch (final InvalidRevisionException ire) {
|
} catch (final InvalidRevisionException ire) {
|
||||||
throw new WebApplicationException(invalidRevision(ire.getMessage()));
|
throw new WebApplicationException(ire, invalidRevision(ire.getMessage()));
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
final String message = String.format("Unable to save UpdateAttribute[id=%s] criteria: %s", requestContext.getId(), e);
|
final String message = String.format("Unable to save UpdateAttribute[id=%s] criteria: %s", requestContext.getId(), e);
|
||||||
logger.error(message, e);
|
logger.error(message, e);
|
||||||
throw new WebApplicationException(error(message));
|
throw new WebApplicationException(e, error(message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,9 @@
|
||||||
<script type="text/javascript" src="../nifi/js/jquery/slickgrid/slick.grid.js"></script>
|
<script type="text/javascript" src="../nifi/js/jquery/slickgrid/slick.grid.js"></script>
|
||||||
<script type="text/javascript" src="../nifi/js/codemirror/lib/codemirror-compressed.js"></script>
|
<script type="text/javascript" src="../nifi/js/codemirror/lib/codemirror-compressed.js"></script>
|
||||||
<script type="text/javascript" src="../nifi/js/nf/nf-namespace.js"></script>
|
<script type="text/javascript" src="../nifi/js/nf/nf-namespace.js"></script>
|
||||||
|
<script type="text/javascript" src="../nifi/js/nf/nf-common.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" src="../nifi/js/nf/nf-universal-capture.js"></script>
|
||||||
<script type="text/javascript" src="../nifi/js/jquery/nfeditor/languages/nfel.js"></script>
|
<script type="text/javascript" src="../nifi/js/jquery/nfeditor/languages/nfel.js"></script>
|
||||||
<script type="text/javascript" src="../nifi/js/jquery/nfeditor/jquery.nfeditor.js"></script>
|
<script type="text/javascript" src="../nifi/js/jquery/nfeditor/jquery.nfeditor.js"></script>
|
||||||
|
|
Loading…
Reference in New Issue