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 index d4535c868c6..6388afc218e 100644 --- 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 @@ -11,7 +11,7 @@ enum class HandoverAction( 1, "已通过", "你提交的权限交接单 %s 已被 %s 通过。恭喜你完成交接。", - "你提交的权限交接单 %s 已被 %s 通过。恭喜你完成交接。", + "你提交的权限交接单 %s 已被 %s 通过。恭喜你完成交接。" ), // 审批驳回 diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/RemoveMemberFromProjectReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/RemoveMemberFromProjectReq.kt index b2a211530ab..6258adb164d 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/RemoveMemberFromProjectReq.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/RemoveMemberFromProjectReq.kt @@ -19,4 +19,8 @@ data class RemoveMemberFromProjectReq( ) } } + + fun isNeedToHandover(): Boolean { + return handoverTo != null + } } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/MemberExitsProjectCheckVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/MemberExitsProjectCheckVo.kt new file mode 100644 index 00000000000..5712d383cb7 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/MemberExitsProjectCheckVo.kt @@ -0,0 +1,24 @@ +package com.tencent.devops.auth.pojo.vo + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "成员退出项目检查返回体") +data class MemberExitsProjectCheckVo( + @get:Schema(title = "组织加入的数量") + val departmentJoinedCount: Int? = 0, + @get:Schema(title = "组织") + val departments: String? = "", + @get:Schema(title = "唯一管理员组的数量") + val uniqueManagerCount: Int? = 0, + @get:Schema(title = "流水线授权数量") + val pipelineAuthorizationCount: Int? = 0, + @get:Schema(title = "代码库授权数量") + val repositoryAuthorizationCount: Int? = 0, + @get:Schema(title = "环境节点授权数量") + val envNodeAuthorizationCount: Int? = 0 +) { + fun canExitsProjectDirectly(): Boolean { + return departmentJoinedCount == 0 && uniqueManagerCount == 0 && pipelineAuthorizationCount == 0 && + repositoryAuthorizationCount == 0 && envNodeAuthorizationCount == 0 + } +} 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 index c8d6608d9f5..0aef5a07304 100644 --- 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 @@ -48,6 +48,7 @@ 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.MemberExitsProjectCheckVo import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo import com.tencent.devops.auth.service.DeptService import com.tencent.devops.auth.service.PermissionAuthorizationService @@ -59,6 +60,7 @@ 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.exception.OperationException import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.api.util.DateTimeUtil import com.tencent.devops.common.api.util.PageUtil @@ -1189,9 +1191,9 @@ class RbacPermissionManageFacadeServiceImpl( val handoverDetails = buildHandoverDetails( projectCode = projectCode, groupIds = groupIds.map { it.toString() }, - invalidPipelines = invalidPipelines, - invalidRepertoryIds = invalidRepertoryIds, - invalidEnvNodeIds = invalidEnvNodeIds + pipelineAuthorizations = invalidPipelines, + repertoryAuthorizations = invalidRepertoryIds, + envNodeAuthorizations = invalidEnvNodeIds ) val projectName = authResourceService.get( projectCode = projectCode, @@ -1217,9 +1219,9 @@ class RbacPermissionManageFacadeServiceImpl( private fun buildHandoverDetails( projectCode: String, groupIds: List, - invalidPipelines: List, - invalidRepertoryIds: List, - invalidEnvNodeIds: List + pipelineAuthorizations: List, + repertoryAuthorizations: List, + envNodeAuthorizations: List ): List { val handoverDetails = mutableListOf() if (groupIds.isNotEmpty()) { @@ -1240,7 +1242,7 @@ class RbacPermissionManageFacadeServiceImpl( } } - invalidPipelines.forEach { pipelineId -> + pipelineAuthorizations.forEach { pipelineId -> handoverDetails.add( HandoverDetailDTO( projectCode = projectCode, @@ -1250,7 +1252,7 @@ class RbacPermissionManageFacadeServiceImpl( ) ) } - invalidRepertoryIds.forEach { repertoryId -> + repertoryAuthorizations.forEach { repertoryId -> handoverDetails.add( HandoverDetailDTO( projectCode = projectCode, @@ -1260,7 +1262,7 @@ class RbacPermissionManageFacadeServiceImpl( ) ) } - invalidEnvNodeIds.forEach { envNodeId -> + envNodeAuthorizations.forEach { envNodeId -> handoverDetails.add( HandoverDetailDTO( projectCode = projectCode, @@ -1438,9 +1440,9 @@ class RbacPermissionManageFacadeServiceImpl( val handoverDetails = buildHandoverDetails( projectCode = projectCode, groupIds = toHandoverGroups.map { it.toString() }, - invalidPipelines = invalidPipelines, - invalidRepertoryIds = invalidRepertoryIds, - invalidEnvNodeIds = invalidEnvNodeIds + pipelineAuthorizations = invalidPipelines, + repertoryAuthorizations = invalidRepertoryIds, + envNodeAuthorizations = invalidEnvNodeIds ) val projectName = authResourceService.get( @@ -1767,8 +1769,7 @@ class RbacPermissionManageFacadeServiceImpl( 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) { + if (memberType == MemberType.USER.type && isNeedToHandover()) { removeMemberFromProjectReq.checkHandoverTo() val handoverMemberDTO = GroupMemberHandoverConditionReq( allSelection = true, @@ -1892,7 +1893,7 @@ class RbacPermissionManageFacadeServiceImpl( "content" to String.format( request.handoverAction.emailContent, request.flowNo, - overview.approver.plus("($handoverToCnName)"), + overview.approver.plus("($handoverToCnName)") ), "weworkContent" to String.format( request.handoverAction.weworkContent, @@ -2132,6 +2133,143 @@ class RbacPermissionManageFacadeServiceImpl( ) } + override fun checkMemberExitsProject( + projectCode: String, + userId: String + ): MemberExitsProjectCheckVo { + logger.info("check member exits project:$projectCode|$userId") + val userDeptInfos = getMemberDeptInfos( + memberId = userId + ) + val userDepartmentsInProject = authResourceGroupMemberDao.isMembersInProject( + dslContext = dslContext, + projectCode = projectCode, + memberNames = userDeptInfos, + memberType = MemberType.DEPARTMENT.type + ).map { it.name } + if (userDepartmentsInProject.isNotEmpty()) { + return MemberExitsProjectCheckVo( + departmentJoinedCount = userDepartmentsInProject.size, + departments = userDepartmentsInProject.joinToString(",") + ) + } + val resourceType2Authorizations = authAuthorizationDao.list( + dslContext = dslContext, + condition = ResourceAuthorizationConditionRequest( + projectCode = projectCode, + handoverFrom = userId + ) + ).groupBy { it.resourceType } + val groupIdsDirectlyJoined = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = GroupMemberCommonConditionReq( + allSelection = true, + targetMember = ResourceMemberInfo( + id = userId, + type = MemberType.USER.type + ) + ) + )[MemberType.USER] ?: emptyList() + val uniqueManagerGroups = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = groupIdsDirectlyJoined + ) + return MemberExitsProjectCheckVo( + uniqueManagerCount = uniqueManagerGroups.size, + pipelineAuthorizationCount = resourceType2Authorizations[ResourceTypeId.PIPELINE]?.size, + repositoryAuthorizationCount = resourceType2Authorizations[ResourceTypeId.REPERTORY]?.size, + envNodeAuthorizationCount = resourceType2Authorizations[ResourceTypeId.ENV_NODE]?.size + ) + } + + override fun memberExitsProject( + projectCode: String, + request: RemoveMemberFromProjectReq + ): String { + logger.info("member exits project :$projectCode|$request") + if (request.isNeedToHandover()) { + request.checkHandoverTo() + val handoverTo = request.handoverTo!! + // 需要交接的用户组 + val groupIds = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = GroupMemberCommonConditionReq( + targetMember = request.targetMember, + allSelection = true + ) + )[MemberType.get(MemberType.USER.type)]?.toMutableList() ?: emptyList() + // 需要交接的授权管理 + val resourceAuthorizations = authAuthorizationDao.list( + dslContext = dslContext, + condition = ResourceAuthorizationConditionRequest( + projectCode = projectCode, + handoverFrom = request.targetMember.id + ) + ) + val resourceType2Authorizations = resourceAuthorizations.groupBy({ it.resourceType }, { it.resourceCode }) + val repertoryAuthorizations = resourceType2Authorizations[ResourceTypeId.REPERTORY] ?: emptyList() + val pipelineAuthorizations = resourceType2Authorizations[ResourceTypeId.PIPELINE] ?: emptyList() + val envNodeRepertoryIds = resourceType2Authorizations[ResourceTypeId.ENV_NODE] ?: emptyList() + if (repertoryAuthorizations.isNotEmpty()) { + permissionAuthorizationService.checkRepertoryAuthorizationsHanover( + operator = request.targetMember.id, + projectCode = projectCode, + repertoryIds = repertoryAuthorizations, + handoverFrom = request.targetMember.id, + handoverTo = handoverTo.id + ) + } + val handoverDetails = buildHandoverDetails( + projectCode = projectCode, + groupIds = groupIds.map { it.toString() }, + pipelineAuthorizations = pipelineAuthorizations, + repertoryAuthorizations = repertoryAuthorizations, + envNodeAuthorizations = envNodeRepertoryIds + ) + val projectName = authResourceService.get( + projectCode = projectCode, + resourceType = ResourceTypeId.PROJECT, + resourceCode = projectCode + ).resourceName + // 创建交接单 + val flowNo = permissionHandoverApplicationService.createHandoverApplication( + overview = HandoverOverviewCreateDTO( + projectCode = projectCode, + projectName = projectName, + applicant = request.targetMember.id, + approver = handoverTo.id, + handoverStatus = HandoverStatus.PENDING, + groupCount = groupIds.size, + authorizationCount = resourceAuthorizations.size + ), + details = handoverDetails + ) + return flowNo + } else { + val result = checkMemberExitsProject( + projectCode = projectCode, + userId = request.targetMember.id + ) + if (!result.canExitsProjectDirectly()) { + throw OperationException( + message = "Direct exit from the project is not allowed!" + ) + } + val removeMemberDTO = GroupMemberRemoveConditionReq( + allSelection = true, + targetMember = request.targetMember + ) + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.REMOVE, + conditionReq = removeMemberDTO, + operateGroupMemberTask = ::deleteTask + ) + } + return "" + } + private fun listGroupsOfHandoverPreview(queryReq: HandoverDetailsQueryReq): SQLPage { val projectCode = queryReq.projectCode val previewConditionReq = queryReq.previewConditionReq!! 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 index f0a56157cf8..c77829e3334 100644 --- 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 @@ -22,6 +22,7 @@ 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.MemberExitsProjectCheckVo import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.common.api.model.SQLPage @@ -174,4 +175,18 @@ class SamplePermissionManageFacadeService : PermissionManageFacadeService { override fun isProjectMember(projectCode: String, userId: String): Boolean { return true } + + override fun checkMemberExitsProject( + projectCode: String, + userId: String + ): MemberExitsProjectCheckVo { + return MemberExitsProjectCheckVo() + } + + override fun memberExitsProject( + projectCode: String, + request: RemoveMemberFromProjectReq + ): String { + return "" + } } 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 index ea693fbedf6..7b20e7ced02 100644 --- 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 @@ -22,6 +22,7 @@ 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.MemberExitsProjectCheckVo import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo import com.tencent.devops.common.api.model.SQLPage @@ -252,4 +253,20 @@ interface PermissionManageFacadeService { projectCode: String, userId: String ): Boolean + + /** + * 用户主动退出项目检查 + * */ + fun checkMemberExitsProject( + projectCode: String, + userId: String + ): MemberExitsProjectCheckVo + + /** + * 用户主动退出项目 + * */ + fun memberExitsProject( + projectCode: String, + request: RemoveMemberFromProjectReq + ): String }