code: /*
   * Copyright (c) 2022 Airbyte, Inc., all rights reserved.
   */
  
  package io.airbyte.oauth.flows.google;
  
  import com.fasterxml.jackson.databind.JsonNode;
  import com.google.common.annotations.VisibleForTesting;
  import com.google.common.collect.ImmutableMap;
  import io.airbyte.config.persistence.ConfigRepository;
  import io.airbyte.oauth.BaseOAuth2Flow;
  import java.io.IOException;
  import java.net.URISyntaxException;
  import java.net.http.HttpClient;
  import java.util.Map;
  import java.util.UUID;
  import java.util.function.Supplier;
  import org.apache.http.client.utils.URIBuilder;
  
  /**
   * Following docs from https://developers.google.com/identity/protocols/oauth2/web-server
   */
  public abstract class GoogleOAuthFlow extends BaseOAuth2Flow {
  
    private static final String ACCESS_TOKEN_URL = "https://oauth2.googleapis.com/token";
  
    public GoogleOAuthFlow(final ConfigRepository configRepository, final HttpClient httpClient) {
      super(configRepository, httpClient);
    }
  
    @VisibleForTesting
    GoogleOAuthFlow(final ConfigRepository configRepository, final HttpClient httpClient, final Supplier<String> stateSupplier) {
      super(configRepository, httpClient, stateSupplier);
    }
  
    @Override
    protected String formatConsentUrl(final UUID definitionId,
                                      final String clientId,
                                      final String redirectUrl,
                                      final JsonNode inputOAuthConfiguration)
        throws IOException {
      final URIBuilder builder = new URIBuilder()
          .setScheme("https")
          .setHost("accounts.google.com")
          .setPath("o/oauth2/v2/auth")
          .addParameter("client_id", clientId)
          .addParameter("redirect_uri", redirectUrl)
          .addParameter("response_type", "code")
          .addParameter("scope", getScope())
          // recommended
          .addParameter("access_type", "offline")
          .addParameter("state", getState())
          // optional
          .addParameter("include_granted_scopes", "true")
          // .addParameter("login_hint", "user_email")
          .addParameter("prompt", "consent");
      try {
        return builder.build().toString();
      } catch (final URISyntaxException e) {
        throw new IOException("Failed to format Consent URL for OAuth flow", e);
      }
    }
  
    /**
     * @return the scope for the specific google oauth implementation.
     */
    protected abstract String getScope();
  
    @Override
    protected String getAccessTokenUrl(final JsonNode inputOAuthConfiguration) {
      return ACCESS_TOKEN_URL;
    }
  
    @Override
    protected Map<String, String> getAccessTokenQueryParameters(final String clientId,
                                                                final String clientSecret,
                                                                final String authCode,
                                                                final String redirectUrl) {
      return ImmutableMap.<String, String>builder()
          .put("client_id", clientId)
          .put("client_secret", clientSecret)
          .put("code", authCode)
          .put("grant_type", "authorization_code")
          .put("redirect_uri", redirectUrl)
          .build();
    }
  
    @Override
    protected Map<String, Object> extractOAuthOutput(final JsonNode data, final String accessTokenUrl) throws IOException {
      final Map<String, Object> result = super.extractOAuthOutput(data, accessTokenUrl);
      if (data.has("access_token")) {
        // google also returns an access token the first time you complete oauth flow
        result.put("access_token", data.get("access_token").asText());
      }
      return result;
    }
  
  }
  