Repository: nifi Updated Branches: refs/heads/NIFI-655 1350483d3 -> efa1939fc
NIFI-655: - Updating packages for log in filters. - Handling new registration exceptions. - Code clean up. Project: http://git-wip-us.apache.org/repos/asf/nifi/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/efa1939f Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/efa1939f Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/efa1939f Branch: refs/heads/NIFI-655 Commit: efa1939fc551f4d02de61718e72e1bc896320350 Parents: 1350483 Author: Matt Gilman <[email protected]> Authored: Mon Nov 9 10:52:18 2015 -0500 Committer: Matt Gilman <[email protected]> Committed: Mon Nov 9 10:52:18 2015 -0500 ---------------------------------------------------------------------- .../nifi/authorized/users/AuthorizedUsers.java | 8 +- .../web/NiFiWebApiSecurityConfiguration.java | 4 +- .../form/LoginAuthenticationFilter.java | 236 ------------------- .../web/security/form/RegistrationFilter.java | 158 ------------- .../login/LoginAuthenticationFilter.java | 236 +++++++++++++++++++ .../web/security/login/RegistrationFilter.java | 163 +++++++++++++ 6 files changed, 405 insertions(+), 400 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi/blob/efa1939f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorized-users/src/main/java/org/apache/nifi/authorized/users/AuthorizedUsers.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorized-users/src/main/java/org/apache/nifi/authorized/users/AuthorizedUsers.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorized-users/src/main/java/org/apache/nifi/authorized/users/AuthorizedUsers.java index 98922e7..abdd48e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorized-users/src/main/java/org/apache/nifi/authorized/users/AuthorizedUsers.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorized-users/src/main/java/org/apache/nifi/authorized/users/AuthorizedUsers.java @@ -130,7 +130,7 @@ public final class AuthorizedUsers { * @return The user identity */ public String getUserIdentity(final NiFiUser user) { - if (User.class.isAssignableFrom(user.getClass())) { + if (user instanceof User) { return ((User) user).getDn(); } else { return ((LoginUser) user).getUsername(); @@ -233,7 +233,7 @@ public final class AuthorizedUsers { // create the user final NiFiUser newUser = creator.createUser(); - if (User.class.isAssignableFrom(newUser.getClass())) { + if (newUser instanceof User) { users.getUser().add((User) newUser); } else { users.getLoginUser().add((LoginUser) newUser); @@ -323,7 +323,7 @@ public final class AuthorizedUsers { // find the desired user final NiFiUser user = finder.findUser(nifiUsers); - if (User.class.isAssignableFrom(user.getClass())) { + if (user instanceof User) { users.getUser().remove((User) user); } else { users.getLoginUser().remove((LoginUser) user); @@ -350,7 +350,7 @@ public final class AuthorizedUsers { // find the desired user final List<NiFiUser> usersToRemove = finder.findUsers(nifiUsers); for (final NiFiUser user : usersToRemove) { - if (User.class.isAssignableFrom(user.getClass())) { + if (user instanceof User) { users.getUser().remove((User) user); } else { users.getLoginUser().remove((LoginUser) user); http://git-wip-us.apache.org/repos/asf/nifi/blob/efa1939f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java index bdc6ebc..4fb3501 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java @@ -24,8 +24,8 @@ import org.apache.nifi.web.security.NiFiAuthenticationProvider; 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.login.LoginAuthenticationFilter; +import org.apache.nifi.web.security.login.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; http://git-wip-us.apache.org/repos/asf/nifi/blob/efa1939f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/LoginAuthenticationFilter.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/LoginAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/LoginAuthenticationFilter.java deleted file mode 100644 index 4848801..0000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/LoginAuthenticationFilter.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.nifi.web.security.form; - -import org.apache.nifi.web.security.token.LoginAuthenticationToken; -import java.io.IOException; -import java.io.PrintWriter; -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateNotYetValidException; -import java.security.cert.X509Certificate; -import java.util.List; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.apache.nifi.authentication.LoginCredentials; -import org.apache.nifi.authentication.LoginIdentityProvider; -import org.apache.nifi.authentication.exception.IdentityAccessException; -import org.apache.nifi.util.StringUtils; -import org.apache.nifi.web.security.ProxiedEntitiesUtils; -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; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; -import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; - -/** - * Exchanges a successful login with the configured provider for a ID token for accessing the API. - */ -public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter { - - private static final Logger logger = LoggerFactory.getLogger(LoginAuthenticationFilter.class); - - private AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService; - - private X509CertificateValidator certificateValidator; - private X509CertificateExtractor certificateExtractor; - private X509PrincipalExtractor principalExtractor; - - private LoginIdentityProvider loginIdentityProvider; - private JwtService jwtService; - - public LoginAuthenticationFilter(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 login 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) { - // look for a certificate - final X509Certificate certificate = certificateExtractor.extractClientCertificate(request); - - // if there is no certificate, look for an existing token - if (certificate == null) { - // if not configured for login, don't consider existing tokens - if (loginIdentityProvider == null) { - throw new BadCredentialsException("Login not supported."); - } - - 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 = principalExtractor.extractPrincipal(certificate).toString(); - - 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 not configuration for login, don't consider credentials - if (loginIdentityProvider == null) { - throw new BadCredentialsException("Login not supported."); - } - - try { - if (loginIdentityProvider.authenticate(credentials)) { - return new LoginAuthenticationToken(credentials); - } else { - throw new BadCredentialsException("The supplied username and password are not valid."); - } - } catch (final IdentityAccessException iae) { - throw new AuthenticationServiceException(iae.getMessage(), iae); - } - } - } - - /** - * Ensures the proxyChain is authorized before allowing the user to be authenticated. - * - * @param proxyChain the proxy chain - * @throws AuthenticationException if the proxy chain is not authorized - */ - private void authorizeProxyIfNecessary(final List<String> proxyChain) throws AuthenticationException { - if (proxyChain.size() > 1) { - try { - userDetailsService.loadUserDetails(new NiFiAuthenticationRequestToken(proxyChain)); - } catch (final UsernameNotFoundException unfe) { - // if a username not found exception was thrown, the proxies were authorized and now - // we can issue a new ID token to the end user - } catch (final Exception e) { - // any other issue we're going to treat as an authentication exception which will return 401 - throw new AuthenticationException(e.getMessage(), e) { - }; - } - } - } - - 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); - } - - @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()); - - if (failed instanceof BadCredentialsException || failed instanceof AuthenticationCredentialsNotFoundException) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } else if (failed instanceof AuthenticationServiceException) { - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } else { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - } - } - - public void setJwtService(JwtService jwtService) { - this.jwtService = jwtService; - } - - public void setLoginIdentityProvider(LoginIdentityProvider loginIdentityProvider) { - this.loginIdentityProvider = loginIdentityProvider; - } - - public void setCertificateValidator(X509CertificateValidator certificateValidator) { - this.certificateValidator = certificateValidator; - } - - public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) { - this.certificateExtractor = certificateExtractor; - } - - public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) { - this.principalExtractor = principalExtractor; - } - - public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService) { - this.userDetailsService = userDetailsService; - } - -} http://git-wip-us.apache.org/repos/asf/nifi/blob/efa1939f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/RegistrationFilter.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/RegistrationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/RegistrationFilter.java deleted file mode 100644 index ea54127..0000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/RegistrationFilter.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.nifi.web.security.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.authentication.exception.IdentityAccessException; -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 - } catch (final IdentityAccessException iae) { - throw new AuthenticationServiceException(iae.getMessage(), iae); - } - - 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); - } - - @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; - } - -} http://git-wip-us.apache.org/repos/asf/nifi/blob/efa1939f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/login/LoginAuthenticationFilter.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/login/LoginAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/login/LoginAuthenticationFilter.java new file mode 100644 index 0000000..39f2782 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/login/LoginAuthenticationFilter.java @@ -0,0 +1,236 @@ +/* + * 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.login; + +import org.apache.nifi.web.security.token.LoginAuthenticationToken; +import java.io.IOException; +import java.io.PrintWriter; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.List; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.nifi.authentication.LoginCredentials; +import org.apache.nifi.authentication.LoginIdentityProvider; +import org.apache.nifi.authentication.exception.IdentityAccessException; +import org.apache.nifi.util.StringUtils; +import org.apache.nifi.web.security.ProxiedEntitiesUtils; +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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; + +/** + * Exchanges a successful login with the configured provider for a ID token for accessing the API. + */ +public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter { + + private static final Logger logger = LoggerFactory.getLogger(LoginAuthenticationFilter.class); + + private AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService; + + private X509CertificateValidator certificateValidator; + private X509CertificateExtractor certificateExtractor; + private X509PrincipalExtractor principalExtractor; + + private LoginIdentityProvider loginIdentityProvider; + private JwtService jwtService; + + public LoginAuthenticationFilter(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 login 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) { + // look for a certificate + final X509Certificate certificate = certificateExtractor.extractClientCertificate(request); + + // if there is no certificate, look for an existing token + if (certificate == null) { + // if not configured for login, don't consider existing tokens + if (loginIdentityProvider == null) { + throw new BadCredentialsException("Login not supported."); + } + + 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 = principalExtractor.extractPrincipal(certificate).toString(); + + 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 not configuration for login, don't consider credentials + if (loginIdentityProvider == null) { + throw new BadCredentialsException("Login not supported."); + } + + try { + if (loginIdentityProvider.authenticate(credentials)) { + return new LoginAuthenticationToken(credentials); + } else { + throw new BadCredentialsException("The supplied username and password are not valid."); + } + } catch (final IdentityAccessException iae) { + throw new AuthenticationServiceException(iae.getMessage(), iae); + } + } + } + + /** + * Ensures the proxyChain is authorized before allowing the user to be authenticated. + * + * @param proxyChain the proxy chain + * @throws AuthenticationException if the proxy chain is not authorized + */ + private void authorizeProxyIfNecessary(final List<String> proxyChain) throws AuthenticationException { + if (proxyChain.size() > 1) { + try { + userDetailsService.loadUserDetails(new NiFiAuthenticationRequestToken(proxyChain)); + } catch (final UsernameNotFoundException unfe) { + // if a username not found exception was thrown, the proxies were authorized and now + // we can issue a new ID token to the end user + } catch (final Exception e) { + // any other issue we're going to treat as an authentication exception which will return 401 + throw new AuthenticationException(e.getMessage(), e) { + }; + } + } + } + + 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); + } + + @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()); + + if (failed instanceof BadCredentialsException || failed instanceof AuthenticationCredentialsNotFoundException) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + } else if (failed instanceof AuthenticationServiceException) { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } else { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + } + + public void setJwtService(JwtService jwtService) { + this.jwtService = jwtService; + } + + public void setLoginIdentityProvider(LoginIdentityProvider loginIdentityProvider) { + this.loginIdentityProvider = loginIdentityProvider; + } + + public void setCertificateValidator(X509CertificateValidator certificateValidator) { + this.certificateValidator = certificateValidator; + } + + public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) { + this.certificateExtractor = certificateExtractor; + } + + public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) { + this.principalExtractor = principalExtractor; + } + + public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService) { + this.userDetailsService = userDetailsService; + } + +} http://git-wip-us.apache.org/repos/asf/nifi/blob/efa1939f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/login/RegistrationFilter.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/login/RegistrationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/login/RegistrationFilter.java new file mode 100644 index 0000000..8a3f02e --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/login/RegistrationFilter.java @@ -0,0 +1,163 @@ +/* + * 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.login; + +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.authentication.exception.IdentityAccessException; +import org.apache.nifi.authentication.exception.IdentityRegistrationException; +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.authentication.BadCredentialsException; +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 + } catch (final IdentityRegistrationException ire) { + // the credentials are not acceptable for some reason + throw new BadCredentialsException(ire.getMessage(), ire); + } catch (final IdentityAccessException iae) { + throw new AuthenticationServiceException(iae.getMessage(), iae); + } + + 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); + } + + @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 || failed instanceof BadCredentialsException) { + 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; + } + +}
