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 f13da31cbdacc6..35dd94329d316e 100644
--- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml
+++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml
@@ -620,7 +620,7 @@
- name: Okta
sourceDefinitionId: 1d4fdb25-64fc-4569-92da-fcdca79a8372
dockerRepository: airbyte/source-okta
- dockerImageTag: 0.1.7
+ dockerImageTag: 0.1.8
documentationUrl: https://docs.airbyte.io/integrations/sources/okta
icon: okta.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 d9b9d43b439307..b4db3d4ec3c2c9 100644
--- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml
+++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml
@@ -6030,32 +6030,119 @@
- - "client_secret"
oauthFlowOutputParameters:
- - "access_token"
-- dockerImage: "airbyte/source-okta:0.1.7"
+- dockerImage: "airbyte/source-okta:0.1.8"
spec:
documentationUrl: "https://docs.airbyte.io/integrations/sources/okta"
connectionSpecification:
$schema: "http://json-schema.org/draft-07/schema#"
title: "Okta Spec"
type: "object"
- required:
- - "token"
- - "base_url"
- additionalProperties: false
+ required: []
+ additionalProperties: true
properties:
- token:
- type: "string"
- title: "API Token"
- description: "A Okta token. See the docs for instructions on how to generate it."
- airbyte_secret: true
- base_url:
+ domain:
type: "string"
- title: "Base URL"
- description: "The Okta base URL."
- airbyte_secret: true
+ title: "Okta domain"
+ description: "The Okta domain. See the docs for instructions on how to find it."
+ airbyte_secret: false
+ credentials:
+ title: "Authorization Method *"
+ type: "object"
+ oneOf:
+ - type: "object"
+ title: "OAuth2.0"
+ required:
+ - "auth_type"
+ - "client_id"
+ - "client_secret"
+ - "refresh_token"
+ properties:
+ auth_type:
+ type: "string"
+ const: "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
+ 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: "API Token"
+ required:
+ - "auth_type"
+ - "api_token"
+ properties:
+ auth_type:
+ type: "string"
+ const: "api_token"
+ order: 0
+ api_token:
+ type: "string"
+ title: "Personal API Token"
+ description: "An Okta token. See the docs for instructions on how to generate it."
+ airbyte_secret: true
supportsNormalization: false
supportsDBT: false
supported_destination_sync_modes: []
+ advanced_auth:
+ auth_flow_type: "oauth2.0"
+ predicate_key:
+ - "credentials"
+ - "auth_type"
+ predicate_value: "oauth2.0"
+ oauth_config_specification:
+ oauth_user_input_from_connector_config_specification:
+ type: "object"
+ additionalProperties: false
+ properties:
+ domain:
+ type: "string"
+ path_in_connector_config:
+ - "domain"
+ 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"
- dockerImage: "airbyte/source-onesignal:0.1.2"
spec:
documentationUrl: "https://docs.airbyte.io/integrations/sources/onesignal"
diff --git a/airbyte-integrations/connectors/source-okta/Dockerfile b/airbyte-integrations/connectors/source-okta/Dockerfile
index ed4dd1de885d31..a853344ee6847a 100644
--- a/airbyte-integrations/connectors/source-okta/Dockerfile
+++ b/airbyte-integrations/connectors/source-okta/Dockerfile
@@ -12,5 +12,5 @@ RUN pip install .
ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py"
ENTRYPOINT ["python", "/airbyte/integration_code/main.py"]
-LABEL io.airbyte.version=0.1.7
+LABEL io.airbyte.version=0.1.8
LABEL io.airbyte.name=airbyte/source-okta
diff --git a/airbyte-integrations/connectors/source-okta/acceptance-test-config.yml b/airbyte-integrations/connectors/source-okta/acceptance-test-config.yml
index 3d55a1bccd41ac..8e4c9a87ab8489 100644
--- a/airbyte-integrations/connectors/source-okta/acceptance-test-config.yml
+++ b/airbyte-integrations/connectors/source-okta/acceptance-test-config.yml
@@ -5,6 +5,10 @@ tests:
connection:
- config_path: "secrets/config.json"
status: "succeed"
+ - config_path: "secrets/config_api_token.json"
+ status: "succeed"
+ - config_path: "secrets/config_oauth.json"
+ status: "succeed"
- config_path: "integration_tests/invalid_config.json"
status: "failed"
discovery:
@@ -12,6 +16,8 @@ tests:
basic_read:
- config_path: "secrets/config.json"
configured_catalog_path: "integration_tests/configured_catalog.json"
+ - config_path: "secrets/config_api_token.json"
+ configured_catalog_path: "integration_tests/configured_catalog.json"
full_refresh:
- config_path: "secrets/config.json"
configured_catalog_path: "integration_tests/configured_catalog.json"
diff --git a/airbyte-integrations/connectors/source-okta/source_okta/schemas/logs.json b/airbyte-integrations/connectors/source-okta/source_okta/schemas/logs.json
index e0b67c1d807308..069d60a6a7659a 100644
--- a/airbyte-integrations/connectors/source-okta/source_okta/schemas/logs.json
+++ b/airbyte-integrations/connectors/source-okta/source_okta/schemas/logs.json
@@ -5,10 +5,7 @@
"alternateId": {
"type": ["string", "null"]
},
- "detail": {
- "additionalProperties": {
- "type": ["object", "null"]
- },
+ "detailEntry": {
"type": ["object", "null"]
},
"displayName": {
@@ -38,11 +35,10 @@
"type": ["string", "null"]
},
"authenticationStep": {
- "type": ["integer", "null"]
+ "type": ["integer"]
},
"credentialProvider": {
"enum": [
- "OKTA_AUTHENTICATION_PROVIDER",
"OKTA_CREDENTIAL_PROVIDER",
"RSA",
"SYMANTEC",
@@ -63,6 +59,10 @@
"EMAIL",
"OAUTH2",
"JWT",
+ "CERTIFICATE",
+ "PRE_SHARED_SYMMETRIC_KEY",
+ "OKTA_CLIENT_SESSION",
+ "DEVICE_UDID",
null
],
"type": ["string", "null"]
@@ -151,9 +151,6 @@
"debugContext": {
"properties": {
"debugData": {
- "additionalProperties": {
- "type": ["object", "null", "string"]
- },
"type": ["object", "null"]
}
},
@@ -174,7 +171,16 @@
"type": ["string", "null"]
},
"result": {
- "type": ["string", "null"]
+ "enum": [
+ "SUCCESS",
+ "FAILURE",
+ "SKIPPED",
+ "ALLOW",
+ "DENY",
+ "CHALLENGE",
+ "UNKNOWN"
+ ],
+ "type": "string"
}
},
"type": ["object", "null"]
@@ -225,7 +231,7 @@
"type": ["string", "null"]
},
"version": {
- "type": "string"
+ "type": ["string", "null"]
}
},
"type": ["object", "null"]
@@ -266,19 +272,16 @@
"type": ["string", "null"]
},
"detailEntry": {
- "additionalProperties": {
- "type": ["object", "null", "string"]
- },
"type": ["object", "null"]
},
"displayName": {
"type": ["string", "null"]
},
"id": {
- "type": ["string", "null"]
+ "type": "string"
},
"type": {
- "type": ["string", "null"]
+ "type": "string"
}
},
"type": ["object", "null"]
@@ -288,9 +291,6 @@
"transaction": {
"properties": {
"detail": {
- "additionalProperties": {
- "type": ["object", "null", "string"]
- },
"type": ["object", "null"]
},
"id": {
diff --git a/airbyte-integrations/connectors/source-okta/source_okta/source.py b/airbyte-integrations/connectors/source-okta/source_okta/source.py
index 81ec19a6919722..bf3602dad2594e 100644
--- a/airbyte-integrations/connectors/source-okta/source_okta/source.py
+++ b/airbyte-integrations/connectors/source-okta/source_okta/source.py
@@ -13,7 +13,7 @@
from airbyte_cdk.sources import AbstractSource
from airbyte_cdk.sources.streams import Stream
from airbyte_cdk.sources.streams.http import HttpStream
-from airbyte_cdk.sources.streams.http.auth import TokenAuthenticator
+from airbyte_cdk.sources.streams.http.auth import Oauth2Authenticator, TokenAuthenticator
class OktaStream(HttpStream, ABC):
@@ -232,18 +232,67 @@ def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str:
return f"users/{user_id}/roles"
+class OktaOauth2Authenticator(Oauth2Authenticator):
+ def get_refresh_request_body(self) -> Mapping[str, Any]:
+ return {
+ "grant_type": "refresh_token",
+ "refresh_token": self.refresh_token,
+ }
+
+ def refresh_access_token(self) -> Tuple[str, int]:
+ try:
+ response = requests.request(
+ method="POST",
+ url=self.token_refresh_endpoint,
+ data=self.get_refresh_request_body(),
+ auth=(self.client_id, self.client_secret),
+ )
+ response.raise_for_status()
+ response_json = response.json()
+ return response_json["access_token"], response_json["expires_in"]
+ except Exception as e:
+ raise Exception(f"Error while refreshing access token: {e}") from e
+
+
class SourceOkta(AbstractSource):
- def initialize_authenticator(self, config: Mapping[str, Any]) -> TokenAuthenticator:
- return TokenAuthenticator(config["token"], auth_method="SSWS")
+ def initialize_authenticator(self, config: Mapping[str, Any]):
+ if "token" in config:
+ return TokenAuthenticator(config["token"], auth_method="SSWS")
+
+ creds = config.get("credentials")
+ if not creds:
+ raise "Config validation error. `credentials` not specified."
+
+ auth_type = creds.get("auth_type")
+ if not auth_type:
+ raise "Config validation error. `auth_type` not specified."
+
+ if auth_type == "api_token":
+ return TokenAuthenticator(creds["api_token"], auth_method="SSWS")
+
+ if auth_type == "oauth2.0":
+ return OktaOauth2Authenticator(
+ token_refresh_endpoint=self.get_token_refresh_endpoint(config),
+ client_secret=creds["client_secret"],
+ client_id=creds["client_id"],
+ refresh_token=creds["refresh_token"],
+ )
+
+ @staticmethod
+ def get_url_base(config: Mapping[str, Any]) -> str:
+ return config.get("base_url") or f"https://{config['domain']}.okta.com"
+
+ def get_api_endpoint(self, config: Mapping[str, Any]) -> str:
+ return parse.urljoin(self.get_url_base(config), "/api/v1/")
- def get_url_base(self, config: Mapping[str, Any]) -> str:
- return parse.urljoin(config["base_url"], "/api/v1/")
+ def get_token_refresh_endpoint(self, config: Mapping[str, Any]) -> str:
+ return parse.urljoin(self.get_url_base(config), "/oauth2/v1/token")
def check_connection(self, logger, config) -> Tuple[bool, any]:
try:
auth = self.initialize_authenticator(config)
- base_url = self.get_url_base(config)
- url = parse.urljoin(base_url, "users")
+ api_endpoint = self.get_api_endpoint(config)
+ url = parse.urljoin(api_endpoint, "users")
response = requests.get(
url,
@@ -260,11 +309,11 @@ def check_connection(self, logger, config) -> Tuple[bool, any]:
def streams(self, config: Mapping[str, Any]) -> List[Stream]:
auth = self.initialize_authenticator(config)
- url_base = self.get_url_base(config)
+ api_endpoint = self.get_api_endpoint(config)
initialization_params = {
"authenticator": auth,
- "url_base": url_base,
+ "url_base": api_endpoint,
}
return [
diff --git a/airbyte-integrations/connectors/source-okta/source_okta/spec.json b/airbyte-integrations/connectors/source-okta/source_okta/spec.json
index 8d83004b19583e..b258cbdc21af20 100644
--- a/airbyte-integrations/connectors/source-okta/source_okta/spec.json
+++ b/airbyte-integrations/connectors/source-okta/source_okta/spec.json
@@ -4,20 +4,126 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Okta Spec",
"type": "object",
- "required": ["token", "base_url"],
- "additionalProperties": false,
+ "required": [],
+ "additionalProperties": true,
"properties": {
- "token": {
+ "domain": {
"type": "string",
- "title": "API Token",
- "description": "A Okta token. See the docs for instructions on how to generate it.",
- "airbyte_secret": true
+ "title": "Okta domain",
+ "description": "The Okta domain. See the docs for instructions on how to find it.",
+ "airbyte_secret": false
},
- "base_url": {
- "type": "string",
- "title": "Base URL",
- "description": "The Okta base URL.",
- "airbyte_secret": true
+ "credentials": {
+ "title": "Authorization Method *",
+ "type": "object",
+ "oneOf": [
+ {
+ "type": "object",
+ "title": "OAuth2.0",
+ "required": [
+ "auth_type",
+ "client_id",
+ "client_secret",
+ "refresh_token"
+ ],
+ "properties": {
+ "auth_type": {
+ "type": "string",
+ "const": "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
+ },
+ "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": "API Token",
+ "required": ["auth_type", "api_token"],
+ "properties": {
+ "auth_type": {
+ "type": "string",
+ "const": "api_token",
+ "order": 0
+ },
+ "api_token": {
+ "type": "string",
+ "title": "Personal API Token",
+ "description": "An Okta token. See the docs for instructions on how to generate it.",
+ "airbyte_secret": true
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "advanced_auth": {
+ "auth_flow_type": "oauth2.0",
+ "predicate_key": ["credentials", "auth_type"],
+ "predicate_value": "oauth2.0",
+ "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": {
+ "domain": {
+ "type": "string",
+ "path_in_connector_config": ["domain"]
+ }
+ }
}
}
}
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 b9484c77fd9d45..d80f0eaeade74e 100644
--- a/airbyte-oauth/src/main/java/io/airbyte/oauth/OAuthImplementationFactory.java
+++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/OAuthImplementationFactory.java
@@ -62,6 +62,7 @@ public OAuthImplementationFactory(final ConfigRepository configRepository, final
.put("airbyte/destination-snowflake", new DestinationSnowflakeOAuthFlow(configRepository, httpClient))
.put("airbyte/destination-google-sheets", new DestinationGoogleSheetsOAuthFlow(configRepository, httpClient))
.put("airbyte/source-snowflake", new SourceSnowflakeOAuthFlow(configRepository, httpClient))
+ .put("airbyte/source-okta", new OktaOAuthFlow(configRepository, httpClient))
.build();
}
diff --git a/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/OktaOAuthFlow.java b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/OktaOAuthFlow.java
new file mode 100644
index 00000000000000..a877c682e76aec
--- /dev/null
+++ b/airbyte-oauth/src/main/java/io/airbyte/oauth/flows/OktaOAuthFlow.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2022 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.commons.json.Jsons;
+import io.airbyte.config.persistence.ConfigRepository;
+import io.airbyte.oauth.BaseOAuth2Flow;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+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.okta.com/docs/guides/implement-oauth-for-okta/main/
+ */
+public class OktaOAuthFlow extends BaseOAuth2Flow {
+
+ public OktaOAuthFlow(final ConfigRepository configRepository, final HttpClient httpClient) {
+ super(configRepository, httpClient);
+ }
+
+ @VisibleForTesting
+ public OktaOAuthFlow(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 domain value from user's config
+ final String domain = getConfigValueUnsafe(inputOAuthConfiguration, "domain");
+
+ final URIBuilder builder = new URIBuilder()
+ .setScheme("https")
+ .setHost(domain + ".okta.com")
+ .setPath("oauth2/v1/authorize")
+ // required
+ .addParameter("client_id", clientId)
+ .addParameter("redirect_uri", redirectUrl)
+ .addParameter("scope", "okta.users.read okta.logs.read okta.groups.read okta.roles.read offline_access")
+ .addParameter("response_type", "code")
+ .addParameter("state", getState());
+
+ try {
+ 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("code", authCode)
+ .put("redirect_uri", redirectUrl)
+ .put("grant_type", "authorization_code")
+ .build();
+ }
+
+ @Override
+ protected String getAccessTokenUrl(final JsonNode inputOAuthConfiguration) {
+ // getting domain value from user's config
+ final String domain = getConfigValueUnsafe(inputOAuthConfiguration, "domain");
+ return "https://" + domain + ".okta.com/oauth2/v1/token";
+ }
+
+ @Override
+ protected Map 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(inputOAuthConfiguration);
+ final byte[] authorization = Base64.getEncoder()
+ .encode((clientId + ":" + clientSecret).getBytes(StandardCharsets.UTF_8));
+ final HttpRequest request = HttpRequest.newBuilder()
+ .POST(HttpRequest.BodyPublishers
+ .ofString(tokenReqContentType.getConverter().apply(
+ getAccessTokenQueryParameters(clientId, clientSecret, authCode, redirectUrl))))
+ .uri(URI.create(accessTokenUrl))
+ .header("Content-Type", tokenReqContentType.getContentType())
+ .header("Accept", "application/json")
+ .header("Authorization", "Basic " + new String(authorization, StandardCharsets.UTF_8))
+ .build();
+ try {
+ final HttpResponse response = httpClient.send(request,
+ HttpResponse.BodyHandlers.ofString());
+
+ return extractOAuthOutput(Jsons.deserialize(response.body()), accessTokenUrl);
+ } catch (final InterruptedException e) {
+ throw new IOException("Failed to complete OAuth flow", e);
+ }
+ }
+
+}
diff --git a/docs/integrations/sources/okta.md b/docs/integrations/sources/okta.md
index 7ecf8d291fae72..aeec289651741b 100644
--- a/docs/integrations/sources/okta.md
+++ b/docs/integrations/sources/okta.md
@@ -41,35 +41,32 @@ The connector is restricted by normal Okta [requests limitation](https://develop
### Requirements
-- Okta API Token
+You can use [OAuth2.0](https://developer.okta.com/docs/guides/implement-grant-type/authcodepkce/main/)
+or an [API token](https://developer.okta.com/docs/guides/create-an-api-token/overview/) to authenticate your Okta account.
+If you choose to authenticate with OAuth2.0, [register](https://dev-01177082-admin.okta.com/admin/apps/active) your Okta application.
### Setup guide
-In order to pull data out of your Okta instance, you need to create an [API Token](https://developer.okta.com/docs/guides/create-an-api-token/overview/).
-
-:::info
+1. Use API token from requirements and Okta [domain](https://developer.okta.com/docs/guides/find-your-domain/-/main/).
+2. Go to local Airbyte page.
+3. In the left navigation bar, click **Sources**. In the top-right corner, click **+ new source**.
+4. On the Set up the source page select **Okta** from the Source type dropdown.
+5. Paste all data to required fields.
+6. Click `Set up source`.
+**Note:**
Different Okta APIs require different admin privilege levels. API tokens inherit the privilege level of the admin account used to create them
-:::
-
-1. Sign in to your Okta organization as a user with [administrator privileges](https://help.okta.com/en/prod/okta_help_CSH.htm#ext_Security_Administrators)
-2. Access the API page: In the Admin Console, select API from the Security menu and then select the Tokens tab.
-3. Click Create Token.
-4. Name your token and click Create Token.
-5. Record the token value. This is the only opportunity to see it and record it.
-6. In Airbyte, create a Okta source.
-7. You can now pull data from your Okta instance!
-
## Changelog
-| Version | Date | Pull Request | Subject |
-| :------ | :--------- | :------------------------------------------------------- | :----------------------------------------------------------------------------- |
+| Version | Date | Pull Request | Subject |
+|:--------|:-----------| :--- | :--- |
+| 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) | Bugfix 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 |
+| 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) | Bugfix 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 |