All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazon.redshift.plugin.OktaCredentialsProvider Maven / Gradle / Ivy

There is a newer version: 2.1.0.30
Show newest version
package com.amazon.redshift.plugin;

import com.amazon.redshift.logger.LogLevel;
import com.amazon.redshift.logger.RedshiftLogger;
import com.amazonaws.SdkClientException;
import com.amazonaws.util.IOUtils;
import com.amazonaws.util.StringUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URLEncoder;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

public class OktaCredentialsProvider extends SamlCredentialsProvider
{
    private static final String KEY_APP_URL = "app_id";
    private static final String KEY_APP_NAME = "app_name";

    protected String m_app_id;
    protected String m_app_name;

    @Override
    public void addParameter(String key, String value)
    {
        super.addParameter(key, value);
        if (KEY_APP_URL.equalsIgnoreCase(key))
        {
            m_app_id = value;
        }
        if (KEY_APP_NAME.equalsIgnoreCase(key))
        {
            m_app_name = value;
        }
    }
    
    @Override
    public String getPluginSpecificCacheKey() {
    	return ((m_app_id != null) ? m_app_id : "")
    					+ ((m_app_name != null) ? m_app_name : "")
    					;
    }
    

    @Override
    protected String getSamlAssertion() throws IOException
    {
        checkRequiredParameters();
        if (StringUtils.isNullOrEmpty(m_app_id))
        {
            throw new IOException("Missing required property: " + KEY_APP_URL);
        }

        CloseableHttpClient httpClient = null;

        try
        {
            httpClient = getHttpClient();
            String strOktaSessionToken = oktaAuthentication(httpClient);
            return handleSamlAssertion(httpClient, strOktaSessionToken);
        }
        catch (GeneralSecurityException e)
        {
            throw new SdkClientException("Failed create SSLContext.", e);
        }
        finally
        {
            IOUtils.closeQuietly(httpClient, null);
        }
    }

    /**
     * Authenticates users credentials via Okta, return Okta session token.
     */
    private String oktaAuthentication(CloseableHttpClient httpClient) throws IOException
    {
        CloseableHttpResponse responseAuthenticate = null;
        try
        {
            ObjectMapper mapper = new ObjectMapper();

            //HTTP Post request to Okta API for session token
            String uri = "https://" + m_idpHost + "/api/v1/authn";
            
            if (RedshiftLogger.isEnable())
          		m_log.logDebug("uri: {0}", uri);
            
            validateURL(uri);
            
            HttpPost httpost = new HttpPost(uri);
            httpost.addHeader("Accept", "application/json");
            httpost.addHeader("Content-Type", "application/json");
            httpost.addHeader("Cache-Control", "no-cache");
            //construction of JSON request
            Map creds = new HashMap();
            creds.put("username", m_userName);
            creds.put("password", m_password);
            StringWriter writer = new StringWriter();
            mapper.writeValue(writer, creds);
            StringEntity entity = new StringEntity(writer.toString(), "UTF-8");
            entity.setContentType("application/json");
            httpost.setEntity(entity);
            responseAuthenticate = httpClient.execute(httpost);
            String content = EntityUtils.toString(responseAuthenticate.getEntity());

          	if(RedshiftLogger.isEnable()) {
                String maskedContent = content.replaceAll(getRegexForJsonKey("sessionToken"), "$1***masked***\"");
                maskedContent = maskedContent.replaceAll(getRegexForJsonKey("id"), "$1***masked***\"");
                maskedContent = maskedContent.replaceAll(getRegexForJsonKey("passwordChanged"), "$1***masked***\"");
                m_log.log(LogLevel.DEBUG, "oktaAuthentication https response:" + maskedContent);
            }
            
            StatusLine statusLine = responseAuthenticate.getStatusLine();
            int requestStatus = statusLine.getStatusCode();
            
            if (requestStatus != 200)
            {
                throw new IOException(statusLine.getReasonPhrase());
            }
            //Retrieve and parse the Okta response for session token
            JsonNode json = mapper.readTree(content);

            if ("SUCCESS".equals(json.get("status").asText()))
            {
                return json.get("sessionToken").asText();
            }
            throw new IOException("No session token in the response.");
        }
        finally
        {
            IOUtils.closeQuietly(responseAuthenticate, null);
        }
    }

    /**
     * Retrieves SAML assertion from Okta containing AWS roles.
     */
    private String handleSamlAssertion(CloseableHttpClient httpClient, String oktaSessionToken) throws IOException
    {
        // If no value was specified for m_app_name, use the current default.
        if (StringUtils.isNullOrEmpty(m_app_name))
        {
            m_app_name = "amazon_aws";
        }
        else
        {
            // Ensure that the string is properly encoded.
            m_app_name = URLEncoder.encode(m_app_name, "UTF-8");
        }
        
        String oktaAWSAppUrl = "https://" + m_idpHost + "/home/" + m_app_name + "/" + m_app_id;
        String oktaAWSAppUrlWithToken = oktaAWSAppUrl + "?onetimetoken=" + oktaSessionToken;
        
        if (RedshiftLogger.isEnable())
      		m_log.logDebug("oktaAWSAppUrl: {0}", oktaAWSAppUrl);
        
        validateURL(oktaAWSAppUrlWithToken);
        HttpGet httpget = new HttpGet(oktaAWSAppUrlWithToken);
        CloseableHttpResponse responseSAML = httpClient.execute(httpget);

        int requestStatus = responseSAML.getStatusLine().getStatusCode();
        if (requestStatus != 200)
        {
            throw new RuntimeException("Failed : HTTP error code : " + responseSAML.getStatusLine().getStatusCode()
                + " : Reason : " + responseSAML.getStatusLine().getReasonPhrase());
        }

        String body = EntityUtils.toString(responseSAML.getEntity());
        
        if (RedshiftLogger.isEnable())
      		m_log.logDebug("body: {0}", sanitizeResponseBody(body));
        
        for (String inputTags : getInputTagsfromHTML(body)) {
            String name = getValueByKey(inputTags, "name");
            String value = getValueByKey(inputTags, "value");
            
            if (RedshiftLogger.isEnable())
          		m_log.logDebug("name: {0}", name);
            
            if ("SAMLResponse".equalsIgnoreCase(name))
            {
                return value.replace("+", "+").replace("=", "=");
            }
        }
        throw new IOException("Failed to retrieve SAMLAssertion.");
    }

    private String sanitizeResponseBody(String response) {
        Document document = Jsoup.parse(response);
        // find input tag with name as SAMLResponse
        Element samlElement = document.selectFirst("input[name=SAMLResponse]");

        if(samlElement != null) {
            samlElement.val("***masked***"); // mask the value attribute for this element
            return document.toString();
        }
        return response;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy