Github user justinleet commented on a diff in the pull request:
https://github.com/apache/metron/pull/1281#discussion_r237920415
--- Diff:
metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/KnoxSSOAuthenticationFilter.java
---
@@ -0,0 +1,314 @@
+/**
+ * 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.metron.rest.config;
+
+import com.nimbusds.jose.JWSObject;
+import com.nimbusds.jose.JWSVerifier;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
+import com.nimbusds.jwt.SignedJWT;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.ldap.core.AttributesMapper;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.support.LdapNameBuilder;
+import
org.springframework.security.authentication.AbstractAuthenticationToken;
+import
org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import
org.springframework.security.web.authentication.WebAuthenticationDetails;
+
+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.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPublicKey;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.springframework.ldap.query.LdapQueryBuilder.query;
+
+/**
+ * This class is a Servlet Filter that authenticates a Knox SSO token.
The token is stored in a cookie and is
+ * verified against a public Knox key. The token expiration and begin
time are also validated. Upon successful validation,
+ * a Spring Authentication object is built from the user name and user
groups queried from LDAP. Currently, user groups are
+ * mapped directly to Spring roles and prepended with "ROLE_".
+ */
+public class KnoxSSOAuthenticationFilter implements Filter {
+ private static final Logger LOG =
LoggerFactory.getLogger(KnoxSSOAuthenticationFilter.class);
+
+ private String userSearchBase;
+ private Path knoxKeyFile;
+ private String knoxKeyString;
+ private String knoxCookie;
+ private LdapTemplate ldapTemplate;
+
+ public KnoxSSOAuthenticationFilter(String userSearchBase,
+ Path knoxKeyFile,
+ String knoxKeyString,
+ String knoxCookie,
+ LdapTemplate ldapTemplate) throws
IOException, CertificateException {
+ this.userSearchBase = userSearchBase;
+ this.knoxKeyFile = knoxKeyFile;
+ this.knoxKeyString = knoxKeyString;
+ this.knoxCookie = knoxCookie;
+ if (ldapTemplate == null) {
+ throw new IllegalStateException("KnoxSSO requires LDAP. You must add
'ldap' to the active profiles.");
+ }
+ this.ldapTemplate = ldapTemplate;
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ /**
+ * Extracts the Knox token from the configured cookie. If basic
authentication headers are present, SSO authentication
+ * is skipped.
+ * @param request
+ * @param response
+ * @param chain
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
+ throws IOException, ServletException {
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+
+ // If a basic authentication header is present, use that to
authenticate and skip SSO
+ String authHeader = httpRequest.getHeader("Authorization");
+ if (authHeader == null || !authHeader.startsWith("Basic")) {
+ String serializedJWT = getJWTFromCookie(httpRequest);
+ if (serializedJWT != null) {
+ SignedJWT jwtToken = null;
+ try {
+ jwtToken = SignedJWT.parse(serializedJWT);
+ String userName = jwtToken.getJWTClaimsSet().getSubject();
+ LOG.info("SSO login user : {} ", userName);
+ if (isValid(jwtToken, userName)) {
+ Authentication authentication = getAuthentication(userName,
httpRequest);
+
SecurityContextHolder.getContext().setAuthentication(authentication);
+ }
+ } catch (ParseException e) {
+ LOG.warn("Unable to parse the JWT token", e);
+ }
+ }
+ }
+ chain.doFilter(request, response);
+ }
+
+ /**
+ * Validates a Knox token with expiration and begin times and verifies
the token with a public Knox key.
+ * @param jwtToken Knox token
+ * @param userName User name associated with the token
+ * @return Whether a token is valid or not
+ * @throws ParseException
+ */
+ protected boolean isValid(SignedJWT jwtToken, String userName) throws
ParseException {
+ // Verify the user name is present
+ if (userName == null || userName.isEmpty()) {
+ LOG.info("Could not find user name in SSO token");
+ return false;
+ }
+
+ Date now = new Date();
+
+ // Verify the token has not expired
+ Date expirationTime = jwtToken.getJWTClaimsSet().getExpirationTime();
+ if (expirationTime != null && now.after(expirationTime)) {
+ LOG.info("SSO token expired: {} ", userName);
+ return false;
+ }
+
+ // Verify the token is not before time
+ Date notBeforeTime = jwtToken.getJWTClaimsSet().getNotBeforeTime();
+ if (notBeforeTime != null && now.before(notBeforeTime)) {
+ LOG.info("SSO token not yet valid: {} ", userName);
+ return false;
+ }
+
+ return validateSignature(jwtToken);
+ }
+
+ /**
+ * Verify the signature of the JWT token in this method. This method
depends on
+ * the public key that was established during init based upon the
provisioned
+ * public key. Override this method in subclasses in order to customize
the
+ * signature verification behavior.
+ *
+ * @param jwtToken
+ * the token that contains the signature to be validated
+ * @return valid true if signature verifies successfully; false otherwise
+ */
+ protected boolean validateSignature(SignedJWT jwtToken) {
+ // Verify the token signature algorithm was as expected
+ String receivedSigAlg = jwtToken.getHeader().getAlgorithm().getName();
+ if (!receivedSigAlg.equals("RS256")) {
--- End diff --
Does this need to be hardcoded to this? And can it be pulled into a
field/constant somewhere if it does?
---