Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎉 Source Mailchimp: support oauth flow #7159

Merged
merged 38 commits into from
Jan 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c27c4d9
add mailchimp oauth support
annalvova05 Oct 19, 2021
1b7715f
add PR
annalvova05 Oct 19, 2021
226b93e
fix creds
annalvova05 Oct 19, 2021
38a5c2f
upd spec
annalvova05 Oct 19, 2021
304dc83
format
annalvova05 Oct 19, 2021
6d72df0
upd creds
annalvova05 Oct 27, 2021
908e15a
upd auth for different creds
annalvova05 Oct 27, 2021
f874716
rename creds
annalvova05 Oct 27, 2021
876b721
rename creds
annalvova05 Oct 27, 2021
6512c62
change ref in campaigns.json
annalvova05 Oct 28, 2021
259ad09
Merge remote-tracking branch 'origin/master' into alvova/6273-add-oau…
annalvova05 Oct 28, 2021
9984e98
Merge remote-tracking branch 'origin/master' into alvova/6273-add-oau…
annalvova05 Oct 29, 2021
df6bacf
upd timeout_seconds
annalvova05 Oct 29, 2021
6ae743e
Merge remote-tracking branch 'origin/master' into alvova/6273-add-oau…
annalvova05 Dec 23, 2021
f439a92
merge
annalvova05 Dec 23, 2021
f7d4391
add oauth java part
annalvova05 Dec 23, 2021
fc5e899
add java test
annalvova05 Dec 23, 2021
277a00b
bump version
annalvova05 Dec 23, 2021
9070cba
update spec
annalvova05 Dec 24, 2021
43593d5
add anotation
annalvova05 Dec 24, 2021
9b638b5
upd spec
annalvova05 Dec 24, 2021
5b6c928
Merge remote-tracking branch 'origin/master' into alvova/6273-add-oau…
annalvova05 Dec 24, 2021
862c286
upd spec
annalvova05 Dec 24, 2021
faedb6b
upd
annalvova05 Dec 28, 2021
86f766f
Merge remote-tracking branch 'origin/master' into alvova/6273-add-oau…
annalvova05 Dec 29, 2021
c32dac4
upd tests
annalvova05 Dec 29, 2021
e3f2a0a
format
annalvova05 Jan 10, 2022
c4710d4
Merge remote-tracking branch 'origin/master' into alvova/6273-add-oau…
annalvova05 Jan 10, 2022
a9334d2
Merge remote-tracking branch 'origin/master' into alvova/6273-add-oau…
annalvova05 Jan 10, 2022
6789d7b
upd
annalvova05 Jan 11, 2022
5c2ff1f
upd
annalvova05 Jan 11, 2022
2fdb304
add state
annalvova05 Jan 12, 2022
99ad78a
Merge remote-tracking branch 'origin/master' into alvova/6273-add-oau…
annalvova05 Jan 12, 2022
8511bc0
add invalid_config
annalvova05 Jan 13, 2022
105db17
Merge remote-tracking branch 'origin/master' into alvova/6273-add-oau…
annalvova05 Jan 13, 2022
95ea02a
bump version
annalvova05 Jan 17, 2022
4fa7eb6
Merge remote-tracking branch 'origin/master' into alvova/6273-add-oau…
annalvova05 Jan 17, 2022
16bb4b3
format
annalvova05 Jan 17, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@
- name: Mailchimp
sourceDefinitionId: b03a9f3e-22a5-11eb-adc1-0242ac120002
dockerRepository: airbyte/source-mailchimp
dockerImageTag: 0.2.10
dockerImageTag: 0.2.11
documentationUrl: https://docs.airbyte.io/integrations/sources/mailchimp
icon: mailchimp.svg
sourceType: api
Expand Down
108 changes: 93 additions & 15 deletions airbyte-config/init/src/main/resources/seed/source_specs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3764,31 +3764,109 @@
supportsNormalization: false
supportsDBT: false
supported_destination_sync_modes: []
- dockerImage: "airbyte/source-mailchimp:0.2.10"
- dockerImage: "airbyte/source-mailchimp:0.2.11"
spec:
documentationUrl: "https://docs.airbyte.io/integrations/sources/mailchimp"
connectionSpecification:
$schema: "http://json-schema.org/draft-07/schema#"
title: "Mailchimp Spec"
type: "object"
required:
- "username"
- "apikey"
additionalProperties: false
required: []
additionalProperties: true
properties:
username:
type: "string"
title: "Username"
description: "The Username or email you use to sign into Mailchimp."
apikey:
type: "string"
airbyte_secret: true
title: "API Key"
description: "Mailchimp API Key. See the <a href=\"https://docs.airbyte.io/integrations/sources/mailchimp\"\
>docs</a> for information on how to generate this key."
credentials:
type: "object"
title: "Authentication Method"
oneOf:
- title: "OAuth2.0"
type: "object"
required:
- "auth_type"
- "access_token"
properties:
auth_type:
type: "string"
const: "oauth2.0"
enum:
- "oauth2.0"
default: "oauth2.0"
order: 0
client_id:
title: "Client ID"
type: "string"
description: "The Client ID of your OAuth application."
airbyte_secret: true
client_secret:
title: "Client Secret"
type: "string"
description: "The Client Secret of your OAuth application."
airbyte_secret: true
access_token:
title: "Access Token"
type: "string"
description: "An access token generated using the above client ID\
\ and secret."
airbyte_secret: true
- type: "object"
title: "API Key"
required:
- "auth_type"
- "apikey"
properties:
auth_type:
type: "string"
const: "apikey"
enum:
- "apikey"
default: "apikey"
order: 1
apikey:
type: "string"
title: "API Key"
description: "Mailchimp API Key. See the <a href=\"https://docs.airbyte.io/integrations/sources/mailchimp\"\
>docs</a> for information on how to generate this key."
airbyte_secret: true
supportsNormalization: false
supportsDBT: false
supported_destination_sync_modes: []
advanced_auth:
auth_flow_type: "oauth2.0"
predicate_key:
- "credentials"
- "auth_type"
predicate_value: "oauth2.0"
oauth_config_specification:
complete_oauth_output_specification:
type: "object"
additionalProperties: false
properties:
access_token:
type: "string"
path_in_connector_config:
- "credentials"
- "access_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:
- "credentials"
- "client_id"
client_secret:
type: "string"
path_in_connector_config:
- "credentials"
- "client_secret"
- dockerImage: "airbyte/source-mailgun:0.1.0"
spec:
documentationUrl: "https://docs.airbyte.io/integrations/sources/mailgun"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.2.10
LABEL io.airbyte.version=0.2.11
LABEL io.airbyte.name=airbyte/source-mailchimp
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,41 @@ connector_image: airbyte/source-mailchimp:dev
tests:
spec:
- spec_path: "source_mailchimp/spec.json"
timeout_seconds: 60
connection:
# for old spec config (without oneOf)
- config_path: "secrets/config.json"
status: "succeed"
timeout_seconds: 180
# for auth with API token
- config_path: "secrets/config_apikey.json"
status: "succeed"
timeout_seconds: 180
# for auth with oauth2 token
- config_path: "secrets/config_oauth.json"
status: "succeed"
timeout_seconds: 180
- config_path: "integration_tests/invalid_config.json"
status: "failed"
timeout_seconds: 180
- config_path: "integration_tests/invalid_config_apikey.json"
status: "failed"
timeout_seconds: 180
- config_path: "integration_tests/invalid_config_oauth.json"
status: "failed"
timeout_seconds: 180
discovery:
# for old spec config (without oneOf)
- config_path: "secrets/config.json"
# for auth with API token
- config_path: "secrets/config_apikey.json"
# for auth with oauth2 token
- config_path: "secrets/config_oauth.json"
basic_read:
- config_path: "secrets/config.json"
configured_catalog_path: "integration_tests/configured_catalog.json"
- config_path: "secrets/config_oauth.json"
configured_catalog_path: "integration_tests/configured_catalog.json"
# THIS TEST IS COMMENTED OUT. Tests are supposed to accept
# `state = {cursor_field: value}`. When we have dependent endpoint path
# `path_begin/{some_id}/path_end` we need a complex state like below:
Expand All @@ -30,3 +55,5 @@ tests:
full_refresh:
- config_path: "secrets/config.json"
configured_catalog_path: "integration_tests/configured_catalog.json"
- config_path: "secrets/config_oauth.json"
configured_catalog_path: "integration_tests/configured_catalog.json"

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"credentials": {
"auth_type": "apikey",
"apikey": "api-key-awesome"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"credentials": {
"auth_type": "oauth2.0",
"client_id": "client_id",
"client_secret": "client_secret",
"access_token": "access_token"
}
}
1 change: 0 additions & 1 deletion airbyte-integrations/connectors/source-mailchimp/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
packages=find_packages(),
install_requires=[
"airbyte-cdk~=0.1.35",
"mailchimp3==3.0.14",
"pytest~=6.1",
],
package_data={"": ["*.json", "schemas/*.json", "schemas/shared/*.json"]},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,61 @@
import base64
from typing import Any, List, Mapping, Tuple

import requests
from airbyte_cdk import AirbyteLogger
from airbyte_cdk.sources import AbstractSource
from airbyte_cdk.sources.streams import Stream
from airbyte_cdk.sources.streams.http.auth import TokenAuthenticator
from mailchimp3 import MailChimp
from requests.auth import AuthBase

from .streams import Campaigns, EmailActivity, Lists


class HttpBasicAuthenticator(TokenAuthenticator):
def __init__(self, auth: Tuple[str, str], auth_method: str = "Basic", **kwargs):
# API keys have the format <key>-<data_center>.
# See https://mailchimp.com/developer/marketing/docs/fundamentals/#api-structure
self.data_center = auth[1].split("-").pop()
auth_string = f"{auth[0]}:{auth[1]}".encode("utf8")
b64_encoded = base64.b64encode(auth_string).decode("utf8")
super().__init__(token=b64_encoded, auth_method=auth_method, **kwargs)
class MailChimpAuthenticator:
@staticmethod
def get_server_prefix(access_token: str) -> str:
try:
response = requests.get(
"https://login.mailchimp.com/oauth2/metadata", headers={"Authorization": "OAuth {}".format(access_token)}
)
return response.json()["dc"]
except Exception as e:
raise Exception(f"Cannot retrieve server_prefix for you account. \n {repr(e)}")

def get_auth(self, config: Mapping[str, Any]) -> AuthBase:
authorization = config.get("credentials", {})
auth_type = authorization.get("auth_type")
if auth_type == "apikey" or not authorization:
# API keys have the format <key>-<data_center>.
# See https://mailchimp.com/developer/marketing/docs/fundamentals/#api-structure
apikey = authorization.get("apikey") or config.get("apikey")
if not apikey:
raise Exception("No apikey in creds")
auth_string = f"anystring:{apikey}".encode("utf8")
b64_encoded = base64.b64encode(auth_string).decode("utf8")
auth = TokenAuthenticator(token=b64_encoded, auth_method="Basic")
auth.data_center = apikey.split("-").pop()

elif auth_type == "oauth2.0":
access_token = authorization["access_token"]
auth = TokenAuthenticator(token=access_token, auth_method="Bearer")
auth.data_center = self.get_server_prefix(access_token)

else:
raise Exception(f"Invalid auth type: {auth_type}")

return auth


class SourceMailchimp(AbstractSource):
def check_connection(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> Tuple[bool, Any]:
try:
client = MailChimp(mc_api=config["apikey"], mc_user=config["username"])
client.ping.get()
authenticator = MailChimpAuthenticator().get_auth(config)
requests.get(f"https://{authenticator.data_center}.api.mailchimp.com/3.0/ping", headers=authenticator.get_auth_header())
return True, None
except Exception as e:
return False, repr(e)

def streams(self, config: Mapping[str, Any]) -> List[Stream]:
authenticator = HttpBasicAuthenticator(auth=("anystring", config["apikey"]))
streams_ = [Lists(authenticator=authenticator), Campaigns(authenticator=authenticator), EmailActivity(authenticator=authenticator)]

return streams_
authenticator = MailChimpAuthenticator().get_auth(config)
return [Lists(authenticator=authenticator), Campaigns(authenticator=authenticator), EmailActivity(authenticator=authenticator)]
Loading