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 3f43ac7e744c6..c24b2381dd6fd 100644
--- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml
+++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml
@@ -836,7 +836,7 @@
- name: Zendesk Support
sourceDefinitionId: 79c1aa37-dae3-42ae-b333-d1c105477715
dockerRepository: airbyte/source-zendesk-support
- dockerImageTag: 0.2.0
+ dockerImageTag: 0.2.1
documentationUrl: https://docs.airbyte.io/integrations/sources/zendesk-support
icon: zendesk.svg
sourceType: api
diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml
index f43075d153a8a..9af4e404f9d75 100644
--- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml
+++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml
@@ -8882,7 +8882,7 @@
path_in_connector_config:
- "credentials"
- "client_secret"
-- dockerImage: "airbyte/source-zendesk-support:0.2.0"
+- dockerImage: "airbyte/source-zendesk-support:0.2.1"
spec:
documentationUrl: "https://docs.airbyte.io/integrations/sources/zendesk-support"
connectionSpecification:
@@ -8892,8 +8892,7 @@
required:
- "start_date"
- "subdomain"
- - "auth_method"
- additionalProperties: false
+ additionalProperties: true
properties:
start_date:
type: "string"
@@ -8906,25 +8905,49 @@
pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$"
subdomain:
type: "string"
- description: "The subdomain for your Zendesk Support"
- auth_method:
+ title: "Subdomain"
+ description: "Identifier of your Zendesk Subdomain, like: https://{MY_SUBDOMAIN}.zendesk.com/,\
+ \ where MY_SUBDOMAIN is the value of your subdomain"
+ credentials:
title: "Authorization Method"
type: "object"
- default: "api_token"
- description: "Zendesk service provides 2 auth method: API token and OAuth2.\
- \ Now only the first one is available. Another one will be added in the\
- \ future."
+ description: "Zendesk service provides two authentication methods. Choose\
+ \ between: `OAuth2.0` or `API token`."
oneOf:
+ - title: "OAuth2.0"
+ type: "object"
+ required:
+ - "access_token"
+ additionalProperties: true
+ properties:
+ credentials:
+ type: "string"
+ const: "oauth2.0"
+ enum:
+ - "oauth2.0"
+ default: "oauth2.0"
+ order: 0
+ access_token:
+ type: "string"
+ title: "Access Token"
+ description: "The value of the API token generated. See the docs\
+ \ for more information."
+ airbyte_secret: true
- title: "API Token"
type: "object"
required:
- "email"
- "api_token"
- additionalProperties: false
+ additionalProperties: true
properties:
- auth_method:
+ credentials:
type: "string"
const: "api_token"
+ enum:
+ - "api_token"
+ default: "api_token"
+ order: 0
email:
title: "Email"
type: "string"
@@ -8936,25 +8959,55 @@
https://docs.airbyte.io/integrations/sources/zendesk-support\">docs\
\ for more information."
airbyte_secret: true
- - title: "OAuth2.0"
- type: "object"
- required:
- - "access_token"
- additionalProperties: false
- properties:
- auth_method:
- type: "string"
- const: "access_token"
- access_token:
- title: "Access Token"
- type: "string"
- description: "The value of the Access token generated. See the docs for more information."
- airbyte_secret: true
supportsNormalization: false
supportsDBT: false
supported_destination_sync_modes: []
+ advanced_auth:
+ auth_flow_type: "oauth2.0"
+ predicate_key:
+ - "credentials"
+ - "credentials"
+ predicate_value: "oauth2.0"
+ oauth_config_specification:
+ oauth_user_input_from_connector_config_specification:
+ type: "object"
+ additionalProperties: false
+ properties:
+ subdomain:
+ type: "string"
+ path_in_connector_config:
+ - "subdomain"
+ 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-zendesk-talk:0.1.3"
spec:
documentationUrl: "https://docs.airbyte.io/integrations/sources/zendesk-talk"
diff --git a/airbyte-integrations/connectors/source-zendesk-support/Dockerfile b/airbyte-integrations/connectors/source-zendesk-support/Dockerfile
index 41daded4afeab..b542ce4cb4e99 100644
--- a/airbyte-integrations/connectors/source-zendesk-support/Dockerfile
+++ b/airbyte-integrations/connectors/source-zendesk-support/Dockerfile
@@ -25,5 +25,5 @@ COPY source_zendesk_support ./source_zendesk_support
ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py"
ENTRYPOINT ["python", "/airbyte/integration_code/main.py"]
-LABEL io.airbyte.version=0.2.0
+LABEL io.airbyte.version=0.2.1
LABEL io.airbyte.name=airbyte/source-zendesk-support
diff --git a/airbyte-integrations/connectors/source-zendesk-support/README.md b/airbyte-integrations/connectors/source-zendesk-support/README.md
index d4cfd188c9161..96c6104b28617 100644
--- a/airbyte-integrations/connectors/source-zendesk-support/README.md
+++ b/airbyte-integrations/connectors/source-zendesk-support/README.md
@@ -101,7 +101,8 @@ Customize `acceptance-test-config.yml` file to configure tests. See [Source Acce
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
```
-python -m pytest integration_tests -p integration_tests.acceptance
+docker build . --no-cache -t airbyte/source-zendesk-support:dev \
+&& python -m pytest -p source_acceptance_test.plugin
```
To run your integration tests with docker
diff --git a/airbyte-integrations/connectors/source-zendesk-support/acceptance-test-config.yml b/airbyte-integrations/connectors/source-zendesk-support/acceptance-test-config.yml
index 565b1a47d614d..6bdcc2ef1a109 100644
--- a/airbyte-integrations/connectors/source-zendesk-support/acceptance-test-config.yml
+++ b/airbyte-integrations/connectors/source-zendesk-support/acceptance-test-config.yml
@@ -13,6 +13,7 @@ tests:
status: "failed"
discovery:
- config_path: "secrets/config.json"
+ - config_path: "secrets/config_oauth.json"
basic_read:
- config_path: "secrets/config.json"
configured_catalog_path: "integration_tests/configured_catalog.json"
diff --git a/airbyte-integrations/connectors/source-zendesk-support/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-zendesk-support/integration_tests/invalid_config.json
index c1562ac3660e6..0eb9ad451f4f1 100644
--- a/airbyte-integrations/connectors/source-zendesk-support/integration_tests/invalid_config.json
+++ b/airbyte-integrations/connectors/source-zendesk-support/integration_tests/invalid_config.json
@@ -1,5 +1,6 @@
{
- "auth_method": {
+ "credentials": {
+ "credentials": "api_token",
"api_token": "",
"email": "broken.email@invalid.config"
},
diff --git a/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/source.py b/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/source.py
index f45d9aa5eab8b..dcb475a0d0c53 100644
--- a/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/source.py
+++ b/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/source.py
@@ -51,11 +51,21 @@ class SourceZendeskSupport(AbstractSource):
@classmethod
def get_authenticator(cls, config: Mapping[str, Any]) -> BasicApiTokenAuthenticator:
- if config["auth_method"]["auth_method"] == "access_token":
- return TokenAuthenticator(token=config["auth_method"]["access_token"])
- elif config["auth_method"]["auth_method"] == "api_token":
- return BasicApiTokenAuthenticator(config["auth_method"]["email"], config["auth_method"]["api_token"])
- raise SourceZendeskException(f"Not implemented authorization method: {config['auth_method']}")
+
+ # old authentication flow support
+ auth_old = config.get("auth_method")
+ if auth_old:
+ if auth_old.get("auth_method") == "api_token":
+ return BasicApiTokenAuthenticator(config["auth_method"]["email"], config["auth_method"]["api_token"])
+ # new authentication flow
+ auth = config.get("credentials")
+ if auth:
+ if auth.get("credentials") == "oauth2.0":
+ return TokenAuthenticator(token=config["credentials"]["access_token"])
+ elif auth.get("credentials") == "api_token":
+ return BasicApiTokenAuthenticator(config["credentials"]["email"], config["credentials"]["api_token"])
+ else:
+ raise SourceZendeskException(f"Not implemented authorization method: {config['credentials']}")
def check_connection(self, logger, config) -> Tuple[bool, any]:
"""Connection check to validate that the user-provided config can be used to connect to the underlying API
diff --git a/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/spec.json b/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/spec.json
index c4ea2fd36e88f..5e01fc832fa46 100644
--- a/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/spec.json
+++ b/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/spec.json
@@ -4,8 +4,8 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Source Zendesk Support Spec",
"type": "object",
- "required": ["start_date", "subdomain", "auth_method"],
- "additionalProperties": false,
+ "required": ["start_date", "subdomain"],
+ "additionalProperties": true,
"properties": {
"start_date": {
"type": "string",
@@ -16,51 +16,57 @@
},
"subdomain": {
"type": "string",
- "description": "The subdomain for your Zendesk Support"
+ "title": "Subdomain",
+ "description": "Identifier of your Zendesk Subdomain, like: https://{MY_SUBDOMAIN}.zendesk.com/, where MY_SUBDOMAIN is the value of your subdomain"
},
- "auth_method": {
+ "credentials": {
"title": "Authorization Method",
"type": "object",
- "default": "api_token",
- "description": "Zendesk service provides 2 auth method: API token and OAuth2. Now only the first one is available. Another one will be added in the future.",
+ "description": "Zendesk service provides two authentication methods. Choose between: `OAuth2.0` or `API token`.",
"oneOf": [
{
- "title": "API Token",
+ "title": "OAuth2.0",
"type": "object",
- "required": ["email", "api_token"],
- "additionalProperties": false,
+ "required": ["access_token"],
+ "additionalProperties": true,
"properties": {
- "auth_method": {
+ "credentials": {
"type": "string",
- "const": "api_token"
+ "const": "oauth2.0",
+ "enum": ["oauth2.0"],
+ "default": "oauth2.0",
+ "order": 0
},
- "email": {
- "title": "Email",
- "type": "string",
- "description": "The user email for your Zendesk account."
- },
- "api_token": {
- "title": "API Token",
+ "access_token": {
"type": "string",
+ "title": "Access Token",
"description": "The value of the API token generated. See the docs for more information.",
"airbyte_secret": true
}
}
},
{
- "title": "OAuth2.0",
+ "title": "API Token",
"type": "object",
- "required": ["access_token"],
- "additionalProperties": false,
+ "required": ["email", "api_token"],
+ "additionalProperties": true,
"properties": {
- "auth_method": {
+ "credentials": {
"type": "string",
- "const": "access_token"
+ "const": "api_token",
+ "enum": ["api_token"],
+ "default": "api_token",
+ "order": 0
},
- "access_token": {
- "title": "Access Token",
+ "email": {
+ "title": "Email",
"type": "string",
- "description": "The value of the Access token generated. See the docs for more information.",
+ "description": "The user email for your Zendesk account."
+ },
+ "api_token": {
+ "title": "API Token",
+ "type": "string",
+ "description": "The value of the API token generated. See the docs for more information.",
"airbyte_secret": true
}
}
@@ -68,5 +74,58 @@
]
}
}
+ },
+ "advanced_auth": {
+ "auth_flow_type": "oauth2.0",
+ "predicate_key": ["credentials", "credentials"],
+ "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"]
+ }
+ }
+ },
+ "oauth_user_input_from_connector_config_specification": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "subdomain": {
+ "type": "string",
+ "path_in_connector_config": ["subdomain"]
+ }
+ }
+ }
+ }
}
}
diff --git a/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/streams.py b/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/streams.py
index c98d5bd1cfbc0..47509ac5b4447 100644
--- a/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/streams.py
+++ b/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/streams.py
@@ -290,6 +290,11 @@ def next_page_token(self, *args, **kwargs):
class SourceZendeskSupportFullRefreshStream(BaseSourceZendeskSupportStream):
+ """
+ # endpoints don't provide the updated_at/created_at fields
+ # thus we can't implement an incremental logic for them
+ """
+
primary_key = "id"
response_list_name: str = None
@@ -327,6 +332,10 @@ def request_params(self, next_page_token: Mapping[str, Any] = None, **kwargs) ->
class SourceZendeskSupportCursorPaginationStream(SourceZendeskSupportFullRefreshStream):
+ """
+ # endpoints provide a cursor pagination and sorting mechanism
+ """
+
next_page_field = "next_page"
prev_start_time = None
@@ -463,9 +472,6 @@ class Macros(SourceZendeskSupportStream):
"""Macros stream: https://developer.zendesk.com/api-reference/ticketing/business-rules/macros/"""
-# endpoints provide a cursor pagination and sorting mechanism
-
-
class TicketAudits(SourceZendeskSupportCursorPaginationStream):
"""TicketAudits stream: https://developer.zendesk.com/api-reference/ticketing/tickets/ticket_audits/"""
@@ -490,10 +496,6 @@ def next_page_token(self, response: requests.Response) -> Optional[Mapping[str,
return response.json().get("before_cursor")
-# endpoints don't provide the updated_at/created_at fields
-# thus we can't implement an incremental logic for them
-
-
class Tags(SourceZendeskSupportFullRefreshStream):
"""Tags stream: https://developer.zendesk.com/api-reference/ticketing/ticket-management/tags/"""
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 aaf390ef3d582..2f01f2bc9d6a4 100644
--- a/airbyte-oauth/src/main/java/io/airbyte/oauth/OAuthImplementationFactory.java
+++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/OAuthImplementationFactory.java
@@ -55,6 +55,7 @@ public OAuthImplementationFactory(final ConfigRepository configRepository, final
.put("airbyte/source-youtube-analytics", new YouTubeAnalyticsOAuthFlow(configRepository, httpClient))
.put("airbyte/source-drift", new DriftOAuthFlow(configRepository, httpClient))
.put("airbyte/source-zendesk-chat", new ZendeskChatOAuthFlow(configRepository, httpClient))
+ .put("airbyte/source-zendesk-support", new ZendeskSupportOAuthFlow(configRepository, httpClient))
.put("airbyte/source-monday", new MondayOAuthFlow(configRepository, httpClient))
.put("airbyte/source-zendesk-sunshine", new ZendeskSunshineOAuthFlow(configRepository, httpClient))
.put("airbyte/source-mailchimp", new MailchimpOAuthFlow(configRepository, httpClient))
diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/ZendeskSupportOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/ZendeskSupportOAuthFlow.java
new file mode 100644
index 0000000000000..71481301bb3ad
--- /dev/null
+++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/ZendeskSupportOAuthFlow.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+ */
+
+package io.airbyte.oauth.flows;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.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.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Supplier;
+import org.apache.http.client.utils.URIBuilder;
+
+/**
+ * Following docs from
+ * https://support.zendesk.com/hc/en-us/articles/4408845965210-Using-OAuth-authentication-with-your-application
+ */
+public class ZendeskSupportOAuthFlow extends BaseOAuth2Flow {
+
+ public ZendeskSupportOAuthFlow(final ConfigRepository configRepository, final HttpClient httpClient) {
+ super(configRepository, httpClient);
+ }
+
+ @VisibleForTesting
+ public ZendeskSupportOAuthFlow(final ConfigRepository configRepository, final HttpClient httpClient, final Supplier stateSupplier) {
+ super(configRepository, httpClient, stateSupplier);
+ }
+
+ @Override
+ protected String formatConsentUrl(final UUID definitionId,
+ final String clientId,
+ final String redirectUrl,
+ final JsonNode inputOAuthConfiguration)
+ throws IOException {
+
+ // getting subdomain value from user's config
+ final String subdomain = getConfigValueUnsafe(inputOAuthConfiguration, "subdomain");
+
+ final URIBuilder builder = new URIBuilder()
+ .setScheme("https")
+ .setHost(subdomain + ".zendesk.com")
+ .setPath("oauth/authorizations/new")
+ // required
+ .addParameter("client_id", clientId)
+ .addParameter("redirect_uri", redirectUrl)
+ .addParameter("response_type", "code")
+ .addParameter("scope", "read")
+ .addParameter("state", getState());
+
+ try {
+ return builder.build().toString();
+ } catch (URISyntaxException e) {
+ throw new IOException("Failed to format Consent URL for OAuth flow", e);
+ }
+ }
+
+ @Override
+ protected Map getAccessTokenQueryParameters(String clientId,
+ String clientSecret,
+ String authCode,
+ String redirectUrl) {
+ return ImmutableMap.builder()
+ // required
+ .put("grant_type", "authorization_code")
+ .put("code", authCode)
+ .put("client_id", clientId)
+ .put("client_secret", clientSecret)
+ .put("redirect_uri", redirectUrl)
+ .put("scope", "read")
+ .build();
+ }
+
+ @Override
+ protected String getAccessTokenUrl(final JsonNode inputOAuthConfiguration) {
+ // getting subdomain value from user's config
+ final String subdomain = getConfigValueUnsafe(inputOAuthConfiguration, "subdomain");
+ return "https://" + subdomain + ".zendesk.com/oauth/tokens";
+ }
+
+ @Override
+ protected Map extractOAuthOutput(final JsonNode data, final String accessTokenUrl) throws IOException {
+ final Map result = new HashMap<>();
+ // getting out access_token
+ if (data.has("access_token")) {
+ result.put("access_token", data.get("access_token").asText());
+ } else {
+ throw new IOException(String.format("Missing 'access_token' in query params from %s", accessTokenUrl));
+ }
+ return result;
+ }
+
+}
diff --git a/docs/integrations/sources/zendesk-support.md b/docs/integrations/sources/zendesk-support.md
index 83cdf21b65f5a..22fbe7a7a4cd9 100644
--- a/docs/integrations/sources/zendesk-support.md
+++ b/docs/integrations/sources/zendesk-support.md
@@ -97,6 +97,7 @@ We recommend creating a restricted, read-only key specifically for Airbyte acces
| Version | Date | Pull Request | Subject |
|:---------|:-----------| :----- |:-------------------------------------------------------|
+| `0.2.1` | 2022-03-15 | [11162](https://github.com/airbytehq/airbyte/pull/11162) | Added support of OAuth2.0 authentication method
| `0.2.0` | 2022-03-01 | [9456](https://github.com/airbytehq/airbyte/pull/9456) | Update source to use future requests |
| `0.1.12` | 2022-01-25 | [9785](https://github.com/airbytehq/airbyte/pull/9785) | Add log message |
| `0.1.11` | 2021-12-21 | [8987](https://github.com/airbytehq/airbyte/pull/8987) | Update connector fields title/description |