This is an automated email from the ASF dual-hosted git repository. arshad pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/seatunnel-web.git
The following commit(s) were added to refs/heads/main by this push: new b343fdf7 [Feature] Add Support for Authorization (#285) b343fdf7 is described below commit b343fdf742df2954b06ffd53ed3dc93b7a470c7e Author: Mohammad Arshad <ars...@apache.org> AuthorDate: Fri Apr 11 15:11:25 2025 +0530 [Feature] Add Support for Authorization (#285) --- pom.xml | 1 + seatunnel-server/seatunnel-app/pom.xml | 5 + .../src/main/bin/seatunnel-backend-daemon.sh | 9 +- .../seatunnel/app/adapter/SeatunnelWebAdapter.java | 3 + .../app/controller/JobConfigController.java | 4 +- .../seatunnel/app/dal/mapper/JobLineMapper.java | 2 - .../seatunnel/app/dal/mapper/UserMapper.java | 2 + .../app/interceptor/AuthenticationInterceptor.java | 8 +- .../app/permission/ISeatunnelPermissonService.java | 60 -- .../SeatunnelAccessControllerConfig.java | 45 ++ .../SeatunnelAccessControllerDefaultImpl.java | 45 ++ .../permission/SeatunnelPermissionServiceImpl.java | 61 -- .../apache/seatunnel/app/security/UserContext.java | 3 +- .../seatunnel/app/security/UserContextHolder.java | 6 + .../seatunnel/app/service/IJobConfigService.java | 6 +- .../app/service/ISeatunnelBaseService.java | 19 +- .../seatunnel/app/service/WorkspaceService.java | 2 - .../app/service/impl/DatasourceServiceImpl.java | 109 ++- .../app/service/impl/JobConfigServiceImpl.java | 26 +- .../app/service/impl/JobDefinitionServiceImpl.java | 53 +- .../app/service/impl/JobInstanceServiceImpl.java | 11 +- .../seatunnel/app/service/impl/JobServiceImpl.java | 20 +- .../app/service/impl/SeatunnelBaseServiceImpl.java | 38 +- .../app/service/impl/TaskInstanceServiceImpl.java | 26 +- .../app/service/impl/UserServiceImpl.java | 41 +- .../app/service/impl/VirtualTableServiceImpl.java | 52 +- .../app/service/impl/WorkspaceServiceImpl.java | 48 +- .../app/utils/GlobalExceptionHandler.java | 6 + .../src/main/resources/application.yml | 1 + .../server/common/SeatunnelErrorEnum.java | 3 +- seatunnel-web-common/pom.xml | 25 + .../common/access/AccessDeniedException.java | 27 +- .../apache/seatunnel/common/access/AccessInfo.java | 18 +- .../apache/seatunnel/common/access/AccessType.java | 22 +- .../seatunnel/common/access/ResourceType.java | 23 +- .../common/access/SeatunnelAccessController.java | 30 +- .../app/common/AccessControllerTestingImp.java | 105 +++ .../app/common/ResourcePermissionData.java | 14 +- .../controller/JobDefinitionControllerWrapper.java | 11 + .../SeatunnelDatasourceControllerWrapper.java | 23 + .../app/test/SeatunnelAccessControllerTest.java | 790 +++++++++++++++++++++ .../src/test/resources/application.yml | 1 + 42 files changed, 1420 insertions(+), 384 deletions(-) diff --git a/pom.xml b/pom.xml index 852f2f30..7be25220 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ <description>Production ready big data processing product based on Apache Spark and Apache Flink.</description> <modules> + <module>seatunnel-web-common</module> <module>seatunnel-server</module> <module>seatunnel-datasource</module> <module>seatunnel-web-dist</module> diff --git a/seatunnel-server/seatunnel-app/pom.xml b/seatunnel-server/seatunnel-app/pom.xml index 98d93670..f6ab19e4 100644 --- a/seatunnel-server/seatunnel-app/pom.xml +++ b/seatunnel-server/seatunnel-app/pom.xml @@ -45,6 +45,11 @@ <groupId>org.apache.seatunnel</groupId> <artifactId>seatunnel-common</artifactId> </dependency> + <dependency> + <groupId>org.apache.seatunnel</groupId> + <artifactId>seatunnel-web-common</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>org.apache.seatunnel</groupId> <artifactId>seatunnel-api</artifactId> diff --git a/seatunnel-server/seatunnel-app/src/main/bin/seatunnel-backend-daemon.sh b/seatunnel-server/seatunnel-app/src/main/bin/seatunnel-backend-daemon.sh index 98506c8d..582fcb46 100755 --- a/seatunnel-server/seatunnel-app/src/main/bin/seatunnel-backend-daemon.sh +++ b/seatunnel-server/seatunnel-app/src/main/bin/seatunnel-backend-daemon.sh @@ -54,9 +54,14 @@ start() { fi echo "$WORKDIR" + CLASSPATH="$WORKDIR/../conf:$WORKDIR/../libs/*:$WORKDIR/../datasource/*" + if [ -d "$WORKDIR/../ranger-seatunnel-plugin" ]; then + CLASSPATH="$CLASSPATH:$WORKDIR/../ranger-seatunnel-plugin/lib/*.jar" + CLASSPATH="$CLASSPATH:$WORKDIR/../ranger-seatunnel-plugin/lib/ranger-seatunnel-plugin-impl/*" + fi + nohup $JAVA_HOME/bin/java $JAVA_OPTS \ - -cp "$WORKDIR/../conf":"$WORKDIR/../libs/*":"$WORKDIR/../datasource/*" \ - $SPRING_OPTS \ + -cp "$CLASSPATH" $SPRING_OPTS \ org.apache.seatunnel.app.SeatunnelApplication >> "${LOGDIR}/seatunnel.out" 2>&1 & echo "seatunnel-web started" } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/adapter/SeatunnelWebAdapter.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/adapter/SeatunnelWebAdapter.java index a20f5a8f..0974e456 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/adapter/SeatunnelWebAdapter.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/adapter/SeatunnelWebAdapter.java @@ -42,6 +42,8 @@ public class SeatunnelWebAdapter implements WebMvcConfigurer { public static final String LOGIN_INTERCEPTOR_PATH_PATTERN = "/**/*"; public static final String LOGIN_PATH_PATTERN = "/seatunnel/api/v1/user/login**"; public static final String REGISTER_PATH_PATTERN = "/users/register"; + private static final String RESOURCE_NAME_PATH_PATTERN = + "/seatunnel/api/v1/resources/workspace"; @Bean public AuthenticationInterceptor authenticationInterceptor() { @@ -74,6 +76,7 @@ public class SeatunnelWebAdapter implements WebMvcConfigurer { .excludePathPatterns( LOGIN_PATH_PATTERN, REGISTER_PATH_PATTERN, + RESOURCE_NAME_PATH_PATTERN, "/swagger-resources/**", "/webjars/**", "/v2/**", diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/JobConfigController.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/JobConfigController.java index 2ec31541..cdce8f6e 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/JobConfigController.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/JobConfigController.java @@ -46,7 +46,7 @@ public class JobConfigController { @ApiParam(value = "jobVersionId", required = true) @PathVariable long jobVersionId, @ApiParam(value = "jobConfig", required = true) @RequestBody JobConfig jobConfig) throws JsonProcessingException { - jobConfigService.updateJobConfig(jobVersionId, jobConfig); + jobConfigService.updateJobConfig(jobVersionId, jobConfig, false); return Result.success(); } @@ -55,6 +55,6 @@ public class JobConfigController { Result<JobConfigRes> getJobConfig( @ApiParam(value = "jobVersionId", required = true) @PathVariable long jobVersionId) throws JsonProcessingException { - return Result.success(jobConfigService.getJobConfig(jobVersionId)); + return Result.success(jobConfigService.getJobConfig(jobVersionId, false)); } } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobLineMapper.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobLineMapper.java index 9317520a..479f2749 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobLineMapper.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobLineMapper.java @@ -27,7 +27,5 @@ import java.util.List; public interface JobLineMapper extends BaseMapper<JobLine> { - void deleteLinesByVersionId(@Param("versionId") long jobVersionId); - void insertBatchLines(@Param("lines") List<JobLine> lines); } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/UserMapper.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/UserMapper.java index 9648ecd9..ff8131bd 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/UserMapper.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/UserMapper.java @@ -49,4 +49,6 @@ public interface UserMapper { @Param("authProvider") String authProvider); List<User> queryEnabledUsers(); + + List<String> queryUserNames(@Param("searchName") String searchName); } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/AuthenticationInterceptor.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/AuthenticationInterceptor.java index 07ccf566..78ee6a85 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/AuthenticationInterceptor.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/AuthenticationInterceptor.java @@ -23,6 +23,7 @@ import org.apache.seatunnel.app.dal.entity.User; import org.apache.seatunnel.app.dal.entity.UserLoginLog; import org.apache.seatunnel.app.security.JwtUtils; import org.apache.seatunnel.app.security.UserContext; +import org.apache.seatunnel.common.access.AccessInfo; import org.apache.commons.lang3.StringUtils; @@ -106,7 +107,12 @@ public class AuthenticationInterceptor implements HandlerInterceptor { UserContext userContext = new UserContext(); userContext.setUser(user); userContext.setWorkspaceId(workspaceIdFromToken); - userContext.setWorkspaceName((String) map.get("workspaceName")); + + AccessInfo accessInfo = new AccessInfo(); + accessInfo.setUsername(user.getUsername()); + accessInfo.setWorkspaceName((String) map.get("workspaceName")); + userContext.setAccessInfo(accessInfo); + request.setAttribute(Constants.SESSION_USER_CONTEXT, userContext); request.setAttribute("userId", userId); diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/permission/ISeatunnelPermissonService.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/permission/ISeatunnelPermissonService.java deleted file mode 100644 index 11d1eae0..00000000 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/permission/ISeatunnelPermissonService.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.seatunnel.app.permission; - -import java.util.List; - -public interface ISeatunnelPermissonService { - - /** - * func permission check - * - * @param permissionKey permissionKey - * @param userId userId - */ - void funcPermissionCheck(String permissionKey, int userId); - - /** - * func permission and resource permission check - * - * @param permissionKey permissionKey - * @param resourceType resourceType - * @param resourceCodes resourceCodes - * @param userId userId - */ - void funcAndResourcePermissionCheck( - String permissionKey, String resourceType, List<Object> resourceCodes, int userId); - - /** - * resource post handle - * - * @param resourceType resourceType - * @param resourceCodes resourceCodes - * @param userId userId - */ - void resourcePostHandle(String resourceType, List<Object> resourceCodes, int userId); - - /** - * available resource range - * - * @param resourceType resourceType - * @param userId userId - * @return list - */ - List<Object> availableResourceRange(String resourceType, int userId); -} diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/permission/SeatunnelAccessControllerConfig.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/permission/SeatunnelAccessControllerConfig.java new file mode 100644 index 00000000..61576a82 --- /dev/null +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/permission/SeatunnelAccessControllerConfig.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.seatunnel.app.permission; + +import org.apache.seatunnel.common.access.SeatunnelAccessController; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SeatunnelAccessControllerConfig { + + @Value( + "${seatunnel-web.access-controller-class:org.apache.seatunnel.app.permission.SeatunnelAccessControllerDefaultImpl org.apache.seatunnel.app.permission.DefaultSeatunnelAccessController}") + private String accessControllerClassName; + + @Bean + public SeatunnelAccessController seatunnelAccessController() { + try { + Class<?> clazz = Class.forName(accessControllerClassName); + return (SeatunnelAccessController) clazz.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new IllegalStateException( + "Failed to create SeatunnelAccessController instance for class: " + + accessControllerClassName, + e); + } + } +} diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/permission/SeatunnelAccessControllerDefaultImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/permission/SeatunnelAccessControllerDefaultImpl.java new file mode 100644 index 00000000..262be786 --- /dev/null +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/permission/SeatunnelAccessControllerDefaultImpl.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.seatunnel.app.permission; + +import org.apache.seatunnel.common.access.AccessInfo; +import org.apache.seatunnel.common.access.AccessType; +import org.apache.seatunnel.common.access.ResourceType; +import org.apache.seatunnel.common.access.SeatunnelAccessController; + +public class SeatunnelAccessControllerDefaultImpl implements SeatunnelAccessController { + + @Override + public void authorizeAccess( + String resourceName, + ResourceType resourceType, + AccessType accessType, + AccessInfo accessInfo) { + // Default implementation: Allow all access + // You can add your custom logic here + } + + @Override + public boolean hasPermission( + String resourceName, + ResourceType resourceType, + AccessType accessType, + AccessInfo accessInfo) { + return true; + } +} diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/permission/SeatunnelPermissionServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/permission/SeatunnelPermissionServiceImpl.java deleted file mode 100644 index d97d2019..00000000 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/permission/SeatunnelPermissionServiceImpl.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.seatunnel.app.permission; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.util.List; - -@Component -public class SeatunnelPermissionServiceImpl implements ISeatunnelPermissonService { - - private static final Logger LOGGER = - LoggerFactory.getLogger(SeatunnelPermissionServiceImpl.class); - - @Autowired private AvailableResourceRangeService availableResourceRangeService; - - @Override - public void funcPermissionCheck(String permissionKey, int userId) { - // user id will be replaced by shiro in ws when user id == 0 - LOGGER.warn("func permission check in seatunnel"); - } - - @Override - public void funcAndResourcePermissionCheck( - String permissionKey, String sourceType, List<Object> sourceCodes, int userId) { - // user id will be replaced by shiro in ws when user id == 0 - LOGGER.warn("func and resource permission check in seatunnel"); - } - - @Override - public void resourcePostHandle(String sourceType, List<Object> sourceCodes, int userId) { - // user id will be replaced by shiro in ws when user id == 0 - LOGGER.warn("resource post handle in seatunnel"); - } - - @Override - public List<Object> availableResourceRange(String sourceType, int userId) { - // user id will be replaced by shiro in ws when user id == 0 - LOGGER.warn("query available resource range in seatunnel"); - return availableResourceRangeService.queryAvailableResourceRangeBySourceType( - sourceType, userId); - } -} diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java index 8897779a..7857b1fc 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java @@ -17,6 +17,7 @@ package org.apache.seatunnel.app.security; import org.apache.seatunnel.app.dal.entity.User; +import org.apache.seatunnel.common.access.AccessInfo; import lombok.AllArgsConstructor; import lombok.Data; @@ -28,5 +29,5 @@ import lombok.NoArgsConstructor; public class UserContext { User user; Long workspaceId; - String workspaceName; + AccessInfo accessInfo; } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContextHolder.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContextHolder.java index bd6eb5c9..e23bc2ee 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContextHolder.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContextHolder.java @@ -17,6 +17,7 @@ package org.apache.seatunnel.app.security; import org.apache.seatunnel.app.dal.entity.User; +import org.apache.seatunnel.common.access.AccessInfo; public class UserContextHolder { private static final ThreadLocal<UserContext> userContextHolder = new ThreadLocal<>(); @@ -30,6 +31,11 @@ public class UserContextHolder { return userContext.getUser(); } + public static AccessInfo getAccessInfo() { + UserContext userContext = getUserContext(); + return userContext.getAccessInfo(); + } + public static UserContext getUserContext() { UserContext userContext = userContextHolder.get(); if (userContext == null) { diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/IJobConfigService.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/IJobConfigService.java index 23891174..ee238f8c 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/IJobConfigService.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/IJobConfigService.java @@ -22,7 +22,9 @@ import org.apache.seatunnel.app.domain.response.job.JobConfigRes; import com.fasterxml.jackson.core.JsonProcessingException; public interface IJobConfigService { - JobConfigRes getJobConfig(long jobVersionIdId) throws JsonProcessingException; + JobConfigRes getJobConfig(long jobVersionIdId, boolean isPermissionChecked) + throws JsonProcessingException; - void updateJobConfig(long jobVersionId, JobConfig jobConfig) throws JsonProcessingException; + void updateJobConfig(long jobVersionId, JobConfig jobConfig, boolean isPermissionChecked) + throws JsonProcessingException; } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/ISeatunnelBaseService.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/ISeatunnelBaseService.java index aa7f38fa..0668f900 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/ISeatunnelBaseService.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/ISeatunnelBaseService.java @@ -17,16 +17,23 @@ package org.apache.seatunnel.app.service; -import java.util.List; +import org.apache.seatunnel.common.access.AccessInfo; +import org.apache.seatunnel.common.access.AccessType; +import org.apache.seatunnel.common.access.ResourceType; public interface ISeatunnelBaseService { void funcPermissionCheck(String permissionKey, int userId); - void funcAndResourcePermissionCheck( - String permissionKey, String resourceType, List resourceCodes, int userId); + void permissionCheck( + String resourceName, + ResourceType resourceType, + AccessType accessType, + AccessInfo accessInfo); - void resourcePostHandle(String sourceType, List resourceCodes, int userId); - - List availableResourceRange(String resourceType, int userId); + boolean hasPermission( + String resourceName, + ResourceType resourceType, + AccessType accessType, + AccessInfo accessInfo); } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/WorkspaceService.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/WorkspaceService.java index 7b413f1a..b8f5b385 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/WorkspaceService.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/WorkspaceService.java @@ -37,7 +37,5 @@ public interface WorkspaceService { Workspace getDefaultWorkspace(); - Long getWorkspaceIdOrDefault(Long workspaceId); - Long getWorkspaceIdOrCurrent(String workspaceName); } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/DatasourceServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/DatasourceServiceImpl.java index 2a0d790c..a55bcded 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/DatasourceServiceImpl.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/DatasourceServiceImpl.java @@ -31,7 +31,7 @@ import org.apache.seatunnel.app.domain.response.datasource.DatasourceRes; import org.apache.seatunnel.app.domain.response.datasource.VirtualTableFieldRes; import org.apache.seatunnel.app.dynamicforms.FormStructure; import org.apache.seatunnel.app.permission.constants.SeatunnelFuncPermissionKeyConstant; -import org.apache.seatunnel.app.permission.enums.SeatunnelResourcePermissionModuleEnum; +import org.apache.seatunnel.app.security.UserContextHolder; import org.apache.seatunnel.app.service.IDatasourceService; import org.apache.seatunnel.app.service.IJobDefinitionService; import org.apache.seatunnel.app.service.ITableSchemaService; @@ -39,6 +39,8 @@ import org.apache.seatunnel.app.thirdparty.datasource.DataSourceClientFactory; import org.apache.seatunnel.app.thirdparty.framework.SeaTunnelOptionRuleWrapper; import org.apache.seatunnel.app.utils.ConfigShadeUtil; import org.apache.seatunnel.app.utils.ServletUtils; +import org.apache.seatunnel.common.access.AccessType; +import org.apache.seatunnel.common.access.ResourceType; import org.apache.seatunnel.common.utils.JsonUtils; import org.apache.seatunnel.datasource.plugin.api.DataSourcePluginInfo; import org.apache.seatunnel.datasource.plugin.api.DatasourcePluginTypeEnum; @@ -106,7 +108,7 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl Map<String, String> datasourceConfig) throws CodeGenerateUtils.CodeGenerateException { Integer userId = ServletUtils.getCurrentUserId(); - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.DATASOURCE_CREATE, userId); + permCheck(datasourceName, AccessType.CREATE); long uuid = CodeGenerateUtils.getInstance().genCode(); boolean unique = datasourceDao.checkDatasourceNameUnique(datasourceName, 0L); if (!unique) { @@ -135,10 +137,6 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl .build(); boolean success = datasourceDao.insertDatasource(datasource); if (success) { - resourcePostHandle( - SeatunnelResourcePermissionModuleEnum.DATASOURCE.name(), - Collections.singletonList(datasource.getId()), - userId); return String.valueOf(uuid); } throw new SeatunnelException(SeatunnelErrorEnum.DATASOURCE_CREATE_FAILED); @@ -150,11 +148,6 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl String datasourceName, String description, Map<String, String> datasourceConfig) { - funcAndResourcePermissionCheck( - SeatunnelFuncPermissionKeyConstant.DATASOURCE_UPDATE, - SeatunnelResourcePermissionModuleEnum.DATASOURCE.name(), - Collections.singletonList(datasourceId), - ServletUtils.getCurrentUserId()); if (datasourceId == null) { throw new SeatunnelException( SeatunnelErrorEnum.DATASOURCE_PRAM_NOT_ALLOWED_NULL, "datasourceId"); @@ -172,6 +165,7 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl SeatunnelErrorEnum.DATASOURCE_NAME_ALREADY_EXISTS, datasourceName); } } + permCheck(datasource.getDatasourceName(), AccessType.UPDATE); datasource.setUpdateUserId(ServletUtils.getCurrentUserId()); datasource.setUpdateTime(new Date()); datasource.setDescription(description); @@ -185,12 +179,6 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl @Override public boolean deleteDatasource(Long datasourceId) { - // check role permission - funcAndResourcePermissionCheck( - SeatunnelFuncPermissionKeyConstant.DATASOURCE_DELETE, - SeatunnelResourcePermissionModuleEnum.DATASOURCE.name(), - Collections.singletonList(datasourceId), - ServletUtils.getCurrentUserId()); // check has job task has used this datasource List<JobTask> jobTaskList = jobTaskDao.getJobTaskByDataSourceId(datasourceId); if (!CollectionUtils.isEmpty(jobTaskList)) { @@ -204,6 +192,11 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl if (!jobDefinitionService.getJobVersionByDataSourceId(datasourceId).isEmpty()) { throw new SeatunnelException(SeatunnelErrorEnum.DATASOURCE_CAN_NOT_DELETE); } + Datasource datasource = datasourceDao.selectDatasourceById(datasourceId); + if (datasource == null) { + return true; + } + permCheck(datasource.getDatasourceName(), AccessType.DELETE); return datasourceDao.deleteDatasourceById(datasourceId); } @@ -219,16 +212,12 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl @Override public boolean testDatasourceConnectionAble(Long datasourceId) { - funcAndResourcePermissionCheck( - SeatunnelFuncPermissionKeyConstant.DATASOURCE_TEST_CONNECT, - SeatunnelResourcePermissionModuleEnum.DATASOURCE.name(), - Collections.singletonList(datasourceId), - ServletUtils.getCurrentUserId()); Datasource datasource = datasourceDao.selectDatasourceById(datasourceId); if (datasource == null) { throw new SeatunnelException( SeatunnelErrorEnum.DATASOURCE_NOT_FOUND, datasourceId.toString()); } + permCheck(datasource.getDatasourceName(), AccessType.EXECUTE); String configJson = datasource.getDatasourceConfig(); Map<String, String> datasourceConfig = JsonUtils.toMap(configJson, String.class, String.class); @@ -301,7 +290,6 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl @Override public List<String> queryTableNames( String datasourceName, String databaseName, String filterName, Integer size) { - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.DATASOURCE_TABLE, 0); Datasource datasource = datasourceDao.queryDatasourceByName(datasourceName); if (null == datasource) { throw new SeatunnelException(SeatunnelErrorEnum.DATASOURCE_NOT_FOUND, datasourceName); @@ -323,7 +311,6 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl @Override public List<String> queryTableNames(String datasourceName, String databaseName) { - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.DATASOURCE_TABLE, 0); Datasource datasource = datasourceDao.queryDatasourceByName(datasourceName); if (null == datasource) { throw new SeatunnelException(SeatunnelErrorEnum.DATASOURCE_NOT_FOUND, datasourceName); @@ -344,7 +331,6 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl @Override public List<TableField> queryTableSchema( String datasourceName, String databaseName, String tableName) { - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.DATASOURCE_TABLE_SCHEMA, 0); Datasource datasource = datasourceDao.queryDatasourceByName(datasourceName); if (null == datasource) { throw new SeatunnelException(SeatunnelErrorEnum.DATASOURCE_NOT_FOUND, datasourceName); @@ -399,18 +385,22 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl @Override public PageInfo<DatasourceRes> queryDatasourceList( String searchVal, String pluginName, Integer pageNo, Integer pageSize) { - Integer userId = ServletUtils.getCurrentUserId(); - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.DATASOURCE_LIST, userId); Page<Datasource> page = new Page<>(pageNo, pageSize); PageInfo<DatasourceRes> pageInfo = new PageInfo<>(); - List<Long> ids = - availableResourceRange( - SeatunnelResourcePermissionModuleEnum.DATASOURCE.name(), userId); - if (org.springframework.util.CollectionUtils.isEmpty(ids)) { + IPage<Datasource> datasourceWithoutAuthorization = + datasourceDao.selectDatasourceByParam(page, null, searchVal, pluginName); + + List<Long> filteredIds = + datasourceWithoutAuthorization.getRecords().stream() + .filter(datasource -> hasReadPerm(datasource.getDatasourceName())) + .map(Datasource::getId) + .collect(Collectors.toList()); + + if (org.springframework.util.CollectionUtils.isEmpty(filteredIds)) { return pageInfo; } IPage<Datasource> datasourcePage = - datasourceDao.selectDatasourceByParam(page, ids, searchVal, pluginName); + datasourceDao.selectDatasourceByParam(page, filteredIds, searchVal, pluginName); pageInfo = new PageInfo<>(); pageInfo.setPageNo((int) datasourcePage.getPages()); pageInfo.setPageSize((int) datasourcePage.getSize()); @@ -419,15 +409,6 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl pageInfo.setData(new ArrayList<>()); return pageInfo; } - List<Integer> userIds = new ArrayList<>(); - datasourcePage - .getRecords() - .forEach( - datasource -> { - userIds.add(datasource.getCreateUserId()); - userIds.add(datasource.getUpdateUserId()); - }); - List<DatasourceRes> datasourceResList = datasourcePage.getRecords().stream() .map( @@ -554,11 +535,6 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl if (CollectionUtils.isEmpty(datasourceIds)) { return new ArrayList<>(); } - funcAndResourcePermissionCheck( - SeatunnelFuncPermissionKeyConstant.DATASOURCE_DETAIL_LIST, - SeatunnelResourcePermissionModuleEnum.DATASOURCE.name(), - datasourceIds, - 0); List<Long> datasourceIdsLong = datasourceIds.stream().map(Long::parseLong).collect(Collectors.toList()); List<Datasource> datasourceList = datasourceDao.selectDatasourceByIds(datasourceIdsLong); @@ -570,22 +546,13 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl if (CollectionUtils.isEmpty(datasourceList)) { return new ArrayList<>(); } - List<Integer> userIds = new ArrayList<>(); - datasourceList.forEach( - datasource -> { - if (null != datasource.getCreateUserId()) { - userIds.add(datasource.getCreateUserId()); - } - if (null != datasource.getUpdateUserId()) { - userIds.add(datasource.getUpdateUserId()); - } - }); List<DatasourceDetailRes> datasourceDetailResList = new ArrayList<>(); - - datasourceList.forEach( - datasource -> { - datasourceDetailResList.add(getDatasourceDetailRes(datasource)); - }); + datasourceList.stream() + .filter(datasource -> hasReadPerm(datasource.getDatasourceName())) + .forEach( + datasource -> { + datasourceDetailResList.add(getDatasourceDetailRes(datasource)); + }); return datasourceDetailResList; } @@ -597,7 +564,7 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl @Override public DatasourceDetailRes queryDatasourceDetailByDatasourceName(String datasourceName) { - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.DATASOURCE_DETAIL, 0); + permCheck(datasourceName, AccessType.READ); Datasource datasource = datasourceDao.queryDatasourceByName(datasourceName); // @cc liuli if (null == datasource) { @@ -627,11 +594,11 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl @Override public DatasourceDetailRes queryDatasourceDetailById(String datasourceId) { long datasourceIdLong = Long.parseLong(datasourceId); - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.DATASOURCE_DETAIL, 0); Datasource datasource = datasourceDao.selectDatasourceById(datasourceIdLong); if (null == datasource) { throw new SeatunnelException(SeatunnelErrorEnum.DATASOURCE_NOT_FOUND, datasourceId); } + permCheck(datasource.getDatasourceName(), AccessType.READ); return getDatasourceDetailRes(datasource); } @@ -639,4 +606,20 @@ public class DatasourceServiceImpl extends SeatunnelBaseServiceImpl public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } + + private void permCheck(String resourceName, AccessType accessType) { + permissionCheck( + resourceName, + ResourceType.DATASOURCE, + accessType, + UserContextHolder.getAccessInfo()); + } + + private boolean hasReadPerm(String resourceName) { + return hasPermission( + resourceName, + ResourceType.DATASOURCE, + AccessType.READ, + UserContextHolder.getAccessInfo()); + } } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobConfigServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobConfigServiceImpl.java index 97bf1884..add8e3b0 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobConfigServiceImpl.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobConfigServiceImpl.java @@ -22,9 +22,11 @@ import org.apache.seatunnel.app.dal.entity.JobDefinition; import org.apache.seatunnel.app.dal.entity.JobVersion; import org.apache.seatunnel.app.domain.request.job.JobConfig; import org.apache.seatunnel.app.domain.response.job.JobConfigRes; -import org.apache.seatunnel.app.permission.constants.SeatunnelFuncPermissionKeyConstant; +import org.apache.seatunnel.app.security.UserContextHolder; import org.apache.seatunnel.app.service.IJobConfigService; import org.apache.seatunnel.app.utils.ServletUtils; +import org.apache.seatunnel.common.access.AccessType; +import org.apache.seatunnel.common.access.ResourceType; import org.apache.seatunnel.common.constants.JobMode; import org.apache.seatunnel.common.utils.JsonUtils; import org.apache.seatunnel.server.common.SeatunnelErrorEnum; @@ -48,14 +50,21 @@ public class JobConfigServiceImpl extends SeatunnelBaseServiceImpl implements IJ @Resource private IJobDefinitionDao jobDefinitionDao; @Override - public JobConfigRes getJobConfig(long jobVersionId) throws JsonProcessingException { - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.JOB_CONFIG_DETAIL, 0); + public JobConfigRes getJobConfig(long jobVersionId, boolean isPermissionChecked) + throws JsonProcessingException { JobVersion jobVersion = jobVersionDao.getVersionById(jobVersionId); if (jobVersion == null) { throw new SeatunnelException( SeatunnelErrorEnum.RESOURCE_NOT_FOUND, "job version not found."); } JobDefinition jobDefinition = jobDefinitionDao.getJob(jobVersion.getJobId()); + if (!isPermissionChecked) { + permissionCheck( + jobDefinition.getName(), + ResourceType.JOB, + AccessType.READ, + UserContextHolder.getAccessInfo()); + } JobConfigRes jobConfigRes = new JobConfigRes(); jobConfigRes.setName(jobDefinition.getName()); jobConfigRes.setId(jobVersion.getId()); @@ -70,15 +79,22 @@ public class JobConfigServiceImpl extends SeatunnelBaseServiceImpl implements IJ @Override @Transactional - public void updateJobConfig(long jobVersionId, JobConfig jobConfig) + public void updateJobConfig(long jobVersionId, JobConfig jobConfig, boolean isPermissionChecked) throws JsonProcessingException { Integer userId = ServletUtils.getCurrentUserId(); - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.JOB_CONFIG_UPDATE, 0); JobVersion version = jobVersionDao.getVersionById(jobVersionId); if (version == null) { throw new SeatunnelException( SeatunnelErrorEnum.RESOURCE_NOT_FOUND, "job version not found."); } + JobDefinition existingJobDefinition = jobDefinitionDao.getJob(version.getJobId()); + if (!isPermissionChecked && existingJobDefinition != null) { + permissionCheck( + existingJobDefinition.getName(), + ResourceType.JOB, + AccessType.UPDATE, + UserContextHolder.getAccessInfo()); + } JobDefinition jobDefinition = new JobDefinition(); jobDefinition.setId(version.getJobId()); jobDefinition.setUpdateUserId(userId); diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobDefinitionServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobDefinitionServiceImpl.java index 07c07125..915193a0 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobDefinitionServiceImpl.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobDefinitionServiceImpl.java @@ -29,9 +29,11 @@ import org.apache.seatunnel.app.domain.request.job.DataSourceOption; import org.apache.seatunnel.app.domain.request.job.JobReq; import org.apache.seatunnel.app.domain.response.PageInfo; import org.apache.seatunnel.app.domain.response.job.JobDefinitionRes; -import org.apache.seatunnel.app.permission.constants.SeatunnelFuncPermissionKeyConstant; +import org.apache.seatunnel.app.security.UserContextHolder; import org.apache.seatunnel.app.service.IJobDefinitionService; import org.apache.seatunnel.app.utils.ServletUtils; +import org.apache.seatunnel.common.access.AccessType; +import org.apache.seatunnel.common.access.ResourceType; import org.apache.seatunnel.common.constants.JobMode; import org.apache.seatunnel.common.utils.JsonUtils; import org.apache.seatunnel.server.common.CodeGenerateUtils; @@ -74,7 +76,7 @@ public class JobDefinitionServiceImpl extends SeatunnelBaseServiceImpl @Transactional public long createJob(JobReq jobReq) throws CodeGenerateUtils.CodeGenerateException { Integer userId = ServletUtils.getCurrentUserId(); - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.JOB_DEFINITION_CREATE, userId); + permCheck(jobReq.getName(), AccessType.CREATE); long uuid = CodeGenerateUtils.getInstance().genCode(); jobDefinitionDao.add( JobDefinition.builder() @@ -111,7 +113,6 @@ public class JobDefinitionServiceImpl extends SeatunnelBaseServiceImpl @Override public PageInfo<JobDefinitionRes> getJob( String searchName, Integer pageNo, Integer pageSize, String jobMode) { - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.JOB_DEFINITION_VIEW, 0); if (StringUtils.isNotEmpty(jobMode)) { try { JobMode.valueOf(jobMode); @@ -120,13 +121,27 @@ public class JobDefinitionServiceImpl extends SeatunnelBaseServiceImpl SeatunnelErrorEnum.ILLEGAL_STATE, "Unsupported JobMode"); } } - return jobDefinitionDao.getJob(searchName, pageNo, pageSize, jobMode); + PageInfo<JobDefinitionRes> job = + jobDefinitionDao.getJob(searchName, pageNo, pageSize, jobMode); + if (CollectionUtils.isEmpty(job.getData())) { + return job; + } + + List<JobDefinitionRes> filteredJobs = + job.getData().stream() + .filter(jobDefinitionRes -> hasReadPerm(jobDefinitionRes.getName())) + .collect(Collectors.toList()); + + PageInfo<JobDefinitionRes> jobs = new PageInfo<>(); + jobs.setData(filteredJobs); + jobs.setPageSize(pageSize); + jobs.setPageNo(pageNo); + jobs.setTotalCount(filteredJobs.size()); + return jobs; } @Override public Map<Long, String> getJob(@NonNull String name) { - - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.JOB_DEFINITION_VIEW, 0); List<JobDefinition> job = jobDefinitionDao.getJobList(name); if (CollectionUtils.isEmpty(job)) { return new HashMap<>(); @@ -135,7 +150,9 @@ public class JobDefinitionServiceImpl extends SeatunnelBaseServiceImpl Map<Long, String> jobDefineMap = new HashMap<>(); job.forEach( jobDefine -> { - jobDefineMap.put(jobDefine.getId(), jobDefine.getName()); + if (hasReadPerm(jobDefine.getName())) { + jobDefineMap.put(jobDefine.getId(), jobDefine.getName()); + } }); return jobDefineMap; @@ -143,8 +160,11 @@ public class JobDefinitionServiceImpl extends SeatunnelBaseServiceImpl @Override public JobDefinition getJobDefinitionByJobId(long jobId) { - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.JOB_DEFINITION_DETAIL, 0); - return jobDefinitionDao.getJob(jobId); + JobDefinition job = jobDefinitionDao.getJob(jobId); + if (null != job) { + permCheck(job.getName(), AccessType.READ); + } + return job; } @Override @@ -179,7 +199,20 @@ public class JobDefinitionServiceImpl extends SeatunnelBaseServiceImpl @Override public void deleteJob(long id) { - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.JOB_DEFINITION_DELETE, 0); + JobDefinition job = jobDefinitionDao.getJob(id); + if (job != null) { + permCheck(job.getName(), AccessType.DELETE); + } jobDefinitionDao.delete(id); } + + private void permCheck(String resourceName, AccessType accessType) { + permissionCheck( + resourceName, ResourceType.JOB, accessType, UserContextHolder.getAccessInfo()); + } + + private boolean hasReadPerm(String resourceName) { + return hasPermission( + resourceName, ResourceType.JOB, AccessType.READ, UserContextHolder.getAccessInfo()); + } } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobInstanceServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobInstanceServiceImpl.java index cd453048..d0db526f 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobInstanceServiceImpl.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobInstanceServiceImpl.java @@ -55,6 +55,7 @@ import org.apache.seatunnel.app.domain.response.datasource.DatasourceDetailRes; import org.apache.seatunnel.app.domain.response.datasource.VirtualTableDetailRes; import org.apache.seatunnel.app.domain.response.executor.JobExecutorRes; import org.apache.seatunnel.app.permission.constants.SeatunnelFuncPermissionKeyConstant; +import org.apache.seatunnel.app.security.UserContextHolder; import org.apache.seatunnel.app.service.IDatasourceService; import org.apache.seatunnel.app.service.IJobInstanceService; import org.apache.seatunnel.app.service.IJobMetricsService; @@ -65,6 +66,8 @@ import org.apache.seatunnel.app.utils.ConfigShadeUtil; import org.apache.seatunnel.app.utils.JobUtils; import org.apache.seatunnel.app.utils.SeaTunnelConfigUtil; import org.apache.seatunnel.app.utils.ServletUtils; +import org.apache.seatunnel.common.access.AccessType; +import org.apache.seatunnel.common.access.ResourceType; import org.apache.seatunnel.common.constants.PluginType; import org.apache.seatunnel.common.utils.ExceptionUtils; import org.apache.seatunnel.common.utils.JsonUtils; @@ -137,12 +140,17 @@ public class JobInstanceServiceImpl extends SeatunnelBaseServiceImpl public JobExecutorRes createExecuteResource( @NonNull Long jobDefineId, JobExecParam executeParam) { int userId = ServletUtils.getCurrentUserId(); - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.JOB_EXECUTOR_RESOURCE, userId); log.info( "receive createExecuteResource request, userId:{}, jobDefineId:{}", userId, jobDefineId); JobDefinition job = jobDefinitionDao.getJob(jobDefineId); + permissionCheck( + job.getName(), + ResourceType.JOB, + AccessType.EXECUTE, + UserContextHolder.getAccessInfo()); + JobVersion latestVersion = jobVersionDao.getLatestVersion(job.getId()); JobInstance jobInstance = new JobInstance(); String jobConfig = createJobConfig(latestVersion, executeParam); @@ -370,7 +378,6 @@ public class JobInstanceServiceImpl extends SeatunnelBaseServiceImpl public void complete( @NonNull Long jobInstanceId, @NonNull String jobEngineId, JobResult jobResult) { int userId = ServletUtils.getCurrentUserId(); - funcPermissionCheck(SeatunnelFuncPermissionKeyConstant.JOB_EXECUTOR_COMPLETE, userId); JobInstance jobInstance = jobInstanceDao.getJobInstanceMapper().selectById(jobInstanceId); jobMetricsService.syncJobDataToDb(jobInstance, jobEngineId); jobInstance.setJobStatus(jobResult.getStatus()); diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobServiceImpl.java index 8a2620f5..8838d1e2 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobServiceImpl.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobServiceImpl.java @@ -26,10 +26,13 @@ import org.apache.seatunnel.app.domain.request.job.JobTaskInfo; import org.apache.seatunnel.app.domain.request.job.PluginConfig; import org.apache.seatunnel.app.domain.response.job.JobConfigRes; import org.apache.seatunnel.app.domain.response.job.JobRes; +import org.apache.seatunnel.app.security.UserContextHolder; import org.apache.seatunnel.app.service.IJobConfigService; import org.apache.seatunnel.app.service.IJobDefinitionService; import org.apache.seatunnel.app.service.IJobService; import org.apache.seatunnel.app.service.IJobTaskService; +import org.apache.seatunnel.common.access.AccessType; +import org.apache.seatunnel.common.access.ResourceType; import org.apache.seatunnel.common.constants.JobMode; import org.apache.seatunnel.server.common.CodeGenerateUtils; import org.apache.seatunnel.server.common.ParamValidationException; @@ -61,6 +64,11 @@ public class JobServiceImpl extends SeatunnelBaseServiceImpl implements IJobServ @Override @Transactional public long createJob(JobCreateReq jobCreateRequest) throws JsonProcessingException { + permissionCheck( + jobCreateRequest.getJobConfig().getName(), + ResourceType.JOB, + AccessType.CREATE, + UserContextHolder.getAccessInfo()); JobReq jobDefinition = getJobDefinition(jobCreateRequest.getJobConfig()); long jobId = jobService.createJob(jobDefinition); createTasks(jobCreateRequest, jobId); @@ -90,7 +98,7 @@ public class JobServiceImpl extends SeatunnelBaseServiceImpl implements IJobServ pluginNameVsPluginId.put(pluginIdKey, newPluginId); } } - jobConfigService.updateJobConfig(jobId, jobCreateRequest.getJobConfig()); + jobConfigService.updateJobConfig(jobId, jobCreateRequest.getJobConfig(), true); JobDAG jobDAG = jobCreateRequest.getJobDAG(); // Replace the plugin name with plugin id List<Edge> edges = jobDAG.getEdges(); @@ -136,13 +144,21 @@ public class JobServiceImpl extends SeatunnelBaseServiceImpl implements IJobServ @Override public void updateJob(long jobVersionId, JobCreateReq jobCreateReq) throws JsonProcessingException { + JobConfigRes jobConfig = jobConfigService.getJobConfig(jobVersionId, true); + if (jobConfig != null) { + permissionCheck( + jobConfig.getName(), + ResourceType.JOB, + AccessType.UPDATE, + UserContextHolder.getAccessInfo()); + } jobTaskService.deleteTaskByVersionId(jobVersionId); createTasks(jobCreateReq, jobVersionId); } @Override public JobRes getJob(long jobVersionId) throws JsonProcessingException { - JobConfigRes jobConfig = jobConfigService.getJobConfig(jobVersionId); + JobConfigRes jobConfig = jobConfigService.getJobConfig(jobVersionId, false); JobTaskInfo taskConfig = jobTaskService.getTaskConfig(jobVersionId); return new JobRes(jobConfig, taskConfig.getPlugins(), new JobDAG(taskConfig.getEdges())); } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/SeatunnelBaseServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/SeatunnelBaseServiceImpl.java index d6341968..878458c3 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/SeatunnelBaseServiceImpl.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/SeatunnelBaseServiceImpl.java @@ -16,38 +16,42 @@ */ package org.apache.seatunnel.app.service.impl; -import org.apache.seatunnel.app.permission.ISeatunnelPermissonService; import org.apache.seatunnel.app.service.ISeatunnelBaseService; +import org.apache.seatunnel.common.access.AccessInfo; +import org.apache.seatunnel.common.access.AccessType; +import org.apache.seatunnel.common.access.ResourceType; +import org.apache.seatunnel.common.access.SeatunnelAccessController; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.List; +import javax.annotation.Resource; @Service public class SeatunnelBaseServiceImpl implements ISeatunnelBaseService { - - @Autowired private ISeatunnelPermissonService iSeatunnelPermissonService; + @Resource private SeatunnelAccessController seatunnelAccessController; @Override public void funcPermissionCheck(String permissionKey, int userId) { - iSeatunnelPermissonService.funcPermissionCheck(permissionKey, userId); - } - - @Override - public void funcAndResourcePermissionCheck( - String permissionKey, String resourceType, List resourceCodes, int userId) { - iSeatunnelPermissonService.funcAndResourcePermissionCheck( - permissionKey, resourceType, resourceCodes, userId); + // Placeholder method: To be removed after thorough analysis of all references and usages. } @Override - public void resourcePostHandle(String sourceType, List resourceCodes, int userId) { - iSeatunnelPermissonService.resourcePostHandle(sourceType, resourceCodes, userId); + public void permissionCheck( + String resourceName, + ResourceType resourceType, + AccessType accessType, + AccessInfo accessInfo) { + seatunnelAccessController.authorizeAccess( + resourceName, resourceType, accessType, accessInfo); } @Override - public List availableResourceRange(String resourceType, int userId) { - return iSeatunnelPermissonService.availableResourceRange(resourceType, userId); + public boolean hasPermission( + String resourceName, + ResourceType resourceType, + AccessType accessType, + AccessInfo accessInfo) { + return seatunnelAccessController.hasPermission( + resourceName, resourceType, accessType, accessInfo); } } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/TaskInstanceServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/TaskInstanceServiceImpl.java index f30ea239..778ee8ff 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/TaskInstanceServiceImpl.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/TaskInstanceServiceImpl.java @@ -25,11 +25,14 @@ import org.apache.seatunnel.app.dal.entity.JobInstance; import org.apache.seatunnel.app.domain.dto.job.SeaTunnelJobInstanceDto; import org.apache.seatunnel.app.domain.response.executor.JobExecutionStatus; import org.apache.seatunnel.app.domain.response.metrics.JobSummaryMetricsRes; +import org.apache.seatunnel.app.security.UserContextHolder; import org.apache.seatunnel.app.service.BaseService; import org.apache.seatunnel.app.service.IJobDefinitionService; import org.apache.seatunnel.app.service.IJobMetricsService; import org.apache.seatunnel.app.service.ITaskInstanceService; import org.apache.seatunnel.app.utils.PageInfo; +import org.apache.seatunnel.common.access.AccessType; +import org.apache.seatunnel.common.access.ResourceType; import org.apache.seatunnel.common.constants.JobMode; import org.apache.seatunnel.server.common.SeatunnelErrorEnum; import org.apache.seatunnel.server.common.SeatunnelException; @@ -50,10 +53,12 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @Service @Slf4j -public class TaskInstanceServiceImpl implements ITaskInstanceService<SeaTunnelJobInstanceDto> { +public class TaskInstanceServiceImpl extends SeatunnelBaseServiceImpl + implements ITaskInstanceService<SeaTunnelJobInstanceDto> { @Autowired IJobInstanceDao jobInstanceDao; @@ -88,13 +93,19 @@ public class TaskInstanceServiceImpl implements ITaskInstanceService<SeaTunnelJo new Page<>(pageNo, pageSize), startDate, endDate, jobDefineName, jobMode); List<SeaTunnelJobInstanceDto> records = jobInstanceIPage.getRecords(); - if (CollectionUtils.isEmpty(records)) { + List<SeaTunnelJobInstanceDto> filteredRecords = + records.stream() + .filter( + jobDefinitionRes -> + hasReadPerm(jobDefinitionRes.getJobDefineName())) + .collect(Collectors.toList()); + if (CollectionUtils.isEmpty(filteredRecords)) { return result; } - addRunningTimeToResult(records); - jobPipelineSummaryMetrics(records, jobMode); + addRunningTimeToResult(filteredRecords); + jobPipelineSummaryMetrics(filteredRecords, jobMode); pageInfo.setTotal((int) jobInstanceIPage.getTotal()); - pageInfo.setTotalList(records); + pageInfo.setTotalList(filteredRecords); result.setData(pageInfo); return result; } @@ -215,4 +226,9 @@ public class TaskInstanceServiceImpl implements ITaskInstanceService<SeaTunnelJo jobInstanceDao.deleteById(jobInstanceId); return Result.success(); } + + private boolean hasReadPerm(String resourceName) { + return hasPermission( + resourceName, ResourceType.JOB, AccessType.READ, UserContextHolder.getAccessInfo()); + } } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/UserServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/UserServiceImpl.java index 138ab4b5..2a47e299 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/UserServiceImpl.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/UserServiceImpl.java @@ -34,6 +34,7 @@ import org.apache.seatunnel.app.domain.response.PageInfo; import org.apache.seatunnel.app.domain.response.user.AddUserRes; import org.apache.seatunnel.app.domain.response.user.UserSimpleInfoRes; import org.apache.seatunnel.app.security.JwtUtils; +import org.apache.seatunnel.app.security.UserContextHolder; import org.apache.seatunnel.app.security.authentication.strategy.IAuthenticationStrategy; import org.apache.seatunnel.app.security.authentication.strategy.impl.DBAuthenticationStrategy; import org.apache.seatunnel.app.security.authentication.strategy.impl.LDAPAuthenticationStrategy; @@ -42,6 +43,8 @@ import org.apache.seatunnel.app.service.IUserService; import org.apache.seatunnel.app.service.WorkspaceService; import org.apache.seatunnel.app.utils.PasswordUtils; import org.apache.seatunnel.app.utils.ServletUtils; +import org.apache.seatunnel.common.access.AccessType; +import org.apache.seatunnel.common.access.ResourceType; import org.apache.seatunnel.server.common.PageData; import org.apache.seatunnel.server.common.SeatunnelErrorEnum; import org.apache.seatunnel.server.common.SeatunnelException; @@ -97,9 +100,9 @@ public class UserServiceImpl extends SeatunnelBaseServiceImpl implements IUserSe @Override @Transactional(rollbackFor = Exception.class) public AddUserRes add(AddUserReq addReq) { + permCheck(addReq.getUsername(), AccessType.CREATE); // 1. check duplicate user first userDaoImpl.checkUserExists(addReq.getUsername()); - // 2. add a new user. final UpdateUserDto dto = UpdateUserDto.builder() @@ -123,6 +126,7 @@ public class UserServiceImpl extends SeatunnelBaseServiceImpl implements IUserSe @Override public void update(UpdateUserReq updateReq) { + permCheck(updateReq.getUsername(), AccessType.UPDATE); final UpdateUserDto dto = UpdateUserDto.builder() .id(updateReq.getUserId()) @@ -145,6 +149,11 @@ public class UserServiceImpl extends SeatunnelBaseServiceImpl implements IUserSe throw new SeatunnelException( SeatunnelErrorEnum.INVALID_OPERATION, "Can't delete yourself"); } + User user = userDaoImpl.getById(id); + if (user == null) { + return; + } + permCheck(user.getUsername(), AccessType.DELETE); userDaoImpl.delete(id); roleServiceImpl.deleteByUserId(id); } @@ -158,7 +167,10 @@ public class UserServiceImpl extends SeatunnelBaseServiceImpl implements IUserSe userDaoImpl.list(dto, userListReq.getRealPageNo(), userListReq.getPageSize()); final List<UserSimpleInfoRes> data = - userPageData.getData().stream().map(this::translate).collect(Collectors.toList()); + userPageData.getData().stream() + .filter(user -> hasReadPerm(user.getUsername())) + .map(this::translate) + .collect(Collectors.toList()); final PageInfo<UserSimpleInfoRes> pageInfo = new PageInfo<>(); pageInfo.setPageNo(userListReq.getPageNo()); pageInfo.setPageSize(userListReq.getPageSize()); @@ -170,12 +182,20 @@ public class UserServiceImpl extends SeatunnelBaseServiceImpl implements IUserSe @Override public void enable(int id) { - userDaoImpl.enable(id); + User user = userDaoImpl.getById(id); + if (user != null) { + permCheck(user.getUsername(), AccessType.UPDATE); + userDaoImpl.enable(id); + } } @Override public void disable(int id) { - userDaoImpl.disable(id); + User user = userDaoImpl.getById(id); + if (user != null) { + permCheck(user.getUsername(), AccessType.UPDATE); + userDaoImpl.disable(id); + } } @Override @@ -224,4 +244,17 @@ public class UserServiceImpl extends SeatunnelBaseServiceImpl implements IUserSe info.setName(user.getUsername()); return info; } + + private void permCheck(String resourceName, AccessType accessType) { + permissionCheck( + resourceName, ResourceType.USER, accessType, UserContextHolder.getAccessInfo()); + } + + private boolean hasReadPerm(String resourceName) { + return hasPermission( + resourceName, + ResourceType.USER, + AccessType.READ, + UserContextHolder.getAccessInfo()); + } } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/VirtualTableServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/VirtualTableServiceImpl.java index b3efb204..12e4ee41 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/VirtualTableServiceImpl.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/VirtualTableServiceImpl.java @@ -33,6 +33,7 @@ import org.apache.seatunnel.app.dynamicforms.FormStructure; import org.apache.seatunnel.app.permission.constants.SeatunnelFuncPermissionKeyConstant; import org.apache.seatunnel.app.service.IJobDefinitionService; import org.apache.seatunnel.app.service.IVirtualTableService; +import org.apache.seatunnel.app.service.WorkspaceService; import org.apache.seatunnel.app.thirdparty.datasource.DataSourceClientFactory; import org.apache.seatunnel.app.thirdparty.framework.SeaTunnelOptionRuleWrapper; import org.apache.seatunnel.app.utils.ServletUtils; @@ -71,6 +72,8 @@ public class VirtualTableServiceImpl extends SeatunnelBaseServiceImpl @Resource(name = "datasourceDaoImpl") IDatasourceDao datasourceDao; + @Autowired private WorkspaceService workspaceService; + @Autowired private ConnectorDataSourceMapperConfig dataSourceMapperConfig; @Override @@ -328,53 +331,4 @@ public class VirtualTableServiceImpl extends SeatunnelBaseServiceImpl Long datasourceIdLong = Long.valueOf(datasourceId); return virtualTableDao.getVirtualDatabaseNames(datasourceIdLong); } - - private VirtualTableFieldRes convertVirtualTableFieldReq(VirtualTableFieldReq req) { - - return VirtualTableFieldRes.builder() - .fieldName(req.getFieldName()) - .fieldType(req.getFieldType()) - .nullable(req.getNullable()) - .defaultValue(req.getDefaultValue()) - .fieldComment(req.getFieldComment()) - .primaryKey(req.getPrimaryKey()) - .build(); - } - - /* private VirtualTableDetailRes convertVirtualTableDetailResponse(VirtualTableDetailResponse response) { - VirtualTableDetailRes res = new VirtualTableDetailRes(); - res.setTableId(response.getId().toString()); - res.setTableName(response.getTableName()); - res.setDatabaseName(response.getDatabaseName()); - res.setDescription(response.getDescription()); - res.setCreateTime(response.getCreateTime()); - res.setUpdateTime(response.getUpdateTime()); - - res.setFields(convertVirtualTableFieldResponse(response.getFields())); - - List<Integer> userIds = new ArrayList<>(); - userIds.add(Integer.parseInt(response.getCreateUserId())); - userIds.add(Integer.parseInt(response.getUpdateUserId())); - Map<Integer, String> userNames = queryUserNamesByIds(userIds); - res.setCreateUserName(userNames.get(Integer.parseInt(response.getCreateUserId()))); - res.setUpdateUserName(userNames.get(Integer.parseInt(response.getUpdateUserId()))); - return res; - } - - private List<VirtualTableFieldRes> convertVirtualTableFieldResponse(List<VirtualTableFieldResponse> fieldResponses) { - List<VirtualTableFieldRes> fields = new ArrayList<>(); - if (fieldResponses != null && !fieldResponses.isEmpty()) { - for (VirtualTableFieldResponse fieldResponse : fieldResponses) { - VirtualTableFieldRes field = new VirtualTableFieldRes(); - field.setFieldName(fieldResponse.getName()); - field.setFieldType(fieldResponse.getType()); - field.setNullable(fieldResponse.getNullable()); - field.setDefaultValue(fieldResponse.getDefaultValue()); - field.setFieldComment(fieldResponse.getComment()); - field.setPrimaryKey(fieldResponse.getPrimaryKey()); - fields.add(field); - } - } - return fields; - }*/ } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/WorkspaceServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/WorkspaceServiceImpl.java index 2700c7d3..9406aada 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/WorkspaceServiceImpl.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/WorkspaceServiceImpl.java @@ -20,8 +20,11 @@ package org.apache.seatunnel.app.service.impl; import org.apache.seatunnel.app.dal.dao.IWorkspaceDao; import org.apache.seatunnel.app.dal.entity.Workspace; import org.apache.seatunnel.app.domain.request.workspace.WorkspaceReq; +import org.apache.seatunnel.app.security.UserContextHolder; import org.apache.seatunnel.app.service.WorkspaceService; import org.apache.seatunnel.app.utils.ServletUtils; +import org.apache.seatunnel.common.access.AccessType; +import org.apache.seatunnel.common.access.ResourceType; import org.apache.seatunnel.server.common.CodeGenerateUtils; import org.apache.seatunnel.server.common.ParamValidationException; import org.apache.seatunnel.server.common.SeatunnelErrorEnum; @@ -29,19 +32,22 @@ import org.apache.seatunnel.server.common.SeatunnelException; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import javax.annotation.Resource; + import java.util.Date; import java.util.List; +import java.util.stream.Collectors; @Service public class WorkspaceServiceImpl extends SeatunnelBaseServiceImpl implements WorkspaceService { - @Autowired private IWorkspaceDao workspaceDao; + @Resource private IWorkspaceDao workspaceDao; @Override public Long createWorkspace(WorkspaceReq workspaceReq) { validateWorkspaceParam(workspaceReq); + permCheck(workspaceReq.getWorkspaceName(), AccessType.CREATE); Workspace workspaceByName = workspaceDao.selectWorkspaceByName(workspaceReq.getWorkspaceName()); if (workspaceByName != null) { @@ -95,6 +101,7 @@ public class WorkspaceServiceImpl extends SeatunnelBaseServiceImpl implements Wo SeatunnelErrorEnum.RESOURCE_NOT_FOUND, "Workspace with id " + id + " not found."); } + permCheck(workspace.getWorkspaceName(), AccessType.UPDATE); validateWorkspaceParam(workspaceReq); // Check if the workspace name is being changed and if it already exists in the database @@ -115,6 +122,7 @@ public class WorkspaceServiceImpl extends SeatunnelBaseServiceImpl implements Wo public boolean deleteWorkspace(Long id) { Workspace workspace = workspaceDao.selectWorkspaceById(id); if (null != workspace) { + permCheck(workspace.getWorkspaceName(), AccessType.DELETE); return workspaceDao.deleteWorkspaceById(id); } return false; @@ -122,7 +130,9 @@ public class WorkspaceServiceImpl extends SeatunnelBaseServiceImpl implements Wo @Override public List<Workspace> getAllWorkspaces() { - return workspaceDao.selectAllWorkspaces(); + return workspaceDao.selectAllWorkspaces().stream() + .filter(workspace -> hasReadPerm(workspace.getWorkspaceName())) + .collect(Collectors.toList()); } @Override @@ -130,22 +140,6 @@ public class WorkspaceServiceImpl extends SeatunnelBaseServiceImpl implements Wo return getWorkspace("default"); } - @Override - public Long getWorkspaceIdOrDefault(Long workspaceId) { - if (workspaceId == null || workspaceId == 0 || workspaceId == 1) { - return getDefaultWorkspace().getId(); - } else { - // Check if the workspace exists - Workspace workspaceById = getWorkspace(workspaceId); - if (workspaceById == null) { - throw new SeatunnelException( - SeatunnelErrorEnum.RESOURCE_NOT_FOUND, - "Workspace with id " + workspaceId + " not found."); - } - return workspaceById.getId(); - } - } - public Long getWorkspaceIdOrCurrent(String workspaceName) { if (StringUtils.isEmpty(workspaceName)) { // get names from current workspace @@ -154,4 +148,20 @@ public class WorkspaceServiceImpl extends SeatunnelBaseServiceImpl implements Wo return getWorkspace(workspaceName).getId(); } } + + private void permCheck(String resourceName, AccessType accessType) { + permissionCheck( + resourceName, + ResourceType.WORKSPACE, + accessType, + UserContextHolder.getAccessInfo()); + } + + private boolean hasReadPerm(String resourceName) { + return hasPermission( + resourceName, + ResourceType.WORKSPACE, + AccessType.READ, + UserContextHolder.getAccessInfo()); + } } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/GlobalExceptionHandler.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/GlobalExceptionHandler.java index f7d22b7d..f18ebe7f 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/GlobalExceptionHandler.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/GlobalExceptionHandler.java @@ -18,6 +18,7 @@ package org.apache.seatunnel.app.utils; import org.apache.seatunnel.app.common.Result; +import org.apache.seatunnel.common.access.AccessDeniedException; import org.apache.seatunnel.datasource.plugin.api.DataSourcePluginException; import org.apache.seatunnel.server.common.ParamValidationException; import org.apache.seatunnel.server.common.SeatunnelErrorEnum; @@ -88,4 +89,9 @@ public class GlobalExceptionHandler { private Result<String> paramValidationHandler(SeatunnelException e) { return Result.failure(e); } + + @ExceptionHandler(value = AccessDeniedException.class) + private Result<String> paramValidationHandler(AccessDeniedException e) { + return Result.failure(SeatunnelErrorEnum.ACCESS_DENIED, e.getMessage()); + } } diff --git a/seatunnel-server/seatunnel-app/src/main/resources/application.yml b/seatunnel-server/seatunnel-app/src/main/resources/application.yml index a90a6366..57117794 100644 --- a/seatunnel-server/seatunnel-app/src/main/resources/application.yml +++ b/seatunnel-server/seatunnel-app/src/main/resources/application.yml @@ -59,6 +59,7 @@ seatunnel-web: keys-to-encrypt: - password - auth + access-controller-class: org.apache.seatunnel.app.permission.SeatunnelAccessControllerDefaultImpl --- spring: config: diff --git a/seatunnel-server/seatunnel-server-common/src/main/java/org/apache/seatunnel/server/common/SeatunnelErrorEnum.java b/seatunnel-server/seatunnel-server-common/src/main/java/org/apache/seatunnel/server/common/SeatunnelErrorEnum.java index 5318b550..77afcff9 100644 --- a/seatunnel-server/seatunnel-server-common/src/main/java/org/apache/seatunnel/server/common/SeatunnelErrorEnum.java +++ b/seatunnel-server/seatunnel-server-common/src/main/java/org/apache/seatunnel/server/common/SeatunnelErrorEnum.java @@ -140,7 +140,8 @@ public enum SeatunnelErrorEnum { INVALID_PARAM(60019, "", "param [%s] is invalid. %s"), TASK_NAME_ALREADY_EXISTS(60020, "task name already exists", "task [%s] already exists"), RESOURCE_NOT_FOUND(404, "", "%s"), - RESOURCE_ALREADY_EXISTS(60021, "resource already exists", "resource [%s] already exists"); + RESOURCE_ALREADY_EXISTS(60021, "resource already exists", "resource [%s] already exists"), + ACCESS_DENIED(403, "Access denied", "%s"); private final int code; private final String msg; diff --git a/seatunnel-web-common/pom.xml b/seatunnel-web-common/pom.xml new file mode 100644 index 00000000..e74ae266 --- /dev/null +++ b/seatunnel-web-common/pom.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.seatunnel</groupId> + <artifactId>seatunnel-web</artifactId> + <version>${revision}</version> + </parent> + <artifactId>seatunnel-web-common</artifactId> +</project> diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java b/seatunnel-web-common/src/main/java/org/apache/seatunnel/common/access/AccessDeniedException.java similarity index 56% copy from seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java copy to seatunnel-web-common/src/main/java/org/apache/seatunnel/common/access/AccessDeniedException.java index 8897779a..c18a3c02 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java +++ b/seatunnel-web-common/src/main/java/org/apache/seatunnel/common/access/AccessDeniedException.java @@ -14,19 +14,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.seatunnel.app.security; -import org.apache.seatunnel.app.dal.entity.User; +package org.apache.seatunnel.common.access; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; +public class AccessDeniedException extends RuntimeException { + public AccessDeniedException(String message) { + super(message); + } -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UserContext { - User user; - Long workspaceId; - String workspaceName; + public static void accessDenied( + String userName, + String resourceName, + ResourceType resourceType, + AccessType accessType) { + throw new AccessDeniedException( + String.format( + "Access denied: user=%s, resource=%s, resource-type=%s, operation=%s", + userName, resourceName, resourceType.getName(), accessType.getName())); + } } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java b/seatunnel-web-common/src/main/java/org/apache/seatunnel/common/access/AccessInfo.java similarity index 74% copy from seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java copy to seatunnel-web-common/src/main/java/org/apache/seatunnel/common/access/AccessInfo.java index 8897779a..7ed8f39c 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java +++ b/seatunnel-web-common/src/main/java/org/apache/seatunnel/common/access/AccessInfo.java @@ -14,19 +14,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.seatunnel.app.security; -import org.apache.seatunnel.app.dal.entity.User; +package org.apache.seatunnel.common.access; -import lombok.AllArgsConstructor; import lombok.Data; -import lombok.NoArgsConstructor; + +import java.util.Set; @Data -@AllArgsConstructor -@NoArgsConstructor -public class UserContext { - User user; - Long workspaceId; - String workspaceName; +public class AccessInfo { + private String username; + private String workspaceName; + private Set<String> userGroups; + private Set<String> userRoles; } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java b/seatunnel-web-common/src/main/java/org/apache/seatunnel/common/access/AccessType.java similarity index 71% copy from seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java copy to seatunnel-web-common/src/main/java/org/apache/seatunnel/common/access/AccessType.java index 8897779a..b8f12a99 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java +++ b/seatunnel-web-common/src/main/java/org/apache/seatunnel/common/access/AccessType.java @@ -14,19 +14,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.seatunnel.app.security; -import org.apache.seatunnel.app.dal.entity.User; +package org.apache.seatunnel.common.access; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; +public enum AccessType { + CREATE, + READ, + UPDATE, + DELETE, + EXECUTE; -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UserContext { - User user; - Long workspaceId; - String workspaceName; + public String getName() { + return this.name().toLowerCase(); + } } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java b/seatunnel-web-common/src/main/java/org/apache/seatunnel/common/access/ResourceType.java similarity index 71% copy from seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java copy to seatunnel-web-common/src/main/java/org/apache/seatunnel/common/access/ResourceType.java index 8897779a..2a1bc67b 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java +++ b/seatunnel-web-common/src/main/java/org/apache/seatunnel/common/access/ResourceType.java @@ -14,19 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.seatunnel.app.security; +package org.apache.seatunnel.common.access; -import org.apache.seatunnel.app.dal.entity.User; +public enum ResourceType { + WORKSPACE, + DATASOURCE, + JOB, + USER, + VIRTUAL_TABLE; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UserContext { - User user; - Long workspaceId; - String workspaceName; + public String getName() { + return this.name().toLowerCase(); + } } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobLineMapper.java b/seatunnel-web-common/src/main/java/org/apache/seatunnel/common/access/SeatunnelAccessController.java similarity index 65% copy from seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobLineMapper.java copy to seatunnel-web-common/src/main/java/org/apache/seatunnel/common/access/SeatunnelAccessController.java index 9317520a..06a8ad4e 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobLineMapper.java +++ b/seatunnel-web-common/src/main/java/org/apache/seatunnel/common/access/SeatunnelAccessController.java @@ -14,20 +14,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.apache.seatunnel.app.dal.mapper; - -import org.apache.seatunnel.app.dal.entity.JobLine; - -import org.apache.ibatis.annotations.Param; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -import java.util.List; - -public interface JobLineMapper extends BaseMapper<JobLine> { - - void deleteLinesByVersionId(@Param("versionId") long jobVersionId); - - void insertBatchLines(@Param("lines") List<JobLine> lines); +package org.apache.seatunnel.common.access; + +public interface SeatunnelAccessController { + void authorizeAccess( + String resourceName, + ResourceType resourceType, + AccessType accessType, + AccessInfo accessInfo); + + boolean hasPermission( + String resourceName, + ResourceType resourceType, + AccessType accessType, + AccessInfo accessInfo); } diff --git a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/common/AccessControllerTestingImp.java b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/common/AccessControllerTestingImp.java new file mode 100644 index 00000000..94e052cb --- /dev/null +++ b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/common/AccessControllerTestingImp.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.seatunnel.app.common; + +import org.apache.seatunnel.common.access.AccessDeniedException; +import org.apache.seatunnel.common.access.AccessInfo; +import org.apache.seatunnel.common.access.AccessType; +import org.apache.seatunnel.common.access.ResourceType; +import org.apache.seatunnel.common.access.SeatunnelAccessController; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class is intended to provide basic access control functionality for testing purposes, rather + * than simulating a full-fledged Ranger access controller. + */ +public class AccessControllerTestingImp implements SeatunnelAccessController { + private static boolean isAccessControllerEnabled = false; + private static final Map<String, List<ResourcePermissionData>> permissionList = new HashMap<>(); + + public static void resetResourcePermission(String username, ResourcePermissionData permission) { + clearPermission(); + addResourcePermission(username, permission); + } + + public static void addResourcePermission(String username, ResourcePermissionData permission) { + List<ResourcePermissionData> resourcePermissionDataList = + permissionList.computeIfAbsent(username, k -> new ArrayList<>()); + resourcePermissionDataList.add(permission); + } + + public static void enableAccessController() { + isAccessControllerEnabled = true; + } + + public static void disableAccessController() { + isAccessControllerEnabled = false; + } + + public static void clearPermission() { + permissionList.clear(); + } + + @Override + public void authorizeAccess( + String resourceName, + ResourceType resourceType, + AccessType accessType, + AccessInfo accessInfo) { + if (!hasPermission(resourceName, resourceType, accessType, accessInfo)) { + AccessDeniedException.accessDenied( + accessInfo.getUsername(), resourceName, resourceType, accessType); + } + } + + @Override + public boolean hasPermission( + String resourceName, + ResourceType resourceType, + AccessType accessType, + AccessInfo accessInfo) { + if (!isAccessControllerEnabled) { + return true; + } + List<ResourcePermissionData> permissions = permissionList.get(accessInfo.getUsername()); + if (permissions != null) { + for (ResourcePermissionData permission : permissions) { + if (resourceType == ResourceType.USER || resourceType == ResourceType.WORKSPACE) { + // Do not consider workspace name + if (permission.getResourceName().equals(resourceName) + && permission.getResourceType() == resourceType + && permission.getAccessTypes().contains(accessType)) { + return true; + } + } else { + if (permission.getWorkspaceName().equals(accessInfo.getWorkspaceName()) + && permission.getResourceName().equals(resourceName) + && permission.getResourceType() == resourceType + && permission.getAccessTypes().contains(accessType)) { + return true; + } + } + } + } + return false; + } +} diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/common/ResourcePermissionData.java similarity index 76% copy from seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java copy to seatunnel-web-it/src/test/java/org/apache/seatunnel/app/common/ResourcePermissionData.java index 8897779a..06e5050a 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java +++ b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/common/ResourcePermissionData.java @@ -14,19 +14,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.seatunnel.app.security; +package org.apache.seatunnel.app.common; -import org.apache.seatunnel.app.dal.entity.User; +import org.apache.seatunnel.common.access.AccessType; +import org.apache.seatunnel.common.access.ResourceType; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; + @Data @AllArgsConstructor @NoArgsConstructor -public class UserContext { - User user; - Long workspaceId; +public class ResourcePermissionData { String workspaceName; + String resourceName; + ResourceType resourceType; + List<AccessType> accessTypes; } diff --git a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/JobDefinitionControllerWrapper.java b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/JobDefinitionControllerWrapper.java index ccff9c3c..3a76371d 100644 --- a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/JobDefinitionControllerWrapper.java +++ b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/JobDefinitionControllerWrapper.java @@ -25,9 +25,11 @@ import org.apache.seatunnel.app.domain.response.job.JobDefinitionRes; import org.apache.seatunnel.app.utils.JSONTestUtils; import org.apache.seatunnel.common.constants.JobMode; import org.apache.seatunnel.common.utils.JsonUtils; +import org.apache.seatunnel.server.common.SeatunnelErrorEnum; import com.fasterxml.jackson.core.type.TypeReference; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class JobDefinitionControllerWrapper extends SeatunnelWebTestingBase { @@ -48,6 +50,15 @@ public class JobDefinitionControllerWrapper extends SeatunnelWebTestingBase { return result.getData(); } + public void createJobExpectingFailure(String jobName) { + JobReq jobReq = new JobReq(); + jobReq.setName(jobName); + jobReq.setDescription(jobName + " description"); + jobReq.setJobType(BusinessMode.DATA_INTEGRATION); + Result<Long> result = createJobDefinition(jobReq); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), result.getCode()); + } + public Result<PageInfo<JobDefinitionRes>> getJobDefinition( String searchName, Integer pageNo, Integer pageSize, JobMode jobMode) { String response = diff --git a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/SeatunnelDatasourceControllerWrapper.java b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/SeatunnelDatasourceControllerWrapper.java index 3f2af1f2..d8fd642c 100644 --- a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/SeatunnelDatasourceControllerWrapper.java +++ b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/SeatunnelDatasourceControllerWrapper.java @@ -25,9 +25,13 @@ import org.apache.seatunnel.app.domain.response.datasource.DatasourceDetailRes; import org.apache.seatunnel.app.domain.response.datasource.DatasourceRes; import org.apache.seatunnel.app.utils.JSONTestUtils; import org.apache.seatunnel.common.utils.JsonUtils; +import org.apache.seatunnel.server.common.SeatunnelErrorEnum; import com.fasterxml.jackson.core.type.TypeReference; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class SeatunnelDatasourceControllerWrapper extends SeatunnelWebTestingBase { @@ -39,6 +43,12 @@ public class SeatunnelDatasourceControllerWrapper extends SeatunnelWebTestingBas return result.getData(); } + public void createDatasourceExpectingFailure(String datasourceName) { + DatasourceReq req = getFakeSourceDatasourceReq(datasourceName); + Result<String> result = createDatasource(req); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), result.getCode()); + } + public String createConsoleDatasource(String datasourceName) { DatasourceReq req = getConsoleDatasourceReq(datasourceName); Result<String> result = createDatasource(req); @@ -121,4 +131,17 @@ public class SeatunnelDatasourceControllerWrapper extends SeatunnelWebTestingBas "{\"url\":\"jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&allowPublicKeyRetrieval=true\",\"driver\":\"com.mysql.cj.jdbc.Driver\",\"user\":\"someUser\",\"password\":\"somePassword\"}"); return req; } + + public Result<List<String>> getDatasourceNames(String namePrefix) { + String response; + if (namePrefix == null) { + response = sendRequest(String.format("%s/datasource/names", baseUrl)); + } else { + response = + sendRequest( + String.format( + "%s/datasource/names?namePrefix=%s", baseUrl, namePrefix)); + } + return JSONTestUtils.parseObject(response, new TypeReference<Result<List<String>>>() {}); + } } diff --git a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/SeatunnelAccessControllerTest.java b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/SeatunnelAccessControllerTest.java new file mode 100644 index 00000000..6aa69b69 --- /dev/null +++ b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/SeatunnelAccessControllerTest.java @@ -0,0 +1,790 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seatunnel.app.test; + +import org.apache.seatunnel.app.common.AccessControllerTestingImp; +import org.apache.seatunnel.app.common.ResourcePermissionData; +import org.apache.seatunnel.app.common.Result; +import org.apache.seatunnel.app.common.SeaTunnelWebCluster; +import org.apache.seatunnel.app.controller.JobConfigControllerWrapper; +import org.apache.seatunnel.app.controller.JobControllerWrapper; +import org.apache.seatunnel.app.controller.JobDefinitionControllerWrapper; +import org.apache.seatunnel.app.controller.JobExecutorControllerWrapper; +import org.apache.seatunnel.app.controller.SeatunnelDatasourceControllerWrapper; +import org.apache.seatunnel.app.controller.UserControllerWrapper; +import org.apache.seatunnel.app.controller.WorkspaceControllerWrapper; +import org.apache.seatunnel.app.dal.entity.Workspace; +import org.apache.seatunnel.app.domain.request.datasource.DatasourceReq; +import org.apache.seatunnel.app.domain.request.job.JobConfig; +import org.apache.seatunnel.app.domain.request.job.JobCreateReq; +import org.apache.seatunnel.app.domain.request.user.AddUserReq; +import org.apache.seatunnel.app.domain.request.user.UpdateUserReq; +import org.apache.seatunnel.app.domain.request.user.UserLoginReq; +import org.apache.seatunnel.app.domain.request.workspace.WorkspaceReq; +import org.apache.seatunnel.app.domain.response.PageInfo; +import org.apache.seatunnel.app.domain.response.datasource.DatasourceDetailRes; +import org.apache.seatunnel.app.domain.response.datasource.DatasourceRes; +import org.apache.seatunnel.app.domain.response.job.JobConfigRes; +import org.apache.seatunnel.app.domain.response.job.JobDefinitionRes; +import org.apache.seatunnel.app.domain.response.job.JobRes; +import org.apache.seatunnel.app.domain.response.user.AddUserRes; +import org.apache.seatunnel.app.domain.response.user.UserSimpleInfoRes; +import org.apache.seatunnel.app.utils.JobTestingUtils; +import org.apache.seatunnel.common.access.AccessType; +import org.apache.seatunnel.common.access.ResourceType; +import org.apache.seatunnel.common.constants.JobMode; +import org.apache.seatunnel.server.common.SeatunnelErrorEnum; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SeatunnelAccessControllerTest { + private static final SeaTunnelWebCluster seaTunnelWebCluster = new SeaTunnelWebCluster(); + private static WorkspaceControllerWrapper workspaceControllerWrapper; + private static UserControllerWrapper userControllerWrapper; + private static SeatunnelDatasourceControllerWrapper datasourceControllerWrapper; + private static JobDefinitionControllerWrapper jobDefinitionControllerWrapper; + private static JobConfigControllerWrapper jobConfigControllerWrapper; + private static JobControllerWrapper jobControllerWrapper; + private static JobExecutorControllerWrapper jobExecutorControllerWrapper; + private static final String uniqueId = "_" + System.currentTimeMillis(); + + @BeforeAll + public static void setUp() { + seaTunnelWebCluster.start(); + workspaceControllerWrapper = new WorkspaceControllerWrapper(); + userControllerWrapper = new UserControllerWrapper(); + datasourceControllerWrapper = new SeatunnelDatasourceControllerWrapper(); + jobDefinitionControllerWrapper = new JobDefinitionControllerWrapper(); + jobConfigControllerWrapper = new JobConfigControllerWrapper(); + jobControllerWrapper = new JobControllerWrapper(); + jobExecutorControllerWrapper = new JobExecutorControllerWrapper(); + AccessControllerTestingImp.enableAccessController(); + } + + @Test + public void testWorkspaceAccessPermission() { + String user1 = "admin"; + String workspaceName = "workspace_access_workspace" + uniqueId; + + Result<Long> createWorkspaceResult = + workspaceControllerWrapper.createWorkspace(workspaceName); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), createWorkspaceResult.getCode()); + + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData( + null, + workspaceName, + ResourceType.WORKSPACE, + Collections.singletonList(AccessType.CREATE))); + createWorkspaceResult = workspaceControllerWrapper.createWorkspace(workspaceName); + assertTrue(createWorkspaceResult.isSuccess()); + + // Handle read operation + AccessControllerTestingImp.clearPermission(); + Result<List<Workspace>> getWorkspaces = workspaceControllerWrapper.getAllWorkspaces(); + assertTrue(getWorkspaces.isSuccess()); + assertEquals(0, getWorkspaces.getData().size()); + + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData( + null, + workspaceName, + ResourceType.WORKSPACE, + Collections.singletonList(AccessType.READ))); + + getWorkspaces = workspaceControllerWrapper.getAllWorkspaces(); + assertTrue(getWorkspaces.isSuccess()); + assertEquals(1, getWorkspaces.getData().size()); + + String anotherWorkspace = "another_workspace_access" + uniqueId; + AccessControllerTestingImp.clearPermission(); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData( + null, + anotherWorkspace, + ResourceType.WORKSPACE, + Collections.singletonList(AccessType.CREATE))); + Result<Long> anotherCreateWorkspaceResult = + workspaceControllerWrapper.createWorkspace(anotherWorkspace); + assertTrue(anotherCreateWorkspaceResult.isSuccess()); + + getWorkspaces = workspaceControllerWrapper.getAllWorkspaces(); + assertTrue(anotherCreateWorkspaceResult.isSuccess()); + assertEquals(0, getWorkspaces.getData().size()); + + AccessControllerTestingImp.clearPermission(); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData( + null, + workspaceName, + ResourceType.WORKSPACE, + Collections.singletonList(AccessType.READ))); + AccessControllerTestingImp.addResourcePermission( + user1, + new ResourcePermissionData( + null, + anotherWorkspace, + ResourceType.WORKSPACE, + Collections.singletonList(AccessType.READ))); + getWorkspaces = workspaceControllerWrapper.getAllWorkspaces(); + assertTrue(anotherCreateWorkspaceResult.isSuccess()); + assertEquals(2, getWorkspaces.getData().size()); + + // Handle update operation + AccessControllerTestingImp.clearPermission(); + WorkspaceReq updateWorkspaceReq = + new WorkspaceReq(workspaceName + "_new", "new description"); + Result<Boolean> updateResult = + workspaceControllerWrapper.updateWorkspace( + createWorkspaceResult.getData(), updateWorkspaceReq); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), updateResult.getCode()); + + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData( + null, + workspaceName, + ResourceType.WORKSPACE, + Collections.singletonList(AccessType.UPDATE))); + updateResult = + workspaceControllerWrapper.updateWorkspace( + createWorkspaceResult.getData(), updateWorkspaceReq); + assertTrue(updateResult.isSuccess(), updateResult.getMsg()); + + // Handle delete operation + AccessControllerTestingImp.clearPermission(); + Result<Boolean> deleteResult = + workspaceControllerWrapper.deleteWorkspace(createWorkspaceResult.getData()); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), deleteResult.getCode()); + + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData( + null, + updateWorkspaceReq.getWorkspaceName(), + ResourceType.WORKSPACE, + Collections.singletonList(AccessType.DELETE))); + deleteResult = workspaceControllerWrapper.deleteWorkspace(createWorkspaceResult.getData()); + assertTrue(deleteResult.isSuccess(), deleteResult.getMsg()); + } + + @Test + public void testUserAccessPermission() { + String user1 = "user_access_user_1" + uniqueId; + String pass = "somePassword"; + String workspaceName = "workspace_access_user" + uniqueId; + + List<AccessType> accessTypes = new ArrayList<>(); + accessTypes.add(AccessType.CREATE); + + createWorkspaceAndUser(workspaceName, user1, pass); + login(new UserLoginReq(user1, pass, workspaceName)); + + // Handle create operation + accessTypes.clear(); + String newUser = "new_user_access" + uniqueId; + Result<AddUserRes> addUserResult = + userControllerWrapper.addUser(getAddUserReq(newUser, pass)); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), addUserResult.getCode()); + accessTypes.add(AccessType.CREATE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData(workspaceName, newUser, ResourceType.USER, accessTypes)); + // should be successful as user1 has access to create user + addUserResult = userControllerWrapper.addUser(getAddUserReq(newUser, pass)); + assertTrue(addUserResult.isSuccess()); + + // Handle read operation + Result<PageInfo<UserSimpleInfoRes>> getUsers = userControllerWrapper.listUsers(newUser); + assertTrue(getUsers.isSuccess()); + assertEquals(0, getUsers.getData().getData().size()); + + accessTypes.clear(); + accessTypes.add(AccessType.READ); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData(workspaceName, newUser, ResourceType.USER, accessTypes)); + getUsers = userControllerWrapper.listUsers(newUser); + assertTrue(getUsers.isSuccess()); + assertEquals(1, getUsers.getData().getData().size()); + + String anotherUser = "another_user_access" + uniqueId; + accessTypes.clear(); + accessTypes.add(AccessType.CREATE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData( + workspaceName, anotherUser, ResourceType.USER, accessTypes)); + Result<AddUserRes> anotherAddUserResult = + userControllerWrapper.addUser(getAddUserReq(anotherUser, pass)); + assertTrue(anotherAddUserResult.isSuccess()); + + Result<PageInfo<UserSimpleInfoRes>> listUsers = userControllerWrapper.listUsers(); + assertTrue(anotherAddUserResult.isSuccess()); + assertEquals(0, listUsers.getData().getData().size()); + + accessTypes.clear(); + accessTypes.add(AccessType.READ); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData(workspaceName, newUser, ResourceType.USER, accessTypes)); + AccessControllerTestingImp.addResourcePermission( + user1, + new ResourcePermissionData( + workspaceName, anotherUser, ResourceType.USER, accessTypes)); + listUsers = userControllerWrapper.listUsers(); + assertTrue(listUsers.isSuccess()); + assertEquals(2, listUsers.getData().getData().size()); + + // Handle update operation + UpdateUserReq updateUserReq = new UpdateUserReq(); + updateUserReq.setUsername(newUser); + updateUserReq.setUserId(addUserResult.getData().getId()); + updateUserReq.setPassword("newPassword"); + updateUserReq.setStatus((byte) 0); + updateUserReq.setType((byte) 0); + Result<Void> updateResult = + userControllerWrapper.updateUser( + String.valueOf(updateUserReq.getUserId()), updateUserReq); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), updateResult.getCode()); + + accessTypes.clear(); + accessTypes.add(AccessType.UPDATE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData(workspaceName, newUser, ResourceType.USER, accessTypes)); + updateResult = + userControllerWrapper.updateUser( + String.valueOf(updateUserReq.getUserId()), updateUserReq); + assertTrue(updateResult.isSuccess(), updateResult.getMsg()); + + // Handle disable operation + AccessControllerTestingImp.clearPermission(); + Result<Void> disableResult = + userControllerWrapper.disableUser(String.valueOf(updateUserReq.getUserId())); + assertEquals( + SeatunnelErrorEnum.ACCESS_DENIED.getCode(), + disableResult.getCode(), + disableResult.getMsg()); + + accessTypes.clear(); + accessTypes.add(AccessType.UPDATE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData(workspaceName, newUser, ResourceType.USER, accessTypes)); + disableResult = + userControllerWrapper.disableUser(String.valueOf(updateUserReq.getUserId())); + assertTrue(disableResult.isSuccess()); + + // Handle enable operation + AccessControllerTestingImp.clearPermission(); + Result<Void> enableResult = + userControllerWrapper.enableUser(String.valueOf(updateUserReq.getUserId())); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), enableResult.getCode()); + + accessTypes.clear(); + accessTypes.add(AccessType.UPDATE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData(workspaceName, newUser, ResourceType.USER, accessTypes)); + enableResult = userControllerWrapper.enableUser(String.valueOf(updateUserReq.getUserId())); + assertTrue(enableResult.isSuccess()); + + // Handle delete operation + AccessControllerTestingImp.clearPermission(); + Result<Void> deleteResult = + userControllerWrapper.deleteUser(String.valueOf(addUserResult.getData().getId())); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), deleteResult.getCode()); + + accessTypes.clear(); + accessTypes.add(AccessType.DELETE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData(workspaceName, newUser, ResourceType.USER, accessTypes)); + deleteResult = + userControllerWrapper.deleteUser(String.valueOf(addUserResult.getData().getId())); + assertTrue(deleteResult.isSuccess()); + } + + @Test + public void testDatasourceAccessPermission() { + String user1 = "user_access_datasource_1" + uniqueId; + String user2 = "user_access_datasource_2" + uniqueId; + String pass = "somePassword"; + String workspaceName = "workspace_access_datasource" + uniqueId; + + // create workspaces and users using admin credentials + createWorkspaceAndUser(workspaceName, user1, pass); + createUserAndVerify(user2, pass); + + login(new UserLoginReq(user1, pass, workspaceName)); + + // Handle create operation + String datasourceName1 = "1_datasource_access" + uniqueId; + datasourceControllerWrapper.createDatasourceExpectingFailure(datasourceName1); + List<AccessType> accessTypes = new ArrayList<>(); + accessTypes.add(AccessType.CREATE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData( + workspaceName, datasourceName1, ResourceType.DATASOURCE, accessTypes)); + // should be successful as user1 has access to create datasource + String datasourceId1 = + datasourceControllerWrapper.createFakeSourceDatasource(datasourceName1); + + Result<DatasourceDetailRes> getDataSource = + datasourceControllerWrapper.getDatasource(datasourceId1); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), getDataSource.getCode()); + + // Handle read operation + accessTypes.clear(); + accessTypes.add(AccessType.READ); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData( + workspaceName, datasourceName1, ResourceType.DATASOURCE, accessTypes)); + getDataSource = datasourceControllerWrapper.getDatasource(datasourceId1); + assertTrue(getDataSource.isSuccess()); + + // Handle update operation + DatasourceReq req = new DatasourceReq(); + req.setDescription(getDataSource.getData().getDescription() + " new description"); + Result<Boolean> updateResult = + datasourceControllerWrapper.updateDatasource(datasourceId1, req); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), updateResult.getCode()); + + accessTypes.clear(); + accessTypes.add(AccessType.UPDATE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData( + workspaceName, datasourceName1, ResourceType.DATASOURCE, accessTypes)); + updateResult = datasourceControllerWrapper.updateDatasource(datasourceId1, req); + assertTrue(updateResult.isSuccess()); + + // Handle delete operation + Result<Boolean> deleteResult = datasourceControllerWrapper.deleteDatasource(datasourceId1); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), deleteResult.getCode()); + + accessTypes.clear(); + accessTypes.add(AccessType.DELETE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData( + workspaceName, datasourceName1, ResourceType.DATASOURCE, accessTypes)); + deleteResult = datasourceControllerWrapper.deleteDatasource(datasourceId1); + assertTrue(deleteResult.isSuccess()); + + // create again to use in list datasource + accessTypes.clear(); + accessTypes.add(AccessType.CREATE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData( + workspaceName, datasourceName1, ResourceType.DATASOURCE, accessTypes)); + datasourceControllerWrapper.createFakeSourceDatasource(datasourceName1); + + // logout and login with another user + userControllerWrapper.logout(); + login(new UserLoginReq(user2, pass, workspaceName)); + // Handle list operation + String datasourceName2 = "2_datasource_access" + uniqueId; + accessTypes.clear(); + accessTypes.add(AccessType.CREATE); + AccessControllerTestingImp.resetResourcePermission( + user2, + new ResourcePermissionData( + workspaceName, datasourceName2, ResourceType.DATASOURCE, accessTypes)); + String datasourceId2 = + datasourceControllerWrapper.createFakeSourceDatasource(datasourceName2); + Result<PageInfo<DatasourceRes>> datasourceList = + datasourceControllerWrapper.getDatasourceList( + "datasource_access" + uniqueId, "FakeSource", 1, 10); + assertTrue(datasourceList.isSuccess()); + assertEquals(0, datasourceList.getData().getData().size()); + + accessTypes.clear(); + accessTypes.add(AccessType.READ); + AccessControllerTestingImp.resetResourcePermission( + user2, + new ResourcePermissionData( + workspaceName, datasourceName2, ResourceType.DATASOURCE, accessTypes)); + datasourceList = + datasourceControllerWrapper.getDatasourceList( + "datasource_access" + uniqueId, "FakeSource", 1, 10); + assertTrue(datasourceList.isSuccess()); + assertEquals(1, datasourceList.getData().getData().size()); + assertEquals(datasourceId2, datasourceList.getData().getData().get(0).getId()); + + // Give permission to user2 on datasource created by user1 + AccessControllerTestingImp.addResourcePermission( + user2, + new ResourcePermissionData( + workspaceName, datasourceName1, ResourceType.DATASOURCE, accessTypes)); + datasourceList = + datasourceControllerWrapper.getDatasourceList( + "datasource_access" + uniqueId, "FakeSource", 1, 10); + assertTrue(datasourceList.isSuccess()); + assertEquals(2, datasourceList.getData().getData().size()); + } + + @Test + public void testJobAccessPermission() { + String user1 = "user_access_job_1" + uniqueId; + String user2 = "user_access_job_2" + uniqueId; + String pass = "somePassword"; + String workspaceName = "workspace_access_job" + uniqueId; + + // create workspaces and users using admin credentials + createWorkspaceAndUser(workspaceName, user1, pass); + createUserAndVerify(user2, pass); + + login(new UserLoginReq(user1, pass, workspaceName)); + + // Handle create operation + String jobName1 = "1_job_access" + uniqueId; + jobDefinitionControllerWrapper.createJobExpectingFailure(jobName1); + List<AccessType> accessTypes = new ArrayList<>(); + accessTypes.add(AccessType.CREATE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData(workspaceName, jobName1, ResourceType.JOB, accessTypes)); + // should be successful as user1 has access to create job + Long jobId = jobDefinitionControllerWrapper.createJobDefinition(jobName1); + + // Handle read operation + Result<JobDefinitionRes> getJob = + jobDefinitionControllerWrapper.getJobDefinitionById(jobId); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), getJob.getCode()); + + Result<JobConfigRes> getJobConfig = jobConfigControllerWrapper.getJobConfig(jobId); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), getJobConfig.getCode()); + + accessTypes.clear(); + accessTypes.add(AccessType.READ); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData(workspaceName, jobName1, ResourceType.JOB, accessTypes)); + getJob = jobDefinitionControllerWrapper.getJobDefinitionById(jobId); + assertTrue(getJob.isSuccess()); + + getJobConfig = jobConfigControllerWrapper.getJobConfig(jobId); + assertTrue(getJobConfig.isSuccess()); + + // Handle update operation + AccessControllerTestingImp.clearPermission(); + JobConfig jobConfig = jobConfigControllerWrapper.populateJobConfigObject(jobName1); + Result<Void> updateResult = jobConfigControllerWrapper.updateJobConfig(jobId, jobConfig); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), updateResult.getCode()); + + accessTypes.clear(); + accessTypes.add(AccessType.UPDATE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData(workspaceName, jobName1, ResourceType.JOB, accessTypes)); + updateResult = jobConfigControllerWrapper.updateJobConfig(jobId, jobConfig); + assertTrue(updateResult.isSuccess()); + + // Handle delete operation + Result<Void> deleteResult = jobDefinitionControllerWrapper.deleteJobDefinition(jobId); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), deleteResult.getCode()); + + accessTypes.clear(); + accessTypes.add(AccessType.DELETE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData(workspaceName, jobName1, ResourceType.JOB, accessTypes)); + deleteResult = jobDefinitionControllerWrapper.deleteJobDefinition(jobId); + assertTrue(deleteResult.isSuccess()); + + // create again to use in list job + accessTypes.clear(); + accessTypes.add(AccessType.CREATE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData(workspaceName, jobName1, ResourceType.JOB, accessTypes)); + jobDefinitionControllerWrapper.createJobDefinition(jobName1); + + // logout and login with another user + userControllerWrapper.logout(); + login(new UserLoginReq(user2, pass, workspaceName)); + // Handle list operation + String jobName2 = "2_job_access" + uniqueId; + accessTypes.clear(); + accessTypes.add(AccessType.CREATE); + AccessControllerTestingImp.resetResourcePermission( + user2, + new ResourcePermissionData(workspaceName, jobName2, ResourceType.JOB, accessTypes)); + Long jobId2 = jobDefinitionControllerWrapper.createJobDefinition(jobName2); + Result<PageInfo<JobDefinitionRes>> jobList = + jobDefinitionControllerWrapper.getJobDefinition( + "job_access" + uniqueId, 1, 10, JobMode.BATCH); + assertTrue(jobList.isSuccess()); + assertEquals(0, jobList.getData().getData().size()); + + accessTypes.clear(); + accessTypes.add(AccessType.READ); + AccessControllerTestingImp.resetResourcePermission( + user2, + new ResourcePermissionData(workspaceName, jobName2, ResourceType.JOB, accessTypes)); + jobList = + jobDefinitionControllerWrapper.getJobDefinition( + "job_access" + uniqueId, 1, 10, JobMode.BATCH); + assertTrue(jobList.isSuccess()); + assertEquals(1, jobList.getData().getData().size()); + assertEquals(jobId2, jobList.getData().getData().get(0).getId()); + + // Give permission to user2 on job created by user1 + AccessControllerTestingImp.addResourcePermission( + user2, + new ResourcePermissionData(workspaceName, jobName1, ResourceType.JOB, accessTypes)); + jobList = + jobDefinitionControllerWrapper.getJobDefinition( + "job_access" + uniqueId, 1, 10, JobMode.BATCH); + assertTrue(jobList.isSuccess()); + assertEquals(2, jobList.getData().getData().size()); + } + + @Test + public void testJobExecutionAccessPermission() { + String userName = "jobExec_user_access" + uniqueId; + String pass = "somePassword"; + String workspaceName = "jobExec_workspace_access" + uniqueId; + + // create workspaces and users using admin credentials + createWorkspaceAndUser(workspaceName, userName, pass); + + login(new UserLoginReq(userName, pass, workspaceName)); + + String jobName = "execJob_access" + uniqueId; + List<AccessType> accessTypes = new ArrayList<>(); + accessTypes.add(AccessType.CREATE); + // job update api is called during job creation, + accessTypes.add(AccessType.UPDATE); + AccessControllerTestingImp.resetResourcePermission( + userName, + new ResourcePermissionData(workspaceName, jobName, ResourceType.JOB, accessTypes)); + AccessControllerTestingImp.addResourcePermission( + userName, + new ResourcePermissionData( + workspaceName, + "source_" + jobName, + ResourceType.DATASOURCE, + Arrays.asList(AccessType.CREATE, AccessType.READ))); + + AccessControllerTestingImp.addResourcePermission( + userName, + new ResourcePermissionData( + workspaceName, + "console_" + jobName, + ResourceType.DATASOURCE, + Arrays.asList(AccessType.CREATE, AccessType.READ))); + + long jobVersionId = JobTestingUtils.createJob(jobName); + + Result<Long> executionResult = jobExecutorControllerWrapper.jobExecutor(jobVersionId); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), executionResult.getCode()); + + accessTypes.add(AccessType.EXECUTE); + AccessControllerTestingImp.resetResourcePermission( + userName, + new ResourcePermissionData(workspaceName, jobName, ResourceType.JOB, accessTypes)); + AccessControllerTestingImp.addResourcePermission( + userName, + new ResourcePermissionData( + workspaceName, + "source_" + jobName, + ResourceType.DATASOURCE, + Collections.singletonList(AccessType.READ))); + + AccessControllerTestingImp.addResourcePermission( + userName, + new ResourcePermissionData( + workspaceName, + "console_" + jobName, + ResourceType.DATASOURCE, + Collections.singletonList(AccessType.READ))); + executionResult = jobExecutorControllerWrapper.jobExecutor(jobVersionId); + assertTrue(executionResult.isSuccess(), executionResult.getMsg()); + } + + @Test + public void testJobAccessPermissionForSingleJobCreateAPI() { + String user1 = "user_access_single_job_1" + uniqueId; + String user2 = "user_access_single_job_2" + uniqueId; + String pass = "somePassword"; + String workspaceName = "workspace_access_single_job" + uniqueId; + + // create workspaces and users using admin credentials + createWorkspaceAndUser(workspaceName, user1, pass); + createUserAndVerify(user2, pass); + + login(new UserLoginReq(user1, pass, workspaceName)); + + // Handle create operation + String jobName = "access_single_api" + uniqueId; + String fsdSourceName = "fake_source_create" + uniqueId; + String csSourceName = "console_create" + uniqueId; + List<AccessType> accessTypes = new ArrayList<>(); + accessTypes.add(AccessType.CREATE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData( + workspaceName, fsdSourceName, ResourceType.DATASOURCE, accessTypes)); + + AccessControllerTestingImp.addResourcePermission( + user1, + new ResourcePermissionData( + workspaceName, csSourceName, ResourceType.DATASOURCE, accessTypes)); + JobCreateReq jobCreateReq = + JobTestingUtils.populateJobCreateReqFromFile(jobName, fsdSourceName, csSourceName); + Result<Long> jobCreation = jobControllerWrapper.createJob(jobCreateReq); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), jobCreation.getCode()); + + accessTypes.clear(); + accessTypes.add(AccessType.CREATE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData(workspaceName, jobName, ResourceType.JOB, accessTypes)); + + AccessControllerTestingImp.addResourcePermission( + user1, + new ResourcePermissionData( + workspaceName, + fsdSourceName, + ResourceType.DATASOURCE, + Collections.singletonList(AccessType.READ))); + + AccessControllerTestingImp.addResourcePermission( + user1, + new ResourcePermissionData( + workspaceName, + csSourceName, + ResourceType.DATASOURCE, + Collections.singletonList(AccessType.READ))); + jobCreation = jobControllerWrapper.createJob(jobCreateReq); + assertTrue(jobCreation.isSuccess()); + + // Handle read operation + Result<JobRes> getJobResponse = jobControllerWrapper.getJob(jobCreation.getData()); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), getJobResponse.getCode()); + + accessTypes.clear(); + accessTypes.add(AccessType.READ); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData(workspaceName, jobName, ResourceType.JOB, accessTypes)); + getJobResponse = jobControllerWrapper.getJob(jobCreation.getData()); + assertTrue(getJobResponse.isSuccess(), getJobResponse.getMsg()); + + // Handle update operation + JobCreateReq jobUpdateReq = + jobControllerWrapper.convertJobResToJobCreateReq(getJobResponse.getData()); + Result<Void> updateResult = + jobControllerWrapper.updateJob(jobCreation.getData(), jobUpdateReq); + assertEquals(SeatunnelErrorEnum.ACCESS_DENIED.getCode(), updateResult.getCode()); + + accessTypes.clear(); + accessTypes.add(AccessType.UPDATE); + AccessControllerTestingImp.resetResourcePermission( + user1, + new ResourcePermissionData(workspaceName, jobName, ResourceType.JOB, accessTypes)); + AccessControllerTestingImp.addResourcePermission( + user1, + new ResourcePermissionData( + workspaceName, + fsdSourceName, + ResourceType.DATASOURCE, + Collections.singletonList(AccessType.READ))); + + AccessControllerTestingImp.addResourcePermission( + user1, + new ResourcePermissionData( + workspaceName, + csSourceName, + ResourceType.DATASOURCE, + Collections.singletonList(AccessType.READ))); + updateResult = jobControllerWrapper.updateJob(jobCreation.getData(), jobUpdateReq); + assertTrue(updateResult.isSuccess(), updateResult.getMsg()); + } + + private void createWorkspaceAndUser(String workspaceName, String username, String password) { + AccessControllerTestingImp.resetResourcePermission( + "admin", + new ResourcePermissionData( + null, + workspaceName, + ResourceType.WORKSPACE, + Arrays.asList(AccessType.CREATE, AccessType.UPDATE))); + workspaceControllerWrapper.createWorkspaceAndVerify(workspaceName); + createUserAndVerify(username, password); + } + + private void createUserAndVerify(String username, String password) { + AccessControllerTestingImp.addResourcePermission( + "admin", + new ResourcePermissionData( + null, + username, + ResourceType.USER, + Collections.singletonList(AccessType.CREATE))); + Result<AddUserRes> result = + userControllerWrapper.addUser(getAddUserReq(username, password)); + assertTrue(result.isSuccess()); + } + + private static void login(UserLoginReq userLoginReq) { + Result<UserSimpleInfoRes> login = userControllerWrapper.login(userLoginReq, null, true); + assertTrue(login.isSuccess()); + } + + private AddUserReq getAddUserReq(String user, String pass) { + AddUserReq addUserReq = new AddUserReq(); + addUserReq.setUsername(user); + addUserReq.setPassword(pass); + addUserReq.setStatus((byte) 0); + addUserReq.setType((byte) 0); + return addUserReq; + } + + @AfterEach + public void cleanup() { + userControllerWrapper.logout(); + AccessControllerTestingImp.clearPermission(); + } + + @AfterAll + public static void tearDown() { + AccessControllerTestingImp.disableAccessController(); + seaTunnelWebCluster.stop(); + } +} diff --git a/seatunnel-web-it/src/test/resources/application.yml b/seatunnel-web-it/src/test/resources/application.yml index 752fbc7b..d691a712 100644 --- a/seatunnel-web-it/src/test/resources/application.yml +++ b/seatunnel-web-it/src/test/resources/application.yml @@ -55,6 +55,7 @@ seatunnel-web: keys-to-encrypt: - password - auth + access-controller-class: org.apache.seatunnel.app.common.AccessControllerTestingImp --- spring: