NIFI-655:

- Starting to add support for registration.
- Creating registration form.
This commit is contained in:
Matt Gilman 2015-11-04 22:03:52 -05:00
parent 2214592865
commit 93aa09dace
18 changed files with 574 additions and 240 deletions

View File

@ -16,6 +16,7 @@
*/
package org.apache.nifi.authentication;
import org.apache.nifi.authorization.exception.IdentityAlreadyExistsException;
import org.apache.nifi.authorization.exception.ProviderCreationException;
import org.apache.nifi.authorization.exception.ProviderDestructionException;
@ -36,7 +37,7 @@ public interface LoginIdentityProvider {
*
* @param credentials the login credentials
*/
void register(LoginCredentials credentials);
void register(LoginCredentials credentials) throws IdentityAlreadyExistsException;
/**
* Authenticates the specified login credentials.

View File

@ -207,6 +207,14 @@ public final class AuthorizedUsers {
saveUsers(users);
}
public synchronized void createOrUpdateUser(final FindUser finder, final CreateUser creator, final UpdateUser updater) {
try {
updateUser(finder, updater);
} catch (final UnknownIdentityException uie) {
createUser(creator);
}
}
public synchronized void updateUser(final FindUser finder, final UpdateUser updater) {
// update the user
final Users users = getUsers();

View File

@ -48,7 +48,7 @@
<xs:complexType name="User">
<xs:complexContent>
<xs:extension base="NiFiUser">
<xs:attribute name="dn">
<xs:attribute name="dn" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
@ -64,7 +64,7 @@
<xs:complexType name="LoginUser">
<xs:complexContent>
<xs:extension base="NiFiUser">
<xs:attribute name="username">
<xs:attribute name="username" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
@ -72,7 +72,7 @@
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="password">
<xs:attribute name="password" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
@ -80,6 +80,7 @@
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="pending" type="xs:boolean" use="required"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>

View File

@ -40,6 +40,7 @@ import org.apache.nifi.authorized.users.AuthorizedUsers.FindUsers;
import org.apache.nifi.authorized.users.AuthorizedUsers.HasUser;
import org.apache.nifi.authorized.users.AuthorizedUsers.UpdateUser;
import org.apache.nifi.authorized.users.AuthorizedUsers.UpdateUsers;
import org.apache.nifi.user.generated.LoginUser;
import org.apache.nifi.user.generated.NiFiUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -182,15 +183,37 @@ public class FileAuthorizationProvider implements AuthorityProvider {
@Override
public void addUser(final String dn, final String group) throws IdentityAlreadyExistsException, AuthorityAccessException {
authorizedUsers.createUser(new CreateUser() {
authorizedUsers.createOrUpdateUser(new FindUser() {
@Override
public NiFiUser createUser() {
// ensure the user doesn't already exist
if (authorizedUsers.hasUser(new HasUserByIdentity(dn))) {
throw new IdentityAlreadyExistsException(String.format("User identity already exists: %s", dn));
public NiFiUser findUser(final List<NiFiUser> users) throws UnknownIdentityException {
// attempt to get the user and ensure it was located
NiFiUser desiredUser = null;
for (final NiFiUser user : users) {
if (dn.equalsIgnoreCase(authorizedUsers.getUserIdentity(user))) {
desiredUser = user;
break;
}
}
// only support adding PreAuthenticatedUser's via this API - LoginUser's are added
// user does not exist, will create
if (desiredUser == null) {
throw new UnknownIdentityException("This exception will trigger the creator to be invoked.");
}
// user exists, verify its still pending
if (LoginUser.class.isAssignableFrom(desiredUser.getClass())) {
if (((LoginUser) desiredUser).isPending()) {
return desiredUser;
}
}
// user exists and account is valid... no good
throw new IdentityAlreadyExistsException(String.format("User identity already exists: %s", dn));
}
}, new CreateUser() {
@Override
public NiFiUser createUser() {
// only support adding PreAuthenticated User's via this API - LoginUser's are added
// via the LoginIdentityProvider
final ObjectFactory objFactory = new ObjectFactory();
final User newUser = objFactory.createUser();
@ -212,6 +235,13 @@ public class FileAuthorizationProvider implements AuthorityProvider {
return newUser;
}
}, new UpdateUser() {
@Override
public void updateUser(final NiFiUser user) {
// only support updating Login Users's via this API - need to mark the account as non pending
LoginUser loginUser = (LoginUser) user;
loginUser.setPending(false);
}
});
}
@ -306,6 +336,13 @@ public class FileAuthorizationProvider implements AuthorityProvider {
this.properties = properties;
}
private boolean isPendingLoginUser(final NiFiUser user) {
if (LoginUser.class.isAssignableFrom(user.getClass())) {
return ((LoginUser) user).isPending();
}
return false;
}
public class HasUserByIdentity implements HasUser {
private final String identity;
@ -324,7 +361,7 @@ public class FileAuthorizationProvider implements AuthorityProvider {
// attempt to get the user and ensure it was located
NiFiUser desiredUser = null;
for (final NiFiUser user : users) {
if (identity.equalsIgnoreCase(authorizedUsers.getUserIdentity(user))) {
if (identity.equalsIgnoreCase(authorizedUsers.getUserIdentity(user)) && !isPendingLoginUser(user)) {
desiredUser = user;
break;
}
@ -352,7 +389,7 @@ public class FileAuthorizationProvider implements AuthorityProvider {
// attempt to get the user and ensure it was located
NiFiUser desiredUser = null;
for (final NiFiUser user : users) {
if (identity.equalsIgnoreCase(authorizedUsers.getUserIdentity(user))) {
if (identity.equalsIgnoreCase(authorizedUsers.getUserIdentity(user)) && !isPendingLoginUser(user)) {
desiredUser = user;
break;
}
@ -366,7 +403,7 @@ public class FileAuthorizationProvider implements AuthorityProvider {
}
}
public static class FindUsersByGroup implements FindUsers {
public class FindUsersByGroup implements FindUsers {
private final String group;
@ -384,7 +421,7 @@ public class FileAuthorizationProvider implements AuthorityProvider {
// get all users with this group
List<NiFiUser> userGroup = new ArrayList<>();
for (final NiFiUser user : users) {
if (group.equals(user.getGroup())) {
if (group.equals(user.getGroup()) && !isPendingLoginUser(user)) {
userGroup.add(user);
}
}
@ -419,7 +456,7 @@ public class FileAuthorizationProvider implements AuthorityProvider {
List<NiFiUser> userList = new ArrayList<>();
for (final NiFiUser user : users) {
final String userIdentity = authorizedUsers.getUserIdentity(user);
if (copy.contains(userIdentity)) {
if (copy.contains(userIdentity) && !isPendingLoginUser(user)) {
copy.remove(userIdentity);
userList.add(user);
}

View File

@ -19,9 +19,11 @@ package org.apache.nifi.authentication;
import java.io.IOException;
import java.util.List;
import org.apache.nifi.authentication.annotation.LoginIdentityProviderContext;
import org.apache.nifi.authorization.exception.IdentityAlreadyExistsException;
import org.apache.nifi.authorization.exception.ProviderCreationException;
import org.apache.nifi.authorization.exception.ProviderDestructionException;
import org.apache.nifi.authorized.users.AuthorizedUsers;
import org.apache.nifi.authorized.users.AuthorizedUsers.CreateUser;
import org.apache.nifi.authorized.users.AuthorizedUsers.HasUser;
import org.apache.nifi.user.generated.LoginUser;
import org.apache.nifi.user.generated.NiFiUser;
@ -57,11 +59,43 @@ public class FileLoginIdentityProvider implements LoginIdentityProvider {
@Override
public boolean supportsRegistration() {
return false;
return true;
}
@Override
public void register(LoginCredentials credentials) {
public void register(final LoginCredentials credentials) throws IdentityAlreadyExistsException {
authorizedUsers.createUser(new CreateUser() {
@Override
public NiFiUser createUser() {
final HasUser hasUser = new HasUser() {
@Override
public boolean hasUser(List<NiFiUser> users) {
for (final NiFiUser user : users) {
// only consider LoginUsers
if (LoginUser.class.isAssignableFrom(user.getClass())) {
final LoginUser loginUser = (LoginUser) user;
if (credentials.getUsername().equals(loginUser.getUsername())) {
return true;
}
}
}
return false;
}
};
// if the user already exists
if (authorizedUsers.hasUser(hasUser)) {
throw new IdentityAlreadyExistsException(String.format("A user account for %s already exists.", credentials.getUsername()));
}
// TODO - need to properly encrypt and hash the user password for storage
final LoginUser user = new LoginUser();
user.setUsername(credentials.getUsername());
user.setPassword(credentials.getPassword());
user.setPending(true);
return user;
}
});
}
@Override
@ -78,7 +112,7 @@ public class FileLoginIdentityProvider implements LoginIdentityProvider {
if (LoginUser.class.isAssignableFrom(user.getClass())) {
final LoginUser loginUser = (LoginUser) user;
// TODO - need to properly encrypt and hash password
// TODO - need to properly encrypt and hash the supplied password for comparison
final String loginUserPassword = loginUser.getPassword();
if (credentials.getUsername().equals(loginUser.getUsername()) && credentials.getPassword().equals(loginUserPassword)) {
return true;

View File

@ -614,9 +614,12 @@ public class JettyServer implements NiFiServer {
private SslContextFactory createSslContextFactory() {
final SslContextFactory contextFactory = new SslContextFactory();
// client auth
contextFactory.setWantClientAuth(true);
contextFactory.setNeedClientAuth(false);
// require client auth when not supporting login or anonymous access
if (StringUtils.isBlank(props.getProperty(NiFiProperties.SECURITY_USER_LOGIN_IDENTITY_PROVIDER)) && props.getAnonymousAuthorities().isEmpty()) {
contextFactory.setNeedClientAuth(true);
} else {
contextFactory.setWantClientAuth(true);
}
/* below code sets JSSE system properties when values are provided */
// keystore properties

View File

@ -25,6 +25,7 @@ import org.apache.nifi.web.security.anonymous.NiFiAnonymousUserFilter;
import org.apache.nifi.web.security.NiFiAuthenticationEntryPoint;
import org.apache.nifi.web.security.RegistrationStatusFilter;
import org.apache.nifi.web.security.form.LoginAuthenticationFilter;
import org.apache.nifi.web.security.form.RegistrationFilter;
import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter;
import org.apache.nifi.web.security.jwt.JwtService;
import org.apache.nifi.web.security.node.NodeAuthorizedUserFilter;
@ -138,17 +139,22 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
}
private Filter buildRegistrationFilter(final String url) {
return null;
final RegistrationFilter registrationFilter = new RegistrationFilter(url);
registrationFilter.setJwtService(jwtService);
registrationFilter.setLoginIdentityProvider(loginIdentityProvider);
registrationFilter.setUserService(userService);
return registrationFilter;
}
private Filter buildRegistrationStatusFilter(final String url) {
final RegistrationStatusFilter registrationFilter = new RegistrationStatusFilter(url);
registrationFilter.setCertificateExtractor(certificateExtractor);
registrationFilter.setPrincipalExtractor(principalExtractor);
registrationFilter.setCertificateValidator(certificateValidator);
registrationFilter.setProperties(properties);
registrationFilter.setUserDetailsService(userDetailsService);
return registrationFilter;
final RegistrationStatusFilter registrationStatusFilter = new RegistrationStatusFilter(url);
registrationStatusFilter.setCertificateExtractor(certificateExtractor);
registrationStatusFilter.setPrincipalExtractor(principalExtractor);
registrationStatusFilter.setCertificateValidator(certificateValidator);
registrationStatusFilter.setProperties(properties);
registrationStatusFilter.setJwtService(jwtService);
registrationStatusFilter.setUserDetailsService(userDetailsService);
return registrationStatusFilter;
}
private NodeAuthorizedUserFilter buildNodeAuthorizedUserFilter() {

View File

@ -30,6 +30,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.nifi.authentication.LoginCredentials;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.util.StringUtils;
import org.apache.nifi.web.security.jwt.JwtService;
import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken;
import org.apache.nifi.web.security.x509.X509CertificateExtractor;
import org.apache.nifi.web.security.x509.X509CertificateValidator;
@ -54,6 +55,7 @@ public class RegistrationStatusFilter extends AbstractAuthenticationProcessingFi
private static final Logger logger = LoggerFactory.getLogger(RegistrationStatusFilter.class);
private NiFiProperties properties;
private JwtService jwtService;
private AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService;
private X509CertificateValidator certificateValidator;
private X509CertificateExtractor certificateExtractor;
@ -78,21 +80,22 @@ public class RegistrationStatusFilter extends AbstractAuthenticationProcessingFi
// if no certificate, just check the credentials
if (certificate == null) {
final LoginCredentials credentials = getLoginCredentials(request);
final String principal = jwtService.getAuthentication(request);
// ensure we have something we can work with (certificate or crendentials)
if (credentials == null) {
if (principal == null) {
throw new BadCredentialsException("Unable to check registration status as no credentials were included with the request.");
}
// without a certificate, this is not a proxied request
final List<String> chain = Arrays.asList(credentials.getUsername());
final List<String> chain = Arrays.asList(principal);
// check authorization for this user
checkAuthorization(chain);
// no issues with authorization
return new RegistrationStatusAuthenticationToken(credentials);
final LoginCredentials tokenCredentials = new LoginCredentials(principal, null);
return new RegistrationStatusAuthenticationToken(tokenCredentials);
} else {
// we have a certificate so let's consider a proxy chain
final String principal = extractPrincipal(certificate);
@ -147,17 +150,6 @@ public class RegistrationStatusFilter extends AbstractAuthenticationProcessingFi
return ProxiedEntitiesUtils.formatProxyDn(certificatePrincipal.toString());
}
private LoginCredentials getLoginCredentials(HttpServletRequest request) {
final String username = request.getParameter("username");
final String password = request.getParameter("password");
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
return null;
} else {
return new LoginCredentials(username, password);
}
}
@Override
protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authentication)
throws IOException, ServletException {
@ -238,6 +230,10 @@ public class RegistrationStatusFilter extends AbstractAuthenticationProcessingFi
}
}
public void setJwtService(JwtService jwtService) {
this.jwtService = jwtService;
}
public void setCertificateValidator(X509CertificateValidator certificateValidator) {
this.certificateValidator = certificateValidator;
}

View File

@ -16,6 +16,7 @@
*/
package org.apache.nifi.web.security.form;
import org.apache.nifi.web.security.token.LoginAuthenticationToken;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.cert.CertificateExpiredException;
@ -36,7 +37,7 @@ import org.apache.nifi.web.security.x509.X509CertificateExtractor;
import org.apache.nifi.web.security.x509.X509CertificateValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@ -84,42 +85,50 @@ public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingF
// look for a certificate
final X509Certificate certificate = certificateExtractor.extractClientCertificate(request);
// if there is no certificate, look for an existing token
if (certificate == null) {
throw new PreAuthenticatedCredentialsNotFoundException("Unable to extract client certificate after processing request with no login credentials specified.");
final String principal = jwtService.getAuthentication(request);
if (principal == null) {
throw new AuthenticationCredentialsNotFoundException("Unable to issue token as issue token as no credentials were found in the request.");
}
final LoginCredentials tokenCredentials = new LoginCredentials(principal, null);
return new LoginAuthenticationToken(tokenCredentials);
} else {
// extract the principal
final String principal = extractPrincipal(certificate);
try {
certificateValidator.validateClientCertificate(request, certificate);
} catch (CertificateExpiredException cee) {
final String message = String.format("Client certificate for (%s) is expired.", principal);
logger.info(message, cee);
if (logger.isDebugEnabled()) {
logger.debug("", cee);
}
return null;
} catch (CertificateNotYetValidException cnyve) {
final String message = String.format("Client certificate for (%s) is not yet valid.", principal);
logger.info(message, cnyve);
if (logger.isDebugEnabled()) {
logger.debug("", cnyve);
}
return null;
} catch (final Exception e) {
logger.info(e.getMessage());
if (logger.isDebugEnabled()) {
logger.debug("", e);
}
return null;
}
// authorize the proxy if necessary
authorizeProxyIfNecessary(ProxiedEntitiesUtils.buildProxyChain(request, principal));
final LoginCredentials preAuthenticatedCredentials = new LoginCredentials(principal, null);
return new LoginAuthenticationToken(preAuthenticatedCredentials);
}
// extract the principal
final String principal = extractPrincipal(certificate);
try {
certificateValidator.validateClientCertificate(request, certificate);
} catch (CertificateExpiredException cee) {
final String message = String.format("Client certificate for (%s) is expired.", principal);
logger.info(message, cee);
if (logger.isDebugEnabled()) {
logger.debug("", cee);
}
return null;
} catch (CertificateNotYetValidException cnyve) {
final String message = String.format("Client certificate for (%s) is not yet valid.", principal);
logger.info(message, cnyve);
if (logger.isDebugEnabled()) {
logger.debug("", cnyve);
}
return null;
} catch (final Exception e) {
logger.info(e.getMessage());
if (logger.isDebugEnabled()) {
logger.debug("", e);
}
return null;
}
// authorize the proxy if necessary
authorizeProxyIfNecessary(ProxiedEntitiesUtils.buildProxyChain(request, principal));
final LoginCredentials preAuthenticatedCredentials = new LoginCredentials(principal, null);
return new LoginAuthenticationToken(preAuthenticatedCredentials);
} else {
if (loginIdentityProvider.authenticate(credentials)) {
return new LoginAuthenticationToken(credentials);
@ -178,39 +187,15 @@ public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingF
@Override
protected void unsuccessfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException failed) throws IOException, ServletException {
// set the response status
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("text/plain");
final PrintWriter out = response.getWriter();
out.println("Unable to authenticate.");
}
/**
* This is an Authentication Token for logging in. Once a user is authenticated, they can be issues an ID token.
*/
public static class LoginAuthenticationToken extends AbstractAuthenticationToken {
final LoginCredentials credentials;
public LoginAuthenticationToken(final LoginCredentials credentials) {
super(null);
setAuthenticated(true);
this.credentials = credentials;
}
public LoginCredentials getLoginCredentials() {
return credentials;
}
@Override
public Object getCredentials() {
return credentials.getPassword();
}
@Override
public Object getPrincipal() {
return credentials.getUsername();
out.println(failed.getMessage());
if (failed instanceof BadCredentialsException || failed instanceof AuthenticationCredentialsNotFoundException) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
} else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}

View File

@ -0,0 +1,160 @@
/*
* 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.form;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.nifi.admin.service.AccountDisabledException;
import org.apache.nifi.admin.service.AccountNotFoundException;
import org.apache.nifi.admin.service.AccountPendingException;
import org.apache.nifi.admin.service.AdministrationException;
import org.apache.nifi.admin.service.UserService;
import org.apache.nifi.authentication.LoginCredentials;
import org.apache.nifi.authentication.LoginIdentityProvider;
import org.apache.nifi.authorization.exception.IdentityAlreadyExistsException;
import org.apache.nifi.util.StringUtils;
import org.apache.nifi.web.security.jwt.JwtService;
import org.apache.nifi.web.security.token.LoginAuthenticationToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
/**
* Exchanges a successful login with the configured provider for a ID token for accessing the API.
*/
public class RegistrationFilter extends AbstractAuthenticationProcessingFilter {
private static final Logger logger = LoggerFactory.getLogger(RegistrationFilter.class);
private LoginIdentityProvider loginIdentityProvider;
private JwtService jwtService;
private UserService userService;
public RegistrationFilter(final String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
// do not continue filter chain... simply exchanging authentication for token
setContinueChainBeforeSuccessfulAuthentication(false);
}
@Override
public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
// only suppport registration when running securely
if (!request.isSecure()) {
return null;
}
// look for the credentials in the request
final LoginCredentials credentials = getLoginCredentials(request);
// if the credentials were not part of the request, attempt to log in with the certificate in the request
if (credentials == null) {
throw new UsernameNotFoundException("User login credentials not found in request.");
} else {
try {
// attempt to register the user
loginIdentityProvider.register(credentials);
} catch (final IdentityAlreadyExistsException iaee) {
// if the identity already exists, try to create the nifi account request
}
try {
// see if the account already exists so we're able to return the current status
userService.checkAuthorization(credentials.getUsername());
// account exists and is valid
throw new AccountStatusException(String.format("An account for %s already exists.", credentials.getUsername())) {
};
} catch (AdministrationException ase) {
throw new AuthenticationServiceException(ase.getMessage(), ase);
} catch (AccountDisabledException | AccountPendingException e) {
throw new AccountStatusException(e.getMessage(), e) {
};
} catch (AccountNotFoundException anfe) {
// create the pending user account
userService.createPendingUserAccount(credentials.getUsername(), request.getParameter("justification"));
// create the login token
return new LoginAuthenticationToken(credentials);
}
}
}
private LoginCredentials getLoginCredentials(HttpServletRequest request) {
final String username = request.getParameter("username");
final String password = request.getParameter("password");
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
return null;
} else {
return new LoginCredentials(username, password);
}
}
@Override
protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authentication)
throws IOException, ServletException {
// generate JWT for response
jwtService.addToken(response, authentication);
// mark as successful
response.setStatus(HttpServletResponse.SC_CREATED);
response.setContentType("text/plain");
response.setContentLength(0);
}
@Override
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());
// set the appropriate response status
if (failed instanceof UsernameNotFoundException) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
} else if (failed instanceof AccountStatusException) {
// account exists (maybe valid, pending, revoked)
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
} else {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
public void setJwtService(JwtService jwtService) {
this.jwtService = jwtService;
}
public void setLoginIdentityProvider(LoginIdentityProvider loginIdentityProvider) {
this.loginIdentityProvider = loginIdentityProvider;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
}

View File

@ -50,7 +50,7 @@ public class JwtService {
* @param authentication The authentication to generate a token for
*/
public void addToken(final HttpServletResponse response, final Authentication authentication) {
// TODO : actually create real token
// TODO : actually create real token... in header or response body?
// create a token the specified authentication
String token = authentication.getName();

View File

@ -0,0 +1,48 @@
/*
* 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.apache.nifi.authentication.LoginCredentials;
import org.springframework.security.authentication.AbstractAuthenticationToken;
/**
* This is an Authentication Token for logging in. Once a user is authenticated, they can be issues an ID token.
*/
public class LoginAuthenticationToken extends AbstractAuthenticationToken {
final LoginCredentials credentials;
public LoginAuthenticationToken(final LoginCredentials credentials) {
super(null);
setAuthenticated(true);
this.credentials = credentials;
}
public LoginCredentials getLoginCredentials() {
return credentials;
}
@Override
public Object getCredentials() {
return credentials.getPassword();
}
@Override
public Object getPrincipal() {
return credentials.getUsername();
}
}

View File

@ -28,6 +28,7 @@
<link rel="stylesheet" href="js/jquery/ui-smoothness/jquery-ui-1.10.4.min.css" type="text/css" />
<script type="text/javascript" src="js/jquery/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="js/jquery/jquery.count.js"></script>
<script type="text/javascript" src="js/jquery/jquery.center.js"></script>
<script type="text/javascript" src="js/jquery/modal/jquery.modal.js?${project.version}"></script>
<script type="text/javascript" src="js/jquery/qtip2/jquery.qtip.min.js"></script>
<script type="text/javascript" src="js/jquery/ui-smoothness/jquery-ui-1.10.4.min.js"></script>
@ -42,5 +43,8 @@
<jsp:include page="/WEB-INF/partials/login/nifi-registration-form.jsp"/>
<jsp:include page="/WEB-INF/partials/login/login-submission.jsp"/>
</div>
<jsp:include page="/WEB-INF/partials/ok-dialog.jsp"/>
<div id="faded-background"></div>
<div id="glass-pane"></div>
</body>
</html>

View File

@ -27,6 +27,10 @@
<div class="setting-name">Password</div>
<div class="setting-field">
<input type="password" id="password"/>
<div id="create-account-message" class="hidden">
<div style="font-style: italic;">Don't have an account?</div>
<div><span id="create-account-link" class="link">Create one</span> to request access.</div>
</div>
</div>
</div>
</div>

View File

@ -16,18 +16,19 @@
--%>
<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
<div id="user-registration-container" class="hidden">
<div class="login-title">Log In</div>
<div class="login-title">Create Account</div>
<div class="setting">
<div class="setting-name">Username</div>
<div class="setting-field">
<input type="text" id="username"/>
<input type="text" id="registration-username"/>
</div>
</div>
<div class="setting">
<div class="setting-name">Password</div>
<div class="setting-field">
<input type="password" id="password"/><br/>
<input type="password" id="password-confirmation" placeholder="Confirm password"/>
<input type="password" id="registration-password" style="margin-bottom: 5px;"/>
<br/>
<input type="password" id="registration-password-confirmation" placeholder="Confirm password"/>
</div>
</div>
</div>

View File

@ -19,7 +19,7 @@
Login Styles
*/
body.login-body {
#login-contents-container {
position: absolute;
top: 0px;
left: 0px;
@ -27,13 +27,8 @@ body.login-body {
right: 0px;
background: #fff url(../images/bg-error.png) left top no-repeat;
font-family: Verdana, Geneva, sans-serif;
color: #191919;
z-index: 999999;
}
#login-contents-container {
margin-top: 100px;
margin-left: 100px;
padding-top: 100px;
padding-left: 100px;
}
#login-message-title {
@ -61,6 +56,14 @@ body.login-body input, body.login-body textarea {
width: 400px;
}
#create-account-message {
margin-top: 2px;
}
#create-account-link {
text-decoration: underline;
}
/*
User Registration
*/

View File

@ -1044,7 +1044,6 @@ nf.Canvas = (function () {
dataType: 'json'
});
// load the identity and authorities for the current user
var userXhr = $.Deferred(function(deferred) {
$.when(authoritiesXhr, identityXhr).done(function (authoritiesResult, identityResult) {
@ -1059,22 +1058,15 @@ nf.Canvas = (function () {
// if the user is logged, we want to determine if they were logged in using a certificate
if (identityResponse.identity !== 'anonymous') {
$('#current-user').text(identityResponse.identity).show();
// attempt to get a token for the current user without passing login credentials
$.ajax({
type: 'GET',
url: config.urls.token
}).fail(function () {
// if this request succeeds, it means the user is logged in using their certificate.
// if this request fails, it means the user is logged in with login credentials so we want to render a logout button.
// render the logout button if there is a token locally
if (nf.Storage.getItem('jwt') !== null) {
$('#logout-link-container').show();
}).always(function () {
deferred.resolve();
});
}
} else {
$('#current-user').text(nf.Canvas.ANONYMOUS_USER_TEXT).show();
deferred.resolve();
}
deferred.resolve();
}).fail(function (xhr, status, error) {
// 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) {

View File

@ -44,133 +44,182 @@ nf.Login = (function () {
// if this nifi supports registration, render the registration form
if (supportsRegistration === true) {
initializeUserRegistration();
// automatically include support for nifi registration
initializeNiFiRegistration();
// hide the submit justification title
$('#nifi-registration-title').hide();
// show the create account message
$('#create-account-message').show();
// update the submit button text
$('#login-submission-button').text('Create');
// toggle between login and signup
$('#create-account-link').on('click', function () {
showUserRegistration();
});
}
// show the login form
};
var showLogin = function () {
$('#login-container').show();
$('#user-registration-container').hide();
$('#nifi-registration-container').hide();
$('#login-submission-button').text('Log in');
};
var initializeUserRegistration = function () {
// show the user registration form
$('#user-registration-container').show();
};
var initializeNiFiRegistration = function () {
$('#nifi-registration-justification').count({
charCountField: '#remaining-characters'
});
};
// update the button text
$('#login-submission-button').text('Submit');
var showUserRegistration = function () {
showNiFiRegistration();
// show the nifi registration container
$('#nifi-registration-title').hide();
$('#user-registration-container').show();
$('#login-submission-button').text('Create');
};
var showNiFiRegistration = function () {
$('#login-container').hide();
$('#nifi-registration-container').show();
$('#login-submission-button').text('Submit');
};
var initializeSubmission = function () {
$('#login-submission-button').one('click', function () {
$('#login-submission-button').on('click', function () {
if ($('#login-container').is(':visible')) {
var username = $('#username').val();
var password = $('#password').val();
// login submit
$.ajax({
type: 'POST',
url: '../nifi-api/token',
data: {
'username': username,
'password': password
}
}).done(function (response, status, xhr) {
var authorization = xhr.getResponseHeader('Authorization');
var badToken = false;
// ensure there was a token in the response
if (authorization) {
var tokens = authorization.split(/ /);
// ensure the token is the appropriate length
if (tokens.length === 2) {
// store the jwt and reload the page
nf.Storage.setItem('jwt', tokens[1]);
// reload as appropriate
if (top !== window) {
parent.window.location = '/nifi';
} else {
window.location = '/nifi';
}
return;
} else {
badToken = true;
}
} else {
badToken = true;
}
if (badToken === true) {
$('#login-message-title').text('An unexpected error has occurred');
$('#login-message').text('The id token could not be parsed.');
// update visibility
$('#login-container').hide();
$('#login-submission-container').hide();
$('#login-message-container').show();
}
}).fail(function (xhr, status, error) {
$('#login-message-title').text('An unexpected error has occurred');
$('#login-message').text(xhr.responseText);
// update visibility
$('#login-container').hide();
$('#login-submission-container').hide();
$('#login-message-container').show();
});
login();
} else if ($('#user-registration-container').is(':visible')) {
var justification = $('#registration-justification').val();
// new user account submit
createUserAccount();
} else if ($('#nifi-registration-container').is(':visible')) {
// attempt to create the user account registration
$.ajax({
type: 'POST',
url: config.urls.users,
data: {
'justification': $('#registration-justification').val()
}
}).done(function (response) {
var markup = 'An administrator will process your request shortly.';
if (isAnonymous === 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('An unexpected error has occurred');
$('#login-message').text(xhr.responseText);
}).always(function () {
// update form visibility
$('#nifi-registration-container').hide();
$('#login-submission-container').hide();
$('#login-message-container').show();
});
submitJustification();
}
});
$('#login-submission-container').show();
};
var login = function () {
// login submit
$.ajax({
type: 'POST',
url: config.urls.token,
data: {
'username': $('#username').val(),
'password': $('#password').val()
}
}).done(function (response, status, xhr) {
var authorization = xhr.getResponseHeader('Authorization');
var badToken = false;
// ensure there was a token in the response
if (authorization) {
var tokens = authorization.split(/ /);
// ensure the token is the appropriate length
if (tokens.length === 2) {
// store the jwt and reload the page
nf.Storage.setItem('jwt', tokens[1]);
// reload as appropriate
if (top !== window) {
parent.window.location = '/nifi';
} else {
window.location = '/nifi';
}
return;
} else {
badToken = true;
}
} else {
badToken = true;
}
if (badToken === true) {
$('#login-message-title').text('An unexpected error has occurred');
$('#login-message').text('The user token could not be parsed.');
// update visibility
$('#login-container').hide();
$('#login-submission-container').hide();
$('#login-message-container').show();
}
}).fail(function (xhr, status, error) {
if (xhr.status === 400) {
nf.Dialog.showOkDialog({
dialogContent: nf.Common.escapeHtml(xhr.responseText),
overlayBackground: false
});
} 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();
}
});
};
var createUserAccount = function () {
// attempt to create the user account registration
$.ajax({
type: 'POST',
url: config.urls.registration,
data: {
'username': $('#registration-username').val(),
'password': $('#registration-password').val(),
'justification': $('#nifi-registration-justification').val()
}
}).done(function (response, status, xhr) {
var markup = 'An administrator will process your request shortly.';
if (isAnonymous === 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 () {
// attempt to create the nifi account registration
$.ajax({
type: 'POST',
url: config.urls.users,
data: {
'justification': $('#nifi-registration-justification').val()
}
}).done(function (response) {
var markup = 'An administrator will process your request shortly.';
if (isAnonymous === 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 submit justification');
$('#login-message').text(xhr.responseText);
}).always(function () {
// update form visibility
$('#nifi-registration-container').hide();
$('#login-submission-container').hide();
$('#login-message-container').show();
});
};
return {
/**
* Initializes the login page.
@ -202,7 +251,7 @@ nf.Login = (function () {
// request a token without including credentials, if successful then the user is using a certificate
token.done(function () {
// the user is using a certificate, see if their account is active/pending/revoked/etc
// the user is using a certificate/token, see if their account is active/pending/revoked/etc
$.ajax({
type: 'GET',
url: config.urls.registrationStatus
@ -212,7 +261,6 @@ nf.Login = (function () {
// account is active and good
$('#login-message-title').text('Success');
$('#login-message').text('Your account is active and you are already logged in.');
deferred.resolve();
}).fail(function (xhr, status, error) {
if (xhr.status === 401) {
// anonymous user and 401 means they need nifi registration
@ -228,6 +276,7 @@ nf.Login = (function () {
$('#login-message').text(xhr.responseText);
}
}
}).always(function () {
deferred.resolve();
});
}).fail(function () {
@ -248,7 +297,7 @@ nf.Login = (function () {
if (xhr.status === 401) {
// attempt to get a token for the current user without passing login credentials
token.done(function () {
// 401 from identity request and 200 from token means they have a certificate 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;
}).fail(function () {
// no token granted, user needs to login with their credentials
@ -271,7 +320,7 @@ nf.Login = (function () {
}
});
}).promise();
var loginConfigXhr = $.ajax({
type: 'GET',
url: config.urls.loginConfig,
@ -282,7 +331,7 @@ nf.Login = (function () {
$.when(loginConfigXhr, pageStateInit).done(function (loginResult) {
var loginResponse = loginResult[0];
var loginConfig = loginResponse.config;
// if login is required, verify its supported
if (loginConfig.supportsLogin === false && needsLogin === true) {
$('#login-message-title').text('Access Denied');
@ -290,13 +339,15 @@ nf.Login = (function () {
showMessage = true;
needsLogin = false;
}
if (showMessage === true) {
initializeMessage();
} else if (needsLogin === true) {
initializeLogin(loginConfig.supportsRegistration);
showLogin();
} else if (needsNiFiRegistration === true) {
initializeNiFiRegistration();
showNiFiRegistration();
}
if (needsLogin === true || needsNiFiRegistration === true) {