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 Google Directory: support oauth #7409

Merged
merged 12 commits into from
Nov 5, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
1 change: 1 addition & 0 deletions .github/workflows/publish-command.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ jobs:
GOOGLE_ANALYTICS_V4_TEST_CREDS_OLD: ${{ secrets.GOOGLE_ANALYTICS_V4_TEST_CREDS_OLD }}
GOOGLE_CLOUD_STORAGE_TEST_CREDS: ${{ secrets.GOOGLE_CLOUD_STORAGE_TEST_CREDS }}
GOOGLE_DIRECTORY_TEST_CREDS: ${{ secrets.GOOGLE_DIRECTORY_TEST_CREDS }}
GOOGLE_DIRECTORY_TEST_CREDS_OAUTH: ${{ secrets.GOOGLE_DIRECTORY_TEST_CREDS_OAUTH }}
GOOGLE_SEARCH_CONSOLE_CDK_TEST_CREDS: ${{ secrets.GOOGLE_SEARCH_CONSOLE_CDK_TEST_CREDS }}
GOOGLE_SEARCH_CONSOLE_CDK_TEST_CREDS_SRV_ACC: ${{ secrets.GOOGLE_SEARCH_CONSOLE_CDK_TEST_CREDS_SRV_ACC }}
GOOGLE_SHEETS_TESTS_CREDS: ${{ secrets.GOOGLE_SHEETS_TESTS_CREDS }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test-command.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ jobs:
GOOGLE_ANALYTICS_V4_TEST_CREDS_OLD: ${{ secrets.GOOGLE_ANALYTICS_V4_TEST_CREDS_OLD }}
GOOGLE_CLOUD_STORAGE_TEST_CREDS: ${{ secrets.GOOGLE_CLOUD_STORAGE_TEST_CREDS }}
GOOGLE_DIRECTORY_TEST_CREDS: ${{ secrets.GOOGLE_DIRECTORY_TEST_CREDS }}
GOOGLE_DIRECTORY_TEST_CREDS_OAUTH: ${{ secrets.GOOGLE_DIRECTORY_TEST_CREDS_OAUTH }}
GOOGLE_SEARCH_CONSOLE_CDK_TEST_CREDS: ${{ secrets.GOOGLE_SEARCH_CONSOLE_CDK_TEST_CREDS }}
GOOGLE_SEARCH_CONSOLE_CDK_TEST_CREDS_SRV_ACC: ${{ secrets.GOOGLE_SEARCH_CONSOLE_CDK_TEST_CREDS_SRV_ACC }}
GOOGLE_SHEETS_TESTS_CREDS: ${{ secrets.GOOGLE_SHEETS_TESTS_CREDS }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
import json
from abc import ABC, abstractmethod
from functools import partial
from typing import Callable, Dict, Iterator, Sequence
from typing import Any, Callable, Dict, Iterator, Mapping, Sequence

import backoff
from google.auth.transport.requests import Request
from google.oauth2 import service_account
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import Resource, build
from googleapiclient.errors import HttpError as GoogleApiHttpError

Expand All @@ -19,19 +21,40 @@


class API:
def __init__(self, credentials_json: str, email: str):
def __init__(self, credentials: Mapping[str, Any]):
self._creds = None
self._credentials_json = credentials_json
self._admin_email = email
self._raw_credentials = credentials

def _load_account_info(self) -> Dict:
account_info = json.loads(self._credentials_json)
@staticmethod
def _load_account_info(credentials_json: str) -> Dict:
account_info = json.loads(credentials_json)
return account_info

def _obtain_creds(self) -> service_account.Credentials:
account_info = self._load_account_info()
def _obtain_service_account_creds(self) -> service_account.Credentials:
"""Obtaining creds based on Service account scenario"""
credentials_json = self._raw_credentials.get("credentials_json")
admin_email = self._raw_credentials.get("email")
account_info = self._load_account_info(credentials_json)
creds = service_account.Credentials.from_service_account_info(account_info, scopes=SCOPES)
self._creds = creds.with_subject(self._admin_email)
self._creds = creds.with_subject(admin_email)

def _obtain_web_app_creds(self) -> Credentials:
"""Obtaining creds based on Web server application scenario"""
info = {
"client_id": self._raw_credentials.get("client_id"),
"client_secret": self._raw_credentials.get("client_secret"),
"refresh_token": self._raw_credentials.get("refresh_token"),
}
creds = Credentials.from_authorized_user_info(info)
if creds.expired:
creds.refresh(Request())
self._creds = creds

def _obtain_creds(self):
if "credentials_json" in self._raw_credentials:
self._obtain_service_account_creds()
elif "client_id" and "client_secret" in self._raw_credentials:
self._obtain_web_app_creds()

def _construct_resource(self) -> Resource:
if not self._creds:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@


class Client(BaseClient):
def __init__(self, credentials_json: str, email: str):
self._api = API(credentials_json, email)
def __init__(self, credentials: Mapping[str, Any] = None, credentials_json: str = None, email: str = None):
# supporting old config format
if not credentials:
credentials = {"credentials_json": credentials_json, "email": email}
self._api = API(credentials)
self._apis = {"users": UsersAPI(self._api), "groups": GroupsAPI(self._api), "group_members": GroupMembersAPI(self._api)}
super().__init__()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,87 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Google Directory Spec",
"type": "object",
"required": ["credentials_json", "email"],
"additionalProperties": false,
"required": [],
"additionalProperties": true,
"properties": {
"credentials_json": {
"type": "string",
"description": "The contents of the JSON service account key. See the <a href=\"https://developers.google.com/admin-sdk/directory/v1/guides/delegation\">docs</a> for more information on how to generate this key.",
"airbyte_secret": true
},
"email": {
"type": "string",
"description": "The email of the user, which has permissions to access the Google Workspace Admin APIs."
"credentials": {
"title": "Google Credentials",
"description": "Google APIs use the OAuth 2.0 protocol for authentication and authorization. The Source supports <a href=\"https://developers.google.com/identity/protocols/oauth2#webserver\" target=\"_blank\">Web server application</a> and <a href=\"https://developers.google.com/identity/protocols/oauth2#serviceaccount\" target=\"_blank\">Service accounts</a> scenarios",
"type": "object",
"oneOf": [
{
"title": "Web server application",
Copy link
Contributor

Choose a reason for hiding this comment

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

making this suggestion because the non-technical personas explained in the UX handbook e.g: data analyst won't know what a "Web Server Application" means

Suggested change
"title": "Web server application",
"title": "Sign in via Google (Oauth)",

"description": "For these scenario user only needs to give permission to read Google Directory data",
"type": "object",
"required": ["client_id", "client_secret", "refresh_token"],
"properties": {
"credentials_title": {
"type": "string",
"title": "Credentials title",
"description": "Authentication scenario",
"const": "Web server app",
"enum": ["Web server app"],
"default": "Web server app",
"order": 0
},
"client_id": {
"title": "Client ID",
"type": "string",
"description": "The client ID of developer application",
"airbyte_secret": true
},
"client_secret": {
"title": "Client secret",
"type": "string",
"description": "The client secret of developer application",
"airbyte_secret": true
},
"refresh_token": {
"title": "Refresh Token",
"type": "string",
"description": "The token for obtaining new access token",
"airbyte_secret": true
}
}
},
{
"title": "Service accounts",
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"title": "Service accounts",
"title": "Service account Key",

"description": "For these scenario user should obtain service account's credentials from the Google API Console and provide delegated email",
"type": "object",
"required": ["credentials_json", "email"],
"properties": {
"credentials_title": {
"type": "string",
"title": "Credentials title",
"description": "Authentication scenario",
"const": "Service accounts",
"enum": ["Service accounts"],
"default": "Service accounts",
"order": 0
},
"credentials_json": {
"type": "string",
"title": "Credentials JSON",
"description": "The contents of the JSON service account key. See the <a href=\"https://developers.google.com/admin-sdk/directory/v1/guides/delegation\">docs</a> for more information on how to generate this key.",
"airbyte_secret": true
},
"email": {
"type": "string",
"title": "Email",
"description": "The email of the user, which has permissions to access the Google Workspace Admin APIs."
}
}
}
]
}
}
},
"authSpecification": {
"auth_type": "oauth2.0",
"oauth2Specification": {
"rootObject": ["credentials", 0],
"oauthFlowInitParameters": [["client_id"], ["client_secret"]],
"oauthFlowOutputParameters": [["refresh_token"]]
}
}
}
4 changes: 3 additions & 1 deletion docs/integrations/sources/google-directory.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ This connector attempts to back off gracefully when it hits Directory API's rate

## Getting started

### Requirements
Google APIs use the OAuth 2.0 protocol for authentication and authorization. The Source supports [Web server application](https://developers.google.com/identity/protocols/oauth2#webserver) and [Service accounts](https://developers.google.com/identity/protocols/oauth2#serviceaccount) scenarios.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Google APIs use the OAuth 2.0 protocol for authentication and authorization. The Source supports [Web server application](https://developers.google.com/identity/protocols/oauth2#webserver) and [Service accounts](https://developers.google.com/identity/protocols/oauth2#serviceaccount) scenarios.
Google APIs use the OAuth 2.0 protocol for authentication and authorization. This connector supports [Web server application](https://developers.google.com/identity/protocols/oauth2#webserver) and [Service accounts](https://developers.google.com/identity/protocols/oauth2#serviceaccount) scenarios.

Can you also add a section explaining the difference in setup between OSS and Cloud? Similar to this one https://docs.airbyte.io/integrations/sources/google-analytics-v4#getting-started-airbyte-cloud


### Requirements Service accounts scenario

* Credentials to a Google Service Account with delegated Domain Wide Authority
* Email address of the workspace admin which created the Service Account
Expand Down
1 change: 1 addition & 0 deletions tools/bin/ci_credentials.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ write_standard_creds source-google-analytics-v4 "$GOOGLE_ANALYTICS_V4_TEST_CREDS
write_standard_creds source-google-analytics-v4 "$GOOGLE_ANALYTICS_V4_TEST_CREDS_SRV_ACC" "service_config.json"
write_standard_creds source-google-analytics-v4 "$GOOGLE_ANALYTICS_V4_TEST_CREDS_OLD" "old_config.json"
write_standard_creds source-google-directory "$GOOGLE_DIRECTORY_TEST_CREDS"
write_standard_creds source-google-directory "$GOOGLE_DIRECTORY_TEST_CREDS_OAUTH" "config_oauth.json"
Copy link
Contributor

Choose a reason for hiding this comment

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

does SAT use this config?
is there any tests for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@keu SAT isn't enabled in this source. I've created PR #7464. I will add required test there.

write_standard_creds source-google-search-console "$GOOGLE_SEARCH_CONSOLE_CDK_TEST_CREDS"
write_standard_creds source-google-search-console "$GOOGLE_SEARCH_CONSOLE_CDK_TEST_CREDS_SRV_ACC" "service_account_config.json"
write_standard_creds source-google-sheets "$GOOGLE_SHEETS_TESTS_CREDS"
Expand Down