From 5ed94a8cdf8906eb6d1986272c7f8805ed51e4c3 Mon Sep 17 00:00:00 2001 From: Baz Date: Thu, 23 Dec 2021 12:24:22 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20Source=20Zendesk-Chat:=20add=20o?= =?UTF-8?q?auth=20flow=20support=20(#7313)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4.json | 2 +- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 126 ++++++++++++++-- .../connectors/source-zendesk-chat/Dockerfile | 2 +- .../connectors/source-zendesk-chat/README.md | 10 ++ .../acceptance-test-config.yml | 12 +- .../integration_tests/invalid_config.json | 8 +- .../sample_files/sample_config.json | 3 +- .../source_zendesk_chat/source.py | 24 +++- .../source_zendesk_chat/spec.json | 135 +++++++++++++++++- .../source_zendesk_chat/streams.py | 3 +- .../unit_tests/unit_test.py | 27 ---- .../oauth/OAuthImplementationFactory.java | 1 + .../oauth/flows/ZendeskChatOAuthFlow.java | 108 ++++++++++++++ docs/integrations/sources/zendesk-chat.md | 11 ++ 15 files changed, 417 insertions(+), 57 deletions(-) create mode 100644 airbyte-oauth/src/main/java/io/airbyte/oauth/flows/ZendeskChatOAuthFlow.java diff --git a/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4.json b/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4.json index ac26ef65b0d6..d60adf00d2a8 100644 --- a/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4.json +++ b/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4.json @@ -2,7 +2,7 @@ "sourceDefinitionId": "40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4", "name": "Zendesk Chat", "dockerRepository": "airbyte/source-zendesk-chat", - "dockerImageTag": "0.1.5", + "dockerImageTag": "0.1.6", "documentationUrl": "https://docs.airbyte.io/integrations/sources/zendesk-chat", "icon": "zendesk.svg" } 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 e86bd39ae1fd..4f3f5da5e7fd 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -746,7 +746,7 @@ - name: Zendesk Chat sourceDefinitionId: 40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4 dockerRepository: airbyte/source-zendesk-chat - dockerImageTag: 0.1.5 + dockerImageTag: 0.1.6 documentationUrl: https://docs.airbyte.io/integrations/sources/zendesk-chat 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 0ba56bd6e161..727d75f74b04 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -7209,7 +7209,7 @@ - - "client_secret" oauthFlowOutputParameters: - - "refresh_token" -- dockerImage: "airbyte/source-zendesk-chat:0.1.5" +- dockerImage: "airbyte/source-zendesk-chat:0.1.6" spec: documentationUrl: "https://docs.airbyte.io/integrations/sources/zendesk-chat" connectionSpecification: @@ -7218,27 +7218,131 @@ type: "object" required: - "start_date" - - "access_token" - additionalProperties: false + additionalProperties: true properties: start_date: type: "string" - description: "UTC date and time in the format: YYYY-MM-DDT00:00:00Z. Any\ - \ data before this date will not be replicated." title: "Start Date" + description: "The date from which you'd like to replicate data for Zendesk\ + \ Chat API, in the format YYYY-MM-DDT00:00:00Z." examples: - "2021-02-01T00:00:00Z" pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" - access_token: + subdomain: type: "string" - description: "The Access Token for making authenticated requests. See the\ - \ docs for more information" - title: "Access Token" - airbyte_secret: true + title: "Subdomain (Optional)" + description: "Required if you access Zendesk Chat from a Zendesk Support\ + \ subdomain." + default: "" + credentials: + title: "Authorization Method" + type: "object" + oneOf: + - type: "object" + title: "OAuth2.0" + required: + - "credentials" + 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 OAuth application" + airbyte_secret: true + client_secret: + type: "string" + title: "Client Secret" + description: "The Client Secret of your OAuth 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 obtain new Access Token, when it's\ + \ expired." + airbyte_secret: true + - type: "object" + title: "Access Token" + required: + - "credentials" + - "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: "The Access Token to make authenticated requests." + 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" + refresh_token: + type: "string" + path_in_connector_config: + - "credentials" + - "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: + - "credentials" + - "client_id" + client_secret: + type: "string" + path_in_connector_config: + - "credentials" + - "client_secret" - dockerImage: "airbyte/source-zendesk-sunshine:0.1.0" spec: documentationUrl: "https://docs.airbyte.io/integrations/sources/zendesk_sunshine" diff --git a/airbyte-integrations/connectors/source-zendesk-chat/Dockerfile b/airbyte-integrations/connectors/source-zendesk-chat/Dockerfile index ca594b7b242c..5ec0398430df 100644 --- a/airbyte-integrations/connectors/source-zendesk-chat/Dockerfile +++ b/airbyte-integrations/connectors/source-zendesk-chat/Dockerfile @@ -16,5 +16,5 @@ RUN pip install . ENTRYPOINT ["python", "/airbyte/integration_code/main_dev.py"] -LABEL io.airbyte.version=0.1.5 +LABEL io.airbyte.version=0.1.6 LABEL io.airbyte.name=airbyte/source-zendesk-chat diff --git a/airbyte-integrations/connectors/source-zendesk-chat/README.md b/airbyte-integrations/connectors/source-zendesk-chat/README.md index 83f972412a7e..9c697ae51a68 100644 --- a/airbyte-integrations/connectors/source-zendesk-chat/README.md +++ b/airbyte-integrations/connectors/source-zendesk-chat/README.md @@ -53,6 +53,16 @@ python main_dev.py discover --config secrets/config.json python main_dev.py read --config secrets/config.json --catalog sample_files/configured_catalog.json ``` +#### 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-zendesk-chat:dev \ +&& python -m pytest -p source_acceptance_test.plugin +``` +To run your integration tests with docker + ### Unit Tests To run unit tests locally, from the connector directory run: ``` diff --git a/airbyte-integrations/connectors/source-zendesk-chat/acceptance-test-config.yml b/airbyte-integrations/connectors/source-zendesk-chat/acceptance-test-config.yml index 266704ef4f2f..2bb1fa957df4 100644 --- a/airbyte-integrations/connectors/source-zendesk-chat/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-zendesk-chat/acceptance-test-config.yml @@ -3,20 +3,28 @@ tests: spec: - spec_path: "source_zendesk_chat/spec.json" connection: + - config_path: "secrets/config_old.json" + status: "succeed" - config_path: "secrets/config.json" status: "succeed" + - config_path: "secrets/config_oauth.json" + status: "succeed" - config_path: "integration_tests/invalid_config.json" status: "failed" discovery: + - config_path: "secrets/config_old.json" - 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" + - config_path: "secrets/config_oauth.json" + configured_catalog_path: "integration_tests/configured_catalog.json" incremental: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" -# Unable to use 'future_state_path' because Zendesk Chat API returns an error when specifying a date in the future. -# future_state_path: "integration_tests/abnormal_state.json" + # Unable to use 'future_state_path' because Zendesk Chat API returns an error when specifying a date in the future. + # future_state_path: "integration_tests/abnormal_state.json" cursor_paths: agents: [ "id" ] bans: [ "id" ] diff --git a/airbyte-integrations/connectors/source-zendesk-chat/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-zendesk-chat/integration_tests/invalid_config.json index 7cf01b4084b4..f54c453752c3 100644 --- a/airbyte-integrations/connectors/source-zendesk-chat/integration_tests/invalid_config.json +++ b/airbyte-integrations/connectors/source-zendesk-chat/integration_tests/invalid_config.json @@ -1,4 +1,8 @@ { - "access_token": "wrongkey-access-token", - "start_date": "2020-12-12T00:00:00Z" + "start_date": "2020-10-01T00:00:00Z", + "subdomain": "", + "credentials": { + "credentials": "access_token", + "access_token": "wrong_access_token" + } } diff --git a/airbyte-integrations/connectors/source-zendesk-chat/sample_files/sample_config.json b/airbyte-integrations/connectors/source-zendesk-chat/sample_files/sample_config.json index b5e68067b85d..7642ef45b457 100644 --- a/airbyte-integrations/connectors/source-zendesk-chat/sample_files/sample_config.json +++ b/airbyte-integrations/connectors/source-zendesk-chat/sample_files/sample_config.json @@ -1,4 +1,5 @@ { "access_token": "", - "start_date": "2020-11-01T00:00:00Z" + "start_date": "2020-11-01T00:00:00Z", + "subdomain": "" } diff --git a/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/source.py b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/source.py index caa3ebc6e4de..9542b9478f90 100644 --- a/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/source.py +++ b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/source.py @@ -3,7 +3,7 @@ # -from typing import Any, List, Mapping, Tuple +from typing import Any, Dict, List, Mapping, Tuple from airbyte_cdk.models import SyncMode from airbyte_cdk.sources import AbstractSource @@ -13,10 +13,28 @@ from .streams import Accounts, Agents, AgentTimelines, Bans, Chats, Departments, Goals, Roles, RoutingSettings, Shortcuts, Skills, Triggers +class ZendeskAuthentication: + """ Provides the authentication capabilities for both old and new methods. """ + + def __init__(self, config: Dict): + self.config = config + + def get_auth(self) -> TokenAuthenticator: + """ Return the TokenAuthenticator object with access_token. """ + + # the old config supports for backward capability + access_token = self.config.get("access_token") + if not access_token: + # the new config supports `OAuth2.0` + access_token = self.config["credentials"]["access_token"] + + return TokenAuthenticator(token=access_token) + + class SourceZendeskChat(AbstractSource): def check_connection(self, logger, config) -> Tuple[bool, any]: + authenticator = ZendeskAuthentication(config).get_auth() try: - authenticator = TokenAuthenticator(config["access_token"]) records = RoutingSettings(authenticator=authenticator).read_records(sync_mode=SyncMode.full_refresh) next(records) return True, None @@ -24,7 +42,7 @@ def check_connection(self, logger, config) -> Tuple[bool, any]: return False, f"Unable to connect to Zendesk Chat API with the provided credentials - {error}" def streams(self, config: Mapping[str, Any]) -> List[Stream]: - authenticator = TokenAuthenticator(config["access_token"]) + authenticator = ZendeskAuthentication(config).get_auth() return [ Accounts(authenticator=authenticator), AgentTimelines(authenticator=authenticator, start_date=config["start_date"]), diff --git a/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/spec.json b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/spec.json index 7966df1b81cb..d4ef38eafa24 100644 --- a/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/spec.json +++ b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/spec.json @@ -4,21 +4,142 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Zendesk Chat Spec", "type": "object", - "required": ["start_date", "access_token"], - "additionalProperties": false, + "required": ["start_date"], + "additionalProperties": true, "properties": { "start_date": { "type": "string", - "description": "UTC date and time in the format: YYYY-MM-DDT00:00:00Z. Any data before this date will not be replicated.", "title": "Start Date", + "description": "The date from which you'd like to replicate data for Zendesk Chat API, in the format YYYY-MM-DDT00:00:00Z.", "examples": ["2021-02-01T00:00:00Z"], "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" }, - "access_token": { + "subdomain": { "type": "string", - "description": "The Access Token for making authenticated requests. See the docs for more information", - "title": "Access Token", - "airbyte_secret": true + "title": "Subdomain (Optional)", + "description": "Required if you access Zendesk Chat from a Zendesk Support subdomain.", + "default": "" + }, + "credentials": { + "title": "Authorization Method", + "type": "object", + "oneOf": [ + { + "type": "object", + "title": "OAuth2.0", + "required": ["credentials"], + "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 OAuth application", + "airbyte_secret": true + }, + "client_secret": { + "type": "string", + "title": "Client Secret", + "description": "The Client Secret of your OAuth 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 obtain new Access Token, when it's expired.", + "airbyte_secret": true + } + } + }, + { + "type": "object", + "title": "Access Token", + "required": ["credentials", "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": "The Access Token to make authenticated requests.", + "airbyte_secret": true + } + } + } + ] + } + } + }, + "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"] + }, + "refresh_token": { + "type": "string", + "path_in_connector_config": ["credentials", "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": ["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-chat/source_zendesk_chat/streams.py b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/streams.py index 7eaf9ae288ea..efa319b7e835 100644 --- a/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/streams.py +++ b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/streams.py @@ -16,6 +16,7 @@ class Stream(HttpStream, ABC): url_base = "https://www.zopim.com/api/v2/" primary_key = "id" + primary_key = None data_field = None limit = 100 @@ -237,7 +238,7 @@ class Bans(IdIncrementalStream): def get_stream_data(self, response_data) -> List[dict]: bans = response_data["ip_address"] + response_data["visitor"] - bans = sorted(bans, key=lambda x: pendulum.parse(x["created_at"])) + bans = sorted(bans, key=lambda x: pendulum.parse(x["created_at"]) if x["created_at"] else pendulum.min) return bans diff --git a/airbyte-integrations/connectors/source-zendesk-chat/unit_tests/unit_test.py b/airbyte-integrations/connectors/source-zendesk-chat/unit_tests/unit_test.py index 6d6d83e3959d..e1814314fc3b 100644 --- a/airbyte-integrations/connectors/source-zendesk-chat/unit_tests/unit_test.py +++ b/airbyte-integrations/connectors/source-zendesk-chat/unit_tests/unit_test.py @@ -3,32 +3,5 @@ # -""" -MIT License - -Copyright (c) 2020 Airbyte - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -# format anchor - - def test_example_method(): assert True 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 b8a1da842533..5bf57c566d58 100644 --- a/airbyte-oauth/src/main/java/io/airbyte/oauth/OAuthImplementationFactory.java +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/OAuthImplementationFactory.java @@ -52,6 +52,7 @@ public OAuthImplementationFactory(final ConfigRepository configRepository, final .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)) + .put("airbyte/source-zendesk-chat", new ZendeskChatOAuthFlow(configRepository, httpClient)) .build(); } diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/ZendeskChatOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/ZendeskChatOAuthFlow.java new file mode 100644 index 000000000000..447a82a72eb2 --- /dev/null +++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/ZendeskChatOAuthFlow.java @@ -0,0 +1,108 @@ +/* + * 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://developer.zendesk.com/documentation/live-chat/getting-started/auth/ + */ +public class ZendeskChatOAuthFlow extends BaseOAuth2Flow { + + private static final String ACCESS_TOKEN_URL = "https://www.zopim.com/oauth2/token"; + + public ZendeskChatOAuthFlow(final ConfigRepository configRepository, final HttpClient httpClient) { + super(configRepository, httpClient); + } + + @VisibleForTesting + public ZendeskChatOAuthFlow(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("www.zopim.com") + .setPath("oauth2/authorizations/new") + // required + .addParameter("client_id", clientId) + .addParameter("redirect_uri", redirectUrl) + .addParameter("response_type", "code") + .addParameter("scope", "read chat") + .addParameter("state", getState()); + + try { + // applying optional parameter of subdomain, if there is any value + if (!subdomain.isEmpty()) { + builder.addParameter("subdomain", subdomain); + } + return builder.build().toString(); + } catch (final 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) { + return ACCESS_TOKEN_URL; + } + + @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)); + } + // getting out refresh_token + if (data.has("refresh_token")) { + result.put("refresh_token", data.get("refresh_token").asText()); + } else { + throw new IOException(String.format("Missing 'refresh_token' in query params from %s", accessTokenUrl)); + } + return result; + } + +} diff --git a/docs/integrations/sources/zendesk-chat.md b/docs/integrations/sources/zendesk-chat.md index 47d1ab404eca..ca53f6493740 100644 --- a/docs/integrations/sources/zendesk-chat.md +++ b/docs/integrations/sources/zendesk-chat.md @@ -52,6 +52,16 @@ The Zendesk connector should not run into Zendesk API limitations under normal u * Zendesk Chat Access Token +### Connect using `OAuth 2.0` option: +1. Select `OAuth2.0` in `Authorization Method` +2. Click on `authenticate your Zendesk Chat account` +2. Proceed the authentication using your credentials for your Zendesk account. + +### Connect using `Access Token` option: +1. Generate a Access Token as described in [Zendesk Chat docs](https://developer.zendesk.com/rest_api/docs/chat/auth) +2. Use the generated `access_token` in Airbyte connection. + + ### Setup guide Generate a Access Token as described in [Zendesk Chat docs](https://developer.zendesk.com/rest_api/docs/chat/auth) @@ -62,6 +72,7 @@ We recommend creating a restricted, read-only key specifically for Airbyte acces | Version | Date | Pull Request | Subject | | :--- | :--- | :--- | :--- | +| 0.1.6 | 2021-12-15 | [7313](https://github.com/airbytehq/airbyte/pull/7313) | Added support of `OAuth 2.0` authentication. [8819](https://github.com/airbytehq/airbyte/pull/8819) Fixed the issue with `created_at` can now be `null` for `bans` stream | | 0.1.5 | 2021-12-06 | [8425](https://github.com/airbytehq/airbyte/pull/8425) | Update title, description fields in spec | | 0.1.4 | 2021-11-22 | [8166](https://github.com/airbytehq/airbyte/pull/8166) | Make `Chats` stream incremental + add tests for all streams | | 0.1.3 | 2021-10-21 | [7210](https://github.com/airbytehq/airbyte/pull/7210) | Chats stream is only getting data from first page |