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

com.google.firebase.auth.internal.CryptoSigners Maven / Gradle / Ivy

package com.google.firebase.auth.internal;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpResponseInterceptor;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.util.StringUtils;
import com.google.auth.ServiceAccountSigner;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteStreams;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseException;
import com.google.firebase.ImplFirebaseTrampolines;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.internal.Utils;
import com.google.firebase.internal.AbstractPlatformErrorHandler;
import com.google.firebase.internal.ApiClientUtils;
import com.google.firebase.internal.ErrorHandlingHttpClient;
import com.google.firebase.internal.HttpRequestInfo;
import com.google.firebase.internal.NonNull;
import java.io.IOException;
import java.util.Map;

/**
 * A set of {@link CryptoSigner} implementations and utilities for interacting with them.
 */
public class CryptoSigners {

  private static final String METADATA_SERVICE_URL =
      "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email";

  private CryptoSigners() { }

  /**
   * A {@link CryptoSigner} implementation that uses service account credentials or equivalent
   * crypto-capable credentials for signing data.
   */
  static class ServiceAccountCryptoSigner implements CryptoSigner {

    private final ServiceAccountSigner signer;

    ServiceAccountCryptoSigner(@NonNull ServiceAccountSigner signer) {
      this.signer = checkNotNull(signer);
    }

    @Override
    public byte[] sign(byte[] payload) {
      return signer.sign(payload);
    }

    @Override
    public String getAccount() {
      return signer.getAccount();
    }
  }

  /**
   * @ {@link CryptoSigner} implementation that uses the
   * 
   * Google IAMCredentials service to sign data.
   */
  static class IAMCryptoSigner implements CryptoSigner {

    private static final String IAM_SIGN_BLOB_URL =
        "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:signBlob";

    private final String serviceAccount;
    private final ErrorHandlingHttpClient httpClient;

    IAMCryptoSigner(
        @NonNull HttpRequestFactory requestFactory,
        @NonNull JsonFactory jsonFactory,
        @NonNull String serviceAccount) {
      checkArgument(!Strings.isNullOrEmpty(serviceAccount));
      this.serviceAccount = serviceAccount;
      this.httpClient = new ErrorHandlingHttpClient<>(
          requestFactory,
          jsonFactory,
          new IAMErrorHandler(jsonFactory));
    }

    void setInterceptor(HttpResponseInterceptor interceptor) {
      httpClient.setInterceptor(interceptor);
    }

    @Override
    public byte[] sign(byte[] payload) throws FirebaseAuthException {
      String encodedPayload = BaseEncoding.base64().encode(payload);
      Map content = ImmutableMap.of("payload", encodedPayload);
      String encodedUrl = String.format(IAM_SIGN_BLOB_URL, serviceAccount);
      HttpRequestInfo requestInfo = HttpRequestInfo.buildJsonPostRequest(encodedUrl, content);
      GenericJson parsed = httpClient.sendAndParse(requestInfo, GenericJson.class);
      return BaseEncoding.base64().decode((String) parsed.get("signedBlob"));
    }

    @Override
    public String getAccount() {
      return serviceAccount;
    }
  }

  /**
   * A {@link CryptoSigner} implementation that doesn't sign data. For use with the Auth Emulator
   * only
   */
  public static class EmulatorCryptoSigner implements CryptoSigner {

    private static final String ACCOUNT = "[email protected]";

    @Override
    public byte[] sign(byte[] payload) {
      return "".getBytes();
    }

    @Override
    public String getAccount() {
      return ACCOUNT;
    }
  }

  private static class IAMErrorHandler
      extends AbstractPlatformErrorHandler {

    IAMErrorHandler(JsonFactory jsonFactory) {
      super(jsonFactory);
    }

    @Override
    protected FirebaseAuthException createException(FirebaseException base) {
      return new FirebaseAuthException(base);
    }
  }

  /**
   * Initializes a {@link CryptoSigner} instance for the given Firebase app. Follows the protocol
   * documented at go/firebase-admin-sign.
   */
  public static CryptoSigner getCryptoSigner(FirebaseApp firebaseApp) throws IOException {
    if (Utils.isEmulatorMode()) {
      return new EmulatorCryptoSigner();
    }

    GoogleCredentials credentials = ImplFirebaseTrampolines.getCredentials(firebaseApp);

    // If the SDK was initialized with a service account, use it to sign bytes.
    if (credentials instanceof ServiceAccountCredentials) {
      return new ServiceAccountCryptoSigner((ServiceAccountCredentials) credentials);
    }

    HttpRequestFactory requestFactory = ApiClientUtils.newAuthorizedRequestFactory(firebaseApp);
    JsonFactory jsonFactory = firebaseApp.getOptions().getJsonFactory();

    // If the SDK was initialized with a service account email, use it with the IAM service
    // to sign bytes.
    String serviceAccountId = firebaseApp.getOptions().getServiceAccountId();
    if (!Strings.isNullOrEmpty(serviceAccountId)) {
      return new IAMCryptoSigner(requestFactory, jsonFactory, serviceAccountId);
    }

    // If the SDK was initialized with some other credential type that supports signing
    // (e.g. GAE credentials), use it to sign bytes.
    if (credentials instanceof ServiceAccountSigner) {
      return new ServiceAccountCryptoSigner((ServiceAccountSigner) credentials);
    }

    // Attempt to discover a service account email from the local Metadata service. Use it
    // with the IAM service to sign bytes.
    serviceAccountId = discoverServiceAccountId(firebaseApp);
    return new IAMCryptoSigner(requestFactory, jsonFactory, serviceAccountId);
  }

  private static String discoverServiceAccountId(FirebaseApp firebaseApp) throws IOException {
    HttpRequestFactory metadataRequestFactory =
        ApiClientUtils.newUnauthorizedRequestFactory(firebaseApp);
    HttpRequest request = metadataRequestFactory.buildGetRequest(
        new GenericUrl(METADATA_SERVICE_URL));
    request.getHeaders().set("Metadata-Flavor", "Google");
    HttpResponse response = request.execute();
    try {
      byte[] output = ByteStreams.toByteArray(response.getContent());
      return StringUtils.newStringUtf8(output).trim();
    } finally {
      ApiClientUtils.disconnectQuietly(response);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy