org.codice.ddf.security.handler.saml.SAMLAssertionHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.security.handler.saml.SAMLAssertionHandler.java

Source

/**
 * Copyright (c) Codice Foundation
 *
 * <p>This is free software: you can redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software Foundation, either version 3 of
 * the License, or any later version.
 *
 * <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details. A copy of the GNU Lesser General Public
 * License is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package org.codice.ddf.security.handler.saml;

import com.google.common.hash.Hashing;
import ddf.security.SecurityConstants;
import ddf.security.assertion.impl.SecurityAssertionImpl;
import ddf.security.common.SecurityTokenHolder;
import ddf.security.common.audit.SecurityLogger;
import ddf.security.http.SessionFactory;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.stream.XMLStreamException;
import org.apache.cxf.staxutils.StaxUtils;
import org.apache.cxf.ws.security.tokenstore.SecurityToken;
import org.codice.ddf.platform.filter.FilterChain;
import org.codice.ddf.security.common.HttpUtils;
import org.codice.ddf.security.common.jaxrs.RestSecurity;
import org.codice.ddf.security.handler.api.AuthenticationHandler;
import org.codice.ddf.security.handler.api.HandlerResult;
import org.codice.ddf.security.handler.api.SAMLAuthenticationToken;
import org.codice.ddf.security.util.SAMLUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;

/**
 * Checks for a SAML assertion that has been returned to us in the ddf security cookie. If it
 * exists, it is retrieved and converted into a SecurityToken.
 */
public class SAMLAssertionHandler implements AuthenticationHandler {
    /** SAML type to use when configuring context policy. */
    private static final String AUTH_TYPE = "SAML";

    private static final Logger LOGGER = LoggerFactory.getLogger(SAMLAssertionHandler.class);

    private SessionFactory sessionFactory;

    public SAMLAssertionHandler() {
        LOGGER.debug("Creating SAML Assertion handler.");
    }

    @Override
    public String getAuthenticationType() {
        return AUTH_TYPE;
    }

    @Override
    public HandlerResult getNormalizedToken(ServletRequest request, ServletResponse response, FilterChain chain,
            boolean resolve) {
        HandlerResult handlerResult = new HandlerResult();

        SecurityToken securityToken;
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String authHeader = ((HttpServletRequest) request).getHeader(SecurityConstants.SAML_HEADER_NAME);

        // check for full SAML assertions coming in (federated requests, etc.)
        if (authHeader != null) {
            String[] tokenizedAuthHeader = authHeader.split(" ");
            if (tokenizedAuthHeader.length == 2 && tokenizedAuthHeader[0].equals("SAML")) {
                String encodedSamlAssertion = tokenizedAuthHeader[1];
                LOGGER.trace("Header retrieved");
                try {
                    String tokenString = RestSecurity.inflateBase64(encodedSamlAssertion);
                    LOGGER.trace("Header value: {}", tokenString);
                    securityToken = SAMLUtils.getInstance().getSecurityTokenFromSAMLAssertion(tokenString);
                    SAMLAuthenticationToken samlToken = new SAMLAuthenticationToken(null, securityToken);
                    handlerResult.setToken(samlToken);
                    handlerResult.setStatus(HandlerResult.Status.COMPLETED);
                } catch (IOException e) {
                    LOGGER.info("Unexpected error converting header value to string", e);
                }
                return handlerResult;
            }
        }

        // Check for legacy SAML cookie
        Map<String, Cookie> cookies = HttpUtils.getCookieMap(httpRequest);
        Cookie samlCookie = cookies.get(SecurityConstants.SAML_COOKIE_NAME);
        if (samlCookie != null) {
            String cookieValue = samlCookie.getValue();
            LOGGER.trace("Cookie retrieved");
            try {
                String tokenString = RestSecurity.inflateBase64(cookieValue);
                LOGGER.trace("Cookie value: {}", tokenString);
                securityToken = new SecurityToken();
                Element thisToken = StaxUtils.read(new StringReader(tokenString)).getDocumentElement();
                securityToken.setToken(thisToken);
                SAMLAuthenticationToken samlToken = new SAMLAuthenticationToken(null, securityToken);
                handlerResult.setToken(samlToken);
                handlerResult.setStatus(HandlerResult.Status.COMPLETED);
            } catch (IOException e) {
                LOGGER.info("Unexpected error converting cookie value to string - proceeding without SAML token.",
                        e);
            } catch (XMLStreamException e) {
                LOGGER.info("Unexpected error converting XML string to element - proceeding without SAML token.",
                        e);
            }
            return handlerResult;
        }

        HttpSession session = httpRequest.getSession(false);
        if (httpRequest.getRequestedSessionId() != null && !httpRequest.isRequestedSessionIdValid()) {
            SecurityLogger.audit(
                    "Incoming HTTP Request contained possible unknown session ID [{}] for this server.",
                    Hashing.sha256().hashString(httpRequest.getRequestedSessionId(), StandardCharsets.UTF_8)
                            .toString());
        }
        if (session == null && httpRequest.getRequestedSessionId() != null) {
            session = sessionFactory.getOrCreateSession(httpRequest);
        }
        if (session != null) {
            // Check if there is a SAML Assertion in the session
            // If so, create a SAMLAuthenticationToken using the sessionId
            SecurityTokenHolder savedToken = (SecurityTokenHolder) session
                    .getAttribute(SecurityConstants.SAML_ASSERTION);
            if (savedToken != null && savedToken.getSecurityToken() != null) {
                SecurityAssertionImpl assertion = new SecurityAssertionImpl(savedToken.getSecurityToken());
                if (assertion.isPresentlyValid()) {
                    LOGGER.trace("Creating SAML authentication token with session.");
                    SAMLAuthenticationToken samlToken = new SAMLAuthenticationToken(null, session.getId());
                    handlerResult.setToken(samlToken);
                    handlerResult.setStatus(HandlerResult.Status.COMPLETED);
                    return handlerResult;
                } else {
                    LOGGER.trace(
                            "SAML token in session has expired - removing from session and returning with no results");
                    savedToken.remove();
                }
            } else {
                LOGGER.trace("No SAML token located in session - returning with no results");
            }
        } else {
            LOGGER.trace("No HTTP Session - returning with no results");
        }

        return handlerResult;
    }

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    /**
     * If an error occurred during the processing of the request, this method will get called. Since
     * SAML handling is typically processed first, then we can assume that there was an error with the
     * presented SAML assertion - either it was invalid, or the reference didn't match a cached
     * assertion, etc. In order not to get stuck in a processing loop, we will return a 401 status
     * code.
     *
     * @param servletRequest http servlet request
     * @param servletResponse http servlet response
     * @param chain rest of the request chain to be invoked after security handling
     * @return result containing the potential credentials and status
     */
    @Override
    public HandlerResult handleError(ServletRequest servletRequest, ServletResponse servletResponse,
            FilterChain chain) {
        HandlerResult result = new HandlerResult();

        HttpServletRequest httpRequest = servletRequest instanceof HttpServletRequest
                ? (HttpServletRequest) servletRequest
                : null;
        HttpServletResponse httpResponse = servletResponse instanceof HttpServletResponse
                ? (HttpServletResponse) servletResponse
                : null;
        if (httpRequest == null || httpResponse == null) {
            return result;
        }

        LOGGER.debug("In error handler for saml - setting status code to 401 and returning status REDIRECTED.");

        // we tried to process an invalid or missing SAML assertion
        try {
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            httpResponse.flushBuffer();
        } catch (IOException e) {
            LOGGER.debug("Failed to send auth response", e);
        }
        result.setStatus(HandlerResult.Status.REDIRECTED);
        return result;
    }
}