<commit_before>/*
 * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. 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.wso2.carbon.identity.application.authenticator.samlsso;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.owasp.encoder.Encode;
import org.wso2.carbon.identity.application.authentication.framework.AbstractApplicationAuthenticator;
import org.wso2.carbon.identity.application.authentication.framework.AuthenticatorStateInfo;
import org.wso2.carbon.identity.application.authentication.framework.FederatedApplicationAuthenticator;
import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext;
import org.wso2.carbon.identity.application.authentication.framework.exception.AuthenticationFailedException;
import org.wso2.carbon.identity.application.authentication.framework.exception.LogoutFailedException;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
import org.wso2.carbon.identity.application.authenticator.samlsso.exception.SAMLSSOException;
import org.wso2.carbon.identity.application.authenticator.samlsso.internal.SAMLSSOAuthenticatorServiceComponent;
import org.wso2.carbon.identity.application.authenticator.samlsso.manager.DefaultSAML2SSOManager;
import org.wso2.carbon.identity.application.authenticator.samlsso.manager.SAML2SSOManager;
import org.wso2.carbon.identity.application.authenticator.samlsso.model.StateInfo;
import org.wso2.carbon.identity.application.authenticator.samlsso.util.SSOConstants;
import org.wso2.carbon.identity.application.authenticator.samlsso.util.SSOUtils;
import org.wso2.carbon.identity.application.common.model.ClaimMapping;
import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

public class SAMLSSOAuthenticator extends AbstractApplicationAuthenticator implements FederatedApplicationAuthenticator {

    private static final long serialVersionUID = -8097512332218044859L;
    public static final String AS_REQUEST = "AS_REQUEST";

    private static Log log = LogFactory.getLog(SAMLSSOAuthenticator.class);

    @Override
    public boolean canHandle(HttpServletRequest request) {

        if (log.isTraceEnabled()) {
            log.trace("Inside canHandle()");
        }

        if (request.getParameter("SAMLResponse") != null) {
            return true;
        }

        return false;
    }

    @Override
    protected void initiateAuthenticationRequest(HttpServletRequest request, HttpServletResponse response,
                                                 AuthenticationContext context)
            throws AuthenticationFailedException {

        Map<String, String> authenticatorProperties = context.getAuthenticatorProperties();

        String idpURL = authenticatorProperties
                .get(IdentityApplicationConstants.Authenticator.SAML2SSO.SSO_URL);
        String ssoUrl = "";
        boolean isPost = false;

        try {
            String requestMethod = authenticatorProperties
                    .get(IdentityApplicationConstants.Authenticator.SAML2SSO.REQUEST_METHOD);

            if (requestMethod != null && requestMethod.trim().length() != 0) {
                if (SSOConstants.POST.equalsIgnoreCase(requestMethod)) {
                    isPost = true;
                } else if (SSOConstants.REDIRECT.equalsIgnoreCase(requestMethod)) {
                    isPost = false;
                } else if (AS_REQUEST.equalsIgnoreCase(requestMethod)) {
                    isPost = context.getAuthenticationRequest().isPost();
                }
            } else {
                isPost = false;
            }

            if (isPost) {
                sendPostRequest(request, response, false, false, idpURL, context);
                return;

            } else {
                SAML2SSOManager saml2SSOManager = getSAML2SSOManagerInstance();
                saml2SSOManager.init(context.getTenantDomain(), context.getAuthenticatorProperties(),
                        context.getExternalIdP().getIdentityProvider());
                ssoUrl = saml2SSOManager.buildRequest(request, false, false, idpURL, context);
                generateAuthenticationRequest(request, response, ssoUrl, authenticatorProperties);

            }
        } catch (SAMLSSOException e) {
            throw new AuthenticationFailedException(e.getMessage(), e);
        }

        return;
    }

    private void generateAuthenticationRequest(HttpServletRequest request, HttpServletResponse response,
                                               String ssoUrl, Map<String, String> authenticatorProperties)
            throws AuthenticationFailedException {
        try {
            String domain = request.getParameter("domain");
<commit_msg>support dynamic query parameters as additional query parameter<commit_after>/*
 * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. 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.wso2.carbon.identity.application.authenticator.samlsso;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.owasp.encoder.Encode;
import org.wso2.carbon.identity.application.authentication.framework.AbstractApplicationAuthenticator;
import org.wso2.carbon.identity.application.authentication.framework.AuthenticatorStateInfo;
import org.wso2.carbon.identity.application.authentication.framework.FederatedApplicationAuthenticator;
import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext;
import org.wso2.carbon.identity.application.authentication.framework.exception.AuthenticationFailedException;
import org.wso2.carbon.identity.application.authentication.framework.exception.LogoutFailedException;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
import org.wso2.carbon.identity.application.authenticator.samlsso.exception.SAMLSSOException;
import org.wso2.carbon.identity.application.authenticator.samlsso.internal.SAMLSSOAuthenticatorServiceComponent;
import org.wso2.carbon.identity.application.authenticator.samlsso.manager.DefaultSAML2SSOManager;
import org.wso2.carbon.identity.application.authenticator.samlsso.manager.SAML2SSOManager;
import org.wso2.carbon.identity.application.authenticator.samlsso.model.StateInfo;
import org.wso2.carbon.identity.application.authenticator.samlsso.util.SSOConstants;
import org.wso2.carbon.identity.application.authenticator.samlsso.util.SSOUtils;
import org.wso2.carbon.identity.application.common.model.ClaimMapping;
import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class SAMLSSOAuthenticator extends AbstractApplicationAuthenticator implements FederatedApplicationAuthenticator {

    private static final long serialVersionUID = -8097512332218044859L;
    public static final String AS_REQUEST = "AS_REQUEST";

    private static Log log = LogFactory.getLog(SAMLSSOAuthenticator.class);

    @Override
    public boolean canHandle(HttpServletRequest request) {

        if (log.isTraceEnabled()) {
            log.trace("Inside canHandle()");
        }

        return request.getParameter("SAMLResponse") != null;

    }

    @Override
    protected void initiateAuthenticationRequest(HttpServletRequest request, HttpServletResponse response,
                                                 AuthenticationContext context)
            throws AuthenticationFailedException {

        Map<String, String> authenticatorProperties = context.getAuthenticatorProperties();

        String idpURL = authenticatorProperties
                .get(IdentityApplicationConstants.Authenticator.SAML2SSO.SSO_URL);
        String ssoUrl = "";
        boolean isPost = false;

        try {
            String requestMethod = authenticatorProperties
                    .get(IdentityApplicationConstants.Authenticator.SAML2SSO.REQUEST_METHOD);

            if (requestMethod != null && requestMethod.trim().length() != 0) {
                if (SSOConstants.POST.equalsIgnoreCase(requestMethod)) {
                    isPost = true;
                } else if (SSOConstants.REDIRECT.equalsIgnoreCase(requestMethod)) {
                    isPost = false;
                } else if (AS_REQUEST.equalsIgnoreCase(requestMethod)) {
                    isPost = context.getAuthenticationRequest().isPost();
                }
            } else {
                isPost = false;
            }

            // Resolves dynamic query parameters from "Additional Query Parameters".
            resolveDynamicParameter(context);

            if (isPost) {
                sendPostRequest(request, response, false, false, idpURL, context);
                return;

            } else {
                SAML2SSOManager saml2SSOManager = getSAML2SSOManagerInstance();
                saml2SSOManager.init(context.getTenantDomain(), context.getAuthenticatorProperties(),
                        context.getExternalIdP().getIdentityProvider());
                ssoUrl = saml2SSOManager.buildRequest(request, false, false, idpURL, context);
                generateAuthenticationRequest(request, response, ssoUrl, authenticatorProperties);

            }
        } catch (SAMLSSOException | UnsupportedEncodingException e) {
            throw new AuthenticationFailedException(e.getMessage(), e);
        }

    }

    /**
     * Resolves dynamic query parameters from "Additional Query Parameters" string.
     *
     * @param context authentication context
     * @throws UnsupportedEncodingException if the character encoding is unsupported
     */
    private void resolveDynamicParameter(AuthenticationContext context) throws UnsupportedEncodingException {

        String queryParameters = context.getAuthenticatorProperties().get(FrameworkConstants.QUERY_PARAMS);
        if (queryParameters != null) {
            context.getAuthenticatorProperties()
                    .put(FrameworkConstants.QUERY_PARAMS, getResolvedQueryParams(context, queryParameters));
        }
    }

    /**
     * Checks for any dynamic query parameters and replaces it with the values in the SAML request.
     *
     * @param context          authentication context
     * @param queryParamString query parameters string
     * @return resolved query parameter string
     * @throws UnsupportedEncodingException if the character encoding is unsupported
     */
    private String getResolvedQueryParams(AuthenticationContext context, String queryParamString)
            throws UnsupportedEncodingException {

        Map<String, String> queryMap = SSOUtils.getQueryMap(queryParamString);
        StringBuilder queryBuilder = new StringBuilder();
        for (Map.Entry<String, String> entry : queryMap.entrySet()) {
            if (entry.getValue().startsWith("{") && entry.getValue().endsWith("}") && entry.getValue().length() > 2) {
                String[] dynamicParam = context.getAuthenticationRequest()
                        .getRequestQueryParam(entry.getValue().substring(1, entry.getValue().length() - 1));
                if (dynamicParam != null && dynamicParam.length > 0) {
                    entry.setValue(dynamicParam[0]);
                }
            }
            if (queryBuilder.length() > 0) {
                queryBuilder.append('&');
            }
            queryBuilder.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name())).append("=")
                    .append((URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8.name())));
        }
        return queryBuilder.toString();
    }

    private void generateAuthenticationRequest(HttpServletRequest request, HttpServletResponse response,
                                               String ssoUrl, Map<String, String> authenticatorProperties)
            throws AuthenticationFailedException {
        try {
            String domain = request.getParameter("domain");

{'fraction_non_alphanumeric': 0.08536929812173422, 'fraction_numerical': 0.008897048439485949, 'mean_word_length': 3.3074817518248176, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '383416', 'n_tokens_mistral': 3688, 'n_tokens_neox': 3518, 'n_words': 735}