From 0475823026808d8d301320a42317dfac6cd3c6ef Mon Sep 17 00:00:00 2001 From: Christophe Duong Date: Tue, 21 Sep 2021 15:02:11 +0200 Subject: [PATCH] Update Google Analytics Oauth flow (#6321) * Change oauth implementation with nested auth_mechanism for Google Analytics v4 * Rename auth_mechanism to credentials --- .../google/GoogleAnalyticsOAuthFlow.java | 24 +++++++++++++ ...ogleAnalyticsOAuthFlowIntegrationTest.java | 18 +++++----- .../google/GoogleAnalyticsOAuthFlowTest.java | 34 +++++++++---------- 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/google/GoogleAnalyticsOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/google/GoogleAnalyticsOAuthFlow.java index 02d5a883fb41..b3b702565762 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/google/GoogleAnalyticsOAuthFlow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/google/GoogleAnalyticsOAuthFlow.java @@ -24,9 +24,13 @@ package io.airbyte.oauth.flows.google; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; import io.airbyte.config.persistence.ConfigRepository; +import java.io.IOException; import java.net.http.HttpClient; +import java.util.Map; import java.util.function.Supplier; public class GoogleAnalyticsOAuthFlow extends GoogleOAuthFlow { @@ -47,4 +51,24 @@ protected String getScope() { return SCOPE_URL; } + @Override + protected String getClientIdUnsafe(JsonNode config) { + // the config object containing client ID and secret is nested inside the "credentials" object + Preconditions.checkArgument(config.hasNonNull("credentials")); + return super.getClientIdUnsafe(config.get("credentials")); + } + + @Override + protected String getClientSecretUnsafe(JsonNode config) { + // the config object containing client ID and secret is nested inside the "credentials" object + Preconditions.checkArgument(config.hasNonNull("credentials")); + return super.getClientSecretUnsafe(config.get("credentials")); + } + + @Override + protected Map extractRefreshToken(JsonNode data) throws IOException { + // the config object containing refresh token is nested inside the "credentials" object + return Map.of("credentials", super.extractRefreshToken(data)); + } + } diff --git a/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleAnalyticsOAuthFlowIntegrationTest.java b/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleAnalyticsOAuthFlowIntegrationTest.java index 5735f3381ca7..28db741a0ca1 100644 --- a/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleAnalyticsOAuthFlowIntegrationTest.java +++ b/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleAnalyticsOAuthFlowIntegrationTest.java @@ -96,10 +96,10 @@ public void testFullGoogleOAuthFlow() throws InterruptedException, ConfigNotFoun .withOauthParameterId(UUID.randomUUID()) .withSourceDefinitionId(definitionId) .withWorkspaceId(workspaceId) - .withConfiguration(Jsons.jsonNode(ImmutableMap.builder() - .put("client_id", credentialsJson.get("client_id").asText()) - .put("client_secret", credentialsJson.get("client_secret").asText()) - .build())))); + .withConfiguration(Jsons.jsonNode(Map.of("credentials", ImmutableMap.builder() + .put("client_id", credentialsJson.get("credentials").get("client_id").asText()) + .put("client_secret", credentialsJson.get("credentials").get("client_secret").asText()) + .build()))))); final String url = googleAnalyticsOAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL); LOGGER.info("Waiting for user consent at: {}", url); // TODO: To automate, start a selenium job to navigate to the Consent URL and click on allowing @@ -112,10 +112,12 @@ public void testFullGoogleOAuthFlow() throws InterruptedException, ConfigNotFoun final Map params = googleAnalyticsOAuthFlow.completeSourceOAuth(workspaceId, definitionId, Map.of("code", serverHandler.getParamValue()), REDIRECT_URL); LOGGER.info("Response from completing OAuth Flow is: {}", params.toString()); - assertTrue(params.containsKey("refresh_token")); - assertTrue(params.get("refresh_token").toString().length() > 0); - assertTrue(params.containsKey("access_token")); - assertTrue(params.get("access_token").toString().length() > 0); + assertTrue(params.containsKey("credentials")); + final Map credentials = (Map) params.get("credentials"); + assertTrue(credentials.containsKey("refresh_token")); + assertTrue(credentials.get("refresh_token").toString().length() > 0); + assertTrue(credentials.containsKey("access_token")); + assertTrue(credentials.get("access_token").toString().length() > 0); } static class ServerHandler implements HttpHandler { diff --git a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/google/GoogleAnalyticsOAuthFlowTest.java b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/google/GoogleAnalyticsOAuthFlowTest.java index d39197f037f7..0fc09e044b7c 100644 --- a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/google/GoogleAnalyticsOAuthFlowTest.java +++ b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/google/GoogleAnalyticsOAuthFlowTest.java @@ -108,9 +108,9 @@ public void testGetSourceConsentUrl() throws IOException, ConfigNotFoundExceptio .withOauthParameterId(UUID.randomUUID()) .withSourceDefinitionId(definitionId) .withWorkspaceId(workspaceId) - .withConfiguration(Jsons.jsonNode(ImmutableMap.builder() + .withConfiguration(Jsons.jsonNode(Map.of("credentials", ImmutableMap.builder() .put("client_id", getClientId()) - .build())))); + .build()))))); final String actualSourceUrl = googleAnalyticsOAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL); final String expectedSourceUrl = String.format( "https://accounts.google.com/o/oauth2/v2/auth?client_id=%s&redirect_uri=%s&response_type=code&scope=%s&access_type=offline&state=%s&include_granted_scopes=true&prompt=consent", @@ -128,9 +128,9 @@ public void testGetDestinationConsentUrl() throws IOException, ConfigNotFoundExc .withOauthParameterId(UUID.randomUUID()) .withDestinationDefinitionId(definitionId) .withWorkspaceId(workspaceId) - .withConfiguration(Jsons.jsonNode(ImmutableMap.builder() + .withConfiguration(Jsons.jsonNode(Map.of("credentials", ImmutableMap.builder() .put("client_id", getClientId()) - .build())))); + .build()))))); // It would be better to make this comparison agnostic of the order of query params but the URI // class' equals() method // considers URLs with different qparam orders different URIs.. @@ -151,10 +151,10 @@ public void testCompleteOAuthMissingCode() throws IOException, ConfigNotFoundExc .withOauthParameterId(UUID.randomUUID()) .withSourceDefinitionId(definitionId) .withWorkspaceId(workspaceId) - .withConfiguration(Jsons.jsonNode(ImmutableMap.builder() + .withConfiguration(Jsons.jsonNode(Map.of("credentials", ImmutableMap.builder() .put("client_id", getClientId()) .put("client_secret", "test_client_secret") - .build())))); + .build()))))); final Map queryParams = Map.of(); assertThrows(IOException.class, () -> googleAnalyticsOAuthFlow.completeSourceOAuth(workspaceId, definitionId, queryParams, REDIRECT_URL)); } @@ -165,17 +165,17 @@ public void testCompleteSourceOAuth() throws IOException, ConfigNotFoundExceptio .withOauthParameterId(UUID.randomUUID()) .withSourceDefinitionId(definitionId) .withWorkspaceId(workspaceId) - .withConfiguration(Jsons.jsonNode(ImmutableMap.builder() + .withConfiguration(Jsons.jsonNode(Map.of("credentials", ImmutableMap.builder() .put("client_id", getClientId()) .put("client_secret", "test_client_secret") - .build())))); - final String expectedQueryParams = Jsons.serialize(Map.of("refresh_token", "refresh_token_response")); + .build()))))); + Map returnedCredentials = Map.of("refresh_token", "refresh_token_response"); final HttpResponse response = mock(HttpResponse.class); - when(response.body()).thenReturn(expectedQueryParams); + when(response.body()).thenReturn(Jsons.serialize(returnedCredentials)); when(httpClient.send(any(), any())).thenReturn(response); final Map queryParams = Map.of("code", "test_code"); final Map actualQueryParams = googleAnalyticsOAuthFlow.completeSourceOAuth(workspaceId, definitionId, queryParams, REDIRECT_URL); - assertEquals(expectedQueryParams, Jsons.serialize(actualQueryParams)); + assertEquals(Jsons.serialize(Map.of("credentials", returnedCredentials)), Jsons.serialize(actualQueryParams)); } @Test @@ -184,18 +184,18 @@ public void testCompleteDestinationOAuth() throws IOException, ConfigNotFoundExc .withOauthParameterId(UUID.randomUUID()) .withDestinationDefinitionId(definitionId) .withWorkspaceId(workspaceId) - .withConfiguration(Jsons.jsonNode(ImmutableMap.builder() + .withConfiguration(Jsons.jsonNode(Map.of("credentials", ImmutableMap.builder() .put("client_id", getClientId()) .put("client_secret", "test_client_secret") - .build())))); - final String expectedQueryParams = Jsons.serialize(Map.of("refresh_token", "refresh_token_response")); + .build()))))); + Map returnedCredentials = Map.of("refresh_token", "refresh_token_response"); final HttpResponse response = mock(HttpResponse.class); - when(response.body()).thenReturn(expectedQueryParams); + when(response.body()).thenReturn(Jsons.serialize(returnedCredentials)); when(httpClient.send(any(), any())).thenReturn(response); final Map queryParams = Map.of("code", "test_code"); final Map actualQueryParams = googleAnalyticsOAuthFlow .completeDestinationOAuth(workspaceId, definitionId, queryParams, REDIRECT_URL); - assertEquals(expectedQueryParams, Jsons.serialize(actualQueryParams)); + assertEquals(Jsons.serialize(Map.of("credentials", returnedCredentials)), Jsons.serialize(actualQueryParams)); } private String getClientId() throws IOException { @@ -204,7 +204,7 @@ private String getClientId() throws IOException { } else { final String fullConfigAsString = new String(Files.readAllBytes(CREDENTIALS_PATH)); final JsonNode credentialsJson = Jsons.deserialize(fullConfigAsString); - return credentialsJson.get("client_id").asText(); + return credentialsJson.get("credentials").get("client_id").asText(); } }