Package com.google.appengine.tools.admin

Source Code of com.google.appengine.tools.admin.OAuth2Native

// Copyright (c) 2011-2012 Google Inc.

package com.google.appengine.tools.admin;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.auth.oauth2.CredentialStore;
import com.google.api.client.auth.oauth2.TokenResponseException;
import com.google.api.client.extensions.java6.auth.oauth2.FileCredentialStore;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.common.base.Preconditions;

import java.awt.Desktop;
import java.awt.Desktop.Action;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;

/**
* Implements OAuth authentication "native" flow recommended for installed clients in which the end
* user must grant access in a web browser and then copy a code into the application.
*
* <p>
* Warning: the client ID and secret are not secured and are plainly visible to users.
* It is a hard problem to secure client credentials in installed applications.
* </p>
*
*/
public class OAuth2Native {
  /**
   * Browser to open in case {@link Desktop#isDesktopSupported()} is {@code false} or {@code null}
   * to prompt user to open the URL in their favorite browser.
   */
  private static final String BROWSER = "google-chrome";

  protected static final String OAUTH2_CLIENT_ID = "550516889912.apps.googleusercontent.com";
  protected static final String OAUTH2_CLIENT_SECRET = "ykPq-0UYfKNprLRjVx1hBBar";
  protected static final String[] OAUTH2_SCOPE = {"https://www.googleapis.com/auth/appengine.admin",
                                                  "https://www.googleapis.com/auth/cloud-platform"};
  protected static final long MIN_EXPIRES_SECONDS = 300;

  /** Token store filename. */
  protected static final String TOKEN_STORE_BASE = ".appcfg_oauth2_tokens_java";

  /** Google client secrets. */
  private static final GoogleClientSecrets DEFAULT_CLIENT_SECRETS =
      createClientSecrets(OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET);

  private GoogleClientSecrets clientSecrets;
  private String refreshTokenOverride;

  private GoogleAuthorizationCodeFlow flow;
  private final VerificationCodeReceiver receiver;
  private final String userId;

  /**
   * Creates the client secrets used for authentication
   *
   * @param clientIdOverride The client id to use
   * @param clientSecretOverride The client secret to use
   * @return The client secrets
   */
  private static GoogleClientSecrets createClientSecrets(
      final String clientIdOverride, final String clientSecretOverride) {
    return new GoogleClientSecrets().setInstalled(new GoogleClientSecrets.Details().setClientId(
        clientIdOverride).setClientSecret(clientSecretOverride));
  }

  public OAuth2Native(boolean usePersistedCredentials) {
    this(usePersistedCredentials, null, null, null);
  }

  /**
   * Initialize a native OAuth2 flow using the specific client id and client secret provided.
   *
   * @param usePersistedCredentials {@code true} to use a file to store credentials, {@code false}
   *        otherwise
   * @param clientIdOverride A client id to use for authentication requests or {@code null} to use
   *        the default for this application. If provided, {@code clientSecretOverride} must also be
   *        provided.
   * @param clientSecretOverride A client secret to use for authentication requests or {@code null}
   *        to use the default for this application. If provided, {@code clientIdOverride} must also
   *        be provided.
   * @param refreshTokenOverride An alternate oauth2 refresh token to use for authorization or
   *        {@code null} to use the default token from the credential store.
   */
  public OAuth2Native(boolean usePersistedCredentials, String clientIdOverride,
      String clientSecretOverride, String refreshTokenOverride) {
    this(new PromptReceiver(), System.getProperty("user.name"), null);

    Preconditions.checkArgument(!(clientIdOverride == null ^ clientSecretOverride == null),
        "If either is given, both a client id and a client secret must be provided");

    if (clientIdOverride != null) {
      clientSecrets = createClientSecrets(clientIdOverride, clientSecretOverride);
    }

    Preconditions.checkArgument(refreshTokenOverride == null || !usePersistedCredentials,
        "A credential store cannot be used when overriding the refresh token");

    this.refreshTokenOverride = refreshTokenOverride;

    try {
      flow = getAuthorizationCodeFlow(usePersistedCredentials);
    } catch (IOException e) {
      System.err.println("Error creating the Authorization Flow: " + e);
    } catch (IllegalArgumentException e) {
      if (exceptionMentionsJson(e)) {
        System.err.format("The credentials file is malformed. Please delete the file '%s'.%n",
                          getTokenStoreFile());
      } else {
        throw e;
      }
    }
  }

  public OAuth2Native(VerificationCodeReceiver receiver, String userId,
                      GoogleAuthorizationCodeFlow flow) {
    this.receiver = receiver;
    this.userId = userId;
    this.flow = flow;
    this.clientSecrets = DEFAULT_CLIENT_SECRETS;
    this.refreshTokenOverride = null;
  }

  /**
   * @return the Google client secrets.
   */
  public GoogleClientSecrets getClientSecrets() {
    return clientSecrets;
  }

  /**
   * @return a File representing the token store file name in the user's home directory.
   */
  protected File getTokenStoreFile() {
    String userDir = System.getProperty("user.home");
    return new File(userDir, TOKEN_STORE_BASE);
  }

  /**
   * Returns the CredentialStore to be used when calling
   * {@link OAuth2Native#getAuthorizationCodeFlow(boolean)} with parameter {@code true}.
   */
  protected CredentialStore getCredentialStore(JsonFactory jsonFactory) throws IOException {
    return new FileCredentialStore(getTokenStoreFile(), jsonFactory);
  }

  /**
   * Creates an authorization code flow with the right CredentialStore based on the argument.
   *
   * @param usePersistedCredentials whether or not to persist the credentials
   * @return a GoogleAuthorizationCodeFlow
   */
  protected GoogleAuthorizationCodeFlow getAuthorizationCodeFlow(boolean usePersistedCredentials)
      throws IOException {
    HttpTransport httpTransport = new NetHttpTransport();
    JsonFactory jsonFactory = new JacksonFactory();
    Collection<String> scopes = Arrays.asList(OAUTH2_SCOPE);
    GoogleClientSecrets clientSecrets = getClientSecrets();
    GoogleAuthorizationCodeFlow flow;
    if (usePersistedCredentials) {
      flow = new GoogleAuthorizationCodeFlow.Builder(
        httpTransport, jsonFactory, clientSecrets, scopes).setAccessType("offline")
        .setApprovalPrompt("force").setCredentialStore(getCredentialStore(jsonFactory)).build();
    } else {
      flow = new GoogleAuthorizationCodeFlow.Builder(
        httpTransport, jsonFactory, clientSecrets, scopes).setAccessType("online")
        .setApprovalPrompt("auto").build();
    }
    return flow;
  }

  /**
   * Checks if an exception mention JSON in its message. This method is intended to detect a
   * specific code path and is somewhat fragile.
   *
   * @param  exception the exception to check
   * @return           true if the exception mentions JSON in its message, false otherwise.
   */
  private static boolean exceptionMentionsJson(Exception exception) {
    return exception.getMessage().toLowerCase().contains("json");
  }

  /**
   * Calls the method refreshToken if there is no access token or if the token is close to expire.
   * Returns true if refreshToken was called. If the token was refreshed and credentialStore is not
   * null, it saves the updated credential.
   *
   * @param  credential the credential to check
   * @return            whether the credential was refreshed or not
   */
  protected boolean refreshCredentialIfNeeded(Credential credential) throws IOException {
    if (credential != null) {
      Long expiresInSeconds = credential.getExpiresInSeconds();
      if (credential.getAccessToken() == null || expiresInSeconds == null ||
          expiresInSeconds < MIN_EXPIRES_SECONDS) {
        credential.refreshToken();

        if (flow.getCredentialStore() != null) {
          flow.getCredentialStore().store(userId, credential);
        }

        return true;
      }
    }
    return false;
  }

  /**
   * Authorizes the installed application to access user's protected data.
   *
   * @return a credential with the accesToken or null if no authorization token could be obtained.
   */
  public Credential authorize(){
    if (flow == null) {
      return null;
    }
    try {
      String redirectUri = receiver.getRedirectUri();

      final Credential credential = getCredential();
      refreshCredentialIfNeeded(credential);
      if (credential != null && credential.getAccessToken() != null){
        return credential;
      }

      browse(flow.newAuthorizationUrl().setRedirectUri(redirectUri).build());
      String code = receiver.waitForCode();
      GoogleTokenResponse response =
          flow.newTokenRequest(code).setRedirectUri(redirectUri).execute();
      return flow.createAndStoreCredential(response, userId);
    } catch (TokenResponseException e) {
      System.err.format("Either the access code is invalid or the OAuth token is revoked." +
                        "Details: %s%n.", e.getDetails().getError());
    } catch (IOException e) {
      System.err.println(e);
    } catch (VerificationCodeReceiverRedirectUriException e) {
      System.err.println(e.getMessage());
    } finally {
      try {
        receiver.stop();
      } catch (VerificationCodeReceiverStopException e) {
        System.err.println(e.getMessage());
      }
    }
    return null;
  }

  /**
   * Gets a new credential either from the configured credential store, or with a specific refresh
   * token.
   *
   * @return A newly created and credential
   * @throws IOException
   */
  Credential getCredential() throws IOException {
    if (refreshTokenOverride == null) {
      return flow.loadCredential(userId);
    }

    final Credential credential =
        new Credential.Builder(flow.getMethod()).setTransport(flow.getTransport())
            .setJsonFactory(flow.getJsonFactory())
            .setTokenServerEncodedUrl(flow.getTokenServerEncodedUrl())
            .setClientAuthentication(flow.getClientAuthentication())
            .setRequestInitializer(flow.getRequestInitializer())
            .setClock(flow.getClock())
            .build();
    credential.setRefreshToken(refreshTokenOverride);

    return credential;
  }

  /**
   * Open a browser at the given URL.
   * <p>
   * It attempts to open the browser using {@link Desktop#isDesktopSupported()}.
   * If that fails, on Windows it tries {@code rundll32}. If that fails, it opens the browser
   * specified in {@link #BROWSER}.
   * Note though that currently we've only tested this code with Google Chrome (hence this is the
   * default value).
   * </p>
   *
   * @param url absolute url to open in the browser
   */
  protected void browse(String url) {
    if (Desktop.isDesktopSupported()) {
      Desktop desktop = Desktop.getDesktop();
      if (desktop.isSupported(Action.BROWSE)) {
        try {
          desktop.browse(URI.create(url));
          return;
        } catch (IOException e) {
        }
      }
    }
    try {
      Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
      return;
    } catch (IOException e) {
    }
    if (BROWSER != null) {
      try {
        Runtime.getRuntime().exec(new String[] {BROWSER, url});
        return;
      } catch (IOException e) {
      }
    }
    System.out.format("Please open the following URL in your browser:%n  %s%n", url);
  }
}
TOP

Related Classes of com.google.appengine.tools.admin.OAuth2Native

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.