diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 422340d56c20..af8e1c7fd30e 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -15,7 +15,7 @@ - name: Amazon Ads sourceDefinitionId: c6b0a29e-1da9-4512-9002-7bfd0cba2246 dockerRepository: airbyte/source-amazon-ads - dockerImageTag: 0.1.3 + dockerImageTag: 0.1.4 documentationUrl: https://docs.airbyte.io/integrations/sources/amazon-ads icon: amazonads.svg sourceType: api diff --git a/airbyte-integrations/connectors/source-amazon-ads/Dockerfile b/airbyte-integrations/connectors/source-amazon-ads/Dockerfile index 1d61ce7afb90..5d7d367c99f2 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/Dockerfile +++ b/airbyte-integrations/connectors/source-amazon-ads/Dockerfile @@ -12,5 +12,5 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.3 +LABEL io.airbyte.version=0.1.4 LABEL io.airbyte.name=airbyte/source-amazon-ads diff --git a/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/source.py b/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/source.py index 9604dd7819d9..4f25885bfbce 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/source.py +++ b/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/source.py @@ -12,7 +12,7 @@ from airbyte_cdk.sources.streams.http.auth import Oauth2Authenticator from .schemas import Profile -from .spec import AmazonAdsConfig +from .spec import AmazonAdsConfig, advanced_auth from .streams import ( Profiles, SponsoredBrandsAdGroups, @@ -95,6 +95,7 @@ def spec(self, *args) -> ConnectorSpecification: return ConnectorSpecification( documentationUrl="https://docs.airbyte.io/integrations/sources/amazon-ads", connectionSpecification=AmazonAdsConfig.schema(), + advanced_auth=advanced_auth, ) @staticmethod diff --git a/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/spec.py b/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/spec.py index 01bf840cfd69..08e5a185e52b 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/spec.py +++ b/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/spec.py @@ -4,6 +4,7 @@ from typing import List +from airbyte_cdk.models import AdvancedAuth, AuthFlowType, OAuthConfigSpecification from pydantic import BaseModel, Field from source_amazon_ads.constants import AmazonAdsRegion @@ -12,6 +13,8 @@ class AmazonAdsConfig(BaseModel): class Config: title = "Amazon Ads Spec" + auth_type: str = Field(default="oauth2.0", const=True, order=0, enum=["oauth2.0"]) + client_id: str = Field( name="Client ID", description=( @@ -79,3 +82,30 @@ def schema(cls, **kvargs): schema["properties"]["region"].pop("allOf", None) schema["properties"]["region"].pop("$ref", None) return schema + + +advanced_auth = AdvancedAuth( + auth_flow_type=AuthFlowType.oauth2_0, + predicate_key=["auth_type"], + predicate_value="oauth2.0", + oauth_config_specification=OAuthConfigSpecification( + complete_oauth_output_specification={ + "type": "object", + "additionalProperties": False, + "properties": {"refresh_token": {"type": "string", "path_in_connector_config": ["refresh_token"]}}, + }, + complete_oauth_server_input_specification={ + "type": "object", + "additionalProperties": False, + "properties": {"client_id": {"type": "string"}, "client_secret": {"type": "string"}}, + }, + complete_oauth_server_output_specification={ + "type": "object", + "additionalProperties": False, + "properties": { + "client_id": {"type": "string", "path_in_connector_config": ["client_id"]}, + "client_secret": {"type": "string", "path_in_connector_config": ["client_secret"]}, + }, + }, + ), +) diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/OAuthImplementationFactory.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/OAuthImplementationFactory.java index 2f01f2bc9d6a..321995fc60af 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/OAuthImplementationFactory.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/OAuthImplementationFactory.java @@ -26,6 +26,7 @@ public class OAuthImplementationFactory { public OAuthImplementationFactory(final ConfigRepository configRepository, final HttpClient httpClient) { OAUTH_FLOW_MAPPING = ImmutableMap.builder() + .put("airbyte/source-amazon-ads", new AmazonAdsOAuthFlow(configRepository, httpClient)) .put("airbyte/source-asana", new AsanaOAuthFlow(configRepository, httpClient)) .put("airbyte/source-facebook-marketing", new FacebookMarketingOAuthFlow(configRepository, httpClient)) .put("airbyte/source-facebook-pages", new FacebookPagesOAuthFlow(configRepository, httpClient)) diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/AmazonAdsOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/AmazonAdsOAuthFlow.java new file mode 100644 index 000000000000..2a293e7f7b12 --- /dev/null +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/AmazonAdsOAuthFlow.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021 Airbyte, Inc., all rights reserved. + */ + +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; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.util.Map; +import java.util.UUID; +import java.util.function.Supplier; +import org.apache.http.client.utils.URIBuilder; + +public class AmazonAdsOAuthFlow extends BaseOAuth2Flow { + + private static final String AUTHORIZE_URL = "https://www.amazon.com/ap/oa"; + private static final String ACCESS_TOKEN_URL = "https://api.amazon.com/auth/o2/token"; + + public AmazonAdsOAuthFlow(final ConfigRepository configRepository, final HttpClient httpClient) { + super(configRepository, httpClient); + } + + public AmazonAdsOAuthFlow(final ConfigRepository configRepository, final HttpClient httpClient, final Supplier stateSupplier) { + super(configRepository, httpClient, stateSupplier); + } + + /** + * 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, + final JsonNode inputOAuthConfiguration) + throws IOException { + try { + return new URIBuilder(AUTHORIZE_URL) + .addParameter("client_id", clientId) + .addParameter("scope", "profile") + .addParameter("response_type", "code") + .addParameter("redirect_uri", redirectUrl) + .addParameter("state", getState()) + .build().toString(); + } catch (final URISyntaxException e) { + throw new IOException("Failed to format Consent URL for OAuth flow", e); + } + } + + @Override + protected Map getAccessTokenQueryParameters(final String clientId, + final String clientSecret, + final String authCode, + final String redirectUrl) { + return ImmutableMap.builder() + // required + .put("client_id", clientId) + .put("redirect_uri", redirectUrl) + .put("client_secret", clientSecret) + .put("code", authCode) + .put("grant_type", "authorization_code") + .build(); + } + + /** + * Returns the URL where to retrieve the access token from. + * + */ + @Override + protected String getAccessTokenUrl(final JsonNode inputOAuthConfiguration) { + return ACCESS_TOKEN_URL; + } + +} diff --git a/docs/integrations/sources/amazon-ads.md b/docs/integrations/sources/amazon-ads.md index d621784426b6..1c0f7aa838b8 100644 --- a/docs/integrations/sources/amazon-ads.md +++ b/docs/integrations/sources/amazon-ads.md @@ -76,6 +76,7 @@ Start date used for generating reports starting from the specified start date. S | Version | Date | Pull Request | Subject | | :--- | :--- | :--- | :--- | +| `0.1.4` | 2022-03-25 | [\#](https://github.com/airbytehq/airbyte/pull/) | `Added support OAuth2.0` | | `0.1.3` | 2021-12-28 | [\#8388](https://github.com/airbytehq/airbyte/pull/8388) | `Add retry if recoverable error occured for reporting stream processing` | | `0.1.2` | 2021-10-01 | [\#6367](https://github.com/airbytehq/airbyte/pull/6461) | `Add option to pull data for different regions. Add option to choose profiles we want to pull data. Add lookback` | | `0.1.1` | 2021-09-22 | [\#6367](https://github.com/airbytehq/airbyte/pull/6367) | `Add seller and vendor filters to profiles stream` |