From b786a180f02c6d48d82f19f51c8c8e730a14b983 Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Mon, 23 Jan 2023 12:42:43 -0500 Subject: [PATCH] Add required roles to API endpoints (#21664) * Restore auth poc * Formatting * Custom Netty pipeline handler to aid authorization * Fix handler name * Cleanup * Remove cloud code * Disable API authorization in OSS * Remove unused dependency * Add newline * Add required roles --- .../io/airbyte/commons/auth/AuthRole.java | 92 +++++++++++++++++++ .../commons/auth/AuthRoleConstants.java | 21 +++++ .../io/airbyte/commons/auth/AuthRoleTest.java | 47 ++++++++++ .../server/apis/AttemptApiController.java | 6 ++ .../server/apis/ConnectionApiController.java | 14 +++ .../server/apis/DestinationApiController.java | 20 +++- .../DestinationDefinitionApiController.java | 19 ++++ ...nDefinitionSpecificationApiController.java | 6 ++ .../apis/DestinationOauthApiController.java | 9 ++ .../server/apis/HealthApiController.java | 3 + .../server/apis/JobsApiController.java | 14 +++ .../server/apis/LogsApiController.java | 6 ++ .../server/apis/NotFoundController.java | 3 + .../apis/NotificationsApiController.java | 6 ++ .../server/apis/OpenapiApiController.java | 6 ++ .../server/apis/OperationApiController.java | 13 +++ .../server/apis/SchedulerApiController.java | 9 ++ .../server/apis/SourceApiController.java | 15 +++ .../apis/SourceDefinitionApiController.java | 19 ++++ ...eDefinitionSpecificationApiController.java | 6 ++ .../server/apis/SourceOauthApiController.java | 9 ++ .../server/apis/StateApiController.java | 8 ++ .../server/apis/WebBackendApiController.java | 15 +++ .../server/apis/WorkspaceApiController.java | 16 ++++ .../src/main/resources/application.yml | 48 +++++++--- deps.toml | 2 + 26 files changed, 417 insertions(+), 15 deletions(-) create mode 100644 airbyte-commons/src/main/java/io/airbyte/commons/auth/AuthRole.java create mode 100644 airbyte-commons/src/main/java/io/airbyte/commons/auth/AuthRoleConstants.java create mode 100644 airbyte-commons/src/test/java/io/airbyte/commons/auth/AuthRoleTest.java diff --git a/airbyte-commons/src/main/java/io/airbyte/commons/auth/AuthRole.java b/airbyte-commons/src/main/java/io/airbyte/commons/auth/AuthRole.java new file mode 100644 index 000000000000..b2ff02702545 --- /dev/null +++ b/airbyte-commons/src/main/java/io/airbyte/commons/auth/AuthRole.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.commons.auth; + +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * This enum describes the standard auth levels for a given resource. It currently is only used for + * 2 resources Workspace and Instance (i.e. the entire instance or deployment of Airbyte). + * + * In the context of a workspace, there is a 1:1 mapping. + * + * In the context of the instance, there are currently only 3 levels. + * + */ +public enum AuthRole { + + OWNER(500, AuthRoleConstants.OWNER), + ADMIN(400, AuthRoleConstants.ADMIN), + EDITOR(300, AuthRoleConstants.EDITOR), + READER(200, AuthRoleConstants.READER), + AUTHENTICATED_USER(100, AuthRoleConstants.AUTHENTICATED_USER), // ONLY USE WITH INSTANCE RESOURCE! + NONE(0, AuthRoleConstants.NONE); + + private final int authority; + private final String label; + + AuthRole(final int authority, final String label) { + this.authority = authority; + this.label = label; + } + + public int getAuthority() { + return authority; + } + + public String getLabel() { + return label; + } + + /** + * Builds the set of roles based on the provided {@link AuthRole} value. + *

+ * The generated set of auth roles contains the provided {@link AuthRole} (if not {@code null}) and + * any other authentication roles with a lesser {@link #getAuthority()} value. + *

+ * + * @param authRole An {@link AuthRole} (may be {@code null}). + * @return The set of {@link AuthRole}s based on the provided {@link AuthRole}. + */ + public static Set buildAuthRolesSet(final AuthRole authRole) { + final Set authRoles = new HashSet<>(); + + if (authRole != null) { + authRoles.add(authRole); + authRoles.addAll(Stream.of(values()) + .filter(role -> !NONE.equals(role)) + .filter(role -> role.getAuthority() < authRole.getAuthority()) + .collect(Collectors.toSet())); + } + + // Sort final set by descending authority order + return authRoles.stream() + .sorted(Comparator.comparingInt(AuthRole::getAuthority)) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + +} diff --git a/airbyte-commons/src/main/java/io/airbyte/commons/auth/AuthRoleConstants.java b/airbyte-commons/src/main/java/io/airbyte/commons/auth/AuthRoleConstants.java new file mode 100644 index 000000000000..6a206ee0e89e --- /dev/null +++ b/airbyte-commons/src/main/java/io/airbyte/commons/auth/AuthRoleConstants.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.commons.auth; + +/** + * Collection of constants that defines authorization roles. + */ +public final class AuthRoleConstants { + + public static final String ADMIN = "ADMIN"; + public static final String AUTHENTICATED_USER = "AUTHENTICATED_USER"; + public static final String EDITOR = "EDITOR"; + public static final String OWNER = "OWNER"; + public static final String NONE = "NONE"; + public static final String READER = "READER"; + + private AuthRoleConstants() {} + +} diff --git a/airbyte-commons/src/test/java/io/airbyte/commons/auth/AuthRoleTest.java b/airbyte-commons/src/test/java/io/airbyte/commons/auth/AuthRoleTest.java new file mode 100644 index 000000000000..835488fdd84a --- /dev/null +++ b/airbyte-commons/src/test/java/io/airbyte/commons/auth/AuthRoleTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.commons.auth; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Set; +import org.junit.jupiter.api.Test; + +/** + * Test suite for the {@link AuthRole} enumeration. + */ +class AuthRoleTest { + + @Test + void testBuildingAuthRoleSet() { + final Set ownerResult = AuthRole.buildAuthRolesSet(AuthRole.OWNER); + assertEquals(5, ownerResult.size()); + assertEquals(Set.of(AuthRole.OWNER, AuthRole.ADMIN, AuthRole.EDITOR, AuthRole.READER, AuthRole.AUTHENTICATED_USER), ownerResult); + + final Set adminResult = AuthRole.buildAuthRolesSet(AuthRole.ADMIN); + assertEquals(4, adminResult.size()); + assertEquals(Set.of(AuthRole.ADMIN, AuthRole.EDITOR, AuthRole.READER, AuthRole.AUTHENTICATED_USER), adminResult); + + final Set editorResult = AuthRole.buildAuthRolesSet(AuthRole.EDITOR); + assertEquals(3, editorResult.size()); + assertEquals(Set.of(AuthRole.EDITOR, AuthRole.READER, AuthRole.AUTHENTICATED_USER), editorResult); + + final Set readerResult = AuthRole.buildAuthRolesSet(AuthRole.READER); + assertEquals(2, readerResult.size()); + assertEquals(Set.of(AuthRole.READER, AuthRole.AUTHENTICATED_USER), readerResult); + + final Set authenticatedUserResult = AuthRole.buildAuthRolesSet(AuthRole.AUTHENTICATED_USER); + assertEquals(1, authenticatedUserResult.size()); + assertEquals(Set.of(AuthRole.AUTHENTICATED_USER), authenticatedUserResult); + + final Set noneResult = AuthRole.buildAuthRolesSet(AuthRole.NONE); + assertEquals(1, noneResult.size()); + assertEquals(Set.of(AuthRole.NONE), noneResult); + + final Set nullResult = AuthRole.buildAuthRolesSet(null); + assertEquals(0, nullResult.size()); + } + +} diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/AttemptApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/AttemptApiController.java index d50e731d5e8d..c7c112405b44 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/AttemptApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/AttemptApiController.java @@ -4,6 +4,8 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.ADMIN; + import io.airbyte.api.generated.AttemptApi; import io.airbyte.api.model.generated.InternalOperationResult; import io.airbyte.api.model.generated.SaveStatsRequestBody; @@ -14,10 +16,13 @@ import io.micronaut.http.annotation.Body; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/attempt/") @Requires(property = "airbyte.deployment-mode", value = "OSS") +@Secured(SecurityRule.IS_AUTHENTICATED) public class AttemptApiController implements AttemptApi { private final AttemptHandler attemptHandler; @@ -36,6 +41,7 @@ public InternalOperationResult saveStats(final SaveStatsRequestBody requestBody) @Override @Post(uri = "/set_workflow_in_attempt", processes = MediaType.APPLICATION_JSON) + @Secured({ADMIN}) public InternalOperationResult setWorkflowInAttempt(@Body final SetWorkflowInAttemptRequestBody requestBody) { return ApiHelper.execute(() -> attemptHandler.setWorkflowInAttempt(requestBody)); } diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/ConnectionApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/ConnectionApiController.java index f0de78d03d31..f61b89e41447 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/ConnectionApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/ConnectionApiController.java @@ -4,6 +4,9 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.EDITOR; +import static io.airbyte.commons.auth.AuthRoleConstants.READER; + import io.airbyte.api.generated.ConnectionApi; import io.airbyte.api.model.generated.ConnectionCreate; import io.airbyte.api.model.generated.ConnectionIdRequestBody; @@ -21,11 +24,14 @@ import io.micronaut.http.annotation.Body; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/connections") @Context() @Requires(property = "airbyte.deployment-mode", value = "OSS") +@Secured(SecurityRule.IS_AUTHENTICATED) public class ConnectionApiController implements ConnectionApi { private final ConnectionsHandler connectionsHandler; @@ -42,24 +48,28 @@ public ConnectionApiController(final ConnectionsHandler connectionsHandler, @Override @Post(uri = "/create") + @Secured({EDITOR}) public ConnectionRead createConnection(@Body final ConnectionCreate connectionCreate) { return ApiHelper.execute(() -> connectionsHandler.createConnection(connectionCreate)); } @Override @Post(uri = "/update") + @Secured({EDITOR}) public ConnectionRead updateConnection(@Body final ConnectionUpdate connectionUpdate) { return ApiHelper.execute(() -> connectionsHandler.updateConnection(connectionUpdate)); } @Override @Post(uri = "/list") + @Secured({READER}) public ConnectionReadList listConnectionsForWorkspace(@Body final WorkspaceIdRequestBody workspaceIdRequestBody) { return ApiHelper.execute(() -> connectionsHandler.listConnectionsForWorkspace(workspaceIdRequestBody)); } @Override @Post(uri = "/list_all") + @Secured({READER}) public ConnectionReadList listAllConnectionsForWorkspace(@Body final WorkspaceIdRequestBody workspaceIdRequestBody) { return ApiHelper.execute(() -> connectionsHandler.listAllConnectionsForWorkspace(workspaceIdRequestBody)); } @@ -72,12 +82,14 @@ public ConnectionReadList searchConnections(@Body final ConnectionSearch connect @Override @Post(uri = "/get") + @Secured({READER}) public ConnectionRead getConnection(@Body final ConnectionIdRequestBody connectionIdRequestBody) { return ApiHelper.execute(() -> connectionsHandler.getConnection(connectionIdRequestBody.getConnectionId())); } @Override @Post(uri = "/delete") + @Secured({EDITOR}) public void deleteConnection(@Body final ConnectionIdRequestBody connectionIdRequestBody) { ApiHelper.execute(() -> { operationsHandler.deleteOperationsForConnection(connectionIdRequestBody); @@ -88,12 +100,14 @@ public void deleteConnection(@Body final ConnectionIdRequestBody connectionIdReq @Override @Post(uri = "/sync") + @Secured({EDITOR}) public JobInfoRead syncConnection(@Body final ConnectionIdRequestBody connectionIdRequestBody) { return ApiHelper.execute(() -> schedulerHandler.syncConnection(connectionIdRequestBody)); } @Override @Post(uri = "/reset") + @Secured({EDITOR}) public JobInfoRead resetConnection(@Body final ConnectionIdRequestBody connectionIdRequestBody) { return ApiHelper.execute(() -> schedulerHandler.resetConnection(connectionIdRequestBody)); } diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationApiController.java index 6c16cec4a596..7a20184b0e4d 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationApiController.java @@ -4,6 +4,9 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.EDITOR; +import static io.airbyte.commons.auth.AuthRoleConstants.READER; + import io.airbyte.api.generated.DestinationApi; import io.airbyte.api.model.generated.CheckConnectionRead; import io.airbyte.api.model.generated.DestinationCloneRequestBody; @@ -20,24 +23,32 @@ import io.micronaut.http.annotation.Body; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; -import lombok.AllArgsConstructor; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/destinations") @Requires(property = "airbyte.deployment-mode", value = "OSS") -@AllArgsConstructor +@Secured(SecurityRule.IS_AUTHENTICATED) public class DestinationApiController implements DestinationApi { private final DestinationHandler destinationHandler; private final SchedulerHandler schedulerHandler; + public DestinationApiController(final DestinationHandler destinationHandler, final SchedulerHandler schedulerHandler) { + this.destinationHandler = destinationHandler; + this.schedulerHandler = schedulerHandler; + } + @Post(uri = "/check_connection") + @Secured({EDITOR}) @Override public CheckConnectionRead checkConnectionToDestination(@Body final DestinationIdRequestBody destinationIdRequestBody) { return ApiHelper.execute(() -> schedulerHandler.checkDestinationConnectionFromDestinationId(destinationIdRequestBody)); } @Post(uri = "/check_connection_for_update") + @Secured({EDITOR}) @Override public CheckConnectionRead checkConnectionToDestinationForUpdate(@Body final DestinationUpdate destinationUpdate) { return ApiHelper.execute(() -> schedulerHandler.checkDestinationConnectionFromDestinationIdForUpdate(destinationUpdate)); @@ -50,12 +61,14 @@ public DestinationRead cloneDestination(@Body final DestinationCloneRequestBody } @Post(uri = "/create") + @Secured({EDITOR}) @Override public DestinationRead createDestination(@Body final DestinationCreate destinationCreate) { return ApiHelper.execute(() -> destinationHandler.createDestination(destinationCreate)); } @Post(uri = "/delete") + @Secured({EDITOR}) @Override public void deleteDestination(@Body final DestinationIdRequestBody destinationIdRequestBody) { ApiHelper.execute(() -> { @@ -65,12 +78,14 @@ public void deleteDestination(@Body final DestinationIdRequestBody destinationId } @Post(uri = "/get") + @Secured({READER}) @Override public DestinationRead getDestination(@Body final DestinationIdRequestBody destinationIdRequestBody) { return ApiHelper.execute(() -> destinationHandler.getDestination(destinationIdRequestBody)); } @Post(uri = "/list") + @Secured({READER}) @Override public DestinationReadList listDestinationsForWorkspace(@Body final WorkspaceIdRequestBody workspaceIdRequestBody) { return ApiHelper.execute(() -> destinationHandler.listDestinationsForWorkspace(workspaceIdRequestBody)); @@ -83,6 +98,7 @@ public DestinationReadList searchDestinations(@Body final DestinationSearch dest } @Post(uri = "/update") + @Secured({EDITOR}) @Override public DestinationRead updateDestination(@Body final DestinationUpdate destinationUpdate) { return ApiHelper.execute(() -> destinationHandler.updateDestination(destinationUpdate)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationDefinitionApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationDefinitionApiController.java index 54409d281021..ab559f6b3b09 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationDefinitionApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationDefinitionApiController.java @@ -4,6 +4,11 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.ADMIN; +import static io.airbyte.commons.auth.AuthRoleConstants.AUTHENTICATED_USER; +import static io.airbyte.commons.auth.AuthRoleConstants.EDITOR; +import static io.airbyte.commons.auth.AuthRoleConstants.READER; + import io.airbyte.api.generated.DestinationDefinitionApi; import io.airbyte.api.model.generated.CustomDestinationDefinitionCreate; import io.airbyte.api.model.generated.DestinationDefinitionIdRequestBody; @@ -19,11 +24,14 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/destination_definitions") @Requires(property = "airbyte.deployment-mode", value = "OSS") @Context +@Secured(SecurityRule.IS_AUTHENTICATED) public class DestinationDefinitionApiController implements DestinationDefinitionApi { private final DestinationDefinitionsHandler destinationDefinitionsHandler; @@ -33,12 +41,14 @@ public DestinationDefinitionApiController(final DestinationDefinitionsHandler de } @Post(uri = "/create_custom") + @Secured({EDITOR}) @Override public DestinationDefinitionRead createCustomDestinationDefinition(final CustomDestinationDefinitionCreate customDestinationDefinitionCreate) { return ApiHelper.execute(() -> destinationDefinitionsHandler.createCustomDestinationDefinition(customDestinationDefinitionCreate)); } @Post(uri = "/delete") + @Secured({ADMIN}) @Override public void deleteDestinationDefinition(final DestinationDefinitionIdRequestBody destinationDefinitionIdRequestBody) { ApiHelper.execute(() -> { @@ -48,18 +58,21 @@ public void deleteDestinationDefinition(final DestinationDefinitionIdRequestBody } @Post(uri = "/get") + @Secured({AUTHENTICATED_USER}) @Override public DestinationDefinitionRead getDestinationDefinition(final DestinationDefinitionIdRequestBody destinationDefinitionIdRequestBody) { return ApiHelper.execute(() -> destinationDefinitionsHandler.getDestinationDefinition(destinationDefinitionIdRequestBody)); } @Post(uri = "/get_for_workspace") + @Secured({READER}) @Override public DestinationDefinitionRead getDestinationDefinitionForWorkspace(final DestinationDefinitionIdWithWorkspaceId destinationDefinitionIdWithWorkspaceId) { return ApiHelper.execute(() -> destinationDefinitionsHandler.getDestinationDefinitionForWorkspace(destinationDefinitionIdWithWorkspaceId)); } @Post(uri = "/grant_definition") + @Secured({ADMIN}) @Override public PrivateDestinationDefinitionRead grantDestinationDefinitionToWorkspace(final DestinationDefinitionIdWithWorkspaceId destinationDefinitionIdWithWorkspaceId) { return ApiHelper @@ -67,30 +80,35 @@ public PrivateDestinationDefinitionRead grantDestinationDefinitionToWorkspace(fi } @Post(uri = "/list") + @Secured({AUTHENTICATED_USER}) @Override public DestinationDefinitionReadList listDestinationDefinitions() { return ApiHelper.execute(destinationDefinitionsHandler::listDestinationDefinitions); } @Post(uri = "/list_for_workspace") + @Secured({READER}) @Override public DestinationDefinitionReadList listDestinationDefinitionsForWorkspace(final WorkspaceIdRequestBody workspaceIdRequestBody) { return ApiHelper.execute(() -> destinationDefinitionsHandler.listDestinationDefinitionsForWorkspace(workspaceIdRequestBody)); } @Post(uri = "/list_latest") + @Secured({AUTHENTICATED_USER}) @Override public DestinationDefinitionReadList listLatestDestinationDefinitions() { return ApiHelper.execute(destinationDefinitionsHandler::listLatestDestinationDefinitions); } @Post(uri = "/list_private") + @Secured({ADMIN}) @Override public PrivateDestinationDefinitionReadList listPrivateDestinationDefinitions(final WorkspaceIdRequestBody workspaceIdRequestBody) { return ApiHelper.execute(() -> destinationDefinitionsHandler.listPrivateDestinationDefinitions(workspaceIdRequestBody)); } @Post(uri = "/revoke_definition") + @Secured({ADMIN}) @Override public void revokeDestinationDefinitionFromWorkspace(final DestinationDefinitionIdWithWorkspaceId destinationDefinitionIdWithWorkspaceId) { ApiHelper.execute(() -> { @@ -100,6 +118,7 @@ public void revokeDestinationDefinitionFromWorkspace(final DestinationDefinition } @Post(uri = "/update") + @Secured({AUTHENTICATED_USER}) @Override public DestinationDefinitionRead updateDestinationDefinition(final DestinationDefinitionUpdate destinationDefinitionUpdate) { return ApiHelper.execute(() -> destinationDefinitionsHandler.updateDestinationDefinition(destinationDefinitionUpdate)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationDefinitionSpecificationApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationDefinitionSpecificationApiController.java index 54492c4a905f..d93bc6dcbe99 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationDefinitionSpecificationApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationDefinitionSpecificationApiController.java @@ -4,6 +4,8 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.AUTHENTICATED_USER; + import io.airbyte.api.generated.DestinationDefinitionSpecificationApi; import io.airbyte.api.model.generated.DestinationDefinitionIdWithWorkspaceId; import io.airbyte.api.model.generated.DestinationDefinitionSpecificationRead; @@ -11,10 +13,13 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/destination_definition_specifications") @Requires(property = "airbyte.deployment-mode", value = "OSS") +@Secured(SecurityRule.IS_AUTHENTICATED) public class DestinationDefinitionSpecificationApiController implements DestinationDefinitionSpecificationApi { private final SchedulerHandler schedulerHandler; @@ -24,6 +29,7 @@ public DestinationDefinitionSpecificationApiController(final SchedulerHandler sc } @Post("/get") + @Secured({AUTHENTICATED_USER}) @Override public DestinationDefinitionSpecificationRead getDestinationDefinitionSpecification(final DestinationDefinitionIdWithWorkspaceId destinationDefinitionIdWithWorkspaceId) { return ApiHelper.execute(() -> schedulerHandler.getDestinationSpecification(destinationDefinitionIdWithWorkspaceId)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationOauthApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationOauthApiController.java index 33318e14efcb..58286dfacd48 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationOauthApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/DestinationOauthApiController.java @@ -4,6 +4,9 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.ADMIN; +import static io.airbyte.commons.auth.AuthRoleConstants.EDITOR; + import io.airbyte.api.generated.DestinationOauthApi; import io.airbyte.api.model.generated.CompleteDestinationOAuthRequest; import io.airbyte.api.model.generated.DestinationOauthConsentRequest; @@ -14,12 +17,15 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; import java.util.Map; @Controller("/api/v1/destination_oauths") @Requires(property = "airbyte.deployment-mode", value = "OSS") @Context +@Secured(SecurityRule.IS_AUTHENTICATED) public class DestinationOauthApiController implements DestinationOauthApi { private final OAuthHandler oAuthHandler; @@ -29,18 +35,21 @@ public DestinationOauthApiController(final OAuthHandler oAuthHandler) { } @Post("/complete_oauth") + @Secured({EDITOR}) @Override public Map completeDestinationOAuth(final CompleteDestinationOAuthRequest completeDestinationOAuthRequest) { return ApiHelper.execute(() -> oAuthHandler.completeDestinationOAuth(completeDestinationOAuthRequest)); } @Post("/get_consent_url") + @Secured({EDITOR}) @Override public OAuthConsentRead getDestinationOAuthConsent(final DestinationOauthConsentRequest destinationOauthConsentRequest) { return ApiHelper.execute(() -> oAuthHandler.getDestinationOAuthConsent(destinationOauthConsentRequest)); } @Post("/oauth_params/create") + @Secured({ADMIN}) @Override public void setInstancewideDestinationOauthParams(final SetInstancewideDestinationOauthParamsRequestBody setInstancewideDestinationOauthParamsRequestBody) { ApiHelper.execute(() -> { diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/HealthApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/HealthApiController.java index a5b46d39742b..3ffb6851cbc5 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/HealthApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/HealthApiController.java @@ -11,10 +11,13 @@ import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/health") @Requires(property = "airbyte.deployment-mode", value = "OSS") +@Secured(SecurityRule.IS_ANONYMOUS) public class HealthApiController implements HealthApi { private final HealthCheckHandler healthCheckHandler; diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/JobsApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/JobsApiController.java index 9fadce045d05..b930472e5d23 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/JobsApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/JobsApiController.java @@ -4,6 +4,10 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.ADMIN; +import static io.airbyte.commons.auth.AuthRoleConstants.EDITOR; +import static io.airbyte.commons.auth.AuthRoleConstants.READER; + import io.airbyte.api.generated.JobsApi; import io.airbyte.api.model.generated.AttemptNormalizationStatusReadList; import io.airbyte.api.model.generated.ConnectionIdRequestBody; @@ -20,11 +24,14 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/jobs") @Requires(property = "airbyte.deployment-mode", value = "OSS") @Context +@Secured(SecurityRule.IS_AUTHENTICATED) public class JobsApiController implements JobsApi { private final JobHistoryHandler jobHistoryHandler; @@ -36,42 +43,49 @@ public JobsApiController(final JobHistoryHandler jobHistoryHandler, final Schedu } @Post("/cancel") + @Secured({EDITOR}) @Override public JobInfoRead cancelJob(final JobIdRequestBody jobIdRequestBody) { return ApiHelper.execute(() -> schedulerHandler.cancelJob(jobIdRequestBody)); } @Post("/get_normalization_status") + @Secured({ADMIN}) @Override public AttemptNormalizationStatusReadList getAttemptNormalizationStatusesForJob(final JobIdRequestBody jobIdRequestBody) { return ApiHelper.execute(() -> jobHistoryHandler.getAttemptNormalizationStatuses(jobIdRequestBody)); } @Post("/get_debug_info") + @Secured({READER}) @Override public JobDebugInfoRead getJobDebugInfo(final JobIdRequestBody jobIdRequestBody) { return ApiHelper.execute(() -> jobHistoryHandler.getJobDebugInfo(jobIdRequestBody)); } @Post("/get") + @Secured({READER}) @Override public JobInfoRead getJobInfo(final JobIdRequestBody jobIdRequestBody) { return ApiHelper.execute(() -> jobHistoryHandler.getJobInfo(jobIdRequestBody)); } @Post("/get_light") + @Secured({READER}) @Override public JobInfoLightRead getJobInfoLight(final JobIdRequestBody jobIdRequestBody) { return ApiHelper.execute(() -> jobHistoryHandler.getJobInfoLight(jobIdRequestBody)); } @Post("/get_last_replication_job") + @Secured({READER}) @Override public JobOptionalRead getLastReplicationJob(final ConnectionIdRequestBody connectionIdRequestBody) { return ApiHelper.execute(() -> jobHistoryHandler.getLastReplicationJob(connectionIdRequestBody)); } @Post("/list") + @Secured({READER}) @Override public JobReadList listJobsFor(final JobListRequestBody jobListRequestBody) { return ApiHelper.execute(() -> jobHistoryHandler.listJobsFor(jobListRequestBody)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/LogsApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/LogsApiController.java index 126ed8202693..13257412cf04 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/LogsApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/LogsApiController.java @@ -4,6 +4,8 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.ADMIN; + import io.airbyte.api.generated.LogsApi; import io.airbyte.api.model.generated.LogsRequestBody; import io.airbyte.server.handlers.LogsHandler; @@ -11,12 +13,15 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; import java.io.File; @Controller("/api/v1/logs") @Requires(property = "airbyte.deployment-mode", value = "OSS") @Context +@Secured(SecurityRule.IS_AUTHENTICATED) public class LogsApiController implements LogsApi { private final LogsHandler logsHandler; @@ -26,6 +31,7 @@ public LogsApiController(final LogsHandler logsHandler) { } @Post("/get") + @Secured({ADMIN}) @Override public File getLogs(final LogsRequestBody logsRequestBody) { return ApiHelper.execute(() -> logsHandler.getLogs(logsRequestBody)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/NotFoundController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/NotFoundController.java index a59dea440b05..0a089a21b762 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/NotFoundController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/NotFoundController.java @@ -11,6 +11,8 @@ import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Error; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; /** * Custom controller that handles global 404 responses for unknown/unmapped paths. @@ -18,6 +20,7 @@ @Controller("/api/notfound") @Requires(property = "airbyte.deployment-mode", value = "OSS") +@Secured(SecurityRule.IS_ANONYMOUS) public class NotFoundController { @Error(status = HttpStatus.NOT_FOUND, diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/NotificationsApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/NotificationsApiController.java index 6af6a1cdf242..c81799b9ac92 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/NotificationsApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/NotificationsApiController.java @@ -4,6 +4,8 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.AUTHENTICATED_USER; + import io.airbyte.api.generated.NotificationsApi; import io.airbyte.api.model.generated.Notification; import io.airbyte.api.model.generated.NotificationRead; @@ -12,10 +14,13 @@ import io.micronaut.http.annotation.Body; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/notifications/try") @Requires(property = "airbyte.deployment-mode", value = "OSS") +@Secured(SecurityRule.IS_AUTHENTICATED) public class NotificationsApiController implements NotificationsApi { private final WorkspacesHandler workspacesHandler; @@ -25,6 +30,7 @@ public NotificationsApiController(final WorkspacesHandler workspacesHandler) { } @Post + @Secured({AUTHENTICATED_USER}) @Override public NotificationRead tryNotificationConfig(@Body final Notification notification) { return ApiHelper.execute(() -> workspacesHandler.tryNotification(notification)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/OpenapiApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/OpenapiApiController.java index 3aa984d7b2c7..5e574a23deb9 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/OpenapiApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/OpenapiApiController.java @@ -4,16 +4,21 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.AUTHENTICATED_USER; + import io.airbyte.api.generated.OpenapiApi; import io.airbyte.server.handlers.OpenApiConfigHandler; import io.micronaut.context.annotation.Requires; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; import java.io.File; @Controller("/api/v1/openapi") @Requires(property = "airbyte.deployment-mode", value = "OSS") +@Secured(SecurityRule.IS_AUTHENTICATED) public class OpenapiApiController implements OpenapiApi { private final OpenApiConfigHandler openApiConfigHandler; @@ -23,6 +28,7 @@ public OpenapiApiController(final OpenApiConfigHandler openApiConfigHandler) { } @Get(produces = "text/plain") + @Secured({AUTHENTICATED_USER}) @Override public File getOpenApiSpec() { return ApiHelper.execute(openApiConfigHandler::getFile); diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/OperationApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/OperationApiController.java index a892ca03d47b..172fbb76e40a 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/OperationApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/OperationApiController.java @@ -4,6 +4,10 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.AUTHENTICATED_USER; +import static io.airbyte.commons.auth.AuthRoleConstants.EDITOR; +import static io.airbyte.commons.auth.AuthRoleConstants.READER; + import io.airbyte.api.generated.OperationApi; import io.airbyte.api.model.generated.CheckOperationRead; import io.airbyte.api.model.generated.ConnectionIdRequestBody; @@ -18,10 +22,13 @@ import io.micronaut.http.annotation.Body; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/operations") @Requires(property = "airbyte.deployment-mode", value = "OSS") +@Secured(SecurityRule.IS_AUTHENTICATED) public class OperationApiController implements OperationApi { private final OperationsHandler operationsHandler; @@ -31,6 +38,7 @@ public OperationApiController(final OperationsHandler operationsHandler) { } @Post("/check") + @Secured({AUTHENTICATED_USER}) @Override public CheckOperationRead checkOperation(@Body final OperatorConfiguration operatorConfiguration) { return ApiHelper.execute(() -> operationsHandler.checkOperation(operatorConfiguration)); @@ -38,11 +46,13 @@ public CheckOperationRead checkOperation(@Body final OperatorConfiguration opera @Post("/create") @Override + @Secured({EDITOR}) public OperationRead createOperation(@Body final OperationCreate operationCreate) { return ApiHelper.execute(() -> operationsHandler.createOperation(operationCreate)); } @Post("/delete") + @Secured({EDITOR}) @Override public void deleteOperation(@Body final OperationIdRequestBody operationIdRequestBody) { ApiHelper.execute(() -> { @@ -52,18 +62,21 @@ public void deleteOperation(@Body final OperationIdRequestBody operationIdReques } @Post("/get") + @Secured({READER}) @Override public OperationRead getOperation(@Body final OperationIdRequestBody operationIdRequestBody) { return ApiHelper.execute(() -> operationsHandler.getOperation(operationIdRequestBody)); } @Post("/list") + @Secured({READER}) @Override public OperationReadList listOperationsForConnection(@Body final ConnectionIdRequestBody connectionIdRequestBody) { return ApiHelper.execute(() -> operationsHandler.listOperationsForConnection(connectionIdRequestBody)); } @Post("/update") + @Secured({EDITOR}) @Override public OperationRead updateOperation(@Body final OperationUpdate operationUpdate) { return ApiHelper.execute(() -> operationsHandler.updateOperation(operationUpdate)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/SchedulerApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/SchedulerApiController.java index 53019774aeca..365dc04afc46 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/SchedulerApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/SchedulerApiController.java @@ -4,6 +4,9 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.AUTHENTICATED_USER; +import static io.airbyte.commons.auth.AuthRoleConstants.EDITOR; + import io.airbyte.api.generated.SchedulerApi; import io.airbyte.api.model.generated.CheckConnectionRead; import io.airbyte.api.model.generated.DestinationCoreConfig; @@ -13,10 +16,13 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/scheduler") @Requires(property = "airbyte.deployment-mode", value = "OSS") +@Secured(SecurityRule.IS_AUTHENTICATED) public class SchedulerApiController implements SchedulerApi { private final SchedulerHandler schedulerHandler; @@ -26,18 +32,21 @@ public SchedulerApiController(final SchedulerHandler schedulerHandler) { } @Post("/destinations/check_connection") + @Secured({AUTHENTICATED_USER}) @Override public CheckConnectionRead executeDestinationCheckConnection(final DestinationCoreConfig destinationCoreConfig) { return ApiHelper.execute(() -> schedulerHandler.checkDestinationConnectionFromDestinationCreate(destinationCoreConfig)); } @Post("/sources/check_connection") + @Secured({AUTHENTICATED_USER}) @Override public CheckConnectionRead executeSourceCheckConnection(final SourceCoreConfig sourceCoreConfig) { return ApiHelper.execute(() -> schedulerHandler.checkSourceConnectionFromSourceCreate(sourceCoreConfig)); } @Post("/sources/discover_schema") + @Secured({EDITOR}) @Override public SourceDiscoverSchemaRead executeSourceDiscoverSchema(final SourceCoreConfig sourceCoreConfig) { return ApiHelper.execute(() -> schedulerHandler.discoverSchemaForSourceFromSourceCreate(sourceCoreConfig)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/SourceApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/SourceApiController.java index 64cf7c58dfd6..0c680f742989 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/SourceApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/SourceApiController.java @@ -4,6 +4,9 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.EDITOR; +import static io.airbyte.commons.auth.AuthRoleConstants.READER; + import io.airbyte.api.generated.SourceApi; import io.airbyte.api.model.generated.ActorCatalogWithUpdatedAt; import io.airbyte.api.model.generated.CheckConnectionRead; @@ -22,10 +25,13 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/sources") @Requires(property = "airbyte.deployment-mode", value = "OSS") +@Secured(SecurityRule.IS_AUTHENTICATED) public class SourceApiController implements SourceApi { private final SchedulerHandler schedulerHandler; @@ -37,12 +43,14 @@ public SourceApiController(final SchedulerHandler schedulerHandler, final Source } @Post("/check_connection") + @Secured({EDITOR}) @Override public CheckConnectionRead checkConnectionToSource(final SourceIdRequestBody sourceIdRequestBody) { return ApiHelper.execute(() -> schedulerHandler.checkSourceConnectionFromSourceId(sourceIdRequestBody)); } @Post("/check_connection_for_update") + @Secured({EDITOR}) @Override public CheckConnectionRead checkConnectionToSourceForUpdate(final SourceUpdate sourceUpdate) { return ApiHelper.execute(() -> schedulerHandler.checkSourceConnectionFromSourceIdForUpdate(sourceUpdate)); @@ -55,12 +63,14 @@ public SourceRead cloneSource(final SourceCloneRequestBody sourceCloneRequestBod } @Post("/create") + @Secured({EDITOR}) @Override public SourceRead createSource(final SourceCreate sourceCreate) { return ApiHelper.execute(() -> sourceHandler.createSource(sourceCreate)); } @Post("/delete") + @Secured({EDITOR}) @Override public void deleteSource(final SourceIdRequestBody sourceIdRequestBody) { ApiHelper.execute(() -> { @@ -70,24 +80,28 @@ public void deleteSource(final SourceIdRequestBody sourceIdRequestBody) { } @Post("/discover_schema") + @Secured({EDITOR}) @Override public SourceDiscoverSchemaRead discoverSchemaForSource(final SourceDiscoverSchemaRequestBody sourceDiscoverSchemaRequestBody) { return ApiHelper.execute(() -> schedulerHandler.discoverSchemaForSourceFromSourceId(sourceDiscoverSchemaRequestBody)); } @Post("/get") + @Secured({READER}) @Override public SourceRead getSource(final SourceIdRequestBody sourceIdRequestBody) { return ApiHelper.execute(() -> sourceHandler.getSource(sourceIdRequestBody)); } @Post("/most_recent_source_actor_catalog") + @Secured({READER}) @Override public ActorCatalogWithUpdatedAt getMostRecentSourceActorCatalog(final SourceIdRequestBody sourceIdRequestBody) { return ApiHelper.execute(() -> sourceHandler.getMostRecentSourceActorCatalogWithUpdatedAt(sourceIdRequestBody)); } @Post("/list") + @Secured({READER}) @Override public SourceReadList listSourcesForWorkspace(final WorkspaceIdRequestBody workspaceIdRequestBody) { return ApiHelper.execute(() -> sourceHandler.listSourcesForWorkspace(workspaceIdRequestBody)); @@ -100,6 +114,7 @@ public SourceReadList searchSources(final SourceSearch sourceSearch) { } @Post("/update") + @Secured({EDITOR}) @Override public SourceRead updateSource(final SourceUpdate sourceUpdate) { return ApiHelper.execute(() -> sourceHandler.updateSource(sourceUpdate)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/SourceDefinitionApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/SourceDefinitionApiController.java index 71222c7873c2..97a030dc7f70 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/SourceDefinitionApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/SourceDefinitionApiController.java @@ -4,6 +4,11 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.ADMIN; +import static io.airbyte.commons.auth.AuthRoleConstants.AUTHENTICATED_USER; +import static io.airbyte.commons.auth.AuthRoleConstants.EDITOR; +import static io.airbyte.commons.auth.AuthRoleConstants.READER; + import io.airbyte.api.generated.SourceDefinitionApi; import io.airbyte.api.model.generated.CustomSourceDefinitionCreate; import io.airbyte.api.model.generated.PrivateSourceDefinitionRead; @@ -19,11 +24,14 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/source_definitions") @Requires(property = "airbyte.deployment-mode", value = "OSS") @Context +@Secured(SecurityRule.IS_AUTHENTICATED) public class SourceDefinitionApiController implements SourceDefinitionApi { private final SourceDefinitionsHandler sourceDefinitionsHandler; @@ -33,12 +41,14 @@ public SourceDefinitionApiController(final SourceDefinitionsHandler sourceDefini } @Post("/create_custom") + @Secured({EDITOR}) @Override public SourceDefinitionRead createCustomSourceDefinition(final CustomSourceDefinitionCreate customSourceDefinitionCreate) { return ApiHelper.execute(() -> sourceDefinitionsHandler.createCustomSourceDefinition(customSourceDefinitionCreate)); } @Post("/delete") + @Secured({ADMIN}) @Override public void deleteSourceDefinition(final SourceDefinitionIdRequestBody sourceDefinitionIdRequestBody) { ApiHelper.execute(() -> { @@ -48,48 +58,56 @@ public void deleteSourceDefinition(final SourceDefinitionIdRequestBody sourceDef } @Post("/get") + @Secured({AUTHENTICATED_USER}) @Override public SourceDefinitionRead getSourceDefinition(final SourceDefinitionIdRequestBody sourceDefinitionIdRequestBody) { return ApiHelper.execute(() -> sourceDefinitionsHandler.getSourceDefinition(sourceDefinitionIdRequestBody)); } @Post("/get_for_workspace") + @Secured({READER}) @Override public SourceDefinitionRead getSourceDefinitionForWorkspace(final SourceDefinitionIdWithWorkspaceId sourceDefinitionIdWithWorkspaceId) { return ApiHelper.execute(() -> sourceDefinitionsHandler.getSourceDefinitionForWorkspace(sourceDefinitionIdWithWorkspaceId)); } @Post("/grant_definition") + @Secured({ADMIN}) @Override public PrivateSourceDefinitionRead grantSourceDefinitionToWorkspace(final SourceDefinitionIdWithWorkspaceId sourceDefinitionIdWithWorkspaceId) { return ApiHelper.execute(() -> sourceDefinitionsHandler.grantSourceDefinitionToWorkspace(sourceDefinitionIdWithWorkspaceId)); } @Post("/list_latest") + @Secured({AUTHENTICATED_USER}) @Override public SourceDefinitionReadList listLatestSourceDefinitions() { return ApiHelper.execute(sourceDefinitionsHandler::listLatestSourceDefinitions); } @Post("/list_private") + @Secured({ADMIN}) @Override public PrivateSourceDefinitionReadList listPrivateSourceDefinitions(final WorkspaceIdRequestBody workspaceIdRequestBody) { return ApiHelper.execute(() -> sourceDefinitionsHandler.listPrivateSourceDefinitions(workspaceIdRequestBody)); } @Post("/list") + @Secured({AUTHENTICATED_USER}) @Override public SourceDefinitionReadList listSourceDefinitions() { return ApiHelper.execute(sourceDefinitionsHandler::listSourceDefinitions); } @Post("/list_for_workspace") + @Secured({READER}) @Override public SourceDefinitionReadList listSourceDefinitionsForWorkspace(final WorkspaceIdRequestBody workspaceIdRequestBody) { return ApiHelper.execute(() -> sourceDefinitionsHandler.listSourceDefinitionsForWorkspace(workspaceIdRequestBody)); } @Post("/revoke_definition") + @Secured({ADMIN}) @Override public void revokeSourceDefinitionFromWorkspace(final SourceDefinitionIdWithWorkspaceId sourceDefinitionIdWithWorkspaceId) { ApiHelper.execute(() -> { @@ -99,6 +117,7 @@ public void revokeSourceDefinitionFromWorkspace(final SourceDefinitionIdWithWork } @Post("/update") + @Secured({AUTHENTICATED_USER}) @Override public SourceDefinitionRead updateSourceDefinition(final SourceDefinitionUpdate sourceDefinitionUpdate) { return ApiHelper.execute(() -> sourceDefinitionsHandler.updateSourceDefinition(sourceDefinitionUpdate)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/SourceDefinitionSpecificationApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/SourceDefinitionSpecificationApiController.java index 590f7340b61b..e87b5c28bddc 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/SourceDefinitionSpecificationApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/SourceDefinitionSpecificationApiController.java @@ -4,6 +4,8 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.AUTHENTICATED_USER; + import io.airbyte.api.generated.SourceDefinitionSpecificationApi; import io.airbyte.api.model.generated.SourceDefinitionIdWithWorkspaceId; import io.airbyte.api.model.generated.SourceDefinitionSpecificationRead; @@ -11,10 +13,13 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/source_definition_specifications") @Requires(property = "airbyte.deployment-mode", value = "OSS") +@Secured(SecurityRule.IS_AUTHENTICATED) public class SourceDefinitionSpecificationApiController implements SourceDefinitionSpecificationApi { private final SchedulerHandler schedulerHandler; @@ -24,6 +29,7 @@ public SourceDefinitionSpecificationApiController(final SchedulerHandler schedul } @Post("/get") + @Secured({AUTHENTICATED_USER}) @Override public SourceDefinitionSpecificationRead getSourceDefinitionSpecification(final SourceDefinitionIdWithWorkspaceId sourceDefinitionIdWithWorkspaceId) { return ApiHelper.execute(() -> schedulerHandler.getSourceDefinitionSpecification(sourceDefinitionIdWithWorkspaceId)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/SourceOauthApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/SourceOauthApiController.java index 2bba632cf638..52cbc82beb97 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/SourceOauthApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/SourceOauthApiController.java @@ -4,6 +4,9 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.ADMIN; +import static io.airbyte.commons.auth.AuthRoleConstants.EDITOR; + import io.airbyte.api.generated.SourceOauthApi; import io.airbyte.api.model.generated.CompleteSourceOauthRequest; import io.airbyte.api.model.generated.OAuthConsentRead; @@ -14,11 +17,14 @@ import io.micronaut.http.annotation.Body; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; import java.util.Map; @Controller("/api/v1/source_oauths") @Requires(property = "airbyte.deployment-mode", value = "OSS") +@Secured(SecurityRule.IS_AUTHENTICATED) public class SourceOauthApiController implements SourceOauthApi { private final OAuthHandler oAuthHandler; @@ -28,18 +34,21 @@ public SourceOauthApiController(final OAuthHandler oAuthHandler) { } @Post("/complete_oauth") + @Secured({EDITOR}) @Override public Map completeSourceOAuth(@Body final CompleteSourceOauthRequest completeSourceOauthRequest) { return ApiHelper.execute(() -> oAuthHandler.completeSourceOAuth(completeSourceOauthRequest)); } @Post("/get_consent_url") + @Secured({EDITOR}) @Override public OAuthConsentRead getSourceOAuthConsent(@Body final SourceOauthConsentRequest sourceOauthConsentRequest) { return ApiHelper.execute(() -> oAuthHandler.getSourceOAuthConsent(sourceOauthConsentRequest)); } @Post("/oauth_params/create") + @Secured({ADMIN}) @Override public void setInstancewideSourceOauthParams(@Body final SetInstancewideSourceOauthParamsRequestBody setInstancewideSourceOauthParamsRequestBody) { ApiHelper.execute(() -> { diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/StateApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/StateApiController.java index b1b62373aaa1..870a499d5999 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/StateApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/StateApiController.java @@ -4,6 +4,9 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.ADMIN; +import static io.airbyte.commons.auth.AuthRoleConstants.READER; + import io.airbyte.api.generated.StateApi; import io.airbyte.api.model.generated.ConnectionIdRequestBody; import io.airbyte.api.model.generated.ConnectionState; @@ -12,10 +15,13 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/state") @Requires(property = "airbyte.deployment-mode", value = "OSS") +@Secured(SecurityRule.IS_AUTHENTICATED) public class StateApiController implements StateApi { private final StateHandler stateHandler; @@ -25,12 +31,14 @@ public StateApiController(final StateHandler stateHandler) { } @Post("/create_or_update") + @Secured({ADMIN}) @Override public ConnectionState createOrUpdateState(final ConnectionStateCreateOrUpdate connectionStateCreateOrUpdate) { return ApiHelper.execute(() -> stateHandler.createOrUpdateState(connectionStateCreateOrUpdate)); } @Post("/get") + @Secured({READER}) @Override public ConnectionState getState(final ConnectionIdRequestBody connectionIdRequestBody) { return ApiHelper.execute(() -> stateHandler.getState(connectionIdRequestBody)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/WebBackendApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/WebBackendApiController.java index ac13e02015c1..af8c5a3ad316 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/WebBackendApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/WebBackendApiController.java @@ -4,6 +4,10 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.AUTHENTICATED_USER; +import static io.airbyte.commons.auth.AuthRoleConstants.EDITOR; +import static io.airbyte.commons.auth.AuthRoleConstants.READER; + import io.airbyte.api.generated.WebBackendApi; import io.airbyte.api.model.generated.ConnectionIdRequestBody; import io.airbyte.api.model.generated.ConnectionStateType; @@ -23,10 +27,13 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/web_backend") @Requires(property = "airbyte.deployment-mode", value = "OSS") +@Secured(SecurityRule.IS_AUTHENTICATED) public class WebBackendApiController implements WebBackendApi { private final WebBackendConnectionsHandler webBackendConnectionsHandler; @@ -42,48 +49,56 @@ public WebBackendApiController(final WebBackendConnectionsHandler webBackendConn } @Post("/state/get_type") + @Secured({READER}) @Override public ConnectionStateType getStateType(final ConnectionIdRequestBody connectionIdRequestBody) { return ApiHelper.execute(() -> webBackendConnectionsHandler.getStateType(connectionIdRequestBody)); } @Post("/check_updates") + @Secured({READER}) @Override public WebBackendCheckUpdatesRead webBackendCheckUpdates() { return ApiHelper.execute(webBackendCheckUpdatesHandler::checkUpdates); } @Post("/connections/create") + @Secured({EDITOR}) @Override public WebBackendConnectionRead webBackendCreateConnection(final WebBackendConnectionCreate webBackendConnectionCreate) { return ApiHelper.execute(() -> webBackendConnectionsHandler.webBackendCreateConnection(webBackendConnectionCreate)); } @Post("/connections/get") + @Secured({READER}) @Override public WebBackendConnectionRead webBackendGetConnection(final WebBackendConnectionRequestBody webBackendConnectionRequestBody) { return ApiHelper.execute(() -> webBackendConnectionsHandler.webBackendGetConnection(webBackendConnectionRequestBody)); } @Post("/workspace/state") + @Secured({READER}) @Override public WebBackendWorkspaceStateResult webBackendGetWorkspaceState(final WebBackendWorkspaceState webBackendWorkspaceState) { return ApiHelper.execute(() -> webBackendConnectionsHandler.getWorkspaceState(webBackendWorkspaceState)); } @Post("/connections/list") + @Secured({READER}) @Override public WebBackendConnectionReadList webBackendListConnectionsForWorkspace(final WebBackendConnectionListRequestBody webBackendConnectionListRequestBody) { return ApiHelper.execute(() -> webBackendConnectionsHandler.webBackendListConnectionsForWorkspace(webBackendConnectionListRequestBody)); } @Post("/geographies/list") + @Secured({AUTHENTICATED_USER}) @Override public WebBackendGeographiesListResult webBackendListGeographies() { return ApiHelper.execute(webBackendGeographiesHandler::listGeographiesOSS); } @Post("/connections/update") + @Secured({EDITOR}) @Override public WebBackendConnectionRead webBackendUpdateConnection(final WebBackendConnectionUpdate webBackendConnectionUpdate) { return ApiHelper.execute(() -> webBackendConnectionsHandler.webBackendUpdateConnection(webBackendConnectionUpdate)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/WorkspaceApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/WorkspaceApiController.java index 14d3fdc90ad8..1942a4d03afb 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/WorkspaceApiController.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/WorkspaceApiController.java @@ -4,6 +4,9 @@ package io.airbyte.server.apis; +import static io.airbyte.commons.auth.AuthRoleConstants.AUTHENTICATED_USER; +import static io.airbyte.commons.auth.AuthRoleConstants.OWNER; + import io.airbyte.api.generated.WorkspaceApi; import io.airbyte.api.model.generated.ConnectionIdRequestBody; import io.airbyte.api.model.generated.SlugRequestBody; @@ -19,10 +22,14 @@ import io.micronaut.http.annotation.Body; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; @Controller("/api/v1/workspaces") @Requires(property = "airbyte.deployment-mode", value = "OSS") +@Secured(SecurityRule.IS_AUTHENTICATED) +@SuppressWarnings("PMD.AvoidDuplicateLiterals") public class WorkspaceApiController implements WorkspaceApi { private final WorkspacesHandler workspacesHandler; @@ -32,12 +39,14 @@ public WorkspaceApiController(final WorkspacesHandler workspacesHandler) { } @Post("/create") + @Secured({AUTHENTICATED_USER}) @Override public WorkspaceRead createWorkspace(@Body final WorkspaceCreate workspaceCreate) { return ApiHelper.execute(() -> workspacesHandler.createWorkspace(workspaceCreate)); } @Post("/delete") + @Secured({OWNER}) @Override public void deleteWorkspace(@Body final WorkspaceIdRequestBody workspaceIdRequestBody) { ApiHelper.execute(() -> { @@ -47,30 +56,35 @@ public void deleteWorkspace(@Body final WorkspaceIdRequestBody workspaceIdReques } @Post("/get") + @Secured({OWNER}) @Override public WorkspaceRead getWorkspace(@Body final WorkspaceIdRequestBody workspaceIdRequestBody) { return ApiHelper.execute(() -> workspacesHandler.getWorkspace(workspaceIdRequestBody)); } @Post("/get_by_slug") + @Secured({OWNER}) @Override public WorkspaceRead getWorkspaceBySlug(@Body final SlugRequestBody slugRequestBody) { return ApiHelper.execute(() -> workspacesHandler.getWorkspaceBySlug(slugRequestBody)); } @Post("/list") + @Secured({AUTHENTICATED_USER}) @Override public WorkspaceReadList listWorkspaces() { return ApiHelper.execute(workspacesHandler::listWorkspaces); } @Post("/update") + @Secured({OWNER}) @Override public WorkspaceRead updateWorkspace(@Body final WorkspaceUpdate workspaceUpdate) { return ApiHelper.execute(() -> workspacesHandler.updateWorkspace(workspaceUpdate)); } @Post("/tag_feedback_status_as_done") + @Secured({OWNER}) @Override public void updateWorkspaceFeedback(@Body final WorkspaceGiveFeedback workspaceGiveFeedback) { ApiHelper.execute(() -> { @@ -80,12 +94,14 @@ public void updateWorkspaceFeedback(@Body final WorkspaceGiveFeedback workspaceG } @Post("/update_name") + @Secured({OWNER}) @Override public WorkspaceRead updateWorkspaceName(@Body final WorkspaceUpdateName workspaceUpdateName) { return ApiHelper.execute(() -> workspacesHandler.updateWorkspaceName(workspaceUpdateName)); } @Post("/get_by_connection_id") + @Secured({AUTHENTICATED_USER}) @Override public WorkspaceRead getWorkspaceByConnectionId(@Body final ConnectionIdRequestBody connectionIdRequestBody) { return ApiHelper.execute(() -> workspacesHandler.getWorkspaceByConnectionId(connectionIdRequestBody)); diff --git a/airbyte-server/src/main/resources/application.yml b/airbyte-server/src/main/resources/application.yml index 53e670c2c523..1c58a63ef546 100644 --- a/airbyte-server/src/main/resources/application.yml +++ b/airbyte-server/src/main/resources/application.yml @@ -2,19 +2,8 @@ micronaut: application: name: airbyte-server security: - intercept-url-map: - - pattern: /** - httpMethod: GET - access: - - isAnonymous() - - pattern: /** - httpMethod: POST - access: - - isAnonymous() - - pattern: /** - httpMethod: HEAD - access: - - isAnonymous() + authentication-provider-strategy: ALL + enabled: ${API_AUTHORIZATION_ENABLED:false} server: port: 8001 cors: @@ -118,6 +107,32 @@ datasources: username: ${DATABASE_USER} password: ${DATABASE_PASSWORD} +endpoints: + beans: + enabled: true + sensitive: false + env: + enabled: true + sensitive: false + health: + enabled: true + sensitive: false + info: + enabled: true + sensitive: true + loggers: + enabled: true + sensitive: true + refresh: + enabled: false + sensitive: true + routes: + enabled: true + sensitive: false + threaddump: + enabled: true + sensitive: true + flyway: enabled: true datasources: @@ -138,3 +153,10 @@ jooq: jobs: jackson-converter-enabled: true sql-dialect: POSTGRES + +logger: + levels: + # Uncomment to help resolve issues with conditional beans + # io.micronaut.context.condition: DEBUG + # Uncomment to help resolve issues with security beans + # io.micronaut.security: DEBUG diff --git a/deps.toml b/deps.toml index f3a642d9bab4..0fa2bd639d69 100644 --- a/deps.toml +++ b/deps.toml @@ -24,6 +24,7 @@ micronaut = "3.8.1" micronaut-test = "3.8.0" platform-testcontainers = "1.17.3" postgresql = "42.3.5" +reactor = "3.5.2" slf4j = "1.7.36" temporal = "1.17.0" @@ -101,6 +102,7 @@ platform-testcontainers-jdbc = { module = "org.testcontainers:jdbc", version.ref platform-testcontainers-postgresql = { module = "org.testcontainers:postgresql", version.ref = "platform-testcontainers" } postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" } quartz-scheduler = { module = "org.quartz-scheduler:quartz", version = "2.3.2" } +reactor-core = { module = "io.projectreactor:reactor-core", version.ref = "reactor" } s3 = { module = "software.amazon.awssdk:s3", version = "2.16.84" } slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } spotbugs-annotations = { module = "com.github.spotbugs:spotbugs-annotations", version = "4.7.3" }