mirror of https://github.com/apache/nifi.git
NIFI-655:
- Ensuring we know the necessary state before we attempt to render the login page. - Building the proxy chain in the JWT authentication filter. - Only rendering the login when appropriate.
This commit is contained in:
parent
71d84117e4
commit
ed27ed0449
|
@ -156,6 +156,8 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
|
|||
final JwtAuthenticationFilter jwtFilter = new JwtAuthenticationFilter();
|
||||
jwtFilter.setProperties(properties);
|
||||
jwtFilter.setJwtService(jwtService);
|
||||
jwtFilter.setCertificateExtractor(certificateExtractor);
|
||||
jwtFilter.setPrincipalExtractor(principalExtractor);
|
||||
jwtFilter.setAuthenticationManager(authenticationManager());
|
||||
return jwtFilter;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ package org.apache.nifi.web.security;
|
|||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
|
@ -75,17 +77,27 @@ public class RegistrationStatusFilter extends AbstractAuthenticationProcessingFi
|
|||
if (certificate == null) {
|
||||
final LoginCredentials credentials = getLoginCredentials(request);
|
||||
|
||||
// ensure we have something we can work with (certificate or crendentials)
|
||||
if (credentials == null) {
|
||||
throw new BadCredentialsException("Unable to check registration status as no credentials were included with the request.");
|
||||
}
|
||||
|
||||
checkAuthorization(ProxiedEntitiesUtils.buildProxyChain(request, credentials.getUsername()));
|
||||
// without a certificate, this is not a proxied request
|
||||
final List<String> chain = Arrays.asList(credentials.getUsername());
|
||||
|
||||
// check authorization for this user
|
||||
checkAuthorization(chain);
|
||||
|
||||
// no issues with authorization
|
||||
return new RegistrationStatusAuthenticationToken(credentials);
|
||||
} else {
|
||||
// we have a certificate so let's use that
|
||||
// TODO - certificate validation
|
||||
|
||||
// we have a certificate so let's consider a proxy chain
|
||||
final String principal = extractPrincipal(certificate);
|
||||
checkAuthorization(ProxiedEntitiesUtils.buildProxyChain(request, principal));
|
||||
|
||||
// no issues with authorization
|
||||
final LoginCredentials preAuthenticatedCredentials = new LoginCredentials(principal, null);
|
||||
return new RegistrationStatusAuthenticationToken(preAuthenticatedCredentials);
|
||||
}
|
||||
|
|
|
@ -83,6 +83,8 @@ public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingF
|
|||
if (certificate == null) {
|
||||
throw new PreAuthenticatedCredentialsNotFoundException("Unable to extract client certificate after processing request with no login credentials specified.");
|
||||
}
|
||||
|
||||
// TODO - certificate validation
|
||||
|
||||
// authorize the proxy if necessary
|
||||
final String principal = extractPrincipal(certificate);
|
||||
|
@ -96,6 +98,8 @@ public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingF
|
|||
|
||||
// if there was a certificate with this request see if it was proxying an end user request
|
||||
if (certificate != null) {
|
||||
// TODO - certificate validation
|
||||
|
||||
// authorize the proxy if necessary
|
||||
final String principal = extractPrincipal(certificate);
|
||||
authorizeProxyIfNecessary(ProxiedEntitiesUtils.buildProxyChain(request, principal));
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package org.apache.nifi.web.security.jwt;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
@ -24,9 +26,12 @@ import org.apache.nifi.web.security.ProxiedEntitiesUtils;
|
|||
import org.apache.nifi.web.security.token.NewAccountAuthenticationRequestToken;
|
||||
import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken;
|
||||
import org.apache.nifi.web.security.user.NewAccountRequest;
|
||||
import org.apache.nifi.web.security.x509.X509CertificateExtractor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
|
||||
|
||||
/**
|
||||
*/
|
||||
|
@ -34,6 +39,8 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
|
|||
|
||||
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
|
||||
|
||||
private X509CertificateExtractor certificateExtractor;
|
||||
private X509PrincipalExtractor principalExtractor;
|
||||
private JwtService jwtService;
|
||||
|
||||
@Override
|
||||
|
@ -43,16 +50,39 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
|
|||
return null;
|
||||
}
|
||||
|
||||
final String principal = jwtService.getAuthentication(request);
|
||||
if (principal == null) {
|
||||
// get the principal out of the user token
|
||||
final String jwtPrincipal = jwtService.getAuthentication(request);
|
||||
if (jwtPrincipal == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final List<String> proxyChain = ProxiedEntitiesUtils.buildProxyChain(request, principal);
|
||||
if (isNewAccountRequest(request)) {
|
||||
return new NewAccountAuthenticationRequestToken(new NewAccountRequest(proxyChain, getJustification(request)));
|
||||
// look for a certificate
|
||||
final X509Certificate certificate = certificateExtractor.extractClientCertificate(request);
|
||||
|
||||
final List<String> chain;
|
||||
if (certificate == null) {
|
||||
// without a certificate, this is not a proxied request
|
||||
chain = Arrays.asList(jwtPrincipal);
|
||||
} else {
|
||||
return new NiFiAuthenticationRequestToken(proxyChain);
|
||||
// TODO - certificate validation
|
||||
|
||||
// extract the principal
|
||||
Object certificatePrincipal = principalExtractor.extractPrincipal(certificate);
|
||||
final String principal = ProxiedEntitiesUtils.formatProxyDn(certificatePrincipal.toString());
|
||||
|
||||
// get the proxy chain and verify the principal is found
|
||||
chain = ProxiedEntitiesUtils.buildProxyChain(request, principal);
|
||||
|
||||
// ensure the chain contains the jwt principal
|
||||
if (!chain.contains(jwtPrincipal)) {
|
||||
throw new BadCredentialsException("Principal in user token not found in the proxy chain.");
|
||||
}
|
||||
}
|
||||
|
||||
if (isNewAccountRequest(request)) {
|
||||
return new NewAccountAuthenticationRequestToken(new NewAccountRequest(chain, getJustification(request)));
|
||||
} else {
|
||||
return new NiFiAuthenticationRequestToken(chain);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,4 +90,12 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
|
|||
this.jwtService = jwtService;
|
||||
}
|
||||
|
||||
public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) {
|
||||
this.certificateExtractor = certificateExtractor;
|
||||
}
|
||||
|
||||
public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) {
|
||||
this.principalExtractor = principalExtractor;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -139,7 +139,9 @@ nf.CanvasHeader = (function () {
|
|||
nf.Shell.showPage(config.urls.helpDocument);
|
||||
});
|
||||
|
||||
if (supportsLogin === true) {
|
||||
// show the login link if supported and user is currently anonymous
|
||||
var isAnonymous = $('#current-user').text() === 'anonymous';
|
||||
if (supportsLogin === true && isAnonymous) {
|
||||
// login link
|
||||
$('#login-link').click(function () {
|
||||
nf.Shell.showPage('login', false);
|
||||
|
|
|
@ -1076,121 +1076,123 @@ nf.Canvas = (function () {
|
|||
// there is no anonymous access and we don't know this user - open the login page which handles login/registration/etc
|
||||
if (xhr.status === 401) {
|
||||
window.location = '/nifi/login';
|
||||
}
|
||||
|
||||
deferred.reject(xhr, status, error);
|
||||
});
|
||||
}).promise();
|
||||
|
||||
// get the controller config to register the status poller
|
||||
var configXhr = $.ajax({
|
||||
type: 'GET',
|
||||
url: config.urls.controllerConfig,
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
// get the login config
|
||||
var loginXhr = $.ajax({
|
||||
type: 'GET',
|
||||
url: config.urls.loginConfig,
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
// create the deferred cluster request
|
||||
var isClusteredRequest = $.Deferred(function (deferred) {
|
||||
$.ajax({
|
||||
type: 'HEAD',
|
||||
url: config.urls.cluster
|
||||
}).done(function (response, status, xhr) {
|
||||
clustered = true;
|
||||
deferred.resolve(response, status, xhr);
|
||||
}).fail(function (xhr, status, error) {
|
||||
if (xhr.status === 404) {
|
||||
clustered = false;
|
||||
deferred.resolve('', 'success', xhr);
|
||||
} else {
|
||||
deferred.reject(xhr, status, error);
|
||||
}
|
||||
});
|
||||
}).promise();
|
||||
|
||||
userXhr.done(function () {
|
||||
// get the controller config to register the status poller
|
||||
var configXhr = $.ajax({
|
||||
type: 'GET',
|
||||
url: config.urls.controllerConfig,
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
// ensure the config requests are loaded
|
||||
$.when(configXhr, loginXhr, userXhr).done(function (configResult, loginResult) {
|
||||
var configResponse = configResult[0];
|
||||
var loginResponse = loginResult[0];
|
||||
// get the login config
|
||||
var loginXhr = $.ajax({
|
||||
type: 'GET',
|
||||
url: config.urls.loginConfig,
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
// calculate the canvas offset
|
||||
var canvasContainer = $('#canvas-container');
|
||||
nf.Canvas.CANVAS_OFFSET = canvasContainer.offset().top;
|
||||
// create the deferred cluster request
|
||||
var isClusteredRequest = $.Deferred(function (deferred) {
|
||||
$.ajax({
|
||||
type: 'HEAD',
|
||||
url: config.urls.cluster
|
||||
}).done(function (response, status, xhr) {
|
||||
clustered = true;
|
||||
deferred.resolve(response, status, xhr);
|
||||
}).fail(function (xhr, status, error) {
|
||||
if (xhr.status === 404) {
|
||||
clustered = false;
|
||||
deferred.resolve('', 'success', xhr);
|
||||
} else {
|
||||
deferred.reject(xhr, status, error);
|
||||
}
|
||||
});
|
||||
}).promise();
|
||||
|
||||
// ensure the config requests are loaded
|
||||
$.when(configXhr, loginXhr, userXhr).done(function (configResult, loginResult) {
|
||||
var configResponse = configResult[0];
|
||||
var loginResponse = loginResult[0];
|
||||
|
||||
// get the config details
|
||||
var configDetails = configResponse.config;
|
||||
var loginDetails = loginResponse.config;
|
||||
// calculate the canvas offset
|
||||
var canvasContainer = $('#canvas-container');
|
||||
nf.Canvas.CANVAS_OFFSET = canvasContainer.offset().top;
|
||||
|
||||
// when both request complete, load the application
|
||||
isClusteredRequest.done(function () {
|
||||
// get the auto refresh interval
|
||||
var autoRefreshIntervalSeconds = parseInt(configDetails.autoRefreshIntervalSeconds, 10);
|
||||
// get the config details
|
||||
var configDetails = configResponse.config;
|
||||
var loginDetails = loginResponse.config;
|
||||
|
||||
// initialize whether site to site is secure
|
||||
secureSiteToSite = configDetails.siteToSiteSecure;
|
||||
// when both request complete, load the application
|
||||
isClusteredRequest.done(function () {
|
||||
// get the auto refresh interval
|
||||
var autoRefreshIntervalSeconds = parseInt(configDetails.autoRefreshIntervalSeconds, 10);
|
||||
|
||||
// load d3
|
||||
loadD3().done(function () {
|
||||
nf.Storage.init();
|
||||
// initialize whether site to site is secure
|
||||
secureSiteToSite = configDetails.siteToSiteSecure;
|
||||
|
||||
// initialize the application
|
||||
initCanvas();
|
||||
nf.Canvas.View.init();
|
||||
nf.ContextMenu.init();
|
||||
nf.CanvasToolbar.init();
|
||||
nf.CanvasToolbox.init();
|
||||
nf.CanvasHeader.init(loginDetails.supportsLogin);
|
||||
nf.GraphControl.init();
|
||||
nf.Search.init();
|
||||
nf.Settings.init();
|
||||
// load d3
|
||||
loadD3().done(function () {
|
||||
nf.Storage.init();
|
||||
|
||||
// initialize the component behaviors
|
||||
nf.Draggable.init();
|
||||
nf.Selectable.init();
|
||||
nf.Connectable.init();
|
||||
// initialize the application
|
||||
initCanvas();
|
||||
nf.Canvas.View.init();
|
||||
nf.ContextMenu.init();
|
||||
nf.CanvasToolbar.init();
|
||||
nf.CanvasToolbox.init();
|
||||
nf.CanvasHeader.init(loginDetails.supportsLogin);
|
||||
nf.GraphControl.init();
|
||||
nf.Search.init();
|
||||
nf.Settings.init();
|
||||
|
||||
// initialize the chart
|
||||
nf.StatusHistory.init(configDetails.timeOffset);
|
||||
// initialize the component behaviors
|
||||
nf.Draggable.init();
|
||||
nf.Selectable.init();
|
||||
nf.Connectable.init();
|
||||
|
||||
// initialize the birdseye
|
||||
nf.Birdseye.init();
|
||||
// initialize the chart
|
||||
nf.StatusHistory.init(configDetails.timeOffset);
|
||||
|
||||
// initialize components
|
||||
nf.ConnectionConfiguration.init();
|
||||
nf.ControllerService.init();
|
||||
nf.ReportingTask.init();
|
||||
nf.ProcessorConfiguration.init();
|
||||
nf.ProcessGroupConfiguration.init();
|
||||
nf.RemoteProcessGroupConfiguration.init();
|
||||
nf.RemoteProcessGroupPorts.init();
|
||||
nf.PortConfiguration.init();
|
||||
nf.SecurePortConfiguration.init();
|
||||
nf.LabelConfiguration.init();
|
||||
nf.ProcessorDetails.init();
|
||||
nf.ProcessGroupDetails.init();
|
||||
nf.PortDetails.init();
|
||||
nf.SecurePortDetails.init();
|
||||
nf.ConnectionDetails.init();
|
||||
nf.RemoteProcessGroupDetails.init();
|
||||
nf.GoTo.init();
|
||||
nf.Graph.init().done(function () {
|
||||
// determine the split between the polling
|
||||
var pollingSplit = autoRefreshIntervalSeconds / 2;
|
||||
// initialize the birdseye
|
||||
nf.Birdseye.init();
|
||||
|
||||
// register the revision and status polling
|
||||
startRevisionPolling(autoRefreshIntervalSeconds);
|
||||
setTimeout(function () {
|
||||
startStatusPolling(autoRefreshIntervalSeconds);
|
||||
}, pollingSplit * 1000);
|
||||
// initialize components
|
||||
nf.ConnectionConfiguration.init();
|
||||
nf.ControllerService.init();
|
||||
nf.ReportingTask.init();
|
||||
nf.ProcessorConfiguration.init();
|
||||
nf.ProcessGroupConfiguration.init();
|
||||
nf.RemoteProcessGroupConfiguration.init();
|
||||
nf.RemoteProcessGroupPorts.init();
|
||||
nf.PortConfiguration.init();
|
||||
nf.SecurePortConfiguration.init();
|
||||
nf.LabelConfiguration.init();
|
||||
nf.ProcessorDetails.init();
|
||||
nf.ProcessGroupDetails.init();
|
||||
nf.PortDetails.init();
|
||||
nf.SecurePortDetails.init();
|
||||
nf.ConnectionDetails.init();
|
||||
nf.RemoteProcessGroupDetails.init();
|
||||
nf.GoTo.init();
|
||||
nf.Graph.init().done(function () {
|
||||
// determine the split between the polling
|
||||
var pollingSplit = autoRefreshIntervalSeconds / 2;
|
||||
|
||||
// hide the splash screen
|
||||
nf.Canvas.hideSplash();
|
||||
// register the revision and status polling
|
||||
startRevisionPolling(autoRefreshIntervalSeconds);
|
||||
setTimeout(function () {
|
||||
startStatusPolling(autoRefreshIntervalSeconds);
|
||||
}, pollingSplit * 1000);
|
||||
|
||||
// hide the splash screen
|
||||
nf.Canvas.hideSplash();
|
||||
}).fail(nf.Common.handleAjaxError);
|
||||
}).fail(nf.Common.handleAjaxError);
|
||||
}).fail(nf.Common.handleAjaxError);
|
||||
}).fail(nf.Common.handleAjaxError);
|
||||
|
|
|
@ -231,6 +231,8 @@ nf.Login = (function () {
|
|||
}).fail(function () {
|
||||
// no token granted, user needs to login with their credentials
|
||||
needsLogin = true;
|
||||
}).always(function () {
|
||||
deferred.resolve();
|
||||
});
|
||||
} else {
|
||||
showMessage = true;
|
||||
|
@ -241,13 +243,14 @@ nf.Login = (function () {
|
|||
} else {
|
||||
$('#login-message').text(xhr.responseText);
|
||||
}
|
||||
|
||||
deferred.resolve();
|
||||
}
|
||||
deferred.resolve();
|
||||
});
|
||||
}).promise();
|
||||
|
||||
// render the page accordingly
|
||||
$.when(pageStateInit).done(function () {
|
||||
pageStateInit.done(function () {
|
||||
if (showMessage === true) {
|
||||
initializeMessage();
|
||||
} else if (needsLogin === true) {
|
||||
|
|
Loading…
Reference in New Issue