diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/service/ServiceProjectAuthResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/service/ServiceProjectAuthResource.kt index 31fc7fdb84c..9fceb3a64d9 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/service/ServiceProjectAuthResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/service/ServiceProjectAuthResource.kt @@ -29,6 +29,7 @@ package com.tencent.devops.auth.api.service import com.tencent.devops.auth.pojo.vo.ProjectPermissionInfoVO import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_BK_TOKEN +import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_USER_ID import com.tencent.devops.common.api.auth.AUTH_HEADER_GIT_TYPE import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID import com.tencent.devops.common.api.pojo.Result @@ -247,4 +248,13 @@ interface ServiceProjectAuthResource { @Parameter(description = "项目Code", required = true) projectCode: String ): Result + + @GET + @Path("/listUserProjects") + @Operation(summary = "获取用户授权相关的项目") + fun listUserProjects( + @HeaderParam(AUTH_HEADER_DEVOPS_USER_ID) + @Parameter(description = "用户ID", required = true) + userId: String + ): Result> } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthAuthorizationResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthAuthorizationResource.kt index 0f672983e72..fac3082e3aa 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthAuthorizationResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthAuthorizationResource.kt @@ -28,6 +28,7 @@ package com.tencent.devops.auth.api.user +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.auth.pojo.vo.ResourceTypeInfoVo import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID_DEFAULT_VALUE @@ -68,6 +69,9 @@ interface UserAuthAuthorizationResource { @Parameter(description = "项目ID", required = true) @PathParam("projectId") projectId: String, + @Parameter(description = "操作渠道", required = true) + @QueryParam("operateChannel") + operateChannel: OperateChannel?, @Parameter(description = "查询条件", required = true) condition: ResourceAuthorizationConditionRequest ): Result> diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthHandoverResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthHandoverResource.kt new file mode 100644 index 00000000000..e330bc8e175 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthHandoverResource.kt @@ -0,0 +1,100 @@ +package com.tencent.devops.auth.api.user + +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.request.ResourceType2CountOfHandoverQuery +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverOverviewVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID +import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID_DEFAULT_VALUE +import com.tencent.devops.common.api.model.SQLPage +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationHandoverConditionRequest +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.tags.Tag +import javax.ws.rs.Consumes +import javax.ws.rs.HeaderParam +import javax.ws.rs.POST +import javax.ws.rs.Path +import javax.ws.rs.PathParam +import javax.ws.rs.Produces +import javax.ws.rs.core.MediaType + +@Tag(name = "USER_RESOURCE_AUTHORIZATION", description = "用户-权限-交接相关") +@Path("/user/auth/handover/") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +interface UserAuthHandoverResource { + @POST + @Path("/{projectId}/handoverAuthorizationsApplication") + @Operation(summary = "交接授权申请") + fun handoverAuthorizationsApplication( + @Parameter(description = "用户ID", required = true, example = AUTH_HEADER_USER_ID_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "项目ID", required = true) + @PathParam("projectId") + projectId: String, + @Parameter(description = "资源授权交接条件实体", required = true) + condition: ResourceAuthorizationHandoverConditionRequest + ): Result + + @POST + @Path("/listHandoverOverviews") + @Operation(summary = "权限交接总览列表") + fun listHandoverOverviews( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "权限交接总览查询", required = true) + queryRequest: HandoverOverviewQueryReq + ): Result> + + @POST + @Path("/getResourceType2CountOfHandover") + @Operation(summary = "获取资源授权管理数量") + fun getResourceType2CountOfHandover( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "查询请求体", required = true) + queryReq: ResourceType2CountOfHandoverQuery + ): Result> + + @POST + @Path("/listAuthorizationsOfHandover") + @Operation(summary = "获取交接单中授权相关") + fun listAuthorizationsOfHandover( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "权限交接详细查询请求体", required = true) + queryReq: HandoverDetailsQueryReq + ): Result> + + @POST + @Path("/listGroupsOfHandover") + @Operation(summary = "获取交接单中用户组相关") + fun listGroupsOfHandover( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "权限交接详细查询请求体", required = true) + queryReq: HandoverDetailsQueryReq + ): Result> + + @POST + @Path("/handleHanoverApplication") + @Operation(summary = "处理交接审批单") + fun handleHanoverApplication( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "更新权限交接总览请求体", required = true) + request: HandoverOverviewUpdateReq + ): Result +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt index 5dc4c034267..61dca343871 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt @@ -30,6 +30,7 @@ package com.tencent.devops.auth.api.user import com.tencent.devops.auth.pojo.dto.GroupMemberRenewalDTO import com.tencent.devops.auth.pojo.dto.RenameGroupDTO +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo import com.tencent.devops.auth.pojo.vo.IamGroupPoliciesVo import com.tencent.devops.common.api.annotation.BkInterfaceI18n @@ -110,6 +111,9 @@ interface UserAuthResourceGroupResource { @QueryParam("action") @Parameter(description = "操作") action: String?, + @QueryParam("operateChannel") + @Parameter(description = "操作渠道") + operateChannel: OperateChannel?, @Parameter(description = "起始位置,从0开始") @QueryParam("start") start: Int, diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceMemberResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceMemberResource.kt index 26a74ba5b87..955b0761da6 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceMemberResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceMemberResource.kt @@ -2,15 +2,17 @@ package com.tencent.devops.auth.api.user import com.tencent.devops.auth.pojo.ResourceMemberInfo import com.tencent.devops.auth.pojo.enum.BatchOperateType +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRemoveConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo -import com.tencent.devops.auth.pojo.vo.MemberGroupCountWithPermissionsVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.api.pojo.Result @@ -96,8 +98,8 @@ interface UserAuthResourceMemberResource { @PUT @Path("/batch/renewal") - @Operation(summary = "批量续期组成员权限--无需进行审批") - fun batchRenewalGroupMembers( + @Operation(summary = "批量续期组成员权限--管理员视角") + fun batchRenewalGroupMembersFromManager( @Parameter(description = "用户名", required = true) @HeaderParam(AUTH_HEADER_USER_ID) userId: String, @@ -110,8 +112,8 @@ interface UserAuthResourceMemberResource { @DELETE @Path("/batch/remove") - @Operation(summary = "批量移除用户组成员") - fun batchRemoveGroupMembers( + @Operation(summary = "批量移除用户组成员--管理员视角") + fun batchRemoveGroupMembersFromManager( @Parameter(description = "用户名", required = true) @HeaderParam(AUTH_HEADER_USER_ID) userId: String, @@ -119,13 +121,41 @@ interface UserAuthResourceMemberResource { @PathParam("projectId") projectId: String, @Parameter(description = "批量移除成员请求实体") - removeMemberDTO: GroupMemberCommonConditionReq + removeMemberDTO: GroupMemberRemoveConditionReq + ): Result + + @DELETE + @Path("/batch/personal/remove") + @Operation(summary = "批量退出用户组成员--个人视角") + fun batchRemoveGroupMembersFromPersonal( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "项目ID", required = true) + @PathParam("projectId") + projectId: String, + @Parameter(description = "批量移除成员请求实体") + removeMemberDTO: GroupMemberRemoveConditionReq ): Result @PUT @Path("/batch/handover") - @Operation(summary = "批量交接用户组成员") - fun batchHandoverGroupMembers( + @Operation(summary = "批量交接用户组成员--管理员视角") + fun batchHandoverGroupMembersFromManager( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "项目ID", required = true) + @PathParam("projectId") + projectId: String, + @Parameter(description = "批量交接成员请求实体") + handoverMemberDTO: GroupMemberHandoverConditionReq + ): Result + + @PUT + @Path("/batch/personal/handover") + @Operation(summary = "批量交接用户组成员--个人视角") + fun batchHandoverApplicationFromPersonal( @Parameter(description = "用户名", required = true) @HeaderParam(AUTH_HEADER_USER_ID) userId: String, @@ -211,6 +241,9 @@ interface UserAuthResourceMemberResource { relatedResourceCode: String?, @QueryParam("action") @Parameter(description = "操作") - action: String? - ): Result> + action: String?, + @QueryParam("operateChannel") + @Parameter(description = "操作渠道") + operateChannel: OperateChannel? + ): Result> } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthI18nConstants.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthI18nConstants.kt index 4be2b229dfc..eca12bd7ec2 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthI18nConstants.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthI18nConstants.kt @@ -47,4 +47,8 @@ object AuthI18nConstants { const val BK_MEMBER_EXPIRED_AT_DISPLAY_EXPIRED = "bkMemberExpiredAtDisplayExpired" // 有效期: 已过期 const val BK_MEMBER_EXPIRED_AT_DISPLAY_NORMAL = "bkMemberExpiredAtDisplayNormal" // 有效期: {0}天 const val BK_MEMBER_EXPIRED_AT_DISPLAY_PERMANENT = "bkMemberExpiredAtDisplayPermanent" // 有效期: 永久 + + const val BK_APPLY_TO_HANDOVER = "bkApplyToHandover" // 申请移交 + const val BK_HANDOVER_GROUPS = "bkHandoverGroups" // {0}个权限用户组 + const val BK_HANDOVER_AUTHORIZATIONS = "bkHandoverAuthorizations" // {0}个授权 } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthMessageCode.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthMessageCode.kt index 4b66ce68fbb..6d83a6ebe1c 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthMessageCode.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthMessageCode.kt @@ -142,4 +142,11 @@ object AuthMessageCode { const val INVALID_EXPIRED_PERM_NOT_ALLOW_TO_HANDOVER = "2121089" // 已过期的权限不允许交接 const val ERROR_USER_INFORMATION_NOT_SYNCED = "2121090" // 请等待第二天用户信息同步后再尝试操作,因为新入职用户的信息尚未同步完成。 + + const val ERROR_HANDOVER_OVERVIEW_NOT_EXIST = "2121091" // 权限交接记录不存在 + const val ERROR_HANDOVER_FINISH = "2121092" // 该交接申请单已被处理,不允许重复操作 + const val ERROR_HANDOVER_REVOKE = "2121093" // 由于您不是该交接申请单的发起人,无法进行撤销操作 + const val ERROR_HANDOVER_APPROVAL = "2121094" // 由于您不是该交接申请单的审批人,无法进行任何操作 + const val ERROR_HANDOVER_HANDLE = "2121095" // 该交接申请单正在被处理中,请耐心等待 + const val ERROR_HANDOVER_AUTHORIZATION = "2121096" // 交接操作不合法,用户没有对应授权的权限 } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/HandoverDetailDTO.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/HandoverDetailDTO.kt new file mode 100644 index 00000000000..84682f8debc --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/HandoverDetailDTO.kt @@ -0,0 +1,20 @@ +package com.tencent.devops.auth.pojo.dto + +import com.tencent.devops.auth.pojo.enum.HandoverType +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "权限交接详细表") +data class HandoverDetailDTO( + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "流程单号") + val flowNo: String, + @get:Schema(title = "授权/组ID") + val itemId: String, + @get:Schema(title = "组/授权资源关联的资源类型") + val resourceType: String, + @get:Schema(title = "交接类型") + val handoverType: HandoverType, + @get:Schema(title = "审批人") + var approver: String? = null +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/HandoverOverviewCreateDTO.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/HandoverOverviewCreateDTO.kt new file mode 100644 index 00000000000..689a9c5625c --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/HandoverOverviewCreateDTO.kt @@ -0,0 +1,26 @@ +package com.tencent.devops.auth.pojo.dto + +import com.tencent.devops.auth.pojo.enum.HandoverStatus +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "创建权限交接总览DTO") +data class HandoverOverviewCreateDTO( + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "项目ID") + var title: String, + @get:Schema(title = "流程单号") + var flowNo: String, + @get:Schema(title = "申请人") + val applicant: String, + @get:Schema(title = "审批人") + val approver: String, + @get:Schema(title = "审批结果") + val handoverStatus: HandoverStatus, + @get:Schema(title = "用户组个数") + val groupCount: Int, + @get:Schema(title = "授权个数") + val authorizationCount: Int, + @get:Schema(title = "备注") + val remark: String? = null +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/InvalidAuthorizationsDTO.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/InvalidAuthorizationsDTO.kt new file mode 100644 index 00000000000..097096aad72 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/InvalidAuthorizationsDTO.kt @@ -0,0 +1,13 @@ +package com.tencent.devops.auth.pojo.dto + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "移除/移交用户组成员导致的无效授权") +data class InvalidAuthorizationsDTO( + @get:Schema(title = "引起代持人权限失效的用户组") + val invalidGroupIds: List, + @get:Schema(title = "引起代持人权限失效的流水线") + val invalidPipelineIds: List, + @get:Schema(title = "引起oauth失效的代码库") + val invalidRepertoryIds: List = emptyList() +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverAction.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverAction.kt new file mode 100644 index 00000000000..7f048434a85 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverAction.kt @@ -0,0 +1,21 @@ +package com.tencent.devops.auth.pojo.enum + +enum class HandoverAction(val value: Int) { + // 审批成功 + AGREE(1), + + // 审批驳回 + REJECT(2), + + // 撤销 + REVOKE(3); + + companion object { + fun get(value: Int): HandoverAction { + HandoverAction.values().forEach { + if (value == it.value) return it + } + throw IllegalArgumentException("No enum for constant $value") + } + } +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverQueryChannel.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverQueryChannel.kt new file mode 100644 index 00000000000..352a6c6bbe9 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverQueryChannel.kt @@ -0,0 +1,34 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.devops.auth.pojo.enum + +enum class HandoverQueryChannel { + PREVIEW, + + HANDOVER_APPLICATION +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverStatus.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverStatus.kt new file mode 100644 index 00000000000..aaf82e6e9ac --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverStatus.kt @@ -0,0 +1,24 @@ +package com.tencent.devops.auth.pojo.enum + +enum class HandoverStatus(val value: Int) { + // 审批中 + PENDING(0), + + // 审批成功 + SUCCEED(1), + + // 审批驳回 + REJECT(2), + + // 撤销 + REVOKE(3); + + companion object { + fun get(value: Int): HandoverStatus { + HandoverStatus.values().forEach { + if (value == it.value) return it + } + throw IllegalArgumentException("No enum for constant $value") + } + } +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverType.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverType.kt new file mode 100644 index 00000000000..37f5267630e --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverType.kt @@ -0,0 +1,18 @@ +package com.tencent.devops.auth.pojo.enum + +enum class HandoverType(val value: String) { + // 用户组 + GROUP("group"), + + // 授权 + AUTHORIZATION("authorization"); + + companion object { + fun get(value: String): HandoverType { + HandoverType.values().forEach { + if (value == it.value) return it + } + throw IllegalArgumentException("No enum for constant $value") + } + } +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/JoinedType.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/JoinedType.kt index 06b700c88bd..8bff11d4870 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/JoinedType.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/JoinedType.kt @@ -32,5 +32,8 @@ enum class JoinedType { DIRECT, // 通过模板加入 - TEMPLATE + TEMPLATE, + + // 通过组织加入 + DEPARTMENT } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/OperateChannel.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/OperateChannel.kt new file mode 100644 index 00000000000..5c08753de64 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/OperateChannel.kt @@ -0,0 +1,37 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +package com.tencent.devops.auth.pojo.enum + +enum class OperateChannel(val value: String) { + // 个人视角 + PERSONAL("personal"), + + // 管理员视角 + MANAGER("manager"); +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/RemoveMemberButtonControl.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/RemoveMemberButtonControl.kt index fc06012edd1..55064743eb0 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/RemoveMemberButtonControl.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/RemoveMemberButtonControl.kt @@ -37,6 +37,9 @@ enum class RemoveMemberButtonControl { // 通过模板加入,不允许移出组 TEMPLATE, + // 用户通过组织 间接加入,不允许移出组 + DEPARTMENT, + // 其他,允许移出组 OTHER } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberCommonConditionReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberCommonConditionReq.kt index db943b94c74..2a82af2f08c 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberCommonConditionReq.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberCommonConditionReq.kt @@ -1,6 +1,7 @@ package com.tencent.devops.auth.pojo.request import com.tencent.devops.auth.pojo.ResourceMemberInfo +import com.tencent.devops.auth.pojo.enum.OperateChannel import io.swagger.v3.oas.annotations.media.Schema @Schema(title = "用户组成员处理公共请求体") @@ -11,14 +12,13 @@ open class GroupMemberCommonConditionReq( open val resourceTypes: List = emptyList(), @get:Schema(title = "全量选择") open val allSelection: Boolean = false, - @get:Schema(title = "是否排除唯一管理员组") - open var excludedUniqueManagerGroup: Boolean = false, @get:Schema(title = "目标对象") - open val targetMember: ResourceMemberInfo + open val targetMember: ResourceMemberInfo, + @get:Schema(title = "操作渠道") + open val operateChannel: OperateChannel = OperateChannel.MANAGER ) { override fun toString(): String { return "GroupMemberCommonConditionReq(groupIds=$groupIds,resourceTypes=$resourceTypes," + - "allSelection=$allSelection,excludedUniqueManagerGroup=$excludedUniqueManagerGroup," + - "targetMember=$targetMember)" + "allSelection=$allSelection,targetMember=$targetMember,operateChannel=$operateChannel)" } } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberHandoverConditionReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberHandoverConditionReq.kt index dc0f85b0daa..7e32532c48e 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberHandoverConditionReq.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberHandoverConditionReq.kt @@ -29,6 +29,7 @@ package com.tencent.devops.auth.pojo.request import com.tencent.devops.auth.constant.AuthMessageCode.INVALID_HANDOVER_TO import com.tencent.devops.auth.pojo.ResourceMemberInfo +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.common.api.exception.ErrorCodeException import io.swagger.v3.oas.annotations.media.Schema @@ -40,17 +41,17 @@ data class GroupMemberHandoverConditionReq( override val resourceTypes: List = emptyList(), @get:Schema(title = "全量选择") override val allSelection: Boolean = false, - @get:Schema(title = "是否排除唯一管理员组") - override var excludedUniqueManagerGroup: Boolean = false, @get:Schema(title = "目标对象") override val targetMember: ResourceMemberInfo, + @get:Schema(title = "操作渠道") + override val operateChannel: OperateChannel = OperateChannel.MANAGER, @get:Schema(title = "授予人") val handoverTo: ResourceMemberInfo ) : GroupMemberCommonConditionReq( groupIds = groupIds, resourceTypes = resourceTypes, allSelection = allSelection, - excludedUniqueManagerGroup = excludedUniqueManagerGroup, + operateChannel = operateChannel, targetMember = targetMember ) { fun checkHandoverTo() { diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberRemoveConditionReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberRemoveConditionReq.kt new file mode 100644 index 00000000000..1c4e68da1e6 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberRemoveConditionReq.kt @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.devops.auth.pojo.request + +import com.tencent.devops.auth.constant.AuthMessageCode.INVALID_HANDOVER_TO +import com.tencent.devops.auth.pojo.ResourceMemberInfo +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.common.api.exception.ErrorCodeException +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "用户组成员移除条件请求体") +data class GroupMemberRemoveConditionReq( + @get:Schema(title = "组IDs") + override val groupIds: List = emptyList(), + @get:Schema(title = "全选的资源类型") + override val resourceTypes: List = emptyList(), + @get:Schema(title = "全量选择") + override val allSelection: Boolean = false, + @get:Schema(title = "目标对象") + override val targetMember: ResourceMemberInfo, + @get:Schema(title = "操作渠道") + override val operateChannel: OperateChannel = OperateChannel.MANAGER, + @get:Schema(title = "授予人") + val handoverTo: ResourceMemberInfo? = null +) : GroupMemberCommonConditionReq( + groupIds = groupIds, + resourceTypes = resourceTypes, + allSelection = allSelection, + operateChannel = operateChannel, + targetMember = targetMember +) { + fun checkHandoverTo() { + if (handoverTo == null || handoverTo.id == targetMember.id) { + throw ErrorCodeException( + errorCode = INVALID_HANDOVER_TO + ) + } + } +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberRenewalConditionReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberRenewalConditionReq.kt index b8b6ce8598c..6dc53591ffa 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberRenewalConditionReq.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberRenewalConditionReq.kt @@ -28,6 +28,7 @@ package com.tencent.devops.auth.pojo.request import com.tencent.devops.auth.pojo.ResourceMemberInfo +import com.tencent.devops.auth.pojo.enum.OperateChannel import io.swagger.v3.oas.annotations.media.Schema @Schema(title = "用户组成员续期") @@ -38,16 +39,16 @@ data class GroupMemberRenewalConditionReq( override val resourceTypes: List = emptyList(), @get:Schema(title = "全量选择") override val allSelection: Boolean = false, - @get:Schema(title = "是否排除唯一管理员组") - override var excludedUniqueManagerGroup: Boolean = false, @get:Schema(title = "目标对象") override val targetMember: ResourceMemberInfo, + @get:Schema(title = "操作渠道") + override val operateChannel: OperateChannel = OperateChannel.MANAGER, @get:Schema(title = "续期时长(天)") val renewalDuration: Int ) : GroupMemberCommonConditionReq( groupIds = groupIds, resourceTypes = resourceTypes, allSelection = allSelection, - excludedUniqueManagerGroup = excludedUniqueManagerGroup, + operateChannel = operateChannel, targetMember = targetMember ) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverDetailsQueryReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverDetailsQueryReq.kt new file mode 100644 index 00000000000..81065ed94d4 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverDetailsQueryReq.kt @@ -0,0 +1,38 @@ +package com.tencent.devops.auth.pojo.request + +import com.tencent.devops.auth.pojo.enum.HandoverQueryChannel +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "权限交接详细查询请求体") +data class HandoverDetailsQueryReq( + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "组/授权资源关联的资源类型") + val resourceType: String, + @get:Schema(title = "流程单号") + val flowNo: String?, + @get:Schema(title = "交接预览请求条件") + val previewConditionReq: GroupMemberCommonConditionReq?, + @get:Schema(title = "渠道") + val queryChannel: HandoverQueryChannel, + @get:Schema(title = "第几页") + val page: Int, + @get:Schema(title = "每页大小") + val pageSize: Int +) { + fun check() { + when (queryChannel) { + HandoverQueryChannel.HANDOVER_APPLICATION -> { + if (flowNo == null) { + throw IllegalArgumentException("flowNo cannot be null!") + } + } + + else -> { + if (previewConditionReq == null) { + throw IllegalArgumentException("previewConditionReq can not be null!") + } + } + } + } +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverOverviewQueryReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverOverviewQueryReq.kt new file mode 100644 index 00000000000..7d4b8aa88ad --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverOverviewQueryReq.kt @@ -0,0 +1,30 @@ +package com.tencent.devops.auth.pojo.request + +import com.tencent.devops.auth.pojo.enum.HandoverStatus +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "权限交接总览查询") +data class HandoverOverviewQueryReq( + @get:Schema(title = "成员ID") + val memberId: String, + @get:Schema(title = "项目ID") + val projectCode: String? = null, + @get:Schema(title = "项目ID") + val title: String? = null, + @get:Schema(title = "流程单号") + val flowNo: String? = null, + @get:Schema(title = "申请人") + val applicant: String? = null, + @get:Schema(title = "审批人") + val approver: String? = null, + @get:Schema(title = "审批结果") + val handoverStatus: HandoverStatus? = null, + @get:Schema(title = "最小提单时间") + val minCreatedTime: Long? = null, + @get:Schema(title = "最打提单时间") + val maxCreatedTime: Long? = null, + @get:Schema(title = "页数") + val page: Int? = null, + @get:Schema(title = "页大小") + val pageSize: Int? = null +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverOverviewUpdateReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverOverviewUpdateReq.kt new file mode 100644 index 00000000000..cd0c27b2b21 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverOverviewUpdateReq.kt @@ -0,0 +1,18 @@ +package com.tencent.devops.auth.pojo.request + +import com.tencent.devops.auth.pojo.enum.HandoverAction +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "更新权限交接总览请求体") +data class HandoverOverviewUpdateReq( + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "流程单号") + val flowNo: String, + @get:Schema(title = "操作人") + val operator: String, + @get:Schema(title = "审批操作") + val handoverAction: HandoverAction, + @get:Schema(title = "备注") + val remark: String? = null +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/ResourceType2CountOfHandoverQuery.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/ResourceType2CountOfHandoverQuery.kt new file mode 100644 index 00000000000..a3c0cd1c298 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/ResourceType2CountOfHandoverQuery.kt @@ -0,0 +1,34 @@ +package com.tencent.devops.auth.pojo.request + +import com.tencent.devops.auth.pojo.enum.BatchOperateType +import com.tencent.devops.auth.pojo.enum.HandoverQueryChannel +import io.swagger.v3.oas.annotations.media.Schema + +data class ResourceType2CountOfHandoverQuery( + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "渠道") + val queryChannel: HandoverQueryChannel, + @get:Schema(title = "流程单号") + val flowNo: String?, + @get:Schema(title = "交接预览请求条件") + val previewConditionReq: GroupMemberCommonConditionReq?, + @get:Schema(title = "批量操作动作") + val batchOperateType: BatchOperateType? +) { + fun check() { + when (queryChannel) { + HandoverQueryChannel.HANDOVER_APPLICATION -> { + if (flowNo == null) { + throw IllegalArgumentException("flowNo cannot be null!") + } + } + + else -> { + if (previewConditionReq == null || batchOperateType == null) { + throw IllegalArgumentException("previewConditionReq or batchOperateType can not be null!") + } + } + } + } +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/BatchOperateGroupMemberCheckVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/BatchOperateGroupMemberCheckVo.kt index 501eb12299c..baa89c70154 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/BatchOperateGroupMemberCheckVo.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/BatchOperateGroupMemberCheckVo.kt @@ -6,6 +6,16 @@ import io.swagger.v3.oas.annotations.media.Schema data class BatchOperateGroupMemberCheckVo( @get:Schema(title = "总数") val totalCount: Int, + @get:Schema(title = "可操作的数量") + val operableCount: Int? = 0, @get:Schema(title = "无法操作的数量") - val inoperableCount: Int? = null + val inoperableCount: Int? = 0, + @get:Schema(title = "唯一管理员组的数量") + val uniqueManagerCount: Int? = 0, + @get:Schema(title = "导致代持人失效的用户组") + val invalidGroupCount: Int? = 0, + @get:Schema(title = "无效的流水线授权数量") + val invalidPipelineAuthorizationCount: Int? = 0, + @get:Schema(title = "无效的代码库授权数量") + val invalidRepositoryAuthorizationCount: Int? = 0 ) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/GroupDetailsInfoVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/GroupDetailsInfoVo.kt index 6193dda5c6e..0e6f8bd256f 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/GroupDetailsInfoVo.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/GroupDetailsInfoVo.kt @@ -29,5 +29,7 @@ data class GroupDetailsInfoVo( @get:Schema(title = "加入方式") val joinedType: JoinedType, @get:Schema(title = "操作人") - val operator: String + val operator: String, + @get:Schema(title = "是否正在交接") + val beingHandedOver: Boolean? = null ) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverAuthorizationDetailVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverAuthorizationDetailVo.kt new file mode 100644 index 00000000000..0828981a8d4 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverAuthorizationDetailVo.kt @@ -0,0 +1,16 @@ +package com.tencent.devops.auth.pojo.vo + +import com.tencent.devops.auth.pojo.enum.HandoverType +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "授权交接详细表") +data class HandoverAuthorizationDetailVo( + @get:Schema(title = "授权资源ID") + val resourceCode: String, + @get:Schema(title = "授权资源名称") + val resourceName: String, + @get:Schema(title = "交接类型") + val handoverType: HandoverType, + @get:Schema(title = "授权人") + val handoverFrom: String +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverGroupDetailVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverGroupDetailVo.kt new file mode 100644 index 00000000000..6a6e3e40e82 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverGroupDetailVo.kt @@ -0,0 +1,19 @@ +package com.tencent.devops.auth.pojo.vo + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "用户组交接详细返回体") +data class HandoverGroupDetailVo( + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "组ID") + val iamGroupId: Int, + @get:Schema(title = "组名称") + val groupName: String, + @get:Schema(title = "组描述") + val groupDesc: String? = null, + @get:Schema(title = "关联的资源ID") + val resourceCode: String, + @get:Schema(title = "关联的资源名称") + val resourceName: String +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverOverviewVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverOverviewVo.kt new file mode 100644 index 00000000000..9c44a521394 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverOverviewVo.kt @@ -0,0 +1,34 @@ +package com.tencent.devops.auth.pojo.vo + +import com.tencent.devops.auth.pojo.enum.HandoverStatus +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "权限交接总览返回体") +data class HandoverOverviewVo( + @get:Schema(title = "ID") + val id: Long, + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "标题") + val title: String, + @get:Schema(title = "流程单号") + val flowNo: String, + @get:Schema(title = "申请人") + val applicant: String, + @get:Schema(title = "审批人") + val approver: String, + @get:Schema(title = "审批结果") + val handoverStatus: HandoverStatus, + @get:Schema(title = "用户组个数") + val groupCount: Int, + @get:Schema(title = "授权个数") + val authorizationCount: Int, + @get:Schema(title = "最后修改人") + val lastOperator: String? = null, + @get:Schema(title = "是否可以撤销,提单为当前用户并且单据处于审批中") + val canRevoke: Boolean? = null, + @get:Schema(title = "是否可以审批,审批人为当前用户并且单据处于审批中") + val canApproval: Boolean? = null, + @get:Schema(title = "备注") + val remark: String? = null +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/MemberGroupCountWithPermissionsVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/ResourceType2CountVo.kt similarity index 63% rename from src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/MemberGroupCountWithPermissionsVo.kt rename to src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/ResourceType2CountVo.kt index 293d3646046..49d6a24ff37 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/MemberGroupCountWithPermissionsVo.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/ResourceType2CountVo.kt @@ -1,13 +1,16 @@ package com.tencent.devops.auth.pojo.vo +import com.tencent.devops.auth.pojo.enum.HandoverType import io.swagger.v3.oas.annotations.media.Schema @Schema(title = "用户有权限的用户组数量") -data class MemberGroupCountWithPermissionsVo( +data class ResourceType2CountVo( @get:Schema(title = "资源类型") val resourceType: String, @get:Schema(title = "资源类型名") val resourceTypeName: String, @get:Schema(title = "数量") - val count: Long + val count: Long, + @get:Schema(title = "类型") + val type: HandoverType = HandoverType.GROUP ) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthAuthorizationDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthAuthorizationDao.kt index dca6f34e966..f633eaf0f19 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthAuthorizationDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthAuthorizationDao.kt @@ -191,6 +191,12 @@ class AuthAuthorizationDao { if (resourceName != null) { conditions.add(RESOURCE_NAME.like("%$resourceName%")) } + if (!filterResourceCodes.isNullOrEmpty()) { + conditions.add(RESOURCE_CODE.`in`(filterResourceCodes)) + } + if (!excludeResourceCodes.isNullOrEmpty()) { + conditions.add(RESOURCE_CODE.notIn(excludeResourceCodes)) + } if (handoverFrom != null) { conditions.add(HANDOVER_FROM.eq(handoverFrom)) } @@ -202,8 +208,22 @@ class AuthAuthorizationDao { return conditions } + fun listUserProjects( + dslContext: DSLContext, + userId: String + ): List { + return with(TAuthResourceAuthorization.T_AUTH_RESOURCE_AUTHORIZATION) { + dslContext.select(PROJECT_CODE) + .from(this) + .where(HANDOVER_FROM.eq(userId)) + .groupBy(PROJECT_CODE) + .fetch().map { it.value1() } + } + } + fun TAuthResourceAuthorizationRecord.convert(): ResourceAuthorizationResponse { return ResourceAuthorizationResponse( + id = id, projectCode = projectCode, resourceType = resourceType, resourceName = resourceName, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthHandoverDetailDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthHandoverDetailDao.kt new file mode 100644 index 00000000000..73a5ac000dc --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthHandoverDetailDao.kt @@ -0,0 +1,128 @@ +package com.tencent.devops.auth.dao + +import com.tencent.devops.auth.pojo.dto.HandoverDetailDTO +import com.tencent.devops.auth.pojo.enum.HandoverType +import com.tencent.devops.model.auth.tables.TAuthHandoverDetail +import com.tencent.devops.model.auth.tables.records.TAuthHandoverDetailRecord +import org.jooq.Condition +import org.jooq.DSLContext +import org.jooq.impl.DSL.count +import org.springframework.stereotype.Repository + +@Repository +class AuthHandoverDetailDao { + fun batchCreate( + dslContext: DSLContext, + handoverDetailDTOs: List + ) { + with(TAuthHandoverDetail.T_AUTH_HANDOVER_DETAIL) { + handoverDetailDTOs.forEach { + dslContext.insertInto( + this, + PROJECT_CODE, + FLOW_NO, + ITEM_ID, + RESOURCE_TYPE, + HANDOVER_TYPE + ).values( + it.projectCode, + it.flowNo, + it.itemId, + it.resourceType, + it.handoverType.value + ).execute() + } + } + } + + fun list( + dslContext: DSLContext, + projectCode: String, + flowNos: List, + resourceType: String?, + handoverType: HandoverType? + ): List { + with(TAuthHandoverDetail.T_AUTH_HANDOVER_DETAIL) { + return dslContext.selectFrom(this) + .where( + buildQueryConditions( + projectCode = projectCode, + flowNos = flowNos, + resourceType = resourceType, + handoverType = handoverType + ) + ).fetch().map { it.convert() } + } + } + + fun count( + dslContext: DSLContext, + projectCode: String, + flowNos: List, + resourceType: String?, + handoverType: HandoverType? + ): Long { + with(TAuthHandoverDetail.T_AUTH_HANDOVER_DETAIL) { + return dslContext.selectCount().from(this) + .where( + buildQueryConditions( + projectCode = projectCode, + flowNos = flowNos, + resourceType = resourceType, + handoverType = handoverType + ) + ).fetchOne(0, Long::class.java)!! + } + } + + fun countWithResourceType( + dslContext: DSLContext, + projectCode: String, + flowNo: String, + handoverType: HandoverType? + ): Map { + with(TAuthHandoverDetail.T_AUTH_HANDOVER_DETAIL) { + return dslContext.select(RESOURCE_TYPE, count()) + .from(this) + .where( + buildQueryConditions( + projectCode = projectCode, + flowNos = listOf(flowNo), + resourceType = null, + handoverType = handoverType + ) + ).groupBy(RESOURCE_TYPE) + .fetch().map { Pair(it.value1(), it.value2().toLong()) }.toMap() + } + } + + private fun buildQueryConditions( + projectCode: String, + flowNos: List, + resourceType: String?, + handoverType: HandoverType? + ): List { + with(TAuthHandoverDetail.T_AUTH_HANDOVER_DETAIL) { + val conditions = mutableListOf() + conditions.add(PROJECT_CODE.eq(projectCode)) + conditions.add(FLOW_NO.`in`(flowNos)) + resourceType?.let { + conditions.add(RESOURCE_TYPE.eq(resourceType)) + } + handoverType?.let { + conditions.add(HANDOVER_TYPE.eq(handoverType.value)) + } + return conditions + } + } + + private fun TAuthHandoverDetailRecord.convert(): HandoverDetailDTO { + return HandoverDetailDTO( + projectCode = projectCode, + flowNo = flowNo, + itemId = itemId, + resourceType = resourceType, + handoverType = HandoverType.get(handoverType) + ) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthHandoverOverviewDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthHandoverOverviewDao.kt new file mode 100644 index 00000000000..d9ae83250ee --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthHandoverOverviewDao.kt @@ -0,0 +1,140 @@ +package com.tencent.devops.auth.dao + +import com.tencent.devops.auth.pojo.dto.HandoverOverviewCreateDTO +import com.tencent.devops.auth.pojo.enum.HandoverStatus +import com.tencent.devops.auth.pojo.request.HandoverOverviewQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.vo.HandoverOverviewVo +import com.tencent.devops.common.api.util.DateTimeUtil +import com.tencent.devops.common.api.util.PageUtil +import com.tencent.devops.model.auth.tables.TAuthHandoverOverview +import com.tencent.devops.model.auth.tables.records.TAuthHandoverOverviewRecord +import org.jooq.Condition +import org.jooq.DSLContext +import org.springframework.stereotype.Repository +import java.time.LocalDateTime + +@Repository +class AuthHandoverOverviewDao { + fun create( + dslContext: DSLContext, + overviewDTO: HandoverOverviewCreateDTO + ) { + with(TAuthHandoverOverview.T_AUTH_HANDOVER_OVERVIEW) { + dslContext.insertInto( + this, + PROJECT_CODE, + TITLE, + FLOW_NO, + APPLICANT, + APPROVER, + STATUS, + GROUP_COUNT, + AUTHORIZATION_COUNT, + REMARK + ).values( + overviewDTO.projectCode, + overviewDTO.title, + overviewDTO.flowNo, + overviewDTO.applicant, + overviewDTO.approver, + overviewDTO.handoverStatus.value, + overviewDTO.groupCount, + overviewDTO.authorizationCount, + overviewDTO.remark + ).execute() + } + } + + fun update( + dslContext: DSLContext, + overviewDTO: HandoverOverviewUpdateReq + ) { + with(TAuthHandoverOverview.T_AUTH_HANDOVER_OVERVIEW) { + dslContext.update(this) + .set(STATUS, overviewDTO.handoverAction.value) + .let { if (overviewDTO.remark != null) it.set(REMARK, overviewDTO.remark) else it } + .set(UPDATE_TIME, LocalDateTime.now()) + .set(LAST_OPERATOR, overviewDTO.operator) + .where(FLOW_NO.eq(overviewDTO.flowNo)) + .and(PROJECT_CODE.eq(overviewDTO.projectCode)) + .execute() + } + } + + fun get( + dslContext: DSLContext, + flowNo: String + ): HandoverOverviewVo? { + return with(TAuthHandoverOverview.T_AUTH_HANDOVER_OVERVIEW) { + dslContext.selectFrom(this) + .where(FLOW_NO.eq(flowNo)) + .fetchAny()?.convert() + } + } + + fun list( + dslContext: DSLContext, + queryRequest: HandoverOverviewQueryReq + ): List { + return with(TAuthHandoverOverview.T_AUTH_HANDOVER_OVERVIEW) { + dslContext.selectFrom(this) + .where(buildQueryConditions(queryRequest)) + .let { + if (queryRequest.page != null && queryRequest.pageSize != null) { + val sqlLimit = PageUtil.convertPageSizeToSQLLimit(queryRequest.page, queryRequest.pageSize) + it.limit(sqlLimit.limit).offset(sqlLimit.offset) + } else { + it + } + }.fetch().map { it.convert(queryRequest.memberId) } + } + } + + fun count( + dslContext: DSLContext, + queryRequest: HandoverOverviewQueryReq + ): Long { + return with(TAuthHandoverOverview.T_AUTH_HANDOVER_OVERVIEW) { + dslContext.selectCount().from(this) + .where(buildQueryConditions(queryRequest)) + .fetchOne(0, Long::class.java)!! + } + } + + private fun buildQueryConditions( + queryRequest: HandoverOverviewQueryReq + ): List { + with(TAuthHandoverOverview.T_AUTH_HANDOVER_OVERVIEW) { + val conditions = mutableListOf() + conditions.add(APPROVER.eq(queryRequest.memberId).or(APPLICANT.eq(queryRequest.memberId))) + queryRequest.projectCode?.let { conditions.add(PROJECT_CODE.eq(queryRequest.projectCode)) } + queryRequest.title?.let { conditions.add(TITLE.like("%${queryRequest.title}%")) } + queryRequest.flowNo?.let { conditions.add(FLOW_NO.eq(queryRequest.flowNo)) } + queryRequest.applicant?.let { conditions.add(APPLICANT.like("%${queryRequest.applicant}%")) } + queryRequest.approver?.let { conditions.add(APPROVER.like("%${queryRequest.approver}%")) } + queryRequest.handoverStatus?.let { conditions.add(STATUS.eq(queryRequest.handoverStatus!!.value)) } + queryRequest.minCreatedTime?.let { conditions.add(CREATE_TIME.ge(DateTimeUtil.convertTimestampToLocalDateTime(it / 1000))) } + queryRequest.maxCreatedTime?.let { conditions.add(CREATE_TIME.le(DateTimeUtil.convertTimestampToLocalDateTime(it / 1000))) } + return conditions + } + } + + fun TAuthHandoverOverviewRecord.convert(memberId: String? = null): HandoverOverviewVo { + return HandoverOverviewVo( + id = id, + projectCode = projectCode, + title = title, + flowNo = flowNo, + applicant = applicant, + approver = approver, + handoverStatus = HandoverStatus.get(status), + groupCount = groupCount, + authorizationCount = authorizationCount, + lastOperator = lastOperator, + canRevoke = memberId?.let { memberId == applicant && status == HandoverStatus.PENDING.value }, + canApproval = memberId?.let { memberId == approver && status == HandoverStatus.PENDING.value }, + remark = remark + ) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupConfigDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupConfigDao.kt index 73a0268d9c8..24e2d69f519 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupConfigDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupConfigDao.kt @@ -28,6 +28,7 @@ package com.tencent.devops.auth.dao +import com.tencent.devops.common.db.utils.skipCheck import com.tencent.devops.model.auth.tables.TAuthResourceGroupConfig import com.tencent.devops.model.auth.tables.records.TAuthResourceGroupConfigRecord import org.jooq.DSLContext @@ -110,6 +111,7 @@ class AuthResourceGroupConfigDao { dslContext.selectFrom(this) .orderBy(CREATE_TIME.desc(), RESOURCE_TYPE, GROUP_CODE) .limit(pageSize).offset((page - 1) * pageSize) + .skipCheck() .fetch() } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt index e18db590fd8..f4a0c58ccbc 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt @@ -30,6 +30,7 @@ package com.tencent.devops.auth.dao import com.tencent.devops.auth.pojo.AuthResourceGroup import com.tencent.devops.common.auth.api.AuthResourceType +import org.jooq.impl.DSL.count import com.tencent.devops.model.auth.tables.TAuthResourceGroup import com.tencent.devops.model.auth.tables.records.TAuthResourceGroupRecord import org.jooq.DSLContext @@ -204,6 +205,21 @@ class AuthResourceGroupDao { } } + fun getResourceType2Count( + dslContext: DSLContext, + projectCode: String, + iamGroupIds: List + ): Map { + return with(TAuthResourceGroup.T_AUTH_RESOURCE_GROUP) { + dslContext.select(RESOURCE_TYPE, count()) + .from(this) + .where(PROJECT_CODE.eq(projectCode)) + .and(RELATION_ID.`in`(iamGroupIds)) + .groupBy(RESOURCE_TYPE) + .fetch().map { Pair(it.value1(), it.value2().toLong()) }.toMap() + } + } + fun delete( dslContext: DSLContext, projectCode: String, @@ -368,6 +384,7 @@ class AuthResourceGroupDao { dslContext: DSLContext, projectCode: String, resourceType: String, + iamGroupIds: List? = null, offset: Int, limit: Int ): List { @@ -375,6 +392,14 @@ class AuthResourceGroupDao { val records = dslContext.selectFrom(this) .where(PROJECT_CODE.eq(projectCode)) .and(RESOURCE_TYPE.eq(resourceType)) + .let { + if (!iamGroupIds.isNullOrEmpty()) { + it.and(RELATION_ID.`in`(iamGroupIds)) + } else { + it + } + } + .orderBy(CREATE_TIME) .offset(offset) .limit(limit) .fetch() diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupMemberDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupMemberDao.kt index 5a016760aaf..05e4ef12e03 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupMemberDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupMemberDao.kt @@ -27,11 +27,12 @@ package com.tencent.devops.auth.dao -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.devops.auth.pojo.AuthResourceGroupMember import com.tencent.devops.auth.pojo.ResourceMemberInfo import com.tencent.devops.auth.pojo.dto.ProjectMembersQueryConditionDTO +import com.tencent.devops.auth.pojo.enum.MemberType import com.tencent.devops.common.auth.api.pojo.BkAuthGroup +import com.tencent.devops.common.db.utils.skipCheck import com.tencent.devops.model.auth.tables.TAuthResourceAuthorization import com.tencent.devops.model.auth.tables.TAuthResourceGroupMember import com.tencent.devops.model.auth.tables.records.TAuthResourceGroupMemberRecord @@ -368,6 +369,7 @@ class AuthResourceGroupMemberDao { ) .orderBy(field(MEMBER_ID)) .offset(offset).limit(limit) + .skipCheck() .fetch().map { ResourceMemberInfo(id = it.value1(), name = it.value2(), type = it.value3()) } @@ -418,17 +420,17 @@ class AuthResourceGroupMemberDao { with(projectMembersQueryConditionDTO) { conditions.add(PROJECT_CODE.eq(projectCode)) if (queryTemplate == false) { - conditions.add(MEMBER_TYPE.notEqual(ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE))) + conditions.add(MEMBER_TYPE.notEqual(MemberType.TEMPLATE.type)) } else { - conditions.add(MEMBER_TYPE.eq(ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE))) + conditions.add(MEMBER_TYPE.eq(MemberType.TEMPLATE.type)) } memberType?.let { type -> conditions.add(MEMBER_TYPE.eq(type)) } userName?.let { name -> - conditions.add(MEMBER_TYPE.eq(ManagerScopesEnum.getType(ManagerScopesEnum.USER))) + conditions.add(MEMBER_TYPE.eq(MemberType.USER.type)) conditions.add(MEMBER_ID.like("%$name%").or(MEMBER_NAME.like("%$name%"))) } deptName?.let { name -> - conditions.add(MEMBER_TYPE.eq(ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT))) + conditions.add(MEMBER_TYPE.eq(MemberType.DEPARTMENT.type)) conditions.add(MEMBER_NAME.like("%$name%")) } minExpiredTime?.let { minTime -> conditions.add(EXPIRED_TIME.ge(minTime)) } @@ -455,6 +457,7 @@ class AuthResourceGroupMemberDao { countDistinct(field(MEMBER_ID, Long::class.java)) ).from(resourceMemberUnionAuthorizationMember) .groupBy(field(MEMBER_TYPE, Long::class.java)) + .skipCheck() .fetch().map { Pair(it.value1(), it.value2()) }.toMap() } @@ -479,6 +482,7 @@ class AuthResourceGroupMemberDao { deptName = deptName ) ) + .skipCheck() .fetchOne(0, Long::class.java) ?: 0L } @@ -494,7 +498,7 @@ class AuthResourceGroupMemberDao { ) .from(tResourceGroupMember) .where(tResourceGroupMember.PROJECT_CODE.eq(projectCode)) - .and(tResourceGroupMember.MEMBER_TYPE.notEqual(ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE))) + .and(tResourceGroupMember.MEMBER_TYPE.notEqual(MemberType.TEMPLATE.type)) .groupBy(tResourceGroupMember.MEMBER_ID) .unionAll( dslContext.select( @@ -523,11 +527,11 @@ class AuthResourceGroupMemberDao { conditions.add(memberTypeField.eq(memberType)) } if (userName != null) { - conditions.add(memberTypeField.eq(ManagerScopesEnum.getType(ManagerScopesEnum.USER))) + conditions.add(memberTypeField.eq(MemberType.USER.type)) conditions.add(memberId.like("%$userName%").or(memberName.like("%$userName%"))) } if (deptName != null) { - conditions.add(memberTypeField.eq(ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT))) + conditions.add(memberTypeField.eq(MemberType.DEPARTMENT.type)) conditions.add(memberName.like("%$deptName%")) } return conditions @@ -543,8 +547,10 @@ class AuthResourceGroupMemberDao { iamTemplateIds: List, resourceType: String? = null, iamGroupIds: List? = null, + excludeIamGroupIds: List? = null, minExpiredAt: LocalDateTime? = null, - maxExpiredAt: LocalDateTime? = null + maxExpiredAt: LocalDateTime? = null, + memberDeptInfos: List? = null ): Map { val conditions = buildMemberGroupCondition( projectCode = projectCode, @@ -552,8 +558,10 @@ class AuthResourceGroupMemberDao { iamTemplateIds = iamTemplateIds, resourceType = resourceType, iamGroupIds = iamGroupIds, + excludeIamGroupIds = excludeIamGroupIds, minExpiredAt = minExpiredAt, - maxExpiredAt = maxExpiredAt + maxExpiredAt = maxExpiredAt, + memberDeptInfos = memberDeptInfos ) return with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { val select = dslContext.select(RESOURCE_TYPE, count()) @@ -592,8 +600,10 @@ class AuthResourceGroupMemberDao { iamTemplateIds: List, resourceType: String? = null, iamGroupIds: List? = null, + excludeIamGroupIds: List? = null, minExpiredAt: LocalDateTime? = null, maxExpiredAt: LocalDateTime? = null, + memberDeptInfos: List? = null, offset: Int? = null, limit: Int? = null ): List { @@ -603,8 +613,10 @@ class AuthResourceGroupMemberDao { iamTemplateIds = iamTemplateIds, resourceType = resourceType, iamGroupIds = iamGroupIds, + excludeIamGroupIds = excludeIamGroupIds, minExpiredAt = minExpiredAt, - maxExpiredAt = maxExpiredAt + maxExpiredAt = maxExpiredAt, + memberDeptInfos = memberDeptInfos ) return with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { dslContext.selectFrom(this) @@ -622,32 +634,42 @@ class AuthResourceGroupMemberDao { iamTemplateIds: List, resourceType: String? = null, iamGroupIds: List? = null, + excludeIamGroupIds: List? = null, minExpiredAt: LocalDateTime? = null, - maxExpiredAt: LocalDateTime? = null + maxExpiredAt: LocalDateTime? = null, + memberDeptInfos: List? = null ): MutableList { val conditions = mutableListOf() with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { conditions.add(PROJECT_CODE.eq(projectCode)) conditions.add( + // 获取直接加入 (MEMBER_ID.eq(memberId).and( - MEMBER_TYPE.`in`( - listOf( - ManagerScopesEnum.getType(ManagerScopesEnum.USER), - ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT) - ) - ) - )) - .or( - MEMBER_ID.`in`(iamTemplateIds) - .and(MEMBER_TYPE.eq(ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE))) - ) - ) + MEMBER_TYPE.`in`(listOf(MemberType.USER.type, MemberType.DEPARTMENT.type)) + )).let { + // 获取模板加入 + if (iamTemplateIds.isNotEmpty()) { + it.or(MEMBER_ID.`in`(iamTemplateIds).and(MEMBER_TYPE.eq(MemberType.TEMPLATE.type))) + } else { + it + } + }.let { + // 获取组织加入 + if (!memberDeptInfos.isNullOrEmpty()) { + it.or(MEMBER_ID.`in`(memberDeptInfos).and(MEMBER_TYPE.eq(MemberType.DEPARTMENT.type))) + } else { + it + } + }) resourceType?.let { conditions.add(RESOURCE_TYPE.eq(resourceType)) } minExpiredAt?.let { conditions.add(EXPIRED_TIME.ge(minExpiredAt)) } maxExpiredAt?.let { conditions.add(EXPIRED_TIME.le(maxExpiredAt)) } if (!iamGroupIds.isNullOrEmpty()) { conditions.add(IAM_GROUP_ID.`in`(iamGroupIds)) } + if (!excludeIamGroupIds.isNullOrEmpty()) { + conditions.add(IAM_GROUP_ID.notIn(excludeIamGroupIds)) + } } return conditions } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupPermissionDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupPermissionDao.kt index 4eb94a340c0..4ca56fad06d 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupPermissionDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupPermissionDao.kt @@ -2,6 +2,7 @@ package com.tencent.devops.auth.dao import com.tencent.devops.auth.pojo.dto.ResourceGroupPermissionDTO import com.tencent.devops.common.auth.api.AuthResourceType +import com.tencent.devops.common.auth.api.ResourceTypeId import com.tencent.devops.model.auth.tables.TAuthResourceGroupPermission import com.tencent.devops.model.auth.tables.records.TAuthResourceGroupPermissionRecord import org.jooq.Condition @@ -183,6 +184,26 @@ class AuthResourceGroupPermissionDao { } } + fun isGroupsHasProjectLevelPermission( + dslContext: DSLContext, + projectCode: String, + filterIamGroupIds: List, + actionRelatedResourceType: String, + action: String + ): Boolean { + return with(TAuthResourceGroupPermission.T_AUTH_RESOURCE_GROUP_PERMISSION) { + dslContext.selectCount() + .from(this) + .where(PROJECT_CODE.eq(projectCode)) + .and(IAM_GROUP_ID.`in`(filterIamGroupIds)) + .and(ACTION_RELATED_RESOURCE_TYPE.eq(actionRelatedResourceType)) + .and(ACTION.eq(action)) + .and(RELATED_RESOURCE_TYPE.eq(ResourceTypeId.PROJECT)) + .and(RELATED_RESOURCE_CODE.eq(projectCode)) + .fetchOne(0, Int::class.java)!! > 0 + } + } + fun listGroupResourcesWithPermission( dslContext: DSLContext, projectCode: String, @@ -194,6 +215,7 @@ class AuthResourceGroupPermissionDao { dslContext.select(RELATED_RESOURCE_TYPE, RELATED_RESOURCE_CODE) .from(this) .where(PROJECT_CODE.eq(projectCode)) + .and(IAM_GROUP_ID.`in`(filterIamGroupIds)) .and(ACTION_RELATED_RESOURCE_TYPE.eq(resourceType)) .and(ACTION.eq(action)) .groupBy(RELATED_RESOURCE_TYPE, RELATED_RESOURCE_CODE) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/StrategyDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/StrategyDao.kt index 6f1303ef2cd..f3a715df452 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/StrategyDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/StrategyDao.kt @@ -28,6 +28,7 @@ package com.tencent.devops.auth.dao import com.tencent.devops.auth.entity.StrategyInfo +import com.tencent.devops.common.db.utils.skipCheck import com.tencent.devops.model.auth.tables.TAuthStrategy import com.tencent.devops.model.auth.tables.records.TAuthStrategyRecord import org.jooq.DSLContext @@ -39,7 +40,8 @@ class StrategyDao { fun create(dslContext: DSLContext, userId: String, strategyInfo: StrategyInfo): Int { with(TAuthStrategy.T_AUTH_STRATEGY) { - return dslContext.insertInto(this, + return dslContext.insertInto( + this, STRATEGY_NAME, STRATEGY_BODY, IS_DELETE, @@ -86,7 +88,11 @@ class StrategyDao { fun list(dslContext: DSLContext): Result? { with(TAuthStrategy.T_AUTH_STRATEGY) { - return dslContext.selectFrom(this).where((IS_DELETE.eq(0))).orderBy(CREATE_TIME.desc()).fetch() + return dslContext.selectFrom(this) + .where((IS_DELETE.eq(0))) + .orderBy(CREATE_TIME.desc()) + .skipCheck() + .fetch() } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/config/RbacAuthConfiguration.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/config/RbacAuthConfiguration.kt index aa62e4ef582..f9a6730ca9c 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/config/RbacAuthConfiguration.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/config/RbacAuthConfiguration.kt @@ -43,6 +43,9 @@ import com.tencent.bk.sdk.iam.service.v2.impl.V2GrantServiceImpl import com.tencent.bk.sdk.iam.service.v2.impl.V2ManagerServiceImpl import com.tencent.bk.sdk.iam.service.v2.impl.V2PolicyServiceImpl import com.tencent.devops.auth.dao.AuthActionDao +import com.tencent.devops.auth.dao.AuthAuthorizationDao +import com.tencent.devops.auth.dao.AuthHandoverDetailDao +import com.tencent.devops.auth.dao.AuthHandoverOverviewDao import com.tencent.devops.auth.dao.AuthMigrationDao import com.tencent.devops.auth.dao.AuthMonitorSpaceDao import com.tencent.devops.auth.dao.AuthResourceDao @@ -62,10 +65,11 @@ import com.tencent.devops.auth.provider.rbac.service.RbacPermissionApplyService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionAuthMonitorSpaceService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionAuthorizationScopesService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionExtService +import com.tencent.devops.auth.provider.rbac.service.RbacPermissionHandoverApplicationService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionItsmCallbackService +import com.tencent.devops.auth.provider.rbac.service.RbacPermissionManageFacadeServiceImpl import com.tencent.devops.auth.provider.rbac.service.RbacPermissionProjectService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionResourceCallbackService -import com.tencent.devops.auth.provider.rbac.service.RbacPermissionResourceGroupAndMemberFacadeServiceImpl import com.tencent.devops.auth.provider.rbac.service.RbacPermissionResourceGroupPermissionService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionResourceGroupService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionResourceGroupSyncService @@ -93,7 +97,8 @@ import com.tencent.devops.auth.service.PermissionAuthorizationService import com.tencent.devops.auth.service.ResourceService import com.tencent.devops.auth.service.SuperManagerService import com.tencent.devops.auth.service.iam.MigrateCreatorFixService -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService +import com.tencent.devops.auth.service.iam.PermissionHandoverApplicationService +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService import com.tencent.devops.auth.service.iam.PermissionResourceGroupService import com.tencent.devops.auth.service.iam.PermissionResourceGroupSyncService @@ -198,7 +203,7 @@ class RbacAuthConfiguration { ) @Bean - fun permissionResourceGroupAndMemberFacadeService( + fun permissionFacadeService( permissionResourceGroupService: PermissionResourceGroupService, groupPermissionService: PermissionResourceGroupPermissionService, permissionResourceMemberService: PermissionResourceMemberService, @@ -207,8 +212,14 @@ class RbacAuthConfiguration { dslContext: DSLContext, deptService: DeptService, iamV2ManagerService: V2ManagerService, - rbacCacheService: RbacCacheService - ) = RbacPermissionResourceGroupAndMemberFacadeServiceImpl( + permissionAuthorizationService: PermissionAuthorizationService, + syncIamGroupMemberService: PermissionResourceGroupSyncService, + authAuthorizationDao: AuthAuthorizationDao, + permissionHandoverApplicationService: PermissionHandoverApplicationService, + rbacCacheService: RbacCacheService, + redisOperation: RedisOperation, + authorizationDao: AuthAuthorizationDao + ) = RbacPermissionManageFacadeServiceImpl( permissionResourceGroupService = permissionResourceGroupService, groupPermissionService = groupPermissionService, permissionResourceMemberService = permissionResourceMemberService, @@ -217,7 +228,13 @@ class RbacAuthConfiguration { dslContext = dslContext, deptService = deptService, iamV2ManagerService = iamV2ManagerService, - rbacCacheService = rbacCacheService + permissionAuthorizationService = permissionAuthorizationService, + syncIamGroupMemberService = syncIamGroupMemberService, + authAuthorizationDao = authAuthorizationDao, + permissionHandoverApplicationService = permissionHandoverApplicationService, + rbacCacheService = rbacCacheService, + redisOperation = redisOperation, + authorizationDao = authorizationDao ) @Bean @@ -259,18 +276,14 @@ class RbacAuthConfiguration { authResourceGroupMemberDao: AuthResourceGroupMemberDao, dslContext: DSLContext, deptService: DeptService, - rbacCacheService: RbacCacheService, - permissionAuthorizationService: PermissionAuthorizationService, - syncIamGroupMemberService: PermissionResourceGroupSyncService + rbacCacheService: RbacCacheService ) = RbacPermissionResourceMemberService( authResourceService = authResourceService, iamV2ManagerService = iamV2ManagerService, authResourceGroupDao = authResourceGroupDao, authResourceGroupMemberDao = authResourceGroupMemberDao, dslContext = dslContext, - deptService = deptService, - permissionAuthorizationService = permissionAuthorizationService, - syncIamGroupMemberService = syncIamGroupMemberService + deptService = deptService ) @Bean @@ -590,13 +603,32 @@ class RbacAuthConfiguration { authResourceService: AuthResourceService, authResourceGroupMemberService: PermissionResourceMemberService, dslContext: DSLContext, - resourceGroupAndMemberFacadeService: PermissionResourceGroupAndMemberFacadeService + permissionManageFacadeService: PermissionManageFacadeService ) = MigratePermissionHandoverService( permissionResourceMemberService = permissionResourceMemberService, authResourceGroupDao = authResourceGroupDao, authResourceService = authResourceService, dslContext = dslContext, - resourceGroupAndMemberFacadeService = resourceGroupAndMemberFacadeService + permissionManageFacadeService = permissionManageFacadeService + ) + + @Bean + fun permissionHandoverService( + dslContext: DSLContext, + handoverOverviewDao: AuthHandoverOverviewDao, + handoverDetailDao: AuthHandoverDetailDao, + authorizationDao: AuthAuthorizationDao, + authResourceGroupDao: AuthResourceGroupDao, + rbacCacheService: RbacCacheService, + redisOperation: RedisOperation + ) = RbacPermissionHandoverApplicationService( + dslContext = dslContext, + handoverOverviewDao = handoverOverviewDao, + handoverDetailDao = handoverDetailDao, + authorizationDao = authorizationDao, + authResourceGroupDao = authResourceGroupDao, + rbacCacheService = rbacCacheService, + redisOperation = redisOperation ) @Bean diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacCacheService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacCacheService.kt index 1a9f467e7b7..d0e9b02ec98 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacCacheService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacCacheService.kt @@ -3,18 +3,21 @@ package com.tencent.devops.auth.provider.rbac.service import com.fasterxml.jackson.core.type.TypeReference import com.github.benmanes.caffeine.cache.Caffeine import com.tencent.bk.sdk.iam.config.IamConfiguration -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.bk.sdk.iam.dto.SubjectDTO import com.tencent.bk.sdk.iam.dto.V2QueryPolicyDTO import com.tencent.bk.sdk.iam.dto.action.ActionDTO import com.tencent.bk.sdk.iam.dto.resource.V2ResourceNode import com.tencent.bk.sdk.iam.service.PolicyService +import com.tencent.devops.auth.constant.AuthI18nConstants import com.tencent.devops.auth.constant.AuthMessageCode import com.tencent.devops.auth.dao.AuthActionDao import com.tencent.devops.auth.dao.AuthResourceGroupConfigDao import com.tencent.devops.auth.dao.AuthResourceTypeDao import com.tencent.devops.auth.pojo.AuthGroupConfigAction +import com.tencent.devops.auth.pojo.enum.HandoverType +import com.tencent.devops.auth.pojo.enum.MemberType import com.tencent.devops.auth.pojo.vo.ActionInfoVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo import com.tencent.devops.auth.pojo.vo.ResourceTypeInfoVo import com.tencent.devops.auth.service.AuthProjectUserMetricsService import com.tencent.devops.common.api.exception.ErrorCodeException @@ -22,6 +25,7 @@ import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.auth.api.AuthPermission import com.tencent.devops.common.auth.api.AuthResourceType import com.tencent.devops.common.auth.rbac.utils.RbacAuthUtils +import com.tencent.devops.common.web.utils.I18nUtil import org.jooq.DSLContext import org.slf4j.LoggerFactory import java.util.concurrent.TimeUnit @@ -190,7 +194,7 @@ class RbacCacheService constructor( val subject = SubjectDTO.builder() .id(userId) - .type(ManagerScopesEnum.getType(ManagerScopesEnum.USER)) + .type(MemberType.USER.type) .build() val queryPolicyDTO = V2QueryPolicyDTO.builder().system(iamConfiguration.systemId) .subject(subject) @@ -213,4 +217,42 @@ class RbacCacheService constructor( ) } } + + fun convertResourceType2Count( + resourceType2Count: Map, + type: HandoverType = HandoverType.GROUP + ): List { + val memberGroupCountList = mutableListOf() + // 项目排在第一位 + resourceType2Count[AuthResourceType.PROJECT.value]?.let { projectCount -> + memberGroupCountList.add( + ResourceType2CountVo( + resourceType = AuthResourceType.PROJECT.value, + resourceTypeName = I18nUtil.getCodeLanMessage( + messageCode = AuthResourceType.PROJECT.value + AuthI18nConstants.RESOURCE_TYPE_NAME_SUFFIX + ), + count = projectCount, + type = type + ) + ) + } + + listResourceTypes() + .filter { it.resourceType != AuthResourceType.PROJECT.value } + .forEach { resourceTypeInfoVo -> + resourceType2Count[resourceTypeInfoVo.resourceType]?.let { count -> + val memberGroupCount = ResourceType2CountVo( + resourceType = resourceTypeInfoVo.resourceType, + resourceTypeName = I18nUtil.getCodeLanMessage( + messageCode = resourceTypeInfoVo.resourceType + AuthI18nConstants.RESOURCE_TYPE_NAME_SUFFIX, + defaultMessage = resourceTypeInfoVo.name + ), + count = count, + type = type + ) + memberGroupCountList.add(memberGroupCount) + } + } + return memberGroupCountList + } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionHandoverApplicationService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionHandoverApplicationService.kt new file mode 100644 index 00000000000..f2d7d8a5c5f --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionHandoverApplicationService.kt @@ -0,0 +1,293 @@ +package com.tencent.devops.auth.provider.rbac.service + +import com.tencent.devops.auth.constant.AuthI18nConstants +import com.tencent.devops.auth.constant.AuthMessageCode +import com.tencent.devops.auth.dao.AuthAuthorizationDao +import com.tencent.devops.auth.dao.AuthHandoverDetailDao +import com.tencent.devops.auth.dao.AuthHandoverOverviewDao +import com.tencent.devops.auth.dao.AuthResourceGroupDao +import com.tencent.devops.auth.pojo.dto.HandoverDetailDTO +import com.tencent.devops.auth.pojo.dto.HandoverOverviewCreateDTO +import com.tencent.devops.auth.pojo.enum.HandoverStatus +import com.tencent.devops.auth.pojo.enum.HandoverType +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverOverviewVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.auth.service.iam.PermissionHandoverApplicationService +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.api.model.SQLPage +import com.tencent.devops.common.api.util.DateTimeUtil +import com.tencent.devops.common.api.util.PageUtil +import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationConditionRequest +import com.tencent.devops.common.redis.RedisOperation +import com.tencent.devops.common.web.utils.I18nUtil +import org.jooq.DSLContext +import org.jooq.impl.DSL +import org.slf4j.LoggerFactory +import java.time.LocalDateTime + +class RbacPermissionHandoverApplicationService( + private val dslContext: DSLContext, + private val handoverOverviewDao: AuthHandoverOverviewDao, + private val handoverDetailDao: AuthHandoverDetailDao, + private val authorizationDao: AuthAuthorizationDao, + private val authResourceGroupDao: AuthResourceGroupDao, + private val rbacCacheService: RbacCacheService, + private val redisOperation: RedisOperation +) : PermissionHandoverApplicationService { + override fun createHandoverApplication( + overview: HandoverOverviewCreateDTO, + details: List + ) { + logger.info("create handover application:{}|{}", overview, details) + // todo 发送邮件/devops-notices通知 + + dslContext.transaction { configuration -> + val transactionContext = DSL.using(configuration) + handoverOverviewDao.create( + dslContext = transactionContext, + overviewDTO = overview + ) + handoverDetailDao.batchCreate( + dslContext = transactionContext, + handoverDetailDTOs = details + ) + } + } + + override fun generateTitle( + groupCount: Int, + authorizationCount: Int + ): String { + return I18nUtil.getCodeLanMessage(messageCode = AuthI18nConstants.BK_APPLY_TO_HANDOVER).let { + when { + groupCount > 0 && authorizationCount > 0 -> { + it.plus(I18nUtil.getCodeLanMessage(AuthI18nConstants.BK_HANDOVER_GROUPS, params = arrayOf(groupCount.toString()))).plus(",") + it.plus( + I18nUtil.getCodeLanMessage(AuthI18nConstants.BK_HANDOVER_AUTHORIZATIONS, params = arrayOf(authorizationCount.toString())) + ) + } + + groupCount > 0 -> { + it.plus(I18nUtil.getCodeLanMessage(AuthI18nConstants.BK_HANDOVER_GROUPS, params = arrayOf(groupCount.toString()))) + } + + else -> { + it.plus( + I18nUtil.getCodeLanMessage(AuthI18nConstants.BK_HANDOVER_AUTHORIZATIONS, params = arrayOf(authorizationCount.toString())) + ) + } + } + } + } + + /** + * 生成格式如 REQ2024111300001 + * REQ 固定前缀 + * 20241113 表示日期 + * 00001 表示当天第几个单号 + * */ + override fun generateFlowNo(): String { + val currentTime = DateTimeUtil.toDateTime(LocalDateTime.now(), DateTimeUtil.YYYYMMDD) + val incrementedValue = redisOperation.increment(String.format(FLOW_NO_KEY, currentTime), 1) + val formattedIncrementedValue = String.format("%05d", incrementedValue) + return FLOW_NO_PREFIX + currentTime + formattedIncrementedValue + } + + override fun updateHandoverApplication(overview: HandoverOverviewUpdateReq) { + logger.info("update handover application:{}", overview) + handoverOverviewDao.update( + dslContext = dslContext, + overviewDTO = overview + ) + } + + override fun getHandoverOverview(flowNo: String): HandoverOverviewVo { + return handoverOverviewDao.get( + dslContext = dslContext, + flowNo = flowNo + ) ?: throw ErrorCodeException( + errorCode = AuthMessageCode.ERROR_HANDOVER_OVERVIEW_NOT_EXIST + ) + } + + override fun listHandoverOverviews( + queryRequest: HandoverOverviewQueryReq + ): SQLPage { + val records = handoverOverviewDao.list( + dslContext = dslContext, + queryRequest = queryRequest + ) + val count = handoverOverviewDao.count( + dslContext = dslContext, + queryRequest = queryRequest + ) + return SQLPage( + records = records, + count = count + ) + } + + override fun listAuthorizationsOfHandoverApplication( + queryReq: HandoverDetailsQueryReq + ): SQLPage { + val flowNo = queryReq.flowNo!! + val overview = getHandoverOverview(flowNo) + val resourceCodes = handoverDetailDao.list( + dslContext = dslContext, + projectCode = overview.projectCode, + flowNos = listOf(flowNo), + resourceType = queryReq.resourceType, + handoverType = HandoverType.AUTHORIZATION + ).map { it.itemId } + val count = handoverDetailDao.count( + dslContext = dslContext, + projectCode = overview.projectCode, + flowNos = listOf(flowNo), + resourceType = queryReq.resourceType, + handoverType = HandoverType.AUTHORIZATION + ) + val records = authorizationDao.list( + dslContext = dslContext, + condition = ResourceAuthorizationConditionRequest( + projectCode = overview.projectCode, + resourceType = queryReq.resourceType, + filterResourceCodes = resourceCodes, + page = queryReq.page, + pageSize = queryReq.pageSize + ) + ).map { + HandoverAuthorizationDetailVo( + resourceCode = it.resourceCode, + resourceName = it.resourceName, + handoverType = HandoverType.AUTHORIZATION, + handoverFrom = overview.applicant + ) + } + return SQLPage(records = records, count = count) + } + + override fun listGroupsOfHandoverApplication( + queryReq: HandoverDetailsQueryReq + ): SQLPage { + val flowNo = queryReq.flowNo!! + val handoverOverview = getHandoverOverview(flowNo) + val iamGroupIdsByHandover = listHandoverDetails( + projectCode = handoverOverview.projectCode, + flowNo = flowNo, + resourceType = queryReq.resourceType, + handoverType = HandoverType.GROUP + ).map { it.itemId } + if (iamGroupIdsByHandover.isEmpty()) + return SQLPage(0, emptyList()) + val convertPageSizeToSQLLimit = PageUtil.convertPageSizeToSQLLimit( + page = queryReq.page, + pageSize = queryReq.pageSize + ) + val records = authResourceGroupDao.listGroupByResourceType( + dslContext = dslContext, + projectCode = handoverOverview.projectCode, + resourceType = queryReq.resourceType, + iamGroupIds = iamGroupIdsByHandover, + offset = convertPageSizeToSQLLimit.offset, + limit = convertPageSizeToSQLLimit.limit + ).map { + HandoverGroupDetailVo( + projectCode = it.projectCode, + iamGroupId = it.relationId, + groupName = it.groupName, + groupDesc = it.description, + resourceCode = it.resourceCode, + resourceName = it.resourceName + ) + } + return SQLPage( + count = iamGroupIdsByHandover.size.toLong(), + records = records + ) + } + + override fun getResourceType2CountOfHandoverApplication(flowNo: String): List { + val handoverOverview = getHandoverOverview(flowNo) + val resourceType2CountWithGroup = handoverDetailDao.countWithResourceType( + dslContext = dslContext, + projectCode = handoverOverview.projectCode, + flowNo = flowNo, + handoverType = HandoverType.GROUP + ) + val resourceType2CountWithAuthorization = handoverDetailDao.countWithResourceType( + dslContext = dslContext, + projectCode = handoverOverview.projectCode, + flowNo = flowNo, + handoverType = HandoverType.AUTHORIZATION + ) + val result = mutableListOf() + if (resourceType2CountWithGroup.isNotEmpty()) { + result.addAll( + rbacCacheService.convertResourceType2Count( + resourceType2Count = resourceType2CountWithGroup, + type = HandoverType.GROUP + ) + ) + } + if (resourceType2CountWithAuthorization.isNotEmpty()) { + result.addAll( + rbacCacheService.convertResourceType2Count( + resourceType2Count = resourceType2CountWithAuthorization, + type = HandoverType.AUTHORIZATION + ) + ) + } + return result + } + + override fun listHandoverDetails( + projectCode: String, + flowNo: String, + resourceType: String?, + handoverType: HandoverType? + ): List { + return handoverDetailDao.list( + dslContext = dslContext, + projectCode = projectCode, + flowNos = listOf(flowNo), + resourceType = resourceType, + handoverType = handoverType + ) + } + + override fun listMemberHandoverDetails( + projectCode: String, + memberId: String, + handoverType: HandoverType, + resourceType: String? + ): List { + val handoverOverviews = listHandoverOverviews( + queryRequest = HandoverOverviewQueryReq( + memberId = memberId, + projectCode = projectCode, + applicant = memberId, + handoverStatus = HandoverStatus.PENDING + ) + ).records + val flowNos = handoverOverviews.map { it.flowNo } + val flowNo2Approver = handoverOverviews.associate { Pair(it.flowNo, it.approver) } + return handoverDetailDao.list( + dslContext = dslContext, + projectCode = projectCode, + flowNos = flowNos, + resourceType = resourceType, + handoverType = handoverType + ).map { it.copy(approver = flowNo2Approver[it.flowNo]) } + } + + companion object { + private val logger = LoggerFactory.getLogger(RbacPermissionHandoverApplicationService::class.java) + private const val FLOW_NO_PREFIX = "REQ" + private const val FLOW_NO_KEY = "AUTH:HANDOVER:FLOW:NO:%s" + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionManageFacadeServiceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionManageFacadeServiceImpl.kt new file mode 100644 index 00000000000..30c8513c8c4 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionManageFacadeServiceImpl.kt @@ -0,0 +1,2074 @@ +package com.tencent.devops.auth.provider.rbac.service + +import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum +import com.tencent.bk.sdk.iam.dto.manager.ManagerMember +import com.tencent.bk.sdk.iam.dto.response.MemberGroupDetailsResponse +import com.tencent.bk.sdk.iam.service.v2.V2ManagerService +import com.tencent.devops.auth.constant.AuthI18nConstants +import com.tencent.devops.auth.constant.AuthMessageCode +import com.tencent.devops.auth.constant.AuthMessageCode.ERROR_HANDOVER_APPROVAL +import com.tencent.devops.auth.constant.AuthMessageCode.ERROR_HANDOVER_FINISH +import com.tencent.devops.auth.constant.AuthMessageCode.ERROR_HANDOVER_HANDLE +import com.tencent.devops.auth.constant.AuthMessageCode.ERROR_HANDOVER_REVOKE +import com.tencent.devops.auth.dao.AuthAuthorizationDao +import com.tencent.devops.auth.dao.AuthResourceGroupDao +import com.tencent.devops.auth.dao.AuthResourceGroupMemberDao +import com.tencent.devops.auth.pojo.AuthResourceGroupMember +import com.tencent.devops.auth.pojo.ResourceMemberInfo +import com.tencent.devops.auth.pojo.dto.HandoverDetailDTO +import com.tencent.devops.auth.pojo.dto.HandoverOverviewCreateDTO +import com.tencent.devops.auth.pojo.dto.IamGroupIdsQueryConditionDTO +import com.tencent.devops.auth.pojo.dto.InvalidAuthorizationsDTO +import com.tencent.devops.auth.pojo.dto.ProjectMembersQueryConditionDTO +import com.tencent.devops.auth.pojo.enum.BatchOperateType +import com.tencent.devops.auth.pojo.enum.HandoverAction +import com.tencent.devops.auth.pojo.enum.HandoverQueryChannel +import com.tencent.devops.auth.pojo.enum.HandoverStatus +import com.tencent.devops.auth.pojo.enum.HandoverType +import com.tencent.devops.auth.pojo.enum.JoinedType +import com.tencent.devops.auth.pojo.enum.MemberType +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.auth.pojo.enum.RemoveMemberButtonControl +import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRemoveConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq +import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq +import com.tencent.devops.auth.pojo.request.ResourceType2CountOfHandoverQuery +import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo +import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverOverviewVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.auth.service.DeptService +import com.tencent.devops.auth.service.PermissionAuthorizationService +import com.tencent.devops.auth.service.iam.PermissionHandoverApplicationService +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService +import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService +import com.tencent.devops.auth.service.iam.PermissionResourceGroupService +import com.tencent.devops.auth.service.iam.PermissionResourceGroupSyncService +import com.tencent.devops.auth.service.iam.PermissionResourceMemberService +import com.tencent.devops.auth.service.lock.HandleHandoverApplicationLock +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.api.model.SQLPage +import com.tencent.devops.common.api.util.DateTimeUtil +import com.tencent.devops.common.api.util.PageUtil +import com.tencent.devops.common.api.util.timestamp +import com.tencent.devops.common.api.util.timestampmilli +import com.tencent.devops.common.auth.api.ActionId +import com.tencent.devops.common.auth.api.AuthResourceType +import com.tencent.devops.common.auth.api.ResourceTypeId +import com.tencent.devops.common.auth.api.pojo.ResetAllResourceAuthorizationReq +import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationConditionRequest +import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationHandoverConditionRequest +import com.tencent.devops.common.auth.enums.HandoverChannelCode +import com.tencent.devops.common.redis.RedisOperation +import com.tencent.devops.common.service.utils.RetryUtils +import com.tencent.devops.common.web.utils.I18nUtil +import com.tencent.devops.model.auth.tables.records.TAuthResourceGroupRecord +import org.jooq.DSLContext +import org.slf4j.LoggerFactory +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +class RbacPermissionManageFacadeServiceImpl( + private val permissionResourceGroupService: PermissionResourceGroupService, + private val groupPermissionService: PermissionResourceGroupPermissionService, + private val permissionResourceMemberService: PermissionResourceMemberService, + private val authResourceGroupDao: AuthResourceGroupDao, + private val authResourceGroupMemberDao: AuthResourceGroupMemberDao, + private val dslContext: DSLContext, + private val deptService: DeptService, + private val iamV2ManagerService: V2ManagerService, + private val authAuthorizationDao: AuthAuthorizationDao, + private val syncIamGroupMemberService: PermissionResourceGroupSyncService, + private val permissionAuthorizationService: PermissionAuthorizationService, + private val permissionHandoverApplicationService: PermissionHandoverApplicationService, + private val rbacCacheService: RbacCacheService, + private val redisOperation: RedisOperation, + private val authorizationDao: AuthAuthorizationDao +) : PermissionManageFacadeService { + override fun getMemberGroupsDetails( + projectId: String, + memberId: String, + resourceType: String?, + iamGroupIds: List?, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long?, + relatedResourceType: String?, + relatedResourceCode: String?, + action: String?, + operateChannel: OperateChannel?, + start: Int?, + limit: Int? + ): SQLPage { + // 根据查询条件查询得到iam组id + val iamGroupIdsByConditions = listIamGroupIdsByConditions( + condition = IamGroupIdsQueryConditionDTO( + projectCode = projectId, + groupName = groupName, + iamGroupIds = iamGroupIds, + relatedResourceType = relatedResourceType, + relatedResourceCode = relatedResourceCode, + action = action + ) + ) + // 查询成员所在资源用户组列表 + val (count, resourceGroupMembers) = listResourceGroupMembers( + projectCode = projectId, + memberId = memberId, + resourceType = resourceType, + iamGroupIds = iamGroupIdsByConditions, + minExpiredAt = minExpiredAt, + maxExpiredAt = maxExpiredAt, + operateChannel = operateChannel, + start = start, + limit = limit + ) + // 用户组对应的资源信息 + val resourceGroupMap = authResourceGroupDao.listByRelationId( + dslContext = dslContext, + projectCode = projectId, + iamGroupIds = resourceGroupMembers.map { it.iamGroupId.toString() } + ).associateBy { it.relationId } + // 只有一个成员的管理员组 + val uniqueManagerGroups = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectId, + iamGroupIds = resourceGroupMembers.map { it.iamGroupId } + ) + // 用户组成员详情 + val groupMemberDetailMap = getGroupMemberDetailMap( + memberId = memberId, + resourceGroupMembers = resourceGroupMembers, + operateChannel = operateChannel + ) + // 获取用户正在交接的用户组,仅用于个人视角 + val groupsBeingHandover = if (operateChannel == OperateChannel.PERSONAL) { + permissionHandoverApplicationService.listMemberHandoverDetails( + projectCode = projectId, + memberId = memberId, + handoverType = HandoverType.GROUP + ).map { it.itemId.toInt() }.distinct() + } else { + emptyList() + } + val records = mutableListOf() + resourceGroupMembers.forEach { + val resourceGroup = resourceGroupMap[it.iamGroupId.toString()]!! + val groupMemberDetail = groupMemberDetailMap["${it.iamGroupId}_${it.memberId}"] + records.add( + convertGroupDetailsInfoVo( + resourceGroup = resourceGroup, + groupMemberDetail = groupMemberDetail, + uniqueManagerGroups = uniqueManagerGroups, + authResourceGroupMember = it, + operateChannel = operateChannel, + groupsBeingHandover = groupsBeingHandover + ) + ) + } + return SQLPage(count = count, records = records) + } + + private fun getGroupMemberDetailMap( + memberId: String, + resourceGroupMembers: List, + operateChannel: OperateChannel? + ): Map { + // 如果用户离职,查询权限中心接口会报错 + if (deptService.isUserDeparted(memberId)) { + return emptyMap() + } + // 用户组成员详情 + val groupMemberDetailMap = mutableMapOf() + // 直接加入的用户 + val userGroupIds = resourceGroupMembers + .filter { it.memberType == MemberType.USER.type } + .map { it.iamGroupId } + if (userGroupIds.isNotEmpty()) { + iamV2ManagerService.listMemberGroupsDetails( + MemberType.USER.type, + memberId, + userGroupIds.joinToString(",") + ).forEach { + groupMemberDetailMap["${it.id}_$memberId"] = it + } + } + val deptGroups = resourceGroupMembers + .filter { it.memberType == MemberType.DEPARTMENT.type } + when { + deptGroups.isEmpty() -> {} + operateChannel == OperateChannel.PERSONAL -> { + // 个人视角,会获取用户通过组织间接加入的组 + deptGroups.groupBy({ it.memberId }, { it.iamGroupId.toString() }) + .forEach { (deptId, iamGroupIds) -> + if (iamGroupIds.isEmpty()) return@forEach + iamV2ManagerService.listMemberGroupsDetails( + MemberType.DEPARTMENT.type, + deptId, + iamGroupIds.joinToString(",") + ).forEach { + groupMemberDetailMap["${it.id}_$deptId"] = it + } + } + } + + else -> { + // 管理员视角,获取组织直接加入的用户组 + val deptGroupIds = deptGroups.map { it.iamGroupId } + iamV2ManagerService.listMemberGroupsDetails( + MemberType.DEPARTMENT.type, + memberId, + deptGroupIds.joinToString(",") + ).forEach { + groupMemberDetailMap["${it.id}_$memberId"] = it + } + } + } + // 人员模板加入的组 + resourceGroupMembers.filter { it.memberType == MemberType.TEMPLATE.type } + .groupBy({ it.memberId }, { it.iamGroupId.toString() }) + .forEach { (iamTemplateId, iamGroupIds) -> + if (iamGroupIds.isEmpty()) return@forEach + iamV2ManagerService.listMemberGroupsDetails( + MemberType.TEMPLATE.type, + iamTemplateId, + iamGroupIds.joinToString(",") + ).forEach { + groupMemberDetailMap["${it.id}_$iamTemplateId"] = it + } + } + return groupMemberDetailMap + } + + private fun convertGroupDetailsInfoVo( + resourceGroup: TAuthResourceGroupRecord, + groupMemberDetail: MemberGroupDetailsResponse?, + uniqueManagerGroups: List, + authResourceGroupMember: AuthResourceGroupMember, + operateChannel: OperateChannel?, + groupsBeingHandover: List + ): GroupDetailsInfoVo { + // 如果用户离职,查询权限中心接口会报错,因此从数据库直接取数据,而不去调用权限中心接口。 + val (expiredAt, joinedTime) = if (groupMemberDetail != null) { + Pair( + TimeUnit.SECONDS.toMillis(groupMemberDetail.expiredAt), + TimeUnit.SECONDS.toMillis(groupMemberDetail.createdAt) + ) + } else { + Pair( + authResourceGroupMember.expiredTime.timestampmilli(), + 0L + ) + } + val between = expiredAt - System.currentTimeMillis() + val groupId = resourceGroup.relationId.toInt() + return GroupDetailsInfoVo( + resourceCode = resourceGroup.resourceCode, + resourceName = resourceGroup.resourceName, + resourceType = resourceGroup.resourceType, + groupId = groupId, + groupName = resourceGroup.groupName, + groupDesc = resourceGroup.description, + expiredAtDisplay = when { + expiredAt == PERMANENT_EXPIRED_TIME -> + I18nUtil.getCodeLanMessage(messageCode = AuthI18nConstants.BK_MEMBER_EXPIRED_AT_DISPLAY_PERMANENT) + + between >= 0 -> I18nUtil.getCodeLanMessage( + messageCode = AuthI18nConstants.BK_MEMBER_EXPIRED_AT_DISPLAY_NORMAL, + params = arrayOf(DateTimeUtil.formatDay(between)) + ) + + else -> I18nUtil.getCodeLanMessage( + messageCode = AuthI18nConstants.BK_MEMBER_EXPIRED_AT_DISPLAY_EXPIRED + ) + }, + expiredAt = expiredAt, + joinedTime = joinedTime, + removeMemberButtonControl = when { + authResourceGroupMember.memberType == MemberType.TEMPLATE.type -> + RemoveMemberButtonControl.TEMPLATE + + operateChannel == OperateChannel.PERSONAL && + authResourceGroupMember.memberType == MemberType.DEPARTMENT.type -> + RemoveMemberButtonControl.DEPARTMENT + + resourceGroup.resourceType == AuthResourceType.PROJECT.value && + uniqueManagerGroups.contains(authResourceGroupMember.iamGroupId) -> + RemoveMemberButtonControl.UNIQUE_MANAGER + + uniqueManagerGroups.contains(authResourceGroupMember.iamGroupId) -> + RemoveMemberButtonControl.UNIQUE_OWNER + + else -> + RemoveMemberButtonControl.OTHER + }, + joinedType = when { + authResourceGroupMember.memberType == MemberType.TEMPLATE.type -> JoinedType.TEMPLATE + authResourceGroupMember.memberType == MemberType.DEPARTMENT.type && + operateChannel == OperateChannel.PERSONAL -> JoinedType.DEPARTMENT + + else -> JoinedType.DIRECT + }, + operator = "", + beingHandedOver = authResourceGroupMember.memberType == MemberType.USER.type + && groupsBeingHandover.contains(groupId) + ) + } + + override fun getMemberGroupsCount( + projectCode: String, + memberId: String, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long?, + relatedResourceType: String?, + relatedResourceCode: String?, + action: String?, + operateChannel: OperateChannel? + ): List { + val (iamTemplateIds, memberDeptInfos) = getMemberTemplateIdsAndDeptInfos( + projectCode = projectCode, + memberId = memberId, + operateChannel = operateChannel + ) + val iamGroupIdsByConditions = listIamGroupIdsByConditions( + condition = IamGroupIdsQueryConditionDTO( + projectCode = projectCode, + groupName = groupName, + relatedResourceType = relatedResourceType, + relatedResourceCode = relatedResourceCode, + action = action + ) + ) + // 获取成员加入的用户组 + val memberGroupCountMap = authResourceGroupMemberDao.countMemberGroup( + dslContext = dslContext, + projectCode = projectCode, + memberId = memberId, + iamTemplateIds = iamTemplateIds, + iamGroupIds = iamGroupIdsByConditions, + minExpiredAt = minExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) }, + maxExpiredAt = maxExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) }, + memberDeptInfos = memberDeptInfos + ) + return rbacCacheService.convertResourceType2Count(memberGroupCountMap) + } + + private fun getMemberTemplateIdsAndDeptInfos( + projectCode: String, + memberId: String, + operateChannel: OperateChannel? + ): Pair, List> { + // 获取用户加入的项目级用户组模板ID + val iamTemplateIds = listProjectMemberGroupTemplateIds( + projectCode = projectCode, + memberId = memberId + ) + // 获取用户部门信息 + val memberDeptInfos = if (operateChannel == OperateChannel.PERSONAL) { + getMemberDeptInfos(memberId) + } else { + emptyList() + } + return Pair(iamTemplateIds, memberDeptInfos) + } + + override fun listIamGroupIdsByConditions(condition: IamGroupIdsQueryConditionDTO): List { + return with(condition) { + val filterGroupsByGroupName = if (isQueryByGroupName()) { + permissionResourceGroupService.listIamGroupIdsByGroupName( + projectId = projectCode, + groupName = groupName!! + ) + } else { + emptyList() + } + val finalGroupIds = if (isQueryByGroupPermissions()) { + groupPermissionService.listGroupsByPermissionConditions( + projectCode = projectCode, + filterIamGroupIds = filterGroupsByGroupName, + relatedResourceType = relatedResourceType!!, + relatedResourceCode = relatedResourceCode, + action = action + ) + } else { + filterGroupsByGroupName + }.toMutableList() + iamGroupIds?.let { finalGroupIds.addAll(it) } + finalGroupIds + } + } + + override fun listMemberGroupIdsInProject( + projectCode: String, + memberId: String + ): List { + // 获取用户加入的项目级用户组模板ID + val iamTemplateIds = listProjectMemberGroupTemplateIds( + projectCode = projectCode, + memberId = memberId + ) + return authResourceGroupMemberDao.listMemberGroupIdsInProject( + dslContext = dslContext, + projectCode = projectCode, + memberId = memberId, + iamTemplateIds = iamTemplateIds + ) + } + + @Suppress("LongParameterList") + override fun listResourceGroupMembers( + projectCode: String, + memberId: String, + resourceType: String?, + iamGroupIds: List?, + excludeIamGroupIds: List?, + minExpiredAt: Long?, + maxExpiredAt: Long?, + operateChannel: OperateChannel?, + start: Int?, + limit: Int? + ): Pair> { + // 获取用户的部门信息以及加入的项目级别用户组模板ID + val (iamTemplateIds, memberDeptInfos) = getMemberTemplateIdsAndDeptInfos( + projectCode = projectCode, + memberId = memberId, + operateChannel = operateChannel + ) + + val minExpiredTime = minExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) } + val maxExpiredTime = maxExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) } + val count = authResourceGroupMemberDao.countMemberGroup( + dslContext = dslContext, + projectCode = projectCode, + memberId = memberId, + iamTemplateIds = iamTemplateIds, + resourceType = resourceType, + iamGroupIds = iamGroupIds, + excludeIamGroupIds = excludeIamGroupIds, + minExpiredAt = minExpiredTime, + maxExpiredAt = maxExpiredTime, + memberDeptInfos = memberDeptInfos + )[resourceType] ?: 0L + val resourceGroupMembers = authResourceGroupMemberDao.listMemberGroupDetail( + dslContext = dslContext, + projectCode = projectCode, + memberId = memberId, + iamTemplateIds = iamTemplateIds, + resourceType = resourceType, + iamGroupIds = iamGroupIds, + excludeIamGroupIds = excludeIamGroupIds, + minExpiredAt = minExpiredTime, + maxExpiredAt = maxExpiredTime, + memberDeptInfos = memberDeptInfos, + offset = start, + limit = limit + ) + return Pair(count, resourceGroupMembers) + } + + // 获取用户加入的项目级用户组模板ID + private fun listProjectMemberGroupTemplateIds( + projectCode: String, + memberId: String + ): List { + // 查询项目下包含该成员的组列表 + val projectGroupIds = authResourceGroupMemberDao.listResourceGroupMember( + dslContext = dslContext, + projectCode = projectCode, + resourceType = AuthResourceType.PROJECT.value, + memberId = memberId + ).map { it.iamGroupId.toString() } + // 通过项目组ID获取人员模板ID + return authResourceGroupDao.listByRelationId( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = projectGroupIds + ).filter { it.iamTemplateId != null } + .map { it.iamTemplateId.toString() } + } + + private fun getMemberDeptInfos( + memberId: String + ): List { + deptService.getUserInfo( + userId = "admin", + name = memberId + )?.deptInfo ?: return emptyList() + return deptService.getUserDeptInfo(memberId).toList() + } + + private fun getGroupIdsByGroupMemberCondition( + projectCode: String, + commonCondition: GroupMemberCommonConditionReq + ): Map> { + val finalMemberGroups = mutableListOf() + + val resourceGroupMembersByCondition = when { + commonCondition.allSelection -> { + listResourceGroupMembers( + projectCode = projectCode, + memberId = commonCondition.targetMember.id, + operateChannel = commonCondition.operateChannel + ).second + } + + commonCondition.resourceTypes.isNotEmpty() -> { + commonCondition.resourceTypes.flatMap { resourceType -> + listResourceGroupMembers( + projectCode = projectCode, + memberId = commonCondition.targetMember.id, + resourceType = resourceType, + operateChannel = commonCondition.operateChannel + ).second + } + } + + else -> emptyList() + } + + finalMemberGroups.addAll(resourceGroupMembersByCondition) + + if (commonCondition.groupIds.isNotEmpty()) { + val groupsOfSelect = listResourceGroupMembers( + projectCode = projectCode, + memberId = commonCondition.targetMember.id, + iamGroupIds = commonCondition.groupIds, + operateChannel = commonCondition.operateChannel + ).second + finalMemberGroups.addAll(groupsOfSelect) + } + + // 分类 + val result = mutableMapOf>() + finalMemberGroups.groupBy { it.memberType }.forEach { (memberType, groups) -> + result[MemberType.get(memberType)] = groups.map { it.iamGroupId } + } + return result + } + + override fun listProjectMembersByComplexConditions( + conditionReq: ProjectMembersQueryConditionReq + ): SQLPage { + logger.info("list project members by complex conditions: $conditionReq") + // 不允许同时查询部门名称和用户名称 + if (conditionReq.userName != null && conditionReq.deptName != null) { + return SQLPage(count = 0, records = emptyList()) + } + + // 简单查询直接返回结果 + if (!conditionReq.isComplexQuery()) { + return permissionResourceMemberService.listProjectMembers( + projectCode = conditionReq.projectCode, + memberType = conditionReq.memberType, + userName = conditionReq.userName, + deptName = conditionReq.deptName, + departedFlag = conditionReq.departedFlag, + page = conditionReq.page, + pageSize = conditionReq.pageSize + ) + } + + // 处理复杂查询条件 + val iamGroupIdsByCondition = if (conditionReq.isNeedToQueryIamGroups()) { + listIamGroupIdsByConditions( + condition = IamGroupIdsQueryConditionDTO( + projectCode = conditionReq.projectCode, + groupName = conditionReq.groupName, + relatedResourceType = conditionReq.relatedResourceType, + relatedResourceCode = conditionReq.relatedResourceCode, + action = conditionReq.action + ) + ) + } else { + emptyList() + }.toMutableList() + + // 查询不到用户组,直接返回空 + if (conditionReq.isNeedToQueryIamGroups() && iamGroupIdsByCondition.isEmpty()) { + return SQLPage(0, emptyList()) + } + + val conditionDTO = ProjectMembersQueryConditionDTO.build(conditionReq, iamGroupIdsByCondition) + + if (iamGroupIdsByCondition.isNotEmpty()) { + logger.debug("iamGroupIdsByCondition :{}", iamGroupIdsByCondition) + // 根据用户组Id查询出对应用户组中的人员模板成员 + val iamTemplateIds = authResourceGroupMemberDao.listProjectMembersByComplexConditions( + dslContext = dslContext, + conditionDTO = ProjectMembersQueryConditionDTO( + projectCode = conditionDTO.projectCode, + queryTemplate = true, + iamGroupIds = conditionDTO.iamGroupIds + ) + ) + if (iamTemplateIds.isNotEmpty()) { + // 根据查询出的人员模板ID,查询出对应的组ID + val iamGroupIdsFromTemplate = authResourceGroupDao.listIamGroupIdsByConditions( + dslContext = dslContext, + projectCode = conditionDTO.projectCode, + iamTemplateIds = iamTemplateIds.map { it.id.toInt() } + ) + iamGroupIdsByCondition.addAll(iamGroupIdsFromTemplate) + logger.debug("iamGroupIdsByCondition and template :{}", iamGroupIdsByCondition) + } + } + + val records = authResourceGroupMemberDao.listProjectMembersByComplexConditions( + dslContext = dslContext, + conditionDTO = conditionDTO + ) + logger.debug("listProjectMembersByComplexConditions :{}", records) + + val count = authResourceGroupMemberDao.countProjectMembersByComplexConditions( + dslContext = dslContext, + conditionDTO = conditionDTO + ) + logger.debug("listProjectMembersByComplexConditions :$count") + // 添加离职标志 + return if (conditionDTO.departedFlag == false) { + SQLPage(count, records) + } else { + SQLPage(count, permissionResourceMemberService.addDepartedFlagToMembers(records)) + } + } + + override fun listInvalidAuthorizationsAfterOperatedGroups( + projectCode: String, + iamGroupIds: List, + memberId: String + ): InvalidAuthorizationsDTO { + val (invalidGroups, invalidPipelines) = listInvalidPipelinesAfterOperatedGroups( + projectCode = projectCode, + iamGroupIds = iamGroupIds, + memberId = memberId + ) + val invalidRepositoryIds = listInvalidRepositoryAfterOperatedGroups( + projectCode = projectCode, + iamGroupIds = iamGroupIds, + memberId = memberId + ) + return InvalidAuthorizationsDTO( + invalidGroupIds = invalidGroups, + invalidPipelineIds = invalidPipelines, + invalidRepertoryIds = invalidRepositoryIds + ) + } + + private fun listInvalidPipelinesAfterOperatedGroups( + projectCode: String, + iamGroupIds: List, + memberId: String + ): InvalidAuthorizationsDTO { + logger.info("list invalid authorizations after operated groups:$projectCode|$iamGroupIds|$memberId") + val startEpoch = System.currentTimeMillis() + try { + // 1.筛选出本次退出/交接中包含流水线执行权限的用户组 + val operatedGroupsWithExecutePerm = groupPermissionService.listGroupsByPermissionConditions( + projectCode = projectCode, + relatedResourceType = AuthResourceType.PIPELINE_DEFAULT.value, + action = ActionId.PIPELINE_EXECUTE, + filterIamGroupIds = iamGroupIds + ) + logger.debug("list operated groups with execute perm:{}", operatedGroupsWithExecutePerm) + + // 2.获取用户退出/交接以上操作的用户组后,还未退出的流水线/项目级别(仅这些类型会包含流水线执行权限)的用户组。 + val userGroupsJoinedAfterOperatedGroups = listResourceGroupMembers( + projectCode = projectCode, + memberId = memberId, + resourceType = ResourceTypeId.PIPELINE, + excludeIamGroupIds = operatedGroupsWithExecutePerm, + operateChannel = OperateChannel.PERSONAL + ).second.toMutableList().apply { + addAll( + listResourceGroupMembers( + projectCode = projectCode, + memberId = memberId, + resourceType = ResourceTypeId.PROJECT, + excludeIamGroupIds = operatedGroupsWithExecutePerm, + operateChannel = OperateChannel.PERSONAL + ).second + ) + }.map { it.iamGroupId } + logger.debug("list user groups joined after operated groups:{}", userGroupsJoinedAfterOperatedGroups) + // 3.查询未退出的流水线/项目级别的用户组中是否包含项目级别的流水线执行权限。 + // 查询用户在未退出的用户组中否还有整个项目的流水线执行权限。若有的话,则对流水线的代持人权限未造成影响。 + val hasAllPipelineExecutePermAfterOperateGroups = groupPermissionService.isGroupsHasProjectLevelPermission( + projectCode = projectCode, + filterIamGroupIds = userGroupsJoinedAfterOperatedGroups, + action = ActionId.PIPELINE_EXECUTE + ) + logger.debug("has all pipeline execute perm after operate groups:{}", hasAllPipelineExecutePermAfterOperateGroups) + + // 3.1.若用户在未退出的组中拥有整个项目的流水线执行权限,则本次不会对任何的流水线代持人权限造成影响。 + if (hasAllPipelineExecutePermAfterOperateGroups) + return InvalidAuthorizationsDTO(emptyList(), emptyList()) + + // 3.2.若没有的话,查询本次退出/交接的用户组中是否包含项目级别的流水线执行权限。 + val hasAllPipelineExecutePermInOperateGroups = groupPermissionService.isGroupsHasProjectLevelPermission( + projectCode = projectCode, + filterIamGroupIds = operatedGroupsWithExecutePerm, + action = ActionId.PIPELINE_EXECUTE + ) + logger.debug("has all pipeline execute perm in operate groups:{}", hasAllPipelineExecutePermInOperateGroups) + + val pipelinesWithoutAuthorization = if (hasAllPipelineExecutePermInOperateGroups) { + // 3.2.1 如果本次退出/交接的用户组中包含项目级别的流水线执行权限, + // 那么查询出用户还有执行流水线权限的流水线,该项目下除了这些流水线,其他的流水线代持人权限都会失效。 + val userHasExecutePermAfterOperatedGroups = groupPermissionService.listGroupResourcesWithPermission( + projectCode = projectCode, + filterIamGroupIds = userGroupsJoinedAfterOperatedGroups, + relatedResourceType = ResourceTypeId.PIPELINE, + action = ActionId.PIPELINE_EXECUTE + )[ResourceTypeId.PIPELINE] ?: emptyList() + logger.debug("user has execute perm after operated groups:{}", userHasExecutePermAfterOperatedGroups) + // 失去代持人权限的流水线 + authAuthorizationDao.list( + dslContext = dslContext, + condition = ResourceAuthorizationConditionRequest( + projectCode = projectCode, + resourceType = ResourceTypeId.PIPELINE, + handoverFrom = memberId, + excludeResourceCodes = userHasExecutePermAfterOperatedGroups + ) + ).map { it.resourceCode } + } else { + // 3.2.2 如果本次退出/交接的用户组中不包含整个项目的流水线执行权限。 + // 通过计算得出,用户本次操作用户组,导致失去流水线执行权限的流水线。 + // 然后再计算失去这些流水线执行权限后,会导致哪些流水线的代持人权限失效。 + val pipelinesWithExecutePermAfterOperatedGroups = groupPermissionService.listGroupResourcesWithPermission( + projectCode = projectCode, + filterIamGroupIds = userGroupsJoinedAfterOperatedGroups, + relatedResourceType = ResourceTypeId.PIPELINE, + action = ActionId.PIPELINE_EXECUTE + )[ResourceTypeId.PIPELINE] ?: emptyList() + logger.debug("pipelines with execute perm after operate groups:{}", pipelinesWithExecutePermAfterOperatedGroups) + + val pipelinesWithExecutePermInOperateGroups = groupPermissionService.listGroupResourcesWithPermission( + projectCode = projectCode, + filterIamGroupIds = operatedGroupsWithExecutePerm, + relatedResourceType = ResourceTypeId.PIPELINE, + action = ActionId.PIPELINE_EXECUTE + )[ResourceTypeId.PIPELINE] ?: emptyList() + logger.debug("pipelines with execute perm in operate groups:{}", pipelinesWithExecutePermInOperateGroups) + + val pipelineExecutePermLostFromUser = pipelinesWithExecutePermInOperateGroups.filterNot { + pipelinesWithExecutePermAfterOperatedGroups.contains(it) + } + // 失去代持人权限的流水线 + authAuthorizationDao.list( + dslContext = dslContext, + condition = ResourceAuthorizationConditionRequest( + projectCode = projectCode, + resourceType = ResourceTypeId.PIPELINE, + handoverFrom = memberId, + filterResourceCodes = pipelineExecutePermLostFromUser + ) + ).map { it.resourceCode } + } + logger.debug("pipelines without authorization:{}", pipelinesWithoutAuthorization) + if (pipelinesWithoutAuthorization.isNotEmpty()) { + return InvalidAuthorizationsDTO( + invalidGroupIds = operatedGroupsWithExecutePerm, + invalidPipelineIds = pipelinesWithoutAuthorization + ) + } + } finally { + logger.info( + "It take(${System.currentTimeMillis() - startEpoch})ms to check invalid authorizations after operated groups" + + "|$projectCode|$iamGroupIds|$memberId" + ) + } + return InvalidAuthorizationsDTO(emptyList(), emptyList()) + } + + private fun listInvalidRepositoryAfterOperatedGroups( + projectCode: String, + iamGroupIds: List, + memberId: String + ): List { + // 获取用户退出/交接以上用户组后还加入的用户组 + val (count, records) = listResourceGroupMembers( + projectCode = projectCode, + memberId = memberId, + excludeIamGroupIds = iamGroupIds, + operateChannel = OperateChannel.PERSONAL + ) + + // 如果退出/交接了项目下所有组,直接返回用户无效代码库oauth列表 + if (count == 0L) { + return authAuthorizationDao.list( + dslContext = dslContext, + condition = ResourceAuthorizationConditionRequest( + projectCode = projectCode, + resourceType = ResourceTypeId.REPERTORY, + handoverFrom = memberId + ) + ).map { it.resourceCode } + } + + // 检查用户是否还有权限访问权限当退出/交接以上组后 + val isHasProjectVisitPermOperatedGroups = groupPermissionService.isGroupsHasPermission( + projectCode = projectCode, + filterIamGroupIds = records.map { it.iamGroupId }, + relatedResourceType = ResourceTypeId.PROJECT, + relatedResourceCode = projectCode, + action = ActionId.PROJECT_VISIT + ) + + // 如果有访问权限,返回空列表,否则直接返回用户无效代码库oauth列表 + return if (isHasProjectVisitPermOperatedGroups) { + emptyList() + } else { + authAuthorizationDao.list( + dslContext = dslContext, + condition = ResourceAuthorizationConditionRequest( + projectCode = projectCode, + resourceType = ResourceTypeId.REPERTORY, + handoverFrom = memberId + ) + ).map { it.resourceCode } + } + } + + override fun renewalGroupMember( + userId: String, + projectCode: String, + renewalConditionReq: GroupMemberSingleRenewalReq + ): Boolean { + logger.info("renewal group member $userId|$projectCode|$renewalConditionReq") + val groupId = renewalConditionReq.groupId + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.RENEWAL, + conditionReq = GroupMemberRenewalConditionReq( + groupIds = listOf(groupId), + targetMember = renewalConditionReq.targetMember, + renewalDuration = renewalConditionReq.renewalDuration + ), + operateGroupMemberTask = ::renewalTask + ) + return true + } + + private fun renewalTask( + projectCode: String, + groupId: Int, + renewalConditionReq: GroupMemberRenewalConditionReq, + expiredAt: Long + ) { + logger.info("renewal group member ${renewalConditionReq.targetMember}|$projectCode|$groupId|$expiredAt") + val targetMember = renewalConditionReq.targetMember + if (targetMember.type == MemberType.USER.type && deptService.isUserDeparted(targetMember.id)) { + return + } + val secondsOfRenewalDuration = TimeUnit.DAYS.toSeconds(renewalConditionReq.renewalDuration.toLong()) + val secondsOfCurrentTime = System.currentTimeMillis() / 1000 + // 若权限已过期,则为当前时间+续期天数,若未过期,则为有效期+续期天数 + val finalExpiredAt = if (expiredAt < secondsOfCurrentTime) { + secondsOfCurrentTime + } else { + expiredAt + } + secondsOfRenewalDuration + if (!isNeedToRenewal(finalExpiredAt)) { + return + } + permissionResourceMemberService.renewalIamGroupMembers( + groupId = groupId, + members = listOf(ManagerMember(targetMember.type, targetMember.id)), + expiredAt = finalExpiredAt + ) + authResourceGroupMemberDao.update( + dslContext = dslContext, + projectCode = projectCode, + iamGroupId = groupId, + expiredTime = DateTimeUtil.convertTimestampToLocalDateTime(finalExpiredAt), + memberId = targetMember.id + ) + } + + private fun isNeedToRenewal(expiredAt: Long): Boolean { + return expiredAt < PERMANENT_EXPIRED_TIME + } + + override fun batchRenewalGroupMembersFromManager( + userId: String, + projectCode: String, + renewalConditionReq: GroupMemberRenewalConditionReq + ): Boolean { + logger.info("batch renewal group member $userId|$projectCode|$renewalConditionReq") + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.RENEWAL, + conditionReq = renewalConditionReq, + operateGroupMemberTask = ::renewalTask + ) + return true + } + + override fun batchHandoverGroupMembersFromManager( + userId: String, + projectCode: String, + handoverMemberDTO: GroupMemberHandoverConditionReq + ): Boolean { + logger.info("batch handover group members from manager $userId|$projectCode|$handoverMemberDTO") + handoverMemberDTO.checkHandoverTo() + // 若交接对象是部门,直接进行交接 + if (handoverMemberDTO.targetMember.type == MemberType.DEPARTMENT.type) { + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.HANDOVER, + conditionReq = handoverMemberDTO, + operateGroupMemberTask = ::handoverTask + ) + } + // 若操作对象是用户,需要将被影响流水线授权一并交接给授权人 + val groupIds = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = handoverMemberDTO + )[MemberType.USER] ?: return true + // 获取导致失效的流水线/代码库授权,并进行交接 + val (invalidGroups, invalidPipelines, invalidRepertoryIds) = + listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIds = groupIds, + memberId = handoverMemberDTO.targetMember.id + ) + // 检查授予人是否有代码库oauth权限 + if (invalidRepertoryIds.isNotEmpty()) { + permissionAuthorizationService.checkRepertoryAuthorizationsHanover( + operator = userId, + projectCode = projectCode, + repertoryIds = invalidRepertoryIds, + handoverFrom = handoverMemberDTO.targetMember.id, + handoverTo = handoverMemberDTO.handoverTo.id + ) + } + // 交接用户组 + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.HANDOVER, + conditionReq = handoverMemberDTO, + operateGroupMemberTask = ::handoverTask + ) + + if (invalidRepertoryIds.isNotEmpty()) { + permissionAuthorizationService.resetResourceAuthorizationByResourceType( + operator = userId, + projectCode = projectCode, + condition = ResourceAuthorizationHandoverConditionRequest( + projectCode = projectCode, + resourceType = ResourceTypeId.REPERTORY, + fullSelection = true, + filterResourceCodes = invalidRepertoryIds, + handoverChannel = HandoverChannelCode.MANAGER, + handoverFrom = handoverMemberDTO.targetMember.id, + handoverTo = handoverMemberDTO.handoverTo.id, + checkPermission = false + ) + ) + } + if (invalidPipelines.isNotEmpty()) { + permissionAuthorizationService.resetResourceAuthorizationByResourceType( + operator = userId, + projectCode = projectCode, + condition = ResourceAuthorizationHandoverConditionRequest( + projectCode = projectCode, + resourceType = ResourceTypeId.PIPELINE, + fullSelection = true, + filterResourceCodes = invalidPipelines, + handoverChannel = HandoverChannelCode.MANAGER, + handoverFrom = handoverMemberDTO.targetMember.id, + handoverTo = handoverMemberDTO.handoverTo.id, + checkPermission = false + ) + ) + } + return true + } + + override fun batchHandoverApplicationFromPersonal( + userId: String, + projectCode: String, + handoverMemberDTO: GroupMemberHandoverConditionReq + ): Boolean { + logger.info("batch handover group members from personal $userId|$projectCode|$handoverMemberDTO") + handoverMemberDTO.checkHandoverTo() + // 成员直接加入的组 + val groupIds = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = handoverMemberDTO + )[MemberType.get(MemberType.USER.type)] + if (groupIds.isNullOrEmpty()) { + return true + } + val resourceGroups = authResourceGroupDao.listByRelationId( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = groupIds.map { it.toString() } + ) + // 本次操作导致失效的授权 + val invalidAuthorizations = listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIds = groupIds, + memberId = handoverMemberDTO.targetMember.id + ) + + val invalidPipelines = invalidAuthorizations.invalidPipelineIds + val invalidRepertoryIds = invalidAuthorizations.invalidRepertoryIds + if (invalidRepertoryIds.isNotEmpty()) { + permissionAuthorizationService.checkRepertoryAuthorizationsHanover( + operator = userId, + projectCode = projectCode, + repertoryIds = invalidRepertoryIds, + handoverFrom = handoverMemberDTO.targetMember.id, + handoverTo = handoverMemberDTO.handoverTo.id + ) + } + val handoverDetails = mutableListOf() + val flowNo = permissionHandoverApplicationService.generateFlowNo() + val title = permissionHandoverApplicationService.generateTitle( + groupCount = groupIds.size, + authorizationCount = invalidPipelines.size + invalidRepertoryIds.size + ) + resourceGroups.forEach { groupInfo -> + handoverDetails.add( + HandoverDetailDTO( + projectCode = projectCode, + flowNo = flowNo, + itemId = groupInfo.relationId, + resourceType = groupInfo.resourceType, + handoverType = HandoverType.GROUP + ) + ) + } + invalidPipelines.forEach { pipelineId -> + handoverDetails.add( + HandoverDetailDTO( + projectCode = projectCode, + flowNo = flowNo, + itemId = pipelineId, + resourceType = ResourceTypeId.PIPELINE, + handoverType = HandoverType.AUTHORIZATION + ) + ) + } + invalidRepertoryIds.forEach { repertoryId -> + handoverDetails.add( + HandoverDetailDTO( + projectCode = projectCode, + flowNo = flowNo, + itemId = repertoryId, + resourceType = ResourceTypeId.REPERTORY, + handoverType = HandoverType.AUTHORIZATION + ) + ) + } + // 创建交接单 + permissionHandoverApplicationService.createHandoverApplication( + overview = HandoverOverviewCreateDTO( + projectCode = projectCode, + flowNo = flowNo, + title = title, + applicant = handoverMemberDTO.targetMember.id, + approver = handoverMemberDTO.handoverTo.id, + handoverStatus = HandoverStatus.PENDING, + groupCount = groupIds.size, + authorizationCount = invalidPipelines.size + invalidRepertoryIds.size + ), + details = handoverDetails + ) + return true + } + + override fun batchDeleteResourceGroupMembersFromManager( + userId: String, + projectCode: String, + removeMemberDTO: GroupMemberRemoveConditionReq + ): Boolean { + logger.info("batch delete group members $userId|$projectCode|$removeMemberDTO") + // 若操作对象是组织,则直接退出即可。 + if (removeMemberDTO.targetMember.type == MemberType.DEPARTMENT.type) { + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.REMOVE, + conditionReq = removeMemberDTO, + operateGroupMemberTask = ::deleteTask + ) + return true + } + // 以下逻辑是用户类型成员的批量移出组 + // 根据条件获取成员直接加入的用户组 + val groupIdsDirectlyJoined = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = removeMemberDTO + )[MemberType.USER] ?: return true + // 获取导致流水线代持人权限受到影响的用户组及流水线以及代码库授权 + val (invalidGroups, invalidPipelines, invalidRepertoryIds) = + listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIds = groupIdsDirectlyJoined, + memberId = removeMemberDTO.targetMember.id + ) + if (invalidRepertoryIds.isNotEmpty()) { + permissionAuthorizationService.checkRepertoryAuthorizationsHanover( + operator = userId, + projectCode = projectCode, + repertoryIds = invalidRepertoryIds, + handoverFrom = removeMemberDTO.targetMember.id, + handoverTo = removeMemberDTO.handoverTo!!.id, + ) + } + // 获取唯一管理员组 + val uniqueManagerGroups = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = groupIdsDirectlyJoined + ) + val (toHandoverGroups, toDeleteGroups) = groupIdsDirectlyJoined.partition { + uniqueManagerGroups.contains(it) || invalidGroups.contains(it) + } + // 直接退出的用户组 + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.REMOVE, + conditionReq = GroupMemberRemoveConditionReq( + groupIds = toDeleteGroups, + targetMember = removeMemberDTO.targetMember + ), + operateGroupMemberTask = ::deleteTask + ) + // 交接唯一拥有者、影响代持人权限的用户组以及流水线/代码库授权 + if (toHandoverGroups.isNotEmpty()) { + removeMemberDTO.checkHandoverTo() + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.HANDOVER, + conditionReq = GroupMemberHandoverConditionReq( + groupIds = toHandoverGroups, + targetMember = removeMemberDTO.targetMember, + handoverTo = removeMemberDTO.handoverTo!! + ), + operateGroupMemberTask = ::handoverTask + ) + } + if (invalidPipelines.isNotEmpty()) { + permissionAuthorizationService.resetResourceAuthorizationByResourceType( + operator = userId, + projectCode = projectCode, + condition = ResourceAuthorizationHandoverConditionRequest( + projectCode = projectCode, + resourceType = ResourceTypeId.PIPELINE, + filterResourceCodes = invalidPipelines, + fullSelection = true, + handoverChannel = HandoverChannelCode.MANAGER, + handoverFrom = removeMemberDTO.targetMember.id, + handoverTo = removeMemberDTO.handoverTo!!.id, + checkPermission = false + ) + ) + } + if (invalidRepertoryIds.isNotEmpty()) { + permissionAuthorizationService.resetResourceAuthorizationByResourceType( + operator = userId, + projectCode = projectCode, + condition = ResourceAuthorizationHandoverConditionRequest( + projectCode = projectCode, + resourceType = ResourceTypeId.REPERTORY, + filterResourceCodes = invalidRepertoryIds, + fullSelection = true, + handoverChannel = HandoverChannelCode.MANAGER, + handoverFrom = removeMemberDTO.targetMember.id, + handoverTo = removeMemberDTO.handoverTo!!.id, + checkPermission = false + ) + ) + } + return true + } + + override fun batchDeleteResourceGroupMembersFromPersonal( + userId: String, + projectCode: String, + removeMemberDTO: GroupMemberRemoveConditionReq + ): Boolean { + logger.info("batch delete group members from personal $userId|$projectCode|$removeMemberDTO") + // 根据条件获取成员直接加入的用户组 + val groupIds = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = removeMemberDTO + )[MemberType.USER] ?: return true + // 获取导致流水线代持人权限受到影响的用户组及流水线 + val (invalidGroups, invalidPipelines, invalidRepertoryIds) = + listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIds = groupIds, + memberId = removeMemberDTO.targetMember.id + ) + + // 检查授予人是否有代码库oauth权限 + if (invalidRepertoryIds.isNotEmpty()) { + permissionAuthorizationService.checkRepertoryAuthorizationsHanover( + operator = userId, + projectCode = projectCode, + repertoryIds = invalidRepertoryIds, + handoverFrom = removeMemberDTO.targetMember.id, + handoverTo = removeMemberDTO.handoverTo!!.id, + ) + } + + // 获取唯一管理员组 + val uniqueManagerGroups = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = groupIds + ) + val (toHandoverGroups, toDeleteGroups) = groupIds.partition { + uniqueManagerGroups.contains(it) || invalidGroups.contains(it) + } + // 直接退出的用户组 + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.REMOVE, + conditionReq = GroupMemberRemoveConditionReq( + groupIds = toDeleteGroups, + targetMember = removeMemberDTO.targetMember + ), + operateGroupMemberTask = ::deleteTask + ) + val handoverDetails = mutableListOf() + val flowNo = permissionHandoverApplicationService.generateFlowNo() + // 交接唯一拥有者、影响代持人权限的用户组 + if (toHandoverGroups.isNotEmpty()) { + removeMemberDTO.checkHandoverTo() + val resourceGroups = authResourceGroupDao.listByRelationId( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = toHandoverGroups.map { it.toString() } + ) + resourceGroups.forEach { groupInfo -> + handoverDetails.add( + HandoverDetailDTO( + projectCode = projectCode, + flowNo = flowNo, + itemId = groupInfo.relationId, + resourceType = groupInfo.resourceType, + handoverType = HandoverType.GROUP + ) + ) + } + + } + // 交接流水线代持失效的流水线 + if (invalidPipelines.isNotEmpty()) { + invalidPipelines.forEach { pipelineId -> + handoverDetails.add( + HandoverDetailDTO( + projectCode = projectCode, + flowNo = flowNo, + itemId = pipelineId, + resourceType = ResourceTypeId.PIPELINE, + handoverType = HandoverType.AUTHORIZATION + ) + ) + } + } + // 交接代码库授权失效的代码库 + if (invalidRepertoryIds.isNotEmpty()) { + invalidRepertoryIds.forEach { repertoryId -> + handoverDetails.add( + HandoverDetailDTO( + projectCode = projectCode, + flowNo = flowNo, + itemId = repertoryId, + resourceType = ResourceTypeId.REPERTORY, + handoverType = HandoverType.AUTHORIZATION + ) + ) + } + } + val title = permissionHandoverApplicationService.generateTitle( + groupCount = groupIds.size, + authorizationCount = invalidPipelines.size + invalidRepertoryIds.size + ) + permissionHandoverApplicationService.createHandoverApplication( + overview = HandoverOverviewCreateDTO( + projectCode = projectCode, + flowNo = flowNo, + title = title, + applicant = removeMemberDTO.targetMember.id, + approver = removeMemberDTO.handoverTo!!.id, + handoverStatus = HandoverStatus.PENDING, + groupCount = toHandoverGroups.size, + authorizationCount = invalidPipelines.size + invalidRepertoryIds.size + ), + details = handoverDetails + ) + return true + } + + private fun handoverTask( + projectCode: String, + groupId: Int, + handoverMemberDTO: GroupMemberHandoverConditionReq, + expiredAt: Long + ) { + logger.info( + "handover group member $projectCode|$groupId|" + + "${handoverMemberDTO.targetMember}|${handoverMemberDTO.handoverTo}" + ) + val currentTimeSeconds = System.currentTimeMillis() / 1000 + var finalExpiredAt = expiredAt + when { + // 若权限已过期,如果是唯一管理员组,允许交接,交接人将获得半年权限;其他的直接删除。 + expiredAt < currentTimeSeconds -> { + val isUniqueManagerGroup = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = listOf(groupId) + ).isNotEmpty() + if (isUniqueManagerGroup) { + finalExpiredAt = currentTimeSeconds + TimeUnit.DAYS.toSeconds(180) + } else { + deleteTask( + projectCode = projectCode, + groupId = groupId, + removeMemberDTO = GroupMemberRemoveConditionReq( + targetMember = handoverMemberDTO.targetMember + ), + expiredAt = finalExpiredAt + ) + return + } + } + // 若交接人已经在用户组内,无需交接。 + authResourceGroupMemberDao.isMemberInGroup( + dslContext = dslContext, + projectCode = projectCode, + iamGroupId = groupId, + memberId = handoverMemberDTO.handoverTo.id + ) -> { + deleteTask( + projectCode = projectCode, + groupId = groupId, + removeMemberDTO = GroupMemberRemoveConditionReq( + targetMember = handoverMemberDTO.targetMember + ), + expiredAt = finalExpiredAt + ) + return + } + } + + val members = listOf( + ManagerMember( + handoverMemberDTO.handoverTo.type, + handoverMemberDTO.handoverTo.id + ) + ) + if (finalExpiredAt < currentTimeSeconds) { + throw ErrorCodeException( + errorCode = AuthMessageCode.INVALID_EXPIRED_PERM_NOT_ALLOW_TO_HANDOVER + ) + } + + permissionResourceMemberService.addIamGroupMember( + groupId = groupId, + members = members, + expiredAt = finalExpiredAt + ) + permissionResourceMemberService.deleteIamGroupMembers( + groupId = groupId, + type = handoverMemberDTO.targetMember.type, + memberIds = listOf(handoverMemberDTO.targetMember.id) + ) + authResourceGroupMemberDao.handoverGroupMembers( + dslContext = dslContext, + projectCode = projectCode, + iamGroupId = groupId, + handoverFrom = handoverMemberDTO.targetMember, + handoverTo = handoverMemberDTO.handoverTo, + expiredTime = DateTimeUtil.convertTimestampToLocalDateTime(finalExpiredAt) + ) + } + + private fun deleteTask( + projectCode: String, + groupId: Int, + removeMemberDTO: GroupMemberRemoveConditionReq, + expiredAt: Long + ) { + val targetMember = removeMemberDTO.targetMember + logger.info("delete group member $projectCode|$groupId|$targetMember") + permissionResourceMemberService.deleteIamGroupMembers( + groupId = groupId, + type = targetMember.type, + memberIds = listOf(targetMember.id) + ) + authResourceGroupMemberDao.batchDeleteGroupMembers( + dslContext = dslContext, + projectCode = projectCode, + iamGroupId = groupId, + memberIds = listOf(removeMemberDTO.targetMember.id) + ) + } + + override fun batchOperateGroupMembersCheck( + userId: String, + projectCode: String, + batchOperateType: BatchOperateType, + conditionReq: GroupMemberCommonConditionReq + ): BatchOperateGroupMemberCheckVo { + logger.info("batch operate group member check|$userId|$projectCode|$batchOperateType|$conditionReq") + // 获取成员加入的用户组 + val joinedType2GroupIds = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = conditionReq + ) + // 通过组织或者模板加入的用户组 + val groupsOfTemplateOrDeptJoined = when (conditionReq.targetMember.type) { + MemberType.USER.type -> { + listOfNotNull( + joinedType2GroupIds[MemberType.DEPARTMENT], + joinedType2GroupIds[MemberType.TEMPLATE] + ).flatten() + } + + else -> joinedType2GroupIds[MemberType.TEMPLATE] ?: emptyList() + } + // 直接加入的组 + val groupsOfDirectlyJoined = joinedType2GroupIds[MemberType.get(conditionReq.targetMember.type)] ?: emptyList() + // 总数 + val totalCount = groupsOfTemplateOrDeptJoined.size + groupsOfDirectlyJoined.size + return when (batchOperateType) { + BatchOperateType.REMOVE -> { + if (conditionReq.targetMember.type == MemberType.DEPARTMENT.type) { + BatchOperateGroupMemberCheckVo( + totalCount = totalCount, + operableCount = groupsOfDirectlyJoined.size, + inoperableCount = groupsOfTemplateOrDeptJoined.size + ) + } else { + val groupsOfUniqueManager = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = groupsOfDirectlyJoined + ) + // 本次操作导致流水线代持人权限受到影响的用户组及流水线/代码库oauth + val (invalidGroups, invalidPipelines, invalidRepositoryIds) = + listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIds = groupsOfDirectlyJoined, + memberId = conditionReq.targetMember.id + ) + // 当批量移出时, + // 直接加入的组中,唯一管理员组/影响流水线代持权限不允许被移出 + // 间接加入的组中,通过组织、模板加入的组不允许被移出 + val groupsOfInOperableWhenBatchRemove = groupsOfDirectlyJoined.count { + groupsOfUniqueManager.contains(it) || invalidGroups.contains(it) + } + groupsOfTemplateOrDeptJoined.size + + BatchOperateGroupMemberCheckVo( + totalCount = totalCount, + operableCount = totalCount - groupsOfInOperableWhenBatchRemove, + inoperableCount = groupsOfInOperableWhenBatchRemove, + uniqueManagerCount = groupsOfUniqueManager.size, + invalidGroupCount = invalidGroups.size, + invalidPipelineAuthorizationCount = invalidPipelines.size, + invalidRepositoryAuthorizationCount = invalidRepositoryIds.size + ) + } + } + + BatchOperateType.RENEWAL -> { + // 部门/组织加入以及永久权限的组不允许再续期 + with(conditionReq) { + val isUserDeparted = targetMember.type == MemberType.USER.type && + deptService.isUserDeparted(targetMember.id) + // 离职用户不允许续期 + if (isUserDeparted) { + BatchOperateGroupMemberCheckVo( + totalCount = totalCount, + inoperableCount = totalCount + ) + } else { + // 永久期限 不允许再续期 + val groupCountOfPermanentExpiredTime = listMemberGroupsDetails( + projectCode = projectCode, + memberId = targetMember.id, + memberType = targetMember.type, + groupIds = groupsOfDirectlyJoined + ).filter { + // iam用的是秒级时间戳 + it.expiredAt == PERMANENT_EXPIRED_TIME / 1000 + }.size + val groupsOfInOperableWhenBatchRenewal = groupCountOfPermanentExpiredTime + groupsOfTemplateOrDeptJoined.size + BatchOperateGroupMemberCheckVo( + totalCount = totalCount, + operableCount = totalCount - groupsOfInOperableWhenBatchRenewal, + inoperableCount = groupsOfInOperableWhenBatchRenewal + ) + } + } + } + + BatchOperateType.HANDOVER -> { + // 已过期(除唯一管理员组 )或通过模板/组织加入的不允许移交 + with(conditionReq) { + val finalGroupIds = groupsOfDirectlyJoined.toMutableList() + val uniqueManagerGroupIds = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = groupsOfDirectlyJoined + ) + // 去除唯一管理员组 + if (uniqueManagerGroupIds.isNotEmpty()) { + finalGroupIds.removeAll(uniqueManagerGroupIds) + } + val groupCountOfExpired = listMemberGroupsDetails( + projectCode = projectCode, + memberId = targetMember.id, + memberType = targetMember.type, + groupIds = finalGroupIds + ).filter { + // iam用的是秒级时间戳 + it.expiredAt < System.currentTimeMillis() / 1000 + }.size + val inoperableCount = groupsOfTemplateOrDeptJoined.size + groupCountOfExpired + // 本次操作导致流水线代持人权限受到影响的流水线 + val (invalidGroups, invalidPipelines, invalidRepositoryIds) = + listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIds = groupsOfDirectlyJoined, + memberId = conditionReq.targetMember.id + ) + + BatchOperateGroupMemberCheckVo( + totalCount = totalCount, + operableCount = totalCount - inoperableCount, + inoperableCount = groupsOfTemplateOrDeptJoined.size + groupCountOfExpired, + invalidPipelineAuthorizationCount = invalidPipelines.size, + invalidRepositoryAuthorizationCount = invalidRepositoryIds.size + ) + } + } + + else -> { + BatchOperateGroupMemberCheckVo( + totalCount = totalCount, + inoperableCount = groupsOfTemplateOrDeptJoined.size, + operableCount = groupsOfDirectlyJoined.size + ) + } + } + } + + override fun removeMemberFromProject( + userId: String, + projectCode: String, + removeMemberFromProjectReq: RemoveMemberFromProjectReq + ): List { + logger.info("remove member from project $userId|$projectCode|$removeMemberFromProjectReq") + return with(removeMemberFromProjectReq) { + val memberType = targetMember.type + val isNeedToHandover = handoverTo != null + if (memberType == MemberType.USER.type && isNeedToHandover) { + removeMemberFromProjectReq.checkHandoverTo() + val handoverMemberDTO = GroupMemberHandoverConditionReq( + allSelection = true, + targetMember = targetMember, + handoverTo = handoverTo!! + ) + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.HANDOVER, + conditionReq = handoverMemberDTO, + operateGroupMemberTask = ::handoverTask + ) + permissionAuthorizationService.resetAllResourceAuthorization( + operator = userId, + projectCode = projectCode, + condition = ResetAllResourceAuthorizationReq( + projectCode = projectCode, + handoverFrom = removeMemberFromProjectReq.targetMember.id, + handoverTo = removeMemberFromProjectReq.handoverTo!!.id, + preCheck = false, + checkPermission = false + ) + ) + } else { + val removeMemberDTO = GroupMemberRemoveConditionReq( + allSelection = true, + targetMember = targetMember + ) + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.REMOVE, + conditionReq = removeMemberDTO, + operateGroupMemberTask = ::deleteTask + ) + } + + if (memberType == MemberType.USER.type) { + // 查询用户还存在那些组织中 + val userDeptInfos = deptService.getUserInfo( + userId = "admin", + name = targetMember.id + )?.deptInfo?.map { it.name!! } + if (userDeptInfos != null) { + return authResourceGroupMemberDao.isMembersInProject( + dslContext = dslContext, + projectCode = projectCode, + memberNames = userDeptInfos, + memberType = MemberType.DEPARTMENT.type + ) + } + } + return emptyList() + } + } + + override fun removeMemberFromProjectCheck( + userId: String, + projectCode: String, + removeMemberFromProjectReq: RemoveMemberFromProjectReq + ): Boolean { + val targetMember = removeMemberFromProjectReq.targetMember + val isMemberHasNoPermission = batchOperateGroupMembersCheck( + userId = userId, + projectCode = projectCode, + batchOperateType = BatchOperateType.HANDOVER, + conditionReq = GroupMemberCommonConditionReq( + allSelection = true, + targetMember = removeMemberFromProjectReq.targetMember + ) + ).let { it.totalCount == it.inoperableCount } + + val isMemberHasNoAuthorizations = + if (targetMember.type == MemberType.USER.type) { + permissionAuthorizationService.listResourceAuthorizations( + condition = ResourceAuthorizationConditionRequest( + projectCode = projectCode, + handoverFrom = targetMember.id + ) + ).count == 0L + } else { + true + } + return isMemberHasNoPermission && isMemberHasNoAuthorizations + } + + override fun handleHanoverApplication(request: HandoverOverviewUpdateReq): Boolean { + val overview = permissionHandoverApplicationService.getHandoverOverview(request.flowNo) + logger.info("revoke hanover application:{}|{} ", request, overview) + HandleHandoverApplicationLock(redisOperation, request.flowNo).use { lock -> + if (!lock.tryLock()) { + logger.warn("The handover application is being processed!$request") + throw ErrorCodeException(errorCode = ERROR_HANDOVER_HANDLE) + } + try { + handleHanoverCheck(request = request, overview = overview) + if (request.handoverAction == HandoverAction.AGREE) { + handleHandoverAgreeAction( + request = request, + overview = overview + ) + } + permissionHandoverApplicationService.updateHandoverApplication( + overview = request + ) + } catch (e: Exception) { + logger.warn("handle hanover application error,$e|$request") + throw e + } + } + return true + } + + override fun getResourceType2CountOfHandover(queryReq: ResourceType2CountOfHandoverQuery): List { + queryReq.check() + return if (queryReq.queryChannel == HandoverQueryChannel.HANDOVER_APPLICATION) { + permissionHandoverApplicationService.getResourceType2CountOfHandoverApplication(queryReq.flowNo!!) + } else { + getResourceType2CountOfHandoverPreview(queryReq) + } + } + + // 交接预览 + private fun getResourceType2CountOfHandoverPreview(queryReq: ResourceType2CountOfHandoverQuery): List { + val projectCode = queryReq.projectCode + val previewConditionReq = queryReq.previewConditionReq!! + val batchOperateType = queryReq.batchOperateType!! + val groupIdsDirectlyJoined = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = previewConditionReq + )[MemberType.USER] ?: return emptyList() + + val result = mutableListOf() + val (invalidGroups, invalidPipelines, invalidRepertoryIds) = listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIds = groupIdsDirectlyJoined, + memberId = previewConditionReq.targetMember.id + ) + if (batchOperateType == BatchOperateType.REMOVE) { + // 只有一个成员的管理员组 + val uniqueManagerGroups = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = groupIdsDirectlyJoined + ) + val needToHandoverGroupIds = invalidGroups.union(uniqueManagerGroups).map { it.toString() } + val resourceType2CountOfGroup = authResourceGroupDao.getResourceType2Count( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = needToHandoverGroupIds + ) + if (resourceType2CountOfGroup.isNotEmpty()) { + result.addAll( + rbacCacheService.convertResourceType2Count( + resourceType2Count = resourceType2CountOfGroup, + type = HandoverType.GROUP + ) + ) + } + } + if (invalidPipelines.isNotEmpty()) { + result.addAll( + rbacCacheService.convertResourceType2Count( + resourceType2Count = mapOf(ResourceTypeId.PIPELINE to invalidPipelines.size.toLong()), + type = HandoverType.AUTHORIZATION + ) + ) + } + if (invalidRepertoryIds.isNotEmpty()) { + result.addAll( + rbacCacheService.convertResourceType2Count( + resourceType2Count = mapOf(ResourceTypeId.REPERTORY to invalidRepertoryIds.size.toLong()), + type = HandoverType.AUTHORIZATION + ) + ) + } + return result + } + + override fun listAuthorizationsOfHandover(queryReq: HandoverDetailsQueryReq): SQLPage { + queryReq.check() + return if (queryReq.queryChannel == HandoverQueryChannel.HANDOVER_APPLICATION) { + permissionHandoverApplicationService.listAuthorizationsOfHandoverApplication(queryReq) + } else { + listAuthorizationsOfHandoverPreview(queryReq) + } + } + + private fun listAuthorizationsOfHandoverPreview(queryReq: HandoverDetailsQueryReq): SQLPage { + val projectCode = queryReq.projectCode + val previewConditionReq = queryReq.previewConditionReq!! + val groupIdsDirectlyJoined = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = previewConditionReq + )[MemberType.USER] ?: return SQLPage(0, emptyList()) + val (invalidGroups, invalidPipelines, invalidRepertoryIds) = listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIds = groupIdsDirectlyJoined, + memberId = previewConditionReq.targetMember.id + ) + + val invalidResources = if (queryReq.resourceType == ResourceTypeId.PIPELINE) { + invalidPipelines + } else { + invalidRepertoryIds + } + val records = authorizationDao.list( + dslContext = dslContext, + condition = ResourceAuthorizationConditionRequest( + projectCode = projectCode, + resourceType = queryReq.resourceType, + filterResourceCodes = invalidResources, + page = queryReq.page, + pageSize = queryReq.pageSize + ) + ).map { + HandoverAuthorizationDetailVo( + resourceCode = it.resourceCode, + resourceName = it.resourceName, + handoverType = HandoverType.AUTHORIZATION, + handoverFrom = it.handoverFrom + ) + } + return SQLPage(count = invalidResources.size.toLong(), records = records) + } + + override fun listGroupsOfHandover(queryReq: HandoverDetailsQueryReq): SQLPage { + queryReq.check() + return if (queryReq.queryChannel == HandoverQueryChannel.HANDOVER_APPLICATION) { + permissionHandoverApplicationService.listGroupsOfHandoverApplication(queryReq) + } else { + listGroupsOfHandoverPreview(queryReq) + } + } + + private fun listGroupsOfHandoverPreview(queryReq: HandoverDetailsQueryReq): SQLPage { + val projectCode = queryReq.projectCode + val previewConditionReq = queryReq.previewConditionReq!! + val convertPageSizeToSQLLimit = PageUtil.convertPageSizeToSQLLimit(queryReq.page, queryReq.pageSize) + val groupIdsDirectlyJoined = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = previewConditionReq + )[MemberType.USER] ?: return SQLPage(0, emptyList()) + val invalidGroupIds = listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIds = groupIdsDirectlyJoined, + memberId = previewConditionReq.targetMember.id + ).invalidGroupIds + // 只有一个成员的管理员组 + val uniqueManagerGroups = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = groupIdsDirectlyJoined + ) + val needToHandoverGroupIds = invalidGroupIds.union(uniqueManagerGroups).map { it.toString() } + val records = authResourceGroupDao.listGroupByResourceType( + dslContext = dslContext, + projectCode = projectCode, + resourceType = queryReq.resourceType, + iamGroupIds = needToHandoverGroupIds, + offset = convertPageSizeToSQLLimit.offset, + limit = convertPageSizeToSQLLimit.limit + ).map { + HandoverGroupDetailVo( + projectCode = it.projectCode, + iamGroupId = it.relationId, + groupName = it.groupName, + groupDesc = it.description, + resourceCode = it.resourceCode, + resourceName = it.resourceName + ) + } + return SQLPage(count = invalidGroupIds.size.toLong(), records = records) + } + + private fun handleHanoverCheck( + request: HandoverOverviewUpdateReq, + overview: HandoverOverviewVo + ) { + if (overview.handoverStatus != HandoverStatus.PENDING) { + throw ErrorCodeException(errorCode = ERROR_HANDOVER_FINISH) + } + if (request.handoverAction == HandoverAction.REVOKE && request.operator != overview.applicant) { + throw ErrorCodeException(errorCode = ERROR_HANDOVER_REVOKE) + } + if (request.handoverAction != HandoverAction.REVOKE && request.operator != overview.approver) { + throw ErrorCodeException(errorCode = ERROR_HANDOVER_APPROVAL) + } + } + + private fun handleHandoverAgreeAction( + request: HandoverOverviewUpdateReq, + overview: HandoverOverviewVo + ) { + val handoverDetails = permissionHandoverApplicationService.listHandoverDetails( + projectCode = overview.projectCode, + flowNo = overview.flowNo + ) + val handoverType2Records = handoverDetails.groupBy { it.handoverType } + + // 交接用户组 + val groupsOfHandover = handoverType2Records[HandoverType.GROUP]?.map { it.itemId.toInt() } + if (!groupsOfHandover.isNullOrEmpty()) { + val targetMember = ResourceMemberInfo( + id = overview.applicant, + name = deptService.getMemberInfo(overview.applicant, ManagerScopesEnum.USER).displayName, + type = MemberType.USER.type + ) + val handoverTo = ResourceMemberInfo( + id = overview.approver, + name = deptService.getMemberInfo(overview.approver, ManagerScopesEnum.USER).displayName, + type = MemberType.USER.type + ) + + val groupMemberHandoverConditionReq = GroupMemberHandoverConditionReq( + groupIds = groupsOfHandover, + targetMember = targetMember, + handoverTo = handoverTo + ) + batchOperateGroupMembers( + projectCode = overview.projectCode, + type = BatchOperateType.HANDOVER, + conditionReq = groupMemberHandoverConditionReq, + operateGroupMemberTask = ::handoverTask + ) + } + + // 交接授权 + val authorizationsOfHandover = handoverType2Records[HandoverType.AUTHORIZATION] + if (!authorizationsOfHandover.isNullOrEmpty()) { + val resourceType2Authorizations = authorizationsOfHandover.groupBy { it.resourceType } + resourceType2Authorizations.forEach { (resourceType, authorizations) -> + permissionAuthorizationService.resetResourceAuthorizationByResourceType( + operator = request.operator, + projectCode = overview.projectCode, + condition = ResourceAuthorizationHandoverConditionRequest( + projectCode = overview.projectCode, + resourceType = resourceType, + filterResourceCodes = authorizations.map { it.itemId }, + fullSelection = true, + handoverChannel = HandoverChannelCode.MANAGER, + handoverFrom = overview.applicant, + handoverTo = overview.approver, + checkPermission = false + ) + ) + } + } + } + + private fun batchOperateGroupMembers( + projectCode: String, + conditionReq: T, + type: BatchOperateType, + operateGroupMemberTask: ( + projectCode: String, + groupId: Int, + conditionReq: T, + expiredAt: Long + ) -> Unit + ): Boolean { + val startEpoch = System.currentTimeMillis() + try { + // 成员直接加入的组 + val groupIds = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = conditionReq + )[MemberType.get(conditionReq.targetMember.type)] + if (groupIds.isNullOrEmpty()) { + return true + } + + val targetMember = conditionReq.targetMember + val memberGroupsDetailsList = listMemberGroupsDetails( + projectCode = projectCode, + memberId = targetMember.id, + memberType = targetMember.type, + groupIds = groupIds + ) + val outOfSyncGroupIds = mutableListOf() + val futures = groupIds.map { groupId -> + CompletableFuture.supplyAsync( + { + val memberGroupsDetails = memberGroupsDetailsList.firstOrNull { it.id == groupId } + if (memberGroupsDetails == null) { + logger.warn("The data is out of sync, and the record no longer exists in the iam.$groupId") + outOfSyncGroupIds.add(groupId) + return@supplyAsync + } + val expiredAt = memberGroupsDetails.expiredAt + RetryUtils.retry(3) { + operateGroupMemberTask.invoke( + projectCode, + groupId, + conditionReq, + expiredAt + ) + } + }, executorService + ) + } + handleFutures( + projectCode = projectCode, + outOfSyncGroupIds = outOfSyncGroupIds, + futures = futures + ) + } finally { + "It take(${System.currentTimeMillis() - startEpoch})ms to $type group members|$projectCode|$conditionReq" + } + return true + } + + private fun listMemberGroupsDetails( + projectCode: String, + memberId: String, + memberType: String, + groupIds: List + ): List { + val memberGroupsDetailsList = mutableListOf() + val groupIdsChunk = groupIds.chunked(100) + val futures = groupIdsChunk.map { + CompletableFuture.supplyAsync( + { + memberGroupsDetailsList.addAll( + // 若离职,则从数据库获取用户加入组的过期时间,调用iam接口会报错。 + // 虽然数据库的过期时间可能不是最新的。 + if (memberType == MemberType.USER.type && deptService.isUserDeparted(memberId)) { + val records = authResourceGroupMemberDao.listMemberGroupDetail( + dslContext = dslContext, + projectCode = projectCode, + memberId = memberId, + iamTemplateIds = emptyList(), + iamGroupIds = it + ) + records.map { record -> + MemberGroupDetailsResponse().apply { + id = record.iamGroupId + expiredAt = record.expiredTime.timestamp() + } + } + } else { + iamV2ManagerService.listMemberGroupsDetails( + memberType, + memberId, + it.joinToString(",") + ) + } + ) + }, executorService + ) + } + try { + CompletableFuture.allOf(*futures.toTypedArray()).join() + } catch (ignore: Exception) { + logger.warn("list member groups details failed!$ignore") + throw ignore + } + return memberGroupsDetailsList + } + + private fun handleFutures( + projectCode: String, + outOfSyncGroupIds: List, + futures: List> + ) { + try { + CompletableFuture.allOf(*futures.toTypedArray()).join() + // 存在iam那边已经把用户组下成员删除,但蓝盾数据库未同步问题 + outOfSyncGroupIds.forEach { + syncIamGroupMemberService.syncIamGroupMember( + projectCode = projectCode, + iamGroupId = it + ) + } + } catch (ignore: Exception) { + logger.warn("batch operate group members failed", ignore) + throw ErrorCodeException( + errorCode = AuthMessageCode.ERROR_BATCH_OPERATE_GROUP_MEMBERS + ) + } + } + + companion object { + private val logger = LoggerFactory.getLogger(RbacPermissionResourceMemberService::class.java) + + private val executorService = Executors.newFixedThreadPool(30) + + // 永久过期时间 + private const val PERMANENT_EXPIRED_TIME = 4102444800000L + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupAndMemberFacadeServiceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupAndMemberFacadeServiceImpl.kt deleted file mode 100644 index 79229b6946b..00000000000 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupAndMemberFacadeServiceImpl.kt +++ /dev/null @@ -1,423 +0,0 @@ -package com.tencent.devops.auth.provider.rbac.service - -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum -import com.tencent.bk.sdk.iam.dto.response.MemberGroupDetailsResponse -import com.tencent.bk.sdk.iam.service.v2.V2ManagerService -import com.tencent.devops.auth.constant.AuthI18nConstants -import com.tencent.devops.auth.dao.AuthResourceGroupDao -import com.tencent.devops.auth.dao.AuthResourceGroupMemberDao -import com.tencent.devops.auth.pojo.AuthResourceGroupMember -import com.tencent.devops.auth.pojo.ResourceMemberInfo -import com.tencent.devops.auth.pojo.dto.IamGroupIdsQueryConditionDTO -import com.tencent.devops.auth.pojo.dto.ProjectMembersQueryConditionDTO -import com.tencent.devops.auth.pojo.enum.JoinedType -import com.tencent.devops.auth.pojo.enum.RemoveMemberButtonControl -import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq -import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo -import com.tencent.devops.auth.pojo.vo.MemberGroupCountWithPermissionsVo -import com.tencent.devops.auth.service.DeptService -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService -import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService -import com.tencent.devops.auth.service.iam.PermissionResourceGroupService -import com.tencent.devops.auth.service.iam.PermissionResourceMemberService -import com.tencent.devops.common.api.model.SQLPage -import com.tencent.devops.common.api.util.DateTimeUtil -import com.tencent.devops.common.api.util.timestampmilli -import com.tencent.devops.common.auth.api.AuthResourceType -import com.tencent.devops.common.web.utils.I18nUtil -import com.tencent.devops.model.auth.tables.records.TAuthResourceGroupRecord -import org.jooq.DSLContext -import org.slf4j.LoggerFactory -import java.util.concurrent.TimeUnit - -class RbacPermissionResourceGroupAndMemberFacadeServiceImpl( - private val permissionResourceGroupService: PermissionResourceGroupService, - private val groupPermissionService: PermissionResourceGroupPermissionService, - private val permissionResourceMemberService: PermissionResourceMemberService, - private val authResourceGroupDao: AuthResourceGroupDao, - private val authResourceGroupMemberDao: AuthResourceGroupMemberDao, - private val dslContext: DSLContext, - private val deptService: DeptService, - private val iamV2ManagerService: V2ManagerService, - private val rbacCacheService: RbacCacheService -) : PermissionResourceGroupAndMemberFacadeService { - override fun getMemberGroupsDetails( - projectId: String, - memberId: String, - resourceType: String?, - iamGroupIds: List?, - groupName: String?, - minExpiredAt: Long?, - maxExpiredAt: Long?, - relatedResourceType: String?, - relatedResourceCode: String?, - action: String?, - start: Int?, - limit: Int? - ): SQLPage { - // 根据查询条件查询得到iam组id - val iamGroupIdsByConditions = listIamGroupIdsByConditions( - condition = IamGroupIdsQueryConditionDTO( - projectCode = projectId, - groupName = groupName, - iamGroupIds = iamGroupIds, - relatedResourceType = relatedResourceType, - relatedResourceCode = relatedResourceCode, - action = action - ) - ) - // 查询成员所在资源用户组列表,直接加入+通过用户组(模板)加入 - val (count, resourceGroupMembers) = permissionResourceMemberService.listResourceGroupMembers( - projectCode = projectId, - memberId = memberId, - resourceType = resourceType, - iamGroupIds = iamGroupIdsByConditions, - minExpiredAt = minExpiredAt, - maxExpiredAt = maxExpiredAt, - start = start, - limit = limit - ) - // 用户组对应的资源信息 - val resourceGroupMap = authResourceGroupDao.listByRelationId( - dslContext = dslContext, - projectCode = projectId, - iamGroupIds = resourceGroupMembers.map { it.iamGroupId.toString() } - ).associateBy { it.relationId } - // 只有一个成员的管理员组 - val uniqueManagerGroups = authResourceGroupMemberDao.listProjectUniqueManagerGroups( - dslContext = dslContext, - projectCode = projectId, - iamGroupIds = resourceGroupMembers.map { it.iamGroupId } - ) - // 用户组成员详情 - val groupMemberDetailMap = getGroupMemberDetailMap( - memberId = memberId, - resourceGroupMembers = resourceGroupMembers - ) - val records = mutableListOf() - resourceGroupMembers.forEach { - val resourceGroup = resourceGroupMap[it.iamGroupId.toString()]!! - val groupMemberDetail = groupMemberDetailMap["${it.iamGroupId}_${it.memberId}"] - records.add( - convertGroupDetailsInfoVo( - resourceGroup = resourceGroup, - groupMemberDetail = groupMemberDetail, - uniqueManagerGroups = uniqueManagerGroups, - authResourceGroupMember = it - ) - ) - } - return SQLPage(count = count, records = records) - } - - private fun getGroupMemberDetailMap( - memberId: String, - resourceGroupMembers: List - ): Map { - // 如果用户离职,查询权限中心接口会报错 - if (deptService.isUserDeparted(memberId)) { - return emptyMap() - } - // 用户组成员详情 - val groupMemberDetailMap = mutableMapOf() - // 直接加入的用户 - val userGroupIds = resourceGroupMembers - .filter { it.memberType == ManagerScopesEnum.getType(ManagerScopesEnum.USER) } - .map { it.iamGroupId } - if (userGroupIds.isNotEmpty()) { - iamV2ManagerService.listMemberGroupsDetails( - ManagerScopesEnum.getType(ManagerScopesEnum.USER), - memberId, - userGroupIds.joinToString(",") - ).forEach { - groupMemberDetailMap["${it.id}_$memberId"] = it - } - } - // 直接加入的组织 - val deptGroupIds = resourceGroupMembers - .filter { it.memberType == ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT) } - .map { it.iamGroupId } - if (deptGroupIds.isNotEmpty()) { - iamV2ManagerService.listMemberGroupsDetails( - ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT), - memberId, - deptGroupIds.joinToString(",") - ).forEach { - groupMemberDetailMap["${it.id}_$memberId"] = it - } - } - // 人员模板加入的组 - resourceGroupMembers.filter { it.memberType == ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE) } - .groupBy({ it.memberId }, { it.iamGroupId.toString() }) - .forEach { (iamTemplateId, iamGroupIds) -> - if (iamGroupIds.isEmpty()) return@forEach - iamV2ManagerService.listMemberGroupsDetails( - ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE), - iamTemplateId, - iamGroupIds.joinToString(",") - ).forEach { - groupMemberDetailMap["${it.id}_$iamTemplateId"] = it - } - } - return groupMemberDetailMap - } - - private fun convertGroupDetailsInfoVo( - resourceGroup: TAuthResourceGroupRecord, - groupMemberDetail: MemberGroupDetailsResponse?, - uniqueManagerGroups: List, - authResourceGroupMember: AuthResourceGroupMember - ): GroupDetailsInfoVo { - // 如果用户离职,查询权限中心接口会报错,因此从数据库直接取数据,而不去调用权限中心接口。 - val (expiredAt, joinedTime) = if (groupMemberDetail != null) { - Pair( - TimeUnit.SECONDS.toMillis(groupMemberDetail.expiredAt), - TimeUnit.SECONDS.toMillis(groupMemberDetail.createdAt) - ) - } else { - Pair( - authResourceGroupMember.expiredTime.timestampmilli(), - 0L - ) - } - val between = expiredAt - System.currentTimeMillis() - return GroupDetailsInfoVo( - resourceCode = resourceGroup.resourceCode, - resourceName = resourceGroup.resourceName, - resourceType = resourceGroup.resourceType, - groupId = resourceGroup.relationId.toInt(), - groupName = resourceGroup.groupName, - groupDesc = resourceGroup.description, - expiredAtDisplay = when { - expiredAt == PERMANENT_EXPIRED_TIME -> - I18nUtil.getCodeLanMessage(messageCode = AuthI18nConstants.BK_MEMBER_EXPIRED_AT_DISPLAY_PERMANENT) - - between >= 0 -> I18nUtil.getCodeLanMessage( - messageCode = AuthI18nConstants.BK_MEMBER_EXPIRED_AT_DISPLAY_NORMAL, - params = arrayOf(DateTimeUtil.formatDay(between)) - ) - - else -> I18nUtil.getCodeLanMessage( - messageCode = AuthI18nConstants.BK_MEMBER_EXPIRED_AT_DISPLAY_EXPIRED - ) - }, - expiredAt = expiredAt, - joinedTime = joinedTime, - removeMemberButtonControl = when { - authResourceGroupMember.memberType == ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE) -> - RemoveMemberButtonControl.TEMPLATE - - resourceGroup.resourceType == AuthResourceType.PROJECT.value && - uniqueManagerGroups.contains(authResourceGroupMember.iamGroupId) -> - RemoveMemberButtonControl.UNIQUE_MANAGER - - uniqueManagerGroups.contains(authResourceGroupMember.iamGroupId) -> - RemoveMemberButtonControl.UNIQUE_OWNER - - else -> - RemoveMemberButtonControl.OTHER - }, - joinedType = when (authResourceGroupMember.memberType) { - ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE) -> JoinedType.TEMPLATE - else -> JoinedType.DIRECT - }, - operator = "" - ) - } - - override fun getMemberGroupsCount( - projectCode: String, - memberId: String, - groupName: String?, - minExpiredAt: Long?, - maxExpiredAt: Long?, - relatedResourceType: String?, - relatedResourceCode: String?, - action: String? - ): List { - // 查询项目下包含该成员的组列表 - val projectGroupIds = authResourceGroupMemberDao.listResourceGroupMember( - dslContext = dslContext, - projectCode = projectCode, - resourceType = AuthResourceType.PROJECT.value, - memberId = memberId - ).map { it.iamGroupId.toString() } - // 通过项目组ID获取人员模板ID - val iamTemplateId = authResourceGroupDao.listByRelationId( - dslContext = dslContext, - projectCode = projectCode, - iamGroupIds = projectGroupIds - ).filter { it.iamTemplateId != null } - .map { it.iamTemplateId.toString() } - - val iamGroupIdsByConditions = listIamGroupIdsByConditions( - condition = IamGroupIdsQueryConditionDTO( - projectCode = projectCode, - groupName = groupName, - relatedResourceType = relatedResourceType, - relatedResourceCode = relatedResourceCode, - action = action - ) - ) - // 获取成员直接加入的组和通过模板加入的组 - val memberGroupCountMap = authResourceGroupMemberDao.countMemberGroup( - dslContext = dslContext, - projectCode = projectCode, - memberId = memberId, - iamTemplateIds = iamTemplateId, - iamGroupIds = iamGroupIdsByConditions, - minExpiredAt = minExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) }, - maxExpiredAt = maxExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) } - ) - val memberGroupCountList = mutableListOf() - // 项目排在第一位 - memberGroupCountMap[AuthResourceType.PROJECT.value]?.let { projectCount -> - memberGroupCountList.add( - MemberGroupCountWithPermissionsVo( - resourceType = AuthResourceType.PROJECT.value, - resourceTypeName = I18nUtil.getCodeLanMessage( - messageCode = AuthResourceType.PROJECT.value + AuthI18nConstants.RESOURCE_TYPE_NAME_SUFFIX - ), - count = projectCount - ) - ) - } - - rbacCacheService.listResourceTypes() - .filter { it.resourceType != AuthResourceType.PROJECT.value } - .forEach { resourceTypeInfoVo -> - memberGroupCountMap[resourceTypeInfoVo.resourceType]?.let { count -> - val memberGroupCount = MemberGroupCountWithPermissionsVo( - resourceType = resourceTypeInfoVo.resourceType, - resourceTypeName = I18nUtil.getCodeLanMessage( - messageCode = resourceTypeInfoVo.resourceType + AuthI18nConstants.RESOURCE_TYPE_NAME_SUFFIX, - defaultMessage = resourceTypeInfoVo.name - ), - count = count - ) - memberGroupCountList.add(memberGroupCount) - } - } - - return memberGroupCountList - } - - override fun listIamGroupIdsByConditions(condition: IamGroupIdsQueryConditionDTO): List { - return with(condition) { - val filterGroupsByGroupName = if (isQueryByGroupName()) { - permissionResourceGroupService.listIamGroupIdsByGroupName( - projectId = projectCode, - groupName = groupName!! - ) - } else { - emptyList() - } - val finalGroupIds = if (isQueryByGroupPermissions()) { - groupPermissionService.listGroupsByPermissionConditions( - projectCode = projectCode, - filterIamGroupIds = filterGroupsByGroupName, - relatedResourceType = relatedResourceType!!, - relatedResourceCode = relatedResourceCode, - action = action - ) - } else { - filterGroupsByGroupName - }.toMutableList() - iamGroupIds?.let { finalGroupIds.addAll(it) } - finalGroupIds - } - } - - override fun listProjectMembersByComplexConditions( - conditionReq: ProjectMembersQueryConditionReq - ): SQLPage { - logger.info("list project members by complex conditions: $conditionReq") - // 不允许同时查询部门名称和用户名称 - if (conditionReq.userName != null && conditionReq.deptName != null) { - return SQLPage(count = 0, records = emptyList()) - } - - // 简单查询直接返回结果 - if (!conditionReq.isComplexQuery()) { - return permissionResourceMemberService.listProjectMembers( - projectCode = conditionReq.projectCode, - memberType = conditionReq.memberType, - userName = conditionReq.userName, - deptName = conditionReq.deptName, - departedFlag = conditionReq.departedFlag, - page = conditionReq.page, - pageSize = conditionReq.pageSize - ) - } - - // 处理复杂查询条件 - val iamGroupIdsByCondition = if (conditionReq.isNeedToQueryIamGroups()) { - listIamGroupIdsByConditions( - condition = IamGroupIdsQueryConditionDTO( - projectCode = conditionReq.projectCode, - groupName = conditionReq.groupName, - relatedResourceType = conditionReq.relatedResourceType, - relatedResourceCode = conditionReq.relatedResourceCode, - action = conditionReq.action - ) - ) - } else { - emptyList() - }.toMutableList() - - // 查询不到用户组,直接返回空 - if (conditionReq.isNeedToQueryIamGroups() && iamGroupIdsByCondition.isEmpty()) { - return SQLPage(0, emptyList()) - } - - val conditionDTO = ProjectMembersQueryConditionDTO.build(conditionReq, iamGroupIdsByCondition) - - if (iamGroupIdsByCondition.isNotEmpty()) { - logger.debug("iamGroupIdsByCondition :{}", iamGroupIdsByCondition) - // 根据用户组Id查询出对应用户组中的人员模板成员 - val iamTemplateIds = authResourceGroupMemberDao.listProjectMembersByComplexConditions( - dslContext = dslContext, - conditionDTO = ProjectMembersQueryConditionDTO( - projectCode = conditionDTO.projectCode, - queryTemplate = true, - iamGroupIds = conditionDTO.iamGroupIds - ) - ) - if (iamTemplateIds.isNotEmpty()) { - // 根据查询出的人员模板ID,查询出对应的组ID - val iamGroupIdsFromTemplate = authResourceGroupDao.listIamGroupIdsByConditions( - dslContext = dslContext, - projectCode = conditionDTO.projectCode, - iamTemplateIds = iamTemplateIds.map { it.id.toInt() } - ) - iamGroupIdsByCondition.addAll(iamGroupIdsFromTemplate) - logger.debug("iamGroupIdsByCondition and template :{}", iamGroupIdsByCondition) - } - } - - val records = authResourceGroupMemberDao.listProjectMembersByComplexConditions( - dslContext = dslContext, - conditionDTO = conditionDTO - ) - logger.debug("listProjectMembersByComplexConditions :{}", records) - - val count = authResourceGroupMemberDao.countProjectMembersByComplexConditions( - dslContext = dslContext, - conditionDTO = conditionDTO - ) - logger.debug("listProjectMembersByComplexConditions :$count") - // 添加离职标志 - return if (conditionDTO.departedFlag == false) { - SQLPage(count, records) - } else { - SQLPage(count, permissionResourceMemberService.addDepartedFlagToMembers(records)) - } - } - - companion object { - private val logger = LoggerFactory.getLogger(RbacPermissionResourceMemberService::class.java) - - // 永久过期时间 - private const val PERMANENT_EXPIRED_TIME = 4102444800000L - } -} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupPermissionService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupPermissionService.kt index eb461b5bbb5..128084b24ce 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupPermissionService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupPermissionService.kt @@ -295,6 +295,21 @@ class RbacPermissionResourceGroupPermissionService( ) } + override fun isGroupsHasProjectLevelPermission( + projectCode: String, + filterIamGroupIds: List, + action: String + ): Boolean { + val actionRelatedResourceType = rbacCacheService.getActionInfo(action).relatedResourceType + return resourceGroupPermissionDao.isGroupsHasProjectLevelPermission( + dslContext = dslContext, + projectCode = projectCode, + filterIamGroupIds = filterIamGroupIds, + actionRelatedResourceType = actionRelatedResourceType, + action = action + ) + } + override fun listGroupResourcesWithPermission( projectCode: String, filterIamGroupIds: List, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupService.kt index 292dba02ec5..87aaf9a7c08 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupService.kt @@ -28,7 +28,6 @@ package com.tencent.devops.auth.provider.rbac.service -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.bk.sdk.iam.dto.V2PageInfoDTO import com.tencent.bk.sdk.iam.dto.manager.ManagerRoleGroup import com.tencent.bk.sdk.iam.dto.manager.dto.ManagerRoleGroupDTO @@ -50,6 +49,7 @@ import com.tencent.devops.auth.pojo.dto.GroupAddDTO import com.tencent.devops.auth.pojo.dto.ListGroupConditionDTO import com.tencent.devops.auth.pojo.dto.RenameGroupDTO import com.tencent.devops.auth.pojo.enum.GroupMemberStatus +import com.tencent.devops.auth.pojo.enum.MemberType import com.tencent.devops.auth.pojo.request.CustomGroupCreateReq import com.tencent.devops.auth.pojo.vo.IamGroupInfoVo import com.tencent.devops.auth.pojo.vo.IamGroupMemberInfoVo @@ -181,8 +181,8 @@ class RbacPermissionResourceGroupService @Autowired constructor( dslContext = dslContext, projectCode = condition.projectId ) - val userCount = projectMemberCount[ManagerScopesEnum.getType(ManagerScopesEnum.USER)] ?: 0 - val departmentCount = projectMemberCount[ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT)] ?: 0 + val userCount = projectMemberCount[MemberType.USER.type] ?: 0 + val departmentCount = projectMemberCount[MemberType.DEPARTMENT.type] ?: 0 val allProjectMemberGroup = IamGroupInfoVo( managerId = managerId, defaultGroup = true, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupSyncService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupSyncService.kt index 29bb6bfdb0e..bdb6e8d6e52 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupSyncService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupSyncService.kt @@ -40,6 +40,7 @@ import com.tencent.devops.auth.pojo.AuthResourceGroup import com.tencent.devops.auth.pojo.AuthResourceGroupMember import com.tencent.devops.auth.pojo.enum.ApplyToGroupStatus import com.tencent.devops.auth.pojo.enum.AuthMigrateStatus +import com.tencent.devops.auth.pojo.enum.MemberType import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService import com.tencent.devops.auth.service.iam.PermissionResourceGroupSyncService import com.tencent.devops.auth.service.lock.SyncGroupAndMemberLock @@ -61,6 +62,7 @@ import org.slf4j.LoggerFactory import org.slf4j.MDC import org.springframework.beans.factory.annotation.Autowired import java.time.LocalDateTime +import java.util.concurrent.Callable import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletionException import java.util.concurrent.Executors @@ -109,6 +111,7 @@ class RbacPermissionResourceGroupSyncService @Autowired constructor( override fun syncGroupMemberExpiredTime(projectConditionDTO: ProjectConditionDTO) { logger.info("start to sync group member expired time|$projectConditionDTO") val traceId = MDC.get(TraceTag.BIZID) + val startEpoch = System.currentTimeMillis() syncExecutorService.submit { MDC.put(TraceTag.BIZID, traceId) var offset = 0 @@ -119,8 +122,8 @@ class RbacPermissionResourceGroupSyncService @Autowired constructor( limit = limit, offset = offset ).data?.map { it.englishName } ?: break - projectCodes.forEach { projectCode -> - syncMemberExpiredExecutorService.submit { + val futures = projectCodes.map { projectCode -> + syncMemberExpiredExecutorService.submit(Callable { logger.info("start to sync project group member expired time|$projectCode") val projectMembersOfExpired = authResourceGroupMemberDao.listResourceGroupMember( dslContext = dslContext, @@ -143,10 +146,12 @@ class RbacPermissionResourceGroupSyncService @Autowired constructor( } } } - } + }) } + futures.forEach { it.get() } offset += limit } while (projectCodes.size == limit) + logger.info("It take(${System.currentTimeMillis() - startEpoch})ms to sync Group Member Expired Time") } } @@ -218,62 +223,58 @@ class RbacPermissionResourceGroupSyncService @Autowired constructor( } override fun syncIamGroupMembersOfApply() { - val traceId = MDC.get(TraceTag.BIZID) - syncExecutorService.submit { - MDC.put(TraceTag.BIZID, traceId) - val limit = 100 - var offset = 0 - val startEpoch = System.currentTimeMillis() - val finalRecordsOfPending = mutableListOf() - val finalRecordsOfSuccess = mutableListOf() - do { - logger.info("sync members of apply | start") - // 获取7天内未审批单据 - val records = authResourceGroupApplyDao.list( - dslContext = dslContext, - day = 7, - limit = limit, - offset = offset - ) - val (recordsOfSuccess, recordsOfPending) = records.partition { - try { - val isMemberJoinedToGroup = iamV2ManagerService.verifyGroupValidMember( - it.memberId, - it.iamGroupId.toString() - )[it.iamGroupId]?.belong == true - isMemberJoinedToGroup - } catch (ignore: Exception) { - logger.warn("verify group valid member failed,${it.memberId}|${it.iamGroupId}", ignore) - authResourceGroupApplyDao.delete(dslContext, it.id) - false - } + val limit = 100 + var offset = 0 + val startEpoch = System.currentTimeMillis() + val finalRecordsOfPending = mutableListOf() + val finalRecordsOfSuccess = mutableListOf() + do { + logger.info("sync members of apply | start") + // 获取7天内未审批单据 + val records = authResourceGroupApplyDao.list( + dslContext = dslContext, + day = 7, + limit = limit, + offset = offset + ) + val (recordsOfSuccess, recordsOfPending) = records.partition { + try { + val isMemberJoinedToGroup = iamV2ManagerService.verifyGroupValidMember( + it.memberId, + it.iamGroupId.toString() + )[it.iamGroupId]?.belong == true + isMemberJoinedToGroup + } catch (ignore: Exception) { + logger.warn("verify group valid member failed,${it.memberId}|${it.iamGroupId}", ignore) + authResourceGroupApplyDao.delete(dslContext, it.id) + false } - finalRecordsOfPending.addAll(recordsOfPending) - finalRecordsOfSuccess.addAll(recordsOfSuccess) - offset += limit - } while (records.size == limit) - if (finalRecordsOfPending.isNotEmpty()) { - authResourceGroupApplyDao.batchUpdate( - dslContext = dslContext, - ids = finalRecordsOfPending.map { it.id }, - applyToGroupStatus = ApplyToGroupStatus.PENDING - ) } - if (finalRecordsOfSuccess.isNotEmpty()) { - finalRecordsOfSuccess.forEach { - syncIamGroupMember( - projectCode = it.projectCode, - iamGroupId = it.iamGroupId - ) - } - authResourceGroupApplyDao.batchUpdate( - dslContext = dslContext, - ids = finalRecordsOfSuccess.map { it.id }, - applyToGroupStatus = ApplyToGroupStatus.SUCCEED + finalRecordsOfPending.addAll(recordsOfPending) + finalRecordsOfSuccess.addAll(recordsOfSuccess) + offset += limit + } while (records.size == limit) + if (finalRecordsOfPending.isNotEmpty()) { + authResourceGroupApplyDao.batchUpdate( + dslContext = dslContext, + ids = finalRecordsOfPending.map { it.id }, + applyToGroupStatus = ApplyToGroupStatus.PENDING + ) + } + if (finalRecordsOfSuccess.isNotEmpty()) { + finalRecordsOfSuccess.forEach { + syncIamGroupMember( + projectCode = it.projectCode, + iamGroupId = it.iamGroupId ) } - logger.info("It take(${System.currentTimeMillis() - startEpoch})ms to sync members of apply") + authResourceGroupApplyDao.batchUpdate( + dslContext = dslContext, + ids = finalRecordsOfSuccess.map { it.id }, + applyToGroupStatus = ApplyToGroupStatus.SUCCEED + ) } + logger.info("It take(${System.currentTimeMillis() - startEpoch})ms to sync members of apply") } override fun syncGroupAndMember(projectCode: String) { @@ -724,7 +725,7 @@ class RbacPermissionResourceGroupSyncService @Autowired constructor( iamGroupId = iamGroupId, memberId = iamGroupTemplate.id, memberName = iamGroupTemplate.name, - memberType = ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE), + memberType = MemberType.TEMPLATE.type, expiredTime = expiredTime ) ) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceMemberService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceMemberService.kt index 401e717b46f..b0ad89bd245 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceMemberService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceMemberService.kt @@ -8,7 +8,6 @@ import com.tencent.bk.sdk.iam.dto.manager.V2ManagerRoleGroupInfo import com.tencent.bk.sdk.iam.dto.manager.dto.GroupMemberRenewApplicationDTO import com.tencent.bk.sdk.iam.dto.manager.dto.ManagerMemberGroupDTO import com.tencent.bk.sdk.iam.dto.manager.dto.SearchGroupDTO -import com.tencent.bk.sdk.iam.dto.response.MemberGroupDetailsResponse import com.tencent.bk.sdk.iam.service.v2.V2ManagerService import com.tencent.devops.auth.constant.AuthMessageCode import com.tencent.devops.auth.dao.AuthResourceGroupDao @@ -16,36 +15,22 @@ import com.tencent.devops.auth.dao.AuthResourceGroupMemberDao import com.tencent.devops.auth.pojo.AuthResourceGroupMember import com.tencent.devops.auth.pojo.ResourceMemberInfo import com.tencent.devops.auth.pojo.dto.GroupMemberRenewalDTO -import com.tencent.devops.auth.pojo.enum.BatchOperateType import com.tencent.devops.auth.pojo.enum.MemberType -import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq -import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq -import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo import com.tencent.devops.auth.pojo.vo.ResourceMemberCountVO import com.tencent.devops.auth.service.DeptService -import com.tencent.devops.auth.service.PermissionAuthorizationService -import com.tencent.devops.auth.service.iam.PermissionResourceGroupSyncService import com.tencent.devops.auth.service.iam.PermissionResourceMemberService import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.api.util.DateTimeUtil import com.tencent.devops.common.api.util.PageUtil -import com.tencent.devops.common.api.util.timestamp import com.tencent.devops.common.auth.api.AuthResourceType import com.tencent.devops.common.auth.api.pojo.BkAuthGroup import com.tencent.devops.common.auth.api.pojo.BkAuthGroupAndUserList -import com.tencent.devops.common.auth.api.pojo.ResetAllResourceAuthorizationReq -import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationConditionRequest -import com.tencent.devops.common.service.utils.RetryUtils import com.tencent.devops.project.constant.ProjectMessageCode import org.apache.commons.lang3.RandomUtils import org.jooq.DSLContext import org.slf4j.LoggerFactory import java.time.LocalDateTime -import java.util.concurrent.CompletableFuture import java.util.concurrent.Executors import java.util.concurrent.TimeUnit @@ -56,9 +41,7 @@ class RbacPermissionResourceMemberService( private val authResourceGroupDao: AuthResourceGroupDao, private val authResourceGroupMemberDao: AuthResourceGroupMemberDao, private val dslContext: DSLContext, - private val deptService: DeptService, - private val permissionAuthorizationService: PermissionAuthorizationService, - private val syncIamGroupMemberService: PermissionResourceGroupSyncService + private val deptService: DeptService ) : PermissionResourceMemberService { override fun getResourceGroupMembers( projectCode: String, @@ -145,8 +128,8 @@ class RbacPermissionResourceMemberService( projectCode = projectCode ) return ResourceMemberCountVO( - userCount = projectMemberCount[ManagerScopesEnum.getType(ManagerScopesEnum.USER)] ?: 0, - departmentCount = projectMemberCount[ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT)] ?: 0 + userCount = projectMemberCount[MemberType.USER.type] ?: 0, + departmentCount = projectMemberCount[MemberType.DEPARTMENT.type] ?: 0 ) } @@ -192,7 +175,7 @@ class RbacPermissionResourceMemberService( override fun addDepartedFlagToMembers(records: List): List { val userMembers = records.filter { - it.type == ManagerScopesEnum.getType(ManagerScopesEnum.USER) + it.type == MemberType.USER.type }.map { it.id } val departedMembers = if (userMembers.isNotEmpty()) { deptService.listDepartedMembers( @@ -202,7 +185,7 @@ class RbacPermissionResourceMemberService( return records } return records.map { - if (it.type != ManagerScopesEnum.getType(ManagerScopesEnum.USER)) { + if (it.type != MemberType.USER.type) { it.copy(departed = false) } else { it.copy(departed = departedMembers.contains(it.id)) @@ -218,7 +201,7 @@ class RbacPermissionResourceMemberService( expiredAt: Long, iamGroupId: Int ): Boolean { - if (memberType == ManagerScopesEnum.getType(ManagerScopesEnum.USER) && + if (memberType == MemberType.USER.type && deptService.isUserDeparted(memberId)) { return true } @@ -285,8 +268,8 @@ class RbacPermissionResourceMemberService( iamGroupId = iamGroupId ) // 获取用户组中用户以及部门 - val userType = ManagerScopesEnum.getType(ManagerScopesEnum.USER) - val deptType = ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT) + val userType = MemberType.USER.type + val deptType = MemberType.DEPARTMENT.type val pageInfoDTO = V2PageInfoDTO().apply { pageSize = 1000 page = 1 @@ -421,8 +404,8 @@ class RbacPermissionResourceMemberService( projectCode = projectCode, iamGroupId = iamGroupId ) - val userType = ManagerScopesEnum.getType(ManagerScopesEnum.USER) - val deptType = ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT) + val userType = MemberType.USER.type + val deptType = MemberType.DEPARTMENT.type val allMemberIds = mutableListOf() if (!members.isNullOrEmpty()) { deleteIamGroupMembers( @@ -486,7 +469,7 @@ class RbacPermissionResourceMemberService( val nowTimestamp = System.currentTimeMillis() / 1000 val (members, deptInfoList) = groupMemberInfoList .filter { it.expiredAt > nowTimestamp } - .partition { it.type == ManagerScopesEnum.getType(ManagerScopesEnum.USER) } + .partition { it.type == MemberType.USER.type } return BkAuthGroupAndUserList( displayName = groupInfo.name, @@ -616,25 +599,6 @@ class RbacPermissionResourceMemberService( return true } - override fun renewalGroupMember( - userId: String, - projectCode: String, - renewalConditionReq: GroupMemberSingleRenewalReq - ): Boolean { - logger.info("renewal group member $userId|$projectCode|$renewalConditionReq") - val groupId = renewalConditionReq.groupId - batchOperateGroupMembers( - projectCode = projectCode, - conditionReq = GroupMemberRenewalConditionReq( - groupIds = listOf(groupId), - targetMember = renewalConditionReq.targetMember, - renewalDuration = renewalConditionReq.renewalDuration - ), - operateGroupMemberTask = ::renewalTask - ) - return true - } - override fun renewalIamGroupMembers( groupId: Int, members: List, @@ -653,82 +617,12 @@ class RbacPermissionResourceMemberService( return true } - override fun batchRenewalGroupMembers( - userId: String, - projectCode: String, - renewalConditionReq: GroupMemberRenewalConditionReq - ): Boolean { - logger.info("batch renewal group member $userId|$projectCode|$renewalConditionReq") - batchOperateGroupMembers( - projectCode = projectCode, - conditionReq = renewalConditionReq, - operateGroupMemberTask = ::renewalTask - ) - return true - } - - private fun renewalTask( - projectCode: String, - groupId: Int, - renewalConditionReq: GroupMemberRenewalConditionReq, - expiredAt: Long - ) { - logger.info("renewal group member ${renewalConditionReq.targetMember}|$projectCode|$groupId|$expiredAt") - val targetMember = renewalConditionReq.targetMember - if (targetMember.type == ManagerScopesEnum.getType(ManagerScopesEnum.USER) && - deptService.isUserDeparted(targetMember.id)) { - return - } - val secondsOfRenewalDuration = TimeUnit.DAYS.toSeconds(renewalConditionReq.renewalDuration.toLong()) - val secondsOfCurrentTime = System.currentTimeMillis() / 1000 - // 若权限已过期,则为当前时间+续期天数,若未过期,则为有效期+续期天数 - val finalExpiredAt = if (expiredAt < secondsOfCurrentTime) { - secondsOfCurrentTime - } else { - expiredAt - } + secondsOfRenewalDuration - if (!isNeedToRenewal(finalExpiredAt)) { - return - } - renewalIamGroupMembers( - groupId = groupId, - members = listOf(ManagerMember(targetMember.type, targetMember.id)), - expiredAt = finalExpiredAt - ) - authResourceGroupMemberDao.update( - dslContext = dslContext, - projectCode = projectCode, - iamGroupId = groupId, - expiredTime = DateTimeUtil.convertTimestampToLocalDateTime(finalExpiredAt), - memberId = targetMember.id - ) - } - - private fun isNeedToRenewal(expiredAt: Long): Boolean { - return expiredAt < PERMANENT_EXPIRED_TIME - } - - override fun batchDeleteResourceGroupMembers( - userId: String, - projectCode: String, - removeMemberDTO: GroupMemberCommonConditionReq - ): Boolean { - logger.info("batch delete group members $userId|$projectCode|$removeMemberDTO") - removeMemberDTO.excludedUniqueManagerGroup = true - batchOperateGroupMembers( - projectCode = projectCode, - conditionReq = removeMemberDTO, - operateGroupMemberTask = ::deleteTask - ) - return true - } - override fun deleteIamGroupMembers( groupId: Int, type: String, memberIds: List ): Boolean { - val membersOfNeedToDelete = if (type == ManagerScopesEnum.getType(ManagerScopesEnum.USER)) { + val membersOfNeedToDelete = if (type == MemberType.USER.type) { memberIds.filterNot { deptService.isUserDeparted(it) } } else { memberIds @@ -743,595 +637,15 @@ class RbacPermissionResourceMemberService( return true } - private fun deleteTask( - projectCode: String, - groupId: Int, - removeMemberDTO: GroupMemberCommonConditionReq, - expiredAt: Long - ) { - val targetMember = removeMemberDTO.targetMember - logger.info("delete group member $projectCode|$groupId|$targetMember") - deleteIamGroupMembers( - groupId = groupId, - type = targetMember.type, - memberIds = listOf(targetMember.id) - ) - authResourceGroupMemberDao.batchDeleteGroupMembers( - dslContext = dslContext, - projectCode = projectCode, - iamGroupId = groupId, - memberIds = listOf(removeMemberDTO.targetMember.id) - ) - } - - override fun batchHandoverGroupMembers( - userId: String, - projectCode: String, - handoverMemberDTO: GroupMemberHandoverConditionReq - ): Boolean { - logger.info("batch handover group members $userId|$projectCode|$handoverMemberDTO") - handoverMemberDTO.checkHandoverTo() - batchOperateGroupMembers( - projectCode = projectCode, - conditionReq = handoverMemberDTO, - operateGroupMemberTask = ::handoverTask - ) - return true - } - - override fun batchOperateGroupMembersCheck( - userId: String, - projectCode: String, - batchOperateType: BatchOperateType, - conditionReq: GroupMemberCommonConditionReq - ): BatchOperateGroupMemberCheckVo { - logger.info("batch operate group member check|$userId|$projectCode|$batchOperateType|$conditionReq") - // 获取用户加入的用户组 - val (groupIdsOfDirectJoined, groupInfoIdsOfTemplateJoined) = getGroupIdsByCondition( - projectCode = projectCode, - commonCondition = conditionReq - ) - val totalCount = groupIdsOfDirectJoined.size + groupInfoIdsOfTemplateJoined.size - val groupCountOfTemplateJoined = groupInfoIdsOfTemplateJoined.size - - return when (batchOperateType) { - BatchOperateType.REMOVE -> { - val groupCountOfUniqueManager = authResourceGroupMemberDao.listProjectUniqueManagerGroups( - dslContext = dslContext, - projectCode = projectCode, - iamGroupIds = groupIdsOfDirectJoined - ).size - BatchOperateGroupMemberCheckVo( - totalCount = totalCount, - inoperableCount = groupCountOfUniqueManager + groupCountOfTemplateJoined - ) - } - - BatchOperateType.RENEWAL -> { - with(conditionReq) { - val isUserDeparted = targetMember.type == ManagerScopesEnum.getType(ManagerScopesEnum.USER) && - deptService.isUserDeparted(targetMember.id) - // 离职用户不允许续期 - if (isUserDeparted) { - BatchOperateGroupMemberCheckVo( - totalCount = totalCount, - inoperableCount = totalCount - ) - } else { - // 永久期限 不允许再续期 - val groupCountOfPermanentExpiredTime = listMemberGroupsDetails( - projectCode = projectCode, - memberId = targetMember.id, - memberType = targetMember.type, - groupIds = groupIdsOfDirectJoined - ).filter { - // iam用的是秒级时间戳 - it.expiredAt == PERMANENT_EXPIRED_TIME / 1000 - }.size - BatchOperateGroupMemberCheckVo( - totalCount = totalCount, - inoperableCount = groupCountOfPermanentExpiredTime + groupCountOfTemplateJoined - ) - } - } - } - - BatchOperateType.HANDOVER -> { - // 已过期(除唯一管理员组)或通过模板加入的不允许移交 - with(conditionReq) { - val finalGroupIds = groupIdsOfDirectJoined.toMutableList() - val uniqueManagerGroupIds = authResourceGroupMemberDao.listProjectUniqueManagerGroups( - dslContext = dslContext, - projectCode = projectCode, - iamGroupIds = groupIdsOfDirectJoined - ) - // 去除唯一管理员组 - if (uniqueManagerGroupIds.isNotEmpty()) { - finalGroupIds.removeAll(uniqueManagerGroupIds) - } - val groupCountOfExpired = listMemberGroupsDetails( - projectCode = projectCode, - memberId = targetMember.id, - memberType = targetMember.type, - groupIds = finalGroupIds - ).filter { - // iam用的是秒级时间戳 - it.expiredAt < System.currentTimeMillis() / 1000 - }.size - BatchOperateGroupMemberCheckVo( - totalCount = totalCount, - inoperableCount = groupCountOfTemplateJoined + groupCountOfExpired - ) - } - } - - else -> { - BatchOperateGroupMemberCheckVo( - totalCount = totalCount, - inoperableCount = groupCountOfTemplateJoined - ) - } - } - } - - override fun removeMemberFromProject( - userId: String, - projectCode: String, - removeMemberFromProjectReq: RemoveMemberFromProjectReq - ): List { - logger.info("remove member from project $userId|$projectCode|$removeMemberFromProjectReq") - return with(removeMemberFromProjectReq) { - val memberType = targetMember.type - val isNeedToHandover = handoverTo != null - if (memberType == ManagerScopesEnum.getType(ManagerScopesEnum.USER) && isNeedToHandover) { - removeMemberFromProjectReq.checkHandoverTo() - val handoverMemberDTO = GroupMemberHandoverConditionReq( - allSelection = true, - targetMember = targetMember, - handoverTo = handoverTo!! - ) - batchOperateGroupMembers( - projectCode = projectCode, - conditionReq = handoverMemberDTO, - operateGroupMemberTask = ::handoverTask - ) - permissionAuthorizationService.resetAllResourceAuthorization( - operator = userId, - projectCode = projectCode, - condition = ResetAllResourceAuthorizationReq( - projectCode = projectCode, - handoverFrom = removeMemberFromProjectReq.targetMember.id, - handoverTo = removeMemberFromProjectReq.handoverTo!!.id, - preCheck = false, - checkPermission = false - ) - ) - } else { - val removeMemberDTO = GroupMemberCommonConditionReq( - allSelection = true, - targetMember = targetMember - ) - batchOperateGroupMembers( - projectCode = projectCode, - conditionReq = removeMemberDTO, - operateGroupMemberTask = ::deleteTask - ) - } - - if (memberType == ManagerScopesEnum.getType(ManagerScopesEnum.USER)) { - // 查询用户还存在那些组织中 - val userDeptInfos = deptService.getUserInfo( - userId = "admin", - name = targetMember.id - )?.deptInfo?.map { it.name!! } - if (userDeptInfos != null) { - return authResourceGroupMemberDao.isMembersInProject( - dslContext = dslContext, - projectCode = projectCode, - memberNames = userDeptInfos, - memberType = ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT) - ) - } - } - return emptyList() - } - } - - override fun removeMemberFromProjectCheck( - userId: String, - projectCode: String, - removeMemberFromProjectReq: RemoveMemberFromProjectReq - ): Boolean { - val targetMember = removeMemberFromProjectReq.targetMember - val isMemberHasNoPermission = batchOperateGroupMembersCheck( - userId = userId, - projectCode = projectCode, - batchOperateType = BatchOperateType.HANDOVER, - conditionReq = GroupMemberCommonConditionReq( - allSelection = true, - targetMember = removeMemberFromProjectReq.targetMember - ) - ).let { it.totalCount == it.inoperableCount } - - val isMemberHasNoAuthorizations = - if (targetMember.type == ManagerScopesEnum.getType(ManagerScopesEnum.USER)) { - permissionAuthorizationService.listResourceAuthorizations( - condition = ResourceAuthorizationConditionRequest( - projectCode = projectCode, - handoverFrom = targetMember.id - ) - ).count == 0L - } else { - true - } - return isMemberHasNoPermission && isMemberHasNoAuthorizations - } - - private fun handoverTask( - projectCode: String, - groupId: Int, - handoverMemberDTO: GroupMemberHandoverConditionReq, - expiredAt: Long - ) { - logger.info( - "handover group member $projectCode|$groupId|" + - "${handoverMemberDTO.targetMember}|${handoverMemberDTO.handoverTo}" - ) - val currentTimeSeconds = System.currentTimeMillis() / 1000 - var finalExpiredAt = expiredAt - when { - // 若权限已过期,如果是唯一管理员组,允许交接,交接人将获得半年权限;其他的直接删除。 - expiredAt < currentTimeSeconds -> { - val isUniqueManagerGroup = authResourceGroupMemberDao.listProjectUniqueManagerGroups( - dslContext = dslContext, - projectCode = projectCode, - iamGroupIds = listOf(groupId) - ).isNotEmpty() - if (isUniqueManagerGroup) { - finalExpiredAt = currentTimeSeconds + TimeUnit.DAYS.toSeconds(180) - } else { - deleteTask( - projectCode = projectCode, - groupId = groupId, - removeMemberDTO = GroupMemberCommonConditionReq( - targetMember = handoverMemberDTO.targetMember - ), - expiredAt = finalExpiredAt - ) - return - } - } - // 若交接人已经在用户组内,无需交接。 - authResourceGroupMemberDao.isMemberInGroup( - dslContext = dslContext, - projectCode = projectCode, - iamGroupId = groupId, - memberId = handoverMemberDTO.handoverTo.id - ) -> { - deleteTask( - projectCode = projectCode, - groupId = groupId, - removeMemberDTO = GroupMemberCommonConditionReq( - targetMember = handoverMemberDTO.targetMember - ), - expiredAt = finalExpiredAt - ) - return - } - } - - val members = listOf( - ManagerMember( - handoverMemberDTO.handoverTo.type, - handoverMemberDTO.handoverTo.id - ) - ) - if (finalExpiredAt < currentTimeSeconds) { - throw ErrorCodeException( - errorCode = AuthMessageCode.INVALID_EXPIRED_PERM_NOT_ALLOW_TO_HANDOVER - ) - } - - addIamGroupMember( - groupId = groupId, - members = members, - expiredAt = finalExpiredAt - ) - deleteIamGroupMembers( - groupId = groupId, - type = handoverMemberDTO.targetMember.type, - memberIds = listOf(handoverMemberDTO.targetMember.id) - ) - authResourceGroupMemberDao.handoverGroupMembers( - dslContext = dslContext, - projectCode = projectCode, - iamGroupId = groupId, - handoverFrom = handoverMemberDTO.targetMember, - handoverTo = handoverMemberDTO.handoverTo, - expiredTime = DateTimeUtil.convertTimestampToLocalDateTime(finalExpiredAt) - ) - } - - private fun batchOperateGroupMembers( - projectCode: String, - conditionReq: T, - operateGroupMemberTask: ( - projectCode: String, - groupId: Int, - conditionReq: T, - expiredAt: Long - ) -> Unit - ): Boolean { - val groupIds = getGroupIdsByCondition( - projectCode = projectCode, - commonCondition = conditionReq - ).first - val targetMember = conditionReq.targetMember - val memberGroupsDetailsList = listMemberGroupsDetails( - projectCode = projectCode, - memberId = targetMember.id, - memberType = targetMember.type, - groupIds = groupIds - ) - val outOfSyncGroupIds = mutableListOf() - val futures = groupIds.map { groupId -> - CompletableFuture.supplyAsync( - { - val memberGroupsDetails = memberGroupsDetailsList.firstOrNull { it.id == groupId } - if (memberGroupsDetails == null) { - logger.warn( - "The data is out of sync, and the record no longer exists in the iam.$groupId" - ) - outOfSyncGroupIds.add(groupId) - return@supplyAsync - } - val expiredAt = memberGroupsDetails.expiredAt - RetryUtils.retry(3) { - operateGroupMemberTask.invoke( - projectCode, - groupId, - conditionReq, - expiredAt - ) - } - }, executorService - ) - } - handleFutures( - projectCode = projectCode, - outOfSyncGroupIds = outOfSyncGroupIds, - futures = futures - ) - return true - } - - private fun handleFutures( - projectCode: String, - outOfSyncGroupIds: List, - futures: List> - ) { - try { - CompletableFuture.allOf(*futures.toTypedArray()).join() - // 存在iam那边已经把用户组下成员删除,但蓝盾数据库未同步问题 - outOfSyncGroupIds.forEach { - syncIamGroupMemberService.syncIamGroupMember( - projectCode = projectCode, - iamGroupId = it - ) - } - } catch (ignore: Exception) { - logger.warn("batch operate group members failed", ignore) - throw ErrorCodeException( - errorCode = AuthMessageCode.ERROR_BATCH_OPERATE_GROUP_MEMBERS - ) - } - } - - private fun getGroupIdsByCondition( - projectCode: String, - commonCondition: GroupMemberCommonConditionReq - ): Pair, List> /*直接加入,模板加入*/ { - val finalResourceGroupMembers = mutableListOf() - with(commonCondition) { - val resourceGroupMembersByCondition = when { - // 全选 - allSelection -> { - listResourceGroupMembers( - projectCode = projectCode, - memberId = commonCondition.targetMember.id - ).second - } - // 全选某些资源类型用户组 - resourceTypes.isNotEmpty() -> { - resourceTypes.flatMap { resourceType -> - listResourceGroupMembers( - projectCode = projectCode, - memberId = commonCondition.targetMember.id, - resourceType = resourceType - ).second - } - } - - else -> { - emptyList() - } - } - - if (resourceGroupMembersByCondition.isNotEmpty()) { - finalResourceGroupMembers.addAll(resourceGroupMembersByCondition) - } - - // Select specific groups individually - if (groupIds.isNotEmpty()) { - val resourceGroupMembersOfSelect = listResourceGroupMembers( - projectCode = projectCode, - memberId = commonCondition.targetMember.id, - iamGroupIds = groupIds - ).second - finalResourceGroupMembers.addAll(resourceGroupMembersOfSelect) - } - - val (groupIdsOfDirectJoined, groupInfoIdsOfTemplateJoined) = finalResourceGroupMembers.partition { - it.memberType != ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE) - }.run { - first.map { it.iamGroupId }.toMutableList() to second.map { it.iamGroupId }.toMutableList() - } - - // When batch removing, if the user is the only manager of the group, ignore and do not transfer - if (excludedUniqueManagerGroup) { - val excludedUniqueManagerGroupIds = authResourceGroupMemberDao.listProjectUniqueManagerGroups( - dslContext = dslContext, - projectCode = projectCode, - iamGroupIds = groupIdsOfDirectJoined - ) - groupIdsOfDirectJoined.removeAll { - excludedUniqueManagerGroupIds.contains(it) - } - } - return Pair(groupIdsOfDirectJoined, groupInfoIdsOfTemplateJoined) - } - } - - private fun listMemberGroupsDetails( - projectCode: String, - memberId: String, - memberType: String, - groupIds: List - ): List { - val memberGroupsDetailsList = mutableListOf() - val groupIdsChunk = groupIds.chunked(100) - val futures = groupIdsChunk.map { - CompletableFuture.supplyAsync( - { - memberGroupsDetailsList.addAll( - // 若离职,则从数据库获取用户加入组的过期时间,调用iam接口会报错。 - // 虽然数据库的过期时间可能不是最新的。 - if (memberType == ManagerScopesEnum.getType(ManagerScopesEnum.USER) && - deptService.isUserDeparted(userId = memberId)) { - val records = authResourceGroupMemberDao.listMemberGroupDetail( - dslContext = dslContext, - projectCode = projectCode, - memberId = memberId, - iamTemplateIds = emptyList(), - iamGroupIds = it - ) - records.map { record -> - MemberGroupDetailsResponse().apply { - id = record.iamGroupId - expiredAt = record.expiredTime.timestamp() - } - } - } else { - iamV2ManagerService.listMemberGroupsDetails( - memberType, - memberId, - it.joinToString(",") - ) - } - ) - }, executorService - ) - } - try { - CompletableFuture.allOf(*futures.toTypedArray()).join() - } catch (ignore: Exception) { - logger.warn("list member groups details failed!$ignore") - throw ignore - } - return memberGroupsDetailsList - } - - // 查询成员所在资源用户组列表,直接加入+通过用户组(模板)加入 - @Suppress("LongParameterList") - override fun listResourceGroupMembers( - projectCode: String, - memberId: String, - resourceType: String?, - iamGroupIds: List?, - minExpiredAt: Long?, - maxExpiredAt: Long?, - start: Int?, - limit: Int? - ): Pair> { - // 获取用户加入的项目级用户组模板ID - val iamTemplateIds = listProjectMemberGroupTemplateIds( - projectCode = projectCode, - memberId = memberId - ) - val minExpiredTime = minExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) } - val maxExpiredTime = maxExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) } - val count = authResourceGroupMemberDao.countMemberGroup( - dslContext = dslContext, - projectCode = projectCode, - memberId = memberId, - iamTemplateIds = iamTemplateIds, - resourceType = resourceType, - iamGroupIds = iamGroupIds, - minExpiredAt = minExpiredTime, - maxExpiredAt = maxExpiredTime - )[resourceType] ?: 0L - val resourceGroupMembers = authResourceGroupMemberDao.listMemberGroupDetail( - dslContext = dslContext, - projectCode = projectCode, - memberId = memberId, - iamTemplateIds = iamTemplateIds, - resourceType = resourceType, - iamGroupIds = iamGroupIds, - minExpiredAt = minExpiredTime, - maxExpiredAt = maxExpiredTime, - offset = start, - limit = limit - ) - return Pair(count, resourceGroupMembers) - } - - override fun listMemberGroupIdsInProject( - projectCode: String, - memberId: String - ): List { - // 获取用户加入的项目级用户组模板ID - val iamTemplateIds = listProjectMemberGroupTemplateIds( - projectCode = projectCode, - memberId = memberId - ) - return authResourceGroupMemberDao.listMemberGroupIdsInProject( - dslContext = dslContext, - projectCode = projectCode, - memberId = memberId, - iamTemplateIds = iamTemplateIds - ) - } - - // 获取用户加入的项目级用户组模板ID - private fun listProjectMemberGroupTemplateIds( - projectCode: String, - memberId: String - ): List { - // 查询项目下包含该成员的组列表 - val projectGroupIds = authResourceGroupMemberDao.listResourceGroupMember( - dslContext = dslContext, - projectCode = projectCode, - resourceType = AuthResourceType.PROJECT.value, - memberId = memberId - ).map { it.iamGroupId.toString() } - // 通过项目组ID获取人员模板ID - return authResourceGroupDao.listByRelationId( - dslContext = dslContext, - projectCode = projectCode, - iamGroupIds = projectGroupIds - ).filter { it.iamTemplateId != null } - .map { it.iamTemplateId.toString() } - } - private fun MutableList.removeDepartedMembers(): List { - val userMemberIds = this.filter { it.type == ManagerScopesEnum.getType(ManagerScopesEnum.USER) }.map { it.id } + val userMemberIds = this.filter { it.type == MemberType.USER.type }.map { it.id } if (userMemberIds.isEmpty()) return this // 获取离职的人员 val departedMembers = deptService.listDepartedMembers( memberIds = userMemberIds ) return this.filterNot { - it.type == ManagerScopesEnum.getType(ManagerScopesEnum.USER) && + it.type == MemberType.USER.type && departedMembers.contains(it.id) } } @@ -1349,8 +663,5 @@ class RbacPermissionResourceMemberService( private val AUTO_RENEWAL_EXPIRED_AT = TimeUnit.DAYS.toSeconds(180) private val executorService = Executors.newFixedThreadPool(30) - - // 永久过期时间 - private const val PERMANENT_EXPIRED_TIME = 4102444800000L } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceValidateService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceValidateService.kt index c0baf8abee1..3198aa05099 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceValidateService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceValidateService.kt @@ -30,6 +30,7 @@ package com.tencent.devops.auth.provider.rbac.service import com.tencent.devops.auth.constant.AuthMessageCode import com.tencent.devops.auth.pojo.dto.PermissionBatchValidateDTO +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.auth.service.iam.PermissionResourceValidateService import com.tencent.devops.auth.service.iam.PermissionService import com.tencent.devops.common.api.exception.ErrorCodeException @@ -152,6 +153,46 @@ class RbacPermissionResourceValidateService( return true } + override fun validateUserProjectPermissionByChannel( + userId: String, + projectCode: String, + operateChannel: OperateChannel, + targetMemberId: String + ) { + if (operateChannel == OperateChannel.PERSONAL) { + // 个人视角校验 + val hasVisitPermission = permissionService.validateUserResourcePermission( + userId = userId, + resourceType = AuthResourceType.PROJECT.value, + action = RbacAuthUtils.buildAction(AuthPermission.VISIT, AuthResourceType.PROJECT), + projectCode = projectCode + ) + if (!hasVisitPermission) { + throw PermissionForbiddenException( + message = "The user does not have permission to visit the project!" + ) + } + if (userId != targetMemberId) { + throw PermissionForbiddenException( + message = "You do not have permission to operate other user groups!" + ) + } + } else { + // 管理员视角校验 + val hasProjectManagePermission = permissionService.validateUserResourcePermission( + userId = userId, + resourceType = AuthResourceType.PROJECT.value, + action = RbacAuthUtils.buildAction(AuthPermission.MANAGE, AuthResourceType.PROJECT), + projectCode = projectCode + ) + if (!hasProjectManagePermission) { + throw PermissionForbiddenException( + message = I18nUtil.getCodeLanMessage(AuthMessageCode.ERROR_AUTH_NO_MANAGE_PERMISSION) + ) + } + } + } + private fun checkProjectApprovalStatus(resourceType: String, resourceCode: String) { if (resourceType == AuthResourceType.PROJECT.value) { val projectInfo = diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionService.kt index 08b14499753..457f56925f4 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionService.kt @@ -29,7 +29,6 @@ package com.tencent.devops.auth.provider.rbac.service import com.tencent.bk.sdk.iam.config.IamConfiguration -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.bk.sdk.iam.dto.InstanceDTO import com.tencent.bk.sdk.iam.dto.PathInfoDTO import com.tencent.bk.sdk.iam.dto.SubjectDTO @@ -39,6 +38,7 @@ import com.tencent.bk.sdk.iam.dto.resource.ResourceDTO import com.tencent.bk.sdk.iam.dto.resource.V2ResourceNode import com.tencent.bk.sdk.iam.helper.AuthHelper import com.tencent.bk.sdk.iam.service.PolicyService +import com.tencent.devops.auth.pojo.enum.MemberType import com.tencent.devops.auth.service.AuthProjectUserMetricsService import com.tencent.devops.auth.service.SuperManagerService import com.tencent.devops.auth.service.iam.PermissionService @@ -179,7 +179,7 @@ class RbacPermissionService( } ?: return false val subject = SubjectDTO.builder() .id(userId) - .type(ManagerScopesEnum.getType(ManagerScopesEnum.USER)) + .type(MemberType.USER.type) .build() val actionDTO = ActionDTO() diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/AbMigratePolicyService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/AbMigratePolicyService.kt index d74c7192ead..968ea175bc3 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/AbMigratePolicyService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/AbMigratePolicyService.kt @@ -29,7 +29,6 @@ package com.tencent.devops.auth.provider.rbac.service.migrate import com.tencent.bk.sdk.iam.config.IamConfiguration -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.bk.sdk.iam.dto.manager.AuthorizationScopes import com.tencent.bk.sdk.iam.dto.manager.ManagerPath import com.tencent.bk.sdk.iam.dto.manager.ManagerResources @@ -41,6 +40,7 @@ import com.tencent.devops.auth.constant.AuthMessageCode import com.tencent.devops.auth.dao.AuthMigrationDao import com.tencent.devops.auth.dao.AuthResourceGroupConfigDao import com.tencent.devops.auth.dao.AuthResourceGroupDao +import com.tencent.devops.auth.pojo.enum.MemberType import com.tencent.devops.auth.provider.rbac.pojo.migrate.MigrateTaskDataResult import com.tencent.devops.auth.provider.rbac.service.RbacCacheService import com.tencent.devops.auth.provider.rbac.service.migrate.MigrateIamApiService.Companion.GROUP_API_POLICY @@ -382,7 +382,7 @@ abstract class AbMigratePolicyService( permissionResourceMemberService.addGroupMember( projectCode = projectCode, memberId = userId, - memberType = ManagerScopesEnum.getType(ManagerScopesEnum.USER), + memberType = MemberType.USER.type, expiredAt = System.currentTimeMillis() / MILLISECOND + TimeUnit.DAYS.toSeconds(DEFAULT_EXPIRED_DAY), iamGroupId = groupId diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigratePermissionHandoverService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigratePermissionHandoverService.kt index e7597f6ccc1..dd14d20be28 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigratePermissionHandoverService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigratePermissionHandoverService.kt @@ -32,7 +32,7 @@ import com.tencent.devops.auth.dao.AuthResourceGroupDao import com.tencent.devops.auth.pojo.dto.PermissionHandoverDTO import com.tencent.devops.auth.pojo.enum.JoinedType import com.tencent.devops.auth.provider.rbac.service.AuthResourceService -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.auth.service.iam.PermissionResourceMemberService import com.tencent.devops.common.auth.api.AuthResourceType import com.tencent.devops.common.auth.api.pojo.DefaultGroupType @@ -43,7 +43,7 @@ class MigratePermissionHandoverService( private val permissionResourceMemberService: PermissionResourceMemberService, private val authResourceGroupDao: AuthResourceGroupDao, private val authResourceService: AuthResourceService, - private val resourceGroupAndMemberFacadeService: PermissionResourceGroupAndMemberFacadeService, + private val permissionManageFacadeService: PermissionManageFacadeService, private val dslContext: DSLContext ) { fun handoverPermissions(permissionHandoverDTO: PermissionHandoverDTO) { @@ -114,7 +114,7 @@ class MigratePermissionHandoverService( ) } // 交接用户组权限 - val userJoinedGroups = resourceGroupAndMemberFacadeService.getMemberGroupsDetails( + val userJoinedGroups = permissionManageFacadeService.getMemberGroupsDetails( projectId = projectCode, memberId = handoverFrom ).records.filter { it.joinedType == JoinedType.DIRECT }.map { it.groupId } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateV0PolicyService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateV0PolicyService.kt index 7e286071d36..564063851c5 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateV0PolicyService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateV0PolicyService.kt @@ -29,7 +29,6 @@ package com.tencent.devops.auth.provider.rbac.service.migrate import com.tencent.bk.sdk.iam.config.IamConfiguration -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.bk.sdk.iam.dto.V2PageInfoDTO import com.tencent.bk.sdk.iam.dto.manager.Action import com.tencent.bk.sdk.iam.dto.manager.AuthorizationScopes @@ -41,6 +40,7 @@ import com.tencent.bk.sdk.iam.service.v2.V2ManagerService import com.tencent.devops.auth.dao.AuthMigrationDao import com.tencent.devops.auth.dao.AuthResourceGroupConfigDao import com.tencent.devops.auth.dao.AuthResourceGroupDao +import com.tencent.devops.auth.pojo.enum.MemberType import com.tencent.devops.auth.provider.rbac.pojo.migrate.MigrateTaskDataResult import com.tencent.devops.auth.provider.rbac.service.AuthResourceCodeConverter import com.tencent.devops.auth.provider.rbac.service.RbacCacheService @@ -486,7 +486,7 @@ class MigrateV0PolicyService constructor( groupId = it.toInt(), defaultGroup = true, member = RoleGroupMemberInfo().apply { - type = ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE) + type = MemberType.TEMPLATE.type id = subjectTemplateId name = subjectTemplateId expiredAt = 0 diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/config/MockAuthConfiguration.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/config/MockAuthConfiguration.kt index 84587ca37b7..d7bc98262d4 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/config/MockAuthConfiguration.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/config/MockAuthConfiguration.kt @@ -8,9 +8,10 @@ import com.tencent.devops.auth.provider.sample.service.SampleAuthPermissionServi import com.tencent.devops.auth.provider.sample.service.SampleOrganizationService import com.tencent.devops.auth.provider.sample.service.SamplePermissionApplyService import com.tencent.devops.auth.provider.sample.service.SamplePermissionExtService +import com.tencent.devops.auth.provider.sample.service.SamplePermissionHandoverApplicationService import com.tencent.devops.auth.provider.sample.service.SamplePermissionItsmCallbackService import com.tencent.devops.auth.provider.sample.service.SamplePermissionMigrateService -import com.tencent.devops.auth.provider.sample.service.SamplePermissionResourceGroupAndMemberFacadeService +import com.tencent.devops.auth.provider.sample.service.SamplePermissionManageFacadeService import com.tencent.devops.auth.provider.sample.service.SamplePermissionResourceGroupPermissionService import com.tencent.devops.auth.provider.sample.service.SamplePermissionResourceGroupService import com.tencent.devops.auth.provider.sample.service.SamplePermissionResourceGroupSyncService @@ -27,10 +28,11 @@ import com.tencent.devops.auth.service.PermissionAuthorizationService import com.tencent.devops.auth.service.SuperManagerService import com.tencent.devops.auth.service.iam.PermissionApplyService import com.tencent.devops.auth.service.iam.PermissionExtService +import com.tencent.devops.auth.service.iam.PermissionHandoverApplicationService import com.tencent.devops.auth.service.iam.PermissionItsmCallbackService import com.tencent.devops.auth.service.iam.PermissionMigrateService import com.tencent.devops.auth.service.iam.PermissionProjectService -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService import com.tencent.devops.auth.service.iam.PermissionResourceGroupService import com.tencent.devops.auth.service.iam.PermissionResourceGroupSyncService @@ -86,8 +88,8 @@ class MockAuthConfiguration { fun samplePermissionResourceGroupService() = SamplePermissionResourceGroupService() @Bean - @ConditionalOnMissingBean(PermissionResourceGroupAndMemberFacadeService::class) - fun samplePermissionResourceGroupAndMemberFacadeService() = SamplePermissionResourceGroupAndMemberFacadeService() + @ConditionalOnMissingBean(PermissionManageFacadeService::class) + fun samplePermissionManageFacadeService() = SamplePermissionManageFacadeService() @Bean @ConditionalOnMissingBean(PermissionResourceGroupPermissionService::class) @@ -128,4 +130,8 @@ class MockAuthConfiguration { @Bean @ConditionalOnMissingBean(PermissionResourceGroupSyncService::class) fun samplePermissionResourceGroupSyncService() = SamplePermissionResourceGroupSyncService() + + @Bean + @ConditionalOnMissingBean(PermissionHandoverApplicationService::class) + fun samplePermissionHandoverService() = SamplePermissionHandoverApplicationService() } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionHandoverApplicationService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionHandoverApplicationService.kt new file mode 100644 index 00000000000..7f147e9cde0 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionHandoverApplicationService.kt @@ -0,0 +1,71 @@ +package com.tencent.devops.auth.provider.sample.service + +import com.tencent.devops.auth.pojo.dto.HandoverDetailDTO +import com.tencent.devops.auth.pojo.dto.HandoverOverviewCreateDTO +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.enum.HandoverType +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewQueryReq +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverOverviewVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.auth.service.iam.PermissionHandoverApplicationService +import com.tencent.devops.common.api.model.SQLPage + +class SamplePermissionHandoverApplicationService : PermissionHandoverApplicationService { + override fun createHandoverApplication( + overview: HandoverOverviewCreateDTO, + details: List + ) { + return + } + + override fun generateTitle(groupCount: Int, authorizationCount: Int): String = "" + + override fun generateFlowNo(): String = "" + + override fun updateHandoverApplication(overview: HandoverOverviewUpdateReq) { + return + } + + override fun getHandoverOverview(flowNo: String): HandoverOverviewVo { + TODO("Not yet implemented") + } + + override fun listHandoverOverviews(queryRequest: HandoverOverviewQueryReq): SQLPage { + return SQLPage(0, emptyList()) + } + + override fun listAuthorizationsOfHandoverApplication( + queryReq: HandoverDetailsQueryReq + ): SQLPage { + return SQLPage(0, emptyList()) + } + + override fun listGroupsOfHandoverApplication(queryReq: HandoverDetailsQueryReq): SQLPage { + return SQLPage(0, emptyList()) + } + + override fun getResourceType2CountOfHandoverApplication(flowNo: String): List { + return emptyList() + } + + override fun listHandoverDetails( + projectCode: String, + flowNo: String, + resourceType: String?, + handoverType: HandoverType? + ): List { + return emptyList() + } + + override fun listMemberHandoverDetails( + projectCode: String, + memberId: String, + handoverType: HandoverType, + resourceType: String? + ): List { + return emptyList() + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionManageFacadeService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionManageFacadeService.kt new file mode 100644 index 00000000000..2898bcecacf --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionManageFacadeService.kt @@ -0,0 +1,156 @@ +package com.tencent.devops.auth.provider.sample.service + +import com.tencent.devops.auth.pojo.AuthResourceGroupMember +import com.tencent.devops.auth.pojo.ResourceMemberInfo +import com.tencent.devops.auth.pojo.dto.IamGroupIdsQueryConditionDTO +import com.tencent.devops.auth.pojo.dto.InvalidAuthorizationsDTO +import com.tencent.devops.auth.pojo.enum.BatchOperateType +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRemoveConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq +import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq +import com.tencent.devops.auth.pojo.request.ResourceType2CountOfHandoverQuery +import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo +import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService +import com.tencent.devops.common.api.model.SQLPage + +class SamplePermissionManageFacadeService : PermissionManageFacadeService { + override fun getMemberGroupsDetails( + projectId: String, + memberId: String, + resourceType: String?, + iamGroupIds: List?, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long?, + relatedResourceType: String?, + relatedResourceCode: String?, + action: String?, + operateChannel: OperateChannel?, + start: Int?, + limit: Int? + ): SQLPage = SQLPage(0, emptyList()) + + override fun getMemberGroupsCount( + projectCode: String, + memberId: String, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long?, + relatedResourceType: String?, + relatedResourceCode: String?, + action: String?, + operateChannel: OperateChannel? + ): List = emptyList() + + override fun listIamGroupIdsByConditions( + condition: IamGroupIdsQueryConditionDTO + ): List = emptyList() + + override fun listMemberGroupIdsInProject( + projectCode: String, + memberId: String + ): List = emptyList() + + override fun listResourceGroupMembers( + projectCode: String, + memberId: String, + resourceType: String?, + iamGroupIds: List?, + excludeIamGroupIds: List?, + minExpiredAt: Long?, + maxExpiredAt: Long?, + operateChannel: OperateChannel?, + start: Int?, + limit: Int? + ): Pair> = Pair(0, emptyList()) + + override fun listProjectMembersByComplexConditions( + conditionReq: ProjectMembersQueryConditionReq + ): SQLPage = SQLPage(0, emptyList()) + + override fun listInvalidAuthorizationsAfterOperatedGroups( + projectCode: String, + iamGroupIds: List, + memberId: String + ): InvalidAuthorizationsDTO = InvalidAuthorizationsDTO(emptyList(), emptyList()) + + override fun renewalGroupMember( + userId: String, + projectCode: String, + renewalConditionReq: GroupMemberSingleRenewalReq + ): Boolean = true + + override fun batchRenewalGroupMembersFromManager( + userId: String, + projectCode: String, + renewalConditionReq: GroupMemberRenewalConditionReq + ): Boolean = true + + override fun batchHandoverGroupMembersFromManager( + userId: String, + projectCode: String, + handoverMemberDTO: GroupMemberHandoverConditionReq + ): Boolean = true + + override fun batchHandoverApplicationFromPersonal( + userId: String, + projectCode: String, + handoverMemberDTO: GroupMemberHandoverConditionReq + ): Boolean = true + + override fun batchDeleteResourceGroupMembersFromManager( + userId: String, + projectCode: String, + removeMemberDTO: GroupMemberRemoveConditionReq + ): Boolean = true + + override fun batchDeleteResourceGroupMembersFromPersonal( + userId: String, + projectCode: String, + removeMemberDTO: GroupMemberRemoveConditionReq + ): Boolean = true + + override fun batchOperateGroupMembersCheck( + userId: String, + projectCode: String, + batchOperateType: BatchOperateType, + conditionReq: GroupMemberCommonConditionReq + ): BatchOperateGroupMemberCheckVo = BatchOperateGroupMemberCheckVo(totalCount = 0) + + override fun removeMemberFromProject( + userId: String, + projectCode: String, + removeMemberFromProjectReq: RemoveMemberFromProjectReq + ): List = emptyList() + + override fun removeMemberFromProjectCheck( + userId: String, + projectCode: String, + removeMemberFromProjectReq: RemoveMemberFromProjectReq + ): Boolean = true + + override fun handleHanoverApplication(request: HandoverOverviewUpdateReq): Boolean = true + + override fun getResourceType2CountOfHandover(queryReq: ResourceType2CountOfHandoverQuery): List { + return emptyList() + } + + override fun listAuthorizationsOfHandover(queryReq: HandoverDetailsQueryReq): SQLPage { + return SQLPage(0, emptyList()) + } + + override fun listGroupsOfHandover(queryReq: HandoverDetailsQueryReq): SQLPage { + return SQLPage(0, emptyList()) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceGroupAndMemberFacadeService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceGroupAndMemberFacadeService.kt deleted file mode 100644 index 289455d5856..00000000000 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceGroupAndMemberFacadeService.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.tencent.devops.auth.provider.sample.service - -import com.tencent.devops.auth.pojo.ResourceMemberInfo -import com.tencent.devops.auth.pojo.dto.IamGroupIdsQueryConditionDTO -import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq -import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo -import com.tencent.devops.auth.pojo.vo.MemberGroupCountWithPermissionsVo -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService -import com.tencent.devops.common.api.model.SQLPage - -class SamplePermissionResourceGroupAndMemberFacadeService : PermissionResourceGroupAndMemberFacadeService { - override fun getMemberGroupsDetails( - projectId: String, - memberId: String, - resourceType: String?, - iamGroupIds: List?, - groupName: String?, - minExpiredAt: Long?, - maxExpiredAt: Long?, - relatedResourceType: String?, - relatedResourceCode: String?, - action: String?, - start: Int?, - limit: Int? - ): SQLPage = SQLPage(0, emptyList()) - - override fun getMemberGroupsCount( - projectCode: String, - memberId: String, - groupName: String?, - minExpiredAt: Long?, - maxExpiredAt: Long?, - relatedResourceType: String?, - relatedResourceCode: String?, - action: String? - ): List = emptyList() - - override fun listIamGroupIdsByConditions( - condition: IamGroupIdsQueryConditionDTO - ): List = emptyList() - - override fun listProjectMembersByComplexConditions( - conditionReq: ProjectMembersQueryConditionReq - ): SQLPage = SQLPage(0, emptyList()) -} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceGroupPermissionService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceGroupPermissionService.kt index 1688b7133dc..997ad7c9ab6 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceGroupPermissionService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceGroupPermissionService.kt @@ -86,6 +86,12 @@ class SamplePermissionResourceGroupPermissionService : PermissionResourceGroupPe action: String ): Boolean = true + override fun isGroupsHasProjectLevelPermission( + projectCode: String, + filterIamGroupIds: List, + action: String + ): Boolean = true + override fun listGroupResourcesWithPermission( projectCode: String, filterIamGroupIds: List, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceMemberService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceMemberService.kt index c412788f26e..79d4ada40a7 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceMemberService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceMemberService.kt @@ -1,16 +1,8 @@ package com.tencent.devops.auth.provider.sample.service import com.tencent.bk.sdk.iam.dto.manager.ManagerMember -import com.tencent.devops.auth.pojo.AuthResourceGroupMember import com.tencent.devops.auth.pojo.ResourceMemberInfo import com.tencent.devops.auth.pojo.dto.GroupMemberRenewalDTO -import com.tencent.devops.auth.pojo.enum.BatchOperateType -import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq -import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq -import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo import com.tencent.devops.auth.pojo.vo.ResourceMemberCountVO import com.tencent.devops.auth.service.iam.PermissionResourceMemberService import com.tencent.devops.common.api.model.SQLPage @@ -70,64 +62,18 @@ class SamplePermissionResourceMemberService : PermissionResourceMemberService { memberRenewalDTO: GroupMemberRenewalDTO ): Boolean = true - override fun renewalGroupMember( - userId: String, - projectCode: String, - renewalConditionReq: GroupMemberSingleRenewalReq - ): Boolean = true - override fun renewalIamGroupMembers( groupId: Int, members: List, expiredAt: Long ): Boolean = true - override fun batchRenewalGroupMembers( - userId: String, - projectCode: String, - renewalConditionReq: GroupMemberRenewalConditionReq - ): Boolean = true - - override fun batchDeleteResourceGroupMembers( - userId: String, - projectCode: String, - removeMemberDTO: GroupMemberCommonConditionReq - ): Boolean = true - override fun deleteIamGroupMembers( groupId: Int, type: String, memberIds: List ): Boolean = true - override fun batchHandoverGroupMembers( - userId: String, - projectCode: String, - handoverMemberDTO: GroupMemberHandoverConditionReq - ): Boolean = true - - override fun batchOperateGroupMembersCheck( - userId: String, - projectCode: String, - batchOperateType: BatchOperateType, - conditionReq: GroupMemberCommonConditionReq - ): BatchOperateGroupMemberCheckVo = BatchOperateGroupMemberCheckVo( - totalCount = 0, - inoperableCount = 0 - ) - - override fun removeMemberFromProject( - userId: String, - projectCode: String, - removeMemberFromProjectReq: RemoveMemberFromProjectReq - ): List = emptyList() - - override fun removeMemberFromProjectCheck( - userId: String, - projectCode: String, - removeMemberFromProjectReq: RemoveMemberFromProjectReq - ): Boolean = true - override fun addGroupMember( projectCode: String, memberId: String, @@ -136,9 +82,11 @@ class SamplePermissionResourceMemberService : PermissionResourceMemberService { iamGroupId: Int ): Boolean = true - override fun addIamGroupMember(groupId: Int, members: List, expiredAt: Long): Boolean { - TODO("Not yet implemented") - } + override fun addIamGroupMember( + groupId: Int, + members: List, + expiredAt: Long + ): Boolean = true override fun getProjectMemberCount(projectCode: String): ResourceMemberCountVO = ResourceMemberCountVO( @@ -161,20 +109,4 @@ class SamplePermissionResourceMemberService : PermissionResourceMemberService { override fun addDepartedFlagToMembers( records: List ): List = emptyList() - - override fun listResourceGroupMembers( - projectCode: String, - memberId: String, - resourceType: String?, - iamGroupIds: List?, - minExpiredAt: Long?, - maxExpiredAt: Long?, - start: Int?, - limit: Int? - ): Pair> = Pair(0, emptyList()) - - override fun listMemberGroupIdsInProject( - projectCode: String, - memberId: String - ): List = emptyList() } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceValidateService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceValidateService.kt index b2ba564a4cf..d8b5f1f1e07 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceValidateService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceValidateService.kt @@ -29,6 +29,7 @@ package com.tencent.devops.auth.provider.sample.service import com.tencent.devops.auth.pojo.dto.PermissionBatchValidateDTO +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.auth.service.iam.PermissionResourceValidateService class SamplePermissionResourceValidateService : PermissionResourceValidateService { @@ -46,4 +47,13 @@ class SamplePermissionResourceValidateService : PermissionResourceValidateServic resourceType: String, resourceCode: String ): Boolean = true + + override fun validateUserProjectPermissionByChannel( + userId: String, + projectCode: String, + operateChannel: OperateChannel, + targetMemberId: String + ) { + return + } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceProjectAuthResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceProjectAuthResourceImpl.kt index e94939ddc5b..e52fd966270 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceProjectAuthResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceProjectAuthResourceImpl.kt @@ -29,6 +29,7 @@ package com.tencent.devops.auth.resources.service import com.tencent.devops.auth.api.service.ServiceProjectAuthResource import com.tencent.devops.auth.pojo.vo.ProjectPermissionInfoVO +import com.tencent.devops.auth.service.PermissionAuthorizationService import com.tencent.devops.auth.service.iam.PermissionProjectService import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.auth.api.pojo.BKAuthProjectRolesResources @@ -41,7 +42,8 @@ import org.springframework.beans.factory.annotation.Autowired @RestResource class ServiceProjectAuthResourceImpl @Autowired constructor( - val permissionProjectService: PermissionProjectService + val permissionProjectService: PermissionProjectService, + val permissionAuthorizationService: PermissionAuthorizationService ) : ServiceProjectAuthResource { @BkApiPermission([BkApiHandleType.API_OPEN_TOKEN_CHECK]) override fun getProjectUsers( @@ -201,4 +203,12 @@ class ServiceProjectAuthResourceImpl @Autowired constructor( ) ) } + + override fun listUserProjects(userId: String): Result> { + return Result( + permissionAuthorizationService.listUserProjects( + userId = userId + ) + ) + } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceGroupResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceGroupResourceImpl.kt index cea4a8350a8..52fde466e35 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceGroupResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceGroupResourceImpl.kt @@ -5,7 +5,7 @@ import com.tencent.devops.auth.pojo.dto.GroupAddDTO import com.tencent.devops.auth.pojo.request.CustomGroupCreateReq import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo import com.tencent.devops.auth.pojo.vo.GroupPermissionDetailVo -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService import com.tencent.devops.auth.service.iam.PermissionResourceGroupService import com.tencent.devops.common.api.model.SQLPage @@ -17,7 +17,7 @@ import com.tencent.devops.common.web.RestResource class ServiceResourceGroupResourceImpl( val permissionResourceGroupService: PermissionResourceGroupService, val resourceGroupPermissionService: PermissionResourceGroupPermissionService, - val resourceGroupAndMemberFacadeService: PermissionResourceGroupAndMemberFacadeService + val permissionManageFacadeService: PermissionManageFacadeService ) : ServiceResourceGroupResource { override fun getGroupPermissionDetail( projectCode: String, @@ -45,7 +45,7 @@ class ServiceResourceGroupResourceImpl( limit: Int? ): Result> { return Result( - resourceGroupAndMemberFacadeService.getMemberGroupsDetails( + permissionManageFacadeService.getMemberGroupsDetails( projectId = projectCode, resourceType = resourceType, memberId = memberId, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceMemberResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceMemberResourceImpl.kt index 02c0636974f..aadd867c2b1 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceMemberResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceMemberResourceImpl.kt @@ -2,6 +2,7 @@ package com.tencent.devops.auth.resources.service import com.tencent.devops.auth.api.service.ServiceResourceMemberResource import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.auth.service.iam.PermissionResourceMemberService import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.auth.api.pojo.BkAuthGroup @@ -14,8 +15,9 @@ import com.tencent.devops.project.pojo.ProjectDeleteUserInfo import java.util.concurrent.TimeUnit @RestResource -class ServiceResourceMemberResourceImpl constructor( - private val permissionResourceMemberService: PermissionResourceMemberService +class ServiceResourceMemberResourceImpl( + private val permissionResourceMemberService: PermissionResourceMemberService, + private val permissionManageFacadeService: PermissionManageFacadeService ) : ServiceResourceMemberResource { @BkApiPermission([BkApiHandleType.API_OPEN_TOKEN_CHECK]) override fun getResourceGroupMembers( @@ -107,7 +109,7 @@ class ServiceResourceMemberResourceImpl constructor( renewalConditionReq: GroupMemberSingleRenewalReq ): Result { return Result( - permissionResourceMemberService.renewalGroupMember( + permissionManageFacadeService.renewalGroupMember( userId = userId, projectCode = projectCode, renewalConditionReq = renewalConditionReq diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthAuthorizationResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthAuthorizationResourceImpl.kt index e4c5ed68aa4..0315323b980 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthAuthorizationResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthAuthorizationResourceImpl.kt @@ -1,8 +1,10 @@ package com.tencent.devops.auth.resources.user import com.tencent.devops.auth.api.user.UserAuthAuthorizationResource +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.auth.pojo.vo.ResourceTypeInfoVo import com.tencent.devops.auth.service.PermissionAuthorizationService +import com.tencent.devops.auth.service.iam.PermissionResourceValidateService import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.auth.api.BkManagerCheck @@ -16,17 +18,25 @@ import com.tencent.devops.common.web.RestResource @RestResource class UserAuthAuthorizationResourceImpl( - val permissionAuthorizationService: PermissionAuthorizationService + val permissionAuthorizationService: PermissionAuthorizationService, + val permissionResourceValidateService: PermissionResourceValidateService ) : UserAuthAuthorizationResource { - @BkManagerCheck override fun listResourceAuthorization( userId: String, projectId: String, + operateChannel: OperateChannel?, condition: ResourceAuthorizationConditionRequest ): Result> { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = operateChannel ?: OperateChannel.MANAGER, + targetMemberId = if (operateChannel == OperateChannel.PERSONAL) condition.handoverFrom!! else userId + ) return Result( permissionAuthorizationService.listResourceAuthorizations( - condition = condition + condition = condition, + operateChannel = operateChannel ) ) } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthHandoverResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthHandoverResourceImpl.kt new file mode 100644 index 00000000000..f78881e72fb --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthHandoverResourceImpl.kt @@ -0,0 +1,93 @@ +package com.tencent.devops.auth.resources.user + +import com.tencent.devops.auth.api.user.UserAuthHandoverResource +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.request.ResourceType2CountOfHandoverQuery +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverOverviewVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.auth.service.PermissionAuthorizationService +import com.tencent.devops.auth.service.iam.PermissionHandoverApplicationService +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService +import com.tencent.devops.auth.service.iam.PermissionResourceValidateService +import com.tencent.devops.common.api.exception.PermissionForbiddenException +import com.tencent.devops.common.api.model.SQLPage +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationHandoverConditionRequest +import com.tencent.devops.common.web.RestResource + +@RestResource +class UserAuthHandoverResourceImpl( + private val permissionAuthorizationService: PermissionAuthorizationService, + private val permissionManageFacadeService: PermissionManageFacadeService, + private val permissionHandoverApplicationService: PermissionHandoverApplicationService, + private val permissionResourceValidateService: PermissionResourceValidateService +) : UserAuthHandoverResource { + override fun handoverAuthorizationsApplication( + userId: String, + projectId: String, + condition: ResourceAuthorizationHandoverConditionRequest + ): Result { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = OperateChannel.PERSONAL, + targetMemberId = condition.handoverFrom!! + ) + return Result( + permissionAuthorizationService.handoverAuthorizationsApplication( + operator = userId, + projectCode = projectId, + condition = condition + ) + ) + } + + override fun listHandoverOverviews( + userId: String, + queryRequest: HandoverOverviewQueryReq + ): Result> { + if (userId != queryRequest.memberId) { + throw PermissionForbiddenException( + message = "You have not permission to view other people's handover details!" + ) + } + + return Result(permissionHandoverApplicationService.listHandoverOverviews(queryRequest = queryRequest)) + } + + override fun getResourceType2CountOfHandover( + userId: String, + queryReq: ResourceType2CountOfHandoverQuery + ): Result> { + return Result(permissionManageFacadeService.getResourceType2CountOfHandover(queryReq = queryReq)) + } + + override fun listAuthorizationsOfHandover( + userId: String, + queryReq: HandoverDetailsQueryReq + ): Result> { + return Result(permissionManageFacadeService.listAuthorizationsOfHandover(queryReq = queryReq)) + } + + override fun listGroupsOfHandover( + userId: String, + queryReq: HandoverDetailsQueryReq + ): Result> { + return Result(permissionManageFacadeService.listGroupsOfHandover(queryReq = queryReq)) + } + + override fun handleHanoverApplication(userId: String, request: HandoverOverviewUpdateReq): Result { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = request.projectCode, + operateChannel = OperateChannel.PERSONAL, + targetMemberId = request.operator + ) + return Result(permissionManageFacadeService.handleHanoverApplication(request = request)) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceGroupResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceGroupResourceImpl.kt index 4c99458fb86..db182a05f1e 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceGroupResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceGroupResourceImpl.kt @@ -28,18 +28,20 @@ package com.tencent.devops.auth.resources.user -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.devops.auth.api.user.UserAuthResourceGroupResource import com.tencent.devops.auth.pojo.ResourceMemberInfo import com.tencent.devops.auth.pojo.dto.GroupMemberRenewalDTO import com.tencent.devops.auth.pojo.dto.RenameGroupDTO -import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq +import com.tencent.devops.auth.pojo.enum.MemberType +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.auth.pojo.request.GroupMemberRemoveConditionReq import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo import com.tencent.devops.auth.pojo.vo.IamGroupPoliciesVo -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService import com.tencent.devops.auth.service.iam.PermissionResourceGroupService import com.tencent.devops.auth.service.iam.PermissionResourceMemberService +import com.tencent.devops.auth.service.iam.PermissionResourceValidateService import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.auth.api.BkManagerCheck @@ -50,8 +52,9 @@ import org.springframework.beans.factory.annotation.Autowired class UserAuthResourceGroupResourceImpl @Autowired constructor( private val permissionResourceGroupService: PermissionResourceGroupService, private val permissionResourceMemberService: PermissionResourceMemberService, - private val permissionResourceGroupAndMemberFacadeService: PermissionResourceGroupAndMemberFacadeService, - private val permissionResourceGroupPermissionService: PermissionResourceGroupPermissionService + private val permissionManageFacadeService: PermissionManageFacadeService, + private val permissionResourceGroupPermissionService: PermissionResourceGroupPermissionService, + private val permissionResourceValidateService: PermissionResourceValidateService ) : UserAuthResourceGroupResource { override fun getGroupPolicies( userId: String, @@ -69,7 +72,6 @@ class UserAuthResourceGroupResourceImpl @Autowired constructor( ) } - @BkManagerCheck override fun getMemberGroupsDetails( userId: String, projectId: String, @@ -81,11 +83,19 @@ class UserAuthResourceGroupResourceImpl @Autowired constructor( relatedResourceType: String?, relatedResourceCode: String?, action: String?, + operateChannel: OperateChannel?, start: Int, limit: Int ): Result> { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = operateChannel ?: OperateChannel.MANAGER, + targetMemberId = memberId + ) + return Result( - permissionResourceGroupAndMemberFacadeService.getMemberGroupsDetails( + permissionManageFacadeService.getMemberGroupsDetails( projectId = projectId, resourceType = resourceType, memberId = memberId, @@ -94,6 +104,7 @@ class UserAuthResourceGroupResourceImpl @Autowired constructor( maxExpiredAt = maxExpiredAt, relatedResourceType = relatedResourceType, relatedResourceCode = relatedResourceCode, + operateChannel = operateChannel, action = action, start = start, limit = limit @@ -108,6 +119,12 @@ class UserAuthResourceGroupResourceImpl @Autowired constructor( groupId: Int, memberRenewalDTO: GroupMemberRenewalDTO ): Result { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = OperateChannel.PERSONAL, + targetMemberId = userId + ) return Result( permissionResourceMemberService.renewalGroupMember( userId = userId, @@ -126,14 +143,14 @@ class UserAuthResourceGroupResourceImpl @Autowired constructor( groupId: Int ): Result { return Result( - permissionResourceMemberService.batchDeleteResourceGroupMembers( + permissionManageFacadeService.batchDeleteResourceGroupMembersFromManager( userId = userId, projectCode = projectId, - removeMemberDTO = GroupMemberCommonConditionReq( + removeMemberDTO = GroupMemberRemoveConditionReq( groupIds = listOf(groupId), targetMember = ResourceMemberInfo( id = userId, - type = ManagerScopesEnum.getType(ManagerScopesEnum.USER) + type = MemberType.USER.type ) ) ) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceMemberResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceMemberResourceImpl.kt index bd76fea6919..cc7e3135bcc 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceMemberResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceMemberResourceImpl.kt @@ -3,17 +3,20 @@ package com.tencent.devops.auth.resources.user import com.tencent.devops.auth.api.user.UserAuthResourceMemberResource import com.tencent.devops.auth.pojo.ResourceMemberInfo import com.tencent.devops.auth.pojo.enum.BatchOperateType +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRemoveConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo -import com.tencent.devops.auth.pojo.vo.MemberGroupCountWithPermissionsVo -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.auth.service.iam.PermissionResourceMemberService +import com.tencent.devops.auth.service.iam.PermissionResourceValidateService import com.tencent.devops.auth.service.iam.PermissionService import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.api.pojo.Result @@ -27,7 +30,8 @@ import com.tencent.devops.common.web.RestResource class UserAuthResourceMemberResourceImpl( private val permissionResourceMemberService: PermissionResourceMemberService, private val permissionService: PermissionService, - private val permissionResourceGroupAndMemberFacadeService: PermissionResourceGroupAndMemberFacadeService + private val permissionManageFacadeService: PermissionManageFacadeService, + private val permissionResourceValidateService: PermissionResourceValidateService ) : UserAuthResourceMemberResource { override fun listProjectMembers( userId: String, @@ -69,7 +73,7 @@ class UserAuthResourceMemberResourceImpl( projectMembersQueryConditionReq: ProjectMembersQueryConditionReq ): Result> { return Result( - permissionResourceGroupAndMemberFacadeService.listProjectMembersByComplexConditions( + permissionManageFacadeService.listProjectMembersByComplexConditions( conditionReq = projectMembersQueryConditionReq ) ) @@ -81,13 +85,13 @@ class UserAuthResourceMemberResourceImpl( projectId: String, renewalConditionReq: GroupMemberSingleRenewalReq ): Result { - permissionResourceMemberService.renewalGroupMember( + permissionManageFacadeService.renewalGroupMember( userId = userId, projectCode = projectId, renewalConditionReq = renewalConditionReq ) return Result( - permissionResourceGroupAndMemberFacadeService.getMemberGroupsDetails( + permissionManageFacadeService.getMemberGroupsDetails( projectId = projectId, memberId = renewalConditionReq.targetMember.id, iamGroupIds = listOf(renewalConditionReq.groupId) @@ -96,13 +100,13 @@ class UserAuthResourceMemberResourceImpl( } @BkManagerCheck - override fun batchRenewalGroupMembers( + override fun batchRenewalGroupMembersFromManager( userId: String, projectId: String, renewalConditionReq: GroupMemberRenewalConditionReq ): Result { return Result( - permissionResourceMemberService.batchRenewalGroupMembers( + permissionManageFacadeService.batchRenewalGroupMembersFromManager( userId = userId, projectCode = projectId, renewalConditionReq = renewalConditionReq @@ -111,13 +115,33 @@ class UserAuthResourceMemberResourceImpl( } @BkManagerCheck - override fun batchRemoveGroupMembers( + override fun batchRemoveGroupMembersFromManager( userId: String, projectId: String, - removeMemberDTO: GroupMemberCommonConditionReq + removeMemberDTO: GroupMemberRemoveConditionReq ): Result { return Result( - permissionResourceMemberService.batchDeleteResourceGroupMembers( + permissionManageFacadeService.batchDeleteResourceGroupMembersFromManager( + userId = userId, + projectCode = projectId, + removeMemberDTO = removeMemberDTO + ) + ) + } + + override fun batchRemoveGroupMembersFromPersonal( + userId: String, + projectId: String, + removeMemberDTO: GroupMemberRemoveConditionReq + ): Result { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = OperateChannel.PERSONAL, + targetMemberId = removeMemberDTO.targetMember.id + ) + return Result( + permissionManageFacadeService.batchDeleteResourceGroupMembersFromPersonal( userId = userId, projectCode = projectId, removeMemberDTO = removeMemberDTO @@ -126,13 +150,33 @@ class UserAuthResourceMemberResourceImpl( } @BkManagerCheck - override fun batchHandoverGroupMembers( + override fun batchHandoverGroupMembersFromManager( userId: String, projectId: String, handoverMemberDTO: GroupMemberHandoverConditionReq ): Result { return Result( - permissionResourceMemberService.batchHandoverGroupMembers( + permissionManageFacadeService.batchHandoverGroupMembersFromManager( + userId = userId, + projectCode = projectId, + handoverMemberDTO = handoverMemberDTO + ) + ) + } + + override fun batchHandoverApplicationFromPersonal( + userId: String, + projectId: String, + handoverMemberDTO: GroupMemberHandoverConditionReq + ): Result { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = OperateChannel.PERSONAL, + targetMemberId = handoverMemberDTO.targetMember.id + ) + return Result( + permissionManageFacadeService.batchHandoverApplicationFromPersonal( userId = userId, projectCode = projectId, handoverMemberDTO = handoverMemberDTO @@ -140,15 +184,20 @@ class UserAuthResourceMemberResourceImpl( ) } - @BkManagerCheck override fun batchOperateGroupMembersCheck( userId: String, projectId: String, batchOperateType: BatchOperateType, conditionReq: GroupMemberCommonConditionReq ): Result { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = conditionReq.operateChannel, + targetMemberId = conditionReq.targetMember.id + ) return Result( - permissionResourceMemberService.batchOperateGroupMembersCheck( + permissionManageFacadeService.batchOperateGroupMembersCheck( userId = userId, projectCode = projectId, batchOperateType = batchOperateType, @@ -164,7 +213,7 @@ class UserAuthResourceMemberResourceImpl( removeMemberFromProjectReq: RemoveMemberFromProjectReq ): Result> { return Result( - permissionResourceMemberService.removeMemberFromProject( + permissionManageFacadeService.removeMemberFromProject( userId = userId, projectCode = projectId, removeMemberFromProjectReq = removeMemberFromProjectReq @@ -179,7 +228,7 @@ class UserAuthResourceMemberResourceImpl( removeMemberFromProjectReq: RemoveMemberFromProjectReq ): Result { return Result( - permissionResourceMemberService.removeMemberFromProjectCheck( + permissionManageFacadeService.removeMemberFromProjectCheck( userId = userId, projectCode = projectId, removeMemberFromProjectReq = removeMemberFromProjectReq @@ -187,7 +236,6 @@ class UserAuthResourceMemberResourceImpl( ) } - @BkManagerCheck override fun getMemberGroupCount( userId: String, projectId: String, @@ -197,10 +245,17 @@ class UserAuthResourceMemberResourceImpl( maxExpiredAt: Long?, relatedResourceType: String?, relatedResourceCode: String?, - action: String? - ): Result> { + action: String?, + operateChannel: OperateChannel? + ): Result> { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = operateChannel ?: OperateChannel.MANAGER, + targetMemberId = memberId + ) return Result( - permissionResourceGroupAndMemberFacadeService.getMemberGroupsCount( + permissionManageFacadeService.getMemberGroupsCount( projectCode = projectId, memberId = memberId, groupName = groupName, @@ -208,7 +263,8 @@ class UserAuthResourceMemberResourceImpl( maxExpiredAt = maxExpiredAt, relatedResourceType = relatedResourceType, relatedResourceCode = relatedResourceCode, - action = action + action = action, + operateChannel = operateChannel ) ) } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationService.kt index 301dd66bea8..746b974aa23 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationService.kt @@ -27,6 +27,7 @@ package com.tencent.devops.auth.service +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.auth.pojo.vo.ResourceTypeInfoVo import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.auth.api.pojo.ResetAllResourceAuthorizationReq @@ -77,9 +78,17 @@ interface PermissionAuthorizationService { * 获取项目资源授予记录--根据条件 */ fun listResourceAuthorizations( - condition: ResourceAuthorizationConditionRequest + condition: ResourceAuthorizationConditionRequest, + operateChannel: OperateChannel? = OperateChannel.MANAGER, ): SQLPage + /** + * 获取用户授权相关项目 + */ + fun listUserProjects( + userId: String + ): List + /** * 修改资源授权管理 */ @@ -121,6 +130,15 @@ interface PermissionAuthorizationService { condition: ResourceAuthorizationHandoverConditionRequest ): Map> + /** + * 交接授权申请 + */ + fun handoverAuthorizationsApplication( + operator: String, + projectCode: String, + condition: ResourceAuthorizationHandoverConditionRequest + ): Boolean + /** * 批量重置授权人--项目下全量 */ @@ -129,4 +147,15 @@ interface PermissionAuthorizationService { projectCode: String, condition: ResetAllResourceAuthorizationReq ): List + + /** + * 检查交接人是否有代码库授权权限 + */ + fun checkRepertoryAuthorizationsHanover( + operator: String, + projectCode: String, + repertoryIds: List, + handoverFrom: String, + handoverTo: String + ) } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationServiceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationServiceImpl.kt index dac016a70d2..3d8b2405209 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationServiceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationServiceImpl.kt @@ -3,14 +3,22 @@ package com.tencent.devops.auth.service import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.devops.auth.constant.AuthI18nConstants import com.tencent.devops.auth.constant.AuthMessageCode +import com.tencent.devops.auth.constant.AuthMessageCode.ERROR_HANDOVER_AUTHORIZATION import com.tencent.devops.auth.dao.AuthAuthorizationDao +import com.tencent.devops.auth.pojo.dto.HandoverDetailDTO +import com.tencent.devops.auth.pojo.dto.HandoverOverviewCreateDTO +import com.tencent.devops.auth.pojo.enum.HandoverStatus +import com.tencent.devops.auth.pojo.enum.HandoverType +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.auth.pojo.vo.ResourceTypeInfoVo +import com.tencent.devops.auth.service.iam.PermissionHandoverApplicationService import com.tencent.devops.auth.service.iam.PermissionResourceValidateService import com.tencent.devops.auth.service.iam.PermissionService import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.auth.api.AuthPermission import com.tencent.devops.common.auth.api.AuthResourceType +import com.tencent.devops.common.auth.api.ResourceTypeId import com.tencent.devops.common.auth.api.pojo.ResetAllResourceAuthorizationReq import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationConditionRequest import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationDTO @@ -30,13 +38,14 @@ import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @Service -class PermissionAuthorizationServiceImpl constructor( +class PermissionAuthorizationServiceImpl( private val dslContext: DSLContext, private val authAuthorizationDao: AuthAuthorizationDao, private val client: Client, private val permissionResourceValidateService: PermissionResourceValidateService, private val deptService: DeptService, - private val permissionService: PermissionService + private val permissionService: PermissionService, + private val permissionHandoverApplicationService: PermissionHandoverApplicationService ) : PermissionAuthorizationService { companion object { private val logger = LoggerFactory.getLogger(PermissionAuthorizationServiceImpl::class.java) @@ -121,21 +130,59 @@ class PermissionAuthorizationServiceImpl constructor( } override fun listResourceAuthorizations( - condition: ResourceAuthorizationConditionRequest + condition: ResourceAuthorizationConditionRequest, + operateChannel: OperateChannel? ): SQLPage { - logger.info("list resource authorizations:$condition") - val record = authAuthorizationDao.list( - dslContext = dslContext, - condition = condition - ) - val count = authAuthorizationDao.count( - dslContext = dslContext, - condition = condition - ) - return SQLPage( - count = count.toLong(), - records = record - ) + logger.info("list resource authorizations:$condition|$operateChannel") + val (records, count) = if (operateChannel != OperateChannel.PERSONAL) { + val records = authAuthorizationDao.list( + dslContext = dslContext, + condition = condition + ) + val count = authAuthorizationDao.count( + dslContext = dslContext, + condition = condition + ) + Pair(records, count) + } else { + val beingHandoverDetails = permissionHandoverApplicationService.listMemberHandoverDetails( + projectCode = condition.projectCode, + memberId = condition.handoverFrom!!, + handoverType = HandoverType.AUTHORIZATION, + resourceType = condition.resourceType!! + ) + val beingHandoverResourceCodes = beingHandoverDetails.map { it.itemId }.distinct() + if (condition.queryHandover == true && beingHandoverResourceCodes.isEmpty()) { + Pair(emptyList(), 0L) + } else { + val finalCondition = condition.apply { + when (this.queryHandover) { + true -> this.filterResourceCodes = beingHandoverResourceCodes + false -> this.excludeResourceCodes = beingHandoverResourceCodes + else -> {} + } + } + val records = authAuthorizationDao.list( + dslContext = dslContext, + condition = finalCondition + ).map { + it.copy( + beingHandover = beingHandoverResourceCodes.contains(it.resourceCode), + approver = beingHandoverDetails.find { details -> details.itemId == it.resourceCode }?.approver + ) + } + val count = authAuthorizationDao.count( + dslContext = dslContext, + condition = finalCondition + ) + Pair(records, count) + } + } + return SQLPage(count = count.toLong(), records = records) + } + + override fun listUserProjects(userId: String): List { + return authAuthorizationDao.listUserProjects(dslContext, userId) } override fun modifyResourceAuthorization(resourceAuthorizationList: List): Boolean { @@ -226,6 +273,68 @@ class PermissionAuthorizationServiceImpl constructor( return result } + override fun handoverAuthorizationsApplication( + operator: String, + projectCode: String, + condition: ResourceAuthorizationHandoverConditionRequest + ): Boolean { + val beingHandoverDetails = permissionHandoverApplicationService.listMemberHandoverDetails( + projectCode = condition.projectCode, + memberId = condition.handoverFrom!!, + handoverType = HandoverType.AUTHORIZATION, + resourceType = condition.resourceType + ).map { it.itemId }.distinct() + + val finalCondition = condition.copy( + preCheck = true, + checkPermission = false, + excludeResourceCodes = beingHandoverDetails + ) + + val handoverResult = resetResourceAuthorizationByResourceType( + operator = operator, + projectCode = projectCode, + condition = finalCondition + ) + if (!handoverResult[ResourceAuthorizationHandoverStatus.FAILED].isNullOrEmpty()) { + throw ErrorCodeException(errorCode = ERROR_HANDOVER_AUTHORIZATION) + } + val resourceAuthorizationList = getResourceAuthorizationList(condition = finalCondition) + val authorizationCount = resourceAuthorizationList.size + val flowNo = permissionHandoverApplicationService.generateFlowNo() + val title = permissionHandoverApplicationService.generateTitle( + groupCount = 0, + authorizationCount = authorizationCount + ) + val handoverDetails = mutableListOf() + resourceAuthorizationList.forEach { authorization -> + handoverDetails.add( + HandoverDetailDTO( + projectCode = projectCode, + flowNo = flowNo, + itemId = authorization.resourceCode, + resourceType = authorization.resourceType, + handoverType = HandoverType.AUTHORIZATION + ) + ) + } + // 创建交接单 + permissionHandoverApplicationService.createHandoverApplication( + overview = HandoverOverviewCreateDTO( + projectCode = projectCode, + flowNo = flowNo, + title = title, + applicant = condition.handoverFrom!!, + approver = condition.handoverTo!!, + handoverStatus = HandoverStatus.PENDING, + groupCount = 0, + authorizationCount = resourceAuthorizationList.size + ), + details = handoverDetails + ) + return true + } + override fun resetAllResourceAuthorization( operator: String, projectCode: String, @@ -261,6 +370,35 @@ class PermissionAuthorizationServiceImpl constructor( return result } + override fun checkRepertoryAuthorizationsHanover( + operator: String, + projectCode: String, + repertoryIds: List, + handoverFrom: String, + handoverTo: String + ) { + val canHandoverRepertory = resetResourceAuthorizationByResourceType( + operator = operator, + projectCode = projectCode, + condition = ResourceAuthorizationHandoverConditionRequest( + projectCode = projectCode, + resourceType = ResourceTypeId.REPERTORY, + fullSelection = true, + filterResourceCodes = repertoryIds, + handoverChannel = HandoverChannelCode.MANAGER, + handoverFrom = handoverFrom, + handoverTo = handoverTo, + checkPermission = false, + preCheck = true + ) + )[ResourceAuthorizationHandoverStatus.FAILED].isNullOrEmpty() + if (!canHandoverRepertory) { + throw ErrorCodeException( + errorCode = ERROR_HANDOVER_AUTHORIZATION + ) + } + } + private fun addHandoverFromCnName( resourceAuthorizationList: List ) { @@ -352,6 +490,7 @@ class PermissionAuthorizationServiceImpl constructor( resourceAuthorizationHandoverDTOs = resourceAuthorizationHandoverDTOs ).data } + AuthResourceType.CODE_REPERTORY.value -> { client.get(ServiceRepositoryAuthorizationResource::class).resetRepositoryAuthorization( projectId = projectId, @@ -359,6 +498,7 @@ class PermissionAuthorizationServiceImpl constructor( resourceAuthorizationHandoverDTOs = resourceAuthorizationHandoverDTOs ).data } + AuthResourceType.ENVIRONMENT_ENV_NODE.value -> { client.get(ServiceEnvNodeAuthorizationResource::class).resetEnvNodeAuthorization( projectId = projectId, @@ -366,6 +506,7 @@ class PermissionAuthorizationServiceImpl constructor( resourceAuthorizationHandoverDTOs = resourceAuthorizationHandoverDTOs ).data } + else -> { null } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionHandoverApplicationService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionHandoverApplicationService.kt new file mode 100644 index 00000000000..b4cad1299b5 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionHandoverApplicationService.kt @@ -0,0 +1,80 @@ +package com.tencent.devops.auth.service.iam + +import com.tencent.devops.auth.pojo.dto.HandoverDetailDTO +import com.tencent.devops.auth.pojo.dto.HandoverOverviewCreateDTO +import com.tencent.devops.auth.pojo.enum.HandoverType +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverOverviewVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.common.api.model.SQLPage + +interface PermissionHandoverApplicationService { + /** + * 创建权限交接申请单 + * */ + fun createHandoverApplication(overview: HandoverOverviewCreateDTO, details: List) + + /** + * 生成交接单标题 + * */ + fun generateTitle(groupCount: Int, authorizationCount: Int): String + + /** + * 生成流程单号 + * */ + fun generateFlowNo(): String + + /** + * 更新权限交接申请单 + * */ + fun updateHandoverApplication(overview: HandoverOverviewUpdateReq) + + /** + * 根据流程单号获取权限交接总览 + * */ + fun getHandoverOverview(flowNo: String): HandoverOverviewVo + + /** + * 权限交接总览列表 + * */ + fun listHandoverOverviews(queryRequest: HandoverOverviewQueryReq): SQLPage + + /** + * 获取交接单详情 + * */ + fun listHandoverDetails( + projectCode: String, + flowNo: String, + resourceType: String? = null, + handoverType: HandoverType? = null + ): List + + /** + * 获取用户在项目下正在交接的组/授权 + * */ + fun listMemberHandoverDetails( + projectCode: String, + memberId: String, + handoverType: HandoverType, + resourceType: String? = null + ): List + + /** + * 获取交接单中授权相关 + * */ + fun listAuthorizationsOfHandoverApplication(queryReq: HandoverDetailsQueryReq): SQLPage + + /** + * 获取交接单中用户组相关 + * */ + fun listGroupsOfHandoverApplication(queryReq: HandoverDetailsQueryReq): SQLPage + + /** + * 根据资源类型进行分类 + * */ + fun getResourceType2CountOfHandoverApplication(flowNo: String): List +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionManageFacadeService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionManageFacadeService.kt new file mode 100644 index 00000000000..6db0cca2a12 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionManageFacadeService.kt @@ -0,0 +1,222 @@ +package com.tencent.devops.auth.service.iam + +import com.tencent.devops.auth.pojo.AuthResourceGroupMember +import com.tencent.devops.auth.pojo.ResourceMemberInfo +import com.tencent.devops.auth.pojo.dto.IamGroupIdsQueryConditionDTO +import com.tencent.devops.auth.pojo.dto.InvalidAuthorizationsDTO +import com.tencent.devops.auth.pojo.enum.BatchOperateType +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRemoveConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq +import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq +import com.tencent.devops.auth.pojo.request.ResourceType2CountOfHandoverQuery +import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo +import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.common.api.model.SQLPage + +/** + * 权限管理门面类 + */ +interface PermissionManageFacadeService { + /** + * 查询成员所在资源用户组详情 + * 管理员视角返回用户直接加入/模板加入的用户组 + * 个人视角返回用户直接加入/模板加入/组织加入的用户组 + * */ + fun getMemberGroupsDetails( + projectId: String, + memberId: String, + resourceType: String? = null, + iamGroupIds: List? = null, + groupName: String? = null, + minExpiredAt: Long? = null, + maxExpiredAt: Long? = null, + relatedResourceType: String? = null, + relatedResourceCode: String? = null, + action: String? = null, + operateChannel: OperateChannel? = OperateChannel.MANAGER, + start: Int? = null, + limit: Int? = null + ): SQLPage + + /** + * 获取用户有权限的用户组数量 + * 管理员视角返回用户直接加入/模板加入的用户组 + * 个人视角返回用户直接加入/模板加入/组织加入的用户组 + * */ + fun getMemberGroupsCount( + projectCode: String, + memberId: String, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long?, + relatedResourceType: String?, + relatedResourceCode: String?, + action: String?, + operateChannel: OperateChannel? = OperateChannel.MANAGER + ): List + + /** + * 根据条件查询组ID + * */ + fun listIamGroupIdsByConditions( + condition: IamGroupIdsQueryConditionDTO + ): List + + /** + * 获取用户在该项目加入的组 + * */ + fun listMemberGroupIdsInProject( + projectCode: String, + memberId: String + ): List + + /** + * 查询成员所在资源用户组列表 + * 管理员视角返回用户直接加入/模板加入的用户组 + * 个人视角返回用户直接加入/模板加入/组织加入的用户组 + * */ + fun listResourceGroupMembers( + projectCode: String, + memberId: String, + resourceType: String? = null, + iamGroupIds: List? = null, + excludeIamGroupIds: List? = null, + minExpiredAt: Long? = null, + maxExpiredAt: Long? = null, + operateChannel: OperateChannel? = OperateChannel.MANAGER, + start: Int? = null, + limit: Int? = null + ): Pair> + + /** + * 根据复杂条件进行搜索,用于用户管理界面 + * */ + fun listProjectMembersByComplexConditions( + conditionReq: ProjectMembersQueryConditionReq + ): SQLPage + + /** + * 为了避免流水线代持人/代码库oauth权限失效,需要对用户退出/交接用户组进行检查。 + * 返回结果: + * 1、引起代持人权限失效的用户组。 + * 2、引起代持人权限失效的流水线。 + * 3、引起代码库oauth失效的代码库(当用户操作完组后,不再拥有项目访问权限时,会代码库oauth引起失效) + **/ + fun listInvalidAuthorizationsAfterOperatedGroups( + projectCode: String, + iamGroupIds: List, + memberId: String + ): InvalidAuthorizationsDTO + + /** + * 续期用户权限-无需审批版本 + * */ + fun renewalGroupMember( + userId: String, + projectCode: String, + renewalConditionReq: GroupMemberSingleRenewalReq + ): Boolean + + /** + * 批量续期用户权限-管理员视角 + * */ + fun batchRenewalGroupMembersFromManager( + userId: String, + projectCode: String, + renewalConditionReq: GroupMemberRenewalConditionReq + ): Boolean + + /** + * 批量交接-管理员视角 + * */ + fun batchHandoverGroupMembersFromManager( + userId: String, + projectCode: String, + handoverMemberDTO: GroupMemberHandoverConditionReq + ): Boolean + + /** + * 批量交接申请-个人视角 + * */ + fun batchHandoverApplicationFromPersonal( + userId: String, + projectCode: String, + handoverMemberDTO: GroupMemberHandoverConditionReq + ): Boolean + + /** + * 批量移除-管理员视角 + * */ + fun batchDeleteResourceGroupMembersFromManager( + userId: String, + projectCode: String, + removeMemberDTO: GroupMemberRemoveConditionReq + ): Boolean + + /** + * 批量退出-个人视角 + * */ + fun batchDeleteResourceGroupMembersFromPersonal( + userId: String, + projectCode: String, + removeMemberDTO: GroupMemberRemoveConditionReq + ): Boolean + + /** + * 批量操作检查 + * */ + fun batchOperateGroupMembersCheck( + userId: String, + projectCode: String, + batchOperateType: BatchOperateType, + conditionReq: GroupMemberCommonConditionReq + ): BatchOperateGroupMemberCheckVo + + /** + * 将用户移出项目-管理员视角 + * */ + fun removeMemberFromProject( + userId: String, + projectCode: String, + removeMemberFromProjectReq: RemoveMemberFromProjectReq + ): List + + /** + * 将用户移出项目检查-管理员视角 + * */ + fun removeMemberFromProjectCheck( + userId: String, + projectCode: String, + removeMemberFromProjectReq: RemoveMemberFromProjectReq + ): Boolean + + /** + * 处理交接审批单 + * */ + fun handleHanoverApplication(request: HandoverOverviewUpdateReq): Boolean + + /** + * 根据资源类型进行分类-交接 + * */ + fun getResourceType2CountOfHandover(queryReq: ResourceType2CountOfHandoverQuery): List + + /** + * 获取交接中授权相关-分为预览/交接单审批两个场景 + * */ + fun listAuthorizationsOfHandover(queryReq: HandoverDetailsQueryReq): SQLPage + + /** + * 获取交接中用户组相关-分为预览/交接单审批两个场景 + * */ + fun listGroupsOfHandover(queryReq: HandoverDetailsQueryReq): SQLPage +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceGroupAndMemberFacadeService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceGroupAndMemberFacadeService.kt deleted file mode 100644 index 4a591487e58..00000000000 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceGroupAndMemberFacadeService.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.tencent.devops.auth.service.iam - -import com.tencent.devops.auth.pojo.ResourceMemberInfo -import com.tencent.devops.auth.pojo.dto.IamGroupIdsQueryConditionDTO -import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq -import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo -import com.tencent.devops.auth.pojo.vo.MemberGroupCountWithPermissionsVo -import com.tencent.devops.common.api.model.SQLPage - -interface PermissionResourceGroupAndMemberFacadeService { - /** - * 查询成员所在资源用户组详情,直接加入+通过用户组(模板)加入 - * */ - fun getMemberGroupsDetails( - projectId: String, - memberId: String, - resourceType: String? = null, - iamGroupIds: List? = null, - groupName: String? = null, - minExpiredAt: Long? = null, - maxExpiredAt: Long? = null, - relatedResourceType: String? = null, - relatedResourceCode: String? = null, - action: String? = null, - start: Int? = null, - limit: Int? = null - ): SQLPage - - /** - * 获取用户有权限的用户组数量 - * */ - fun getMemberGroupsCount( - projectCode: String, - memberId: String, - groupName: String?, - minExpiredAt: Long?, - maxExpiredAt: Long?, - relatedResourceType: String?, - relatedResourceCode: String?, - action: String? - ): List - - /** - * 根据条件查询组ID - * */ - fun listIamGroupIdsByConditions( - condition: IamGroupIdsQueryConditionDTO - ): List - - /** - * 根据复杂条件进行搜索,用于用户管理界面 - * */ - fun listProjectMembersByComplexConditions( - conditionReq: ProjectMembersQueryConditionReq - ): SQLPage -} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceGroupPermissionService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceGroupPermissionService.kt index 0172b4516c2..9063615cc7e 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceGroupPermissionService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceGroupPermissionService.kt @@ -91,6 +91,15 @@ interface PermissionResourceGroupPermissionService { action: String ): Boolean + /** + * 是否用户拥有项目级别权限,如整个项目流水线执行权限/项目的管理权限等。 + * */ + fun isGroupsHasProjectLevelPermission( + projectCode: String, + filterIamGroupIds: List, + action: String + ): Boolean + /** * 获取用户组有权限的资源 * */ diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceMemberService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceMemberService.kt index 91073660f2f..7b4aa254ddc 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceMemberService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceMemberService.kt @@ -1,16 +1,8 @@ package com.tencent.devops.auth.service.iam import com.tencent.bk.sdk.iam.dto.manager.ManagerMember -import com.tencent.devops.auth.pojo.AuthResourceGroupMember import com.tencent.devops.auth.pojo.ResourceMemberInfo import com.tencent.devops.auth.pojo.dto.GroupMemberRenewalDTO -import com.tencent.devops.auth.pojo.enum.BatchOperateType -import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq -import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq -import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo import com.tencent.devops.auth.pojo.vo.ResourceMemberCountVO import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.auth.api.pojo.BkAuthGroup @@ -50,25 +42,6 @@ interface PermissionResourceMemberService { fun addDepartedFlagToMembers(records: List): List - fun listResourceGroupMembers( - projectCode: String, - memberId: String, - resourceType: String? = null, - iamGroupIds: List? = null, - minExpiredAt: Long? = null, - maxExpiredAt: Long? = null, - start: Int? = null, - limit: Int? = null - ): Pair> - - /** - * 获取用户在该项目加入的组 - * */ - fun listMemberGroupIdsInProject( - projectCode: String, - memberId: String - ): List - fun batchDeleteResourceGroupMembers( projectCode: String, iamGroupId: Int, @@ -76,43 +49,12 @@ interface PermissionResourceMemberService { departments: List? = emptyList() ): Boolean - fun batchDeleteResourceGroupMembers( - userId: String, - projectCode: String, - removeMemberDTO: GroupMemberCommonConditionReq - ): Boolean - fun deleteIamGroupMembers( groupId: Int, type: String, memberIds: List ): Boolean - fun batchHandoverGroupMembers( - userId: String, - projectCode: String, - handoverMemberDTO: GroupMemberHandoverConditionReq - ): Boolean - - fun batchOperateGroupMembersCheck( - userId: String, - projectCode: String, - batchOperateType: BatchOperateType, - conditionReq: GroupMemberCommonConditionReq - ): BatchOperateGroupMemberCheckVo - - fun removeMemberFromProject( - userId: String, - projectCode: String, - removeMemberFromProjectReq: RemoveMemberFromProjectReq - ): List - - fun removeMemberFromProjectCheck( - userId: String, - projectCode: String, - removeMemberFromProjectReq: RemoveMemberFromProjectReq - ): Boolean - fun roleCodeToIamGroupId( projectCode: String, roleCode: String @@ -134,25 +76,12 @@ interface PermissionResourceMemberService { memberRenewalDTO: GroupMemberRenewalDTO ): Boolean - // 无需审批版本 - fun renewalGroupMember( - userId: String, - projectCode: String, - renewalConditionReq: GroupMemberSingleRenewalReq - ): Boolean - fun renewalIamGroupMembers( groupId: Int, members: List, expiredAt: Long ): Boolean - fun batchRenewalGroupMembers( - userId: String, - projectCode: String, - renewalConditionReq: GroupMemberRenewalConditionReq - ): Boolean - fun addGroupMember( projectCode: String, memberId: String, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceValidateService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceValidateService.kt index e81196b7eec..c9c9e261ce9 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceValidateService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceValidateService.kt @@ -29,6 +29,7 @@ package com.tencent.devops.auth.service.iam import com.tencent.devops.auth.pojo.dto.PermissionBatchValidateDTO +import com.tencent.devops.auth.pojo.enum.OperateChannel interface PermissionResourceValidateService { fun batchValidateUserResourcePermission( @@ -46,4 +47,14 @@ interface PermissionResourceValidateService { resourceType: String, resourceCode: String ): Boolean + + /** + * 根据渠道来校验用户权限,主要用户管理界面/个人视角 + */ + fun validateUserProjectPermissionByChannel( + userId: String, + projectCode: String, + operateChannel: OperateChannel, + targetMemberId: String + ) } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/lock/HandleHandoverApplicationLock.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/lock/HandleHandoverApplicationLock.kt new file mode 100644 index 00000000000..741873edb55 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/lock/HandleHandoverApplicationLock.kt @@ -0,0 +1,45 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.devops.auth.service.lock + +import com.tencent.devops.common.redis.RedisLock +import com.tencent.devops.common.redis.RedisOperation + +class HandleHandoverApplicationLock( + redisOperation: RedisOperation, + flowNo: String +) : + RedisLock( + redisOperation = redisOperation, + lockKey = "auth.handover.$flowNo.lock", + expiredTimeInSeconds = 1800 + ) { + override fun decorateKey(key: String): String { + return key + } +} diff --git a/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/BKRepoProjectUpdateRequest.kt b/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/BKRepoProjectUpdateRequest.kt index 9d88f752de6..3657af9a6d6 100644 --- a/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/BKRepoProjectUpdateRequest.kt +++ b/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/BKRepoProjectUpdateRequest.kt @@ -12,5 +12,5 @@ data class BKRepoProjectUpdateRequest( @get:Schema(title = "项目新建仓库默认使用的存储") val credentialsKey: String? = null, @get:Schema(title = "设置项目新建仓库默认使用默认存储") - val useDefaultCredentialsKey: Boolean? = false, + val useDefaultCredentialsKey: Boolean? = false ) diff --git a/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/ProjectMetadata.kt b/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/ProjectMetadata.kt index f53d41090d8..2c31b9a545b 100644 --- a/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/ProjectMetadata.kt +++ b/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/ProjectMetadata.kt @@ -8,5 +8,5 @@ data class ProjectMetadata( /** * 元数据值 */ - var value: Any, + var value: Any ) diff --git a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/ActionId.kt b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/ActionId.kt index 835cf51999a..b331faa1d4b 100644 --- a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/ActionId.kt +++ b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/ActionId.kt @@ -2,6 +2,7 @@ package com.tencent.devops.common.auth.api object ActionId { // 项目 + const val PROJECT_VISIT = "project_visit" const val PROJECT_CREATE = "project_create" const val PROJECT_EDIT = "project_edit" const val PROJECT_ENABLE = "project_enable" diff --git a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationConditionRequest.kt b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationConditionRequest.kt index d5f8241e220..9ec5d746ead 100644 --- a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationConditionRequest.kt +++ b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationConditionRequest.kt @@ -12,12 +12,18 @@ open class ResourceAuthorizationConditionRequest( open val resourceType: String? = null, @get:Schema(title = "资源名称") open val resourceName: String? = null, + @get:Schema(title = "过滤资源ID列表") + open var filterResourceCodes: List? = null, + @get:Schema(title = "排除资源ID列表") + open var excludeResourceCodes: List? = null, @get:Schema(title = "授予人") open val handoverFrom: String? = null, @get:Schema(title = "greaterThanHandoverTime") open val greaterThanHandoverTime: Long? = null, @get:Schema(title = "lessThanHandoverTime") open val lessThanHandoverTime: Long? = null, + @get:Schema(title = "是否查询交接中单据") + open val queryHandover: Boolean? = null, @Parameter(description = "第几页", required = false) open val page: Int? = null, @Parameter(description = "每页条数", required = false) diff --git a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationHandoverConditionRequest.kt b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationHandoverConditionRequest.kt index 21fd1979736..633031a4fc8 100644 --- a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationHandoverConditionRequest.kt +++ b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationHandoverConditionRequest.kt @@ -12,6 +12,10 @@ data class ResourceAuthorizationHandoverConditionRequest( override val resourceType: String, @get:Schema(title = "资源名称") override val resourceName: String? = null, + @get:Schema(title = "过滤资源ID列表") + override var filterResourceCodes: List? = null, + @get:Schema(title = "排除资源ID列表") + override var excludeResourceCodes: List? = null, @get:Schema(title = "授予人") override val handoverFrom: String? = null, @get:Schema(title = "greaterThanHandoverTime") @@ -38,6 +42,8 @@ data class ResourceAuthorizationHandoverConditionRequest( projectCode = projectCode, resourceType = resourceType, resourceName = resourceName, + filterResourceCodes = filterResourceCodes, + excludeResourceCodes = excludeResourceCodes, handoverFrom = handoverFrom, greaterThanHandoverTime = greaterThanHandoverTime, lessThanHandoverTime = lessThanHandoverTime, diff --git a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationResponse.kt b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationResponse.kt index 0c396b8c198..179fc004daa 100644 --- a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationResponse.kt +++ b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationResponse.kt @@ -5,6 +5,8 @@ import io.swagger.v3.oas.annotations.media.Schema @Schema(title = "资源授权返回体") @Suppress("LongParameterList") data class ResourceAuthorizationResponse( + @get:Schema(title = "ID") + val id: Long? = null, @get:Schema(title = "项目ID") val projectCode: String, @get:Schema(title = "资源类型") @@ -20,5 +22,9 @@ data class ResourceAuthorizationResponse( @get:Schema(title = "授予人中文名称") val handoverFromCnName: String? = null, @get:Schema(title = "是否有执行权限") - val executePermission: Boolean? = null + val executePermission: Boolean? = null, + @get:Schema(title = "是否正在交接,用于我的授权界面") + val beingHandover: Boolean? = null, + @get:Schema(title = "交接审批人") + val approver: String? = null ) diff --git a/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/api/user/UserProjectResource.kt b/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/api/user/UserProjectResource.kt index e8f127c08cd..3faea6cd8b3 100644 --- a/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/api/user/UserProjectResource.kt +++ b/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/api/user/UserProjectResource.kt @@ -89,7 +89,10 @@ interface UserProjectResource { sortType: ProjectSortType?, @Parameter(description = "排序规则", required = false) @QueryParam("collation") - collation: ProjectCollation? + collation: ProjectCollation?, + @Parameter(description = "是否查询授权相关项目", required = false) + @QueryParam("queryAuthorization") + queryAuthorization: Boolean? ): Result> @GET diff --git a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/resources/UserProjectResourceImpl.kt b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/resources/UserProjectResourceImpl.kt index 5dc5d7e84de..2973270a59a 100644 --- a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/resources/UserProjectResourceImpl.kt +++ b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/resources/UserProjectResourceImpl.kt @@ -69,7 +69,8 @@ class UserProjectResourceImpl @Autowired constructor( enabled: Boolean?, unApproved: Boolean?, sortType: ProjectSortType?, - collation: ProjectCollation? + collation: ProjectCollation?, + queryAuthorization: Boolean? ): Result> { return Result( projectService.list( @@ -78,7 +79,8 @@ class UserProjectResourceImpl @Autowired constructor( enabled = enabled, unApproved = unApproved ?: false, sortType = sortType ?: ProjectSortType.PROJECT_NAME, - collation = collation ?: ProjectCollation.DEFAULT + collation = collation ?: ProjectCollation.DEFAULT, + queryAuthorization = queryAuthorization ) ) } diff --git a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/ProjectService.kt b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/ProjectService.kt index c59da263686..4c1452278e3 100644 --- a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/ProjectService.kt +++ b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/ProjectService.kt @@ -164,7 +164,9 @@ interface ProjectService { enabled: Boolean? = null, unApproved: Boolean, sortType: ProjectSortType? = null, - collation: ProjectCollation? = null + collation: ProjectCollation? = null, + // 获取授权相关项目(主要用于个人视角界面) + queryAuthorization: Boolean? = false ): List fun listProjectsForApply( diff --git a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/impl/AbsProjectServiceImpl.kt b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/impl/AbsProjectServiceImpl.kt index cda63255184..f841f698ad4 100644 --- a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/impl/AbsProjectServiceImpl.kt +++ b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/impl/AbsProjectServiceImpl.kt @@ -32,6 +32,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.tencent.bk.audit.annotations.ActionAuditRecord import com.tencent.bk.audit.annotations.AuditInstanceRecord import com.tencent.bk.audit.context.ActionAuditContext +import com.tencent.devops.auth.api.service.ServiceProjectAuthResource import com.tencent.devops.common.api.enums.SystemModuleEnum import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.exception.InvalidParamException @@ -809,15 +810,29 @@ abstract class AbsProjectServiceImpl @Autowired constructor( enabled: Boolean?, unApproved: Boolean, sortType: ProjectSortType?, - collation: ProjectCollation? + collation: ProjectCollation?, + queryAuthorization: Boolean? ): List { val startEpoch = System.currentTimeMillis() var success = false try { + // 获取有访问权限的项目 val projectsWithVisitPermission = getProjectFromAuth( userId = userId, accessToken = accessToken - ).toSet() + ).toMutableSet() + // 获取授权相关项目,主要用于个人视角权限管理 + if (queryAuthorization == true) { + val projectWithAuthorization = try { + client.get(ServiceProjectAuthResource::class).listUserProjects(userId).data ?: emptyList() + } catch (ex: Exception) { + emptyList() + } + projectsWithVisitPermission.apply { + this.addAll(projectWithAuthorization) + } + } + if (projectsWithVisitPermission.isEmpty() && !unApproved) { return emptyList() } diff --git a/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/dao/v2/QualityMetadataDao.kt b/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/dao/v2/QualityMetadataDao.kt index 878379d7290..a6b5950add2 100644 --- a/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/dao/v2/QualityMetadataDao.kt +++ b/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/dao/v2/QualityMetadataDao.kt @@ -28,6 +28,7 @@ package com.tencent.devops.quality.dao.v2 import com.tencent.devops.common.api.util.PageUtil +import com.tencent.devops.common.db.utils.skipCheck import com.tencent.devops.model.quality.tables.TQualityMetadata import com.tencent.devops.model.quality.tables.records.TQualityMetadataRecord import com.tencent.devops.quality.api.v2.pojo.op.QualityMetaData @@ -65,6 +66,7 @@ class QualityMetadataDao { return with(TQualityMetadata.T_QUALITY_METADATA) { dslContext.selectFrom(this) .where(ELEMENT_TYPE.eq(elementType)) + .skipCheck() .fetch() } } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryAuthService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryAuthService.kt index 0d5ae9b7177..d69d780b489 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryAuthService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryAuthService.kt @@ -153,7 +153,7 @@ class RepositoryAuthService @Autowired constructor( repositoryInfos.map { val entity = ResourceAuthorizationResponse( projectCode = projectId, - resourceType = AuthResourceType.PIPELINE_DEFAULT.value, + resourceType = AuthResourceType.CODE_REPERTORY.value, resourceName = it.aliasName, resourceCode = it.repositoryHashId!!, handoverTime = it.updatedTime, diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/AtomDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/AtomDao.kt index f127eb0e29e..3c2a3237fc4 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/AtomDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/AtomDao.kt @@ -1198,7 +1198,7 @@ class AtomDao : AtomBaseDao() { listOf( AtomStatusEnum.UNDERCARRIAGING.status.toByte(), AtomStatusEnum.UNDERCARRIAGED.status.toByte(), - AtomStatusEnum.RELEASED.status.toByte(), + AtomStatusEnum.RELEASED.status.toByte() ) )) } else { diff --git a/support-files/i18n/auth/message_en_US.properties b/support-files/i18n/auth/message_en_US.properties index 9b78ae65485..1c955293c00 100644 --- a/support-files/i18n/auth/message_en_US.properties +++ b/support-files/i18n/auth/message_en_US.properties @@ -83,6 +83,13 @@ 2121088=The target member and the handover person are not allowed to be the same. 2121089=Expired permissions are not allowed to be transferred 2121090=Please wait until the next day for user information to be synchronized before trying again, as the information of new users has not yet been synchronized. +2121091=Handover overview not exist +2121092=The handover request has already been processed, and duplicate actions are not allowed +2121093=You are not the initiator of this handover request, so you are not authorized to revoke it +2121094=Due to the fact that you are not the approver of this handover request, you are unable to perform any actions on it. +2121095=The handover request is currently being processed. Please be patient and wait for further updates. +2121096=The handover operation is illegal and the user does not have authorization permissions + bkAdministratorNotExpired=Permission has not expired and no action is required bkAgreeRenew=Agree to renew bkApproverAgreeRenew=Approver agreed to your permission renewal @@ -333,3 +340,6 @@ rule.resourceType.name=Andon Rule bkMemberExpiredAtDisplayExpired=expired bkMemberExpiredAtDisplayNormal={0} days bkMemberExpiredAtDisplayPermanent=permanent +bkApplyToHandover=apply to hand over +bkHandoverGroups={0} groups +bkHandoverAuthorizations={0} authorizations diff --git a/support-files/i18n/auth/message_zh_CN.properties b/support-files/i18n/auth/message_zh_CN.properties index c883c136e04..c6aa3be3893 100644 --- a/support-files/i18n/auth/message_zh_CN.properties +++ b/support-files/i18n/auth/message_zh_CN.properties @@ -83,6 +83,13 @@ 2121088=目标对象和交接人不允许相同 2121089=已过期的权限不允许交接 2121090=请等待第二天用户信息同步后再尝试操作,因为新入职用户的信息尚未同步完成。 +2121091=权限交接记录不存在 +2121092=该交接申请单已被处理,不允许重复操作 +2121093=由于您不是该交接申请单的发起人,无法进行撤销操作 +2121094=由于您不是该交接申请单的审批人,无法进行任何操作 +2121095=该交接申请单正在被处理中,请耐心等待 +2121096=交接操作不合法,用户没有对应授权的权限 + bkAdministratorNotExpired=权限还未过期,不需要操作 bkAgreeRenew=同意续期 bkApproverAgreeRenew=审批人同意了您的权限续期 @@ -332,7 +339,10 @@ project.resourceType.name=项目 quality_group.resourceType.name=质量红线通知组 repertory.resourceType.name=代码库 rule.resourceType.name=质量红线规则 - bkMemberExpiredAtDisplayExpired=已过期 bkMemberExpiredAtDisplayNormal={0} 天 bkMemberExpiredAtDisplayPermanent=永久 + +bkApplyToHandover=申请移交 +bkHandoverGroups={0}个权限用户组 +bkHandoverAuthorizations={0}个授权 diff --git a/support-files/sql/1001_ci_auth_ddl_mysql.sql b/support-files/sql/1001_ci_auth_ddl_mysql.sql index 41e31bc6291..77ef7a6cf94 100644 --- a/support-files/sql/1001_ci_auth_ddl_mysql.sql +++ b/support-files/sql/1001_ci_auth_ddl_mysql.sql @@ -440,4 +440,35 @@ CREATE TABLE IF NOT EXISTS `T_AUTH_RESOURCE_GROUP_PERMISSION` ( INDEX `IDX_PROJECT_AND_ACTION_RESOURCE_TYPE` (`PROJECT_CODE`, `ACTION_RELATED_RESOURCE_TYPE`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='资源组权限表'; +CREATE TABLE IF NOT EXISTS `T_AUTH_HANDOVER_OVERVIEW` ( + `ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `PROJECT_CODE` varchar(64) NOT NULL COMMENT '项目ID', + `FLOW_NO` varchar(64) NOT NULL COMMENT '流程单号', + `TITLE` varchar(256) DEFAULT NULL COMMENT '标题', + `APPLICANT` varchar(32) NOT NULL COMMENT '申请人', + `APPROVER` varchar(32) DEFAULT NULL COMMENT '审批人', + `STATUS` int DEFAULT 0 COMMENT '审批结果,0-审批中,1-审批成功,2-审批拒绝,3-撤销', + `GROUP_COUNT` int DEFAULT 0 comment '用户组数', + `AUTHORIZATION_COUNT` int DEFAULT 0 comment '授权个数', + `REMARK` varchar(256) DEFAULT NULL COMMENT '备注', + `CREATE_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `UPDATE_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`ID`), + UNIQUE KEY `UNIQ_FLOW_NO` (`FLOW_NO`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限交接总览表'; + + +CREATE TABLE IF NOT EXISTS `T_AUTH_HANDOVER_DETAIL` ( + `ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `PROJECT_CODE` varchar(64) NOT NULL COMMENT '项目ID', + `FLOW_NO` varchar(64) NOT NULL COMMENT '流程单号', + `ITEM_ID` varchar(255) NOT NULL COMMENT '组/授权资源ID', + `RESOURCE_TYPE` varchar(32) NOT NULL COMMENT '组/授权资源关联的资源类型', + `HANDOVER_TYPE` varchar(32) NOT NULL COMMENT '交接类型-group/authorization', + `CREATE_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `UPDATE_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`ID`), + INDEX `IDX_PROJECT_FLOW` (`PROJECT_CODE`,`FLOW_NO`,`HANDOVER_TYPE`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限交接详细表'; + SET FOREIGN_KEY_CHECKS = 1; diff --git a/support-files/sql/2004_v3.x/2030_ci_auth-update_v3.0_mysql.sql b/support-files/sql/2004_v3.x/2030_ci_auth-update_v3.0_mysql.sql index dd612f62c83..a151c2258e5 100644 --- a/support-files/sql/2004_v3.x/2030_ci_auth-update_v3.0_mysql.sql +++ b/support-files/sql/2004_v3.x/2030_ci_auth-update_v3.0_mysql.sql @@ -38,6 +38,14 @@ BEGIN ALTER TABLE T_AUTH_OAUTH2_ACCESS_TOKEN ADD COLUMN `PASS_WORD` VARCHAR(64) DEFAULT NULL COMMENT '用于密码模式' AFTER `USER_NAME`; END IF; + IF NOT EXISTS(SELECT 1 + FROM information_schema.statistics + WHERE TABLE_SCHEMA = db + AND TABLE_NAME = 'T_AUTH_RESOURCE_AUTHORIZATION' + AND INDEX_NAME = 'HANDOVER_FROM_PROJECT_CODE_INDEX') THEN + ALTER TABLE T_AUTH_RESOURCE_AUTHORIZATION ADD INDEX `HANDOVER_FROM_PROJECT_CODE_INDEX` (`HANDOVER_FROM`,`PROJECT_CODE`); + END IF; + COMMIT; END DELIMITER ;