From 34c76fbc55eb264c3bd4e39168ba43e3a490f1e0 Mon Sep 17 00:00:00 2001 From: Evyatar Rosner Date: Thu, 15 Aug 2024 02:00:27 +0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Source=20Okta:=20Authentication=20w?= =?UTF-8?q?ith=20private=20key=20(OAuth=202.0)=20support=20(#43382)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../connectors/source-okta/metadata.yaml | 2 +- .../connectors/source-okta/pyproject.toml | 2 +- .../sample_files/config_private_key.json | 11 ++++ .../source-okta/source_okta/__init__.py | 2 +- ...custom_authenticators.py => components.py} | 55 ++++++++++++++++++ .../source-okta/source_okta/manifest.yaml | 45 ++++++++++++++- .../source-okta/source_okta/spec.yaml | 36 ++++++++++++ .../source-okta/unit_tests/test_source.py | 2 +- .../source-okta/unit_tests/test_streams.py | 2 +- docs/integrations/sources/okta.md | 57 ++++++++++--------- 10 files changed, 179 insertions(+), 35 deletions(-) create mode 100644 airbyte-integrations/connectors/source-okta/sample_files/config_private_key.json rename airbyte-integrations/connectors/source-okta/source_okta/{custom_authenticators.py => components.py} (51%) diff --git a/airbyte-integrations/connectors/source-okta/metadata.yaml b/airbyte-integrations/connectors/source-okta/metadata.yaml index 11b39b4c6700..21d9cbd7e803 100644 --- a/airbyte-integrations/connectors/source-okta/metadata.yaml +++ b/airbyte-integrations/connectors/source-okta/metadata.yaml @@ -19,7 +19,7 @@ data: connectorSubtype: api connectorType: source definitionId: 1d4fdb25-64fc-4569-92da-fcdca79a8372 - dockerImageTag: 0.2.11 + dockerImageTag: 0.3.0 dockerRepository: airbyte/source-okta githubIssueLabel: source-okta icon: icon.svg diff --git a/airbyte-integrations/connectors/source-okta/pyproject.toml b/airbyte-integrations/connectors/source-okta/pyproject.toml index 499285e21041..2ffb208c82a4 100644 --- a/airbyte-integrations/connectors/source-okta/pyproject.toml +++ b/airbyte-integrations/connectors/source-okta/pyproject.toml @@ -3,7 +3,7 @@ requires = [ "poetry-core>=1.0.0",] build-backend = "poetry.core.masonry.api" [tool.poetry] -version = "0.2.11" +version = "0.3.0" name = "source-okta" description = "Source implementation for okta." authors = [ "Airbyte ",] diff --git a/airbyte-integrations/connectors/source-okta/sample_files/config_private_key.json b/airbyte-integrations/connectors/source-okta/sample_files/config_private_key.json new file mode 100644 index 000000000000..ca497d484840 --- /dev/null +++ b/airbyte-integrations/connectors/source-okta/sample_files/config_private_key.json @@ -0,0 +1,11 @@ +{ + "domain": "myorg", + "start_date": "2024-08-02T00:00:00Z", + "credentials": { + "auth_type": "oauth2.0_private_key", + "client_id": "0odiji39hrrlrggPb7d7", + "key_id": "IvLuOYbri0fMcIhdnaOhcjD5S_WXzkk7Cxdo0_M28r0", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9b0BAQEFAASCBKcwggSjAgEAAoIBARC1Zzwf3mpAJBJ7\nbnSZr+wqgUlh10I/5hhLSYWlB0Ak+GlnhHFFJcSE8SByrAPlorggtBAuTdoVMGc8\nlDSodtQM+WiurwltCbbSA8VqmeftIA53Axi7ZwCqm9MVpiNIB6uUFzjFh1ewKRdM\nQYzIWmIL+onDl0P4vzQylsEO0dy6Z3SCoodpw1mgcViGRL3MH73hymauEn9n5n0s\n3CI6g9+tzk1f2gYHWB+o1XHBb78ZR7vkCBuOCY58P3Bps2hG0gTTDaBOrAtSd8Df\n4PVAoVAnh70WtUyzIGBu6pmSbIZ05G32xz+wANFgCZ+G4e5+k9GYz+2/2bwDh3Ra\nrL16CY4bAgMBAAECggEAHZt+YlJg5BwfGh9Cj6z7bkqQuBPin7xF3c/frLo8uWwD\n/2ITfLY313zlj2HM9wdyZwAMngod3JR8XRJRb+eJH577e3tdHftWZ/uuloINLRIs\n2jbarAeZP79UGfX2TzTVR8Psg6zd3oYuY8dVG4RI+WyIXLCNKwXGFrWtR+Zv0Mp4\nOnqc5aCq0qbSYmj/WEJFL3yW0VQznt/olpuV9Ek45FlahCTpojLxhcS9+LG+L+pm\nt0a5cAu98lkthrn0UIkkirl2Q5iDnn56bHydhBnjj5X4XCXmX4SMLOfnF0u3q3wz\nTkYWdfaVkMLoOfOYMTy+HOuo52KiPXrsknPR8TLneQKBgQDtHe2BzbY++KnUabDN\nvxGTrj89uUaDM53FRlsSWm8ATRCueNcXbFAfKXlnOZzPr0/GEZ2a6o6cRJh5iT4s\nDGLeWtCNSg59pHHfq1SNDqdrxESbZTVScxYpnTa2yVc6tAw3cYAUo4PSgks/ErYu\nkSjHXhb9RnuDSS+AWpwoYMnqcwKBgQDD2Xr2mQi5vic/WK03hV00gqXTezRVo+g7\nrOrCyj0HKjcewLtzBkkSFU5JLUeyC7fUFYOIBwaJMmt7UxwYePXX/AB//u/IRIkh\nbjPIZqSjgX0rq62Ou5uy12WZ1S+ZYyZV4rD4ehzR4z+5KkIHJ/7wGkAtZQYoKdsR\ntEJmz8cbuQKBgExdzid3DFH9nhy2KWYqOkv4249Sg34v+okVnrErhQJwz4WRj5yQ\nmsFehyYSrQlKagPdmofRMTrs8Lp71BU1rAX286H9jusyMiaaNHH1nUAdBweRMfoq\n7KFca8m00K4sXJ7ipCCBhSwgIIHg0eHviFWlXPwXXiIrSOwqwo5SldU3AoGAdUdt\nn/ACTqA1BnUGvUGqj8BQpvSXYVVWwy2II39RzlGUUmEdnwK7jQ2fJKjtzwu/WExN\nyI5UdqHvxRj+sRT2OxFYB03VrvqDl7ZTYgU9QABRwW3774Ye9ZiQ6e7EozjBgxrN\n2O3fBjzsMujAQ2LLAmLl3Ykqh7CQ0+g6/zAbTlkCgYEAjR/nNSCf7qClRKB88aWg\nFUmJsK4DW2we0fHufPyuUUG3dxbPT66SS7chvivLHhmZdOoZg6LX+v3vjfH08qv4\nARRKKgoyaEZR+Dna4fxYPUWzpv95egdea9q63Rw2xXc5pBvp7wDXkd3Sr4yhZYzk\nGg8CnyEMRaPgTor51VNN1gU=\n-----END PRIVATE KEY-----\n", + "scope": "okta.users.read" + } +} diff --git a/airbyte-integrations/connectors/source-okta/source_okta/__init__.py b/airbyte-integrations/connectors/source-okta/source_okta/__init__.py index 6a75f70b0bf0..dce455d73972 100644 --- a/airbyte-integrations/connectors/source-okta/source_okta/__init__.py +++ b/airbyte-integrations/connectors/source-okta/source_okta/__init__.py @@ -5,4 +5,4 @@ from .source import SourceOkta -__all__ = ["SourceOkta", "custom_authenticators"] +__all__ = ["SourceOkta", "components"] diff --git a/airbyte-integrations/connectors/source-okta/source_okta/custom_authenticators.py b/airbyte-integrations/connectors/source-okta/source_okta/components.py similarity index 51% rename from airbyte-integrations/connectors/source-okta/source_okta/custom_authenticators.py rename to airbyte-integrations/connectors/source-okta/source_okta/components.py index c3ce2a4945a9..bfe858878197 100644 --- a/airbyte-integrations/connectors/source-okta/source_okta/custom_authenticators.py +++ b/airbyte-integrations/connectors/source-okta/source_okta/components.py @@ -2,9 +2,12 @@ # Copyright (c) 2024 Airbyte, Inc., all rights reserved. # +import time +import uuid from dataclasses import InitVar, dataclass from typing import Any, Mapping, Tuple +import jwt import requests from airbyte_cdk.sources.declarative.auth import DeclarativeOauth2Authenticator from airbyte_cdk.sources.declarative.auth.declarative_authenticator import DeclarativeAuthenticator @@ -61,3 +64,55 @@ def refresh_access_token(self) -> Tuple[str, int]: return response_json["access_token"], response_json["expires_in"] except Exception as e: raise Exception(f"Error while refreshing access token: {e}") from e + + +@dataclass +class CustomOauth2PrivateKeyAuthenticator(DeclarativeAuthenticator): + """ + Custom authenticator that uses a signed JWT with a private key to authenticate against Okta. + """ + + config: Config + + @property + def auth_header(self) -> str: + return "Authorization" + + @property + def token(self) -> str: + domain = self.config["domain"] + client_id = self.config["credentials"]["client_id"] + key_id = self.config["credentials"]["key_id"] + private_key = self.config["credentials"]["private_key"] + scope = self.config["credentials"]["scope"] + now = int(time.time()) + + jwt_payload = { + "iss": client_id, + "sub": client_id, + "aud": f"https://{domain}.okta.com/oauth2/v1/token", + "iat": now, + "exp": now + 3600, + "jti": str(uuid.uuid4()), + } + jwt_headers = {"kid": key_id, "alg": "RS256"} + + client_assertion = jwt.encode(jwt_payload, private_key, algorithm="RS256", headers=jwt_headers) + token_url = f"https://{domain}.okta.com/oauth2/v1/token" + token_response = requests.post( + token_url, + data={ + "grant_type": "client_credentials", + "client_id": client_id, + "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", + "client_assertion": client_assertion, + "scope": scope, + }, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + + try: + response = token_response.json() + return f"Bearer {response['access_token']}" + except Exception as e: + raise Exception(f"Error while getting access token: {e}") from e diff --git a/airbyte-integrations/connectors/source-okta/source_okta/manifest.yaml b/airbyte-integrations/connectors/source-okta/source_okta/manifest.yaml index 249e9f10ae2c..468896a71e5d 100644 --- a/airbyte-integrations/connectors/source-okta/source_okta/manifest.yaml +++ b/airbyte-integrations/connectors/source-okta/source_okta/manifest.yaml @@ -4,7 +4,7 @@ type: DeclarativeSource definitions: custom_oauth_authenticator: type: CustomAuthenticator - class_name: source_okta.custom_authenticators.CustomOauth2Authenticator + class_name: source_okta.components.CustomOauth2Authenticator client_id: "{{ config['credentials']['client_id'] }}" client_secret: "{{ config['credentials']['client_secret'] }}" refresh_token: "{{ config['credentials']['refresh_token'] }}" @@ -12,15 +12,20 @@ definitions: token_refresh_endpoint: "https://{{ config['domain'] }}.okta.com/oauth2/v1/token" grant_type: refresh_token + custom_oauth_private_key_authenticator: + type: CustomAuthenticator + class_name: source_okta.components.CustomOauth2PrivateKeyAuthenticator + custom_bearer_authenticator: type: CustomAuthenticator - class_name: source_okta.custom_authenticators.CustomBearerAuthenticator + class_name: source_okta.components.CustomBearerAuthenticator selective_authenticator: type: SelectiveAuthenticator authenticator_selection_path: ["credentials", "auth_type"] authenticators: oauth2.0: "#/definitions/custom_oauth_authenticator" + oauth2.0_private_key: "#/definitions/custom_oauth_private_key_authenticator" api_token: "#/definitions/custom_bearer_authenticator" paginator: @@ -1850,6 +1855,42 @@ spec: title: Refresh Token description: Refresh Token to obtain new Access Token, when it's expired. airbyte_secret: true + - type: object + title: OAuth 2.0 with private key + required: + - auth_type + - client_id + - key_id + - private_key + - scope + properties: + auth_type: + type: string + const: oauth2.0_private_key + order: 0 + client_id: + type: string + title: Client ID + description: The Client ID of your OAuth application. + airbyte_secret: true + order: 1 + key_id: + type: string + title: Key ID + description: The key ID (kid). + airbyte_secret: true + order: 2 + private_key: + type: string + title: Private key + description: The private key in PEM format + airbyte_secret: true + order: 3 + scope: + type: string + title: Scope + description: The OAuth scope. + order: 4 - type: object title: API Token required: diff --git a/airbyte-integrations/connectors/source-okta/source_okta/spec.yaml b/airbyte-integrations/connectors/source-okta/source_okta/spec.yaml index d33c025df594..865f7c5bba72 100644 --- a/airbyte-integrations/connectors/source-okta/source_okta/spec.yaml +++ b/airbyte-integrations/connectors/source-okta/source_okta/spec.yaml @@ -35,6 +35,42 @@ connectionSpecification: title: Refresh Token description: Refresh Token to obtain new Access Token, when it's expired. airbyte_secret: true + - type: object + title: OAuth 2.0 with private key + required: + - auth_type + - client_id + - key_id + - private_key + - scope + properties: + auth_type: + type: string + const: oauth2.0_private_key + order: 0 + client_id: + type: string + title: Client ID + description: The Client ID of your OAuth application. + airbyte_secret: true + order: 1 + key_id: + type: string + title: Key ID + description: The key ID (kid). + airbyte_secret: true + order: 2 + private_key: + type: string + title: Private key + description: The private key in PEM format + airbyte_secret: true + order: 3 + scope: + type: string + title: Scope + description: The OAuth scope. + order: 4 - type: object title: API Token required: diff --git a/airbyte-integrations/connectors/source-okta/unit_tests/test_source.py b/airbyte-integrations/connectors/source-okta/unit_tests/test_source.py index 5f83eb47abb2..4983c1130c20 100644 --- a/airbyte-integrations/connectors/source-okta/unit_tests/test_source.py +++ b/airbyte-integrations/connectors/source-okta/unit_tests/test_source.py @@ -2,7 +2,7 @@ # Copyright (c) 2024 Airbyte, Inc., all rights reserved. # -from source_okta.custom_authenticators import CustomBearerAuthenticator, CustomOauth2Authenticator +from source_okta.components import CustomBearerAuthenticator, CustomOauth2Authenticator from source_okta.source import SourceOkta diff --git a/airbyte-integrations/connectors/source-okta/unit_tests/test_streams.py b/airbyte-integrations/connectors/source-okta/unit_tests/test_streams.py index 6e14236e362d..546e50c59041 100644 --- a/airbyte-integrations/connectors/source-okta/unit_tests/test_streams.py +++ b/airbyte-integrations/connectors/source-okta/unit_tests/test_streams.py @@ -10,7 +10,7 @@ import pytest import requests from airbyte_cdk.sources.streams import Stream -from source_okta.custom_authenticators import CustomBearerAuthenticator, CustomOauth2Authenticator +from source_okta.components import CustomBearerAuthenticator, CustomOauth2Authenticator from source_okta.source import SourceOkta diff --git a/docs/integrations/sources/okta.md b/docs/integrations/sources/okta.md index 688717882a12..df007df9e814 100644 --- a/docs/integrations/sources/okta.md +++ b/docs/integrations/sources/okta.md @@ -4,7 +4,7 @@ Okta is the complete identity solution for all your apps and people that’s uni ## Prerequisites -- Created Okta account with added application on [Add Application Page](https://okta-domain.okta.com/enduser/catalog) page. (change okta-domain to you'r domain received after complete registration) +- Created Okta account with added application on [Add Application Page](https://okta-domain.okta.com/enduser/catalog) page. (change okta-domain to your domain received after complete registration) ## Airbyte Open Source @@ -86,33 +86,34 @@ The connector is restricted by normal Okta [requests limitation](https://develop | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:-------------------------------------------------------------------------------| -| 0.2.11 | 2024-08-12 | [43820](https://github.com/airbytehq/airbyte/pull/43820) | Update dependencies | -| 0.2.10 | 2024-08-10 | [43672](https://github.com/airbytehq/airbyte/pull/43672) | Update dependencies | -| 0.2.9 | 2024-08-03 | [43279](https://github.com/airbytehq/airbyte/pull/43279) | Update dependencies | -| 0.2.8 | 2024-07-27 | [42739](https://github.com/airbytehq/airbyte/pull/42739) | Update dependencies | -| 0.2.7 | 2024-07-20 | [42284](https://github.com/airbytehq/airbyte/pull/42284) | Update dependencies | -| 0.2.6 | 2024-07-13 | [41756](https://github.com/airbytehq/airbyte/pull/41756) | Update dependencies | -| 0.2.5 | 2024-07-10 | [41269](https://github.com/airbytehq/airbyte/pull/41269) | Update dependencies | -| 0.2.4 | 2024-07-06 | [40904](https://github.com/airbytehq/airbyte/pull/40904) | Update dependencies | -| 0.2.3 | 2024-06-25 | [40316](https://github.com/airbytehq/airbyte/pull/40316) | Update dependencies | -| 0.2.2 | 2024-06-22 | [40002](https://github.com/airbytehq/airbyte/pull/40002) | Update dependencies | -| 0.2.1 | 2024-06-04 | [39016](https://github.com/airbytehq/airbyte/pull/39016) | [autopull] Upgrade base image to v1.2.1 | -| 0.2.0 | 2024-05-16 | [36509](https://github.com/airbytehq/airbyte/pull/36509) | Migrate to Low Code | -| 0.1.16 | 2023-07-07 | [20833](https://github.com/airbytehq/airbyte/pull/20833) | Fix infinite loop for GroupMembers stream | -| 0.1.15 | 2023-06-20 | [27533](https://github.com/airbytehq/airbyte/pull/27533) | Fixed group member stream and resource sets stream pagination | -| 0.1.14 | 2022-12-24 | [20877](https://github.com/airbytehq/airbyte/pull/20877) | Disabled OAuth2.0 authorization method | -| 0.1.13 | 2022-08-12 | [14700](https://github.com/airbytehq/airbyte/pull/14700) | Add resource sets | -| 0.1.12 | 2022-08-05 | [15050](https://github.com/airbytehq/airbyte/pull/15050) | Add parameter `start_date` for Logs stream | -| 0.1.11 | 2022-08-03 | [14739](https://github.com/airbytehq/airbyte/pull/14739) | Add permissions for custom roles | -| 0.1.10 | 2022-08-01 | [15179](https://github.com/airbytehq/airbyte/pull/15179) | Fix broken schemas for all streams | -| 0.1.9 | 2022-07-25 | [15001](https://github.com/airbytehq/airbyte/pull/15001) | Return deprovisioned users | -| 0.1.8 | 2022-07-19 | [14710](https://github.com/airbytehq/airbyte/pull/14710) | Implement OAuth2.0 authorization method | -| 0.1.7 | 2022-07-13 | [14556](https://github.com/airbytehq/airbyte/pull/14556) | Add User_Role_Assignments and Group_Role_Assignments streams (full fetch only) | -| 0.1.6 | 2022-07-11 | [14610](https://github.com/airbytehq/airbyte/pull/14610) | Add custom roles stream | -| 0.1.5 | 2022-07-04 | [14380](https://github.com/airbytehq/airbyte/pull/14380) | Add Group_Members stream to okta source | -| 0.1.4 | 2021-11-02 | [7584](https://github.com/airbytehq/airbyte/pull/7584) | Fix incremental params for log stream | -| 0.1.3 | 2021-09-08 | [5905](https://github.com/airbytehq/airbyte/pull/5905) | Fix incremental stream defect | -| 0.1.2 | 2021-07-01 | [4456](https://github.com/airbytehq/airbyte/pull/4456) | Fix infinite pagination in logs stream | +| 0.3.0 | 2024-08-13 | [43382](https://github.com/airbytehq/airbyte/pull/43382) | Support OAuth 2.0 with private key | +| 0.2.11 | 2024-08-12 | [43820](https://github.com/airbytehq/airbyte/pull/43820) | Update dependencies | +| 0.2.10 | 2024-08-10 | [43672](https://github.com/airbytehq/airbyte/pull/43672) | Update dependencies | +| 0.2.9 | 2024-08-03 | [43279](https://github.com/airbytehq/airbyte/pull/43279) | Update dependencies | +| 0.2.8 | 2024-07-27 | [42739](https://github.com/airbytehq/airbyte/pull/42739) | Update dependencies | +| 0.2.7 | 2024-07-20 | [42284](https://github.com/airbytehq/airbyte/pull/42284) | Update dependencies | +| 0.2.6 | 2024-07-13 | [41756](https://github.com/airbytehq/airbyte/pull/41756) | Update dependencies | +| 0.2.5 | 2024-07-10 | [41269](https://github.com/airbytehq/airbyte/pull/41269) | Update dependencies | +| 0.2.4 | 2024-07-06 | [40904](https://github.com/airbytehq/airbyte/pull/40904) | Update dependencies | +| 0.2.3 | 2024-06-25 | [40316](https://github.com/airbytehq/airbyte/pull/40316) | Update dependencies | +| 0.2.2 | 2024-06-22 | [40002](https://github.com/airbytehq/airbyte/pull/40002) | Update dependencies | +| 0.2.1 | 2024-06-04 | [39016](https://github.com/airbytehq/airbyte/pull/39016) | [autopull] Upgrade base image to v1.2.1 | +| 0.2.0 | 2024-05-16 | [36509](https://github.com/airbytehq/airbyte/pull/36509) | Migrate to Low Code | +| 0.1.16 | 2023-07-07 | [20833](https://github.com/airbytehq/airbyte/pull/20833) | Fix infinite loop for GroupMembers stream | +| 0.1.15 | 2023-06-20 | [27533](https://github.com/airbytehq/airbyte/pull/27533) | Fixed group member stream and resource sets stream pagination | +| 0.1.14 | 2022-12-24 | [20877](https://github.com/airbytehq/airbyte/pull/20877) | Disabled OAuth2.0 authorization method | +| 0.1.13 | 2022-08-12 | [14700](https://github.com/airbytehq/airbyte/pull/14700) | Add resource sets | +| 0.1.12 | 2022-08-05 | [15050](https://github.com/airbytehq/airbyte/pull/15050) | Add parameter `start_date` for Logs stream | +| 0.1.11 | 2022-08-03 | [14739](https://github.com/airbytehq/airbyte/pull/14739) | Add permissions for custom roles | +| 0.1.10 | 2022-08-01 | [15179](https://github.com/airbytehq/airbyte/pull/15179) | Fix broken schemas for all streams | +| 0.1.9 | 2022-07-25 | [15001](https://github.com/airbytehq/airbyte/pull/15001) | Return deprovisioned users | +| 0.1.8 | 2022-07-19 | [14710](https://github.com/airbytehq/airbyte/pull/14710) | Implement OAuth2.0 authorization method | +| 0.1.7 | 2022-07-13 | [14556](https://github.com/airbytehq/airbyte/pull/14556) | Add User_Role_Assignments and Group_Role_Assignments streams (full fetch only) | +| 0.1.6 | 2022-07-11 | [14610](https://github.com/airbytehq/airbyte/pull/14610) | Add custom roles stream | +| 0.1.5 | 2022-07-04 | [14380](https://github.com/airbytehq/airbyte/pull/14380) | Add Group_Members stream to okta source | +| 0.1.4 | 2021-11-02 | [7584](https://github.com/airbytehq/airbyte/pull/7584) | Fix incremental params for log stream | +| 0.1.3 | 2021-09-08 | [5905](https://github.com/airbytehq/airbyte/pull/5905) | Fix incremental stream defect | +| 0.1.2 | 2021-07-01 | [4456](https://github.com/airbytehq/airbyte/pull/4456) | Fix infinite pagination in logs stream | | 0.1.1 | 2021-06-09 | [3937](https://github.com/airbytehq/airbyte/pull/3973) | Add `AIRBYTE_ENTRYPOINT` env variable for kubernetes support | | 0.1.0 | 2021-05-30 | [3563](https://github.com/airbytehq/airbyte/pull/3563) | Initial Release |