org.codice.ddf.security.common.jaxrs.RestSecurity.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.security.common.jaxrs.RestSecurity.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.common.jaxrs;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.cxf.jaxrs.client.Client;
import org.apache.cxf.ws.security.tokenstore.SecurityToken;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.common.saml.SamlAssertionWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ddf.security.Subject;
import ddf.security.assertion.SecurityAssertion;
import ddf.security.common.audit.SecurityLogger;

/**
 * Provides methods that help with securing RESTful (jaxrs) communications.
 */
public final class RestSecurity {

    public static final String SAML_HEADER_PREFIX = "SAML ";

    public static final String BASIC_HEADER_PREFIX = "BASIC ";

    public static final String AUTH_HEADER = "Authorization";

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

    public static final boolean GZIP_COMPATIBLE = true;

    /**
     * Parses the incoming subject for a saml assertion and sets that as a header on the client.
     *
     * @param subject Subject containing a SAML-based security token.
     * @param client  Non-null client to set the cookie on.
     * @throws NullPointerException if client is null
     */
    public static void setSubjectOnClient(Subject subject, Client client) {
        if (client != null && subject != null && "https".equalsIgnoreCase(client.getCurrentURI().getScheme())) {
            String encodedSamlHeader = createSamlHeader(subject);
            if (encodedSamlHeader == null) {
                LOGGER.debug("SAML Header was null. Unable to set the header for the client.");
                return;
            }
            client.header(AUTH_HEADER, encodedSamlHeader);
        }
    }

    public static void setUserOnClient(String username, String password, Client client)
            throws UnsupportedEncodingException {
        if (client != null && username != null && password != null) {
            if (!StringUtils.startsWithIgnoreCase(client.getCurrentURI().getScheme(), "https")) {
                if (Boolean.valueOf(System.getProperty("org.codice.allowBasicAuthOverHttp", "false"))) {
                    LOGGER.warn("CAUTION: Passing username & password on an un-encrypted protocol [{}]."
                            + " This is a security issue. ", client.getCurrentURI());
                    SecurityLogger.auditWarn("Passing username & password on an un-encrypted protocol ["
                            + client.getCurrentURI() + "].");
                } else {
                    LOGGER.warn("Passing username & password is not allowed on an un-encrypted protocol [{}].",
                            client.getCurrentURI());
                    return;
                }
            }
            String basicCredentials = username + ":" + password;
            String encodedHeader = BASIC_HEADER_PREFIX
                    + Base64.getEncoder().encodeToString(basicCredentials.getBytes(StandardCharsets.UTF_8));
            client.header(AUTH_HEADER, encodedHeader);

        }
    }

    /**
     * Creates an authorization header to be returned to the browser if the token was successfully
     * exchanged for a SAML assertion
     *
     * @param subject - {@link ddf.security.Subject} to create the header from
     */
    private static String createSamlHeader(Subject subject) {
        String encodedSamlHeader = null;
        org.w3c.dom.Element samlToken = null;
        try {
            for (Object principal : subject.getPrincipals().asList()) {
                if (principal instanceof SecurityAssertion) {
                    SecurityToken securityToken = ((SecurityAssertion) principal).getSecurityToken();
                    samlToken = securityToken.getToken();
                }
            }
            if (samlToken != null) {
                SamlAssertionWrapper assertion = new SamlAssertionWrapper(samlToken);
                String saml = assertion.assertionToString();
                encodedSamlHeader = SAML_HEADER_PREFIX + deflateAndBase64Encode(saml);
            }
        } catch (WSSecurityException | ArithmeticException | IOException e) {
            LOGGER.error("Unable to parse SAML assertion from subject.", e);
        }
        return encodedSamlHeader;
    }

    /**
     * Deflates a value and Base64 encodes the result.
     *
     * @param value value to deflate and Base64 encode
     * @return String
     * @throws IOException if the value cannot be converted
     */
    public static String deflateAndBase64Encode(String value) throws IOException {
        ByteArrayOutputStream valueBytes = new ByteArrayOutputStream();
        try (OutputStream tokenStream = new DeflaterOutputStream(valueBytes,
                new Deflater(Deflater.DEFLATED, GZIP_COMPATIBLE))) {
            tokenStream.write(value.getBytes(StandardCharsets.UTF_8));
            tokenStream.close();

            return Base64.getEncoder().encodeToString(valueBytes.toByteArray());
        }
    }

    public static String inflateBase64(String base64EncodedValue) throws IOException {
        byte[] deflatedValue = Base64.getMimeDecoder().decode(base64EncodedValue);
        InputStream is = new InflaterInputStream(new ByteArrayInputStream(deflatedValue),
                new Inflater(GZIP_COMPATIBLE));
        return IOUtils.toString(is, StandardCharsets.UTF_8.name());
    }

}