org.broad.igv.ga4gh.OAuthUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.broad.igv.ga4gh.OAuthUtils.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2007-2015 Broad Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package org.broad.igv.ga4gh;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import org.apache.log4j.Logger;
import org.broad.igv.DirectoryManager;
import org.broad.igv.batch.CommandListener;
import org.broad.igv.ui.util.MessageUtils;
import org.broad.igv.util.FileUtils;
import org.broad.igv.util.HttpUtils;

import java.awt.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.prefs.Preferences;

/**
 * Created by jrobinso on 11/19/14.
 */
public class OAuthUtils {

    private static Logger log = Logger.getLogger(OAuthUtils.class);

    // dwm08
    public static String authProvider = "Google";
    // dwm08
    private String appIdURI = null;
    // dwm08
    public static String findString = null;
    // dwm08
    public static String replaceString = null;

    private static final String REFRESH_TOKEN_KEY = "oauth_refresh_token";
    private static final String PROPERTIES_URL = "https://igvdata.broadinstitute.org/app/oauth_native.json";
    private String genomicsScope = "https://www.googleapis.com/auth/genomics";
    private String gsScope = "https://www.googleapis.com/auth/devstorage.read_write";
    private String profileScope = "https://www.googleapis.com/auth/userinfo.profile";
    private String state = "%2Fprofile";
    private String redirectURI = "http%3A%2F%2Flocalhost%3A60151%2FoauthCallback";
    private String oobURI = "urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob";
    private String clientId;
    private String clientSecret;
    private String authURI;
    private String tokenURI;

    private String authorizationCode;
    private String accessToken;
    private String refreshToken;
    private long expirationTime;

    public static String GS_HOST = "www.googleapis.com";

    private static OAuthUtils theInstance;
    private String currentUserName;

    // dwm08
    // by default this is the google scope
    private String scope = genomicsScope + "%20" + gsScope + "%20" + profileScope;

    // Construct OAuthUtils earcly so Google menu can be updated to the 
    // correct oauth provider. dwm08
    static {
        if (theInstance == null) {
            theInstance = new OAuthUtils();
        }
    }

    public static synchronized OAuthUtils getInstance() {

        if (theInstance == null) {
            theInstance = new OAuthUtils();
        }

        return theInstance;
    }

    private OAuthUtils() {
        // Attempt to fetch refresh token from local store.
        restoreRefreshToken();
        // do this early -- dwm08
        try {
            fetchOauthProperties();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void fetchOauthProperties() throws IOException {

        String oauthConfig = DirectoryManager.getIgvDirectory() + "/oauth-config.json";
        //IGVPreferences.getInstance().get(IGVPreferences.OAUTH_CONFIG);

        if (oauthConfig == null || !FileUtils.resourceExists(oauthConfig)) {
            String propString = HttpUtils.getInstance().getContentsAsString(new URL(PROPERTIES_URL));
            JsonParser parser = new JsonParser();
            JsonObject obj = parser.parse(propString).getAsJsonObject().get("installed").getAsJsonObject();
            authURI = obj.get("auth_uri").getAsString();
            clientSecret = obj.get("client_secret").getAsString();
            tokenURI = obj.get("token_uri").getAsString();
            clientId = obj.get("client_id").getAsString();
        } else {
            // Experimental -- this will change -- dwm08
            JsonParser parser = new JsonParser();
            String json = FileUtils.getContents(oauthConfig);
            JsonObject obj = parser.parse(json).getAsJsonObject();
            authURI = obj.get("authorization_endpoint").getAsString();
            clientSecret = obj.get("client_secret").getAsString();
            tokenURI = obj.get("token_endpoint").getAsString();
            clientId = obj.get("client_id").getAsString();
            GS_HOST = obj.get("hosts").getAsString();
            appIdURI = obj.get("app_id_uri").getAsString();
            authProvider = obj.get("auth_provider").getAsString();
            String scope = obj.get("scope").getAsString();
            if (scope.equals("none")) {
                this.scope = null;
            }
            JsonElement je = obj.get("find_string");
            if (je != null) {
                findString = je.getAsString();
            }
            je = obj.get("replace_string");
            if (je != null) {
                replaceString = je.getAsString();
            }

        }
    }

    /**
     * Send request to authorization provider to start the oauth 2.0
     * authorization process. If the listener is up, wait for a callback
     * Otherwise, provide a dialog where user can provide authentication token.
     * This method has been generalized to use any auth provider (originally google only)
     * dwm08
     *
     * @throws IOException
     * @throws URISyntaxException
     */
    public void openAuthorizationPage() throws IOException, URISyntaxException {
        Desktop desktop = Desktop.getDesktop();

        // properties moved to early init dwm08
        //if (clientId == null) fetchOauthProperties();

        String redirect = oobURI;
        // if the listener is active, then set the redirect URI.  dwm08
        if (CommandListener.isListening()) {
            redirect = redirectURI;
        }
        String url;
        // for auth providers that need scope,
        // set the scope parameter
        //if (scope != null) {
        if (appIdURI == null) {
            url = authURI + "?" + "scope=" + scope + "&" + "state=" + state + "&" + "redirect_uri=" + redirect + "&"
                    + "response_type=code&" + "client_id=" + clientId; // Native app
        }
        // for auth providers that need the resource setting
        // the the resource paremeter
        //else if (appIdURI != null) {
        else {

            url = authURI + "?" + "scope=" + scope + "&" + "state=" + state + "&" + "redirect_uri=" + redirect + "&"
                    + "response_type=code&" + "resource=" + appIdURI + "&" + "client_id=" + clientId; // Native app
        }
        //        else {
        //           throw new IOException("Either scope or resource must be provided to authenticate.");
        //        }

        // check if the "browse" Desktop action is suppported (many Linux DEs cannot directly 
        // launch browsers!)
        if (desktop.isSupported(Desktop.Action.BROWSE)) {
            desktop.browse(new URI(url));
        } else { // otherwise, display a dialog box for the user to copy the URL manually.
            MessageUtils.showMessage("Copy this authorization URL into your web browser: " + url);
        }

        // if the listener is not active, prompt the user
        // for the access token
        if (!CommandListener.isListening()) {
            String ac = MessageUtils.showInputDialog("Please paste authorization code here:");
            if (ac != null) {
                setAuthorizationCode(ac, oobURI);
            }
        }

    }

    // Called from port listener upon receiving the oauth request with a "code" parameter
    public void setAuthorizationCode(String ac) throws IOException {
        setAuthorizationCode(ac, redirectURI);
    }

    public void setAuthorizationCode(String ac, String redirect) throws IOException {
        authorizationCode = ac;
        fetchTokens(redirect);
        fetchUserProfile();
    }

    // Called from port listener upon receiving the oauth request with a "token" parameter TODO -- does this ever happen?
    public void setAccessToken(String accessToken) throws IOException {
        this.accessToken = accessToken;
        fetchUserProfile();
    }

    private void fetchTokens(String redirect) throws IOException {

        // properties moved to early init dwm08
        //if (clientId == null) fetchOauthProperties();

        URL url = new URL(tokenURI);

        Map<String, String> params = new HashMap<String, String>();
        params.put("code", authorizationCode);
        params.put("client_id", clientId);
        params.put("client_secret", clientSecret);
        params.put("redirect_uri", redirect);
        params.put("grant_type", "authorization_code");

        // set the resource if it necessary for the auth provider dwm08
        if (appIdURI != null) {
            params.put("resource", appIdURI);
        }

        String response = HttpUtils.getInstance().doPost(url, params);
        JsonParser parser = new JsonParser();
        JsonObject obj = parser.parse(response).getAsJsonObject();

        accessToken = obj.getAsJsonPrimitive("access_token").getAsString();
        refreshToken = obj.getAsJsonPrimitive("refresh_token").getAsString();
        expirationTime = System.currentTimeMillis() + (obj.getAsJsonPrimitive("expires_in").getAsInt() * 1000);

        // Try to store in java.util.prefs
        saveRefreshToken();
    }

    /**
     * Fetch a new access token from a refresh token.
     *
     * @throws IOException
     */
    private void refreshAccessToken() throws IOException {

        // properties moved to early init dwm08
        //if (clientId == null) fetchOauthProperties();

        URL url = new URL(tokenURI);

        Map<String, String> params = new HashMap<String, String>();
        params.put("refresh_token", refreshToken);
        params.put("client_id", clientId);
        params.put("client_secret", clientSecret);
        params.put("grant_type", "refresh_token");

        // set the resource if it necessary for the auth provider dwm08
        if (appIdURI != null) {
            params.put("resource", appIdURI);
        }

        String response = HttpUtils.getInstance().doPost(url, params);
        JsonParser parser = new JsonParser();
        JsonObject obj = parser.parse(response).getAsJsonObject();

        JsonPrimitive atprim = obj.getAsJsonPrimitive("access_token");
        if (atprim != null) {
            accessToken = obj.getAsJsonPrimitive("access_token").getAsString();
            expirationTime = System.currentTimeMillis() + (obj.getAsJsonPrimitive("expires_in").getAsInt() * 1000);
            fetchUserProfile();
        } else {
            // Refresh token has failed, reauthorize from scratch
            reauthorize();
        }

    }

    private void reauthorize() throws IOException {
        logout();
        try {
            openAuthorizationPage();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    /**
     * Check if the username is in the claim information. If so, extract it.
     * Otherwise call out to the server to get the current user name.
     * dwm08
     *
     * @throws IOException
     */
    private void fetchUserProfile() throws IOException {

        // dwm08 - removing functionality to get user profile info from microsoft oauth. Just not worth the trouble
        //            JWT jwt = JWT.decode(accessToken);
        //            Map<String, Claim> claims = jwt.getClaims();
        //            for (String claim: claims.keySet()) {
        //               System.out.println(claim + " = " + claims.get(claim).asString());
        //            }
        //            currentUserName = claims.get("unique_name").asString();
        try {
            URL url = new URL("https://www.googleapis.com/plus/v1/people/me?access_token=" + accessToken);
            String response = HttpUtils.getInstance().getContentsAsJSON(url);
            JsonParser parser = new JsonParser();
            JsonObject obj = parser.parse(response).getAsJsonObject();
            currentUserName = obj.get("displayName").getAsString();

        } catch (Throwable exception) {

        }

    }

    public String getAccessToken() {

        // Check expiration time, with 1 minute cushion
        if (accessToken == null || (System.currentTimeMillis() > (expirationTime - 60 * 1000))) {
            if (refreshToken != null) {
                try {
                    this.refreshAccessToken();
                } catch (IOException e) {
                    log.error("Error fetching access token", e);
                }
            }
        }

        return accessToken;
    }

    public boolean isLoggedIn() {
        return getAccessToken() != null;
    }

    public String getCurrentUserName() {
        return currentUserName;
    }

    public void logout() {
        accessToken = null;
        refreshToken = null;
        expirationTime = -1;
        currentUserName = null;
        removeRefreshToken();
    }

    private void saveRefreshToken() {
        try {
            Preferences.userRoot().put(REFRESH_TOKEN_KEY, refreshToken);
        } catch (Exception e) {
            log.error("Error storing refresh token", e);
        }
    }

    private void restoreRefreshToken() {
        try {
            refreshToken = Preferences.userRoot().get(REFRESH_TOKEN_KEY, null);
        } catch (Exception e) {
            log.error("Error fetching oauth refresh token", e);
        }
    }

    private void removeRefreshToken() {
        try {
            Preferences.userRoot().remove(REFRESH_TOKEN_KEY);
        } catch (Exception e) {
            log.error("Error removing oauth refresh token", e);
        }
    }

    // Doesn't really belong here....
    public static boolean isGoogleCloud(String url) {
        return url.contains(GS_HOST);
    }

    public void updateSaveOption(boolean aBoolean) {
        if (aBoolean) {
            if (refreshToken != null) {
                saveRefreshToken();
            }
        } else {
            removeRefreshToken();

        }
    }
}