From 83c19598c5efb9b9465023b2bc122fdeec875065 Mon Sep 17 00:00:00 2001 From: Christophe Duong Date: Wed, 17 Nov 2021 20:07:04 +0100 Subject: [PATCH] Implement connector config dependency for OAuth consent URL (#7983) * Refactor OAuth consent flow with new API * Use input connector configuration in getConsentURL for OAuth flow * Instance-wide params should only be injected if a connector will be using oauth (#8018) * Add test for nested parameters --- .../java/io/airbyte/commons/json/Jsons.java | 4 + .../java/io/airbyte/oauth/BaseOAuth2Flow.java | 52 +++- .../java/io/airbyte/oauth/BaseOAuthFlow.java | 58 +++-- .../io/airbyte/oauth/MoreOAuthParameters.java | 26 +- .../oauth/OAuthFlowImplementation.java | 19 +- .../airbyte/oauth/flows/AsanaOAuthFlow.java | 7 +- .../airbyte/oauth/flows/GithubOAuthFlow.java | 6 +- .../airbyte/oauth/flows/HubspotOAuthFlow.java | 16 +- .../oauth/flows/IntercomOAuthFlow.java | 6 +- .../oauth/flows/PipeDriveOAuthFlow.java | 7 +- .../oauth/flows/QuickbooksOAuthFlow.java | 7 +- .../oauth/flows/SalesforceOAuthFlow.java | 7 +- .../airbyte/oauth/flows/SlackOAuthFlow.java | 7 +- .../flows/SnapchatMarketingOAuthFlow.java | 7 +- .../oauth/flows/SurveymonkeyOAuthFlow.java | 6 +- .../airbyte/oauth/flows/TrelloOAuthFlow.java | 19 +- .../flows/facebook/FacebookOAuthFlow.java | 6 +- .../oauth/flows/google/GoogleOAuthFlow.java | 6 +- .../FacebookOAuthFlowIntegrationTest.java | 5 +- .../GithubOAuthFlowIntegrationTest.java | 5 +- .../IntercomOAuthFlowIntegrationTest.java | 5 +- .../PipeDriveOAuthFlowIntegrationTest.java | 4 +- .../QuickbooksOAuthFlowIntegrationTest.java | 5 +- .../SalesforceOAuthFlowIntegrationTest.java | 8 +- .../SlackOAuthFlowIntegrationTest.java | 5 +- ...chatMarketingOAuthFlowIntegrationTest.java | 6 +- .../SurveymonkeyOAuthFlowIntegrationTest.java | 5 +- .../TrelloOAuthFlowIntegrationTest.java | 2 +- .../HubspotOAuthFlowIntegrationTest.java | 2 +- .../GoogleAdsOAuthFlowIntegrationTest.java | 2 +- ...ogleAnalyticsOAuthFlowIntegrationTest.java | 2 +- ...SearchConsoleOAuthFlowIntegrationTest.java | 2 +- .../GoogleSheetsOAuthFlowIntegrationTest.java | 2 +- .../oauth/MoreOAuthParametersTest.java | 48 +--- .../oauth/flows/BaseOAuthFlowTest.java | 234 +++++++++++++----- .../oauth/flows/GithubOAuthFlowTest.java | 5 +- .../oauth/flows/IntercomOAuthFlowTest.java | 5 +- .../flows/SurveymonkeyOAuthFlowTest.java | 5 +- .../oauth/flows/TrelloOAuthFlowTest.java | 2 +- .../FacebookMarketingOAuthFlowTest.java | 5 +- .../facebook/FacebookPagesOAuthFlowTest.java | 5 +- .../facebook/InstagramOAuthFlowTest.java | 5 +- .../airbyte/scheduler/app/JobScheduler.java | 2 +- .../job_factory/OAuthConfigSupplier.java | 124 +++++++--- .../job_factory/OAuthConfigSupplierTest.java | 234 +++++++++++++----- .../java/io/airbyte/server/ServerApp.java | 2 +- .../airbyte/server/apis/ConfigurationApi.java | 2 +- .../airbyte/server/handlers/OAuthHandler.java | 48 +++- 48 files changed, 716 insertions(+), 336 deletions(-) diff --git a/airbyte-commons/src/main/java/io/airbyte/commons/json/Jsons.java b/airbyte-commons/src/main/java/io/airbyte/commons/json/Jsons.java index 970bbeb070f2a..64282d6e80fcd 100644 --- a/airbyte-commons/src/main/java/io/airbyte/commons/json/Jsons.java +++ b/airbyte-commons/src/main/java/io/airbyte/commons/json/Jsons.java @@ -141,6 +141,10 @@ public static JsonNode navigateTo(JsonNode node, final List keys) { return node; } + public static void replaceNestedValue(final JsonNode json, final List keys, final JsonNode replacement) { + replaceNested(json, keys, (node, finalKey) -> node.put(finalKey, replacement)); + } + public static void replaceNestedString(final JsonNode json, final List keys, final String replacement) { replaceNested(json, keys, (node, finalKey) -> node.put(finalKey, replacement)); } diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/BaseOAuth2Flow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/BaseOAuth2Flow.java index e132a8eea87d9..f5fde3bfa4793 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/BaseOAuth2Flow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/BaseOAuth2Flow.java @@ -12,6 +12,8 @@ import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.protocol.models.OAuthConfigSpecification; +import io.airbyte.validation.json.JsonSchemaValidator; +import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; import java.lang.reflect.Type; import java.net.URI; @@ -75,25 +77,45 @@ public BaseOAuth2Flow(final ConfigRepository configRepository, } @Override - public String getSourceConsentUrl(final UUID workspaceId, final UUID sourceDefinitionId, final String redirectUrl) - throws IOException, ConfigNotFoundException { + public String getSourceConsentUrl(final UUID workspaceId, + final UUID sourceDefinitionId, + final String redirectUrl, + final JsonNode inputOAuthConfiguration, + final OAuthConfigSpecification oAuthConfigSpecification) + throws IOException, ConfigNotFoundException, JsonValidationException { + validateInputOAuthConfiguration(oAuthConfigSpecification, inputOAuthConfiguration); final JsonNode oAuthParamConfig = getSourceOAuthParamConfig(workspaceId, sourceDefinitionId); - return formatConsentUrl(sourceDefinitionId, getClientIdUnsafe(oAuthParamConfig), redirectUrl); + return formatConsentUrl(sourceDefinitionId, getClientIdUnsafe(oAuthParamConfig), redirectUrl, inputOAuthConfiguration); } @Override - public String getDestinationConsentUrl(final UUID workspaceId, final UUID destinationDefinitionId, final String redirectUrl) - throws IOException, ConfigNotFoundException { + public String getDestinationConsentUrl(final UUID workspaceId, + final UUID destinationDefinitionId, + final String redirectUrl, + final JsonNode inputOAuthConfiguration, + final OAuthConfigSpecification oAuthConfigSpecification) + throws IOException, ConfigNotFoundException, JsonValidationException { + validateInputOAuthConfiguration(oAuthConfigSpecification, inputOAuthConfiguration); final JsonNode oAuthParamConfig = getDestinationOAuthParamConfig(workspaceId, destinationDefinitionId); - return formatConsentUrl(destinationDefinitionId, getClientIdUnsafe(oAuthParamConfig), redirectUrl); + return formatConsentUrl(destinationDefinitionId, getClientIdUnsafe(oAuthParamConfig), redirectUrl, inputOAuthConfiguration); } /** * 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. + * + * @param definitionId The configured definition ID of this client + * @param clientId The configured client ID + * @param redirectUrl the redirect URL + * @param inputOAuthConfiguration any configuration property from connector necessary for this OAuth + * Flow */ - protected abstract String formatConsentUrl(UUID definitionId, String clientId, String redirectUrl) throws IOException; + protected abstract String formatConsentUrl(final UUID definitionId, + final String clientId, + final String redirectUrl, + final JsonNode inputOAuthConfiguration) + throws IOException; private static String generateRandomState() { return RandomStringUtils.randomAlphanumeric(7); @@ -151,8 +173,9 @@ public Map completeSourceOAuth(final UUID workspaceId, final String redirectUrl, final JsonNode inputOAuthConfiguration, final OAuthConfigSpecification oAuthConfigSpecification) - throws IOException, ConfigNotFoundException { - final JsonNode oAuthParamConfig = getDestinationOAuthParamConfig(workspaceId, sourceDefinitionId); + throws IOException, ConfigNotFoundException, JsonValidationException { + validateInputOAuthConfiguration(oAuthConfigSpecification, inputOAuthConfiguration); + final JsonNode oAuthParamConfig = getSourceOAuthParamConfig(workspaceId, sourceDefinitionId); return formatOAuthOutput( oAuthParamConfig, completeOAuthFlow( @@ -171,7 +194,8 @@ public Map completeDestinationOAuth(final UUID workspaceId, final String redirectUrl, final JsonNode inputOAuthConfiguration, final OAuthConfigSpecification oAuthConfigSpecification) - throws IOException, ConfigNotFoundException { + throws IOException, ConfigNotFoundException, JsonValidationException { + validateInputOAuthConfiguration(oAuthConfigSpecification, inputOAuthConfiguration); final JsonNode oAuthParamConfig = getDestinationOAuthParamConfig(workspaceId, destinationDefinitionId); return formatOAuthOutput( oAuthParamConfig, @@ -260,6 +284,14 @@ public List getDefaultOAuthOutputPath() { return List.of("credentials"); } + private static void validateInputOAuthConfiguration(final OAuthConfigSpecification oauthConfigSpecification, final JsonNode inputOAuthConfiguration) + throws JsonValidationException { + if (oauthConfigSpecification != null && oauthConfigSpecification.getOauthUserInputFromConnectorConfigSpecification() != null) { + final JsonSchemaValidator validator = new JsonSchemaValidator(); + validator.ensure(oauthConfigSpecification.getOauthUserInputFromConnectorConfigSpecification(), inputOAuthConfiguration); + } + } + private static String urlEncode(final String s) { try { return URLEncoder.encode(s, StandardCharsets.UTF_8); diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/BaseOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/BaseOAuthFlow.java index f0f2aa17a21cc..8aefa2359c204 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/BaseOAuthFlow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/BaseOAuthFlow.java @@ -8,19 +8,23 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.map.MoreMaps; import io.airbyte.config.ConfigSchema; import io.airbyte.config.DestinationOAuthParameter; import io.airbyte.config.SourceOAuthParameter; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.protocol.models.OAuthConfigSpecification; +import io.airbyte.validation.json.JsonSchemaValidator; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.function.BiConsumer; /** * Abstract Class implementing common base methods for managing oAuth config (instance-wide) and @@ -28,6 +32,7 @@ */ public abstract class BaseOAuthFlow implements OAuthFlowImplementation { + public static final String PROPERTIES = "properties"; private final ConfigRepository configRepository; public BaseOAuthFlow(final ConfigRepository configRepository) { @@ -56,7 +61,7 @@ protected JsonNode getDestinationOAuthParamConfig(final UUID workspaceId, final final Optional param = MoreOAuthParameters.getDestinationOAuthParameter( configRepository.listDestinationOAuthParam().stream(), workspaceId, destinationDefinitionId); if (param.isPresent()) { - // TODO: if we write a flyway migration to flatten persisted configs in db, we don't need to flatten + // TODO: if we write a migration to flatten persisted configs in db, we don't need to flatten // here see https://github.com/airbytehq/airbyte/issues/7624 return MoreOAuthParameters.flattenOAuthConfig(param.get().getConfiguration()); } else { @@ -107,7 +112,6 @@ protected Map formatOAuthOutput(final JsonNode oAuthParamConfig, final Map oauthOutput, final List outputPath) { Map result = new HashMap<>(oauthOutput); - // inject masked params outputs for (final String key : Jsons.keys(oAuthParamConfig)) { result.put(key, MoreOAuthParameters.SECRET_MASK); } @@ -124,21 +128,45 @@ protected Map formatOAuthOutput(final JsonNode oAuthParamConfig, */ protected Map formatOAuthOutput(final JsonNode oAuthParamConfig, final Map completeOAuthFlow, - final OAuthConfigSpecification oAuthConfigSpecification) { - final Builder outputs = ImmutableMap.builder(); - // inject masked params outputs - for (final String key : Jsons.keys(oAuthParamConfig)) { - if (oAuthConfigSpecification.getCompleteOauthServerOutputSpecification().has(key)) { - outputs.put(key, MoreOAuthParameters.SECRET_MASK); - } - } - // collect oauth result outputs - for (final String key : completeOAuthFlow.keySet()) { - if (oAuthConfigSpecification.getCompleteOauthOutputSpecification().has(key)) { - outputs.put(key, completeOAuthFlow.get(key)); + final OAuthConfigSpecification oAuthConfigSpecification) + throws JsonValidationException { + final JsonSchemaValidator validator = new JsonSchemaValidator(); + + final Map oAuthOutputs = formatOAuthOutput( + validator, + oAuthConfigSpecification.getCompleteOauthOutputSpecification(), + completeOAuthFlow.keySet(), + (resultMap, key) -> resultMap.put(key, completeOAuthFlow.get(key))); + + final Map oAuthServerOutputs = formatOAuthOutput( + validator, + oAuthConfigSpecification.getCompleteOauthServerOutputSpecification(), + Jsons.keys(oAuthParamConfig), + // TODO secrets should be masked with the correct type + // https://github.com/airbytehq/airbyte/issues/5990 + // In the short-term this is not world-ending as all secret fields are currently strings + (resultMap, key) -> resultMap.put(key, MoreOAuthParameters.SECRET_MASK)); + + return MoreMaps.merge(oAuthServerOutputs, oAuthOutputs); + } + + private static Map formatOAuthOutput(final JsonSchemaValidator validator, + final JsonNode outputSchema, + final Collection keys, + final BiConsumer, String> replacement) + throws JsonValidationException { + Map result = Map.of(); + if (outputSchema != null && outputSchema.has(PROPERTIES)) { + final Builder mapBuilder = ImmutableMap.builder(); + for (final String key : keys) { + if (outputSchema.get(PROPERTIES).has(key)) { + replacement.accept(mapBuilder, key); + } } + result = mapBuilder.build(); + validator.ensure(outputSchema, Jsons.jsonNode(result)); } - return outputs.build(); + return result; } /** diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/MoreOAuthParameters.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/MoreOAuthParameters.java index 60ed411008825..acab263909bab 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/MoreOAuthParameters.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/MoreOAuthParameters.java @@ -74,42 +74,26 @@ private static ObjectNode flattenOAuthConfig(final ObjectNode flatConfig, final } public static JsonNode mergeJsons(final ObjectNode mainConfig, final ObjectNode fromConfig) { - return mergeJsons(mainConfig, fromConfig, null); - } - - public static JsonNode mergeJsons(final ObjectNode mainConfig, final ObjectNode fromConfig, final JsonNode maskedValue) { for (final String key : Jsons.keys(fromConfig)) { if (fromConfig.get(key).getNodeType() == OBJECT) { // nested objects are merged rather than overwrite the contents of the equivalent object in config if (mainConfig.get(key) == null) { - mergeJsons(mainConfig.putObject(key), (ObjectNode) fromConfig.get(key), maskedValue); + mergeJsons(mainConfig.putObject(key), (ObjectNode) fromConfig.get(key)); } else if (mainConfig.get(key).getNodeType() == OBJECT) { - mergeJsons((ObjectNode) mainConfig.get(key), (ObjectNode) fromConfig.get(key), maskedValue); + mergeJsons((ObjectNode) mainConfig.get(key), (ObjectNode) fromConfig.get(key)); } else { throw new IllegalStateException("Can't merge an object node into a non-object node!"); } } else { - if (maskedValue != null && !maskedValue.isNull()) { - LOGGER.debug(String.format("Masking instance wide parameter %s in config", key)); - mainConfig.set(key, maskedValue); - } else { - if (!mainConfig.has(key) || isSecretMask(mainConfig.get(key).asText())) { - LOGGER.debug(String.format("injecting instance wide parameter %s into config", key)); - mainConfig.set(key, fromConfig.get(key)); - } + if (!mainConfig.has(key) || isSecretMask(mainConfig.get(key).asText())) { + LOGGER.debug(String.format("injecting instance wide parameter %s into config", key)); + mainConfig.set(key, fromConfig.get(key)); } } } return mainConfig; } - public static JsonNode getSecretMask() { - // TODO secrets should be masked with the correct type - // https://github.com/airbytehq/airbyte/issues/5990 - // In the short-term this is not world-ending as all secret fields are currently strings - return Jsons.jsonNode(SECRET_MASK); - } - private static boolean isSecretMask(final String input) { return Strings.isNullOrEmpty(input.replaceAll("\\*", "")); } diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/OAuthFlowImplementation.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/OAuthFlowImplementation.java index ec03f0109f283..b64f25b934b21 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/OAuthFlowImplementation.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/OAuthFlowImplementation.java @@ -7,15 +7,26 @@ import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.protocol.models.OAuthConfigSpecification; +import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; import java.util.Map; import java.util.UUID; public interface OAuthFlowImplementation { - String getSourceConsentUrl(UUID workspaceId, UUID sourceDefinitionId, String redirectUrl) throws IOException, ConfigNotFoundException; + String getSourceConsentUrl(UUID workspaceId, + UUID sourceDefinitionId, + String redirectUrl, + JsonNode inputOAuthConfiguration, + OAuthConfigSpecification oauthConfigSpecification) + throws IOException, ConfigNotFoundException, JsonValidationException; - String getDestinationConsentUrl(UUID workspaceId, UUID destinationDefinitionId, String redirectUrl) throws IOException, ConfigNotFoundException; + String getDestinationConsentUrl(UUID workspaceId, + UUID destinationDefinitionId, + String redirectUrl, + JsonNode inputOAuthConfiguration, + OAuthConfigSpecification oauthConfigSpecification) + throws IOException, ConfigNotFoundException, JsonValidationException; @Deprecated Map completeSourceOAuth(UUID workspaceId, UUID sourceDefinitionId, Map queryParams, String redirectUrl) @@ -27,7 +38,7 @@ Map completeSourceOAuth(UUID workspaceId, String redirectUrl, JsonNode inputOAuthConfiguration, OAuthConfigSpecification oauthConfigSpecification) - throws IOException, ConfigNotFoundException; + throws IOException, ConfigNotFoundException, JsonValidationException; @Deprecated Map completeDestinationOAuth(UUID workspaceId, UUID destinationDefinitionId, Map queryParams, String redirectUrl) @@ -39,6 +50,6 @@ Map completeDestinationOAuth(UUID workspaceId, String redirectUrl, JsonNode inputOAuthConfiguration, OAuthConfigSpecification oAuthConfigSpecification) - throws IOException, ConfigNotFoundException; + throws IOException, ConfigNotFoundException, JsonValidationException; } diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/AsanaOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/AsanaOAuthFlow.java index a4a0f6f7ab903..590b9b3e14aa2 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/AsanaOAuthFlow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/AsanaOAuthFlow.java @@ -4,6 +4,7 @@ package io.airbyte.oauth.flows; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import io.airbyte.config.persistence.ConfigRepository; @@ -34,7 +35,11 @@ public AsanaOAuthFlow(final ConfigRepository configRepository, final HttpClient } @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, + final JsonNode inputOAuthConfiguration) + throws IOException { try { return new URIBuilder(AUTHORIZE_URL) .addParameter("client_id", clientId) diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/GithubOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/GithubOAuthFlow.java index 8b55f0a09d863..508525611dc96 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/GithubOAuthFlow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/GithubOAuthFlow.java @@ -35,7 +35,11 @@ public GithubOAuthFlow(final ConfigRepository configRepository, final HttpClient } @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, + final JsonNode inputOAuthConfiguration) + 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 diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/HubspotOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/HubspotOAuthFlow.java index 8cb4382a9d9b7..dfa2a8b6a9ff6 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/HubspotOAuthFlow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/HubspotOAuthFlow.java @@ -4,6 +4,7 @@ package io.airbyte.oauth.flows; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.oauth.BaseOAuth2Flow; @@ -27,17 +28,12 @@ public HubspotOAuthFlow(final ConfigRepository configRepository, final HttpClien super(configRepository, httpClient, stateSupplier, TOKEN_REQUEST_CONTENT_TYPE.JSON); } - /** - * 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. - * - * @param definitionId The configured definition ID of this client - * @param clientId The configured client ID - * @param redirectUrl the redirect URL - */ @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, + final JsonNode inputOAuthConfiguration) + throws IOException { try { return new URIBuilder(AUTHORIZE_URL) .addParameter("client_id", clientId) diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/IntercomOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/IntercomOAuthFlow.java index 3ec6cd48bb1b6..0f0dc883e7bd6 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/IntercomOAuthFlow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/IntercomOAuthFlow.java @@ -33,7 +33,11 @@ public IntercomOAuthFlow(final ConfigRepository configRepository, final HttpClie } @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, + final JsonNode inputOAuthConfiguration) + throws IOException { try { return new URIBuilder(AUTHORIZE_URL) .addParameter("client_id", clientId) diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/PipeDriveOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/PipeDriveOAuthFlow.java index 93102865c47e1..9681ba2a6fc1c 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/PipeDriveOAuthFlow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/PipeDriveOAuthFlow.java @@ -4,6 +4,7 @@ package io.airbyte.oauth.flows; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import io.airbyte.config.persistence.ConfigRepository; @@ -35,7 +36,11 @@ public PipeDriveOAuthFlow(final ConfigRepository configRepository, final HttpCli } @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, + final JsonNode inputOAuthConfiguration) + throws IOException { try { return new URIBuilder(AUTHORIZE_URL) .addParameter("client_id", clientId) diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/QuickbooksOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/QuickbooksOAuthFlow.java index 5918b8892b71f..fd29c13be0096 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/QuickbooksOAuthFlow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/QuickbooksOAuthFlow.java @@ -4,6 +4,7 @@ package io.airbyte.oauth.flows; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import io.airbyte.config.persistence.ConfigRepository; @@ -36,7 +37,11 @@ public String getScopes() { } @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, + final JsonNode inputOAuthConfiguration) + throws IOException { try { return (new URIBuilder(CONSENT_URL) diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SalesforceOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SalesforceOAuthFlow.java index e83773c223a1e..a577438e2ac41 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SalesforceOAuthFlow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SalesforceOAuthFlow.java @@ -4,6 +4,7 @@ package io.airbyte.oauth.flows; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import io.airbyte.config.persistence.ConfigRepository; @@ -38,7 +39,11 @@ public SalesforceOAuthFlow(final ConfigRepository configRepository, final HttpCl } @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, + final JsonNode inputOAuthConfiguration) + throws IOException { try { return new URIBuilder(AUTHORIZE_URL) .addParameter("client_id", clientId) diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SlackOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SlackOAuthFlow.java index dc98e7d150bfc..043d1be00be66 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SlackOAuthFlow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SlackOAuthFlow.java @@ -4,6 +4,7 @@ package io.airbyte.oauth.flows; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.annotations.VisibleForTesting; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.oauth.BaseOAuth2Flow; @@ -34,7 +35,11 @@ public SlackOAuthFlow(final ConfigRepository configRepository, final HttpClient * accordingly. */ @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, + final JsonNode inputOAuthConfiguration) + throws IOException { try { return new URIBuilder(SLACK_CONSENT_URL_BASE) .addParameter("client_id", clientId) diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SnapchatMarketingOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SnapchatMarketingOAuthFlow.java index 1579ac8a12b4d..cbfcde0326bd7 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SnapchatMarketingOAuthFlow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SnapchatMarketingOAuthFlow.java @@ -4,6 +4,7 @@ package io.airbyte.oauth.flows; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import io.airbyte.config.persistence.ConfigRepository; @@ -36,7 +37,11 @@ public SnapchatMarketingOAuthFlow(final ConfigRepository configRepository, final } @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, + final JsonNode inputOAuthConfiguration) + throws IOException { try { return new URIBuilder(AUTHORIZE_URL) .addParameter("client_id", clientId) diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SurveymonkeyOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SurveymonkeyOAuthFlow.java index 63c10d703a17c..d76e47b251454 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SurveymonkeyOAuthFlow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/SurveymonkeyOAuthFlow.java @@ -37,7 +37,11 @@ public SurveymonkeyOAuthFlow(final ConfigRepository configRepository, final Http } @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, + final JsonNode inputOAuthConfiguration) + throws IOException { try { return new URIBuilder(AUTHORIZE_URL) .addParameter("client_id", clientId) diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/TrelloOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/TrelloOAuthFlow.java index a7ae99c61ea3e..254f69a8053d2 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/TrelloOAuthFlow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/TrelloOAuthFlow.java @@ -17,6 +17,7 @@ import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.oauth.BaseOAuthFlow; import io.airbyte.protocol.models.OAuthConfigSpecification; +import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; import java.net.http.HttpClient; import java.util.List; @@ -53,20 +54,28 @@ public TrelloOAuthFlow(final ConfigRepository configRepository, final HttpTransp } @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 JsonNode inputOAuthConfiguration, + final OAuthConfigSpecification oauthConfigSpecification) throws IOException, ConfigNotFoundException { final JsonNode oAuthParamConfig = getSourceOAuthParamConfig(workspaceId, sourceDefinitionId); return getConsentUrl(oAuthParamConfig, redirectUrl); } @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 JsonNode inputOAuthConfiguration, + final OAuthConfigSpecification oauthConfigSpecification) throws IOException, ConfigNotFoundException { 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 { final String clientKey = getClientIdUnsafe(oAuthParamConfig); final String clientSecret = getClientSecretUnsafe(oAuthParamConfig); final OAuthGetTemporaryToken oAuthGetTemporaryToken = new OAuthGetTemporaryToken(REQUEST_TOKEN_URL); @@ -113,7 +122,7 @@ public Map completeSourceOAuth(final UUID workspaceId, final String redirectUrl, final JsonNode inputOAuthConfiguration, final OAuthConfigSpecification oAuthConfigSpecification) - throws IOException, ConfigNotFoundException { + throws IOException, ConfigNotFoundException, JsonValidationException { final JsonNode oAuthParamConfig = getDestinationOAuthParamConfig(workspaceId, sourceDefinitionId); return formatOAuthOutput(oAuthParamConfig, internalCompleteOAuth(oAuthParamConfig, queryParams), oAuthConfigSpecification); } @@ -125,7 +134,7 @@ public Map completeDestinationOAuth(final UUID workspaceId, final String redirectUrl, final JsonNode inputOAuthConfiguration, final OAuthConfigSpecification oAuthConfigSpecification) - throws IOException, ConfigNotFoundException { + throws IOException, ConfigNotFoundException, JsonValidationException { final JsonNode oAuthParamConfig = getDestinationOAuthParamConfig(workspaceId, destinationDefinitionId); return formatOAuthOutput(oAuthParamConfig, internalCompleteOAuth(oAuthParamConfig, queryParams), oAuthConfigSpecification); } diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/facebook/FacebookOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/facebook/FacebookOAuthFlow.java index 5b787800e9469..7423d0e7431fc 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/facebook/FacebookOAuthFlow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/facebook/FacebookOAuthFlow.java @@ -41,7 +41,11 @@ public FacebookOAuthFlow(final ConfigRepository configRepository, final HttpClie protected abstract String getScopes(); @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, + final JsonNode inputOAuthConfiguration) + throws IOException { try { return new URIBuilder(AUTH_CODE_TOKEN_URL) .addParameter("client_id", clientId) diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/google/GoogleOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/google/GoogleOAuthFlow.java index 6d3cf3850c141..6feed0c01a0da 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/google/GoogleOAuthFlow.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/google/GoogleOAuthFlow.java @@ -34,7 +34,11 @@ public GoogleOAuthFlow(final ConfigRepository configRepository, final HttpClient } @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, + final JsonNode inputOAuthConfiguration) + throws IOException { final URIBuilder builder = new URIBuilder() .setScheme("https") .setHost("accounts.google.com") diff --git a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/FacebookOAuthFlowIntegrationTest.java b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/FacebookOAuthFlowIntegrationTest.java index 16f2820cd3c7b..be75654c3e1ea 100644 --- a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/FacebookOAuthFlowIntegrationTest.java +++ b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/FacebookOAuthFlowIntegrationTest.java @@ -37,10 +37,11 @@ protected Path getCredentialsPath() { } @Override - protected OAuthFlowImplementation getFlowImplementation(ConfigRepository configRepository, HttpClient httpClient) { + protected OAuthFlowImplementation getFlowImplementation(final ConfigRepository configRepository, final HttpClient httpClient) { return new FacebookMarketingOAuthFlow(configRepository, httpClient); } + @Override @BeforeEach public void setup() throws IOException { super.setup(); @@ -65,7 +66,7 @@ public void testFullFacebookOAuthFlow() throws InterruptedException, ConfigNotFo .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); + final String url = flow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, Jsons.emptyObject(), null); LOGGER.info("Waiting for user consent at: {}", url); waitForResponse(20); assertTrue(serverHandler.isSucceeded(), "Failed to get User consent on time"); diff --git a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/GithubOAuthFlowIntegrationTest.java b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/GithubOAuthFlowIntegrationTest.java index 9677a82a5e5c0..1c99d8566fd34 100644 --- a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/GithubOAuthFlowIntegrationTest.java +++ b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/GithubOAuthFlowIntegrationTest.java @@ -37,7 +37,7 @@ protected Path getCredentialsPath() { } @Override - protected OAuthFlowImplementation getFlowImplementation(ConfigRepository configRepository, HttpClient httpClient) { + protected OAuthFlowImplementation getFlowImplementation(final ConfigRepository configRepository, final HttpClient httpClient) { return new GithubOAuthFlow(configRepository, httpClient); } @@ -46,6 +46,7 @@ protected int getServerListeningPort() { return SERVER_LISTENING_PORT; } + @Override @BeforeEach public void setup() throws IOException { super.setup(); @@ -66,7 +67,7 @@ public void testFullGithubOAuthFlow() throws InterruptedException, ConfigNotFoun .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); + final String url = flow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, Jsons.emptyObject(), null); 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... diff --git a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/IntercomOAuthFlowIntegrationTest.java b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/IntercomOAuthFlowIntegrationTest.java index 25fa792047e44..a0258a7178fac 100644 --- a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/IntercomOAuthFlowIntegrationTest.java +++ b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/IntercomOAuthFlowIntegrationTest.java @@ -37,7 +37,7 @@ protected Path getCredentialsPath() { } @Override - protected OAuthFlowImplementation getFlowImplementation(ConfigRepository configRepository, HttpClient httpClient) { + protected OAuthFlowImplementation getFlowImplementation(final ConfigRepository configRepository, final HttpClient httpClient) { return new IntercomOAuthFlow(configRepository, httpClient); } @@ -46,6 +46,7 @@ protected int getServerListeningPort() { return SERVER_LISTENING_PORT; } + @Override @BeforeEach public void setup() throws IOException { super.setup(); @@ -69,7 +70,7 @@ public void testFullIntercomOAuthFlow() throws InterruptedException, ConfigNotFo .put("client_secret", credentialsJson.get("client_secret").asText()) .build()))))); - final String url = flow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL); + final String url = flow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, Jsons.emptyObject(), null); 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 diff --git a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/PipeDriveOAuthFlowIntegrationTest.java b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/PipeDriveOAuthFlowIntegrationTest.java index eb1bfb2e2a071..e8780b9c1bec3 100644 --- a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/PipeDriveOAuthFlowIntegrationTest.java +++ b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/PipeDriveOAuthFlowIntegrationTest.java @@ -36,6 +36,7 @@ protected String getRedirectUrl() { return "http://localhost:3000/auth_flow"; } + @Override protected int getServerListeningPort() { return 3000; } @@ -59,7 +60,8 @@ public void testFullPipeDriveOAuthFlow() throws InterruptedException, ConfigNotF .put("client_id", credentialsJson.get("client_id").asText()) .put("client_secret", credentialsJson.get("client_secret").asText()) .build()))))); - final String url = getFlowImplementation(configRepository, httpClient).getSourceConsentUrl(workspaceId, definitionId, getRedirectUrl()); + final String url = getFlowImplementation(configRepository, httpClient).getSourceConsentUrl(workspaceId, definitionId, getRedirectUrl(), + Jsons.emptyObject(), null); LOGGER.info("Waiting for user consent at: {}", url); waitForResponse(20); assertTrue(serverHandler.isSucceeded(), "Failed to get User consent on time"); diff --git a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/QuickbooksOAuthFlowIntegrationTest.java b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/QuickbooksOAuthFlowIntegrationTest.java index 21daa5b2ee57c..658bf7acf27c7 100644 --- a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/QuickbooksOAuthFlowIntegrationTest.java +++ b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/QuickbooksOAuthFlowIntegrationTest.java @@ -41,7 +41,7 @@ protected Path getCredentialsPath() { } @Override - protected OAuthFlowImplementation getFlowImplementation(ConfigRepository configRepository, HttpClient httpClient) { + protected OAuthFlowImplementation getFlowImplementation(final ConfigRepository configRepository, final HttpClient httpClient) { return new QuickbooksOAuthFlow(configRepository, httpClient); } @@ -61,7 +61,8 @@ public void testFullOAuthFlow() throws InterruptedException, ConfigNotFoundExcep .put("client_id", credentialsJson.get("client_id").asText()) .put("client_secret", credentialsJson.get("client_secret").asText()) .build()))))); - final String url = getFlowImplementation(configRepository, httpClient).getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL); + final String url = + getFlowImplementation(configRepository, httpClient).getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, Jsons.emptyObject(), null); 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... diff --git a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SalesforceOAuthFlowIntegrationTest.java b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SalesforceOAuthFlowIntegrationTest.java index 0cee2aaca757d..8c255c23d9203 100644 --- a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SalesforceOAuthFlowIntegrationTest.java +++ b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SalesforceOAuthFlowIntegrationTest.java @@ -84,7 +84,7 @@ public void testFullSalesforceOAuthFlow() throws InterruptedException, ConfigNot .put("client_id", clientId) .put("client_secret", credentialsJson.get("client_secret").asText()) .build())))); - final String url = salesforceOAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL); + final String url = salesforceOAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, Jsons.emptyObject(), null); 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... @@ -153,13 +153,13 @@ public void handle(HttpExchange t) { } } - private static Map deserialize(String query) { + private static Map deserialize(final String query) { if (query == null) { return null; } final Map result = new HashMap<>(); - for (String param : query.split("&")) { - String[] entry = param.split("=", 2); + for (final String param : query.split("&")) { + final String[] entry = param.split("=", 2); if (entry.length > 1) { result.put(entry[0], entry[1]); } else { diff --git a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SlackOAuthFlowIntegrationTest.java b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SlackOAuthFlowIntegrationTest.java index 10903cb7b8ef9..3e69a32df32fe 100644 --- a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SlackOAuthFlowIntegrationTest.java +++ b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SlackOAuthFlowIntegrationTest.java @@ -37,7 +37,7 @@ protected String getRedirectUrl() { } @Override - protected OAuthFlowImplementation getFlowImplementation(ConfigRepository configRepository, HttpClient httpClient) { + protected OAuthFlowImplementation getFlowImplementation(final ConfigRepository configRepository, final HttpClient httpClient) { return new SlackOAuthFlow(configRepository, httpClient); } @@ -56,7 +56,8 @@ public void testFullSlackOAuthFlow() throws InterruptedException, ConfigNotFound .put("client_id", credentialsJson.get("client_id").asText()) .put("client_secret", credentialsJson.get("client_secret").asText()) .build())))); - final String url = getFlowImplementation(configRepository, httpClient).getSourceConsentUrl(workspaceId, definitionId, getRedirectUrl()); + final String url = getFlowImplementation(configRepository, httpClient).getSourceConsentUrl(workspaceId, definitionId, getRedirectUrl(), + Jsons.emptyObject(), null); 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... diff --git a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SnapchatMarketingOAuthFlowIntegrationTest.java b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SnapchatMarketingOAuthFlowIntegrationTest.java index bbefbe97fba32..21b3d4cc6b598 100644 --- a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SnapchatMarketingOAuthFlowIntegrationTest.java +++ b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SnapchatMarketingOAuthFlowIntegrationTest.java @@ -36,12 +36,13 @@ protected String getRedirectUrl() { return "https://f215-195-114-147-152.ngrok.io/auth_flow"; } + @Override protected int getServerListeningPort() { return 3000; } @Override - protected OAuthFlowImplementation getFlowImplementation(ConfigRepository configRepository, HttpClient httpClient) { + protected OAuthFlowImplementation getFlowImplementation(final ConfigRepository configRepository, final HttpClient httpClient) { return new SnapchatMarketingOAuthFlow(configRepository, httpClient); } @@ -59,7 +60,8 @@ public void testFullSnapchatMarketingOAuthFlow() throws InterruptedException, Co .put("client_id", credentialsJson.get("client_id").asText()) .put("client_secret", credentialsJson.get("client_secret").asText()) .build())))); - final String url = getFlowImplementation(configRepository, httpClient).getSourceConsentUrl(workspaceId, definitionId, getRedirectUrl()); + final String url = getFlowImplementation(configRepository, httpClient).getSourceConsentUrl(workspaceId, definitionId, getRedirectUrl(), + Jsons.emptyObject(), null); LOGGER.info("Waiting for user consent at: {}", url); waitForResponse(20); assertTrue(serverHandler.isSucceeded(), "Failed to get User consent on time"); diff --git a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SurveymonkeyOAuthFlowIntegrationTest.java b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SurveymonkeyOAuthFlowIntegrationTest.java index 5791ed62b1a4c..ac97847bcbd7f 100644 --- a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SurveymonkeyOAuthFlowIntegrationTest.java +++ b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/SurveymonkeyOAuthFlowIntegrationTest.java @@ -36,10 +36,11 @@ protected Path getCredentialsPath() { } @Override - protected OAuthFlowImplementation getFlowImplementation(ConfigRepository configRepository, HttpClient httpClient) { + protected OAuthFlowImplementation getFlowImplementation(final ConfigRepository configRepository, final HttpClient httpClient) { return new SurveymonkeyOAuthFlow(configRepository, httpClient); } + @Override @BeforeEach public void setup() throws IOException { super.setup(); @@ -64,7 +65,7 @@ public void testFullSurveymonkeyOAuthFlow() throws InterruptedException, ConfigN .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); + final String url = flow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, Jsons.emptyObject(), null); LOGGER.info("Waiting for user consent at: {}", url); waitForResponse(20); assertTrue(serverHandler.isSucceeded(), "Failed to get User consent on time"); diff --git a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/TrelloOAuthFlowIntegrationTest.java b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/TrelloOAuthFlowIntegrationTest.java index 57a0d2e883e2e..39b6876857cf0 100644 --- a/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/TrelloOAuthFlowIntegrationTest.java +++ b/airbyte-oauth/src/test-integration/java/io.airbyte.oauth.flows/TrelloOAuthFlowIntegrationTest.java @@ -84,7 +84,7 @@ 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, Jsons.emptyObject(), null); 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... diff --git a/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/HubspotOAuthFlowIntegrationTest.java b/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/HubspotOAuthFlowIntegrationTest.java index 2346a6061a086..844a3f1424464 100644 --- a/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/HubspotOAuthFlowIntegrationTest.java +++ b/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/HubspotOAuthFlowIntegrationTest.java @@ -56,7 +56,7 @@ public void testFullOAuthFlow() throws InterruptedException, ConfigNotFoundExcep .put("client_secret", credentialsJson.get("credentials").get("client_secret").asText()) .build())))); var flowObject = getFlowImplementation(configRepository, httpClient); - final String url = flowObject.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL); + final String url = flowObject.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, Jsons.emptyObject(), null); 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... diff --git a/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleAdsOAuthFlowIntegrationTest.java b/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleAdsOAuthFlowIntegrationTest.java index 29d6d909dba7d..d3b925f4dd28b 100644 --- a/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleAdsOAuthFlowIntegrationTest.java +++ b/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleAdsOAuthFlowIntegrationTest.java @@ -83,7 +83,7 @@ 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, Jsons.emptyObject(), null); 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... 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 499f8ad56c0b4..c043152344e79 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 @@ -83,7 +83,7 @@ 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, Jsons.emptyObject(), null); 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... diff --git a/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleSearchConsoleOAuthFlowIntegrationTest.java b/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleSearchConsoleOAuthFlowIntegrationTest.java index 886ba1c91f835..6d9be552abe8c 100644 --- a/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleSearchConsoleOAuthFlowIntegrationTest.java +++ b/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleSearchConsoleOAuthFlowIntegrationTest.java @@ -83,7 +83,7 @@ public void testFullGoogleOAuthFlow() throws InterruptedException, ConfigNotFoun .put("client_id", credentialsJson.get("authorization").get("client_id").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, Jsons.emptyObject(), null); 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... diff --git a/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleSheetsOAuthFlowIntegrationTest.java b/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleSheetsOAuthFlowIntegrationTest.java index 24f4ae1d9a94a..21a6a83e4acd6 100644 --- a/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleSheetsOAuthFlowIntegrationTest.java +++ b/airbyte-oauth/src/test-integration/java/io/airbyte/oauth/flows/google/GoogleSheetsOAuthFlowIntegrationTest.java @@ -83,7 +83,7 @@ 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 = googleSheetsOAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL); + final String url = googleSheetsOAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, Jsons.emptyObject(), null); 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... diff --git a/airbyte-oauth/src/test/java/io/airbyte/oauth/MoreOAuthParametersTest.java b/airbyte-oauth/src/test/java/io/airbyte/oauth/MoreOAuthParametersTest.java index 77ed66adc77f4..1318f76702dd1 100644 --- a/airbyte-oauth/src/test/java/io/airbyte/oauth/MoreOAuthParametersTest.java +++ b/airbyte-oauth/src/test/java/io/airbyte/oauth/MoreOAuthParametersTest.java @@ -8,7 +8,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.ImmutableMap; import io.airbyte.commons.json.Jsons; @@ -42,31 +41,8 @@ void testFailureFlattenConfig() { assertThrows(IllegalStateException.class, () -> MoreOAuthParameters.flattenOAuthConfig(nestedConfig)); } - private void maskAllValues(final ObjectNode node) { - for (final String key : Jsons.keys(node)) { - if (node.get(key).getNodeType() == JsonNodeType.OBJECT) { - maskAllValues((ObjectNode) node.get(key)); - } else { - node.set(key, MoreOAuthParameters.getSecretMask()); - } - } - } - - @Test - void testInjectUnnestedNode_Masked() { - final ObjectNode oauthParams = (ObjectNode) Jsons.jsonNode(generateOAuthParameters()); - final ObjectNode maskedOauthParams = Jsons.clone(oauthParams); - maskAllValues(maskedOauthParams); - final ObjectNode actual = generateJsonConfig(); - final ObjectNode expected = Jsons.clone(actual); - expected.setAll(maskedOauthParams); - - MoreOAuthParameters.mergeJsons(actual, oauthParams, MoreOAuthParameters.getSecretMask()); - assertEquals(expected, actual); - } - @Test - void testInjectUnnestedNode_Unmasked() { + void testInjectUnnestedNode() { final ObjectNode oauthParams = (ObjectNode) Jsons.jsonNode(generateOAuthParameters()); final ObjectNode actual = generateJsonConfig(); @@ -78,27 +54,9 @@ void testInjectUnnestedNode_Unmasked() { assertEquals(expected, actual); } - @Test - void testInjectNewNestedNode_Masked() { - final ObjectNode oauthParams = (ObjectNode) Jsons.jsonNode(generateOAuthParameters()); - final ObjectNode maskedOauthParams = Jsons.clone(oauthParams); - maskAllValues(maskedOauthParams); - final ObjectNode nestedConfig = (ObjectNode) Jsons.jsonNode(ImmutableMap.builder() - .put("oauth_credentials", oauthParams) - .build()); - - // nested node does not exist in actual object - final ObjectNode actual = generateJsonConfig(); - final ObjectNode expected = Jsons.clone(actual); - expected.putObject("oauth_credentials").setAll(maskedOauthParams); - - MoreOAuthParameters.mergeJsons(actual, nestedConfig, MoreOAuthParameters.getSecretMask()); - assertEquals(expected, actual); - } - @Test @DisplayName("A nested config should be inserted with the same nesting structure") - void testInjectNewNestedNode_Unmasked() { + void testInjectNewNestedNode() { final ObjectNode oauthParams = (ObjectNode) Jsons.jsonNode(generateOAuthParameters()); final ObjectNode nestedConfig = (ObjectNode) Jsons.jsonNode(ImmutableMap.builder() .put("oauth_credentials", oauthParams) @@ -116,7 +74,7 @@ void testInjectNewNestedNode_Unmasked() { @Test @DisplayName("A nested node which partially exists in the main config should be merged into the main config, not overwrite the whole nested object") - void testInjectedPartiallyExistingNestedNode_Unmasked() { + void testInjectedPartiallyExistingNestedNode() { final ObjectNode oauthParams = (ObjectNode) Jsons.jsonNode(generateOAuthParameters()); final ObjectNode nestedConfig = (ObjectNode) Jsons.jsonNode(ImmutableMap.builder() .put("oauth_credentials", oauthParams) diff --git a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/BaseOAuthFlowTest.java b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/BaseOAuthFlowTest.java index 8e1a3e979465b..dea12adbaff26 100644 --- a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/BaseOAuthFlowTest.java +++ b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/BaseOAuthFlowTest.java @@ -71,26 +71,23 @@ public void setup() throws JsonValidationException, IOException { } /** + * This should be implemented for the particular oauth flow implementation + * * @return the oauth flow implementation to test */ protected abstract BaseOAuthFlow getOAuthFlow(); /** + * This should be implemented for the particular oauth flow implementation + * * @return the expected consent URL */ protected abstract String getExpectedConsentUrl(); /** - * @return the instance wide config params for this oauth flow - */ - protected JsonNode getOAuthParamConfig() { - return Jsons.jsonNode(ImmutableMap.builder() - .put("client_id", "test_client_id") - .put("client_secret", "test_client_secret") - .build()); - } - - /** + * Redefine if the oauth flow implementation does not return `refresh_token`. (maybe for example + * using `access_token` like in the `GithubOAuthFlowTest` instead?) + * * @return the full output expected to be returned by this oauth flow + all its instance wide * variables */ @@ -102,14 +99,54 @@ protected Map getExpectedOutput() { } /** - * @return the backward compatible path that is used in the deprecated oauth flows (should match - * getDefaultOAuthOutputPath()) + * Redefine if the oauth flow implementation does not return `refresh_token`. (maybe for example + * using `access_token` like in the `GithubOAuthFlowTest` instead?) + * + * @return the output specification used to identify what the oauth flow should be returning + */ + protected JsonNode getCompleteOAuthOutputSpecification() { + return getJsonSchema(Map.of("refresh_token", Map.of("type", "string"))); + } + + /** + * Redefine if the oauth flow implementation does not return `refresh_token`. (maybe for example + * using `access_token` like in the `GithubOAuthFlowTest` instead?) + * + * @return the filtered outputs once it is filtered by the output specifications + */ + protected Map getExpectedFilteredOutput() { + return Map.of( + "refresh_token", "refresh_token_response", + "client_id", MoreOAuthParameters.SECRET_MASK); + } + + /** + * @return the output specification used to filter what the oauth flow should be returning + */ + protected JsonNode getCompleteOAuthServerOutputSpecification() { + return getJsonSchema(Map.of("client_id", Map.of("type", "string"))); + } + + /** + * Redefine to match the oauth implementation flow getDefaultOAuthOutputPath() + * + * @return the backward compatible path that is used in the deprecated oauth flows. */ protected List getExpectedOutputPath() { return List.of("credentials"); } /** + * @return if the OAuth implementation flow has a dependency on input values from connector config. + */ + protected boolean hasDependencyOnConnectorConfigValues() { + return !getInputOAuthConfiguration().isEmpty(); + } + + /** + * If the OAuth implementation flow has a dependency on input values from connector config, this + * method should be redefined. + * * @return the input configuration sent to oauth flow (values from connector config) */ protected JsonNode getInputOAuthConfiguration() { @@ -117,34 +154,37 @@ protected JsonNode getInputOAuthConfiguration() { } /** - * @return the output specification used to filter what the oauth flow should be returning + * If the OAuth implementation flow has a dependency on input values from connector config, this + * method should be redefined. + * + * @return the input configuration sent to oauth flow (values from connector config) */ - protected JsonNode getOutputOAuthSpecification() { - return Jsons.jsonNode(Map.of( - "refresh_token", Map.of("type", "String"))); + protected JsonNode getUserInputFromConnectorConfigSpecification() { + return getJsonSchema(Map.of()); } /** - * @return the output specification used to filter what the oauth flow should be returning + * @return the instance wide config params for this oauth flow */ - protected JsonNode getOutputOAuthParameterSpecification() { - return Jsons.jsonNode(Map.of( - "client_id", Map.of("type", "String"))); + protected JsonNode getOAuthParamConfig() { + return Jsons.jsonNode(ImmutableMap.builder() + .put("client_id", "test_client_id") + .put("client_secret", "test_client_secret") + .build()); } - /** - * @return the fitlered outputs once it is filtered by the output specifications - */ - protected Map getExpectedFilteredOutput() { - return Map.of( - "refresh_token", "refresh_token_response", - "client_id", MoreOAuthParameters.SECRET_MASK); + protected static JsonNode getJsonSchema(final Map properties) { + return Jsons.jsonNode(Map.of( + "type", "object", + "additionalProperties", "false", + "properties", properties)); } private OAuthConfigSpecification getoAuthConfigSpecification() { return new OAuthConfigSpecification() - .withCompleteOauthOutputSpecification(getOutputOAuthSpecification()) - .withCompleteOauthServerOutputSpecification(getOutputOAuthParameterSpecification()); + .withOauthUserInputFromConnectorConfigSpecification(getUserInputFromConnectorConfigSpecification()) + .withCompleteOauthOutputSpecification(getCompleteOAuthOutputSpecification()) + .withCompleteOauthServerOutputSpecification(getCompleteOAuthServerOutputSpecification()); } private OAuthConfigSpecification getEmptyOAuthConfigSpecification() { @@ -162,12 +202,28 @@ public void testGetDefaultOutputPath() { assertEquals(getExpectedOutputPath(), oauthFlow.getDefaultOAuthOutputPath()); } + @Test + public void testValidateInputOAuthConfigurationFailure() { + final JsonNode invalidInputOAuthConfiguration = Jsons.jsonNode(Map.of("UnexpectedRandomField", 42)); + assertThrows(JsonValidationException.class, + () -> oauthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, invalidInputOAuthConfiguration, getoAuthConfigSpecification())); + assertThrows(JsonValidationException.class, () -> oauthFlow.getDestinationConsentUrl(workspaceId, definitionId, REDIRECT_URL, + invalidInputOAuthConfiguration, getoAuthConfigSpecification())); + assertThrows(JsonValidationException.class, () -> oauthFlow.completeSourceOAuth(workspaceId, definitionId, Map.of(), REDIRECT_URL, + invalidInputOAuthConfiguration, getoAuthConfigSpecification())); + assertThrows(JsonValidationException.class, () -> oauthFlow.completeDestinationOAuth(workspaceId, definitionId, Map.of(), REDIRECT_URL, + invalidInputOAuthConfiguration, getoAuthConfigSpecification())); + } + @Test public void testGetConsentUrlEmptyOAuthParameters() throws JsonValidationException, IOException { when(configRepository.listSourceOAuthParam()).thenReturn(List.of()); when(configRepository.listDestinationOAuthParam()).thenReturn(List.of()); - assertThrows(ConfigNotFoundException.class, () -> oauthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL)); - assertThrows(ConfigNotFoundException.class, () -> oauthFlow.getDestinationConsentUrl(workspaceId, definitionId, REDIRECT_URL)); + assertThrows(ConfigNotFoundException.class, + () -> oauthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, getInputOAuthConfiguration(), getoAuthConfigSpecification())); + assertThrows(ConfigNotFoundException.class, + () -> oauthFlow.getDestinationConsentUrl(workspaceId, definitionId, REDIRECT_URL, getInputOAuthConfiguration(), + getoAuthConfigSpecification())); } @Test @@ -182,19 +238,46 @@ public void testGetConsentUrlIncompleteOAuthParameters() throws IOException, Jso .withDestinationDefinitionId(definitionId) .withWorkspaceId(workspaceId) .withConfiguration(Jsons.emptyObject()))); - assertThrows(IllegalArgumentException.class, () -> oauthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL)); - assertThrows(IllegalArgumentException.class, () -> oauthFlow.getDestinationConsentUrl(workspaceId, definitionId, REDIRECT_URL)); + assertThrows(IllegalArgumentException.class, + () -> oauthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, getInputOAuthConfiguration(), getoAuthConfigSpecification())); + assertThrows(IllegalArgumentException.class, + () -> oauthFlow.getDestinationConsentUrl(workspaceId, definitionId, REDIRECT_URL, getInputOAuthConfiguration(), + getoAuthConfigSpecification())); + } + + @Test + public void testGetSourceConsentUrlEmptyOAuthSpec() throws IOException, ConfigNotFoundException, JsonValidationException { + if (hasDependencyOnConnectorConfigValues()) { + assertThrows(IOException.class, () -> oauthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, Jsons.emptyObject(), null), + "OAuth Flow Implementations with dependencies on connector config can't be supported without OAuthConfigSpecifications"); + } else { + final String consentUrl = oauthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, Jsons.emptyObject(), null); + assertEquals(getExpectedConsentUrl(), consentUrl); + } + } + + @Test + public void testGetDestinationConsentUrlEmptyOAuthSpec() throws IOException, ConfigNotFoundException, JsonValidationException { + if (hasDependencyOnConnectorConfigValues()) { + assertThrows(IOException.class, () -> oauthFlow.getDestinationConsentUrl(workspaceId, definitionId, REDIRECT_URL, Jsons.emptyObject(), null), + "OAuth Flow Implementations with dependencies on connector config can't be supported without OAuthConfigSpecifications"); + } else { + final String consentUrl = oauthFlow.getDestinationConsentUrl(workspaceId, definitionId, REDIRECT_URL, Jsons.emptyObject(), null); + assertEquals(getExpectedConsentUrl(), consentUrl); + } } @Test - public void testGetSourceConsentUrl() throws IOException, ConfigNotFoundException { - final String consentUrl = oauthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL); + public void testGetSourceConsentUrl() throws IOException, ConfigNotFoundException, JsonValidationException { + final String consentUrl = + oauthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, getInputOAuthConfiguration(), getoAuthConfigSpecification()); assertEquals(getExpectedConsentUrl(), consentUrl); } @Test - public void testGetDestinationConsentUrl() throws IOException, ConfigNotFoundException { - final String consentUrl = oauthFlow.getDestinationConsentUrl(workspaceId, definitionId, REDIRECT_URL); + public void testGetDestinationConsentUrl() throws IOException, ConfigNotFoundException, JsonValidationException { + final String consentUrl = + oauthFlow.getDestinationConsentUrl(workspaceId, definitionId, REDIRECT_URL, getInputOAuthConfiguration(), getoAuthConfigSpecification()); assertEquals(getExpectedConsentUrl(), consentUrl); } @@ -211,16 +294,22 @@ public void testDeprecatedCompleteSourceOAuth() throws IOException, InterruptedE when(response.body()).thenReturn(Jsons.serialize(returnedCredentials)); when(httpClient.send(any(), any())).thenReturn(response); final Map queryParams = Map.of("code", "test_code"); - Map actualRawQueryParams = oauthFlow.completeSourceOAuth(workspaceId, definitionId, queryParams, REDIRECT_URL); - for (final String node : getExpectedOutputPath()) { - assertNotNull(actualRawQueryParams.get(node)); - actualRawQueryParams = (Map) actualRawQueryParams.get(node); + + if (hasDependencyOnConnectorConfigValues()) { + assertThrows(IOException.class, () -> oauthFlow.completeSourceOAuth(workspaceId, definitionId, queryParams, REDIRECT_URL), + "OAuth Flow Implementations with dependencies on connector config can't be supported in the deprecated APIs"); + } else { + Map actualRawQueryParams = oauthFlow.completeSourceOAuth(workspaceId, definitionId, queryParams, REDIRECT_URL); + for (final String node : getExpectedOutputPath()) { + assertNotNull(actualRawQueryParams.get(node)); + actualRawQueryParams = (Map) actualRawQueryParams.get(node); + } + final Map expectedOutput = returnedCredentials; + final Map actualQueryParams = actualRawQueryParams; + assertEquals(expectedOutput.size(), actualQueryParams.size(), + String.format("Expected %s values but got\n\t%s\ninstead of\n\t%s", expectedOutput.size(), actualQueryParams, expectedOutput)); + expectedOutput.forEach((key, value) -> assertEquals(value, actualQueryParams.get(key))); } - final Map expectedOutput = returnedCredentials; - final Map actualQueryParams = actualRawQueryParams; - assertEquals(expectedOutput.size(), actualQueryParams.size(), - String.format("Expected %s values but got\n\t%s\ninstead of\n\t%s", expectedOutput.size(), actualQueryParams, expectedOutput)); - expectedOutput.forEach((key, value) -> assertEquals(value, actualQueryParams.get(key))); } @Test @@ -230,20 +319,26 @@ public void testDeprecatedCompleteDestinationOAuth() throws IOException, ConfigN when(response.body()).thenReturn(Jsons.serialize(returnedCredentials)); when(httpClient.send(any(), any())).thenReturn(response); final Map queryParams = Map.of("code", "test_code"); - Map actualRawQueryParams = oauthFlow.completeDestinationOAuth(workspaceId, definitionId, queryParams, REDIRECT_URL); - for (final String node : getExpectedOutputPath()) { - assertNotNull(actualRawQueryParams.get(node)); - actualRawQueryParams = (Map) actualRawQueryParams.get(node); + + if (hasDependencyOnConnectorConfigValues()) { + assertThrows(IOException.class, () -> oauthFlow.completeDestinationOAuth(workspaceId, definitionId, queryParams, REDIRECT_URL), + "OAuth Flow Implementations with dependencies on connector config can't be supported in the deprecated APIs"); + } else { + Map actualRawQueryParams = oauthFlow.completeDestinationOAuth(workspaceId, definitionId, queryParams, REDIRECT_URL); + for (final String node : getExpectedOutputPath()) { + assertNotNull(actualRawQueryParams.get(node)); + actualRawQueryParams = (Map) actualRawQueryParams.get(node); + } + final Map expectedOutput = returnedCredentials; + final Map actualQueryParams = actualRawQueryParams; + assertEquals(expectedOutput.size(), actualQueryParams.size(), + String.format("Expected %s values but got\n\t%s\ninstead of\n\t%s", expectedOutput.size(), actualQueryParams, expectedOutput)); + expectedOutput.forEach((key, value) -> assertEquals(value, actualQueryParams.get(key))); } - final Map expectedOutput = returnedCredentials; - final Map actualQueryParams = actualRawQueryParams; - assertEquals(expectedOutput.size(), actualQueryParams.size(), - String.format("Expected %s values but got\n\t%s\ninstead of\n\t%s", expectedOutput.size(), actualQueryParams, expectedOutput)); - expectedOutput.forEach((key, value) -> assertEquals(value, actualQueryParams.get(key))); } @Test - public void testEmptyOutputCompleteSourceOAuth() throws IOException, InterruptedException, ConfigNotFoundException { + public void testEmptyOutputCompleteSourceOAuth() throws IOException, InterruptedException, ConfigNotFoundException, JsonValidationException { final Map returnedCredentials = getExpectedOutput(); final HttpResponse response = mock(HttpResponse.class); when(response.body()).thenReturn(Jsons.serialize(returnedCredentials)); @@ -256,7 +351,7 @@ public void testEmptyOutputCompleteSourceOAuth() throws IOException, Interrupted } @Test - public void testEmptyOutputCompleteDestinationOAuth() throws IOException, InterruptedException, ConfigNotFoundException { + public void testEmptyOutputCompleteDestinationOAuth() throws IOException, InterruptedException, ConfigNotFoundException, JsonValidationException { final Map returnedCredentials = getExpectedOutput(); final HttpResponse response = mock(HttpResponse.class); when(response.body()).thenReturn(Jsons.serialize(returnedCredentials)); @@ -269,7 +364,7 @@ public void testEmptyOutputCompleteDestinationOAuth() throws IOException, Interr } @Test - public void testEmptyInputCompleteSourceOAuth() throws IOException, InterruptedException, ConfigNotFoundException { + public void testEmptyInputCompleteSourceOAuth() throws IOException, InterruptedException, ConfigNotFoundException, JsonValidationException { final Map returnedCredentials = getExpectedOutput(); final HttpResponse response = mock(HttpResponse.class); when(response.body()).thenReturn(Jsons.serialize(returnedCredentials)); @@ -284,7 +379,7 @@ public void testEmptyInputCompleteSourceOAuth() throws IOException, InterruptedE } @Test - public void testEmptyInputCompleteDestinationOAuth() throws IOException, InterruptedException, ConfigNotFoundException { + public void testEmptyInputCompleteDestinationOAuth() throws IOException, InterruptedException, ConfigNotFoundException, JsonValidationException { final Map returnedCredentials = getExpectedOutput(); final HttpResponse response = mock(HttpResponse.class); when(response.body()).thenReturn(Jsons.serialize(returnedCredentials)); @@ -299,7 +394,7 @@ public void testEmptyInputCompleteDestinationOAuth() throws IOException, Interru } @Test - public void testCompleteSourceOAuth() throws IOException, InterruptedException, ConfigNotFoundException { + public void testCompleteSourceOAuth() throws IOException, InterruptedException, ConfigNotFoundException, JsonValidationException { final Map returnedCredentials = getExpectedOutput(); final HttpResponse response = mock(HttpResponse.class); when(response.body()).thenReturn(Jsons.serialize(returnedCredentials)); @@ -314,7 +409,7 @@ public void testCompleteSourceOAuth() throws IOException, InterruptedException, } @Test - public void testCompleteDestinationOAuth() throws IOException, InterruptedException, ConfigNotFoundException { + public void testCompleteDestinationOAuth() throws IOException, InterruptedException, ConfigNotFoundException, JsonValidationException { final Map returnedCredentials = getExpectedOutput(); final HttpResponse response = mock(HttpResponse.class); when(response.body()).thenReturn(Jsons.serialize(returnedCredentials)); @@ -328,4 +423,21 @@ public void testCompleteDestinationOAuth() throws IOException, InterruptedExcept expectedOutput.forEach((key, value) -> assertEquals(value, actualQueryParams.get(key))); } + @Test + public void testValidateOAuthOutputFailure() throws IOException, InterruptedException, ConfigNotFoundException, JsonValidationException { + final Map returnedCredentials = getExpectedOutput(); + final HttpResponse response = mock(HttpResponse.class); + when(response.body()).thenReturn(Jsons.serialize(returnedCredentials)); + when(httpClient.send(any(), any())).thenReturn(response); + final Map queryParams = Map.of("code", "test_code"); + final OAuthConfigSpecification oAuthConfigSpecification = getoAuthConfigSpecification() + // change property types to induce json validation errors. + .withCompleteOauthServerOutputSpecification(getJsonSchema(Map.of("client_id", Map.of("type", "integer")))) + .withCompleteOauthOutputSpecification(getJsonSchema(Map.of("refresh_token", Map.of("type", "integer")))); + assertThrows(JsonValidationException.class, () -> oauthFlow.completeSourceOAuth(workspaceId, definitionId, queryParams, REDIRECT_URL, + getInputOAuthConfiguration(), oAuthConfigSpecification)); + assertThrows(JsonValidationException.class, () -> oauthFlow.completeDestinationOAuth(workspaceId, definitionId, queryParams, REDIRECT_URL, + getInputOAuthConfiguration(), oAuthConfigSpecification)); + } + } diff --git a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/GithubOAuthFlowTest.java b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/GithubOAuthFlowTest.java index b912ce90fcb7e..397784797c13d 100644 --- a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/GithubOAuthFlowTest.java +++ b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/GithubOAuthFlowTest.java @@ -5,7 +5,6 @@ package io.airbyte.oauth.flows; import com.fasterxml.jackson.databind.JsonNode; -import io.airbyte.commons.json.Jsons; import io.airbyte.oauth.BaseOAuthFlow; import io.airbyte.oauth.MoreOAuthParameters; import java.util.Map; @@ -31,8 +30,8 @@ protected Map getExpectedOutput() { } @Override - protected JsonNode getOutputOAuthSpecification() { - return Jsons.jsonNode(Map.of("access_token", Map.of("type", "String"))); + protected JsonNode getCompleteOAuthOutputSpecification() { + return getJsonSchema(Map.of("access_token", Map.of("type", "string"))); } @Override diff --git a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/IntercomOAuthFlowTest.java b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/IntercomOAuthFlowTest.java index 0f71ba6a9e1bb..c9fc2a2454c57 100644 --- a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/IntercomOAuthFlowTest.java +++ b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/IntercomOAuthFlowTest.java @@ -5,7 +5,6 @@ package io.airbyte.oauth.flows; import com.fasterxml.jackson.databind.JsonNode; -import io.airbyte.commons.json.Jsons; import io.airbyte.oauth.BaseOAuthFlow; import io.airbyte.oauth.MoreOAuthParameters; import java.util.List; @@ -37,8 +36,8 @@ protected Map getExpectedOutput() { } @Override - protected JsonNode getOutputOAuthSpecification() { - return Jsons.jsonNode(Map.of("access_token", Map.of("type", "String"))); + protected JsonNode getCompleteOAuthOutputSpecification() { + return getJsonSchema(Map.of("access_token", Map.of("type", "string"))); } @Override diff --git a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/SurveymonkeyOAuthFlowTest.java b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/SurveymonkeyOAuthFlowTest.java index 83d9e7d9516ed..3a664af13fb37 100644 --- a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/SurveymonkeyOAuthFlowTest.java +++ b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/SurveymonkeyOAuthFlowTest.java @@ -5,7 +5,6 @@ package io.airbyte.oauth.flows; import com.fasterxml.jackson.databind.JsonNode; -import io.airbyte.commons.json.Jsons; import io.airbyte.oauth.BaseOAuthFlow; import io.airbyte.oauth.MoreOAuthParameters; import java.util.List; @@ -37,8 +36,8 @@ protected Map getExpectedOutput() { } @Override - protected JsonNode getOutputOAuthSpecification() { - return Jsons.jsonNode(Map.of("access_token", Map.of("type", "String"))); + protected JsonNode getCompleteOAuthOutputSpecification() { + return getJsonSchema(Map.of("access_token", Map.of("type", "string"))); } @Override diff --git a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/TrelloOAuthFlowTest.java b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/TrelloOAuthFlowTest.java index df88c307392d7..3582c7ae40626 100644 --- a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/TrelloOAuthFlowTest.java +++ b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/TrelloOAuthFlowTest.java @@ -76,7 +76,7 @@ public LowLevelHttpResponse execute() throws IOException { @Test public void testGetSourceConsentUrl() throws IOException, InterruptedException, ConfigNotFoundException { final String consentUrl = - trelloOAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL); + trelloOAuthFlow.getSourceConsentUrl(workspaceId, definitionId, REDIRECT_URL, Jsons.emptyObject(), null); assertEquals("https://trello.com/1/OAuthAuthorizeToken?oauth_token=test_token", consentUrl); } diff --git a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/facebook/FacebookMarketingOAuthFlowTest.java b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/facebook/FacebookMarketingOAuthFlowTest.java index b0b878c6b3121..41110a64fb46b 100644 --- a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/facebook/FacebookMarketingOAuthFlowTest.java +++ b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/facebook/FacebookMarketingOAuthFlowTest.java @@ -5,7 +5,6 @@ package io.airbyte.oauth.flows.facebook; import com.fasterxml.jackson.databind.JsonNode; -import io.airbyte.commons.json.Jsons; import io.airbyte.oauth.BaseOAuthFlow; import io.airbyte.oauth.MoreOAuthParameters; import io.airbyte.oauth.flows.BaseOAuthFlowTest; @@ -38,8 +37,8 @@ protected Map getExpectedOutput() { } @Override - protected JsonNode getOutputOAuthSpecification() { - return Jsons.jsonNode(Map.of("access_token", Map.of("type", "String"))); + protected JsonNode getCompleteOAuthOutputSpecification() { + return getJsonSchema(Map.of("access_token", Map.of("type", "string"))); } @Override diff --git a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/facebook/FacebookPagesOAuthFlowTest.java b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/facebook/FacebookPagesOAuthFlowTest.java index 66e1c56ded25b..b458c39a72025 100644 --- a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/facebook/FacebookPagesOAuthFlowTest.java +++ b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/facebook/FacebookPagesOAuthFlowTest.java @@ -5,7 +5,6 @@ package io.airbyte.oauth.flows.facebook; import com.fasterxml.jackson.databind.JsonNode; -import io.airbyte.commons.json.Jsons; import io.airbyte.oauth.BaseOAuthFlow; import io.airbyte.oauth.MoreOAuthParameters; import io.airbyte.oauth.flows.BaseOAuthFlowTest; @@ -38,8 +37,8 @@ protected Map getExpectedOutput() { } @Override - protected JsonNode getOutputOAuthSpecification() { - return Jsons.jsonNode(Map.of("access_token", Map.of("type", "String"))); + protected JsonNode getCompleteOAuthOutputSpecification() { + return getJsonSchema(Map.of("access_token", Map.of("type", "string"))); } @Override diff --git a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/facebook/InstagramOAuthFlowTest.java b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/facebook/InstagramOAuthFlowTest.java index 628049c3862a2..f4ed295a23005 100644 --- a/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/facebook/InstagramOAuthFlowTest.java +++ b/airbyte-oauth/src/test/java/io/airbyte/oauth/flows/facebook/InstagramOAuthFlowTest.java @@ -5,7 +5,6 @@ package io.airbyte.oauth.flows.facebook; import com.fasterxml.jackson.databind.JsonNode; -import io.airbyte.commons.json.Jsons; import io.airbyte.oauth.BaseOAuthFlow; import io.airbyte.oauth.MoreOAuthParameters; import io.airbyte.oauth.flows.BaseOAuthFlowTest; @@ -38,8 +37,8 @@ protected Map getExpectedOutput() { } @Override - protected JsonNode getOutputOAuthSpecification() { - return Jsons.jsonNode(Map.of("access_token", Map.of("type", "String"))); + protected JsonNode getCompleteOAuthOutputSpecification() { + return getJsonSchema(Map.of("access_token", Map.of("type", "string"))); } @Override diff --git a/airbyte-scheduler/app/src/main/java/io/airbyte/scheduler/app/JobScheduler.java b/airbyte-scheduler/app/src/main/java/io/airbyte/scheduler/app/JobScheduler.java index 7e2db6e7b9006..7d9cb732d3912 100644 --- a/airbyte-scheduler/app/src/main/java/io/airbyte/scheduler/app/JobScheduler.java +++ b/airbyte-scheduler/app/src/main/java/io/airbyte/scheduler/app/JobScheduler.java @@ -56,7 +56,7 @@ public JobScheduler(final JobPersistence jobPersistence, new DefaultSyncJobFactory( new DefaultJobCreator(jobPersistence, configRepository), configRepository, - new OAuthConfigSupplier(configRepository, false, trackingClient))); + new OAuthConfigSupplier(configRepository, trackingClient))); } @Override diff --git a/airbyte-scheduler/persistence/src/main/java/io/airbyte/scheduler/persistence/job_factory/OAuthConfigSupplier.java b/airbyte-scheduler/persistence/src/main/java/io/airbyte/scheduler/persistence/job_factory/OAuthConfigSupplier.java index a1181d6611cf3..4654a9749b2e4 100644 --- a/airbyte-scheduler/persistence/src/main/java/io/airbyte/scheduler/persistence/job_factory/OAuthConfigSupplier.java +++ b/airbyte-scheduler/persistence/src/main/java/io/airbyte/scheduler/persistence/job_factory/OAuthConfigSupplier.java @@ -4,10 +4,15 @@ package io.airbyte.scheduler.persistence.job_factory; +import static com.fasterxml.jackson.databind.node.JsonNodeType.ARRAY; +import static com.fasterxml.jackson.databind.node.JsonNodeType.OBJECT; + import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.ImmutableMap; import io.airbyte.analytics.TrackingClient; +import io.airbyte.commons.json.Jsons; import io.airbyte.commons.lang.Exceptions; import io.airbyte.config.StandardDestinationDefinition; import io.airbyte.config.StandardSourceDefinition; @@ -18,17 +23,22 @@ import io.airbyte.scheduler.persistence.job_tracker.TrackingMetadata; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class OAuthConfigSupplier { + private static final Logger LOGGER = LoggerFactory.getLogger(OAuthConfigSupplier.class); + + public static final String PATH_IN_CONNECTOR_CONFIG = "path_in_connector_config"; final private ConfigRepository configRepository; - private final boolean maskSecrets; private final TrackingClient trackingClient; - public OAuthConfigSupplier(final ConfigRepository configRepository, final boolean maskSecrets, final TrackingClient trackingClient) { + public OAuthConfigSupplier(final ConfigRepository configRepository, final TrackingClient trackingClient) { this.configRepository = configRepository; - this.maskSecrets = maskSecrets; this.trackingClient = trackingClient; } @@ -39,23 +49,15 @@ public static boolean hasOAuthConfigSpecification(final ConnectorSpecification s public JsonNode injectSourceOAuthParameters(final UUID sourceDefinitionId, final UUID workspaceId, final JsonNode sourceConnectorConfig) throws IOException { try { - final ImmutableMap metadata = generateSourceMetadata(sourceDefinitionId); - // TODO there will be cases where we shouldn't write oauth params. See - // https://github.com/airbytehq/airbyte/issues/5989 + final StandardSourceDefinition sourceDefinition = configRepository.getStandardSourceDefinition(sourceDefinitionId); MoreOAuthParameters.getSourceOAuthParameter(configRepository.listSourceOAuthParam().stream(), workspaceId, sourceDefinitionId) - .ifPresent( - sourceOAuthParameter -> { - if (maskSecrets) { - // when maskSecrets = true, no real oauth injections is happening, only masked values - MoreOAuthParameters.mergeJsons( - (ObjectNode) sourceConnectorConfig, - (ObjectNode) sourceOAuthParameter.getConfiguration(), - MoreOAuthParameters.getSecretMask()); - } else { - MoreOAuthParameters.mergeJsons((ObjectNode) sourceConnectorConfig, (ObjectNode) sourceOAuthParameter.getConfiguration()); - Exceptions.swallow(() -> trackingClient.track(workspaceId, "OAuth Injection - Backend", metadata)); - } - }); + .ifPresent(sourceOAuthParameter -> { + if (injectOAuthParameters(sourceDefinition.getName(), sourceDefinition.getSpec(), sourceOAuthParameter.getConfiguration(), + sourceConnectorConfig)) { + final ImmutableMap metadata = TrackingMetadata.generateSourceDefinitionMetadata(sourceDefinition); + Exceptions.swallow(() -> trackingClient.track(workspaceId, "OAuth Injection - Backend", metadata)); + } + }); return sourceConnectorConfig; } catch (final JsonValidationException | ConfigNotFoundException e) { throw new IOException(e); @@ -67,17 +69,12 @@ public JsonNode injectDestinationOAuthParameters(final UUID destinationDefinitio final JsonNode destinationConnectorConfig) throws IOException { try { - final ImmutableMap metadata = generateDestinationMetadata(destinationDefinitionId); + final StandardDestinationDefinition destinationDefinition = configRepository.getStandardDestinationDefinition(destinationDefinitionId); MoreOAuthParameters.getDestinationOAuthParameter(configRepository.listDestinationOAuthParam().stream(), workspaceId, destinationDefinitionId) .ifPresent(destinationOAuthParameter -> { - if (maskSecrets) { - // when maskSecrets = true, no real oauth injections is happening, only masked values - MoreOAuthParameters.mergeJsons( - (ObjectNode) destinationConnectorConfig, - (ObjectNode) destinationOAuthParameter.getConfiguration(), - MoreOAuthParameters.getSecretMask()); - } else { - MoreOAuthParameters.mergeJsons((ObjectNode) destinationConnectorConfig, (ObjectNode) destinationOAuthParameter.getConfiguration()); + if (injectOAuthParameters(destinationDefinition.getName(), destinationDefinition.getSpec(), destinationOAuthParameter.getConfiguration(), + destinationConnectorConfig)) { + final ImmutableMap metadata = TrackingMetadata.generateDestinationDefinitionMetadata(destinationDefinition); Exceptions.swallow(() -> trackingClient.track(workspaceId, "OAuth Injection - Backend", metadata)); } }); @@ -87,16 +84,71 @@ public JsonNode injectDestinationOAuthParameters(final UUID destinationDefinitio } } - private ImmutableMap generateSourceMetadata(final UUID sourceDefinitionId) - throws JsonValidationException, ConfigNotFoundException, IOException { - final StandardSourceDefinition sourceDefinition = configRepository.getStandardSourceDefinition(sourceDefinitionId); - return TrackingMetadata.generateSourceDefinitionMetadata(sourceDefinition); + private static boolean injectOAuthParameters(final String connectorName, + final ConnectorSpecification spec, + final JsonNode oAuthParameters, + final JsonNode connectorConfig) { + if (!hasOAuthConfigSpecification(spec)) { + // keep backward compatible behavior if connector does not declare an OAuth config spec + MoreOAuthParameters.mergeJsons((ObjectNode) connectorConfig, (ObjectNode) oAuthParameters); + return true; + } + if (!checkOAuthPredicate(spec.getAdvancedAuth().getPredicateKey(), spec.getAdvancedAuth().getPredicateValue(), connectorConfig)) { + // OAuth is not applicable in this connectorConfig due to the predicate not being verified + return false; + } + // TODO: if we write a migration to flatten persisted configs in db, we don't need to flatten + // here see https://github.com/airbytehq/airbyte/issues/7624 + final JsonNode flatOAuthParameters = MoreOAuthParameters.flattenOAuthConfig(oAuthParameters); + final JsonNode outputSpec = spec.getAdvancedAuth().getOauthConfigSpecification().getCompleteOauthServerOutputSpecification(); + boolean result = false; + for (final String key : Jsons.keys(outputSpec)) { + final JsonNode node = outputSpec.get(key); + if (node.getNodeType() == OBJECT) { + final JsonNode pathNode = node.get(PATH_IN_CONNECTOR_CONFIG); + if (pathNode != null && pathNode.getNodeType() == ARRAY) { + final List propertyPath = new ArrayList<>(); + final ArrayNode arrayNode = (ArrayNode) pathNode; + for (int i = 0; i < arrayNode.size(); ++i) { + propertyPath.add(arrayNode.get(i).asText()); + } + if (propertyPath.size() > 0) { + Jsons.replaceNestedValue(connectorConfig, propertyPath, flatOAuthParameters.get(key)); + result = true; + } else { + LOGGER.error(String.format("In %s's advanced_auth spec, completeOAuthServerOutputSpecification includes an invalid empty %s for %s", + connectorName, PATH_IN_CONNECTOR_CONFIG, key)); + } + } else { + LOGGER.error( + String.format("In %s's advanced_auth spec, completeOAuthServerOutputSpecification does not declare an Array %s for %s", + connectorName, PATH_IN_CONNECTOR_CONFIG, key)); + } + } else { + LOGGER.error(String.format("In %s's advanced_auth spec, completeOAuthServerOutputSpecification does not declare an ObjectNode for %s", + connectorName, key)); + } + } + return result; } - private ImmutableMap generateDestinationMetadata(final UUID destinationDefinitionId) - throws JsonValidationException, ConfigNotFoundException, IOException { - final StandardDestinationDefinition destinationDefinition = configRepository.getStandardDestinationDefinition(destinationDefinitionId); - return TrackingMetadata.generateDestinationDefinitionMetadata(destinationDefinition); + private static boolean checkOAuthPredicate(final List predicateKey, final String predicateValue, final JsonNode connectorConfig) { + if (predicateKey != null && !predicateKey.isEmpty()) { + JsonNode node = connectorConfig; + for (final String key : predicateKey) { + if (node.has(key)) { + node = node.get(key); + } else { + return false; + } + } + if (predicateValue != null && !predicateValue.isBlank()) { + return node.asText().equals(predicateValue); + } else { + return true; + } + } + return true; } } diff --git a/airbyte-scheduler/persistence/src/test/java/io/airbyte/scheduler/persistence/job_factory/OAuthConfigSupplierTest.java b/airbyte-scheduler/persistence/src/test/java/io/airbyte/scheduler/persistence/job_factory/OAuthConfigSupplierTest.java index f3f5509e0acd5..6f060d0c032b6 100644 --- a/airbyte-scheduler/persistence/src/test/java/io/airbyte/scheduler/persistence/job_factory/OAuthConfigSupplierTest.java +++ b/airbyte-scheduler/persistence/src/test/java/io/airbyte/scheduler/persistence/job_factory/OAuthConfigSupplierTest.java @@ -15,14 +15,16 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.collect.ImmutableMap; import io.airbyte.analytics.TrackingClient; import io.airbyte.commons.json.Jsons; import io.airbyte.config.SourceOAuthParameter; import io.airbyte.config.StandardSourceDefinition; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; -import io.airbyte.oauth.MoreOAuthParameters; +import io.airbyte.protocol.models.AdvancedAuth; +import io.airbyte.protocol.models.AdvancedAuth.AuthFlowType; +import io.airbyte.protocol.models.ConnectorSpecification; +import io.airbyte.protocol.models.OAuthConfigSpecification; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; import java.util.List; @@ -33,6 +35,9 @@ public class OAuthConfigSupplierTest { + public static final String API_CLIENT = "api_client"; + public static final String CREDENTIALS = "credentials"; + private ConfigRepository configRepository; private TrackingClient trackingClient; private OAuthConfigSupplier oAuthConfigSupplier; @@ -42,16 +47,15 @@ public class OAuthConfigSupplierTest { public void setup() throws JsonValidationException, ConfigNotFoundException, IOException { configRepository = mock(ConfigRepository.class); trackingClient = mock(TrackingClient.class); - oAuthConfigSupplier = new OAuthConfigSupplier(configRepository, false, trackingClient); + oAuthConfigSupplier = new OAuthConfigSupplier(configRepository, trackingClient); sourceDefinitionId = UUID.randomUUID(); - when(configRepository.getStandardSourceDefinition(any())).thenReturn(new StandardSourceDefinition() - .withSourceDefinitionId(sourceDefinitionId) - .withName("test") - .withDockerImageTag("dev")); + setupStandardDefinitionMock(createAdvancedAuth() + .withPredicateKey(List.of(CREDENTIALS, "auth_type")) + .withPredicateValue("oauth")); } @Test - public void testInjectEmptyOAuthParameters() throws IOException { + public void testNoOAuthInjectionBecauseEmptyParams() throws IOException { final JsonNode config = generateJsonConfig(); final UUID workspaceId = UUID.randomUUID(); final JsonNode actualConfig = oAuthConfigSupplier.injectSourceOAuthParameters(sourceDefinitionId, workspaceId, Jsons.clone(config)); @@ -60,71 +64,168 @@ public void testInjectEmptyOAuthParameters() throws IOException { } @Test - public void testInjectGlobalOAuthParameters() throws JsonValidationException, IOException { + public void testNoOAuthInjectionBecauseMissingPredicateKey() throws IOException, JsonValidationException, ConfigNotFoundException { + setupStandardDefinitionMock(createAdvancedAuth() + .withPredicateKey(List.of("some_random_fields", "auth_type")) + .withPredicateValue("oauth")); final JsonNode config = generateJsonConfig(); final UUID workspaceId = UUID.randomUUID(); - final Map oauthParameters = generateOAuthParameters(); - when(configRepository.listSourceOAuthParam()).thenReturn(List.of( - new SourceOAuthParameter() - .withOauthParameterId(UUID.randomUUID()) + setupOAuthParamMocks(generateOAuthParameters()); + final JsonNode actualConfig = oAuthConfigSupplier.injectSourceOAuthParameters(sourceDefinitionId, workspaceId, Jsons.clone(config)); + assertEquals(config, actualConfig); + assertNoTracking(); + } + + @Test + public void testNoOAuthInjectionBecauseWrongPredicateValue() throws IOException, JsonValidationException, ConfigNotFoundException { + setupStandardDefinitionMock(createAdvancedAuth() + .withPredicateKey(List.of(CREDENTIALS, "auth_type")) + .withPredicateValue("wrong_auth_type")); + final JsonNode config = generateJsonConfig(); + final UUID workspaceId = UUID.randomUUID(); + setupOAuthParamMocks(generateOAuthParameters()); + final JsonNode actualConfig = oAuthConfigSupplier.injectSourceOAuthParameters(sourceDefinitionId, workspaceId, Jsons.clone(config)); + assertEquals(config, actualConfig); + assertNoTracking(); + } + + @Test + public void testOAuthInjection() throws JsonValidationException, IOException { + final JsonNode config = generateJsonConfig(); + final UUID workspaceId = UUID.randomUUID(); + final Map oauthParameters = generateOAuthParameters(); + setupOAuthParamMocks(oauthParameters); + final JsonNode actualConfig = oAuthConfigSupplier.injectSourceOAuthParameters(sourceDefinitionId, workspaceId, Jsons.clone(config)); + final JsonNode expectedConfig = getExpectedNode((String) oauthParameters.get(API_CLIENT)); + assertEquals(expectedConfig, actualConfig); + assertTracking(workspaceId); + } + + @Test + public void testOAuthInjectionWithoutPredicate() throws JsonValidationException, IOException, ConfigNotFoundException { + setupStandardDefinitionMock(createAdvancedAuth() + .withPredicateKey(null) + .withPredicateValue(null)); + final JsonNode config = generateJsonConfig(); + final UUID workspaceId = UUID.randomUUID(); + final Map oauthParameters = generateOAuthParameters(); + setupOAuthParamMocks(oauthParameters); + final JsonNode actualConfig = oAuthConfigSupplier.injectSourceOAuthParameters(sourceDefinitionId, workspaceId, Jsons.clone(config)); + final JsonNode expectedConfig = getExpectedNode((String) oauthParameters.get(API_CLIENT)); + assertEquals(expectedConfig, actualConfig); + assertTracking(workspaceId); + } + + @Test + public void testOAuthInjectionWithoutPredicateValue() throws JsonValidationException, IOException, ConfigNotFoundException { + setupStandardDefinitionMock(createAdvancedAuth() + .withPredicateKey(List.of(CREDENTIALS, "auth_type")) + .withPredicateValue("")); + final JsonNode config = generateJsonConfig(); + final UUID workspaceId = UUID.randomUUID(); + final Map oauthParameters = generateOAuthParameters(); + setupOAuthParamMocks(oauthParameters); + final JsonNode actualConfig = oAuthConfigSupplier.injectSourceOAuthParameters(sourceDefinitionId, workspaceId, Jsons.clone(config)); + final JsonNode expectedConfig = getExpectedNode((String) oauthParameters.get(API_CLIENT)); + assertEquals(expectedConfig, actualConfig); + assertTracking(workspaceId); + } + + @Test + public void testOAuthFullInjectionBecauseNoOAuthSpec() throws JsonValidationException, IOException, ConfigNotFoundException { + final JsonNode config = generateJsonConfig(); + final UUID workspaceId = UUID.randomUUID(); + final Map oauthParameters = generateOAuthParameters(); + when(configRepository.getStandardSourceDefinition(any())) + .thenReturn(new StandardSourceDefinition() .withSourceDefinitionId(sourceDefinitionId) - .withWorkspaceId(null) - .withConfiguration(Jsons.jsonNode(oauthParameters)), - new SourceOAuthParameter() - .withOauthParameterId(UUID.randomUUID()) - .withSourceDefinitionId(UUID.randomUUID()) - .withWorkspaceId(null) - .withConfiguration(Jsons.jsonNode(generateOAuthParameters())))); + .withName("test") + .withDockerImageTag("dev") + .withSpec(null)); + setupOAuthParamMocks(oauthParameters); final JsonNode actualConfig = oAuthConfigSupplier.injectSourceOAuthParameters(sourceDefinitionId, workspaceId, Jsons.clone(config)); final ObjectNode expectedConfig = ((ObjectNode) Jsons.clone(config)); for (final String key : oauthParameters.keySet()) { expectedConfig.set(key, Jsons.jsonNode(oauthParameters.get(key))); } assertEquals(expectedConfig, actualConfig); - verify(trackingClient, times(1)).track(workspaceId, "OAuth Injection - Backend", Map.of( - "connector_source", "test", - "connector_source_definition_id", sourceDefinitionId, - "connector_source_version", "dev")); + assertTracking(workspaceId); } @Test - public void testInjectWorkspaceOAuthParameters() throws JsonValidationException, IOException { + public void testOAuthInjectionScopedToWorkspace() throws JsonValidationException, IOException { final JsonNode config = generateJsonConfig(); final UUID workspaceId = UUID.randomUUID(); + final Map oauthParameters = generateOAuthParameters(); when(configRepository.listSourceOAuthParam()).thenReturn(List.of( new SourceOAuthParameter() .withOauthParameterId(UUID.randomUUID()) - .withSourceDefinitionId(sourceDefinitionId) + .withSourceDefinitionId(UUID.randomUUID()) .withWorkspaceId(null) .withConfiguration(Jsons.jsonNode(generateOAuthParameters())), new SourceOAuthParameter() .withOauthParameterId(UUID.randomUUID()) .withSourceDefinitionId(sourceDefinitionId) .withWorkspaceId(workspaceId) - .withConfiguration(Jsons.jsonNode(ImmutableMap.builder() - .put("api_secret", "my secret workspace") - .put("api_client", Map.of("anyOf", List.of(Map.of("id", "id"), Map.of("service", "account")))) - .build())))); + .withConfiguration(Jsons.jsonNode(oauthParameters)))); final JsonNode actualConfig = oAuthConfigSupplier.injectSourceOAuthParameters(sourceDefinitionId, workspaceId, Jsons.clone(config)); - final ObjectNode expectedConfig = (ObjectNode) Jsons.clone(config); - expectedConfig.set("api_secret", Jsons.jsonNode("my secret workspace")); - expectedConfig.set("api_client", Jsons.jsonNode(Map.of("anyOf", List.of( - Map.of("id", "id"), - Map.of("service", "account"))))); + final JsonNode expectedConfig = getExpectedNode((String) oauthParameters.get(API_CLIENT)); assertEquals(expectedConfig, actualConfig); - verify(trackingClient, times(1)).track(workspaceId, "OAuth Injection - Backend", Map.of( - "connector_source", "test", - "connector_source_definition_id", sourceDefinitionId, - "connector_source_version", "dev")); + assertTracking(workspaceId); } @Test - void testInjectMaskedOAuthParameters() throws JsonValidationException, IOException { - final OAuthConfigSupplier maskingSupplier = new OAuthConfigSupplier(configRepository, true, trackingClient); + public void testOAuthFullInjectionBecauseNoOAuthSpecNestedParameters() throws JsonValidationException, IOException, ConfigNotFoundException { + // Until https://github.com/airbytehq/airbyte/issues/7624 is solved, we need to handle nested oauth + // parameters + final JsonNode config = generateJsonConfig(); + final UUID workspaceId = UUID.randomUUID(); + final Map oauthParameters = generateNestedOAuthParameters(); + setupOAuthParamMocks(oauthParameters); + final JsonNode actualConfig = oAuthConfigSupplier.injectSourceOAuthParameters(sourceDefinitionId, workspaceId, Jsons.clone(config)); + final JsonNode expectedConfig = Jsons.jsonNode(Map.of( + "fieldName", "fieldValue", + CREDENTIALS, Map.of( + "api_secret", "123", + "auth_type", "oauth", + API_CLIENT, ((Map) oauthParameters.get(CREDENTIALS)).get(API_CLIENT)))); + assertEquals(expectedConfig, actualConfig); + assertTracking(workspaceId); + } + @Test + public void testOAuthInjectionNestedParameters() throws JsonValidationException, IOException { + // Until https://github.com/airbytehq/airbyte/issues/7624 is solved, we need to handle nested oauth + // parameters final JsonNode config = generateJsonConfig(); final UUID workspaceId = UUID.randomUUID(); - final Map oauthParameters = generateOAuthParameters(); + final Map oauthParameters = generateNestedOAuthParameters(); + setupOAuthParamMocks(oauthParameters); + final JsonNode actualConfig = oAuthConfigSupplier.injectSourceOAuthParameters(sourceDefinitionId, workspaceId, Jsons.clone(config)); + final JsonNode expectedConfig = getExpectedNode((String) ((Map) oauthParameters.get(CREDENTIALS)).get(API_CLIENT)); + assertEquals(expectedConfig, actualConfig); + assertTracking(workspaceId); + } + + private static AdvancedAuth createAdvancedAuth() { + return new AdvancedAuth() + .withAuthFlowType(AuthFlowType.OAUTH_2_0) + .withOauthConfigSpecification(new OAuthConfigSpecification() + .withCompleteOauthServerOutputSpecification(Jsons.jsonNode(Map.of( + API_CLIENT, Map.of( + "type", "string", + OAuthConfigSupplier.PATH_IN_CONNECTOR_CONFIG, List.of(CREDENTIALS, API_CLIENT)))))); + } + + private void setupStandardDefinitionMock(final AdvancedAuth advancedAuth) throws JsonValidationException, ConfigNotFoundException, IOException { + when(configRepository.getStandardSourceDefinition(any())).thenReturn(new StandardSourceDefinition() + .withSourceDefinitionId(sourceDefinitionId) + .withName("test") + .withDockerImageTag("dev") + .withSpec(new ConnectorSpecification().withAdvancedAuth(advancedAuth))); + } + + private void setupOAuthParamMocks(final Map oauthParameters) throws JsonValidationException, IOException { when(configRepository.listSourceOAuthParam()).thenReturn(List.of( new SourceOAuthParameter() .withOauthParameterId(UUID.randomUUID()) @@ -136,31 +237,46 @@ void testInjectMaskedOAuthParameters() throws JsonValidationException, IOExcepti .withSourceDefinitionId(UUID.randomUUID()) .withWorkspaceId(null) .withConfiguration(Jsons.jsonNode(generateOAuthParameters())))); - final JsonNode actualConfig = maskingSupplier.injectSourceOAuthParameters(sourceDefinitionId, workspaceId, Jsons.clone(config)); - final ObjectNode expectedConfig = ((ObjectNode) Jsons.clone(config)); - for (final String key : oauthParameters.keySet()) { - expectedConfig.set(key, MoreOAuthParameters.getSecretMask()); - } - assertEquals(expectedConfig, actualConfig); - assertNoTracking(); } - private ObjectNode generateJsonConfig() { - return (ObjectNode) Jsons.jsonNode(ImmutableMap.builder() - .put("apiSecret", "123") - .put("client", "testing") - .build()); + private static ObjectNode generateJsonConfig() { + return (ObjectNode) Jsons.jsonNode( + Map.of( + "fieldName", "fieldValue", + CREDENTIALS, Map.of( + "api_secret", "123", + "auth_type", "oauth"))); + } + + private static Map generateOAuthParameters() { + return Map.of( + "api_secret", "mysecret", + API_CLIENT, UUID.randomUUID().toString()); } - private Map generateOAuthParameters() { - return ImmutableMap.builder() - .put("api_secret", "mysecret") - .put("api_client", UUID.randomUUID().toString()) - .build(); + private static Map generateNestedOAuthParameters() { + return Map.of(CREDENTIALS, generateOAuthParameters()); + } + + private static JsonNode getExpectedNode(final String apiClient) { + return Jsons.jsonNode( + Map.of( + "fieldName", "fieldValue", + CREDENTIALS, Map.of( + "api_secret", "123", + "auth_type", "oauth", + API_CLIENT, apiClient))); } private void assertNoTracking() { verify(trackingClient, times(0)).track(any(), anyString(), anyMap()); } + private void assertTracking(final UUID workspaceId) { + verify(trackingClient, times(1)).track(workspaceId, "OAuth Injection - Backend", Map.of( + "connector_source", "test", + "connector_source_definition_id", sourceDefinitionId, + "connector_source_version", "dev")); + } + } diff --git a/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java b/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java index 4f4cafea6625d..81db4d09de75b 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java +++ b/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java @@ -213,7 +213,7 @@ public static ServerRunnable getServer(final ServerFactory apiFactory, final Con final JobTracker jobTracker = new JobTracker(configRepository, jobPersistence, trackingClient); final WorkflowServiceStubs temporalService = TemporalUtils.createTemporalService(configs.getTemporalHost()); final TemporalClient temporalClient = TemporalClient.production(configs.getTemporalHost(), configs.getWorkspaceRoot()); - final OAuthConfigSupplier oAuthConfigSupplier = new OAuthConfigSupplier(configRepository, false, trackingClient); + final OAuthConfigSupplier oAuthConfigSupplier = new OAuthConfigSupplier(configRepository, trackingClient); final SchedulerJobClient schedulerJobClient = new DefaultSchedulerJobClient(jobPersistence, new DefaultJobCreator(jobPersistence, configRepository)); final DefaultSynchronousSchedulerClient syncSchedulerClient = diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java b/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java index bf43f35bf979e..ef28a297055a7 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java @@ -179,7 +179,7 @@ public ConfigurationApi(final ConfigRepository configRepository, jobPersistence, jobNotifier, temporalService, - new OAuthConfigSupplier(configRepository, false, trackingClient), workerEnvironment, logConfigs); + new OAuthConfigSupplier(configRepository, trackingClient), workerEnvironment, logConfigs); final WorkspaceHelper workspaceHelper = new WorkspaceHelper(configRepository, jobPersistence); sourceDefinitionsHandler = new SourceDefinitionsHandler(configRepository, synchronousSchedulerClient); connectionsHandler = new ConnectionsHandler(configRepository, workspaceHelper, trackingClient); diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/OAuthHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/OAuthHandler.java index 225afd29f6ea7..83460cd484616 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/OAuthHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/OAuthHandler.java @@ -57,13 +57,23 @@ public OAuthConsentRead getSourceOAuthConsent(final SourceOauthConsentRequest so throws JsonValidationException, ConfigNotFoundException, IOException { final StandardSourceDefinition sourceDefinition = configRepository.getStandardSourceDefinition(sourceDefinitionIdRequestBody.getSourceDefinitionId()); - final OAuthFlowImplementation oAuthFlowImplementation = - oAuthImplementationFactory.create(sourceDefinition); + final OAuthFlowImplementation oAuthFlowImplementation = oAuthImplementationFactory.create(sourceDefinition); + final ConnectorSpecification spec = specFetcher.getSpec(sourceDefinition); final ImmutableMap metadata = generateSourceMetadata(sourceDefinitionIdRequestBody.getSourceDefinitionId()); - final OAuthConsentRead result = new OAuthConsentRead().consentUrl(oAuthFlowImplementation.getSourceConsentUrl( - sourceDefinitionIdRequestBody.getWorkspaceId(), - sourceDefinitionIdRequestBody.getSourceDefinitionId(), - sourceDefinitionIdRequestBody.getRedirectUrl())); + final OAuthConsentRead result; + if (OAuthConfigSupplier.hasOAuthConfigSpecification(spec)) { + result = new OAuthConsentRead().consentUrl(oAuthFlowImplementation.getSourceConsentUrl( + sourceDefinitionIdRequestBody.getWorkspaceId(), + sourceDefinitionIdRequestBody.getSourceDefinitionId(), + sourceDefinitionIdRequestBody.getRedirectUrl(), + sourceDefinitionIdRequestBody.getoAuthInputConfiguration(), + spec.getAdvancedAuth().getOauthConfigSpecification())); + } else { + result = new OAuthConsentRead().consentUrl(oAuthFlowImplementation.getSourceConsentUrl( + sourceDefinitionIdRequestBody.getWorkspaceId(), + sourceDefinitionIdRequestBody.getSourceDefinitionId(), + sourceDefinitionIdRequestBody.getRedirectUrl(), Jsons.emptyObject(), null)); + } try { trackingClient.track(sourceDefinitionIdRequestBody.getWorkspaceId(), "Get Oauth Consent URL - Backend", metadata); } catch (final Exception e) { @@ -76,13 +86,23 @@ public OAuthConsentRead getDestinationOAuthConsent(final DestinationOauthConsent throws JsonValidationException, ConfigNotFoundException, IOException { final StandardDestinationDefinition destinationDefinition = configRepository.getStandardDestinationDefinition(destinationDefinitionIdRequestBody.getDestinationDefinitionId()); - final OAuthFlowImplementation oAuthFlowImplementation = - oAuthImplementationFactory.create(destinationDefinition); + final OAuthFlowImplementation oAuthFlowImplementation = oAuthImplementationFactory.create(destinationDefinition); + final ConnectorSpecification spec = specFetcher.getSpec(destinationDefinition); final ImmutableMap metadata = generateDestinationMetadata(destinationDefinitionIdRequestBody.getDestinationDefinitionId()); - final OAuthConsentRead result = new OAuthConsentRead().consentUrl(oAuthFlowImplementation.getDestinationConsentUrl( - destinationDefinitionIdRequestBody.getWorkspaceId(), - destinationDefinitionIdRequestBody.getDestinationDefinitionId(), - destinationDefinitionIdRequestBody.getRedirectUrl())); + final OAuthConsentRead result; + if (OAuthConfigSupplier.hasOAuthConfigSpecification(spec)) { + result = new OAuthConsentRead().consentUrl(oAuthFlowImplementation.getDestinationConsentUrl( + destinationDefinitionIdRequestBody.getWorkspaceId(), + destinationDefinitionIdRequestBody.getDestinationDefinitionId(), + destinationDefinitionIdRequestBody.getRedirectUrl(), + destinationDefinitionIdRequestBody.getoAuthInputConfiguration(), + spec.getAdvancedAuth().getOauthConfigSpecification())); + } else { + result = new OAuthConsentRead().consentUrl(oAuthFlowImplementation.getDestinationConsentUrl( + destinationDefinitionIdRequestBody.getWorkspaceId(), + destinationDefinitionIdRequestBody.getDestinationDefinitionId(), + destinationDefinitionIdRequestBody.getRedirectUrl(), Jsons.emptyObject(), null)); + } try { trackingClient.track(destinationDefinitionIdRequestBody.getWorkspaceId(), "Get Oauth Consent URL - Backend", metadata); } catch (final Exception e) { @@ -161,6 +181,8 @@ public void setSourceInstancewideOauthParams(final SetInstancewideSourceOauthPar .orElseGet(() -> new SourceOAuthParameter().withOauthParameterId(UUID.randomUUID())) .withConfiguration(Jsons.jsonNode(requestBody.getParams())) .withSourceDefinitionId(requestBody.getSourceDefinitionId()); + // TODO validate requestBody.getParams() against + // spec.getAdvancedAuth().getOauthConfigSpecification().getCompleteOauthServerInputSpecification() configRepository.writeSourceOAuthParam(param); } @@ -171,6 +193,8 @@ public void setDestinationInstancewideOauthParams(final SetInstancewideDestinati .orElseGet(() -> new DestinationOAuthParameter().withOauthParameterId(UUID.randomUUID())) .withConfiguration(Jsons.jsonNode(requestBody.getParams())) .withDestinationDefinitionId(requestBody.getDestinationDefinitionId()); + // TODO validate requestBody.getParams() against + // spec.getAdvancedAuth().getOauthConfigSpecification().getCompleteOauthServerInputSpecification() configRepository.writeDestinationOAuthParam(param); }