Skip to content

Commit

Permalink
#7243 🎉 Source Shopify: implement OAuth Java part
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandr-shegeda committed Oct 21, 2021
1 parent 8d878b6 commit 57bea73
Show file tree
Hide file tree
Showing 20 changed files with 176 additions and 69 deletions.
3 changes: 3 additions & 0 deletions airbyte-api/src/main/openapi/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3256,6 +3256,9 @@ components:
redirectUrl:
description: The url to redirect to after getting the user consent
type: string
params:
type: object
additionalProperties: true
OAuthConsentRead:
type: object
required:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
"auth_method": {
"type": "string",
"const": "access_token",
"enum": ["access_token"],
"enum": [
"access_token"
],
"default": "access_token",
"order": 0
},
Expand Down
30 changes: 22 additions & 8 deletions airbyte-oauth/src/main/java/io/airbyte/oauth/BaseOAuthFlow.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,39 @@ public BaseOAuthFlow(final ConfigRepository configRepository, final HttpClient h
}

@Override
public String getSourceConsentUrl(final UUID workspaceId, final UUID sourceDefinitionId, final String redirectUrl)
public String getSourceConsentUrl(final UUID workspaceId,
final UUID sourceDefinitionId,
final String redirectUrl,
final Map<String, Object> params)
throws IOException, ConfigNotFoundException {
final JsonNode oAuthParamConfig = getSourceOAuthParamConfig(workspaceId, sourceDefinitionId);
return formatConsentUrl(sourceDefinitionId, getClientIdUnsafe(oAuthParamConfig), redirectUrl);
return formatConsentUrl(sourceDefinitionId, getClientIdUnsafe(oAuthParamConfig), redirectUrl,
params);
}

@Override
public String getDestinationConsentUrl(final UUID workspaceId, final UUID destinationDefinitionId, final String redirectUrl)
public String getDestinationConsentUrl(final UUID workspaceId,
final UUID destinationDefinitionId,
final String redirectUrl,
final Map<String, Object> params)
throws IOException, ConfigNotFoundException {
final JsonNode oAuthParamConfig = getDestinationOAuthParamConfig(workspaceId, destinationDefinitionId);
return formatConsentUrl(destinationDefinitionId, getClientIdUnsafe(oAuthParamConfig), redirectUrl);
final JsonNode oAuthParamConfig = getDestinationOAuthParamConfig(workspaceId,
destinationDefinitionId);
return formatConsentUrl(destinationDefinitionId, getClientIdUnsafe(oAuthParamConfig),
redirectUrl,
params);
}

/**
* Depending on the OAuth flow implementation, the URL to grant user's consent may differ,
* especially in the query parameters to be provided. This function should generate such consent URL
* accordingly.
* especially in the query parameters to be provided. This function should generate such consent
* URL accordingly.
*/
protected abstract String formatConsentUrl(UUID definitionId, String clientId, String redirectUrl) throws IOException;
protected abstract String formatConsentUrl(UUID definitionId,
String clientId,
String redirectUrl,
Map<String, Object> params)
throws IOException;

private static String generateRandomState() {
return RandomStringUtils.randomAlphanumeric(7);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,24 @@

public interface OAuthFlowImplementation {

String getSourceConsentUrl(UUID workspaceId, UUID sourceDefinitionId, String redirectUrl) throws IOException, ConfigNotFoundException;
String getSourceConsentUrl(UUID workspaceId,
UUID sourceDefinitionId,
String redirectUrl,
Map<String, Object> params)
throws IOException, ConfigNotFoundException;

String getDestinationConsentUrl(UUID workspaceId, UUID destinationDefinitionId, String redirectUrl) throws IOException, ConfigNotFoundException;
String getDestinationConsentUrl(UUID workspaceId,
UUID destinationDefinitionId,
String redirectUrl,
Map<String, Object> params)
throws IOException, ConfigNotFoundException;

Map<String, Object> completeSourceOAuth(UUID workspaceId, UUID sourceDefinitionId, Map<String, Object> queryParams, String redirectUrl)
Map<String, Object> completeSourceOAuth(UUID workspaceId, UUID sourceDefinitionId,
Map<String, Object> queryParams, String redirectUrl)
throws IOException, ConfigNotFoundException;

Map<String, Object> completeDestinationOAuth(UUID workspaceId, UUID destinationDefinitionId, Map<String, Object> queryParams, String redirectUrl)
Map<String, Object> completeDestinationOAuth(UUID workspaceId, UUID destinationDefinitionId,
Map<String, Object> queryParams, String redirectUrl)
throws IOException, ConfigNotFoundException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ public AsanaOAuthFlow(ConfigRepository configRepository) {
}

@Override
protected String formatConsentUrl(UUID definitionId, String clientId, String redirectUrl) throws IOException {
protected String formatConsentUrl(UUID definitionId,
String clientId,
String redirectUrl,
Map<String, Object> params)
throws IOException {
try {
return new URIBuilder(AUTHORIZE_URL)
.addParameter("client_id", clientId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ public FacebookMarketingOAuthFlow(final ConfigRepository configRepository) {
}

@Override
protected String formatConsentUrl(final UUID definitionId, final String clientId, final String redirectUrl) throws IOException {
protected String formatConsentUrl(final UUID definitionId,
final String clientId,
final String redirectUrl,
Map<String, Object> params)
throws IOException {
final URIBuilder builder = new URIBuilder()
.setScheme("https")
.setHost("www.facebook.com")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,22 @@ public void setAuthPrefix(String authPrefix) {
}

@VisibleForTesting
ShopifyOAuthFlow(ConfigRepository configRepository, HttpClient httpClient,
ShopifyOAuthFlow(ConfigRepository configRepository,
HttpClient httpClient,
Supplier<String> stateSupplier) {
super(configRepository, httpClient, stateSupplier);
}

@Override
protected String formatConsentUrl(UUID definitionId, String clientId, String redirectUrl)
protected String formatConsentUrl(UUID definitionId,
String clientId,
String redirectUrl,
Map<String, Object> params)
throws IOException {
if (params != null && params.containsKey("shopDomain")) {
authPrefix = String.valueOf(params.get("shopDomain"));
}

String host = authPrefix + ".myshopify.com";
final URIBuilder builder = new URIBuilder()
.setScheme("https")
Expand Down Expand Up @@ -110,8 +118,10 @@ protected String getAccessTokenUrl() {
}

@Override
protected Map<String, String> getAccessTokenQueryParameters(String clientId, String clientSecret,
String authCode, String redirectUrl) {
protected Map<String, String> getAccessTokenQueryParameters(String clientId,
String clientSecret,
String authCode,
String redirectUrl) {
return ImmutableMap.<String, String>builder()
.put("client_id", clientId)
.put("client_secret", clientSecret)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,22 +49,31 @@ public TrelloOAuthFlow(final ConfigRepository configRepository, final HttpTransp
this.transport = transport;
}

public String getSourceConsentUrl(final UUID workspaceId, final UUID sourceDefinitionId, final String redirectUrl)
public String getSourceConsentUrl(final UUID workspaceId,
final UUID sourceDefinitionId,
final String redirectUrl,
Map<String, Object> params)
throws IOException, ConfigNotFoundException {
final JsonNode oAuthParamConfig = getSourceOAuthParamConfig(workspaceId, sourceDefinitionId);
return getConsentUrl(oAuthParamConfig, redirectUrl);
}

public String getDestinationConsentUrl(final UUID workspaceId, final UUID destinationDefinitionId, final String redirectUrl)
public String getDestinationConsentUrl(final UUID workspaceId,
final UUID destinationDefinitionId,
final String redirectUrl,
Map<String, Object> params)
throws IOException, ConfigNotFoundException {
final JsonNode oAuthParamConfig = getDestinationOAuthParamConfig(workspaceId, destinationDefinitionId);
final JsonNode oAuthParamConfig = getDestinationOAuthParamConfig(workspaceId,
destinationDefinitionId);
return getConsentUrl(oAuthParamConfig, redirectUrl);
}

private String getConsentUrl(final JsonNode oAuthParamConfig, final String redirectUrl) throws IOException, ConfigNotFoundException {
private String getConsentUrl(final JsonNode oAuthParamConfig, final String redirectUrl)
throws IOException, ConfigNotFoundException {
final String clientKey = getClientIdUnsafe(oAuthParamConfig);
final String clientSecret = getClientSecretUnsafe(oAuthParamConfig);
final OAuthGetTemporaryToken oAuthGetTemporaryToken = new OAuthGetTemporaryToken(REQUEST_TOKEN_URL);
final OAuthGetTemporaryToken oAuthGetTemporaryToken = new OAuthGetTemporaryToken(
REQUEST_TOKEN_URL);
signer.clientSharedSecret = clientSecret;
signer.tokenSharedSecret = null;
oAuthGetTemporaryToken.signer = signer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ public GoogleOAuthFlow(final ConfigRepository configRepository) {
}

@Override
protected String formatConsentUrl(final UUID definitionId, final String clientId, final String redirectUrl) throws IOException {
protected String formatConsentUrl(final UUID definitionId,
final String clientId,
final String redirectUrl,
Map<String, Object> params)
throws IOException {
final URIBuilder builder = new URIBuilder()
.setScheme("https")
.setHost("accounts.google.com")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package io.airbyte.oauth.flows;

import static java.util.Collections.emptyMap;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -81,7 +82,8 @@ public void testFullAsanaOAuthFlow() throws InterruptedException, ConfigNotFound
.put("client_id", clientId)
.put("client_secret", credentialsJson.get("client_secret").asText())
.build()))));
final String url = asanaOAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL);
final String url = asanaOAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL,
emptyMap());
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
// access...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package io.airbyte.oauth.flows;

import static java.util.Collections.emptyMap;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -80,7 +81,9 @@ public void testFullGoogleOAuthFlow() throws InterruptedException, ConfigNotFoun
.put("client_id", credentialsJson.get("client_id").asText())
.put("client_secret", credentialsJson.get("client_secret").asText())
.build()))));
final String url = facebookMarketingOAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL);
final String url = facebookMarketingOAuthFlow.getSourceConsentUrl(workspaceId, definitionId,
REDIRECT_URL,
emptyMap());
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
// access...
Expand All @@ -89,7 +92,8 @@ public void testFullGoogleOAuthFlow() throws InterruptedException, ConfigNotFoun
limit -= 1;
}
assertTrue(serverHandler.isSucceeded(), "Failed to get User consent on time");
final Map<String, Object> params = facebookMarketingOAuthFlow.completeSourceOAuth(workspaceId, definitionId,
final Map<String, Object> params = facebookMarketingOAuthFlow.completeSourceOAuth(workspaceId,
definitionId,
Map.of("code", serverHandler.getParamValue()), REDIRECT_URL);
LOGGER.info("Response from completing OAuth Flow is: {}", params.toString());
assertTrue(params.containsKey("access_token"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package io.airbyte.oauth.flows;

import static java.util.Collections.emptyMap;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -81,7 +82,8 @@ public void testFullGoogleOAuthFlow() throws InterruptedException, ConfigNotFoun
.put("client_id", clientId)
.put("client_secret", credentialsJson.get("client_secret").asText())
.build()))));
final String url = trelloOAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL);
final String url = trelloOAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL,
emptyMap());
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
// access...
Expand All @@ -90,8 +92,10 @@ public void testFullGoogleOAuthFlow() throws InterruptedException, ConfigNotFoun
limit -= 1;
}
assertTrue(serverHandler.isSucceeded(), "Failed to get User consent on time");
final Map<String, Object> params = trelloOAuthFlow.completeSourceOAuth(workspaceId, definitionId,
Map.of("oauth_verifier", serverHandler.getParamValue(), "oauth_token", serverHandler.getResponseQuery().get("oauth_token")), REDIRECT_URL);
final Map<String, Object> params = trelloOAuthFlow.completeSourceOAuth(workspaceId,
definitionId,
Map.of("oauth_verifier", serverHandler.getParamValue(), "oauth_token",
serverHandler.getResponseQuery().get("oauth_token")), REDIRECT_URL);
LOGGER.info("Response from completing OAuth Flow is: {}", params.toString());
assertTrue(params.containsKey("token"));
assertTrue(params.containsKey("key"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package io.airbyte.oauth.flows.google;

import static java.util.Collections.emptyMap;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -80,7 +81,9 @@ public void testFullGoogleOAuthFlow() throws InterruptedException, ConfigNotFoun
.put("client_id", credentialsJson.get("credentials").get("client_id").asText())
.put("client_secret", credentialsJson.get("credentials").get("client_secret").asText())
.build())))));
final String url = googleAdsOAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL);
final String url = googleAdsOAuthFlow.getSourceConsentUrl(workspaceId, definitionId,
REDIRECT_URL,
emptyMap());
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
// access...
Expand All @@ -89,7 +92,8 @@ public void testFullGoogleOAuthFlow() throws InterruptedException, ConfigNotFoun
limit -= 1;
}
assertTrue(serverHandler.isSucceeded(), "Failed to get User consent on time");
final Map<String, Object> params = googleAdsOAuthFlow.completeSourceOAuth(workspaceId, definitionId,
final Map<String, Object> params = googleAdsOAuthFlow.completeSourceOAuth(workspaceId,
definitionId,
Map.of("code", serverHandler.getParamValue()), REDIRECT_URL);
LOGGER.info("Response from completing OAuth Flow is: {}", params.toString());
assertTrue(params.containsKey("credentials"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package io.airbyte.oauth.flows.google;

import static java.util.Collections.emptyMap;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -80,7 +81,9 @@ public void testFullGoogleOAuthFlow() throws InterruptedException, ConfigNotFoun
.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);
final String url = googleAnalyticsOAuthFlow.getSourceConsentUrl(workspaceId, definitionId,
REDIRECT_URL,
emptyMap());
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
// access...
Expand All @@ -89,7 +92,8 @@ public void testFullGoogleOAuthFlow() throws InterruptedException, ConfigNotFoun
limit -= 1;
}
assertTrue(serverHandler.isSucceeded(), "Failed to get User consent on time");
final Map<String, Object> params = googleAnalyticsOAuthFlow.completeSourceOAuth(workspaceId, definitionId,
final Map<String, Object> 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("credentials"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package io.airbyte.oauth.flows.google;

import static java.util.Collections.emptyMap;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -78,9 +79,12 @@ public void testFullGoogleOAuthFlow() throws InterruptedException, ConfigNotFoun
.withWorkspaceId(workspaceId)
.withConfiguration(Jsons.jsonNode(Map.of("authorization", ImmutableMap.builder()
.put("client_id", credentialsJson.get("authorization").get("client_id").asText())
.put("client_secret", credentialsJson.get("authorization").get("client_secret").asText())
.put("client_secret",
credentialsJson.get("authorization").get("client_secret").asText())
.build())))));
final String url = googleSearchConsoleOAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL);
final String url = googleSearchConsoleOAuthFlow.getSourceConsentUrl(workspaceId, definitionId,
REDIRECT_URL,
emptyMap());
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
// access...
Expand All @@ -89,7 +93,8 @@ public void testFullGoogleOAuthFlow() throws InterruptedException, ConfigNotFoun
limit -= 1;
}
assertTrue(serverHandler.isSucceeded(), "Failed to get User consent on time");
final Map<String, Object> params = googleSearchConsoleOAuthFlow.completeSourceOAuth(workspaceId, definitionId,
final Map<String, Object> params = googleSearchConsoleOAuthFlow.completeSourceOAuth(workspaceId,
definitionId,
Map.of("code", serverHandler.getParamValue()), REDIRECT_URL);
LOGGER.info("Response from completing OAuth Flow is: {}", params.toString());
assertTrue(params.containsKey("authorization"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package io.airbyte.oauth.flows;

import static java.util.Collections.emptyMap;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
Expand Down Expand Up @@ -59,7 +60,7 @@ public void setup() throws IOException, JsonValidationException {
@Test
public void testGetSourceConcentUrl() throws IOException, InterruptedException, ConfigNotFoundException {
final String concentUrl =
asanaoAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL);
asanaoAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, emptyMap());
assertEquals(concentUrl,
"https://app.asana.com/-/oauth_authorize?client_id=test_client_id&redirect_uri=https%3A%2F%2Fairbyte.io&response_type=code&state=state");
}
Expand Down
Loading

0 comments on commit 57bea73

Please sign in to comment.