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/jwt/JwtAuthenticationFilter.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/jwt/JwtAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java new file mode 100644 index 0000000..5b85bd2 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,58 @@ +/* + * 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.jwt; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.nifi.web.security.NiFiAuthenticationFilter; +import org.apache.nifi.web.security.ProxiedEntitiesUtils; +import org.apache.nifi.web.security.token.NewAccountAuthenticationRequestToken; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; +import org.apache.nifi.web.security.user.NewAccountRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; + +/** + */ +public class JwtAuthenticationFilter extends NiFiAuthenticationFilter { + + private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class); + + private JwtService jwtService; + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) { + final String principal = jwtService.getAuthentication(request); + if (principal == null) { + return null; + } + + final List<String> proxyChain = ProxiedEntitiesUtils.buildProxyChain(request, principal); + if (isNewAccountRequest(request)) { + return new NewAccountAuthenticationRequestToken(new NewAccountRequest(proxyChain, getJustification(request))); + } else { + return new NiFiAuthenticationRequestToken(proxyChain); + } + } + + public void setJwtService(JwtService jwtService) { + this.jwtService = jwtService; + } + +}
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/jwt/JwtAuthenticationProvider.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/jwt/JwtAuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProvider.java new file mode 100644 index 0000000..77e9982 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProvider.java @@ -0,0 +1,47 @@ +/* + * 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.jwt; + +import org.apache.nifi.web.security.token.NewAccountAuthenticationRequestToken; +import org.apache.nifi.web.security.token.NewAccountAuthenticationToken; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + +/** + * + */ +public class JwtAuthenticationProvider implements AuthenticationProvider { + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + if (NewAccountAuthenticationRequestToken.class.isAssignableFrom(authentication.getClass())) { + return new NewAccountAuthenticationToken(((NewAccountAuthenticationRequestToken) authentication).getNewAccountRequest()); + } else if (NiFiAuthenticationRequestToken.class.isAssignableFrom(authentication.getClass())) { + return authentication; + } else { + return null; + } + } + + @Override + public boolean supports(Class<?> authentication) { + return 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/jwt/JwtService.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/jwt/JwtService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java new file mode 100644 index 0000000..803cd79 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java @@ -0,0 +1,51 @@ +/* + * 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.jwt; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.core.Authentication; + +/** + * + */ +public class JwtService { + + /** + * Gets the Authentication by extracting a JWT token from the specified request. + * + * @param request Request to extract the token from + * @return The user identifier from the token + */ + public String getAuthentication(final HttpServletRequest request) { + // extract/verify token from incoming request + // load user details with identifier from token + // create authentication using user details + return null; + } + + /** + * Adds a token for the specified authentication in the specified response. + * + * @param response The response to add the token to + * @param authentication The authentication to generate a token for + */ + public void addToken(final HttpServletResponse response, final Authentication authentication) { + // create a token the specified authentication + // add the token as a response header + } +} 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/node/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/node/NodeAuthorizedUserFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/node/NodeAuthorizedUserFilter.java new file mode 100644 index 0000000..e73fd9e --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/node/NodeAuthorizedUserFilter.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.security.node; + +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 final NiFiProperties properties; + private final AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); + private final X509CertificateExtractor certificateExtractor = new X509CertificateExtractor(); + private final X509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); + + public NodeAuthorizedUserFilter(NiFiProperties properties) { + this.properties = properties; + } + + @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); + } +} 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/token/NewAccountAuthenticationRequestToken.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/token/NewAccountAuthenticationRequestToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthenticationRequestToken.java new file mode 100644 index 0000000..52e5172 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthenticationRequestToken.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.web.security.token; + +import org.apache.nifi.web.security.user.NewAccountRequest; + +/** + * This is an Authentication Token for a user that is request authentication in + * order to submit a new account request. + */ +public class NewAccountAuthenticationRequestToken extends NiFiAuthenticationRequestToken { + + final NewAccountRequest newAccountRequest; + + public NewAccountAuthenticationRequestToken(final NewAccountRequest newAccountRequest) { + super(newAccountRequest.getChain()); + this.newAccountRequest = newAccountRequest; + } + + public String getJustification() { + return newAccountRequest.getJustification(); + } + + public NewAccountRequest getNewAccountRequest() { + return newAccountRequest; + } +} 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/token/NewAccountAuthenticationToken.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/token/NewAccountAuthenticationToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthenticationToken.java new file mode 100644 index 0000000..6fe34df --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthenticationToken.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.web.security.token; + +import org.apache.nifi.web.security.user.NewAccountRequest; +import org.springframework.security.authentication.AbstractAuthenticationToken; + +/** + * This is an Authentication Token for a user that has been authenticated but is + * not authorized to access the NiFi APIs. Typically, this authentication token is + * used successfully when requesting a NiFi account. Requesting any other endpoint + * would be rejected due to lack of roles. + */ +public class NewAccountAuthenticationToken extends AbstractAuthenticationToken { + + final NewAccountRequest newAccountRequest; + + public NewAccountAuthenticationToken(final NewAccountRequest newAccountRequest) { + super(null); + super.setAuthenticated(true); + this.newAccountRequest = newAccountRequest; + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getPrincipal() { + return newAccountRequest; + } + +} 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/token/NiFiAuthenticationRequestToken.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/token/NiFiAuthenticationRequestToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthenticationRequestToken.java new file mode 100644 index 0000000..0e00e5b --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthenticationRequestToken.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.web.security.token; + +import java.util.Collections; +import java.util.List; +import org.springframework.security.authentication.AbstractAuthenticationToken; + +/** + * An authentication token that is used as an authentication request. The request + * chain is specified during creation and is used authenticate the user(s). If the + * user is authenticated, the token is used to authorized the user(s). + */ +public class NiFiAuthenticationRequestToken extends AbstractAuthenticationToken { + + private final List<String> chain; + + public NiFiAuthenticationRequestToken(final List<String> chain) { + super(null); + this.chain = chain; + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getPrincipal() { + return chain; + } + + public List<String> getChain() { + return Collections.unmodifiableList(chain); + } + + @Override + public final void setAuthenticated(boolean authenticated) { + throw new IllegalArgumentException("Cannot change the authenticated state."); + } +} 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/token/NiFiAuthorizationToken.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/token/NiFiAuthorizationToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthorizationToken.java new file mode 100644 index 0000000..0945d08 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthorizationToken.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.web.security.token; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.userdetails.UserDetails; + +/** + * An authentication token that represents an Authenticated and Authorized user + * of the NiFi Apis. The authorities are based off the specified UserDetails. + */ +public class NiFiAuthorizationToken extends AbstractAuthenticationToken { + + final UserDetails nifiUserDetails; + + public NiFiAuthorizationToken(final UserDetails nifiUserDetails) { + super(nifiUserDetails.getAuthorities()); + super.setAuthenticated(true); + setDetails(nifiUserDetails); + this.nifiUserDetails = nifiUserDetails; + } + + @Override + public Object getCredentials() { + return nifiUserDetails.getPassword(); + } + + @Override + public Object getPrincipal() { + return nifiUserDetails; + } + + @Override + public final void setAuthenticated(boolean authenticated) { + throw new IllegalArgumentException("Cannot change the authenticated state."); + } +} 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/user/NewAccountRequest.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/user/NewAccountRequest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NewAccountRequest.java new file mode 100644 index 0000000..e70c5ab --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NewAccountRequest.java @@ -0,0 +1,31 @@ +package org.apache.nifi.web.security.user; + +import java.util.List; + +/** + * + */ +public class NewAccountRequest { + + private final List<String> chain; + private final String justification; + + public NewAccountRequest(final List<String> chain, final String justification) { + this.chain = chain; + this.justification = justification; + } + + public List<String> getChain() { + return chain; + } + + public String getJustification() { + return justification; + } + + public String getUsername() { + // the end user is the first item in the chain + return chain.get(0); + } + +} 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/user/NiFiUserDetails.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/user/NiFiUserDetails.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NiFiUserDetails.java index c69b1e6..0fc2f53 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NiFiUserDetails.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NiFiUserDetails.java @@ -41,7 +41,7 @@ public class NiFiUserDetails implements UserDetails { public NiFiUserDetails(NiFiUser user) { this.user = user; } - + /** * Get the user for this UserDetails. * @@ -76,7 +76,6 @@ public class NiFiUserDetails implements UserDetails { return user.getDn(); } - // TODO: not sure how to handle these yet @Override public boolean isAccountNonExpired() { return true; 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/user/NiFiUserUtils.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/user/NiFiUserUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NiFiUserUtils.java index bf1fe43..e435ade 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NiFiUserUtils.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NiFiUserUtils.java @@ -78,6 +78,27 @@ public final class NiFiUserUtils { return user; } + + /** + * Returns the NewAccountRequest or null if this is not a new account request. + * + * @return new account request + */ + public static NewAccountRequest getNewAccountRequest() { + NewAccountRequest newAccountRequest = null; + + // obtain the principal in the current authentication + final SecurityContext context = SecurityContextHolder.getContext(); + final Authentication authentication = context.getAuthentication(); + if (authentication != null) { + Object principal = authentication.getPrincipal(); + if (principal instanceof NewAccountRequest) { + newAccountRequest = (NewAccountRequest) principal; + } + } + + return newAccountRequest; + } public static String getNiFiUserName() { // get the nifi user to extract the username 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/x509/X509AuthenticationFilter.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/x509/X509AuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java index 72baecb..609edd8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java @@ -17,114 +17,36 @@ package org.apache.nifi.web.security.x509; import org.apache.nifi.web.security.x509.ocsp.OcspCertificateValidator; -import java.io.IOException; -import java.io.PrintWriter; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.nifi.admin.service.AdministrationException; -import org.apache.nifi.admin.service.UserService; -import org.apache.nifi.web.security.DnUtils; -import org.apache.nifi.web.security.UntrustedProxyException; -import org.apache.nifi.util.NiFiProperties; -import org.apache.commons.lang3.StringUtils; -import org.springframework.security.authentication.AccountStatusException; -import org.springframework.security.authentication.AuthenticationServiceException; +import org.apache.nifi.web.security.NiFiAuthenticationFilter; +import org.apache.nifi.web.security.ProxiedEntitiesUtils; +import org.apache.nifi.web.security.token.NewAccountAuthenticationRequestToken; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; +import org.apache.nifi.web.security.user.NewAccountRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; 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.preauth.AbstractPreAuthenticatedProcessingFilter; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; /** * Custom X509 filter that will inspect the HTTP headers for a proxied user * before extracting the user details from the client certificate. */ -public class X509AuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter { +public class X509AuthenticationFilter extends NiFiAuthenticationFilter { - 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 final X509CertificateExtractor certificateExtractor = new X509CertificateExtractor(); - private final X509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); + private static final Logger logger = LoggerFactory.getLogger(X509AuthenticationFilter.class); + + private X509PrincipalExtractor principalExtractor; + private X509CertificateExtractor certificateExtractor; private OcspCertificateValidator certificateValidator; - private NiFiProperties properties; - private UserService userService; - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - final HttpServletResponse httpResponse = (HttpServletResponse) response; - - // determine if this request is attempting to create a new account - if (isNewAccountRequest((HttpServletRequest) request)) { - // determine if this nifi supports new account requests - if (properties.getSupportNewAccountRequests()) { - // ensure there is a certificate in the request - X509Certificate certificate = certificateExtractor.extractClientCertificate((HttpServletRequest) request); - if (certificate != null) { - // extract the principal from the certificate - Object certificatePrincipal = principalExtractor.extractPrincipal(certificate); - String principal = certificatePrincipal.toString(); - - // log the new user account request - logger.info("Requesting new user account for " + principal); - - try { - // get the justification - String justification = request.getParameter("justification"); - if (justification == null) { - justification = StringUtils.EMPTY; - } - - // create the pending user account - userService.createPendingUserAccount(principal, justification); - - // generate a response - httpResponse.setStatus(HttpServletResponse.SC_CREATED); - httpResponse.setContentType("text/plain"); - - // write the response message - PrintWriter out = response.getWriter(); - out.println("Not authorized. User account created. Authorization pending."); - } catch (IllegalArgumentException iae) { - handleUserServiceError((HttpServletRequest) request, httpResponse, HttpServletResponse.SC_BAD_REQUEST, iae.getMessage()); - } catch (AdministrationException ae) { - handleUserServiceError((HttpServletRequest) request, httpResponse, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ae.getMessage()); - } - } else { - // can this really happen? - handleMissingCertificate((HttpServletRequest) request, httpResponse); - } - } else { - handleUserServiceError((HttpServletRequest) request, httpResponse, HttpServletResponse.SC_NOT_FOUND, "This NiFi does not support new account requests."); - } - } else { - try { - // this not a request to create a user account - try to authorize - super.doFilter(request, response, chain); - } catch (AuthenticationException ae) { - // continue the filter chain since anonymous access should be supported - if (!properties.getNeedClientAuth()) { - chain.doFilter(request, response); - } else { - // create an appropriate response for the given exception - handleUnsuccessfulAuthentication((HttpServletRequest) request, httpResponse, ae); - } - } - } - } @Override - protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) { - String principal; - + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) { // extract the cert X509Certificate certificate = certificateExtractor.extractClientCertificate(request); @@ -135,7 +57,7 @@ public class X509AuthenticationFilter extends AbstractPreAuthenticatedProcessing // extract the principal Object certificatePrincipal = principalExtractor.extractPrincipal(certificate); - principal = DnUtils.formatProxyDn(certificatePrincipal.toString()); + final String principal = ProxiedEntitiesUtils.formatProxyDn(certificatePrincipal.toString()); try { // ensure the cert is valid @@ -166,152 +88,27 @@ public class X509AuthenticationFilter extends AbstractPreAuthenticatedProcessing } return null; } - - // look for a proxied user - if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { - principal = request.getHeader(PROXY_ENTITIES_CHAIN) + principal; - } - - // log the request attempt - response details will be logged later - logger.info(String.format("Attempting request for (%s) %s %s (source ip: %s)", principal, request.getMethod(), - request.getRequestURL().toString(), request.getRemoteAddr())); - - return principal; - } - - @Override - protected Object getPreAuthenticatedCredentials(HttpServletRequest request) { - return certificateExtractor.extractClientCertificate(request); - } - - @Override - protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) { - if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { - response.setHeader(PROXY_ENTITIES_ACCEPTED, Boolean.TRUE.toString()); - } - super.successfulAuthentication(request, response, authResult); - } - - @Override - protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) { - if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { - response.setHeader(PROXY_ENTITIES_DETAILS, failed.getMessage()); - } - super.unsuccessfulAuthentication(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 - */ - private 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; - } - - /** - * Handles requests that were unable to be authorized. - * - * @param request request - * @param response response - * @param ae ex - * @throws IOException ex - */ - private void handleUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException ae) throws IOException { - // set the response status - response.setContentType("text/plain"); - - // write the response message - PrintWriter out = response.getWriter(); - - // use the type of authentication exception to determine the response code - if (ae instanceof UsernameNotFoundException) { - if (properties.getSupportNewAccountRequests()) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - out.println("Not authorized."); - } else { - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - out.println("Access is denied."); - } - } else if (ae instanceof AccountStatusException) { - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - out.println(ae.getMessage()); - } else if (ae instanceof UntrustedProxyException) { - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - out.println(ae.getMessage()); - } else if (ae instanceof AuthenticationServiceException) { - logger.error(String.format("Unable to authorize: %s", ae.getMessage()), ae); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - out.println(String.format("Unable to authorize: %s", ae.getMessage())); + + final List<String> proxyChain = ProxiedEntitiesUtils.buildProxyChain(request, principal); + if (isNewAccountRequest(request)) { + return new NewAccountAuthenticationRequestToken(new NewAccountRequest(proxyChain, getJustification(request))); } else { - logger.error(String.format("Unable to authorize: %s", ae.getMessage()), ae); - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - out.println("Access is denied."); - } - - // log the failure - logger.info(String.format("Rejecting access to web api: %s", ae.getMessage())); - - // optionally log the stack trace - if (logger.isDebugEnabled()) { - logger.debug(StringUtils.EMPTY, ae); + return new NiFiAuthenticationRequestToken(proxyChain); } } - private void handleUserServiceError(HttpServletRequest request, HttpServletResponse response, int responseCode, String message) throws IOException { - // set the response status - response.setContentType("text/plain"); - response.setStatus(responseCode); - - // write the response message - PrintWriter out = response.getWriter(); - out.println(message); - - // log the failure - logger.info(String.format("Unable to process request because %s", message)); - } - - /** - * Handles requests that failed because they were bad input. - * - * @param request request - * @param response response - * @throws IOException ioe - */ - private void handleMissingCertificate(HttpServletRequest request, HttpServletResponse response) throws IOException { - // set the response status - response.setContentType("text/plain"); - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - - // write the response message - PrintWriter out = response.getWriter(); - out.println("Unable to process request because the user certificate was not specified."); - - // log the failure - logger.info("Unable to process request because the user certificate was not specified."); - } - /* setters */ - public void setProperties(NiFiProperties properties) { - this.properties = properties; + + public void setCertificateValidator(OcspCertificateValidator certificateValidator) { + this.certificateValidator = certificateValidator; } - public void setUserService(UserService userService) { - this.userService = userService; + public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) { + this.principalExtractor = principalExtractor; } - public void setCertificateValidator(OcspCertificateValidator certificateValidator) { - this.certificateValidator = certificateValidator; + public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) { + this.certificateExtractor = certificateExtractor; } } 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/x509/X509AuthenticationFilterOld.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/x509/X509AuthenticationFilterOld.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilterOld.java new file mode 100644 index 0000000..711639b --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilterOld.java @@ -0,0 +1,317 @@ +/* + * 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.x509; + +import org.apache.nifi.web.security.x509.ocsp.OcspCertificateValidator; +import java.io.IOException; +import java.io.PrintWriter; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +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 javax.servlet.http.HttpServletResponse; +import org.apache.nifi.admin.service.AdministrationException; +import org.apache.nifi.admin.service.UserService; +import org.apache.nifi.web.security.ProxiedEntitiesUtils; +import org.apache.nifi.web.security.UntrustedProxyException; +import org.apache.nifi.util.NiFiProperties; +import org.apache.commons.lang3.StringUtils; +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.preauth.AbstractPreAuthenticatedProcessingFilter; +import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; + +/** + * Custom X509 filter that will inspect the HTTP headers for a proxied user + * before extracting the user details from the client certificate. + */ +public class X509AuthenticationFilterOld extends AbstractPreAuthenticatedProcessingFilter { + + 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 final X509CertificateExtractor certificateExtractor = new X509CertificateExtractor(); + private final X509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); + private OcspCertificateValidator certificateValidator; + private NiFiProperties properties; + private UserService userService; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + final HttpServletResponse httpResponse = (HttpServletResponse) response; + + // determine if this request is attempting to create a new account + if (isNewAccountRequest((HttpServletRequest) request)) { + // determine if this nifi supports new account requests + if (properties.getSupportNewAccountRequests()) { + // ensure there is a certificate in the request + X509Certificate certificate = certificateExtractor.extractClientCertificate((HttpServletRequest) request); + if (certificate != null) { + // extract the principal from the certificate + Object certificatePrincipal = principalExtractor.extractPrincipal(certificate); + String principal = certificatePrincipal.toString(); + + // log the new user account request + logger.info("Requesting new user account for " + principal); + + try { + // get the justification + String justification = request.getParameter("justification"); + if (justification == null) { + justification = StringUtils.EMPTY; + } + + // create the pending user account + userService.createPendingUserAccount(principal, justification); + + // generate a response + httpResponse.setStatus(HttpServletResponse.SC_CREATED); + httpResponse.setContentType("text/plain"); + + // write the response message + PrintWriter out = response.getWriter(); + out.println("Not authorized. User account created. Authorization pending."); + } catch (IllegalArgumentException iae) { + handleUserServiceError((HttpServletRequest) request, httpResponse, HttpServletResponse.SC_BAD_REQUEST, iae.getMessage()); + } catch (AdministrationException ae) { + handleUserServiceError((HttpServletRequest) request, httpResponse, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ae.getMessage()); + } + } else { + // can this really happen? + handleMissingCertificate((HttpServletRequest) request, httpResponse); + } + } else { + handleUserServiceError((HttpServletRequest) request, httpResponse, HttpServletResponse.SC_NOT_FOUND, "This NiFi does not support new account requests."); + } + } else { + try { + // this not a request to create a user account - try to authorize + super.doFilter(request, response, chain); + } catch (AuthenticationException ae) { + // continue the filter chain since anonymous access should be supported + if (!properties.getNeedClientAuth()) { + chain.doFilter(request, response); + } else { + // create an appropriate response for the given exception + handleUnsuccessfulAuthentication((HttpServletRequest) request, httpResponse, ae); + } + } + } + } + + @Override + protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) { + String principal; + + // extract the cert + X509Certificate certificate = certificateExtractor.extractClientCertificate(request); + + // ensure the cert was found + if (certificate == null) { + return null; + } + + // extract the principal + Object certificatePrincipal = principalExtractor.extractPrincipal(certificate); + principal = ProxiedEntitiesUtils.formatProxyDn(certificatePrincipal.toString()); + + try { + // ensure the cert is valid + certificate.checkValidity(); + } 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; + } + + // validate the certificate in question + try { + certificateValidator.validate(request); + } catch (final Exception e) { + logger.info(e.getMessage()); + if (logger.isDebugEnabled()) { + logger.debug("", e); + } + return null; + } + + // look for a proxied user + if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { + principal = request.getHeader(PROXY_ENTITIES_CHAIN) + principal; + } + + // log the request attempt - response details will be logged later + logger.info(String.format("Attempting request for (%s) %s %s (source ip: %s)", principal, request.getMethod(), + request.getRequestURL().toString(), request.getRemoteAddr())); + + return principal; + } + + @Override + protected Object getPreAuthenticatedCredentials(HttpServletRequest request) { + return certificateExtractor.extractClientCertificate(request); + } + + @Override + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) { + if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { + response.setHeader(PROXY_ENTITIES_ACCEPTED, Boolean.TRUE.toString()); + } + super.successfulAuthentication(request, response, authResult); + } + + @Override + protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) { + if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { + response.setHeader(PROXY_ENTITIES_DETAILS, failed.getMessage()); + } + super.unsuccessfulAuthentication(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 + */ + private 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; + } + + /** + * Handles requests that were unable to be authorized. + * + * @param request request + * @param response response + * @param ae ex + * @throws IOException ex + */ + private void handleUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException ae) throws IOException { + // set the response status + response.setContentType("text/plain"); + + // write the response message + PrintWriter out = response.getWriter(); + + // use the type of authentication exception to determine the response code + if (ae instanceof UsernameNotFoundException) { + if (properties.getSupportNewAccountRequests()) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + out.println("Not authorized."); + } else { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + out.println("Access is denied."); + } + } else if (ae instanceof AccountStatusException) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + out.println(ae.getMessage()); + } else if (ae instanceof UntrustedProxyException) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + out.println(ae.getMessage()); + } else if (ae instanceof AuthenticationServiceException) { + logger.error(String.format("Unable to authorize: %s", ae.getMessage()), ae); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + out.println(String.format("Unable to authorize: %s", ae.getMessage())); + } else { + logger.error(String.format("Unable to authorize: %s", ae.getMessage()), ae); + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + out.println("Access is denied."); + } + + // log the failure + logger.info(String.format("Rejecting access to web api: %s", ae.getMessage())); + + // optionally log the stack trace + if (logger.isDebugEnabled()) { + logger.debug(StringUtils.EMPTY, ae); + } + } + + private void handleUserServiceError(HttpServletRequest request, HttpServletResponse response, int responseCode, String message) throws IOException { + // set the response status + response.setContentType("text/plain"); + response.setStatus(responseCode); + + // write the response message + PrintWriter out = response.getWriter(); + out.println(message); + + // log the failure + logger.info(String.format("Unable to process request because %s", message)); + } + + /** + * Handles requests that failed because they were bad input. + * + * @param request request + * @param response response + * @throws IOException ioe + */ + private void handleMissingCertificate(HttpServletRequest request, HttpServletResponse response) throws IOException { + // set the response status + response.setContentType("text/plain"); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + + // write the response message + PrintWriter out = response.getWriter(); + out.println("Unable to process request because the user certificate was not specified."); + + // log the failure + logger.info("Unable to process request because the user certificate was not specified."); + } + + /* setters */ + public void setProperties(NiFiProperties properties) { + this.properties = properties; + } + + public void setUserService(UserService userService) { + this.userService = userService; + } + + public void setCertificateValidator(OcspCertificateValidator certificateValidator) { + this.certificateValidator = certificateValidator; + } + +} 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/x509/X509AuthenticationProvider.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/x509/X509AuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java new file mode 100644 index 0000000..d24b9cb --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java @@ -0,0 +1,31 @@ +package org.apache.nifi.web.security.x509; + +import org.apache.nifi.web.security.token.NewAccountAuthenticationRequestToken; +import org.apache.nifi.web.security.token.NewAccountAuthenticationToken; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + +/** + * + */ +public class X509AuthenticationProvider implements AuthenticationProvider { + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + if (NewAccountAuthenticationRequestToken.class.isAssignableFrom(authentication.getClass())) { + return new NewAccountAuthenticationToken(((NewAccountAuthenticationRequestToken) authentication).getNewAccountRequest()); + } else if (NiFiAuthenticationRequestToken.class.isAssignableFrom(authentication.getClass())) { + return authentication; + } else { + return null; + } + } + + @Override + public boolean supports(Class<?> authentication) { + return 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/resources/nifi-web-security-context.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml index fa5b9e2..b201364 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml @@ -13,71 +13,75 @@ See the License for the specific language governing permissions and limitations under the License. --> -<!-- marked as lazy so that security beans are not created when applications runs in non-secure mode --> -<beans default-lazy-init="true" - xmlns="http://www.springframework.org/schema/beans" +<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:sec="http://www.springframework.org/schema/security" - xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd - http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd"> + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"> - <!-- http security configuration --> - <sec:http create-session="stateless" auto-config="false" entry-point-ref="entryPoint"> +<!-- <sec:http create-session="stateless" auto-config="false" entry-point-ref="entryPoint"> <sec:anonymous enabled="false"/> <sec:custom-filter ref="nodeAuthorizedUserFilter" before="PRE_AUTH_FILTER"/> <sec:custom-filter ref="x509Filter" position="PRE_AUTH_FILTER"/> <sec:custom-filter ref="anonymousFilter" position="ANONYMOUS_FILTER"/> - </sec:http> + </sec:http>--> <!-- enable method level security --> - <sec:global-method-security pre-post-annotations="enabled"/> + <!--<sec:global-method-security pre-post-annotations="enabled"/>--> + + <!--<bean class="org.apache.nifi.web.security.NiFiSecurityWebApplicationInitializer"></bean>--> + + <!-- security config --> +<!-- <bean id="securityConfig" class="org.apache.nifi.web.security.NiFiSecurityConfig"> + <property name="properties" ref="nifiProperties"/> + <property name="userDetailsService" ref="userAuthorizationService"/> + <property name="authorizedUserFilter" ref="nodeAuthorizedUserFilter"/> + <property name="entryPoint" ref="entryPoint"/> + </bean>--> <!-- entry point reference --> - <bean id="entryPoint" class="org.apache.nifi.web.security.authentication.NiFiAuthenticationEntryPoint"/> + <!--<bean id="entryPoint" class="org.apache.nifi.web.security.authentication.NiFiAuthenticationEntryPoint"/>--> <!-- authentication manager --> - <sec:authentication-manager alias="authenticationManager"> +<!-- <sec:authentication-manager alias="authenticationManager"> <sec:authentication-provider ref="preauthAuthProvider"/> - </sec:authentication-manager> + </sec:authentication-manager>--> <!-- pre-authentication provider --> - <bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider"> +<!-- <bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider"> <property name="preAuthenticatedUserDetailsService"> <bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <property name="userDetailsService" ref="userAuthorizationService"/> </bean> </property> - </bean> + </bean>--> <!-- user details service --> - <bean id="userAuthorizationService" class="org.apache.nifi.web.security.authorization.NiFiAuthorizationService"> + <bean id="userDetailsService" class="org.apache.nifi.web.security.authorization.NiFiAuthorizationService"> <property name="userService" ref="userService"/> <property name="properties" ref="nifiProperties"/> </bean> - + + <!-- jwt service --> + <bean id="jwtService" class="org.apache.nifi.web.security.jwt.JwtService"></bean> + <!-- performs ocsp certificate validation --> - <bean id="ocspCertificateValidator" class="org.apache.nifi.web.security.x509.ocsp.OcspCertificateValidator"> +<!-- <bean id="ocspCertificateValidator" class="org.apache.nifi.web.security.x509.ocsp.OcspCertificateValidator"> <constructor-arg ref="nifiProperties"/> - </bean> + </bean>--> <!-- custom x509 filter for checking for proxied users --> - <bean id="x509Filter" class="org.apache.nifi.web.security.x509.X509AuthenticationFilter"> - <property name="authenticationManager" ref="authenticationManager"/> - <property name="properties" ref="nifiProperties"/> - <property name="userService" ref="userService"/> +<!-- <bean id="x509Filter" class="org.apache.nifi.web.security.x509.X509AuthenticationFilter"> <property name="certificateValidator" ref="ocspCertificateValidator"/> - <property name="continueFilterChainOnUnsuccessfulAuthentication" value="false"/> - </bean> + </bean>--> <!-- custom filter for checking for proxied users that are already authenticated --> - <bean id="nodeAuthorizedUserFilter" class="org.apache.nifi.web.security.authorization.NodeAuthorizedUserFilter"> +<!-- <bean id="nodeAuthorizedUserFilter" class="org.apache.nifi.web.security.authorization.NodeAuthorizedUserFilter"> <property name="properties" ref="nifiProperties"/> - </bean> + </bean>--> <!-- custom anonymous filter to assign default roles based on current operating mode --> - <bean id="anonymousFilter" class="org.apache.nifi.web.security.anonymous.NiFiAnonymousUserFilter"> +<!-- <bean id="anonymousFilter" class="org.apache.nifi.web.security.anonymous.NiFiAnonymousUserFilter"> <property name="userService" ref="userService"/> <property name="properties" ref="nifiProperties"/> - </bean> + </bean>--> </beans>
