From 0d7cdcb4749993ca61af68c72f2f1c1b7928b79b Mon Sep 17 00:00:00 2001 From: Christophe Duong Date: Wed, 1 Sep 2021 00:21:38 +0200 Subject: [PATCH] Add API endpoint to get oauth request/consent (#5699) Co-authored-by: Sherif A. Nada --- airbyte-api/src/main/openapi/config.yaml | 151 +++++++++- .../airbyte/server/apis/ConfigurationApi.java | 32 ++ .../ApplicationErrorKnownException.java | 42 +++ .../airbyte/server/handlers/OAuthHandler.java | 57 ++++ .../api/generated-api-html/index.html | 276 ++++++++++++++++++ 5 files changed, 557 insertions(+), 1 deletion(-) create mode 100644 airbyte-server/src/main/java/io/airbyte/server/errors/ApplicationErrorKnownException.java create mode 100644 airbyte-server/src/main/java/io/airbyte/server/handlers/OAuthHandler.java diff --git a/airbyte-api/src/main/openapi/config.yaml b/airbyte-api/src/main/openapi/config.yaml index a587c3455a1a2..9e1f726455f8b 100644 --- a/airbyte-api/src/main/openapi/config.yaml +++ b/airbyte-api/src/main/openapi/config.yaml @@ -45,9 +45,11 @@ tags: - name: destination_definition_specification description: DestinationDefinitionSpecification related resources. - name: destination - description: Destination related resources. + description: Destination related resources. - name: connection description: Connection between sources and destinations. + - name: oauth + description: OAuth related resources to delegate access from user. - name: db_migration description: Database migration related resources. - name: web_backend @@ -1182,6 +1184,98 @@ paths: $ref: "#/components/responses/NotFoundResponse" "422": $ref: "#/components/responses/InvalidInputResponse" + /v1/source_oauths/get_consent_url: + post: + tags: + - oauth + summary: Given a source connector definition ID, return the URL to the consent screen where to redirect the user to. + operationId: getSourceOAuthConsent + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SourceOauthConsentRequest" + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/OAuthConsentRead" + "404": + $ref: "#/components/responses/NotFoundResponse" + "422": + $ref: "#/components/responses/InvalidInputResponse" + /v1/source_oauths/complete_oauth: + post: + tags: + - oauth + summary: Given a source def ID and optional workspaceID generate an access/refresh token etc. + operationId: completeSourceOAuth + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CompleteSourceOauthRequest" + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/CompleteOauthResponse" + "404": + $ref: "#/components/responses/NotFoundResponse" + "422": + $ref: "#/components/responses/InvalidInputResponse" + /v1/destination_oauths/get_consent_url: + post: + tags: + - oauth + summary: Given a destination connector definition ID, return the URL to the consent screen where to redirect the user to. + operationId: getDestinationOAuthConsent + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/DestinationOauthConsentRequest" + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/OAuthConsentRead" + "404": + $ref: "#/components/responses/NotFoundResponse" + "422": + $ref: "#/components/responses/InvalidInputResponse" + /v1/destination_oauths/complete_oauth: + post: + tags: + - oauth + summary: + operationId: completeDestinationOAuth + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CompleteDestinationOAuthRequest" + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/CompleteOauthResponse" + "404": + $ref: "#/components/responses/NotFoundResponse" + "422": + $ref: "#/components/responses/InvalidInputResponse" /v1/web_backend/connections/list: post: tags: @@ -2876,6 +2970,61 @@ components: type: array items: $ref: "#/components/schemas/DbMigrationRead" + # OAuth + SourceOauthConsentRequest: + type: object + required: + - sourceDefinitionId + properties: + sourceDefinitionId: + $ref: "#/components/schemas/SourceDefinitionId" + workspaceId: + $ref: "#/components/schemas/WorkspaceId" + DestinationOauthConsentRequest: + type: object + required: + - destinationDefinitionId + properties: + sourceDefinitionId: + $ref: "#/components/schemas/DestinationDefinitionId" + workspaceId: + $ref: "#/components/schemas/WorkspaceId" + OAuthConsentRead: + type: object + required: + - consentUrl + properties: + consentUrl: + type: string + CompleteSourceOauthRequest: + type: object + required: + - sourceDefinitionId + properties: + sourceDefinitionId: + $ref: "#/components/schemas/SourceDefinitionId" + workspaceId: + $ref: "#/components/schemas/WorkspaceId" + queryParams: + description: The query parameters present in the redirect URL after a user granted consent e.g auth code + type: object + additionalProperties: true # Oauth parameters like code, state, etc.. will be different per API so we don't specify them in advance + CompleteDestinationOAuthRequest: + type: object + required: + - destinationDefinitionId + properties: + destinationDefinitionId: + $ref: "#/components/schemas/DestinationDefinitionId" + workspaceId: + $ref: "#/components/schemas/WorkspaceId" + queryParams: + description: The query parameters present in the redirect URL after a user granted consent e.g auth code + type: object + additionalProperties: true # Oauth parameters like code, state, etc.. will be different per API so we don't specify them in advance + CompleteOauthResponse: + type: object + additionalProperties: true # Oauth parameters like refresh/access token etc.. will be different per API so we don't specify them in advance # Web Backend WebBackendConnectionRead: type: object diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java b/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java index 24f444d419f72..f481fb112c004 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java @@ -26,6 +26,8 @@ import io.airbyte.api.model.CheckConnectionRead; import io.airbyte.api.model.CheckOperationRead; +import io.airbyte.api.model.CompleteDestinationOAuthRequest; +import io.airbyte.api.model.CompleteSourceOauthRequest; import io.airbyte.api.model.ConnectionCreate; import io.airbyte.api.model.ConnectionIdRequestBody; import io.airbyte.api.model.ConnectionRead; @@ -44,6 +46,7 @@ import io.airbyte.api.model.DestinationDefinitionSpecificationRead; import io.airbyte.api.model.DestinationDefinitionUpdate; import io.airbyte.api.model.DestinationIdRequestBody; +import io.airbyte.api.model.DestinationOauthConsentRequest; import io.airbyte.api.model.DestinationRead; import io.airbyte.api.model.DestinationReadList; import io.airbyte.api.model.DestinationRecreate; @@ -58,6 +61,7 @@ import io.airbyte.api.model.LogsRequestBody; import io.airbyte.api.model.Notification; import io.airbyte.api.model.NotificationRead; +import io.airbyte.api.model.OAuthConsentRead; import io.airbyte.api.model.OperationCreate; import io.airbyte.api.model.OperationIdRequestBody; import io.airbyte.api.model.OperationRead; @@ -75,6 +79,7 @@ import io.airbyte.api.model.SourceDefinitionUpdate; import io.airbyte.api.model.SourceDiscoverSchemaRead; import io.airbyte.api.model.SourceIdRequestBody; +import io.airbyte.api.model.SourceOauthConsentRequest; import io.airbyte.api.model.SourceRead; import io.airbyte.api.model.SourceReadList; import io.airbyte.api.model.SourceRecreate; @@ -111,6 +116,7 @@ import io.airbyte.server.handlers.HealthCheckHandler; import io.airbyte.server.handlers.JobHistoryHandler; import io.airbyte.server.handlers.LogsHandler; +import io.airbyte.server.handlers.OAuthHandler; import io.airbyte.server.handlers.OpenApiConfigHandler; import io.airbyte.server.handlers.OperationsHandler; import io.airbyte.server.handlers.SchedulerHandler; @@ -126,6 +132,7 @@ import io.temporal.serviceclient.WorkflowServiceStubs; import java.io.File; import java.io.IOException; +import java.util.Map; @javax.ws.rs.Path("/v1") public class ConfigurationApi implements io.airbyte.api.V1Api { @@ -147,6 +154,7 @@ public class ConfigurationApi implements io.airbyte.api.V1Api { private final LogsHandler logsHandler; private final OpenApiConfigHandler openApiConfigHandler; private final DbMigrationHandler dbMigrationHandler; + private final OAuthHandler oAuthHandler; private final Configs configs; public ConfigurationApi(final ConfigRepository configRepository, @@ -193,6 +201,7 @@ public ConfigurationApi(final ConfigRepository configRepository, logsHandler = new LogsHandler(); openApiConfigHandler = new OpenApiConfigHandler(); dbMigrationHandler = new DbMigrationHandler(configsDatabase, jobsDatabase); + oAuthHandler = new OAuthHandler(); this.configs = configs; } @@ -270,6 +279,18 @@ public SourceDefinitionSpecificationRead getSourceDefinitionSpecification(final return execute(() -> schedulerHandler.getSourceDefinitionSpecification(sourceDefinitionIdRequestBody)); } + // SOURCE OAUTH + + @Override + public OAuthConsentRead getSourceOAuthConsent(SourceOauthConsentRequest sourceOauthConsentRequest) { + return execute(() -> oAuthHandler.getSourceOAuthConsent(sourceOauthConsentRequest)); + } + + @Override + public Map completeSourceOAuth(CompleteSourceOauthRequest completeSourceOauthRequest) { + return execute(() -> oAuthHandler.completeSourceOAuth(completeSourceOauthRequest)); + } + // SOURCE IMPLEMENTATION @Override @@ -361,6 +382,17 @@ public DestinationDefinitionSpecificationRead getDestinationDefinitionSpecificat return execute(() -> schedulerHandler.getDestinationSpecification(destinationDefinitionIdRequestBody)); } + // DESTINATION OAUTH + @Override + public OAuthConsentRead getDestinationOAuthConsent(DestinationOauthConsentRequest destinationOauthConsentRequest) { + return execute(() -> oAuthHandler.getDestinationOAuthConsent(destinationOauthConsentRequest)); + } + + @Override + public Map completeDestinationOAuth(CompleteDestinationOAuthRequest requestBody) { + return execute(() -> oAuthHandler.completeDestinationOAuth(requestBody)); + } + // DESTINATION IMPLEMENTATION @Override diff --git a/airbyte-server/src/main/java/io/airbyte/server/errors/ApplicationErrorKnownException.java b/airbyte-server/src/main/java/io/airbyte/server/errors/ApplicationErrorKnownException.java new file mode 100644 index 0000000000000..8dd2e5cebc336 --- /dev/null +++ b/airbyte-server/src/main/java/io/airbyte/server/errors/ApplicationErrorKnownException.java @@ -0,0 +1,42 @@ +/* + * 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. + */ + +package io.airbyte.server.errors; + +public class ApplicationErrorKnownException extends KnownException { + + public ApplicationErrorKnownException(String message) { + super(message); + } + + public ApplicationErrorKnownException(String message, Throwable cause) { + super(message, cause); + } + + @Override + public int getHttpCode() { + return 422; + } + +} diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/OAuthHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/OAuthHandler.java new file mode 100644 index 0000000000000..84fed43c8c09f --- /dev/null +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/OAuthHandler.java @@ -0,0 +1,57 @@ +/* + * 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. + */ + +package io.airbyte.server.handlers; + +import io.airbyte.api.model.CompleteDestinationOAuthRequest; +import io.airbyte.api.model.CompleteSourceOauthRequest; +import io.airbyte.api.model.DestinationOauthConsentRequest; +import io.airbyte.api.model.OAuthConsentRead; +import io.airbyte.api.model.SourceOauthConsentRequest; +import io.airbyte.server.errors.ApplicationErrorKnownException; +import java.util.Map; + +public class OAuthHandler { + + public OAuthConsentRead getSourceOAuthConsent(SourceOauthConsentRequest sourceDefinitionIdRequestBody) { + // TODO: Implement OAuth module to be called here https://github.com/airbytehq/airbyte/issues/5641 + throw new ApplicationErrorKnownException("Source connector does not supports OAuth yet."); + } + + public OAuthConsentRead getDestinationOAuthConsent(DestinationOauthConsentRequest destinationDefinitionIdRequestBody) { + // TODO: Implement OAuth module to be called here https://github.com/airbytehq/airbyte/issues/5641 + throw new ApplicationErrorKnownException("Destination connector does not supports OAuth yet."); + } + + public Map completeSourceOAuth(CompleteSourceOauthRequest oauthSourceRequestBody) { + // TODO: Implement OAuth module to be called here https://github.com/airbytehq/airbyte/issues/5641 + throw new ApplicationErrorKnownException("Source connector does not supports OAuth yet."); + } + + public Map completeDestinationOAuth(CompleteDestinationOAuthRequest oauthDestinationRequestBody) { + // TODO: Implement OAuth module to be called here https://github.com/airbytehq/airbyte/issues/5641 + throw new ApplicationErrorKnownException("Destination connector does not supports OAuth yet."); + } + +} diff --git a/docs/reference/api/generated-api-html/index.html b/docs/reference/api/generated-api-html/index.html index abdb7fc82625d..88e4a9bd63ef1 100644 --- a/docs/reference/api/generated-api-html/index.html +++ b/docs/reference/api/generated-api-html/index.html @@ -282,6 +282,13 @@

Notifications

+

Oauth

+

Openapi