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 Drift: add OAuth 2.0 authentication option #7337

Merged
merged 14 commits into from
Nov 22, 2021
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"sourceDefinitionId": "445831eb-78db-4b1f-8f1f-0d96ad8739e2",
"name": "Drift",
"dockerRepository": "airbyte/source-drift",
"dockerImageTag": "0.2.3",
"dockerImageTag": "0.2.4",
"documentationUrl": "https://docs.airbyte.io/integrations/sources/drift",
"icon": "drift.svg"
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
- name: Drift
sourceDefinitionId: 445831eb-78db-4b1f-8f1f-0d96ad8739e2
dockerRepository: airbyte/source-drift
dockerImageTag: 0.2.3
dockerImageTag: 0.2.4
documentationUrl: https://docs.airbyte.io/integrations/sources/drift
icon: drift.svg
sourceType: api
Expand Down
82 changes: 73 additions & 9 deletions airbyte-config/init/src/main/resources/seed/source_specs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1112,25 +1112,89 @@
supportsNormalization: false
supportsDBT: false
supported_destination_sync_modes: []
- dockerImage: "airbyte/source-drift:0.2.3"
- dockerImage: "airbyte/source-drift:0.2.4"
spec:
documentationUrl: "https://docs.airbyte.io/integrations/sources/drift"
connectionSpecification:
$schema: "http://json-schema.org/draft-07/schema#"
title: "Drift Spec"
type: "object"
required:
- "access_token"
additionalProperties: false
required: []
additionalProperties: true
properties:
access_token:
type: "string"
description: "Drift Access Token. See the <a href=\"https://docs.airbyte.io/integrations/sources/drift\"\
>docs</a> for more information on how to generate this key."
airbyte_secret: true
credentials:
title: "Authorization Method"
type: "object"
oneOf:
- type: "object"
title: "OAuth2.0"
required:
- "client_id"
- "client_secret"
- "access_token"
- "refresh_token"
properties:
credentials:
type: "string"
const: "oauth2.0"
enum:
- "oauth2.0"
default: "oauth2.0"
order: 0
client_id:
type: "string"
title: "Client ID"
description: "The Client ID of your Drift developer application."
airbyte_secret: true
client_secret:
type: "string"
title: "Client Secret"
description: "The Client Secret of your Drift developer application."
airbyte_secret: true
access_token:
type: "string"
title: "Access Token"
description: "Access Token for making authenticated requests."
airbyte_secret: true
refresh_token:
type: "string"
title: "Refresh Token"
description: "Refresh Token to renew the expired access_token."
default: ""
airbyte_secret: true
- title: "Access Token"
type: "object"
required:
- "access_token"
properties:
credentials:
type: "string"
const: "access_token"
enum:
- "access_token"
default: "access_token"
order: 0
access_token:
type: "string"
title: "Access Token"
description: "Drift Access Token. See the <a href=\"https://docs.airbyte.io/integrations/sources/drift\"\
>docs</a> for more information on how to generate this key."
airbyte_secret: true
supportsNormalization: false
supportsDBT: false
supported_destination_sync_modes: []
authSpecification:
auth_type: "oauth2.0"
oauth2Specification:
rootObject:
- "credentials"
- "0"
oauthFlowInitParameters:
- - "client_id"
- - "client_secret"
oauthFlowOutputParameters:
- - "access_token"
- - "refresh_token"
- dockerImage: "airbyte/source-exchange-rates:0.2.5"
spec:
documentationUrl: "https://docs.airbyte.io/integrations/sources/exchangeratesapi"
Expand Down
2 changes: 1 addition & 1 deletion airbyte-integrations/connectors/source-drift/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ COPY source_drift ./source_drift
ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py"
ENTRYPOINT ["python", "/airbyte/integration_code/main.py"]

LABEL io.airbyte.version=0.2.3
LABEL io.airbyte.version=0.2.4
LABEL io.airbyte.name=airbyte/source-drift
18 changes: 14 additions & 4 deletions airbyte-integrations/connectors/source-drift/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ and place them into `secrets/config.json`.

### Locally running the connector
```
python main_dev.py spec
python main_dev.py check --config secrets/config.json
python main_dev.py discover --config secrets/config.json
python main_dev.py read --config secrets/config.json --catalog sample_files/configured_catalog.json
python main.py spec
python main.py check --config secrets/config.json
python main.py discover --config secrets/config.json
python main.py read --config secrets/config.json --catalog sample_files/configured_catalog.json
```

### Unit Tests
Expand Down Expand Up @@ -88,6 +88,16 @@ docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/sample_files:/sample_files
1. To run additional integration tests, place your integration tests in a new directory `integration_tests` and run them with `python -m pytest -s integration_tests`.
Make sure to familiarize yourself with [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) to know how your test files and methods should be named.

#### Acceptance Tests
Customize `acceptance-test-config.yml` file to configure tests. See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) for more information.
If your connector requires to create or destroy resources for use during acceptance tests create fixtures for it and place them inside integration_tests/acceptance.py.
To run your integration tests with acceptance tests, from the connector root, run
```
docker build . --no-cache -t airbyte/source-drift:dev \
&& python -m pytest -p source_acceptance_test.plugin
```
To run your integration tests with docker

## Dependency Management
All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{
"access_token": "invalid_access_token"
}
"access_token": "invalid_access_token",
"credentials": {
"credentials": "access_token",
"access_token": "migrated_from_old_config"
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"access_token": "1234567890abcdefghijk"
}
"credentials": {
"access_token": "123412341234sfsdfs"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,30 @@
#


from typing import Iterator, Tuple
from typing import Dict, Iterator, Tuple

from airbyte_cdk.sources.deprecated.client import BaseClient

from .api import APIClient
from .common import AuthError, ValidationError


class DriftAuthenticator:
def __init__(self, config: Dict):
self.config = config

def get_token(self) -> str:
access_token = self.config.get("access_token")
if access_token:
return access_token
else:
return self.config.get("credentials").get("access_token")


class Client(BaseClient):
def __init__(self, access_token: str):
def __init__(self, **config: Dict):
super().__init__()
self._client = APIClient(access_token)
self._client = APIClient(access_token=DriftAuthenticator(config).get_token())

def stream__accounts(self, **kwargs) -> Iterator[dict]:
yield from self._client.accounts.list()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,83 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Drift Spec",
"type": "object",
"required": ["access_token"],
"additionalProperties": false,
"required": [],
"additionalProperties": true,
"properties": {
"access_token": {
"type": "string",
"description": "Drift Access Token. See the <a href=\"https://docs.airbyte.io/integrations/sources/drift\">docs</a> for more information on how to generate this key.",
"airbyte_secret": true
"credentials": {
"title": "Authorization Method",
"type": "object",
"oneOf": [
{
"type": "object",
"title": "OAuth2.0",
"required": ["client_id", "client_secret", "access_token", "refresh_token"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

access token is not required since we can use the other params to generate it

"properties": {
"credentials": {
"type": "string",
"const": "oauth2.0",
"enum": ["oauth2.0"],
"default": "oauth2.0",
"order": 0
},
"client_id": {
"type": "string",
bazarnov marked this conversation as resolved.
Show resolved Hide resolved
"title": "Client ID",
"description": "The Client ID of your Drift developer application.",
"airbyte_secret": true
},
"client_secret": {
"type": "string",
"title": "Client Secret",
"description": "The Client Secret of your Drift developer application.",
"airbyte_secret": true
},
"access_token": {
"type": "string",
"title": "Access Token",
"description": "Access Token for making authenticated requests.",
"airbyte_secret": true
},
"refresh_token": {
"type": "string",
"title": "Refresh Token",
"description": "Refresh Token to renew the expired access_token.",
"default": "",
"airbyte_secret": true
}
}
},
{
"title": "Access Token",
"type": "object",
"required": ["access_token"],
"properties": {
"credentials": {
"type": "string",
"const": "access_token",
"enum": ["access_token"],
"default": "access_token",
"order": 0
},
"access_token": {
"type": "string",
"title": "Access Token",
"description": "Drift Access Token. See the <a href=\"https://docs.airbyte.io/integrations/sources/drift\">docs</a> for more information on how to generate this key.",
"airbyte_secret": true
}
}
}
]
}
}
},
"authSpecification": {
"auth_type": "oauth2.0",
"oauth2Specification": {
"rootObject": ["credentials", "0"],
"oauthFlowInitParameters": [["client_id"], ["client_secret"]],
"oauthFlowOutputParameters": [["access_token"], ["refresh_token"]]
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@
import pytest
from source_drift.client import AuthError, Client

config = {"credentials": {"access_token": "wrong_key"}}


def test__heal_check_with_wrong_token():
client = Client(access_token="wrong_key")
client = Client(**config)
alive, error = client.health_check()

assert not alive
assert error == "(401, 'The access token is invalid or has expired')"


def test__users_with_wrong_token():
client = Client(access_token="wrong_key")
client = Client(**config)
with pytest.raises(AuthError, match="(401, 'The access token is invalid or has expired')"):
next(client.stream__users())
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.airbyte.config.StandardSourceDefinition;
import io.airbyte.config.persistence.ConfigRepository;
import io.airbyte.oauth.flows.AsanaOAuthFlow;
import io.airbyte.oauth.flows.DriftOAuthFlow;
import io.airbyte.oauth.flows.GithubOAuthFlow;
import io.airbyte.oauth.flows.HarvestOAuthFlow;
import io.airbyte.oauth.flows.HubspotOAuthFlow;
Expand Down Expand Up @@ -59,6 +60,7 @@ public OAuthImplementationFactory(final ConfigRepository configRepository, final
.put("airbyte/source-surveymonkey", new SurveymonkeyOAuthFlow(configRepository, httpClient))
.put("airbyte/source-trello", new TrelloOAuthFlow(configRepository, httpClient))
.put("airbyte/source-youtube-analytics", new YouTubeAnalyticsOAuthFlow(configRepository, httpClient))
.put("airbyte/source-drift", new DriftOAuthFlow(configRepository, httpClient))
.build();
}

Expand Down
Loading