NIFI-655:

- Updating the login API authenticate method to use a richer set of exceptions.
- UI code clean.
This commit is contained in:
Matt Gilman 2015-11-10 14:55:02 -05:00
parent 2e158d15be
commit e61ccea7a0
8 changed files with 147 additions and 133 deletions

View File

@ -17,6 +17,7 @@
package org.apache.nifi.authentication; package org.apache.nifi.authentication;
import org.apache.nifi.authentication.exception.IdentityAccessException; import org.apache.nifi.authentication.exception.IdentityAccessException;
import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException;
import org.apache.nifi.authorization.exception.ProviderCreationException; import org.apache.nifi.authorization.exception.ProviderCreationException;
import org.apache.nifi.authorization.exception.ProviderDestructionException; import org.apache.nifi.authorization.exception.ProviderDestructionException;
@ -29,10 +30,10 @@ public interface LoginIdentityProvider {
* Authenticates the specified login credentials. * Authenticates the specified login credentials.
* *
* @param credentials the credentials * @param credentials the credentials
* @return was able to check the user credentials and returns whether the user was authenticated * @throws InvalidLoginCredentialsException The login credentials were invalid
* @throws IdentityAccessException Unable to register the user due to an issue accessing the underlying storage * @throws IdentityAccessException Unable to register the user due to an issue accessing the underlying storage
*/ */
boolean authenticate(LoginCredentials credentials) throws IdentityAccessException; void authenticate(LoginCredentials credentials) throws InvalidLoginCredentialsException, IdentityAccessException;
/** /**
* Called immediately after instance creation for implementers to perform additional setup * Called immediately after instance creation for implementers to perform additional setup

View File

@ -0,0 +1,33 @@
/*
* 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.authentication.exception;
/**
* Represents the case when the identity could not be confirmed because the
* login credentials were invalid.
*/
public class InvalidLoginCredentialsException extends RuntimeException {
public InvalidLoginCredentialsException(String message, Throwable cause) {
super(message, cause);
}
public InvalidLoginCredentialsException(String message) {
super(message);
}
}

View File

@ -30,6 +30,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.nifi.authentication.LoginCredentials; import org.apache.nifi.authentication.LoginCredentials;
import org.apache.nifi.authentication.LoginIdentityProvider; import org.apache.nifi.authentication.LoginIdentityProvider;
import org.apache.nifi.authentication.exception.IdentityAccessException; import org.apache.nifi.authentication.exception.IdentityAccessException;
import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException;
import org.apache.nifi.util.StringUtils; import org.apache.nifi.util.StringUtils;
import org.apache.nifi.web.security.ProxiedEntitiesUtils; import org.apache.nifi.web.security.ProxiedEntitiesUtils;
import org.apache.nifi.web.security.jwt.JwtService; import org.apache.nifi.web.security.jwt.JwtService;
@ -142,11 +143,13 @@ public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingF
} }
try { try {
if (loginIdentityProvider.authenticate(credentials)) { // attempt to authenticate
loginIdentityProvider.authenticate(credentials);
// create the authentication token
return new LoginAuthenticationToken(credentials); return new LoginAuthenticationToken(credentials);
} else { } catch (final InvalidLoginCredentialsException ilce) {
throw new BadCredentialsException("The supplied username and password are not valid."); throw new BadCredentialsException("The supplied username and password are not valid.", ilce);
}
} catch (final IdentityAccessException iae) { } catch (final IdentityAccessException iae) {
throw new AuthenticationServiceException(iae.getMessage(), iae); throw new AuthenticationServiceException(iae.getMessage(), iae);
} }
@ -195,11 +198,6 @@ public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingF
@Override @Override
protected void unsuccessfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException failed) throws IOException, ServletException { protected void unsuccessfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException failed) throws IOException, ServletException {
response.setContentType("text/plain");
final PrintWriter out = response.getWriter();
out.println(failed.getMessage());
if (failed instanceof BadCredentialsException || failed instanceof AuthenticationCredentialsNotFoundException) { if (failed instanceof BadCredentialsException || failed instanceof AuthenticationCredentialsNotFoundException) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
} else if (failed instanceof AuthenticationServiceException) { } else if (failed instanceof AuthenticationServiceException) {
@ -207,6 +205,11 @@ public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingF
} else { } else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
} }
response.setContentType("text/plain");
final PrintWriter out = response.getWriter();
out.println(failed.getMessage());
} }
public void setJwtService(JwtService jwtService) { public void setJwtService(JwtService jwtService) {

View File

@ -259,9 +259,9 @@ public class LoginIdentityProviderFactoryBean implements FactoryBean, Disposable
return new LoginIdentityProvider() { return new LoginIdentityProvider() {
@Override @Override
public boolean authenticate(LoginCredentials credentials) { public void authenticate(LoginCredentials credentials) {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseProvider.authenticate(credentials); baseProvider.authenticate(credentials);
} }
} }

View File

@ -141,15 +141,21 @@ nf.CanvasHeader = (function () {
nf.Shell.showPage(config.urls.helpDocument); nf.Shell.showPage(config.urls.helpDocument);
}); });
// hide the login link if the user is already logged in // show the login link if supported and user is currently anonymous
if ($('#current-user').text() !== nf.Canvas.ANONYMOUS_USER_TEXT) { var isAnonymous = $('#current-user').text() === nf.Canvas.ANONYMOUS_USER_TEXT;
$('#login-link-container').css('display', 'none'); if (supportsLogin === true && isAnonymous) {
}
// login link // login link
$('#login-link').click(function () { $('#login-link').click(function () {
nf.Shell.showPage('login', false); nf.Shell.showPage('login', false);
}); });
} else {
$('#login-link-container').css('display', 'none');
}
// if login is not supported, don't show the current user
if (supportsLogin === false) {
$('#current-user-container').css('display', 'none');
}
// logout link // logout link
$('#logout-link').click(function () { $('#logout-link').click(function () {
@ -344,7 +350,6 @@ nf.CanvasHeader = (function () {
toolbar.find('.secondary').hide(); toolbar.find('.secondary').hide();
} }
}, },
/** /**
* Reloads and clears any warnings. * Reloads and clears any warnings.
*/ */

View File

@ -1087,6 +1087,13 @@ nf.Canvas = (function () {
dataType: 'json' dataType: 'json'
}); });
// get the login config
var loginXhr = $.ajax({
type: 'GET',
url: config.urls.loginConfig,
dataType: 'json'
});
// create the deferred cluster request // create the deferred cluster request
var isClusteredRequest = $.Deferred(function (deferred) { var isClusteredRequest = $.Deferred(function (deferred) {
$.ajax({ $.ajax({
@ -1106,8 +1113,9 @@ nf.Canvas = (function () {
}).promise(); }).promise();
// ensure the config requests are loaded // ensure the config requests are loaded
$.when(configXhr, userXhr).done(function (configResult) { $.when(configXhr, loginXhr, userXhr).done(function (configResult, loginResult) {
var configResponse = configResult[0]; var configResponse = configResult[0];
var loginResponse = loginResult[0];
// calculate the canvas offset // calculate the canvas offset
var canvasContainer = $('#canvas-container'); var canvasContainer = $('#canvas-container');
@ -1115,6 +1123,7 @@ nf.Canvas = (function () {
// get the config details // get the config details
var configDetails = configResponse.config; var configDetails = configResponse.config;
var loginDetails = loginResponse.config;
// when both request complete, load the application // when both request complete, load the application
isClusteredRequest.done(function () { isClusteredRequest.done(function () {
@ -1134,7 +1143,7 @@ nf.Canvas = (function () {
nf.ContextMenu.init(); nf.ContextMenu.init();
nf.CanvasToolbar.init(); nf.CanvasToolbar.init();
nf.CanvasToolbox.init(); nf.CanvasToolbox.init();
nf.CanvasHeader.init(); nf.CanvasHeader.init(loginDetails.supportsLogin);
nf.GraphControl.init(); nf.GraphControl.init();
nf.Search.init(); nf.Search.init();
nf.Settings.init(); nf.Settings.init();

View File

@ -40,17 +40,18 @@ nf.Login = (function () {
$('#login-message-container').show(); $('#login-message-container').show();
}; };
var initializeLogin = function () {
};
var showLogin = function () { var showLogin = function () {
$('#login-container').show(); // reset the forms
$('#user-registration-container').hide(); $('#username').val('');
$('#nifi-registration-container').hide(); $('#password').val('');
$('#login-submission-button').text('Log in'); $('#login-submission-button').text('Log in');
};
var initializeUserRegistration = function () { // update the form visibility
$('#login-container').show();
$('#nifi-registration-container').hide();
// set the focus
$('#username').focus();
}; };
var initializeNiFiRegistration = function () { var initializeNiFiRegistration = function () {
@ -64,26 +65,20 @@ nf.Login = (function () {
}); });
}; };
var showUserRegistration = function () {
showNiFiRegistration();
$('div.nifi-submit-justification').hide();
$('#user-registration-container').show();
$('#login-submission-button').text('Create');
};
var showNiFiRegistration = function () { var showNiFiRegistration = function () {
// reset the forms
$('#login-submission-button').text('Submit');
$('#nifi-registration-justification').val('');
// update the form visibility
$('#login-container').hide(); $('#login-container').hide();
$('#nifi-registration-container').show(); $('#nifi-registration-container').show();
$('#login-submission-button').text('Submit');
}; };
var initializeSubmission = function () { var initializeSubmission = function () {
$('#login-submission-button').on('click', function () { $('#login-submission-button').on('click', function () {
if ($('#login-container').is(':visible')) { if ($('#login-container').is(':visible')) {
login(); login();
} else if ($('#user-registration-container').is(':visible')) {
createUserAccount();
} else if ($('#nifi-registration-container').is(':visible')) { } else if ($('#nifi-registration-container').is(':visible')) {
submitJustification(); submitJustification();
} }
@ -105,12 +100,50 @@ nf.Login = (function () {
// store the jwt and reload the page // store the jwt and reload the page
nf.Storage.setItem('jwt', jwt); nf.Storage.setItem('jwt', jwt);
// check to see if they actually have access now
$.ajax({
type: 'GET',
url: config.urls.identity,
dataType: 'json'
}).done(function (response) {
if (response.identity === 'anonymous') {
showLogoutLink();
// show the user
var user = getJwtSubject(jwt);
$('#nifi-user-submit-justification').text(user);
// show the registration form
initializeNiFiRegistration();
showNiFiRegistration();
} else {
// reload as appropriate // reload as appropriate
if (top !== window) { if (top !== window) {
parent.window.location = '/nifi'; parent.window.location = '/nifi';
} else { } else {
window.location = '/nifi'; window.location = '/nifi';
} }
}
}).fail(function (xhr, status, error) {
showLogoutLink();
// show the user
var user = getJwtSubject(jwt);
$('#nifi-user-submit-justification').text(user);
if (xhr.status === 401) {
initializeNiFiRegistration();
showNiFiRegistration();
} else {
$('#login-message-title').text('Unable to log in');
$('#login-message').text(xhr.responseText);
// update visibility
$('#login-container').hide();
$('#login-submission-container').hide();
$('#login-message-container').show();
}
});
}).fail(function (xhr, status, error) { }).fail(function (xhr, status, error) {
if (xhr.status === 400) { if (xhr.status === 400) {
nf.Dialog.showOkDialog({ nf.Dialog.showOkDialog({
@ -129,53 +162,6 @@ nf.Login = (function () {
}); });
}; };
var createUserAccount = function () {
var password = $('#registration-password').val();
var passwordConfirmation = $('#registration-password-confirmation').val();
// ensure the password matches
if (password !== passwordConfirmation) {
nf.Dialog.showOkDialog({
dialogContent: 'The specified passwords do not match.',
overlayBackground: false
});
return;
}
// attempt to create the user account registration
$.ajax({
type: 'POST',
url: config.urls.registration,
data: {
'username': $('#registration-username').val(),
'password': password,
'justification': $('#nifi-registration-justification').val()
}
}).done(function (jwt) {
// store the jwt
nf.Storage.setItem('jwt', jwt);
showLogoutLink();
// inform the user of their pending request
var markup = 'An administrator will process your request shortly.';
if (supportsAnonymous === true) {
markup += '<br/><br/>In the meantime you can continue accessing anonymously.';
}
$('#login-message-title').text('Thanks!');
$('#login-message').html(markup);
}).fail(function (xhr, status, error) {
$('#login-message-title').text('Unable to create user account');
$('#login-message').text(xhr.responseText);
}).always(function () {
// update form visibility
$('#user-registration-container').hide();
$('#nifi-registration-container').hide();
$('#login-submission-container').hide();
$('#login-message-container').show();
});
};
var submitJustification = function () { var submitJustification = function () {
// attempt to create the nifi account registration // attempt to create the nifi account registration
$.ajax({ $.ajax({
@ -236,12 +222,6 @@ nf.Login = (function () {
var showLogoutLink = function () { var showLogoutLink = function () {
$('#user-logout-container').show(); $('#user-logout-container').show();
// handle logout
$('#user-logout').on('click', function () {
logout();
window.location = '/nifi/login';
});
}; };
return { return {
@ -297,11 +277,6 @@ nf.Login = (function () {
// show the user // show the user
$('#nifi-user-submit-justification').text(user); $('#nifi-user-submit-justification').text(user);
// render the logout button if there is a token locally
if (nf.Storage.getItem('jwt') !== null) {
$('#nifi-user-submit-justification-logout').show();
}
// anonymous user and 401 means they need nifi registration // anonymous user and 401 means they need nifi registration
needsNiFiRegistration = true; needsNiFiRegistration = true;
} else { } else {
@ -346,11 +321,6 @@ nf.Login = (function () {
// show the user // show the user
$('#nifi-user-submit-justification').text(user); $('#nifi-user-submit-justification').text(user);
// render the logout button if there is a token locally
if (nf.Storage.getItem('jwt') !== null) {
$('#nifi-user-submit-justification-logout').show();
}
// 401 from identity request and 200 from token means they have a certificate/token but have not yet requested an account // 401 from identity request and 200 from token means they have a certificate/token but have not yet requested an account
needsNiFiRegistration = true; needsNiFiRegistration = true;
}).fail(function (tokenXhr) { }).fail(function (tokenXhr) {
@ -402,7 +372,6 @@ nf.Login = (function () {
if (showMessage === true) { if (showMessage === true) {
initializeMessage(); initializeMessage();
} else if (needsLogin === true) { } else if (needsLogin === true) {
initializeLogin();
showLogin(); showLogin();
} else if (needsNiFiRegistration === true) { } else if (needsNiFiRegistration === true) {
initializeNiFiRegistration(); initializeNiFiRegistration();

View File

@ -72,13 +72,7 @@ $(document).ready(function () {
// handle logout // handle logout
$('#user-logout').on('click', function () { $('#user-logout').on('click', function () {
nf.Storage.removeItem('jwt'); nf.Storage.removeItem('jwt');
window.location = '/nifi/login';
// reload as appropriate
if (top !== window) {
parent.window.location = '/nifi';
} else {
window.location = '/nifi';
}
}); });
}); });