NIFI-655: - Refactoring web security to use Spring Security Java Configuration. - Introducing security in Web UI in order to get JWT.
Project: http://git-wip-us.apache.org/repos/asf/nifi/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/61046707 Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/61046707 Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/61046707 Branch: refs/heads/NIFI-655 Commit: 6104670735325492fd58ca12d123a061818d62cd Parents: 31fba6b Author: Matt Gilman <[email protected]> Authored: Wed Oct 7 13:33:34 2015 -0400 Committer: Matt Gilman <[email protected]> Committed: Wed Oct 7 13:46:11 2015 -0400 ---------------------------------------------------------------------- .../resources/nifi-administration-context.xml | 2 +- .../org/apache/nifi/web/server/JettyServer.java | 5 +- .../org/apache/nifi/web/NiFiServiceFacade.java | 6 + .../nifi/web/NiFiWebApiConfiguration.java | 24 + .../web/NiFiWebApiSecurityConfiguration.java | 127 +++++ .../nifi/web/StandardNiFiServiceFacade.java | 22 + .../nifi/web/api/ApplicationResource.java | 10 +- .../org/apache/nifi/web/api/UserResource.java | 33 ++ .../nifi/web/controller/ControllerFacade.java | 13 +- .../src/main/webapp/WEB-INF/web.xml | 13 +- .../nifi/integration/util/NiFiTestUser.java | 21 +- .../org/apache/nifi/web/security/DnUtils.java | 85 ---- .../security/NiFiAuthenticationEntryPoint.java | 69 +++ .../web/security/NiFiAuthenticationFilter.java | 144 ++++++ .../security/NiFiAuthenticationProvider.java | 62 +++ .../nifi/web/security/ProxiedEntitiesUtils.java | 168 +++++++ .../anonymous/NiFiAnonymousUserFilter.java | 55 +- .../NiFiAuthenticationEntryPoint.java | 69 --- .../authorization/NiFiAuthorizationService.java | 33 +- .../authorization/NodeAuthorizedUserFilter.java | 128 ----- .../security/form/FormAuthenticationFilter.java | 72 +++ .../security/jwt/JwtAuthenticationFilter.java | 58 +++ .../security/jwt/JwtAuthenticationProvider.java | 47 ++ .../nifi/web/security/jwt/JwtService.java | 51 ++ .../security/node/NodeAuthorizedUserFilter.java | 127 +++++ .../NewAccountAuthenticationRequestToken.java | 41 ++ .../token/NewAccountAuthenticationToken.java | 48 ++ .../token/NiFiAuthenticationRequestToken.java | 55 ++ .../security/token/NiFiAuthorizationToken.java | 51 ++ .../web/security/user/NewAccountRequest.java | 31 ++ .../nifi/web/security/user/NiFiUserDetails.java | 3 +- .../nifi/web/security/user/NiFiUserUtils.java | 21 + .../security/x509/X509AuthenticationFilter.java | 257 +--------- .../x509/X509AuthenticationFilterOld.java | 317 ++++++++++++ .../x509/X509AuthenticationProvider.java | 31 ++ .../resources/nifi-web-security-context.xml | 62 +-- .../NiFiAuthorizationServiceTest.java | 502 +++++++++---------- .../nifi-framework/nifi-web/nifi-web-ui/pom.xml | 22 +- .../apache/nifi/web/NiFiWebUiConfiguration.java | 35 ++ .../web/NiFiWebUiSecurityConfiguration.java | 69 +++ .../src/main/webapp/WEB-INF/pages/login.jsp | 66 +++ .../WEB-INF/partials/canvas/canvas-header.jsp | 3 + .../nifi-web-ui/src/main/webapp/WEB-INF/web.xml | 44 +- pom.xml | 2 +- 44 files changed, 2204 insertions(+), 900 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/resources/nifi-administration-context.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/resources/nifi-administration-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/resources/nifi-administration-context.xml index a36619f..8cb4b97 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/resources/nifi-administration-context.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/resources/nifi-administration-context.xml @@ -23,7 +23,7 @@ http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd"> <!-- user authority provider --> - <bean id="authorityProvider" class="org.apache.nifi.authorization.AuthorityProviderFactoryBean" depends-on="clusterManager flowController"> + <bean id="authorityProvider" class="org.apache.nifi.authorization.AuthorityProviderFactoryBean" depends-on="clusterManager"> <property name="properties" ref="nifiProperties"/> </bean> http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java index 99c11a8..b2b3013 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java @@ -614,8 +614,9 @@ public class JettyServer implements NiFiServer { private SslContextFactory createSslContextFactory() { final SslContextFactory contextFactory = new SslContextFactory(); - // need client auth - contextFactory.setNeedClientAuth(props.getNeedClientAuth()); + // client auth + contextFactory.setWantClientAuth(true); + contextFactory.setNeedClientAuth(false); /* below code sets JSSE system properties when values are provided */ // keystore properties http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java index c98b1e4..7395cfc 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java @@ -1236,6 +1236,12 @@ public interface NiFiServiceFacade { Collection<UserDTO> getUsers(Boolean grouped); /** + * Creates a new account request. + * @return user + */ + UserDTO createUser(); + + /** * Updates the specified user accordingly. * * @param user The user to update http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiConfiguration.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiConfiguration.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiConfiguration.java new file mode 100644 index 0000000..04264e7 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiConfiguration.java @@ -0,0 +1,24 @@ +package org.apache.nifi.web; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportResource; + +/** + * + */ +@Configuration +@Import({ NiFiWebApiSecurityConfiguration.class}) +@ImportResource( {"classpath:nifi-context.xml", + "classpath:nifi-administration-context.xml", + "classpath:nifi-cluster-manager-context.xml", + "classpath:nifi-cluster-protocol-context.xml", + "classpath:nifi-web-security-context.xml", + "classpath:nifi-web-api-context.xml"} ) +public class NiFiWebApiConfiguration { + + public NiFiWebApiConfiguration() { + super(); + } + +} http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/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 new file mode 100644 index 0000000..305aaf6 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java @@ -0,0 +1,127 @@ +/* + * 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; + +import org.apache.nifi.admin.service.UserService; +import org.apache.nifi.util.NiFiProperties; +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.node.NodeAuthorizedUserFilter; +import org.apache.nifi.web.security.x509.SubjectDnX509PrincipalExtractor; +import org.apache.nifi.web.security.x509.X509AuthenticationFilter; +import org.apache.nifi.web.security.x509.X509AuthenticationProvider; +import org.apache.nifi.web.security.x509.X509CertificateExtractor; +import org.apache.nifi.web.security.x509.ocsp.OcspCertificateValidator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; +import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; + +/** + * NiFi Web Api Spring security + */ +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapter { + + private NiFiProperties properties; + private UserService userService; + private AuthenticationUserDetailsService userDetailsService; + + public NiFiWebApiSecurityConfiguration() { + super(true); // disable defaults + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .rememberMe().disable() + .exceptionHandling() + .authenticationEntryPoint(new NiFiAuthenticationEntryPoint()) + .and() + .authorizeRequests() + .anyRequest().fullyAuthenticated() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS); + + // cluster - authorized user + final NodeAuthorizedUserFilter authorizedUserFilter = new NodeAuthorizedUserFilter(properties); + http.addFilterBefore(authorizedUserFilter, AnonymousAuthenticationFilter.class); + + // x509 + http.addFilterBefore(buildX509Filter(), AnonymousAuthenticationFilter.class); + + // anonymous + final NiFiAnonymousUserFilter anonymousFilter = new NiFiAnonymousUserFilter(); + anonymousFilter.setProperties(properties); + anonymousFilter.setUserService(userService); + http.anonymous().authenticationFilter(anonymousFilter); + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + // override xxxBean method so the authentication manager is available in app context (necessary for the method level security) + return super.authenticationManagerBean(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // x509 + final AuthenticationProvider x509AuthenticationProvider = new NiFiAuthenticationProvider(new X509AuthenticationProvider(), userDetailsService); + + auth + .authenticationProvider(x509AuthenticationProvider); + } + + private X509AuthenticationFilter buildX509Filter() throws Exception { + final X509AuthenticationFilter x509Filter = new X509AuthenticationFilter(); + x509Filter.setPrincipalExtractor(new SubjectDnX509PrincipalExtractor()); + x509Filter.setCertificateExtractor(new X509CertificateExtractor()); + x509Filter.setCertificateValidator(new OcspCertificateValidator(properties)); + x509Filter.setAuthenticationManager(authenticationManager()); + return x509Filter; + } + + @Autowired + public void setUserDetailsService(AuthenticationUserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Autowired + public void setUserService(UserService userService) { + this.userService = userService; + } + + @Autowired + public void setProperties(NiFiProperties properties) { + this.properties = properties; + } + +} http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java index 2286213..e47b339 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java @@ -16,6 +16,7 @@ */ package org.apache.nifi.web; +import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -32,6 +33,8 @@ import java.util.Set; import java.util.TimeZone; import java.util.UUID; import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.WebApplicationException; @@ -152,6 +155,7 @@ import org.apache.nifi.web.util.SnippetUtils; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.admin.service.AdministrationException; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.components.Validator; import org.apache.nifi.controller.ReportingTaskNode; @@ -168,6 +172,7 @@ import org.apache.nifi.web.api.dto.status.ClusterProcessGroupStatusDTO; import org.apache.nifi.web.api.dto.status.NodeProcessGroupStatusDTO; import org.apache.nifi.web.dao.ControllerServiceDAO; import org.apache.nifi.web.dao.ReportingTaskDAO; +import org.apache.nifi.web.security.user.NewAccountRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.AccessDeniedException; @@ -1808,6 +1813,23 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { } @Override + public UserDTO createUser() { + NewAccountRequest newAccountRequest = NiFiUserUtils.getNewAccountRequest(); + + // log the new user account request + logger.info("Requesting new user account for " + newAccountRequest.getUsername()); + + // get the justification + String justification = newAccountRequest.getJustification(); + if (justification == null) { + justification = StringUtils.EMPTY; + } + + // create the pending user account + return dtoFactory.createUserDTO(userService.createPendingUserAccount(newAccountRequest.getUsername(), justification)); + } + + @Override public UserDTO updateUser(UserDTO userDto) { NiFiUser user; http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java index aa51925..7ad0504 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java @@ -23,7 +23,6 @@ import com.sun.jersey.server.impl.model.method.dispatch.FormDispatchProvider; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; -import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; @@ -44,9 +43,8 @@ import org.apache.nifi.action.Operation; import org.apache.nifi.cluster.context.ClusterContext; import org.apache.nifi.cluster.context.ClusterContextThreadLocal; import org.apache.nifi.cluster.manager.impl.WebClusterManager; -import org.apache.nifi.web.security.DnUtils; +import org.apache.nifi.web.security.ProxiedEntitiesUtils; import org.apache.nifi.web.security.user.NiFiUserDetails; -import org.apache.nifi.web.security.x509.X509CertificateExtractor; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.api.entity.Entity; import org.apache.nifi.web.api.request.ClientIdParameter; @@ -361,12 +359,10 @@ public abstract class ApplicationResource { result.put(PROXY_SCHEME_HTTP_HEADER, httpServletRequest.getScheme()); } - // if this is a secure request, add the custom headers for proxying user requests - final X509Certificate cert = new X509CertificateExtractor().extractClientCertificate(httpServletRequest); - if (cert != null) { + if (httpServletRequest.isSecure()) { // add the certificate DN to the proxy chain - final String xProxiedEntitiesChain = DnUtils.getXProxiedEntitiesChain(httpServletRequest); + final String xProxiedEntitiesChain = ProxiedEntitiesUtils.getXProxiedEntitiesChain(httpServletRequest); if (StringUtils.isNotBlank(xProxiedEntitiesChain)) { result.put(PROXIED_ENTITIES_CHAIN_HTTP_HEADER, xProxiedEntitiesChain); } http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/UserResource.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/UserResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/UserResource.java index 4a61ef4..c7a84c3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/UserResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/UserResource.java @@ -16,12 +16,15 @@ */ package org.apache.nifi.web.api; +import com.sun.jersey.api.Responses; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; import com.wordnik.swagger.annotations.ApiParam; import com.wordnik.swagger.annotations.ApiResponse; import com.wordnik.swagger.annotations.ApiResponses; import com.wordnik.swagger.annotations.Authorization; +import java.io.PrintWriter; +import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -33,12 +36,15 @@ import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.HttpMethod; +import static javax.ws.rs.HttpMethod.POST; +import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -59,9 +65,11 @@ import org.apache.nifi.web.api.entity.UserSearchResultsEntity; import org.apache.nifi.web.api.entity.UsersEntity; import org.apache.nifi.web.api.request.ClientIdParameter; import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.user.NiFiUser; import org.apache.nifi.web.NiFiServiceFacade; import static org.apache.nifi.web.api.ApplicationResource.CLIENT_ID; import org.apache.nifi.web.api.dto.RevisionDTO; +import org.apache.nifi.web.security.user.NiFiUserUtils; import org.springframework.security.access.prepost.PreAuthorize; /** @@ -83,6 +91,31 @@ public class UserResource extends ApplicationResource { private NiFiProperties properties; private NiFiServiceFacade serviceFacade; + @POST + @Consumes(MediaType.WILDCARD) + @Produces(MediaType.TEXT_PLAIN) + @Path("") // necessary due to a bug in swagger + @ApiOperation( + value = "Creates a user", + response = String.class + ) + public Response createUser() { + if (!properties.getSupportNewAccountRequests()) { + return Responses.notFound().entity("This NiFi does not support new account requests.").build(); + } + + final NiFiUser nifiUser = NiFiUserUtils.getNiFiUser(); + if (nifiUser != null) { + throw new IllegalArgumentException("User account already created " + nifiUser.getDn()); + } + + // create an account request for the current user + final UserDTO user = serviceFacade.createUser(); + + final String uri = generateResourceUri("controller", "templates", user.getId()); + return generateCreatedResponse(URI.create(uri), "Not authorized. User account created. Authorization pending.").build(); + } + /** * Gets all users that are registered within this Controller. * http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java index 8bf5553..3bc8120 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java @@ -113,6 +113,7 @@ import org.apache.nifi.authorization.DownloadAuthorization; import org.apache.nifi.processor.DataUnit; import org.apache.nifi.reporting.BulletinQuery; import org.apache.nifi.reporting.ComponentType; +import org.apache.nifi.web.security.ProxiedEntitiesUtils; import org.apache.nifi.web.security.user.NiFiUserUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -822,17 +823,7 @@ public class ControllerFacade { final Map<String, String> attributes = event.getAttributes(); // calculate the dn chain - final List<String> dnChain = new ArrayList<>(); - - // build the dn chain - NiFiUser chainedUser = user; - do { - // add the entry for this user - dnChain.add(chainedUser.getDn()); - - // go to the next user in the chain - chainedUser = chainedUser.getChain(); - } while (chainedUser != null); + final List<String> dnChain = ProxiedEntitiesUtils.getXProxiedEntitiesChain(user); // ensure the users in this chain are allowed to download this content final DownloadAuthorization downloadAuthorization = userService.authorizeDownload(dnChain, attributes); http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/webapp/WEB-INF/web.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/webapp/WEB-INF/web.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/webapp/WEB-INF/web.xml index 4ce319e..b57998d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/webapp/WEB-INF/web.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/webapp/WEB-INF/web.xml @@ -16,15 +16,12 @@ <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <display-name>nifi-api</display-name> <context-param> + <param-name>contextClass</param-name> + <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> + </context-param> + <context-param> <param-name>contextConfigLocation</param-name> - <param-value> - classpath:nifi-context.xml - classpath:nifi-web-api-context.xml - classpath:nifi-web-security-context.xml - classpath:nifi-administration-context.xml - classpath:nifi-cluster-manager-context.xml - classpath:nifi-cluster-protocol-context.xml - </param-value> + <param-value>org.apache.nifi.web</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java index 52f4522..c0e9246 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java @@ -22,8 +22,7 @@ import com.sun.jersey.api.client.WebResource; import com.sun.jersey.core.util.MultivaluedMapImpl; import java.util.Map; import javax.ws.rs.core.MediaType; -import org.apache.nifi.web.security.DnUtils; -import org.apache.nifi.web.security.x509.X509AuthenticationFilter; +import org.apache.nifi.web.security.ProxiedEntitiesUtils; /** * @@ -37,7 +36,7 @@ public class NiFiTestUser { public NiFiTestUser(Client client, String dn) { this.client = client; - this.proxyDn = DnUtils.formatProxyDn(dn); + this.proxyDn = ProxiedEntitiesUtils.formatProxyDn(dn); } /** @@ -70,7 +69,7 @@ public class NiFiTestUser { } // perform the query - return resource.accept(MediaType.APPLICATION_JSON).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn).get(ClientResponse.class); + return resource.accept(MediaType.APPLICATION_JSON).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn).get(ClientResponse.class); } /** @@ -94,7 +93,7 @@ public class NiFiTestUser { */ public ClientResponse testPost(String url, Object entity) throws Exception { // get the resource - WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn); + WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn); // include the request entity if (entity != null) { @@ -115,7 +114,7 @@ public class NiFiTestUser { */ public ClientResponse testPostMultiPart(String url, Object entity) throws Exception { // get the resource - WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_XML).type(MediaType.MULTIPART_FORM_DATA).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn); + WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_XML).type(MediaType.MULTIPART_FORM_DATA).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn); // include the request entity if (entity != null) { @@ -143,7 +142,7 @@ public class NiFiTestUser { // get the resource WebResource.Builder resourceBuilder - = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn); + = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn); // add the form data if necessary if (!entity.isEmpty()) { @@ -164,7 +163,7 @@ public class NiFiTestUser { */ public ClientResponse testPut(String url, Object entity) throws Exception { // get the resource - WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn); + WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn); // include the request entity if (entity != null) { @@ -192,7 +191,7 @@ public class NiFiTestUser { // get the resource WebResource.Builder resourceBuilder - = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn); + = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn); // add the form data if necessary if (!entity.isEmpty()) { @@ -224,7 +223,7 @@ public class NiFiTestUser { */ public ClientResponse testDelete(String url, Object entity) throws Exception { // get the resource - WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_JSON).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn); + WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_JSON).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn); // append any query parameters if (entity != null) { @@ -255,7 +254,7 @@ public class NiFiTestUser { } // perform the request - return resource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn).delete(ClientResponse.class); + return resource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED).header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn).delete(ClientResponse.class); } } http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/DnUtils.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/DnUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/DnUtils.java deleted file mode 100644 index f3bd11e..0000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/DnUtils.java +++ /dev/null @@ -1,85 +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; - -import java.security.cert.X509Certificate; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; -import org.apache.nifi.web.security.x509.SubjectDnX509PrincipalExtractor; -import org.apache.nifi.web.security.x509.X509CertificateExtractor; -import org.apache.commons.lang3.StringUtils; - -/** - * - */ -public class DnUtils { - - private static final Pattern proxyChainPattern = Pattern.compile("<(.*?)>"); - - /** - * @param request http request - * @return the X-ProxiedEntitiesChain from the specified request - */ - public static String getXProxiedEntitiesChain(final HttpServletRequest request) { - String xProxiedEntitiesChain = request.getHeader("X-ProxiedEntitiesChain"); - final X509Certificate cert = new X509CertificateExtractor().extractClientCertificate(request); - if (cert != null) { - final SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); - final String extractedPrincipal = principalExtractor.extractPrincipal(cert).toString(); - final String formattedPrincipal = formatProxyDn(extractedPrincipal); - if (StringUtils.isBlank(xProxiedEntitiesChain)) { - xProxiedEntitiesChain = formattedPrincipal; - } else { - xProxiedEntitiesChain += formattedPrincipal; - } - } - - return xProxiedEntitiesChain; - } - - /** - * Formats the specified DN to be set as a HTTP header using well known - * conventions. - * - * @param dn raw dn - * @return the dn formatted as an HTTP header - */ - public static String formatProxyDn(String dn) { - return "<" + dn + ">"; - } - - /** - * Tokenizes the specified proxy chain. - * - * @param rawProxyChain raw chain - * @return tokenized proxy chain - */ - public static Deque<String> tokenizeProxyChain(String rawProxyChain) { - final Deque<String> dnList = new ArrayDeque<>(); - - // parse the proxy chain - final Matcher rawProxyChainMatcher = proxyChainPattern.matcher(rawProxyChain); - while (rawProxyChainMatcher.find()) { - dnList.push(rawProxyChainMatcher.group(1)); - } - - return dnList; - } -} http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationEntryPoint.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/NiFiAuthenticationEntryPoint.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationEntryPoint.java new file mode 100644 index 0000000..6cae1f0 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationEntryPoint.java @@ -0,0 +1,69 @@ +/* + * 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; + +import java.io.IOException; +import java.io.PrintWriter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.WebAttributes; + +/** + * This is our own implementation of + * org.springframework.security.web.AuthenticationEntryPoint that allows us to + * send the response to the client exactly how we want to and log the results. + */ +public class NiFiAuthenticationEntryPoint implements AuthenticationEntryPoint { + + private static final Logger logger = LoggerFactory.getLogger(NiFiAuthenticationEntryPoint.class); + + /** + * Always returns a 403 error code to the client. + * + * @param request request + * @param response response + * @param ae ae + * @throws java.io.IOException ex + * @throws javax.servlet.ServletException ex + */ + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ae) throws IOException, ServletException { + // get the last exception - the exception that is being passed in is a generic no credentials found + // exception because the authentication could not be found in the security context. the actual cause + // of the problem is stored in the session as the authentication_exception + Object authenticationException = request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); + + // log request result + if (authenticationException instanceof AuthenticationException) { + ae = (AuthenticationException) authenticationException; + logger.info(String.format("Rejecting access to web api: %s", ae.getMessage())); + } + + // set the response status + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + response.setContentType("text/plain"); + + // write the response message + PrintWriter out = response.getWriter(); + out.println("Access is denied."); + } +} http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.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/NiFiAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java new file mode 100644 index 0000000..52ac2f1 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java @@ -0,0 +1,144 @@ +/* + * 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; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.web.security.user.NiFiUserUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; + +/** + * + */ +public abstract class NiFiAuthenticationFilter implements Filter { + + private static final Logger logger = LoggerFactory.getLogger(NiFiAuthenticationFilter.class); + + private AuthenticationManager authenticationManager; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + /** + * + * @param request + * @param response + * @param chain + * @throws java.io.IOException + * @throws javax.servlet.ServletException + */ + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + if (logger.isDebugEnabled()) { + logger.debug("Checking secure context token: " + SecurityContextHolder.getContext().getAuthentication()); + } + + if (requiresAuthentication((HttpServletRequest) request)) { + authenticate((HttpServletRequest) request, (HttpServletResponse) response); + } + + chain.doFilter(request, response); + } + + private boolean requiresAuthentication(final HttpServletRequest request) { + return NiFiUserUtils.getNiFiUser() == null && NiFiUserUtils.getNewAccountRequest() == null; + } + + private void authenticate(final HttpServletRequest request, final HttpServletResponse response) { + try { + final Authentication authenticated = attemptAuthentication(request, response); + if (authenticated != null) { + final Authentication authorized = authenticationManager.authenticate(authenticated); + successfulAuthorization(request, response, authorized); + } + } catch (final AuthenticationException ae) { + unsuccessfulAuthorization(request, response, ae); + } + } + + public abstract Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response); + + protected void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) { + if (logger.isDebugEnabled()) { + logger.debug("Authentication success: " + authResult); + } + + SecurityContextHolder.getContext().setAuthentication(authResult); + ProxiedEntitiesUtils.successfulAuthorization(request, response, authResult); + } + + protected void unsuccessfulAuthorization(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) { + ProxiedEntitiesUtils.unsuccessfulAuthorization(request, response, failed); + } + + /** + * Determines if the specified request is attempting to register a new user account. + * + * @param request http request + * @return true if new user + */ + protected final boolean isNewAccountRequest(HttpServletRequest request) { + if ("POST".equalsIgnoreCase(request.getMethod())) { + String path = request.getPathInfo(); + if (StringUtils.isNotBlank(path)) { + if ("/controller/users".equals(path)) { + return true; + } + } + } + return false; + } + + /** + * Extracts the justification from the specified request. + * + * @param request The request + * @return The justification + */ + protected final String getJustification(HttpServletRequest request) { + // get the justification + String justification = request.getParameter("justification"); + if (justification == null) { + justification = StringUtils.EMPTY; + } + return justification; + } + + @Override + public void destroy() { + } + + public void setAuthenticationManager(AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + +} http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.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/NiFiAuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java new file mode 100644 index 0000000..5f15b76 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java @@ -0,0 +1,62 @@ +package org.apache.nifi.web.security; + +import org.apache.nifi.web.security.token.NewAccountAuthenticationToken; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; +import org.apache.nifi.web.security.token.NiFiAuthorizationToken; +import org.springframework.security.authentication.AuthenticationProvider; +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.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * + */ +public class NiFiAuthenticationProvider implements AuthenticationProvider { + + private final AuthenticationProvider provider; + private final AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService; + + public NiFiAuthenticationProvider(final AuthenticationProvider provider, final AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService) { + this.provider = provider; + this.userDetailsService = userDetailsService; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + final NiFiAuthenticationRequestToken request = (NiFiAuthenticationRequestToken) authentication; + + // ensure the base provider could authenticate + final Authentication result = provider.authenticate(request); + if (result == null) { + return null; + } + + try { + // defer to the nifi user details service to authorize the user + final UserDetails userDetails = userDetailsService.loadUserDetails(request); + + // build an authentication for accesing nifi + return new NiFiAuthorizationToken(userDetails); + } catch (final UsernameNotFoundException unfe) { + // if the result was an authenticated new account request and it could not be authorized because the user was not found, + // return the token so the new account could be created + if (isNewAccountAuthenticationToken(result)) { + return result; + } else { + throw unfe; + } + } + } + + private boolean isNewAccountAuthenticationToken(final Authentication authentication) { + return NewAccountAuthenticationToken.class.isAssignableFrom(authentication.getClass()); + } + + @Override + public boolean supports(Class<?> authentication) { + return provider.supports(authentication) && NiFiAuthenticationRequestToken.class.isAssignableFrom(authentication); + } + +} http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.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/ProxiedEntitiesUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java new file mode 100644 index 0000000..e0d810c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java @@ -0,0 +1,168 @@ +/* + * 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; + +import java.security.cert.X509Certificate; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.nifi.web.security.x509.SubjectDnX509PrincipalExtractor; +import org.apache.nifi.web.security.x509.X509CertificateExtractor; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.user.NiFiUser; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + +/** + * + */ +public class ProxiedEntitiesUtils { + + public static final String PROXY_ENTITIES_CHAIN = "X-ProxiedEntitiesChain"; + public static final String PROXY_ENTITIES_ACCEPTED = "X-ProxiedEntitiesAccepted"; + public static final String PROXY_ENTITIES_DETAILS = "X-ProxiedEntitiesDetails"; + + private static final Pattern proxyChainPattern = Pattern.compile("<(.*?)>"); + + /** + * @param request http request + * @return the X-ProxiedEntitiesChain from the specified request + */ + public static String getXProxiedEntitiesChain(final HttpServletRequest request) { + String xProxiedEntitiesChain = request.getHeader("X-ProxiedEntitiesChain"); + final X509Certificate cert = new X509CertificateExtractor().extractClientCertificate(request); + if (cert != null) { + final SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); + final String extractedPrincipal = principalExtractor.extractPrincipal(cert).toString(); + final String formattedPrincipal = formatProxyDn(extractedPrincipal); + if (StringUtils.isBlank(xProxiedEntitiesChain)) { + xProxiedEntitiesChain = formattedPrincipal; + } else { + xProxiedEntitiesChain += formattedPrincipal; + } + } + + return xProxiedEntitiesChain; + } + + /** + * Builds the dn chain for the specified user. + * + * @param user The current user + * @return The dn chain for that user + */ + public static List<String> getXProxiedEntitiesChain(final NiFiUser user) { + // calculate the dn chain + final List<String> dnChain = new ArrayList<>(); + + // build the dn chain + NiFiUser chainedUser = user; + do { + // add the entry for this user + dnChain.add(chainedUser.getDn()); + + // go to the next user in the chain + chainedUser = chainedUser.getChain(); + } while (chainedUser != null); + + return dnChain; + } + + /** + * Formats the specified DN to be set as a HTTP header using well known + * conventions. + * + * @param dn raw dn + * @return the dn formatted as an HTTP header + */ + public static String formatProxyDn(String dn) { + return "<" + dn + ">"; + } + +// /** +// * Tokenizes the specified proxy chain. +// * +// * @param rawProxyChain raw chain +// * @return tokenized proxy chain +// */ +// public static Deque<String> tokenizeProxyChain(String rawProxyChain) { +// final Deque<String> dnList = new ArrayDeque<>(); +// +// // parse the proxy chain +// final Matcher rawProxyChainMatcher = proxyChainPattern.matcher(rawProxyChain); +// while (rawProxyChainMatcher.find()) { +// dnList.push(rawProxyChainMatcher.group(1)); +// } +// +// return dnList; +// } + + public static List<String> buildProxyChain(final HttpServletRequest request, final String username) { + String principal; + if (username.startsWith("<") && username.endsWith(">")) { + principal = username; + } else { + principal = formatProxyDn(username); + } + + // look for a proxied user + if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { + principal = request.getHeader(PROXY_ENTITIES_CHAIN) + principal; + } + + // parse the proxy chain + final List<String> proxyChain = new ArrayList<>(); + final Matcher rawProxyChainMatcher = proxyChainPattern.matcher(principal); + while (rawProxyChainMatcher.find()) { + proxyChain.add(rawProxyChainMatcher.group(1)); + } + + return proxyChain; + } + + public static String extractProxiedEntitiesChain(final HttpServletRequest request, final String username) { + String principal; + if (username.startsWith("<") && username.endsWith(">")) { + principal = username; + } else { + principal = formatProxyDn(username); + } + + // look for a proxied user + if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { + principal = request.getHeader(PROXY_ENTITIES_CHAIN) + principal; + } + return principal; + } + + public static void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) { + if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { + response.setHeader(PROXY_ENTITIES_ACCEPTED, Boolean.TRUE.toString()); + } + } + + public static void unsuccessfulAuthorization(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) { + if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { + response.setHeader(PROXY_ENTITIES_DETAILS, failed.getMessage()); + } + } +} http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.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/anonymous/NiFiAnonymousUserFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java index 295f09c..7026124 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java @@ -16,8 +16,6 @@ */ package org.apache.nifi.web.security.anonymous; -import java.util.ArrayList; -import java.util.List; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.admin.service.AdministrationException; @@ -25,11 +23,10 @@ import org.apache.nifi.admin.service.UserService; import org.apache.nifi.user.NiFiUser; import org.apache.nifi.web.security.user.NiFiUserDetails; import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.web.security.token.NiFiAuthorizationToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; /** @@ -51,45 +48,31 @@ public class NiFiAnonymousUserFilter extends AnonymousAuthenticationFilter { @Override protected Authentication createAuthentication(HttpServletRequest request) { - Authentication authentication; - try { - // load the anonymous user from the database - NiFiUser user = userService.getUserByDn(NiFiUser.ANONYMOUS_USER_DN); - NiFiUserDetails userDetails = new NiFiUserDetails(user); + Authentication authentication = null; - // get the granted authorities - List<GrantedAuthority> authorities = new ArrayList<>(userDetails.getAuthorities()); - authentication = new AnonymousAuthenticationToken(ANONYMOUS_KEY, userDetails, authorities); - } catch (AdministrationException ase) { - // record the issue - anonymousUserFilterLogger.warn("Unable to load anonymous user from accounts database: " + ase.getMessage()); - if (anonymousUserFilterLogger.isDebugEnabled()) { - anonymousUserFilterLogger.warn(StringUtils.EMPTY, ase); - } + // only support anonymous when the request is non-secure or one way ssl +// if (!request.isSecure() || !properties.getNeedClientAuth()) { + if (true) { + try { + // load the anonymous user from the database + NiFiUser user = userService.getUserByDn(NiFiUser.ANONYMOUS_USER_DN); + NiFiUserDetails userDetails = new NiFiUserDetails(user); - // defer to the base implementation - authentication = super.createAuthentication(request); + // get the granted authorities + authentication = new NiFiAuthorizationToken(userDetails); + } catch (AdministrationException ase) { + // record the issue + anonymousUserFilterLogger.warn("Unable to load anonymous user from accounts database: " + ase.getMessage()); + if (anonymousUserFilterLogger.isDebugEnabled()) { + anonymousUserFilterLogger.warn(StringUtils.EMPTY, ase); + } + } } return authentication; } - /** - * Only supports anonymous users for non-secure requests or one way ssl. - * - * @param request request - * @return true if allowed - */ - @Override - protected boolean applyAnonymousForThisRequest(HttpServletRequest request) { - // anonymous for non secure requests - if ("http".equalsIgnoreCase(request.getScheme())) { - return true; - } - - return !properties.getNeedClientAuth(); - } - /* setters */ + public void setUserService(UserService userService) { this.userService = userService; } http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authentication/NiFiAuthenticationEntryPoint.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/authentication/NiFiAuthenticationEntryPoint.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authentication/NiFiAuthenticationEntryPoint.java deleted file mode 100644 index cd5f1ac..0000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authentication/NiFiAuthenticationEntryPoint.java +++ /dev/null @@ -1,69 +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.authentication; - -import java.io.IOException; -import java.io.PrintWriter; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.WebAttributes; - -/** - * This is our own implementation of - * org.springframework.security.web.AuthenticationEntryPoint that allows us to - * send the response to the client exactly how we want to and log the results. - */ -public class NiFiAuthenticationEntryPoint implements AuthenticationEntryPoint { - - private static final Logger logger = LoggerFactory.getLogger(NiFiAuthenticationEntryPoint.class); - - /** - * Always returns a 403 error code to the client. - * - * @param request request - * @param response response - * @param ae ae - * @throws java.io.IOException ex - * @throws javax.servlet.ServletException ex - */ - @Override - public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ae) throws IOException, ServletException { - // get the last exception - the exception that is being passed in is a generic no credentials found - // exception because the authentication could not be found in the security context. the actual cause - // of the problem is stored in the session as the authentication_exception - Object authenticationException = request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); - - // log request result - if (authenticationException instanceof AuthenticationException) { - ae = (AuthenticationException) authenticationException; - logger.info(String.format("Rejecting access to web api: %s", ae.getMessage())); - } - - // set the response status - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - response.setContentType("text/plain"); - - // write the response message - PrintWriter out = response.getWriter(); - out.println("Access is denied."); - } -} http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.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/authorization/NiFiAuthorizationService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java index 95b4669..c3a5e43 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java @@ -16,34 +16,35 @@ */ package org.apache.nifi.web.security.authorization; -import java.util.Deque; -import java.util.Iterator; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import org.apache.commons.lang3.StringUtils; 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.authorization.Authority; -import org.apache.nifi.web.security.DnUtils; import org.apache.nifi.user.NiFiUser; -import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.security.UntrustedProxyException; import org.apache.nifi.web.security.user.NiFiUserDetails; -import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; import org.springframework.security.authentication.AccountStatusException; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; /** * UserDetailsService that will verify user identity and grant user authorities. */ -public class NiFiAuthorizationService implements UserDetailsService { +public class NiFiAuthorizationService implements AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> { private static final Logger logger = LoggerFactory.getLogger(NiFiAuthorizationService.class); @@ -58,30 +59,30 @@ public class NiFiAuthorizationService implements UserDetailsService { * made for each individual request as a whole (without other request * potentially impacting it). * - * @param rawProxyChain proxy chain + * @param request request * @return user details * @throws UsernameNotFoundException ex * @throws org.springframework.dao.DataAccessException ex */ @Override - public synchronized UserDetails loadUserByUsername(String rawProxyChain) throws UsernameNotFoundException, DataAccessException { + public synchronized UserDetails loadUserDetails(NiFiAuthenticationRequestToken request) throws UsernameNotFoundException, DataAccessException { NiFiUserDetails userDetails = null; - final Deque<String> dnList = DnUtils.tokenizeProxyChain(rawProxyChain); + final List<String> chain = new ArrayList<>(request.getChain()); // ensure valid input - if (dnList.size() == 0) { - logger.warn("Malformed proxy chain: " + rawProxyChain); + if (chain.isEmpty()) { + logger.warn("Malformed proxy chain: " + StringUtils.join(request.getChain())); throw new UntrustedProxyException("Malformed proxy chain."); } NiFiUser proxy = null; // process each part of the proxy chain - for (final Iterator<String> dnIter = dnList.iterator(); dnIter.hasNext();) { - final String dn = dnIter.next(); + for (final ListIterator<String> chainIter = request.getChain().listIterator(chain.size()); chainIter.hasPrevious();) { + final String dn = chainIter.previous(); // if there is another dn after this one, this dn is a proxy for the request - if (dnIter.hasNext()) { + if (chainIter.hasPrevious()) { try { // get the user details for the proxy final NiFiUserDetails proxyDetails = getNiFiUserDetails(dn); @@ -110,7 +111,7 @@ public class NiFiAuthorizationService implements UserDetailsService { userService.createPendingUserAccount(dn, "Automatic account request generated for unknown proxy."); // propagate the exception to return the appropriate response - throw new UsernameNotFoundException(String.format("An account request was generated for the proxy '%s'.", dn)); + throw new UntrustedProxyException(String.format("An account request was generated for the proxy '%s'.", dn)); } catch (AdministrationException ae) { throw new AuthenticationServiceException(String.format("Unable to create an account request for '%s': %s", dn, ae.getMessage()), ae); } catch (IllegalArgumentException iae) { http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NodeAuthorizedUserFilter.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/authorization/NodeAuthorizedUserFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NodeAuthorizedUserFilter.java deleted file mode 100644 index 80feed7..0000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NodeAuthorizedUserFilter.java +++ /dev/null @@ -1,128 +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.authorization; - -import java.io.IOException; -import java.io.Serializable; -import java.security.cert.X509Certificate; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import org.apache.nifi.controller.FlowController; -import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.web.security.user.NiFiUserDetails; -import org.apache.nifi.web.security.x509.SubjectDnX509PrincipalExtractor; -import org.apache.nifi.web.security.x509.X509CertificateExtractor; -import org.apache.nifi.user.NiFiUser; -import org.apache.nifi.util.NiFiProperties; -import org.apache.nifi.web.util.WebUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; -import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; -import org.springframework.web.context.support.WebApplicationContextUtils; -import org.springframework.web.filter.GenericFilterBean; - -/** - * Custom filter to extract a user's authorities from the request where the user - * was authenticated by the cluster manager and populate the threadlocal with - * the authorized user. If the request contains the appropriate header with - * authorities and the application instance is a node connected to the cluster, - * then the authentication/authorization steps remaining in the filter chain are - * skipped. - * - * Checking if the application instance is a connected node is important because - * it prevents external clients from faking the request headers and bypassing - * the authentication processing chain. - */ -public class NodeAuthorizedUserFilter extends GenericFilterBean { - - private static final Logger LOGGER = LoggerFactory.getLogger(NodeAuthorizedUserFilter.class); - - public static final String PROXY_USER_DETAILS = "X-ProxiedEntityUserDetails"; - - private NiFiProperties properties; - private final AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - private final X509CertificateExtractor certificateExtractor = new X509CertificateExtractor(); - private final X509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - final HttpServletRequest httpServletRequest = (HttpServletRequest) request; - - // get the proxied user's authorities - final String hexEncodedUserDetails = httpServletRequest.getHeader(PROXY_USER_DETAILS); - - // check if the request has the necessary header information and this instance is configured as a node - if (StringUtils.isNotBlank(hexEncodedUserDetails) && properties.isNode()) { - - // get the flow controller from the Spring context - final ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); - final FlowController flowController = ctx.getBean("flowController", FlowController.class); - - // check that we are connected to the cluster - if (flowController.getNodeId() != null) { - try { - // get the DN from the cert in the request - final X509Certificate certificate = certificateExtractor.extractClientCertificate((HttpServletRequest) request); - if (certificate != null) { - // extract the principal from the certificate - final Object certificatePrincipal = principalExtractor.extractPrincipal(certificate); - final String dn = certificatePrincipal.toString(); - - // only consider the pre-authorized user when the request came from the NCM according to the DN in the certificate - final String clusterManagerDN = flowController.getClusterManagerDN(); - if (clusterManagerDN != null && clusterManagerDN.equals(dn)) { - // deserialize hex encoded object - final Serializable userDetailsObj = WebUtils.deserializeHexToObject(hexEncodedUserDetails); - - // if we have a valid object, set the authentication token and bypass the remaining authentication processing chain - if (userDetailsObj instanceof NiFiUserDetails) { - final NiFiUserDetails userDetails = (NiFiUserDetails) userDetailsObj; - final NiFiUser user = userDetails.getNiFiUser(); - - // log the request attempt - response details will be logged later - logger.info(String.format("Attempting request for (%s) %s %s (source ip: %s)", user.getDn(), httpServletRequest.getMethod(), - httpServletRequest.getRequestURL().toString(), request.getRemoteAddr())); - - // we do not create the authentication token with the X509 certificate because the certificate is from the sending system, not the proxied user - final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - token.setDetails(authenticationDetailsSource.buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(token); - } - } - } - } catch (final ClassNotFoundException cnfe) { - LOGGER.warn("Classpath issue detected because failed to deserialize authorized user in request header due to: " + cnfe, cnfe); - } - } - } - - chain.doFilter(request, response); - } - - /* setters */ - public void setProperties(NiFiProperties properties) { - this.properties = properties; - } -} http://git-wip-us.apache.org/repos/asf/nifi/blob/61046707/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/FormAuthenticationFilter.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/FormAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/FormAuthenticationFilter.java new file mode 100644 index 0000000..5367ba2 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/FormAuthenticationFilter.java @@ -0,0 +1,72 @@ +/* + * 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.web.security.jwt.JwtService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; + +/** + */ +public class FormAuthenticationFilter extends AbstractAuthenticationProcessingFilter { + + private static final Logger logger = LoggerFactory.getLogger(FormAuthenticationFilter.class); + + private JwtService jwtService; + + public FormAuthenticationFilter(final String defaultFilterProcessesUrl) { + super(defaultFilterProcessesUrl); + } + + @Override + public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws AuthenticationException, IOException, ServletException { + final String username = request.getParameter("username"); + final String password = request.getParameter("password"); + return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(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 { + // set the response status + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("text/plain"); + + final PrintWriter out = response.getWriter(); + out.println("Invalid username/password"); + } + + public void setJwtService(JwtService jwtService) { + this.jwtService = jwtService; + } + +}
