/*
 * Copyright (c) 2012 Google Inc.
 *
 * Licensed 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 com.google.api.client.extensions.java6.auth.oauth2;

import com.google.api.client.auth.oauth2.AuthorizationCodeFlow;
import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.util.Preconditions;

import java.awt.Desktop;
import java.awt.Desktop.Action;
import java.io.IOException;
import java.net.URI;

/**
 * OAuth 2.0 authorization code flow for an installed Java application that persists end-user
 * credentials.
 *
 * <p>
 * Implementation is thread-safe.
 * </p>
 *
 * @since 1.11
 * @author Yaniv Inbar
 */
public class AuthorizationCodeInstalledApp {

  /** Authorization code flow. */
  private final AuthorizationCodeFlow flow;

  /** Verification code receiver. */
  private final VerificationCodeReceiver receiver;

  /**
   * @param flow authorization code flow
   * @param receiver verification code receiver
   */
  public AuthorizationCodeInstalledApp(
      AuthorizationCodeFlow flow, VerificationCodeReceiver receiver) {
    this.flow = Preconditions.checkNotNull(flow);
    this.receiver = Preconditions.checkNotNull(receiver);
  }

  /**
   * Authorizes the installed application to access user's protected data.
   *
   * @param userId user ID or {@code null} if not using a persisted credential store
   * @return credential
   */
  public Credential authorize(String userId) throws IOException {
    try {
      Credential credential = flow.loadCredential(userId);
      if (credential != null
          && (credential.getRefreshToken() != null || credential.getExpiresInSeconds() > 60)) {
        return credential;
      }
      // open in browser
      String redirectUri = receiver.getRedirectUri();
      AuthorizationCodeRequestUrl authorizationUrl =
          flow.newAuthorizationUrl().setRedirectUri(redirectUri);
      onAuthorization(authorizationUrl);
      // receive authorization code and exchange it for an access token
      String code = receiver.waitForCode();
      TokenResponse response = flow.newTokenRequest(code).setRedirectUri(redirectUri).execute();
      // store credential and return it
      return flow.createAndStoreCredential(response, userId);
    } finally {
      receiver.stop();
    }
  }

  /**
   * Handles user authorization by redirecting to the OAuth 2.0 authorization server.
   *
   * <p>
   * Default implementation is to call {@code browse(authorizationUrl.build())}. Subclasses may
   * override to provide optional parameters such as the recommended state parameter. Sample
   * implementation:
   * </p>
   *
   * <pre>
  &#64;Override
  protected void onAuthorization(AuthorizationCodeRequestUrl authorizationUrl) throws IOException {
    authorizationUrl.setState("xyz");
    super.onAuthorization(authorizationUrl);
  }
   * </pre>
   *
   * @param authorizationUrl authorization URL
   * @throws IOException I/O exception
   */
  protected void onAuthorization(AuthorizationCodeRequestUrl authorizationUrl) throws IOException {
    browse(authorizationUrl.build());
  }

  /**
   * Open a browser at the given URL using {@link Desktop} if available, or alternatively output the
   * URL to {@link System#out} for command-line applications.
   *
   * @param url URL to browse
   */
  public static void browse(String url) {
    Preconditions.checkNotNull(url);
    if (Desktop.isDesktopSupported()) {
      Desktop desktop = Desktop.getDesktop();
      if (desktop.isSupported(Action.BROWSE)) {
        try {
          desktop.browse(URI.create(url));
          return;
        } catch (IOException e) {
          // handled below
        }
      }
    }
    // Finally just ask user to open in their browser using copy-paste
    System.out.println("Please open the following URL in your browser:");
    System.out.println("  " + url);
  }

  /** Returns the authorization code flow. */
  public final AuthorizationCodeFlow getFlow() {
    return flow;
  }

  /** Returns the verification code receiver. */
  public final VerificationCodeReceiver getReceiver() {
    return receiver;
  }
}

@@ -1042,16 +1042,81 @@
 net.URI;
+%0Aimport java.util.logging.Level;%0Aimport java.util.logging.Logger;
 %0A%0A/**%0A *
@@ -1534,16 +1534,127 @@
 eiver;%0A%0A
+  private static final Logger LOGGER =%0A      Logger.getLogger(AuthorizationCodeInstalledApp.class.getName());%0A%0A
   /**%0A  
@@ -4152,24 +4152,36 @@
 tNull(url);%0A
+    try %7B%0A  
     if (Desk
@@ -4204,24 +4204,26 @@
 ported()) %7B%0A
+  
       Deskto
@@ -4252,24 +4252,26 @@
 tDesktop();%0A
+  
       if (de
@@ -4310,22 +4310,8 @@
 ) %7B%0A
-        try %7B%0A
     
@@ -4376,16 +4376,30 @@
        %7D
+%0A      %7D%0A    %7D
  catch (
@@ -4425,53 +4425,506 @@
     
-    // handled below%0A        %7D%0A      %7D
+// Handled below.%0A      LOGGER.log(Level.WARNING, %22Unable to open browser%22, e);%0A    %7D catch (InternalError e) %7B%0A      // A bug in a JRE can cause Desktop.isDesktopSupported() to throw an%0A      // InternalError rather than returning false. The error reads,%0A      // %22Can't connect to X11 window server using ':0.0' as the value of the%0A      // DISPLAY variable.%22 The exact error message may vary slightly.%0A      // Handled below.%0A      LOGGER.log(Level.WARNING, %22Unable to open browser%22, e);
 %0A    %7D%0A
+%0A
     

Handle error thrown by JRE when calling Desktop.isDesktopSupported https://codereview.appspot.com/8426045/