Skip to content

Commit

Permalink
feat: 支持云桌面分享人上传文件 #2873
Browse files Browse the repository at this point in the history
  • Loading branch information
yaoxuwan authored Jan 2, 2025
1 parent d1a2388 commit 434988b
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,9 @@ data class DevXProperties(
*/
var authToken: String = "",

/**
* 搜索云桌面接口url
*/
var workspaceSearchUrl: String = "",

)
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@ data class DevXWorkSpace(
val innerIp: String? = null,
@JsonProperty("real_owner")
val realOwner: String,
@JsonProperty("viewers")
val viewers: List<String>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@

package com.tencent.bkrepo.websocket.controller

import com.tencent.bkrepo.common.api.constant.ANONYMOUS_USER
import com.tencent.bkrepo.common.api.constant.USER_KEY
import com.tencent.bkrepo.websocket.pojo.fs.CopyPDU
import com.tencent.bkrepo.websocket.pojo.fs.PastePDU
import com.tencent.bkrepo.websocket.service.ClipboardService
import org.springframework.messaging.handler.annotation.MessageMapping
import org.springframework.messaging.simp.SimpMessageHeaderAccessor
import org.springframework.stereotype.Controller

@Controller
Expand All @@ -40,8 +43,9 @@ class ClipboardController(
) {

@MessageMapping("/copy")
fun copy(copyPDU: CopyPDU) {
clipboardService.copy(copyPDU)
fun copy(copyPDU: CopyPDU, accessor: SimpMessageHeaderAccessor) {
val userId = accessor.sessionAttributes?.get(USER_KEY)?.toString() ?: ANONYMOUS_USER
clipboardService.copy(userId, copyPDU)
}

@MessageMapping("/paste")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,25 @@
package com.tencent.bkrepo.websocket.exception

import com.tencent.bkrepo.common.api.exception.ErrorCodeException
import com.tencent.bkrepo.common.api.pojo.Response
import com.tencent.bkrepo.common.service.exception.AbstractExceptionHandler
import com.tencent.bkrepo.common.api.message.CommonMessageCode
import com.tencent.bkrepo.common.service.log.LoggerHolder
import com.tencent.bkrepo.common.service.util.LocaleMessageUtils
import org.springframework.messaging.handler.annotation.MessageExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice

@RestControllerAdvice
class WebsocketExceptionHandler : AbstractExceptionHandler() {
class WebsocketExceptionHandler {

@MessageExceptionHandler(ErrorCodeException::class)
fun handleException(exception: ErrorCodeException): Response<Void> {
return response(exception)
fun handleException(exception: ErrorCodeException) {
val errorMessage = LocaleMessageUtils.getLocalizedMessage(exception.messageCode, exception.params)
LoggerHolder.logErrorCodeException(exception, "[${exception.messageCode.getCode()}]$errorMessage")
}

@MessageExceptionHandler(Exception::class)
fun handleException(exception: Exception): Response<Void> {
return response(exception)
fun handleException(exception: Exception) {
val errorMessage = LocaleMessageUtils.getLocalizedMessage(CommonMessageCode.SYSTEM_ERROR)
val code = CommonMessageCode.SYSTEM_ERROR.getCode()
LoggerHolder.logException(exception, "[$code]$errorMessage", true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class SessionHandler(
val (accessKey, secretKey) = String(Base64.getDecoder().decode(platformToken)).split(COLON)
val appId = authenticationManager.checkPlatformAccount(accessKey, secretKey)
session.attributes[PLATFORM_KEY] = appId
session.attributes[USER_KEY] = session.handshakeHeaders[AUTH_HEADER_UID]
session.attributes[USER_KEY] = session.handshakeHeaders[AUTH_HEADER_UID]?.first()
}
uri.path.startsWith(APP_ENDPOINT) -> {
val token = session.handshakeHeaders[HttpHeaders.AUTHORIZATION]?.firstOrNull().orEmpty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@ data class CopyPDU(
val files: Map<String, Long>,
val timestamp: Long,
val dstPath: String? = null,
val strategy: ConflictStrategy = ConflictStrategy.OVERWRITE
val strategy: ConflictStrategy = ConflictStrategy.OVERWRITE,
var token: String? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,47 @@

package com.tencent.bkrepo.websocket.service

import com.tencent.bkrepo.auth.pojo.enums.PermissionAction
import com.tencent.bkrepo.common.api.constant.HttpHeaders
import com.tencent.bkrepo.common.api.constant.MediaTypes
import com.tencent.bkrepo.common.api.util.readJsonString
import com.tencent.bkrepo.common.api.util.toJsonString
import com.tencent.bkrepo.common.metadata.permission.PermissionManager
import com.tencent.bkrepo.common.security.exception.PermissionException
import com.tencent.bkrepo.common.security.http.jwt.JwtAuthProperties
import com.tencent.bkrepo.common.security.interceptor.devx.ApiAuth
import com.tencent.bkrepo.common.security.interceptor.devx.DevXProperties
import com.tencent.bkrepo.common.security.interceptor.devx.DevXWorkSpace
import com.tencent.bkrepo.common.security.util.JwtUtils
import com.tencent.bkrepo.common.service.util.okhttp.HttpClientBuilderFactory
import com.tencent.bkrepo.fs.server.constant.JWT_CLAIMS_PERMIT
import com.tencent.bkrepo.fs.server.constant.JWT_CLAIMS_REPOSITORY
import com.tencent.bkrepo.websocket.dispatch.TransferDispatch
import com.tencent.bkrepo.websocket.dispatch.push.CopyPDUTransferPush
import com.tencent.bkrepo.websocket.dispatch.push.PastePDUTransferPush
import com.tencent.bkrepo.websocket.pojo.fs.CopyPDU
import com.tencent.bkrepo.websocket.pojo.fs.PastePDU
import com.tencent.devops.api.pojo.Response
import okhttp3.Request
import okio.IOException
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service

@Service
class ClipboardService(
private val transferDispatch: TransferDispatch
private val transferDispatch: TransferDispatch,
private val devXProperties: DevXProperties,
private val jwtAuthProperties: JwtAuthProperties,
private val permissionManager: PermissionManager,
) {

fun copy(copyPDU: CopyPDU) {
logger.info("CopyPDU: $copyPDU")
private val httpClient = HttpClientBuilderFactory.create().build()
private val signingKey = JwtUtils.createSigningKey(jwtAuthProperties.secretKey)

fun copy(userId: String, copyPDU: CopyPDU) {
logger.info("userId: $userId, CopyPDU: $copyPDU")
val token = generateToken(userId, copyPDU)
copyPDU.token = token
val copyPDUTransferPush = CopyPDUTransferPush(copyPDU)
transferDispatch.dispatch(copyPDUTransferPush)
}
Expand All @@ -52,7 +78,66 @@ class ClipboardService(
transferDispatch.dispatch(pastePDUTransferPush)
}

/**
* 云桌面拥有者上传文件时,不再生成token
* 云桌面分享人上传文件时,生成分享人的token
*/
private fun generateToken(userId: String, copyPDU: CopyPDU): String? {
if (userId != copyPDU.userId) {
throw PermissionException("can't send copy pdu with userId[${copyPDU.userId}]")
}
if (devXProperties.workspaceSearchUrl.isEmpty()) {
return null
}
val apiAuth = ApiAuth(devXProperties.appCode, devXProperties.appSecret)
val token = apiAuth.toJsonString().replace(System.lineSeparator(), "")
val request = Request.Builder().url(
"${devXProperties.workspaceSearchUrl}?projectId=${copyPDU.projectId}&workspaceName=${copyPDU.workspaceName}"
)
.header("X-Bkapi-Authorization", token)
.header("X-Devops-Uid", userId)
.header(HttpHeaders.CONTENT_TYPE, MediaTypes.APPLICATION_JSON)
.get()
.build()
try {
httpClient.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
logger.error("request url failed: " +
"${request.url}, ${response.code}, ${response.headers["X-Devops-RID"]}")
return null
}
val workspace = response.body!!.string().readJsonString<Response<DevXWorkSpace>>().data
?: throw PermissionException("can't find workspace by name[${copyPDU.workspaceName}]")
logger.debug("workspace: {}", workspace)
if (workspace.realOwner != userId && !workspace.viewers.contains(userId)) {
throw PermissionException("user[$userId] is not the owner or viewer of [${copyPDU.workspaceName}]")
}
return if (workspace.realOwner == userId) {
null
} else {
createToken(copyPDU.projectId, userId)
}
}
} catch (e: IOException) {
logger.error("Error while processing request: ${e.message}")
return null
}
}

private fun createToken(projectId: String, userId: String): String {
val claims = mutableMapOf(JWT_CLAIMS_REPOSITORY to "$projectId/$REPO_NAME")
permissionManager.checkRepoPermission(
action = PermissionAction.DOWNLOAD,
projectId = projectId,
repoName = REPO_NAME,
userId = userId
)
claims[JWT_CLAIMS_PERMIT] = PermissionAction.READ.name
return JwtUtils.generateToken(signingKey, jwtAuthProperties.expiration, userId, claims)
}

companion object {
private val logger = LoggerFactory.getLogger(ClipboardService::class.java)
private const val REPO_NAME = "lsync"
}
}

0 comments on commit 434988b

Please sign in to comment.