Skip to content

Commit

Permalink
feat: 支持NodeSearch接口过滤无权限路径 TencentBlueKing#1585 (TencentBlueKing#1613)
Browse files Browse the repository at this point in the history
* feat: 支持NodeSearch接口过滤无权限路径 TencentBlueKing#1585

* feat: 支持NodeList接口过滤无权限路径 TencentBlueKing#1585
  • Loading branch information
cnlkl authored Jan 4, 2024
1 parent 6c1c3c2 commit 7476d57
Show file tree
Hide file tree
Showing 15 changed files with 252 additions and 37 deletions.
1 change: 1 addition & 0 deletions src/backend/auth/api-auth/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ListPathOperationType, List<String>>,
val path: Map<OperationType, List<String>>,
)

enum class ListPathOperationType {
/**
* 获取用户没有权限的路径
*/
NIN
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -54,7 +54,7 @@ class ServicePermissionController @Autowired constructor(
override fun listPermissionPath(userId: String, projectId: String, repoName: String): Response<ListPathResult> {
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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -53,5 +54,9 @@ data class NodeListOption(
@ApiModelProperty("排序字段")
val sortProperty: List<String> = emptyList(),
@ApiModelProperty("排序方向")
val direction: List<String> = emptyList()
val direction: List<String> = emptyList(),
@ApiModelProperty("无权限路径")
var noPermissionPath: List<String> = emptyList(),
@ApiModelProperty("操作用户")
var operator: String = SYSTEM_USER,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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 {
Expand All @@ -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)
}
Expand All @@ -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<String>): 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<String>? {
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,
Expand All @@ -159,4 +200,8 @@ class RepoNameRuleInterceptor(
false
}
}

companion object {
private val logger = LoggerFactory.getLogger(RepoNameRuleInterceptor::class.java)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -41,15 +42,18 @@ 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
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
Expand Down Expand Up @@ -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
Expand All @@ -112,6 +117,7 @@ abstract class NodeBaseService(
override fun listNode(artifact: ArtifactInfo, option: NodeListOption): List<NodeInfo> {
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)
Expand All @@ -123,6 +129,7 @@ abstract class NodeBaseService(
override fun listNodePage(artifact: ArtifactInfo, option: NodeListOption): Page<NodeInfo> {
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")
Expand Down Expand Up @@ -469,6 +476,27 @@ abstract class NodeBaseService(
)
}

/**
* 获取用户无权限路径列表
*/
private fun getNoPermissionPaths(userId: String, projectId: String, repoName: String): List<String>? {
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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -76,6 +78,7 @@ class NodeServiceImpl(
quotaService,
repositoryProperties,
messageSupplier,
servicePermissionClient,
) {

override fun computeSize(artifact: ArtifactInfo, estimated: Boolean): NodeSizeInfo {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -85,7 +87,8 @@ class CommitEdgeCenterNodeServiceImpl(
storageService,
quotaService,
repositoryProperties,
messageSupplier
messageSupplier,
servicePermissionClient,
) {

override fun checkRepo(projectId: String, repoName: String): TRepository {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -63,7 +65,8 @@ abstract class EdgeNodeBaseService(
storageService,
quotaService,
repositoryProperties,
messageSupplier
messageSupplier,
servicePermissionClient,
) {

val centerNodeClient: ClusterNodeClient by lazy {
Expand Down
Loading

0 comments on commit 7476d57

Please sign in to comment.