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

add OAuth 2.0 for microsoft_teams source connector #7807

Merged
merged 36 commits into from
Dec 13, 2021
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d65eddc
add OAuth 2.0 for microsoft_teams source connector
grubberr Nov 10, 2021
8c87b42
Merge branch 'master' into grubberr/7704-source-microsoft-teams
grubberr Nov 10, 2021
7f28921
bump version 0.2.2 -> 0.2.3
grubberr Nov 10, 2021
b5f9825
authSpecification added
grubberr Nov 10, 2021
dade790
Merge branch 'master' into grubberr/7704-source-microsoft-teams
grubberr Nov 10, 2021
76a64e6
MicrosoftTeamsOAuthFlow.java added
grubberr Nov 10, 2021
d8403ff
Merge branch 'master' into grubberr/7704-source-microsoft-teams
grubberr Nov 10, 2021
6ed96f5
getScopes added
grubberr Nov 10, 2021
942260a
Merge branch 'master' into grubberr/7704-source-microsoft-teams
grubberr Nov 13, 2021
8e61a41
Merge branch 'master' into grubberr/7704-source-microsoft-teams
grubberr Nov 13, 2021
55b8b84
Merge branch 'master' into grubberr/7704-source-microsoft-teams
grubberr Nov 18, 2021
6d37423
fix formatConsentUrl
grubberr Nov 18, 2021
13b320b
Merge branch 'master' into grubberr/7704-source-microsoft-teams
grubberr Nov 25, 2021
8f68634
lint code
grubberr Nov 25, 2021
42bd078
Merge branch 'master' into grubberr/7704-source-microsoft-teams
grubberr Dec 7, 2021
02fec68
bump version 0.2.3 -> 0.2.4
grubberr Dec 7, 2021
f8c7ab7
Merge branch 'master' into grubberr/7704-source-microsoft-teams
grubberr Dec 8, 2021
1dc07be
spec.json updated
grubberr Dec 8, 2021
82ac267
old_config.json, config_oauth.json added
grubberr Dec 8, 2021
05d227c
make self.msal_app lazy
grubberr Dec 8, 2021
6e505c7
bugfix
grubberr Dec 8, 2021
8ae29ce
getSourceConsentUrl added
grubberr Dec 8, 2021
35e22ed
Merge branch 'master' into grubberr/7704-source-microsoft-teams
grubberr Dec 9, 2021
2c68c3f
bugfix move tenant_id -> "oauth_user_input_from_connector_config_spec…
grubberr Dec 9, 2021
9842438
Revert "getSourceConsentUrl added"
grubberr Dec 9, 2021
23fff99
Merge branch 'master' into grubberr/7704-source-microsoft-teams
grubberr Dec 10, 2021
8f9251a
get tenantId from 'inputOAuthConfiguration'
grubberr Dec 10, 2021
3320fd2
Merge branch 'master' into grubberr/7704-source-microsoft-teams
grubberr Dec 12, 2021
5c28fe6
MicrosoftTeamsOAuthFlow.java improved
grubberr Dec 12, 2021
efb1851
MicrosoftTeamsOAuthFlowTest.java added
grubberr Dec 12, 2021
046decb
Merge branch 'master' into grubberr/7704-source-microsoft-teams
grubberr Dec 13, 2021
fde20a8
pass inputOAuthConfiguration to getAccessTokenUrl method
grubberr Dec 13, 2021
c09d965
lint code
grubberr Dec 13, 2021
0394383
improve 'not supported' message
grubberr Dec 13, 2021
5475a43
Merge branch 'master' into grubberr/7704-source-microsoft-teams
grubberr Dec 13, 2021
430e442
source_specs.yaml updated
grubberr Dec 13, 2021
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 @@ -393,7 +393,7 @@
- name: Microsoft teams
sourceDefinitionId: eaf50f04-21dd-4620-913b-2a83f5635227
dockerRepository: airbyte/source-microsoft-teams
dockerImageTag: 0.2.3
dockerImageTag: 0.2.4
documentationUrl: https://docs.airbyte.io/integrations/sources/microsoft-teams
icon: microsoft-teams.svg
sourceType: api
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ COPY source_microsoft_teams ./source_microsoft_teams
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-microsoft-teams
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ tests:
connection:
- config_path: "secrets/config.json"
status: "succeed"
- config_path: "secrets/old_config.json"
status: "succeed"
- config_path: "secrets/config_oauth.json"
status: "succeed"
- config_path: "integration_tests/invalid_config.json"
status: "exception"
status: "failed"
discovery:
- config_path: "secrets/config.json"
basic_read:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import pkgutil
import sys
import time
from functools import lru_cache
from typing import Dict, List, Optional, Tuple, Union

import backoff
Expand Down Expand Up @@ -50,11 +51,22 @@ def __init__(self, config: json):
"team_device_usage_report": self.get_team_device_usage_report,
}
self.configs = config
self.credentials = config.get("credentials")
if not self.credentials:
self.credentials = {
"tenant_id": config["tenant_id"],
"client_id": config["client_id"],
"client_secret": config["client_secret"],
}
self._group_ids = None
self.msal_app = msal.ConfidentialClientApplication(
self.configs["client_id"],
authority=f"https://login.microsoftonline.com/" f"{self.configs['tenant_id']}",
client_credential=self.configs["client_secret"],

@property
@lru_cache(maxsize=None)
def msal_app(self):
return msal.ConfidentialClientApplication(
self.credentials["client_id"],
authority=f"https://login.microsoftonline.com/" f"{self.credentials['tenant_id']}",
client_credential=self.credentials["client_secret"],
)

def _get_api_url(self, endpoint: str) -> str:
Expand All @@ -63,10 +75,10 @@ def _get_api_url(self, endpoint: str) -> str:

def _get_access_token(self) -> str:
scope = ["https://graph.microsoft.com/.default"]
# First, the code looks up a token from the cache.
result = self.msal_app.acquire_token_silent(scope, account=None)
# If no suitable token exists in cache. Let's get a new one from AAD.
if not result:
refresh_token = self.credentials.get("refresh_token")
if refresh_token:
result = self.msal_app.acquire_token_by_refresh_token(refresh_token, scopes=scope)
else:
result = self.msal_app.acquire_token_for_client(scopes=scope)
if "access_token" in result:
return result["access_token"]
Expand Down Expand Up @@ -124,6 +136,8 @@ def health_check(self) -> Tuple[bool, object]:
return True, None
except MsalServiceError as err:
return False, err.args[0]
except Exception as e:
return False, str(e)

def get_streams(self):
streams = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,141 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Microsoft Teams Spec",
"type": "object",
"required": ["tenant_id", "client_id", "client_secret", "period"],
"additionalProperties": false,
"required": ["period"],
"additionalProperties": true,
"properties": {
"tenant_id": {
"title": "Directory (tenant) ID",
"type": "string",
"description": "Directory (tenant) ID"
},
"client_id": {
"title": "Application (client) ID",
"type": "string",
"description": "Application (client) ID"
},
"client_secret": {
"title": "Client Secret",
"type": "string",
"description": "Client secret",
"airbyte_secret": true
},
"period": {
"type": "string",
"description": "Specifies the length of time over which the Team Device Report stream is aggregated. The supported values are: D7, D30, D90, and D180.",
"examples": ["D7"]
},
"credentials": {
"title": "Authentication mechanism",
"description": "Choose how to authenticate to Microsoft",
"type": "object",
"oneOf": [
{
"type": "object",
"title": "Authenticate via Microsoft (OAuth 2.0)",
"required": ["tenant_id", "client_id", "client_secret", "refresh_token"],
"additionalProperties": false,
"properties": {
"auth_type": {
"type": "string",
"const": "Client",
"enum": ["Client"],
"default": "Client",
"order": 0
},
"tenant_id": {
"title": "Directory (tenant) ID",
"type": "string",
"description": "Directory (tenant) ID"
},
"client_id": {
"title": "Application (client) ID",
"type": "string",
"description": "Application (client) ID"
},
"client_secret": {
"title": "Client Secret",
"type": "string",
"description": "Client secret",
"airbyte_secret": true
},
"refresh_token": {
"title": "Refresh Token",
"type": "string",
"description": "A refresh token generated using the above client ID and secret",
"airbyte_secret": true
}
}
},
{
"type": "object",
"title": "Authenticate via Microsoft",
"required": ["tenant_id", "client_id", "client_secret"],
"additionalProperties": false,
"properties": {
"auth_type": {
"type": "string",
"const": "Token",
"enum": ["Token"],
"default": "Token",
"order": 0
},
"tenant_id": {
"title": "Directory (tenant) ID",
"type": "string",
"description": "Directory (tenant) ID"
},
"client_id": {
"title": "Application (client) ID",
"type": "string",
"description": "Application (client) ID"
},
"client_secret": {
"title": "Client Secret",
"type": "string",
"description": "Client secret",
"airbyte_secret": true
}
}
}
]
}
}
},
"advanced_auth": {
"auth_flow_type": "oauth2.0",
"predicate_key": ["credentials", "auth_type"],
"predicate_value": "Client",
"oauth_config_specification": {
"complete_oauth_output_specification": {
"type": "object",
"additionalProperties": false,
"properties": {
"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": {
"tenant_id": {
"type": "string",
"path_in_connector_config": ["credentials", "tenant_id"]
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ public Map<String, Object> completeSourceOAuth(final UUID workspaceId,
getClientSecretUnsafe(oAuthParamConfig),
extractCodeParameter(queryParams),
redirectUrl,
Jsons.emptyObject(),
oAuthParamConfig),
getDefaultOAuthOutputPath());
}
Expand All @@ -162,6 +163,7 @@ public Map<String, Object> completeDestinationOAuth(final UUID workspaceId,
getClientSecretUnsafe(oAuthParamConfig),
extractCodeParameter(queryParams),
redirectUrl,
Jsons.emptyObject(),
oAuthParamConfig),
getDefaultOAuthOutputPath());
}
Expand All @@ -183,6 +185,7 @@ public Map<String, Object> completeSourceOAuth(final UUID workspaceId,
getClientSecretUnsafe(oAuthParamConfig),
extractCodeParameter(queryParams),
redirectUrl,
inputOAuthConfiguration,
oAuthParamConfig),
oAuthConfigSpecification);
}
Expand All @@ -204,6 +207,7 @@ public Map<String, Object> completeDestinationOAuth(final UUID workspaceId,
getClientSecretUnsafe(oAuthParamConfig),
extractCodeParameter(queryParams),
redirectUrl,
inputOAuthConfiguration,
oAuthParamConfig),
oAuthConfigSpecification);
}
Expand All @@ -212,9 +216,10 @@ protected Map<String, Object> completeOAuthFlow(final String clientId,
final String clientSecret,
final String authCode,
final String redirectUrl,
final JsonNode inputOAuthConfiguration,
final JsonNode oAuthParamConfig)
throws IOException {
final var accessTokenUrl = getAccessTokenUrl();
final var accessTokenUrl = getAccessTokenUrl(inputOAuthConfiguration);
final HttpRequest request = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers
.ofString(tokenReqContentType.converter.apply(getAccessTokenQueryParameters(clientId, clientSecret, authCode, redirectUrl))))
Expand Down Expand Up @@ -263,7 +268,7 @@ protected String extractCodeParameter(final Map<String, Object> queryParams) thr
/**
* Returns the URL where to retrieve the access token from.
*/
protected abstract String getAccessTokenUrl();
protected abstract String getAccessTokenUrl(final JsonNode inputOAuthConfiguration);

/**
* Extract all OAuth outputs from distant API response and store them in a flat map.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ protected String getClientSecretUnsafe(final JsonNode oauthConfig) {
return getConfigValueUnsafe(oauthConfig, "client_secret");
}

private static String getConfigValueUnsafe(final JsonNode oauthConfig, final String fieldName) {
protected static String getConfigValueUnsafe(final JsonNode oauthConfig, final String fieldName) {
if (oauthConfig.get(fieldName) != null) {
return oauthConfig.get(fieldName).asText();
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import io.airbyte.oauth.flows.HubspotOAuthFlow;
import io.airbyte.oauth.flows.IntercomOAuthFlow;
import io.airbyte.oauth.flows.LinkedinAdsOAuthFlow;
import io.airbyte.oauth.flows.MicrosoftTeamsOAuthFlow;
import io.airbyte.oauth.flows.PipeDriveOAuthFlow;
import io.airbyte.oauth.flows.QuickbooksOAuthFlow;
import io.airbyte.oauth.flows.RetentlyOAuthFlow;
Expand Down Expand Up @@ -52,6 +53,7 @@ public OAuthImplementationFactory(final ConfigRepository configRepository, final
.put("airbyte/source-hubspot", new HubspotOAuthFlow(configRepository, httpClient))
.put("airbyte/source-intercom", new IntercomOAuthFlow(configRepository, httpClient))
.put("airbyte/source-instagram", new InstagramOAuthFlow(configRepository, httpClient))
.put("airbyte/source-microsoft-teams", new MicrosoftTeamsOAuthFlow(configRepository, httpClient))
.put("airbyte/source-pipedrive", new PipeDriveOAuthFlow(configRepository, httpClient))
.put("airbyte/source-quickbooks", new QuickbooksOAuthFlow(configRepository, httpClient))
.put("airbyte/source-retently", new RetentlyOAuthFlow(configRepository, httpClient))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ protected String formatConsentUrl(final UUID definitionId,
}

@Override
protected String getAccessTokenUrl() {
protected String getAccessTokenUrl(final JsonNode inputOAuthConfiguration) {
return ACCESS_TOKEN_URL;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ protected String extractCodeParameter(Map<String, Object> queryParams) throws IO
}

@Override
protected String getAccessTokenUrl() {
protected String getAccessTokenUrl(final JsonNode inputOAuthConfiguration) {
return ACCESS_TOKEN_URL;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ protected String formatConsentUrl(final UUID definitionId,
}

@Override
protected String getAccessTokenUrl() {
protected String getAccessTokenUrl(final JsonNode inputOAuthConfiguration) {
return ACCESS_TOKEN_URL;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ protected Map<String, String> getAccessTokenQueryParameters(final String clientI
*
*/
@Override
protected String getAccessTokenUrl() {
protected String getAccessTokenUrl(final JsonNode inputOAuthConfiguration) {
return ACCESS_TOKEN_URL;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ private String getScopes() {
*
*/
@Override
protected String getAccessTokenUrl() {
protected String getAccessTokenUrl(final JsonNode inputOAuthConfiguration) {
return "https://api.hubapi.com/oauth/v1/token";
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ protected String formatConsentUrl(final UUID definitionId,
}

@Override
protected String getAccessTokenUrl() {
protected String getAccessTokenUrl(final JsonNode inputOAuthConfiguration) {
return ACCESS_TOKEN_URL;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ protected String formatConsentUrl(UUID definitionId,
}

@Override
protected String getAccessTokenUrl() {
protected String getAccessTokenUrl(final JsonNode inputOAuthConfiguration) {
return ACCESS_TOKEN_URL;
}

Expand Down
Loading