All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.cloud.artifactregistry.auth.GcloudCredentials Maven / Gradle / Ivy

/*
 * Copyright 2019 Google LLC
 *
 * 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
 *
 *     https://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.cloud.artifactregistry.auth;

import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.GenericData;
import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class GcloudCredentials extends GoogleCredentials {
  private static final Logger LOGGER = LoggerFactory.getLogger(GcloudCredentials.class.getName());

  private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
  private static final String KEY_ACCESS_TOKEN = "access_token";
  private static final String KEY_TOKEN_EXPIRY = "token_expiry";


  public GcloudCredentials(AccessToken initialToken) {
    super(initialToken);
  }



  /**
   * Tries to get credentials from gcloud. Returns null if credentials are not available.
   * @return The Credentials from gcloud
   * @throws IOException if there was an error retrieving credentials from gcloud
   */
  public static GcloudCredentials tryCreateGcloudCredentials() throws IOException {
    return new GcloudCredentials(validateAccessToken(getGcloudAccessToken()));
  }

  private static String gCloudCommand() {
    boolean isWindows = System.getProperty("os.name").startsWith("Windows");
    return isWindows ? "gcloud.cmd" : "gcloud";
  }

  // This is called if the token expires, from the calls to refreshIfExpired()
  @Override
  public AccessToken refreshAccessToken() throws IOException {
    LOGGER.info("Refreshing gcloud credentials...");
    return validateAccessToken(getGcloudAccessToken());
  }

  // Checks that the token is valid, throws IOException if it is expired.
  // If this plugin is run when gcloud has expired auth, then it gcloud doesn't
  // throw any errors, it simply returns an expired token. We check the token
  // that is returned and throw an error if it's expired to prompt the user to
  // login.
  private static AccessToken validateAccessToken(AccessToken token) throws IOException {
      Date expiry = token.getExpirationTime();
      if (expiry.before(new Date())) {
        throw new IOException("AccessToken is expired - maybe run `gcloud auth login`");
      }
      return token;
  }

  private static AccessToken getGcloudAccessToken() throws IOException {
    ProcessBuilder processBuilder = new ProcessBuilder();
    String gcloud = gCloudCommand();
    processBuilder.command(gcloud, "config", "config-helper", "--format=json(credential)");
    Process process = processBuilder.start();
    try {
      int exitCode = process.waitFor();
      String stdOut = readStreamToString(process.getInputStream());
      if (exitCode != 0) {
        String stdErr = readStreamToString(process.getErrorStream());
        throw new IOException(String.format("gcloud exited with status: %d\nOutput:\n%s\nError Output:\n%s\n",
            exitCode, stdOut, stdErr));
      }

      GenericData result = JSON_FACTORY.fromString(stdOut, GenericData.class);
      Map credential = (Map) result.get("credential");
      if (credential == null) {
        throw new IOException("No credential returned from gcloud");
      }
      if (!credential.containsKey(KEY_ACCESS_TOKEN) || !credential.containsKey(KEY_TOKEN_EXPIRY)) {
        throw new IOException("Malformed response from gcloud");
      }
      DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
      df.setTimeZone(TimeZone.getTimeZone("UTC"));
      Date expiry = df.parse((String) credential.get(KEY_TOKEN_EXPIRY));
      return new AccessToken((String) credential.get(KEY_ACCESS_TOKEN), expiry);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new IOException(e);
    } catch (ParseException e) {
      throw new IOException("Failed to parse timestamp from gcloud output", e);
    }
  }

  // Reads a stream to a string, this code is basically copied from 'copyReaderToBuilder' from
  // com.google.io.CharStreams in the Guava library.
  private static String readStreamToString(InputStream input) throws IOException {
    InputStreamReader reader = new InputStreamReader(input);
    StringBuilder output = new StringBuilder();
    char[] buf = new char[0x800];
    int nRead;
    while ((nRead = reader.read(buf)) != -1) {
      output.append(buf, 0, nRead);
    }
    return output.toString();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy