From 7476d5738861b4913b106f669dacacc4c56bc8c3 Mon Sep 17 00:00:00 2001 From: kunlongli <16629885+cnlkl@users.noreply.github.com> Date: Thu, 4 Jan 2024 10:20:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81NodeSearch=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E8=BF=87=E6=BB=A4=E6=97=A0=E6=9D=83=E9=99=90=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=20#1585=20(#1613)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 支持NodeSearch接口过滤无权限路径 #1585 * feat: 支持NodeList接口过滤无权限路径 #1585 --- src/backend/auth/api-auth/build.gradle.kts | 1 + .../auth/pojo/permission/ListPathResult.kt | 11 +-- .../service/ServicePermissionController.kt | 4 +- .../repository/pojo/node/NodeListOption.kt | 7 +- .../search/common/CommonQueryInterpreter.kt | 3 +- .../search/common/RepoNameRuleInterceptor.kt | 79 +++++++++++++++---- .../service/node/impl/NodeBaseService.kt | 28 +++++++ .../service/node/impl/NodeServiceImpl.kt | 3 + .../center/CommitEdgeCenterNodeServiceImpl.kt | 5 +- .../node/impl/edge/EdgeNodeBaseService.kt | 5 +- .../node/impl/edge/EdgeNodeServiceImpl.kt | 7 +- .../bkrepo/repository/util/NodeQueryHelper.kt | 13 ++- .../service/NodeSearchServiceTest.kt | 76 +++++++++++++++++- .../repository/service/NodeServiceTest.kt | 42 +++++++++- .../repository/service/ServiceBaseTest.kt | 5 +- 15 files changed, 252 insertions(+), 37 deletions(-) diff --git a/src/backend/auth/api-auth/build.gradle.kts b/src/backend/auth/api-auth/build.gradle.kts index f32a38f9c9..7e31618d77 100644 --- a/src/backend/auth/api-auth/build.gradle.kts +++ b/src/backend/auth/api-auth/build.gradle.kts @@ -30,6 +30,7 @@ */ dependencies { implementation(project(":common:common-api")) + implementation(project(":common:common-query:query-api")) api(project(":common:common-operate:operate-annotation")) compileOnly("org.springframework.cloud:spring-cloud-openfeign-core") api("com.tencent.devops:devops-boot-starter-plugin") diff --git a/src/backend/auth/api-auth/src/main/kotlin/com/tencent/bkrepo/auth/pojo/permission/ListPathResult.kt b/src/backend/auth/api-auth/src/main/kotlin/com/tencent/bkrepo/auth/pojo/permission/ListPathResult.kt index e0d8e6df38..168f8d07dd 100644 --- a/src/backend/auth/api-auth/src/main/kotlin/com/tencent/bkrepo/auth/pojo/permission/ListPathResult.kt +++ b/src/backend/auth/api-auth/src/main/kotlin/com/tencent/bkrepo/auth/pojo/permission/ListPathResult.kt @@ -32,16 +32,11 @@ package com.tencent.bkrepo.auth.pojo.permission import io.swagger.annotations.ApiModel +import com.tencent.bkrepo.common.query.enums.OperationType + @ApiModel("校验权限请求") data class ListPathResult( // when true, need to compare val status: Boolean, - val path: Map>, + val path: Map>, ) - -enum class ListPathOperationType { - /** - * 获取用户没有权限的路径 - */ - NIN -} diff --git a/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/controller/service/ServicePermissionController.kt b/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/controller/service/ServicePermissionController.kt index c6c2033ca3..635b2c7d4f 100644 --- a/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/controller/service/ServicePermissionController.kt +++ b/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/controller/service/ServicePermissionController.kt @@ -34,10 +34,10 @@ package com.tencent.bkrepo.auth.controller.service import com.tencent.bkrepo.auth.api.ServicePermissionClient import com.tencent.bkrepo.auth.controller.OpenResource import com.tencent.bkrepo.auth.pojo.permission.CheckPermissionRequest -import com.tencent.bkrepo.auth.pojo.permission.ListPathOperationType import com.tencent.bkrepo.auth.pojo.permission.ListPathResult import com.tencent.bkrepo.auth.service.PermissionService import com.tencent.bkrepo.common.api.pojo.Response +import com.tencent.bkrepo.common.query.enums.OperationType import com.tencent.bkrepo.common.service.util.ResponseBuilder import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.bind.annotation.RestController @@ -54,7 +54,7 @@ class ServicePermissionController @Autowired constructor( override fun listPermissionPath(userId: String, projectId: String, repoName: String): Response { val permissionPath = permissionService.listNoPermissionPath(userId, projectId, repoName) val status = permissionPath.isNotEmpty() - val result = ListPathResult(status = status, path = mapOf(ListPathOperationType.NIN to permissionPath)) + val result = ListPathResult(status = status, path = mapOf(OperationType.NIN to permissionPath)) return ResponseBuilder.success(result) } diff --git a/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/node/NodeListOption.kt b/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/node/NodeListOption.kt index 9feade3949..b9bbbf8f68 100644 --- a/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/node/NodeListOption.kt +++ b/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/node/NodeListOption.kt @@ -33,6 +33,7 @@ package com.tencent.bkrepo.repository.pojo.node import com.tencent.bkrepo.common.api.constant.DEFAULT_PAGE_NUMBER import com.tencent.bkrepo.common.api.constant.DEFAULT_PAGE_SIZE +import com.tencent.bkrepo.repository.constant.SYSTEM_USER import io.swagger.annotations.ApiModel import io.swagger.annotations.ApiModelProperty @@ -53,5 +54,9 @@ data class NodeListOption( @ApiModelProperty("排序字段") val sortProperty: List = emptyList(), @ApiModelProperty("排序方向") - val direction: List = emptyList() + val direction: List = emptyList(), + @ApiModelProperty("无权限路径") + var noPermissionPath: List = emptyList(), + @ApiModelProperty("操作用户") + var operator: String = SYSTEM_USER, ) diff --git a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/search/common/CommonQueryInterpreter.kt b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/search/common/CommonQueryInterpreter.kt index 43b14b6ee8..341b2c16ac 100644 --- a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/search/common/CommonQueryInterpreter.kt +++ b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/search/common/CommonQueryInterpreter.kt @@ -39,7 +39,8 @@ open class CommonQueryInterpreter( override fun interpret(queryModel: QueryModel): QueryContext { val context = super.interpret(queryModel) as CommonQueryContext if (!context.permissionChecked) { - permissionManager.checkProjectPermission(PermissionAction.READ, context.findProjectId()) + // 未执行权限校验时表示没有仓库查询条件,可能为跨仓库查询,需要项目管理员权限 + permissionManager.checkProjectPermission(PermissionAction.MANAGE, context.findProjectId()) context.permissionChecked = true } return context diff --git a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/search/common/RepoNameRuleInterceptor.kt b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/search/common/RepoNameRuleInterceptor.kt index 8bc6ba74c4..4a5c5d072a 100644 --- a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/search/common/RepoNameRuleInterceptor.kt +++ b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/search/common/RepoNameRuleInterceptor.kt @@ -31,7 +31,9 @@ package com.tencent.bkrepo.repository.search.common +import com.tencent.bkrepo.auth.api.ServicePermissionClient import com.tencent.bkrepo.auth.pojo.enums.PermissionAction +import com.tencent.bkrepo.common.api.constant.ensureSuffix import com.tencent.bkrepo.common.artifact.exception.RepoNotFoundException import com.tencent.bkrepo.common.query.enums.OperationType import com.tencent.bkrepo.common.query.interceptor.QueryContext @@ -43,6 +45,7 @@ import com.tencent.bkrepo.common.security.util.SecurityUtils import com.tencent.bkrepo.repository.pojo.node.NodeInfo import com.tencent.bkrepo.repository.pojo.repo.RepoListOption import com.tencent.bkrepo.repository.service.repo.RepositoryService +import org.slf4j.LoggerFactory import org.springframework.data.mongodb.core.query.Criteria import org.springframework.stereotype.Component @@ -54,7 +57,8 @@ import org.springframework.stereotype.Component @Component class RepoNameRuleInterceptor( private val permissionManager: PermissionManager, - private val repositoryService: RepositoryService + private val repositoryService: RepositoryService, + private val servicePermissionClient: ServicePermissionClient, ) : QueryRuleInterceptor { override fun match(rule: Rule): Boolean { @@ -77,10 +81,10 @@ class RepoNameRuleInterceptor( OperationType.NIN -> { val listValue = value require(listValue is List<*>) - handleRepoNameNin(projectId, listValue, context) + handleRepoNameNin(projectId, listValue) } else -> throw IllegalArgumentException("RepoName only support EQ IN and NIN operation type.") - }.toFixed() + } context.permissionChecked = true return context.interpreter.resolveRule(queryRule, context) } @@ -89,53 +93,90 @@ class RepoNameRuleInterceptor( private fun handleRepoNameEq( projectId: String, value: String - ): Rule.QueryRule { + ): Rule { if (!hasRepoPermission(projectId, value)) { throw PermissionException() } - return Rule.QueryRule(NodeInfo::repoName.name, value, OperationType.EQ) + return buildRule(projectId, listOf(value)) } private fun handleRepoNameIn( projectId: String, value: List<*>, context: CommonQueryContext - ): Rule.QueryRule { + ): Rule { val repoNameList = if (context.repoList != null) { context.repoList!!.filter { hasRepoPermission(projectId, it.name, it.public) }.map { it.name } } else { value.filter { hasRepoPermission(projectId, it.toString()) }.map { it.toString() } } - return if (repoNameList.size == 1) { - Rule.QueryRule(NodeInfo::repoName.name, repoNameList.first(), OperationType.EQ) - } else { - Rule.QueryRule(NodeInfo::repoName.name, repoNameList, OperationType.IN) - } + return buildRule(projectId, repoNameList) } private fun handleRepoNameNin( projectId: String, value: List<*>, - context: CommonQueryContext - ): Rule.QueryRule { + ): Rule { val userId = SecurityUtils.getUserId() val repoNameList = repositoryService.listPermissionRepo( userId = userId, projectId = projectId, option = RepoListOption() )?.map { it.name }?.filter { repo -> repo !in (value.map { it.toString() }) } - return if (repoNameList.isNullOrEmpty()) { + return buildRule(projectId, repoNameList) + } + + private fun buildRule(projectId: String, repoNames: List): Rule { + if (repoNames.isEmpty()) { throw PermissionException( "${SecurityUtils.getUserId()} hasn't any PermissionRepo in project [$projectId], " + "or project [$projectId] hasn't any repo" ) - } else if (repoNameList.size == 1) { - Rule.QueryRule(NodeInfo::repoName.name, repoNameList.first(), OperationType.EQ) + } + if (repoNames.size == 1) { + // 单仓库查询 + return buildRule(projectId, repoNames.first()) + } + + // 跨仓库查询 + val rules = repoNames.mapTo(ArrayList(repoNames.size)) { repoName -> buildRule(projectId, repoName) } + return if (rules.all { it !is Rule.NestedRule }) { + // 不需要校验路径权限 + Rule.QueryRule(NodeInfo::repoName.name, repoNames, OperationType.IN).toFixed() } else { - Rule.QueryRule(NodeInfo::repoName.name, repoNameList, OperationType.IN) + // 存在某个仓库需要进行路径权限校验 + Rule.NestedRule(rules, Rule.NestedRule.RelationType.OR) } } + private fun buildRule(projectId: String, repoName: String): Rule { + val repoRule = Rule.QueryRule(NodeInfo::repoName.name, repoName, OperationType.EQ).toFixed() + return listNoPermissionPath(projectId, repoName)?.let { + val pathRules = it.flatMapTo(ArrayList(it.size)) { path -> + listOf( + Rule.QueryRule(NodeInfo::fullPath.name, path.ensureSuffix("/"), OperationType.PREFIX) as Rule, + Rule.QueryRule(NodeInfo::fullPath.name, path, OperationType.EQ) as Rule, + ) + } + val pathRule = Rule.NestedRule(pathRules, Rule.NestedRule.RelationType.NOR) + Rule.NestedRule(mutableListOf(repoRule, pathRule)) + } ?: repoRule + } + + private fun listNoPermissionPath(projectId: String, repoName: String): List? { + val userId = SecurityUtils.getUserId() + val result = servicePermissionClient.listPermissionPath(userId, projectId, repoName).data!! + if (result.status) { + val paths = result.path.flatMap { + require(it.key == OperationType.NIN) + it.value + } + logger.info("user[$userId] does not have permission to $paths of [$projectId/$repoName], will be filtered") + return paths.ifEmpty { null } + } + return null + } + private fun hasRepoPermission( projectId: String, repoName: String, @@ -159,4 +200,8 @@ class RepoNameRuleInterceptor( false } } + + companion object { + private val logger = LoggerFactory.getLogger(RepoNameRuleInterceptor::class.java) + } } diff --git a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/NodeBaseService.kt b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/NodeBaseService.kt index 7ac3ab4797..521199d02b 100644 --- a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/NodeBaseService.kt +++ b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/NodeBaseService.kt @@ -27,6 +27,7 @@ package com.tencent.bkrepo.repository.service.node.impl +import com.tencent.bkrepo.auth.api.ServicePermissionClient import com.tencent.bkrepo.auth.pojo.enums.PermissionAction import com.tencent.bkrepo.common.api.exception.BadRequestException import com.tencent.bkrepo.common.api.exception.ErrorCodeException @@ -41,8 +42,10 @@ import com.tencent.bkrepo.common.artifact.path.PathUtils import com.tencent.bkrepo.common.artifact.pojo.RepositoryType import com.tencent.bkrepo.common.mongo.dao.AbstractMongoDao.Companion.ID import com.tencent.bkrepo.common.mongo.dao.util.Pages +import com.tencent.bkrepo.common.query.enums.OperationType import com.tencent.bkrepo.common.query.model.Sort import com.tencent.bkrepo.common.security.manager.PermissionManager +import com.tencent.bkrepo.common.security.util.SecurityUtils import com.tencent.bkrepo.common.service.util.SpringContextUtils.Companion.publishEvent import com.tencent.bkrepo.common.storage.core.StorageService import com.tencent.bkrepo.common.stream.constant.BinderType @@ -50,6 +53,7 @@ import com.tencent.bkrepo.common.stream.event.supplier.MessageSupplier import com.tencent.bkrepo.fs.server.constant.FAKE_MD5 import com.tencent.bkrepo.fs.server.constant.FAKE_SHA256 import com.tencent.bkrepo.repository.config.RepositoryProperties +import com.tencent.bkrepo.repository.constant.SYSTEM_USER import com.tencent.bkrepo.repository.dao.NodeDao import com.tencent.bkrepo.repository.dao.RepositoryDao import com.tencent.bkrepo.repository.model.TNode @@ -96,6 +100,7 @@ abstract class NodeBaseService( open val quotaService: QuotaService, open val repositoryProperties: RepositoryProperties, open val messageSupplier: MessageSupplier, + open val servicePermissionClient: ServicePermissionClient, ) : NodeService { @Autowired @@ -112,6 +117,7 @@ abstract class NodeBaseService( override fun listNode(artifact: ArtifactInfo, option: NodeListOption): List { checkNodeListOption(option) with(artifact) { + getNoPermissionPaths(SecurityUtils.getUserId(), projectId, repoName)?.let { option.noPermissionPath = it } val query = NodeQueryHelper.nodeListQuery(projectId, repoName, getArtifactFullPath(), option) if (nodeDao.count(query) > repositoryProperties.listCountLimit) { throw ErrorCodeException(ArtifactMessageCode.NODE_LIST_TOO_LARGE) @@ -123,6 +129,7 @@ abstract class NodeBaseService( override fun listNodePage(artifact: ArtifactInfo, option: NodeListOption): Page { checkNodeListOption(option) with(artifact) { + getNoPermissionPaths(SecurityUtils.getUserId(), projectId, repoName)?.let { option.noPermissionPath = it } val pageNumber = option.pageNumber val pageSize = option.pageSize Preconditions.checkArgument(pageNumber >= 0, "pageNumber") @@ -469,6 +476,27 @@ abstract class NodeBaseService( ) } + /** + * 获取用户无权限路径列表 + */ + private fun getNoPermissionPaths(userId: String, projectId: String, repoName: String): List? { + if (userId == SYSTEM_USER) { + return null + } + val result = servicePermissionClient.listPermissionPath(userId, projectId, repoName).data!! + if (result.status) { + val paths = result.path.flatMap { + require(it.key == OperationType.NIN) + it.value + }.ifEmpty { null } + logger.info( + "user[$userId] does not have permission of paths[$paths] in [$projectId/$repoName], will be filterd" + ) + return paths + } + return null + } + companion object { private val logger = LoggerFactory.getLogger(NodeBaseService::class.java) private const val TOPIC = "bkbase_bkrepo_artifact_node_created" diff --git a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/NodeServiceImpl.kt b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/NodeServiceImpl.kt index 5ba6b53654..dea0a23c72 100644 --- a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/NodeServiceImpl.kt +++ b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/NodeServiceImpl.kt @@ -27,6 +27,7 @@ package com.tencent.bkrepo.repository.service.node.impl +import com.tencent.bkrepo.auth.api.ServicePermissionClient import com.tencent.bkrepo.common.artifact.api.ArtifactInfo import com.tencent.bkrepo.common.service.cluster.DefaultCondition import com.tencent.bkrepo.common.storage.core.StorageService @@ -67,6 +68,7 @@ class NodeServiceImpl( override val quotaService: QuotaService, override val repositoryProperties: RepositoryProperties, override val messageSupplier: MessageSupplier, + override val servicePermissionClient: ServicePermissionClient, ) : NodeBaseService( nodeDao, repositoryDao, @@ -76,6 +78,7 @@ class NodeServiceImpl( quotaService, repositoryProperties, messageSupplier, + servicePermissionClient, ) { override fun computeSize(artifact: ArtifactInfo, estimated: Boolean): NodeSizeInfo { diff --git a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/center/CommitEdgeCenterNodeServiceImpl.kt b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/center/CommitEdgeCenterNodeServiceImpl.kt index 976b5a5d41..1186273a36 100644 --- a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/center/CommitEdgeCenterNodeServiceImpl.kt +++ b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/center/CommitEdgeCenterNodeServiceImpl.kt @@ -27,6 +27,7 @@ package com.tencent.bkrepo.repository.service.node.impl.center +import com.tencent.bkrepo.auth.api.ServicePermissionClient import com.tencent.bkrepo.common.api.exception.ErrorCodeException import com.tencent.bkrepo.common.api.message.CommonMessageCode import com.tencent.bkrepo.common.artifact.message.ArtifactMessageCode @@ -76,6 +77,7 @@ class CommitEdgeCenterNodeServiceImpl( override val quotaService: QuotaService, override val repositoryProperties: RepositoryProperties, override val messageSupplier: MessageSupplier, + override val servicePermissionClient: ServicePermissionClient, val clusterProperties: ClusterProperties ) : NodeServiceImpl( nodeDao, @@ -85,7 +87,8 @@ class CommitEdgeCenterNodeServiceImpl( storageService, quotaService, repositoryProperties, - messageSupplier + messageSupplier, + servicePermissionClient, ) { override fun checkRepo(projectId: String, repoName: String): TRepository { diff --git a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/edge/EdgeNodeBaseService.kt b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/edge/EdgeNodeBaseService.kt index 09e95b83c9..47bcbc8645 100644 --- a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/edge/EdgeNodeBaseService.kt +++ b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/edge/EdgeNodeBaseService.kt @@ -27,6 +27,7 @@ package com.tencent.bkrepo.repository.service.node.impl.edge +import com.tencent.bkrepo.auth.api.ServicePermissionClient import com.tencent.bkrepo.common.service.cluster.ClusterProperties import com.tencent.bkrepo.common.service.feign.FeignClientFactory import com.tencent.bkrepo.common.storage.core.StorageService @@ -54,6 +55,7 @@ abstract class EdgeNodeBaseService( override val quotaService: QuotaService, override val repositoryProperties: RepositoryProperties, override val messageSupplier: MessageSupplier, + override val servicePermissionClient: ServicePermissionClient, open val clusterProperties: ClusterProperties ) : NodeBaseService( nodeDao, @@ -63,7 +65,8 @@ abstract class EdgeNodeBaseService( storageService, quotaService, repositoryProperties, - messageSupplier + messageSupplier, + servicePermissionClient, ) { val centerNodeClient: ClusterNodeClient by lazy { diff --git a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/edge/EdgeNodeServiceImpl.kt b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/edge/EdgeNodeServiceImpl.kt index c615b0eec5..ea3e6a30b1 100644 --- a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/edge/EdgeNodeServiceImpl.kt +++ b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/edge/EdgeNodeServiceImpl.kt @@ -27,6 +27,7 @@ package com.tencent.bkrepo.repository.service.node.impl.edge +import com.tencent.bkrepo.auth.api.ServicePermissionClient import com.tencent.bkrepo.common.artifact.api.ArtifactInfo import com.tencent.bkrepo.common.service.cluster.ClusterProperties import com.tencent.bkrepo.common.service.cluster.CommitEdgeEdgeCondition @@ -76,7 +77,8 @@ class EdgeNodeServiceImpl( override val quotaService: QuotaService, override val repositoryProperties: RepositoryProperties, override val messageSupplier: MessageSupplier, - override val clusterProperties: ClusterProperties + override val servicePermissionClient: ServicePermissionClient, + override val clusterProperties: ClusterProperties, ) : EdgeNodeBaseService( nodeDao, repositoryDao, @@ -86,7 +88,8 @@ class EdgeNodeServiceImpl( quotaService, repositoryProperties, messageSupplier, - clusterProperties + servicePermissionClient, + clusterProperties, ) { override fun computeSize(artifact: ArtifactInfo, estimated: Boolean): NodeSizeInfo { return NodeStatsSupport(this).computeSize(artifact, estimated) diff --git a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/util/NodeQueryHelper.kt b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/util/NodeQueryHelper.kt index d8d2718bb3..3cb664f7ae 100644 --- a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/util/NodeQueryHelper.kt +++ b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/util/NodeQueryHelper.kt @@ -39,6 +39,7 @@ import org.springframework.data.mongodb.core.query.Update import org.springframework.data.mongodb.core.query.and import org.springframework.data.mongodb.core.query.inValues import org.springframework.data.mongodb.core.query.isEqualTo +import org.springframework.data.mongodb.core.query.regex import org.springframework.data.mongodb.core.query.where import java.time.LocalDateTime @@ -85,7 +86,17 @@ object NodeQueryHelper { if (!option.includeFolder) { criteria.and(TNode::folder).isEqualTo(false) } - return criteria + return if (option.noPermissionPath.isNotEmpty()) { + val noPermissionPathCriteria = option.noPermissionPath.flatMap { + listOf( + TNode::fullPath.isEqualTo(it), + TNode::fullPath.regex("^${escapeRegex(it)}") + ) + } + Criteria().andOperator(criteria, Criteria().norOperator(noPermissionPathCriteria)) + } else { + criteria + } } fun nodeListQuery( diff --git a/src/backend/repository/biz-repository/src/test/kotlin/com/tencent/bkrepo/repository/service/NodeSearchServiceTest.kt b/src/backend/repository/biz-repository/src/test/kotlin/com/tencent/bkrepo/repository/service/NodeSearchServiceTest.kt index 106b27f83e..aa9840bf9d 100644 --- a/src/backend/repository/biz-repository/src/test/kotlin/com/tencent/bkrepo/repository/service/NodeSearchServiceTest.kt +++ b/src/backend/repository/biz-repository/src/test/kotlin/com/tencent/bkrepo/repository/service/NodeSearchServiceTest.kt @@ -31,9 +31,15 @@ package com.tencent.bkrepo.repository.service +import com.tencent.bkrepo.auth.pojo.permission.ListPathResult import com.tencent.bkrepo.common.artifact.path.PathUtils.ROOT +import com.tencent.bkrepo.common.artifact.pojo.RepositoryCategory +import com.tencent.bkrepo.common.artifact.pojo.RepositoryType import com.tencent.bkrepo.common.query.enums.OperationType +import com.tencent.bkrepo.common.query.model.QueryModel +import com.tencent.bkrepo.common.query.model.Rule import com.tencent.bkrepo.common.query.model.Sort +import com.tencent.bkrepo.common.service.util.ResponseBuilder import com.tencent.bkrepo.repository.UT_PROJECT_ID import com.tencent.bkrepo.repository.UT_REPO_NAME import com.tencent.bkrepo.repository.UT_USER @@ -42,6 +48,7 @@ import com.tencent.bkrepo.repository.dao.NodeDao import com.tencent.bkrepo.repository.pojo.metadata.MetadataModel import com.tencent.bkrepo.repository.pojo.node.NodeInfo import com.tencent.bkrepo.repository.pojo.node.service.NodeCreateRequest +import com.tencent.bkrepo.repository.pojo.repo.RepoCreateRequest import com.tencent.bkrepo.repository.pojo.search.NodeQueryBuilder import com.tencent.bkrepo.repository.search.common.LocalDatetimeRuleInterceptor import com.tencent.bkrepo.repository.search.common.RepoNameRuleInterceptor @@ -57,6 +64,9 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance +import org.mockito.ArgumentMatchers.anyString +import org.mockito.kotlin.isNull +import org.mockito.kotlin.whenever import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest import org.springframework.context.annotation.Import @@ -84,12 +94,12 @@ class NodeSearchServiceTest @Autowired constructor( @BeforeAll fun beforeAll() { - initMock() initRepoForUnitTest(projectService, repositoryService) } @BeforeEach fun beforeEach() { + initMock() nodeService.deleteByPath(UT_PROJECT_ID, UT_REPO_NAME, ROOT, UT_USER) } @@ -236,6 +246,70 @@ class NodeSearchServiceTest @Autowired constructor( Assertions.assertEquals(0, result.records.size) } + @Test + fun testNoPermissionPathSearch() { + val utRepoName2 = "$UT_REPO_NAME-2" + val utRepoName3 = "$UT_REPO_NAME-3" + whenever(servicePermissionClient.listPermissionPath(anyString(), anyString(), anyString())).thenReturn( + ResponseBuilder.success(ListPathResult(status = true, path = mapOf(OperationType.NIN to listOf("/a")))) + ) + whenever(servicePermissionClient.listPermissionRepo(anyString(), anyString(), isNull())).thenReturn( + ResponseBuilder.success(listOf(UT_REPO_NAME, utRepoName2, utRepoName3)) + ) + // 创建仓库 + val repoCreateRequest = RepoCreateRequest( + projectId = UT_PROJECT_ID, + name = utRepoName2, + type = RepositoryType.GENERIC, + category = RepositoryCategory.LOCAL, + public = false, + operator = UT_USER, + ) + repositoryService.createRepo(repoCreateRequest) + repositoryService.createRepo(repoCreateRequest.copy(name = utRepoName3)) + + // 创建node + val createNodeRequest = createRequest("/a/a1.txt", false) + nodeService.createNode(createNodeRequest) + nodeService.createNode(createNodeRequest.copy(fullPath = "/b/b1.txt")) + nodeService.createNode(createNodeRequest.copy(fullPath = "/c/c1.txt")) + + nodeService.createNode(createNodeRequest.copy(repoName = utRepoName2, fullPath = "/a/a2.txt")) + nodeService.createNode(createNodeRequest.copy(repoName = utRepoName2, fullPath = "/b/b2.txt")) + nodeService.createNode(createNodeRequest.copy(repoName = utRepoName2, fullPath = "/c/c2.txt")) + + nodeService.createNode(createNodeRequest.copy(repoName = utRepoName3, fullPath = "/a/a3.txt")) + nodeService.createNode(createNodeRequest.copy(repoName = utRepoName3, fullPath = "/b/b3.txt")) + nodeService.createNode(createNodeRequest.copy(repoName = utRepoName3, fullPath = "/c/c3.txt")) + + // 无仓库查询测试 + var result = nodeSearchService.search(NodeQueryBuilder().projectId(UT_PROJECT_ID).build()) + Assertions.assertEquals(18, result.totalRecords) + + // 单仓库查询 + var queryModel = createQueryBuilder().build() + result = nodeSearchService.search(queryModel) + Assertions.assertEquals(4, result.totalRecords) + + // 多仓库查询IN + queryModel = NodeQueryBuilder() + .projectId(UT_PROJECT_ID) + .repoNames(utRepoName2, utRepoName3) + .build() + result = nodeSearchService.search(queryModel) + Assertions.assertEquals(8, result.totalRecords) + + + // 多仓库查询NIN + val rules = mutableListOf( + Rule.QueryRule(NodeInfo::projectId.name, UT_PROJECT_ID) as Rule, + Rule.QueryRule(NodeInfo::repoName.name, listOf(utRepoName2, utRepoName3), OperationType.NIN) as Rule, + ) + queryModel = QueryModel(sort = null, select = null, rule = Rule.NestedRule(rules)) + result = nodeSearchService.search(queryModel) + Assertions.assertEquals(4, result.totalRecords) + } + private fun testLocalDateTimeOperation( createdDate: String, operationType: OperationType, diff --git a/src/backend/repository/biz-repository/src/test/kotlin/com/tencent/bkrepo/repository/service/NodeServiceTest.kt b/src/backend/repository/biz-repository/src/test/kotlin/com/tencent/bkrepo/repository/service/NodeServiceTest.kt index 98f818d2f0..c8bdd4aa64 100644 --- a/src/backend/repository/biz-repository/src/test/kotlin/com/tencent/bkrepo/repository/service/NodeServiceTest.kt +++ b/src/backend/repository/biz-repository/src/test/kotlin/com/tencent/bkrepo/repository/service/NodeServiceTest.kt @@ -31,10 +31,13 @@ package com.tencent.bkrepo.repository.service +import com.tencent.bkrepo.auth.pojo.permission.ListPathResult import com.tencent.bkrepo.common.api.exception.ErrorCodeException import com.tencent.bkrepo.common.artifact.api.ArtifactInfo import com.tencent.bkrepo.common.artifact.api.DefaultArtifactInfo import com.tencent.bkrepo.common.artifact.path.PathUtils.ROOT +import com.tencent.bkrepo.common.query.enums.OperationType +import com.tencent.bkrepo.common.service.util.ResponseBuilder import com.tencent.bkrepo.repository.UT_PROJECT_ID import com.tencent.bkrepo.repository.UT_REPO_NAME import com.tencent.bkrepo.repository.UT_USER @@ -61,6 +64,8 @@ import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.assertThrows +import org.mockito.ArgumentMatchers.anyString +import org.mockito.kotlin.whenever import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest import org.springframework.context.annotation.Import @@ -86,12 +91,12 @@ class NodeServiceTest @Autowired constructor( @BeforeAll fun beforeAll() { - initMock() initRepoForUnitTest(projectService, repositoryService) } @BeforeEach fun beforeEach() { + initMock() repositoryProperties.nodeCreateTimeout = 5000 nodeService.deleteByPath(UT_PROJECT_ID, UT_REPO_NAME, ROOT, UT_USER) } @@ -257,6 +262,41 @@ class NodeServiceTest @Autowired constructor( assertEquals(7, page.pageNumber) } + @Test + @DisplayName("测试查询无路径权限的目录") + fun testListNoPathPermissionNode() { + val size = 5L + repeat(size.toInt()) { i -> nodeService.createNode(createRequest("/a/$i/$i.txt", false)) } + whenever(servicePermissionClient.listPermissionPath(anyString(), anyString(), anyString())).thenReturn( + ResponseBuilder.success( + ListPathResult( + status = true, + path = mapOf(OperationType.NIN to listOf("/a/1", "/a/2")) + ) + ) + ) + + val option = NodeListOption( + pageNumber = 0, + pageSize = 10, + includeFolder = true, + includeMetadata = false, + deep = false, + sort = false + ) + val page = nodeService.listNodePage(node("/a"), option) + assertEquals(3, page.totalRecords) + assertEquals("/a/0", page.records[0].fullPath) + assertEquals("/a/3", page.records[1].fullPath) + assertEquals("/a/4", page.records[2].fullPath) + + val result = nodeService.listNode(node("/a"), option) + assertEquals(3, result.size) + assertEquals("/a/0", result[0].fullPath) + assertEquals("/a/3", result[1].fullPath) + assertEquals("/a/4", result[2].fullPath) + } + @Test @DisplayName("测试删除文件") fun testDeleteFile() { diff --git a/src/backend/repository/biz-repository/src/test/kotlin/com/tencent/bkrepo/repository/service/ServiceBaseTest.kt b/src/backend/repository/biz-repository/src/test/kotlin/com/tencent/bkrepo/repository/service/ServiceBaseTest.kt index ead867e35b..609cc6b1b9 100644 --- a/src/backend/repository/biz-repository/src/test/kotlin/com/tencent/bkrepo/repository/service/ServiceBaseTest.kt +++ b/src/backend/repository/biz-repository/src/test/kotlin/com/tencent/bkrepo/repository/service/ServiceBaseTest.kt @@ -35,6 +35,7 @@ import com.tencent.bkrepo.auth.api.ServiceBkiamV3ResourceClient import com.tencent.bkrepo.auth.api.ServicePermissionClient import com.tencent.bkrepo.auth.api.ServiceRoleClient import com.tencent.bkrepo.auth.api.ServiceUserClient +import com.tencent.bkrepo.auth.pojo.permission.ListPathResult import com.tencent.bkrepo.common.artifact.event.base.ArtifactEvent import com.tencent.bkrepo.common.artifact.event.project.ProjectCreatedEvent import com.tencent.bkrepo.common.artifact.pojo.RepositoryCategory @@ -151,7 +152,9 @@ open class ServiceBaseTest { whenever(servicePermissionClient.listPermissionRepo(anyString(), anyString(), anyString())).thenReturn( ResponseBuilder.success() ) - + whenever(servicePermissionClient.listPermissionPath(anyString(), anyString(), anyString())).thenReturn( + ResponseBuilder.success(ListPathResult(status = false, path = emptyMap())) + ) whenever(messageSupplier.delegateToSupplier(any(), anyOrNull(), anyString(), anyOrNull(), any())) .then {} whenever(resourcePermissionListener.handle(any())).then {}