/**
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.atlas.web.filters;

import org.apache.atlas.ApplicationProperties;
import org.apache.atlas.AtlasConfiguration;
import org.apache.atlas.security.SecurityProperties;
import org.apache.atlas.utils.AuthenticationUtil;
import org.apache.atlas.web.security.AtlasAuthenticationProvider;
import org.apache.atlas.web.util.Servlets;
import org.apache.commons.collections.iterators.IteratorEnumeration;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationConverter;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.security.authentication.client.KerberosAuthenticator;
import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
import org.apache.hadoop.security.authentication.server.AuthenticationHandler;
import org.apache.hadoop.security.authentication.server.AuthenticationToken;
import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler;
import org.apache.hadoop.security.authentication.util.Signer;
import org.apache.hadoop.security.authentication.util.SignerException;
import org.apache.hadoop.security.authentication.util.SignerSecretProvider;
import org.apache.hadoop.security.authorize.AuthorizationException;
import org.apache.hadoop.security.authorize.ProxyUsers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.Response;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.Principal;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.apache.atlas.web.filters.RestUtil.constructForwardableURL;

/**
 * This enforces authentication as part of the filter before processing the request.
 * todo: Subclass of {@link AuthenticationFilter}.
 */

@Component
public class AtlasAuthenticationFilter extends AuthenticationFilter {
    private static final Logger LOG = LoggerFactory.getLogger(AtlasAuthenticationFilter.class);

    protected static final ServletContext nullContext = new NullServletContext();

    private static final int      SESSION_TIMEOUT_DISABLED_VALUE = -1;
    private static final String   CONFIG_KERBEROS_TOKEN_VALIDITY = "atlas.authentication.method.kerberos.token.validity";
    private static final String   CONFIG_PROXY_USERS             = "atlas.proxyusers";
    private static final String   PREFIX                         = "atlas.authentication.method";
    private static final String[] DEFAULT_PROXY_USERS            = new String[] {"knox"};
    private static final String   CONF_PROXYUSER_PREFIX          = "atlas.proxyuser";
    private static final String   ORIGINAL_URL_QUERY_PARAM       = "originalUrl";

    private final boolean isKerberos = AuthenticationUtil.isKerberosAuthenticationEnabled();

    private Signer               signer;
    private SignerSecretProvider secretProvider;
    private boolean              isInitializedByTomcat;
    private Set<Pattern>         browserUserAgents;
    private boolean              supportKeyTabBrowserLogin;
    private Configuration        configuration;
    private Properties           headerProperties;
    private Set<String>          atlasProxyUsers = new HashSet<>();
    private HttpServlet          optionsServlet;
    private boolean              supportTrustedProxy;
    private int                  sessionTimeout;

    private SecurityContextLogoutHandler logoutHandler;

    public AtlasAuthenticationFilter() {
        LOG.info("==> AtlasAuthenticationFilter()");

        try {
            init(null);
        } catch (ServletException e) {
            LOG.error("Error while initializing AtlasAuthenticationFilter", e);
        }

        LOG.info("<== AtlasAuthenticationFilter()");
    }

    /**
     * Initialize the filter.
     *
     * @param filterConfig filter configuration.
     * @throws ServletException thrown if the filter could not be initialized.
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        LOG.info("==> AtlasAuthenticationFilter.init");

        final FilterConfig        globalConf = filterConfig;
        final Map<String, String> params     = new HashMap<>();

        try {
            configuration = ApplicationProperties.get();
        } catch (Exception e) {
            throw new ServletException(e);
        }

        String tokenValidityStr = null;

        if (configuration != null) {
            tokenValidityStr = configuration.getString(CONFIG_KERBEROS_TOKEN_VALIDITY);
        }

        if (StringUtils.isNotBlank(tokenValidityStr)) {
            try {
                long tokenValidity = Long.parseLong(tokenValidityStr);

                if (tokenValidity > 0) {
                    params.put(AuthenticationFilter.AUTH_TOKEN_VALIDITY, Long.toString(tokenValidity));
                } else {
                    throw new ServletException(tokenValidity + ": invalid value for property '" + CONFIG_KERBEROS_TOKEN_VALIDITY + "'. Must be a positive integer");
                }
            } catch (NumberFormatException e) {
                throw new ServletException(tokenValidityStr + ": invalid value for property '" + CONFIG_KERBEROS_TOKEN_VALIDITY + "'. Must be a positive integer", e);
            }
        }

        FilterConfig filterConfig1 = new FilterConfig() {
            @Override
            public String getFilterName() {
                return "AtlasAuthenticationFilter";
            }

            @Override
            public ServletContext getServletContext() {
                if (globalConf != null) {
                    return globalConf.getServletContext();
                } else {
                    return nullContext;
                }
            }

            @Override
            public String getInitParameter(String param) {
                return params.get(param);
            }

            @SuppressWarnings("unchecked")
            @Override
            public Enumeration<String> getInitParameterNames() {
                return new IteratorEnumeration(params.keySet().iterator());
            }
        };

        super.init(filterConfig1);

        ProxyUsers.refreshSuperUserGroupsConfiguration(getProxyuserConfiguration(), CONF_PROXYUSER_PREFIX);

        optionsServlet = new HttpServlet() {};

        optionsServlet.init();

        if (sessionTimeout != -1) {
            logoutHandler = new SecurityContextLogoutHandler();
        }

        LOG.info("<== AtlasAuthenticationFilter.init(filterConfig={})", filterConfig);
    }

    @Override
    public void initializeSecretProvider(FilterConfig filterConfig) throws ServletException {
        LOG.info("==> AtlasAuthenticationFilter.initializeSecretProvider");

        secretProvider = (SignerSecretProvider) filterConfig.getServletContext().getAttribute(AuthenticationFilter.SIGNER_SECRET_PROVIDER_ATTRIBUTE);

        if (secretProvider == null) {
            // As tomcat cannot specify the provider object in the configuration.
            // It'll go into this path
            String configPrefix = filterConfig.getInitParameter(CONFIG_PREFIX);

            configPrefix = (configPrefix != null) ? configPrefix + "." : "";

            try {
                secretProvider = AuthenticationFilter.constructSecretProvider(filterConfig.getServletContext(), super.getConfiguration(configPrefix, filterConfig), false);

                this.isInitializedByTomcat = true;
            } catch (Exception ex) {
                throw new ServletException(ex);
            }
        }

        signer = new Signer(secretProvider);

        LOG.info("<== AtlasAuthenticationFilter.initializeSecretProvider(filterConfig={})", filterConfig);
    }

    @Override
    public void destroy() {
        if ((this.secretProvider != null) && (this.isInitializedByTomcat)) {
            this.secretProvider.destroy();

            this.secretProvider = null;
        }

        optionsServlet.destroy();

        super.destroy();
    }

    @Override
    protected Properties getConfiguration(String configPrefix, FilterConfig filterConfig) throws ServletException {
        LOG.info("==> AtlasAuthenticationFilter.getConfiguration()");

        try {
            configuration = ApplicationProperties.get();
        } catch (Exception e) {
            throw new ServletException(e);
        }

        Properties ret = new Properties();

        String kerberosAuthEnabled = configuration != null ? configuration.getString("atlas.authentication.method.kerberos") : null;

        final String authMethod;

        if (kerberosAuthEnabled == null || kerberosAuthEnabled.equalsIgnoreCase("false")) {
            LOG.info("No authentication method configured.  Defaulting to simple authentication");

            authMethod = "simple";
        } else if (kerberosAuthEnabled.equalsIgnoreCase("true")) {
            authMethod = "kerberos";

            if (configuration.getString("atlas.authentication.method.kerberos.name.rules") != null) {
                ret.put("kerberos.name.rules", configuration.getString("atlas.authentication.method.kerberos.name.rules"));
            }

            if (configuration.getString("atlas.authentication.method.kerberos.keytab") != null) {
                ret.put("kerberos.keytab", configuration.getString("atlas.authentication.method.kerberos.keytab"));
            }

            if (configuration.getString("atlas.authentication.method.kerberos.principal") != null) {
                ret.put("kerberos.principal", configuration.getString("atlas.authentication.method.kerberos.principal"));
            }
        } else {
            authMethod = "";
        }

        ret.put(AuthenticationFilter.AUTH_TYPE, authMethod);
        ret.put(AuthenticationFilter.COOKIE_PATH, "/");

        // add any config passed in as init parameters
        Enumeration<String> enumeration = filterConfig.getInitParameterNames();

        while (enumeration.hasMoreElements()) {
            String name = enumeration.nextElement();

            ret.put(name, filterConfig.getInitParameter(name));
        }

        //Resolve _HOST into bind address
        String bindAddress = configuration.getString(SecurityProperties.BIND_ADDRESS);

        if (bindAddress == null) {
            LOG.info("No host name configured. Defaulting to local host name.");

            try {
                bindAddress = InetAddress.getLocalHost().getHostName();
            } catch (UnknownHostException e) {
                throw new ServletException("Unable to obtain host name", e);
            }
        }

        String principal = ret.getProperty(KerberosAuthenticationHandler.PRINCIPAL);

        if (principal != null) {
            try {
                principal = SecurityUtil.getServerPrincipal(principal, bindAddress);
            } catch (IOException ex) {
                throw new RuntimeException("Could not resolve Kerberos principal name: " + ex, ex);
            }

            ret.put(KerberosAuthenticationHandler.PRINCIPAL, principal);
        }

        LOG.debug(" AuthenticationFilterConfig: {}", ret);

        sessionTimeout = AtlasConfiguration.SESSION_TIMEOUT_SECS.getInt();

        LOG.info("AtlasAuthenticationFilter: {} = {}: {}", AtlasConfiguration.SESSION_TIMEOUT_SECS.getPropertyName(), sessionTimeout, (sessionTimeout == SESSION_TIMEOUT_DISABLED_VALUE) ? "Disabled" : "Enabled");

        supportKeyTabBrowserLogin = configuration.getBoolean("atlas.authentication.method.kerberos.support.keytab.browser.login", false);
        supportTrustedProxy       = configuration.getBoolean("atlas.authentication.method.trustedproxy", true);

        String agents = configuration.getString(AtlasCSRFPreventionFilter.BROWSER_USER_AGENT_PARAM, AtlasCSRFPreventionFilter.BROWSER_USER_AGENTS_DEFAULT);

        if (agents == null) {
            agents = AtlasCSRFPreventionFilter.BROWSER_USER_AGENTS_DEFAULT;
        }

        String[] proxyUsers = configuration.getStringArray(CONFIG_PROXY_USERS);

        if (proxyUsers == null || proxyUsers.length == 0) {
            proxyUsers = DEFAULT_PROXY_USERS;
        }

        atlasProxyUsers = new HashSet<>(Arrays.asList(proxyUsers));

        parseBrowserUserAgents(agents);

        LOG.info("<== AtlasAuthenticationFilter.getConfiguration(configPrefix={}, filterConfig={}): {}", configPrefix, filterConfig, ret);

        return ret;
    }

    @Override
    protected AuthenticationToken getToken(HttpServletRequest request) throws AuthenticationException {
        AuthenticationToken token    = null;
        String              tokenStr = null;
        Cookie[]            cookies  = request.getCookies();

        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(AuthenticatedURL.AUTH_COOKIE)) {
                    tokenStr = cookie.getValue();

                    try {
                        tokenStr = this.signer.verifyAndExtract(tokenStr);
                    } catch (SignerException ex) {
                        throw new AuthenticationException(ex);
                    }
                }
            }
        }

        if (tokenStr != null) {
            token = AuthenticationToken.parse(tokenStr);

            if (token != null) {
                AuthenticationHandler authHandler = getAuthenticationHandler();

                if (!token.getType().equals(authHandler.getType())) {
                    throw new AuthenticationException("Invalid AuthenticationToken type");
                }

                if (token.isExpired()) {
                    throw new AuthenticationException("AuthenticationToken expired");
                }
            }
        }

        return token;
    }

    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain) throws IOException, ServletException {
        final HttpServletRequest httpRequest = (HttpServletRequest) request;

        try {
            Authentication              existingAuth    = SecurityContextHolder.getContext().getAuthentication();
            HttpServletResponse         httpResponse    = (HttpServletResponse) response;
            AtlasResponseRequestWrapper responseWrapper = new AtlasResponseRequestWrapper(httpResponse);
            String                      action          = httpRequest.getParameter("action");
            String                      doAsUser        = request.getParameter("doAs");

            HeadersUtil.setSecurityHeaders(responseWrapper);

            if (logoutHandler != null && supportTrustedProxy && StringUtils.isNotEmpty(doAsUser) && StringUtils.equals(action, RestUtil.TIMEOUT_ACTION)) {
                if (existingAuth != null) {
                    logoutHandler.logout(httpRequest, httpResponse, existingAuth);
                }

                redirectTimeoutReqeust(httpRequest, httpResponse);

                return;
            }

            if (existingAuth == null) {
                String authHeader = httpRequest.getHeader("Authorization");

                if (authHeader != null && authHeader.startsWith("Basic")) {
                    filterChain.doFilter(request, response);
                } else if (isKerberos) {
                    doKerberosAuth(request, response, filterChain);
                } else {
                    filterChain.doFilter(request, response);
                }
            } else {
                filterChain.doFilter(request, response);
            }
        } catch (NullPointerException e) {
            LOG.error("Exception in AtlasAuthenticationFilter ", e);

            //PseudoAuthenticationHandler.getUserName() from hadoop-auth throws NPE if user name is not specified
            ((HttpServletResponse) response).sendError(Response.Status.BAD_REQUEST.getStatusCode(), "Authentication is enabled and user is not specified. Specify user.name parameter");
        }
    }

    void parseBrowserUserAgents(String userAgents) {
        String[] agentsArray = userAgents.split(",");

        browserUserAgents = new HashSet<>();

        for (String patternString : agentsArray) {
            browserUserAgents.add(Pattern.compile(patternString));
        }
    }

    boolean isBrowser(String userAgent) {
        if (userAgent != null) {
            for (Pattern pattern : browserUserAgents) {
                Matcher matcher = pattern.matcher(userAgent);

                if (matcher.matches()) {
                    return true;
                }
            }
        }

        return false;
    }

    private void redirectTimeoutReqeust(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException {
        String logoutUrl = httpRequest.getRequestURL().toString();

        logoutUrl = StringUtils.replace(logoutUrl, httpRequest.getRequestURI(), RestUtil.LOGOUT_URL);

        LOG.debug("logoutUrl value is {}", logoutUrl);

        String xForwardedURL = constructForwardableURL(httpRequest);

        LOG.debug("xForwardedURL = {}", xForwardedURL);

        String redirectUrl = RestUtil.constructRedirectURL(httpRequest, logoutUrl, xForwardedURL, ORIGINAL_URL_QUERY_PARAM);

        if (LOG.isDebugEnabled()) {
            LOG.debug("Redirect URL = {}", redirectUrl);
            LOG.debug("session id = {}", httpRequest.getRequestedSessionId());
        }

        httpResponse.sendRedirect(redirectUrl);
    }

    /**
     * This method is copied from hadoop auth lib, code added for error handling and fallback to other auth methods
     *
     * If the request has a valid authentication token it allows the request to continue to the target resource,
     * otherwise it triggers an authentication sequence using the configured {@link AuthenticationHandler}.
     *
     * @param request     the request object.
     * @param response    the response object.
     * @param filterChain the filter chain object.
     *
     * @throws IOException      thrown if an IO error occurred.
     * @throws ServletException thrown if a processing error occurred.
     */
    private void doKerberosAuth(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        KerberosFilterChainWrapper filterChainWrapper   = new KerberosFilterChainWrapper(request, response, filterChain);
        boolean                    unauthorizedResponse = true;
        int                        errCode              = HttpServletResponse.SC_UNAUTHORIZED;
        AuthenticationException    authenticationEx     = null;
        HttpServletRequest         httpRequest          = (HttpServletRequest) request;
        HttpServletResponse        httpResponse         = (HttpServletResponse) response;
        boolean                    isHttps              = "https".equals(httpRequest.getScheme());
        AuthenticationHandler      authHandler          = getAuthenticationHandler();
        String                     doAsUser             = supportTrustedProxy ? Servlets.getDoAsUser(httpRequest) : null;

        try {
            boolean             newToken = false;
            AuthenticationToken token;

            try {
                token = getToken(httpRequest);
            } catch (AuthenticationException ex) {
                LOG.warn("AuthenticationToken ignored: {}", String.valueOf(ex));

                // will be sent back in a 401 unless filter authenticates
                authenticationEx = ex;
                token            = null;
            }

            if (authHandler.managementOperation(token, httpRequest, httpResponse)) {
                if (token == null) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Request [{}] triggering authentication", getRequestURL(httpRequest));
                    }

                    token = authHandler.authenticate(httpRequest, httpResponse);

                    if (token != null && token.getExpires() != 0 && token != AuthenticationToken.ANONYMOUS) {
                        token.setExpires(System.currentTimeMillis() + getValidity() * 1000);
                    }

                    newToken = true;
                }

                if (token != null) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Request [{}] user [{}] authenticated", getRequestURL(httpRequest), token.getUserName());
                    }

                    unauthorizedResponse = false;

                    final AuthenticationToken authToken = token;

                    httpRequest = new HttpServletRequestWrapper(httpRequest) {
                        @Override
                        public String getAuthType() {
                            return authToken.getType();
                        }

                        @Override
                        public String getRemoteUser() {
                            return authToken.getUserName();
                        }

                        @Override
                        public Principal getUserPrincipal() {
                            return (authToken != AuthenticationToken.ANONYMOUS) ? authToken : null;
                        }
                    };

                    // Create the proxy user if doAsUser exists

                    if (supportTrustedProxy && doAsUser != null && !doAsUser.equals(httpRequest.getRemoteUser())) {
                        LOG.debug("doAsUser is {}", doAsUser);

                        UserGroupInformation requestUgi = (token != null) ? UserGroupInformation.createRemoteUser(token.getUserName()) : null;

                        if (requestUgi != null) {
                            requestUgi = UserGroupInformation.createProxyUser(doAsUser, requestUgi);

                            try {
                                ProxyUsers.authorize(requestUgi, request.getRemoteAddr());

                                request.setAttribute("proxyUser", doAsUser);
                            } catch (AuthorizationException ex) {
                                LOG.warn("Proxy user AuthorizationException", ex);

                                httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
                                filterChain.doFilter(request, response);

                                return;
                            }
                        }
                    } else if (StringUtils.isNotBlank(httpRequest.getRemoteUser()) && atlasProxyUsers.contains(httpRequest.getRemoteUser())) {
                        LOG.info("Ignoring kerberos login from proxy user {}", httpRequest.getRemoteUser());

                        httpResponse.setHeader(KerberosAuthenticator.WWW_AUTHENTICATE, "");
                        httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                        filterChain.doFilter(request, response);

                        return;
                    }

                    if (newToken && !token.isExpired() && token != AuthenticationToken.ANONYMOUS) {
                        String signedToken = signer.sign(token.toString());

                        createAtlasAuthCookie(httpResponse, signedToken, getCookieDomain(), getCookiePath(), token.getExpires(), isHttps);
                    }

                    filterChainWrapper.doFilter(httpRequest, httpResponse);
                }
            } else {
                unauthorizedResponse = false;
            }
        } catch (AuthenticationException ex) {
            LOG.warn("Authentication exception: {}", ex.getMessage(), ex);

            // exception from the filter itself is fatal
            errCode          = HttpServletResponse.SC_FORBIDDEN;
            authenticationEx = ex;
        }

        if (unauthorizedResponse) {
            if (!httpResponse.isCommitted()) {
                createAtlasAuthCookie(httpResponse, "", getCookieDomain(), getCookiePath(), 0, isHttps);

                // If response code is 401. Then WWW-Authenticate Header should be
                // present.. reset to 403 if not found..
                if (errCode == HttpServletResponse.SC_UNAUTHORIZED && !httpResponse.containsHeader(KerberosAuthenticator.WWW_AUTHENTICATE)) {
                    errCode = HttpServletResponse.SC_FORBIDDEN;
                }

                boolean isKerberosOnBrowser = supportKeyTabBrowserLogin || doAsUser != null;

                if (authenticationEx == null) { // added this code for atlas error handling and fallback
                    if (!isKerberosOnBrowser && isBrowser(httpRequest.getHeader("User-Agent"))) {
                        filterChain.doFilter(request, response);
                    } else {
                        boolean            chk         = true;
                        Collection<String> headerNames = httpResponse.getHeaderNames();

                        for (String headerName : headerNames) {
                            String value = httpResponse.getHeader(headerName);

                            if (headerName.equalsIgnoreCase("Set-Cookie") && value.startsWith("ATLASSESSIONID")) {
                                chk = false;
                                break;
                            }
                        }

                        String authHeader = httpRequest.getHeader("Authorization");

                        if (authHeader == null && chk) {
                            filterChain.doFilter(request, response);
                        } else if (authHeader != null && authHeader.startsWith("Basic")) {
                            filterChain.doFilter(request, response);
                        }
                    }
                } else {
                    httpResponse.sendError(errCode, authenticationEx.getMessage());
                }
            }
        }
    }

    private static String readUserFromCookie(HttpServletResponse response1) {
        String  userName    = null;
        boolean isCookieSet = response1.containsHeader("Set-Cookie");

        if (isCookieSet) {
            Collection<String> authUserName = response1.getHeaders("Set-Cookie");

            if (authUserName != null) {
                for (String cookie : authUserName) {
                    if (!StringUtils.isEmpty(cookie)) {
                        if (cookie.toLowerCase().startsWith(AuthenticatedURL.AUTH_COOKIE.toLowerCase()) && cookie.contains("u=")) {
                            String[] split = cookie.split(";");

                            for (String s : split) {
                                if (!StringUtils.isEmpty(s) && s.toLowerCase().startsWith(AuthenticatedURL.AUTH_COOKIE.toLowerCase())) {
                                    int ustr = s.indexOf("u=");

                                    if (ustr != -1) {
                                        int andStr = s.indexOf("&", ustr);

                                        if (andStr != -1) {
                                            try {
                                                userName = s.substring(ustr + 2, andStr);
                                                break;
                                            } catch (Exception e) {
                                                userName = null;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        return userName;
    }

    private void createAtlasAuthCookie(HttpServletResponse resp, String token, String domain, String path, long expires, boolean isSecure) {
        StringBuilder sb = (new StringBuilder(AuthenticatedURL.AUTH_COOKIE)).append("=");

        if (token != null && !token.isEmpty()) {
            sb.append("\"").append(token).append("\"");
        }

        sb.append("; Version=1");

        if (path != null) {
            sb.append("; Path=").append(path);
        }

        if (domain != null) {
            sb.append("; Domain=").append(domain);
        }

        if (expires >= 0L) {
            SimpleDateFormat df = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss zzz");

            df.setTimeZone(TimeZone.getTimeZone("GMT"));
            sb.append("; Expires=").append(df.format(new Date(expires)));
        }

        if (isSecure) {
            sb.append("; Secure");
        }

        sb.append("; HttpOnly");
        resp.addHeader("Set-Cookie", sb.toString());
    }

    private org.apache.hadoop.conf.Configuration getProxyuserConfiguration() {
        org.apache.hadoop.conf.Configuration ret = new org.apache.hadoop.conf.Configuration(false);

        if (configuration != null) {
            Properties props = ConfigurationConverter.getProperties(configuration.subset(CONF_PROXYUSER_PREFIX));

            for (String key : props.stringPropertyNames()) {
                ret.set(CONF_PROXYUSER_PREFIX + "." + key, props.getProperty(key));
            }
        }

        return ret;
    }

    private class KerberosFilterChainWrapper implements FilterChain {
        private final ServletRequest  request;
        private final ServletResponse response;
        private final FilterChain     filterChain;

        KerberosFilterChainWrapper(ServletRequest request, ServletResponse response, FilterChain filterChain) {
            this.request     = request;
            this.response    = response;
            this.filterChain = filterChain;
        }

        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException, ServletException {
            final HttpServletRequest  httpRequest  = (HttpServletRequest) servletRequest;
            final HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
            final Authentication      existingAuth = SecurityContextHolder.getContext().getAuthentication();
            String                    loggedInUser = readUserFromCookie(httpResponse);
            String                    userName     = loggedInUser;

            if (!StringUtils.isEmpty((String) httpRequest.getAttribute("proxyUser"))) {
                userName = (String) httpRequest.getAttribute("proxyUser");
            } else if (StringUtils.isEmpty(userName) && !StringUtils.isEmpty(httpRequest.getRemoteUser())) {
                userName = httpRequest.getRemoteUser();
            }

            if ((existingAuth == null || !existingAuth.isAuthenticated()) && !StringUtils.isEmpty(userName)) {
                final List<GrantedAuthority>   grantedAuths        = AtlasAuthenticationProvider.getAuthoritiesFromUGI(userName);
                final UserDetails                 principal           = new User(userName, "", grantedAuths);
                final AbstractAuthenticationToken finalAuthentication = new UsernamePasswordAuthenticationToken(principal, "", grantedAuths);
                final WebAuthenticationDetails    webDetails          = new WebAuthenticationDetails(httpRequest);

                finalAuthentication.setDetails(webDetails);

                SecurityContextHolder.getContext().setAuthentication(finalAuthentication);

                if (sessionTimeout != SESSION_TIMEOUT_DISABLED_VALUE) {
                    httpRequest.getSession().setMaxInactiveInterval(sessionTimeout);
                }

                request.setAttribute("atlas.http.authentication.type", true);

                if (!StringUtils.equals(loggedInUser, userName)) {
                    LOG.info("Logged into Atlas as = {}, by proxyUser = {}", userName, loggedInUser);
                } else {
                    LOG.info("Logged into Atlas as = {}", userName);
                }
            }

            // OPTIONS method is sent from quick start jersey atlas client
            if (httpRequest.getMethod().equals("OPTIONS")) {
                optionsServlet.service(request, response);
            } else {
                String requestUser = httpRequest.getRemoteUser();

                LOG.info("Request from authenticated user: {}, URL={}", requestUser, Servlets.getRequestURI(httpRequest));

                filterChain.doFilter(servletRequest, servletResponse);
            }
        }
    }
}
