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

com.rt.storage.auth.oauth2.IamUtils Maven / Gradle / Ivy

package com.rt.storage.auth.oauth2;

import com.rt.storage.api.client.http.GenericUrl;
import com.rt.storage.api.client.http.HttpRequest;
import com.rt.storage.api.client.http.HttpResponse;
import com.rt.storage.api.client.http.HttpStatusCodes;
import com.rt.storage.api.client.http.HttpTransport;
import com.rt.storage.api.client.http.json.JsonHttpContent;
import com.rt.storage.api.client.json.GenericJson;
import com.rt.storage.api.client.json.JsonObjectParser;
import com.rt.storage.api.client.util.GenericData;
import com.rt.storage.auth.Credentials;
import com.rt.storage.auth.ServiceAccountSigner;
import com.rt.storage.auth.http.HttpCredentialsAdapter;
import com.google.common.io.BaseEncoding;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

/**
 * This internal class provides shared utilities for interacting with the IAM API for common
 * features like signing.
 */
class IamUtils {
  private static final String SIGN_BLOB_URL_FORMAT =
      "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:signBlob";
  private static final String ID_TOKEN_URL_FORMAT =
      "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateIdToken";
  private static final String PARSE_ERROR_MESSAGE = "Error parsing error message response. ";
  private static final String PARSE_ERROR_SIGNATURE = "Error parsing signature response. ";

  /**
   * Returns a signature for the provided bytes.
   *
   * @param serviceAccountEmail the email address for the service account used for signing
   * @param credentials credentials required for making the IAM call
   * @param transport transport used for building the HTTP request
   * @param toSign bytes to sign
   * @param additionalFields additional fields to send in the IAM call
   * @return signed bytes
   * @throws ServiceAccountSigner.SigningException if signing fails
   */
  static byte[] sign(
      String serviceAccountEmail,
      Credentials credentials,
      HttpTransport transport,
      byte[] toSign,
      Map additionalFields) {
    BaseEncoding base64 = BaseEncoding.base64();
    String signature;
    try {
      signature =
          getSignature(
              serviceAccountEmail, credentials, transport, base64.encode(toSign), additionalFields);
    } catch (IOException ex) {
      throw new ServiceAccountSigner.SigningException("Failed to sign the provided bytes", ex);
    }
    return base64.decode(signature);
  }

  private static String getSignature(
      String serviceAccountEmail,
      Credentials credentials,
      HttpTransport transport,
      String bytes,
      Map additionalFields)
      throws IOException {
    String signBlobUrl = String.format(SIGN_BLOB_URL_FORMAT, serviceAccountEmail);
    GenericUrl genericUrl = new GenericUrl(signBlobUrl);

    GenericData signRequest = new GenericData();
    signRequest.set("payload", bytes);
    for (Map.Entry entry : additionalFields.entrySet()) {
      signRequest.set(entry.getKey(), entry.getValue());
    }
    JsonHttpContent signContent = new JsonHttpContent(OAuth2Utils.JSON_FACTORY, signRequest);

    HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(credentials);
    HttpRequest request =
        transport.createRequestFactory(adapter).buildPostRequest(genericUrl, signContent);

    JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
    request.setParser(parser);
    request.setThrowExceptionOnExecuteError(false);

    HttpResponse response = request.execute();
    int statusCode = response.getStatusCode();
    if (statusCode >= 400 && statusCode < HttpStatusCodes.STATUS_CODE_SERVER_ERROR) {
      GenericData responseError = response.parseAs(GenericData.class);
      Map error =
          OAuth2Utils.validateMap(responseError, "error", PARSE_ERROR_MESSAGE);
      String errorMessage = OAuth2Utils.validateString(error, "message", PARSE_ERROR_MESSAGE);
      throw new IOException(
          String.format(
              "Error code %s trying to sign provided bytes: %s", statusCode, errorMessage));
    }
    if (statusCode != HttpStatusCodes.STATUS_CODE_OK) {
      throw new IOException(
          String.format(
              "Unexpected Error code %s trying to sign provided bytes: %s",
              statusCode, response.parseAsString()));
    }
    InputStream content = response.getContent();
    if (content == null) {
      // Throw explicitly here on empty content to avoid NullPointerException from parseAs call.
      // Mock transports will have success code with empty content by default.
      throw new IOException("Empty content from sign blob server request.");
    }

    GenericData responseData = response.parseAs(GenericData.class);
    return OAuth2Utils.validateString(responseData, "signedBlob", PARSE_ERROR_SIGNATURE);
  }

  /**
   * Returns an IdToken issued to the serviceAccount with a specified targetAudience
   *
   * @param serviceAccountEmail the email address for the service account to get an ID Token for
   * @param credentials credentials required for making the IAM call
   * @param transport transport used for building the HTTP request
   * @param targetAudience the audience the issued ID token should include
   * @param additionalFields additional fields to send in the IAM call
   * @return IdToken issed to the serviceAccount
   * @throws IOException if the IdToken cannot be issued.
   */
  static IdToken getIdToken(
      String serviceAccountEmail,
      Credentials credentials,
      HttpTransport transport,
      String targetAudience,
      boolean includeEmail,
      Map additionalFields)
      throws IOException {

    String idTokenUrl = String.format(ID_TOKEN_URL_FORMAT, serviceAccountEmail);
    GenericUrl genericUrl = new GenericUrl(idTokenUrl);

    GenericData idTokenRequest = new GenericData();
    idTokenRequest.set("audience", targetAudience);
    idTokenRequest.set("includeEmail", includeEmail);
    for (Map.Entry entry : additionalFields.entrySet()) {
      idTokenRequest.set(entry.getKey(), entry.getValue());
    }
    JsonHttpContent idTokenContent = new JsonHttpContent(OAuth2Utils.JSON_FACTORY, idTokenRequest);

    HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(credentials);
    HttpRequest request =
        transport.createRequestFactory(adapter).buildPostRequest(genericUrl, idTokenContent);

    JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
    request.setParser(parser);
    request.setThrowExceptionOnExecuteError(false);

    HttpResponse response = request.execute();
    int statusCode = response.getStatusCode();
    if (statusCode >= 400 && statusCode < HttpStatusCodes.STATUS_CODE_SERVER_ERROR) {
      GenericData responseError = response.parseAs(GenericData.class);
      Map error =
          OAuth2Utils.validateMap(responseError, "error", PARSE_ERROR_MESSAGE);
      String errorMessage = OAuth2Utils.validateString(error, "message", PARSE_ERROR_MESSAGE);
      throw new IOException(
          String.format("Error code %s trying to getIDToken: %s", statusCode, errorMessage));
    }
    if (statusCode != HttpStatusCodes.STATUS_CODE_OK) {
      throw new IOException(
          String.format(
              "Unexpected Error code %s trying to getIDToken: %s",
              statusCode, response.parseAsString()));
    }
    InputStream content = response.getContent();
    if (content == null) {
      // Throw explicitly here on empty content to avoid NullPointerException from
      // parseAs call.
      // Mock transports will have success code with empty content by default.
      throw new IOException("Empty content from generateIDToken server request.");
    }

    GenericJson responseData = response.parseAs(GenericJson.class);
    String rawToken = OAuth2Utils.validateString(responseData, "token", PARSE_ERROR_MESSAGE);
    return IdToken.create(rawToken);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy