Package com.force.sdk.oauth.connector

Source Code of com.force.sdk.oauth.connector.ForceOAuthConnector

/**
* Copyright (c) 2011, salesforce.com, inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
*    Redistributions of source code must retain the above copyright notice, this list of conditions and the
*    following disclaimer.
*
*    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
*    the following disclaimer in the documentation and/or other materials provided with the distribution.
*
*    Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
*    promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

package com.force.sdk.oauth.connector;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import com.force.sdk.connector.ForceConnector;
import com.force.sdk.connector.ForceConnectorUtils;
import com.force.sdk.oauth.context.SecurityContext;
import com.force.sdk.oauth.userdata.UserDataRetrievalService;
import com.sforce.ws.ConnectionException;

/**
*
* Main actor in the OAuth handshake. The other pieces of the OAuth integration
* rely on the {@code ForceOAuthConnector} to handle the details of the OAuth flow.
* <p>
* In addition to the usual OAuth 2.0 protocol a {@code UserDataRetrievalService} is passed in and used to retrieve
* data about the authenticated user after the handshake completes. This service can either be the standard
* service or an application specific one. This allows user data to be accessible to the application.
* <p>
* This object is used from a per application singleton. DO NOT store per request values with this class.
*
* @author Fiaz Hossain
* @author Tim Kral
*/
public class ForceOAuthConnector implements ForceConnector {

    /**
     * OAuth spec version.
     *
     * @author Fiaz Hossain
     */
    public enum OAuthVersion {
        /**
         * OAuth version 2.0.
         */
        VERSION_2_0;
       
        private static OAuthVersion parse(String version) {
            if ("2.0".equals(version)) return VERSION_2_0;
            throw new UnsupportedOperationException("Unsupported OAuth version: " + version);
        }
    }
   
    /**
     * The url that is used for the authentication code callback.
     */
    public static final String REDIRECT_AUTH_URI = "/_auth";
    /**
     * The request attribute that the login redirect url will be read from.
     */
    public static final String LOGIN_REDIRECT_URL_ATTRIBUTE = "__login_redirect_url_attrbute__";

    private static final String LOCAL_HOST_PORT = "https://localhost:8443";
   
    private static final String ACCESS_TOKEN_JSON_KEY = "access_token";
    private static final String REFRESH_TOKEN_JSON_KEY = "refresh_token";
    private static final String INSTANCE_URL_JSON_KEY = "instance_url";
   
    // these two are application specific.
    private OAuthVersion version;
   
    // The connection info used to construct a connection
    private ForceOAuthConnectionInfo connInfo;
   
    // State that can be used to construct connInfo
    private String connectionName;               // Named connections can look up connection construction state       
    private ForceOAuthConnectionInfo externalConnInfo; // ForceOAuthConnectionInfo injected from an external source
    private UserDataRetrievalService userDataRetrievalService; // Service for retrieving data about the authenticated user
    private TokenRetrievalService tokenRetrievalService;
   
    /**
     * Default constructor. Sets OAuth version to 2.0. Uses standard
     * data retrieval service.
     */
    public ForceOAuthConnector() {
        this.version = OAuthVersion.VERSION_2_0;
        this.userDataRetrievalService = new UserDataRetrievalService();
        this.tokenRetrievalService = new TokenRetrievalServiceImpl();
    }
   
    /**
     * Sets OAuth version to 2.0. Takes in a {@code UserDataRetrievalService} so that
     * an extension can be used.
     *
     * @param userDataRetrievalService UserDataRetrievalService
     */
    public ForceOAuthConnector(UserDataRetrievalService userDataRetrievalService) {
        this.version = OAuthVersion.VERSION_2_0;
        this.userDataRetrievalService = userDataRetrievalService;
        this.tokenRetrievalService = new TokenRetrievalServiceImpl();
    }
   
    /**
     * Gets the access token for user. This gets called after an access code has been obtained.
     * That access code will now be exchanged for an access token or "auth token". Once an
     * access token is obtained, it will immediately be used to retrieve data about the user to
     * populate a {@code SecurityContext}.
     *
     * @param accessCode String
     * @param redirectUri String
     * @return a SecurityContext containing data about the authenticated user
     * @throws IOException i/o error
     */
    public SecurityContext getAccessToken(String accessCode, String redirectUri) throws IOException {
        StringBuffer urlParams = new StringBuffer("grant_type=authorization_code")
        .append("&code=").append(accessCode)
        .append("&redirect_uri=").append(URLEncoder.encode(redirectUri, "UTF-8"));

        getConnInfo().appendOauthKeyParam(urlParams);
        getConnInfo().appendOauthSecretParam(urlParams);
       
        return createTokenInternal(urlParams.toString(), null /* refresh token */);
    }
   
    /**
     * Uses the refresh token to obtain a new auth token for the user.
     *
     * @param refreshToken String
     * @return a SecurityContext containing data about the authenticated user
     * @throws IOException i/o error
     */
    public SecurityContext refreshAccessToken(String refreshToken) throws IOException {
        StringBuffer urlParams = new StringBuffer("grant_type=refresh_token")
        .append("&refresh_token=").append(refreshToken);

        getConnInfo().appendOauthKeyParam(urlParams);
        getConnInfo().appendOauthSecretParam(urlParams);
       
        return createTokenInternal(urlParams.toString(), refreshToken);
    }
   
    /**
     * Obtains an access token by calling the OAuth authentication endpoint and either trading an
     * access code or refresh token for it.
     *
     * The {@code UserDataRetrievalService} is called to retrieve data about the user
     * after the access token is obtained.
     *
     * @param params String
     * @param refreshToken String
     * @return SecurityContext containing data about the authenticated user
     * @throws IOException i/o error
     */
    @SuppressWarnings("unchecked")
    private SecurityContext createTokenInternal(String params, String refreshToken) throws IOException {
        String instanceUrl = null;
        String session = null;
        String refresh = refreshToken;
        try {
           
            String responsePayload = tokenRetrievalService.retrieveToken(
                    getHostPort(connInfo.getEndpoint(), null), params, refreshToken, getConnInfo());
           
            JSONParser parser = new JSONParser();
            Map<String, String> map = (Map) parser.parse(responsePayload);
           
            if ((session = map.get(ACCESS_TOKEN_JSON_KEY)) == null) {
                throw new IOException("Missing access token on response");
            }
           
            if (refresh == null) {
                if ((refresh = map.get(REFRESH_TOKEN_JSON_KEY)) == null) {
                    throw new IOException("Missing refresh token on response");
                }
            }
           
           
            if ((instanceUrl = map.get(INSTANCE_URL_JSON_KEY)) == null) {
                throw new IOException("Missing instance url on response");
            }
           
            SecurityContext sc =
                userDataRetrievalService.retrieveUserData(
                        session, ForceConnectorUtils.buildForceApiEndpoint(instanceUrl), refresh);
           
            return sc;
        } catch (ConnectionException ce) {
            throw new IOException("Unable to create token due to connection exception", ce);
        } catch (ParseException pe) {
            throw new IOException("Unable to create token due to parse exception", pe);
        }
    }
   
    /**
     * Closes the connection.
     */
    public void close() {
        this.connInfo = null;
       
        this.connectionName = null;
       
        this.externalConnInfo = null;
    }

    /**
     * Parses the access code in the servlet request.
     *
     * @param request HttpServletRequest
     * @return OAuth access code
     */
    public String getAccessCode(HttpServletRequest request) {
        return request.getParameter("code");
    }

    /**
     * Builds the logout url.
     *
     * @param request HttpServletRequest
     * @param forceEndpoint String
     * @param localLogoutSuccessfulPath String
     * @return the logout url
     */
    public String getForceLogoutUrl(HttpServletRequest request, String forceEndpoint, String localLogoutSuccessfulPath) {
       
        // Replace the force endpoint path with the logout path
        StringBuffer forceLogoutUrl =
            new StringBuffer(forceEndpoint.substring(0, forceEndpoint.indexOf('/', 9)) + "/secur/logout.jsp");
       
        //TODO: This doesn't work for multiple reasons. First the param is retURL. However fixing that makes
        //things worse because it won't let you redirect to a non-salesforce.com site.
        if (localLogoutSuccessfulPath != null && localLogoutSuccessfulPath.length() > 0) {
            try {
                forceLogoutUrl.append("?retUrl=").append(getHostPort(request))
                        .append(request.getContextPath())
                        .append(URLEncoder.encode(localLogoutSuccessfulPath, "UTF-8"));
               
                getConnInfo().appendOauthKeyParam(forceLogoutUrl);
            } catch (IOException e) {
                // log the error
            }
        }
       
        return forceLogoutUrl.toString();
    }

    /**
     * Returns the host and port of the endpoint.
     *
     * @param request HttpServletRequest
     * @return the host and port string
     */
    public String getHostPort(HttpServletRequest request) {
        String host = request.getHeader("Host");
        if (host == null) {
            return LOCAL_HOST_PORT;
        }
       
        return getHostPort(host, request.getScheme());
    }
   
    /**
     * Returns the host and port of the endpoint.
     *
     * @param endpoint String
     * @param protocol String
     * @return the host and port string
     */
    public String getHostPort(String endpoint, String protocol) {
        String[] parsedEndpoint = endpoint.split("://", 2);
        String endpointWithoutProtocol = parsedEndpoint[parsedEndpoint.length - 1];
       
        if (protocol == null && parsedEndpoint.length == 2) {
            protocol = parsedEndpoint[0];
        }
       
        if (endpointWithoutProtocol.startsWith("localhost") || endpointWithoutProtocol.contains("internal")) {
            if (protocol != null) return protocol + "://" + endpointWithoutProtocol;
           
            return "http://" + endpointWithoutProtocol;
        }
       
        return "https://" + endpointWithoutProtocol;
    }
   
    /**
     * Gets the url that the user will be redirected to for authentication. This url should send the user to a login
     * screen so that they can enter their credentials. Once the user successfully authenticates, a callback will
     * be received with the access code.
     *
     * @param request HttpServletRequest
     * @return the url to redirect the user to
     * @throws IOException i/o error
     */
    public String getLoginRedirectUrl(HttpServletRequest request) throws IOException {
        String redirectAttribute = (String) request.getAttribute(LOGIN_REDIRECT_URL_ATTRIBUTE);
       
        // Build the state url parameter
        StringBuffer state;
        if (redirectAttribute != null) {
            state = new StringBuffer(redirectAttribute);
        } else {
           
            // host:post/contextPath/servletPath
            state = new StringBuffer(getHostPort(request))
                    .append(request.getContextPath())
                    .append(request.getServletPath());
           
            if (request.getPathInfo() != null) {
                state.append(request.getPathInfo());
            }

            if (request.getQueryString() != null) {
                state.append("?" + request.getQueryString());
            }
        }
       
        // Build the login redirect url
        StringBuffer loginRedirectUrl =
            new StringBuffer(getHostPort(getConnInfo().getEndpoint(), null)).append("/services/oauth2/authorize")
                .append("?response_type=code")
                .append("&redirect_uri=").append(URLEncoder.encode(getRedirectUri(request), "UTF-8"))
                .append("&state=").append(URLEncoder.encode(state.toString(), "UTF-8"));
   
        // Add the oauth key parameter
        getConnInfo().appendOauthKeyParam(loginRedirectUrl);
       
        return loginRedirectUrl.toString();
    }

    /**
     * Gets the URI to redirect to for user authentication.
     *
     * @param request HttpServletRequest
     * @return the redirection URI
     */
    public String getRedirectUri(HttpServletRequest request) {
        return getHostPort(request) + request.getContextPath() + REDIRECT_AUTH_URI;
    }
   
    /**
     * Creates a connection info object. This returns the same output regardless
     * of whether the connector was supplied with a connection name that must be
     * resolved or directly given the oauth key, secret, and endpoint.
     *
     * @return The force oauth connection info
     * @throws IOException i/o error
     */
    ForceOAuthConnectionInfo getConnInfo() throws IOException {
        if (connInfo == null) {
            if (this.externalConnInfo != null) {
                connInfo = this.externalConnInfo;
            } else if (this.connectionName != null) {
                connInfo = ForceOAuthConnectionInfo.loadFromName(this.connectionName);
            }
           
            // Out of options.  There's not enough here to construct theConnInfo.
            if (connInfo == null) {
                StringBuffer errorMsg = new StringBuffer();
                errorMsg.append("No state was found to construct an oauth connection.")
                        .append(" Please provide an endpoint, key and secret or connection url.");
           
                if (this.connectionName != null) {
                    errorMsg
                        .append(" Or create a classpath properties file, environment variable or java property for the name '")
                        .append(this.connectionName).append("'");
                }
           
                throw new IOException(errorMsg.toString());
            }
           
            connInfo.validate();
        }
       
        return connInfo;
    }

    public void setConnectionName(String connectionName) {
        this.connectionName = connectionName;
    }
   
    String getConnectionName() {
        return connectionName;
    }
   
    public void setConnectionInfo(ForceOAuthConnectionInfo connectionInfo) {
        this.externalConnInfo = connectionInfo;
    }

    public void setOAuthVersion(String oauthVersion) {
        this.version = OAuthVersion.parse(oauthVersion);
    }
   
    public OAuthVersion getOAuthVersion() {
        return this.version;
    }
   
    public void setUserDataRetrievalService(UserDataRetrievalService userDataRetrievalService) {
        this.userDataRetrievalService = userDataRetrievalService;
    }
   
    public void setTokenRetrievalService(TokenRetrievalService tokenRetrievalService) {
        this.tokenRetrievalService = tokenRetrievalService;
    }
}
TOP

Related Classes of com.force.sdk.oauth.connector.ForceOAuthConnector

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.