-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Dmytro
authored
Oct 21, 2021
1 parent
a35f93f
commit b230bc5
Showing
10 changed files
with
267 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
airbyte-oauth/src/main/java/io/airbyte/oauth/flows/GithubOAuthFlow.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* | ||
* Copyright (c) 2021 Airbyte, Inc., all rights reserved. | ||
*/ | ||
|
||
package io.airbyte.oauth.flows; | ||
|
||
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 io.airbyte.oauth.BaseOAuthFlow; | ||
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://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#web-application-flow | ||
*/ | ||
public class GithubOAuthFlow extends BaseOAuthFlow { | ||
|
||
private static final String AUTHORIZE_URL = "https://github.com/login/oauth/authorize"; | ||
private static final String ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token"; | ||
|
||
public GithubOAuthFlow(final ConfigRepository configRepository) { | ||
super(configRepository); | ||
} | ||
|
||
@VisibleForTesting | ||
GithubOAuthFlow(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) throws IOException { | ||
try { | ||
// No scope means read-only access to public information | ||
// https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes | ||
return new URIBuilder(AUTHORIZE_URL) | ||
.addParameter("client_id", clientId) | ||
.addParameter("redirect_uri", redirectUrl) | ||
.addParameter("state", getState()) | ||
.build().toString(); | ||
} catch (URISyntaxException e) { | ||
throw new IOException("Failed to format Consent URL for OAuth flow", e); | ||
} | ||
} | ||
|
||
@Override | ||
protected String getAccessTokenUrl() { | ||
return ACCESS_TOKEN_URL; | ||
} | ||
|
||
@Override | ||
protected Map<String, Object> extractRefreshToken(final JsonNode data, String accessTokenUrl) throws IOException { | ||
System.out.println(data); | ||
if (data.has("access_token")) { | ||
return Map.of("credentials", Map.of("access_token", data.get("access_token").asText())); | ||
} else { | ||
throw new IOException(String.format("Missing 'access_token' in query params from %s", ACCESS_TOKEN_URL)); | ||
} | ||
} | ||
|
||
@Override | ||
protected String getClientIdUnsafe(final 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(final 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")); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
...auth/src/test-integration/java/io.airbyte.oauth.flows/GithubOAuthFlowIntegrationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/* | ||
* Copyright (c) 2021 Airbyte, Inc., all rights reserved. | ||
*/ | ||
|
||
package io.airbyte.oauth.flows; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
import static org.mockito.Mockito.when; | ||
|
||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.google.common.collect.ImmutableMap; | ||
import io.airbyte.commons.json.Jsons; | ||
import io.airbyte.config.SourceOAuthParameter; | ||
import io.airbyte.config.persistence.ConfigNotFoundException; | ||
import io.airbyte.config.persistence.ConfigRepository; | ||
import io.airbyte.oauth.OAuthFlowImplementation; | ||
import io.airbyte.validation.json.JsonValidationException; | ||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.UUID; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
|
||
public class GithubOAuthFlowIntegrationTest extends OAuthFlowIntegrationTest { | ||
|
||
protected static final Path CREDENTIALS_PATH = Path.of("secrets/github.json"); | ||
protected static final String REDIRECT_URL = "http://localhost:8000/auth_flow"; | ||
protected static final int SERVER_LISTENING_PORT = 8000; | ||
|
||
@Override | ||
protected Path get_credentials_path() { | ||
return CREDENTIALS_PATH; | ||
} | ||
|
||
@Override | ||
protected OAuthFlowImplementation getFlowObject(ConfigRepository configRepository) { | ||
return new GithubOAuthFlow(configRepository); | ||
} | ||
|
||
@Override | ||
protected int getServerListeningPort() { | ||
return SERVER_LISTENING_PORT; | ||
} | ||
|
||
@BeforeEach | ||
public void setup() throws IOException { | ||
super.setup(); | ||
} | ||
|
||
@Test | ||
public void testFullGithubOAuthFlow() throws InterruptedException, ConfigNotFoundException, IOException, JsonValidationException { | ||
int limit = 20; | ||
final UUID workspaceId = UUID.randomUUID(); | ||
final UUID definitionId = UUID.randomUUID(); | ||
final String fullConfigAsString = new String(Files.readAllBytes(CREDENTIALS_PATH)); | ||
final JsonNode credentialsJson = Jsons.deserialize(fullConfigAsString); | ||
when(configRepository.listSourceOAuthParam()).thenReturn(List.of(new SourceOAuthParameter() | ||
.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())))); | ||
final String url = flow.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 | ||
// access... | ||
while (!serverHandler.isSucceeded() && limit > 0) { | ||
Thread.sleep(1000); | ||
limit -= 1; | ||
} | ||
assertTrue(serverHandler.isSucceeded(), "Failed to get User consent on time"); | ||
final Map<String, Object> params = flow.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")); | ||
assertTrue(params.get("access_token").toString().length() > 0); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
airbyte-oauth/src/test/java/io/airbyte/oauth/flows/GithubOAuthFlowTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* | ||
* Copyright (c) 2021 Airbyte, Inc., all rights reserved. | ||
*/ | ||
|
||
package io.airbyte.oauth.flows; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.when; | ||
|
||
import com.google.common.collect.ImmutableMap; | ||
import io.airbyte.commons.json.Jsons; | ||
import io.airbyte.config.SourceOAuthParameter; | ||
import io.airbyte.config.persistence.ConfigNotFoundException; | ||
import io.airbyte.config.persistence.ConfigRepository; | ||
import io.airbyte.validation.json.JsonValidationException; | ||
import java.io.IOException; | ||
import java.net.http.HttpClient; | ||
import java.net.http.HttpResponse; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.UUID; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
|
||
public class GithubOAuthFlowTest { | ||
|
||
private UUID workspaceId; | ||
private UUID definitionId; | ||
private ConfigRepository configRepository; | ||
private GithubOAuthFlow githuboAuthFlow; | ||
private HttpClient httpClient; | ||
|
||
private static final String REDIRECT_URL = "https://airbyte.io"; | ||
|
||
private static String getConstantState() { | ||
return "state"; | ||
} | ||
|
||
@BeforeEach | ||
public void setup() throws IOException, JsonValidationException { | ||
workspaceId = UUID.randomUUID(); | ||
definitionId = UUID.randomUUID(); | ||
configRepository = mock(ConfigRepository.class); | ||
httpClient = mock(HttpClient.class); | ||
when(configRepository.listSourceOAuthParam()).thenReturn(List.of(new SourceOAuthParameter() | ||
.withOauthParameterId(UUID.randomUUID()) | ||
.withSourceDefinitionId(definitionId) | ||
.withWorkspaceId(workspaceId) | ||
.withConfiguration(Jsons.jsonNode( | ||
Map.of("credentials", | ||
ImmutableMap.builder() | ||
.put("client_id", "test_client_id") | ||
.put("client_secret", "test_client_secret") | ||
.build()))))); | ||
githuboAuthFlow = new GithubOAuthFlow(configRepository, httpClient, GithubOAuthFlowTest::getConstantState); | ||
|
||
} | ||
|
||
@Test | ||
public void testGetSourceConcentUrl() throws IOException, InterruptedException, ConfigNotFoundException { | ||
final String concentUrl = | ||
githuboAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL); | ||
assertEquals(concentUrl, | ||
"https://github.com/login/oauth/authorize?client_id=test_client_id&redirect_uri=https%3A%2F%2Fairbyte.io&state=state"); | ||
} | ||
|
||
@Test | ||
public void testCompleteSourceOAuth() throws IOException, JsonValidationException, InterruptedException, ConfigNotFoundException { | ||
|
||
Map<String, String> returnedCredentials = Map.of("access_token", "refresh_token_response"); | ||
final HttpResponse response = mock(HttpResponse.class); | ||
when(response.body()).thenReturn(Jsons.serialize(returnedCredentials)); | ||
when(httpClient.send(any(), any())).thenReturn(response); | ||
final Map<String, Object> queryParams = Map.of("code", "test_code"); | ||
final Map<String, Object> actualQueryParams = | ||
githuboAuthFlow.completeSourceOAuth(workspaceId, definitionId, queryParams, REDIRECT_URL); | ||
assertEquals(Jsons.serialize(Map.of("credentials", returnedCredentials)), Jsons.serialize(actualQueryParams)); | ||
} | ||
|
||
} |