diff --git a/src/backend/auth/api-auth/src/main/kotlin/com/tencent/bkrepo/auth/api/ServiceUserClient.kt b/src/backend/auth/api-auth/src/main/kotlin/com/tencent/bkrepo/auth/api/ServiceUserClient.kt index dea0eff58c..8da50928fa 100644 --- a/src/backend/auth/api-auth/src/main/kotlin/com/tencent/bkrepo/auth/api/ServiceUserClient.kt +++ b/src/backend/auth/api-auth/src/main/kotlin/com/tencent/bkrepo/auth/api/ServiceUserClient.kt @@ -103,4 +103,10 @@ interface ServiceUserClient { fun userPwdById( @PathVariable uid: String ): Response + + @ApiOperation("获取用户token") + @GetMapping("/usertoken/{uid}") + fun userTokenById( + @PathVariable uid: String + ): Response> } diff --git a/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/controller/service/ServiceUserController.kt b/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/controller/service/ServiceUserController.kt index 452e410e10..e5e34829d7 100644 --- a/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/controller/service/ServiceUserController.kt +++ b/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/controller/service/ServiceUserController.kt @@ -89,4 +89,8 @@ class ServiceUserController @Autowired constructor( override fun userPwdById(uid: String): Response { return ResponseBuilder.success(userService.getUserPwdById(uid)) } + + override fun userTokenById(uid: String): Response> { + return ResponseBuilder.success(userService.listValidToken(uid).map { it.id }) + } } diff --git a/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/service/UserService.kt b/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/service/UserService.kt index 6d30ad6656..92ae5a0db3 100644 --- a/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/service/UserService.kt +++ b/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/service/UserService.kt @@ -71,6 +71,8 @@ interface UserService { fun listUserToken(userId: String): List + fun listValidToken(userId: String): List + fun removeToken(userId: String, name: String): Boolean fun findUserByUserToken(userId: String, pwd: String): User? diff --git a/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/service/local/UserServiceImpl.kt b/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/service/local/UserServiceImpl.kt index 9b63532d92..26710662e6 100644 --- a/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/service/local/UserServiceImpl.kt +++ b/src/backend/auth/biz-auth/src/main/kotlin/com/tencent/bkrepo/auth/service/local/UserServiceImpl.kt @@ -303,6 +303,13 @@ class UserServiceImpl constructor( return UserRequestUtil.convTokenResult(userRepository.findFirstByUserId(userId)!!.tokens) } + override fun listValidToken(userId: String): List { + checkUserExist(userId) + return userRepository.findFirstByUserId(userId)!!.tokens.filter { + it.expiredAt == null || it.expiredAt!!.isAfter(LocalDateTime.now()) + } + } + override fun removeToken(userId: String, name: String): Boolean { logger.info("remove token userId : [$userId] ,name : [$name]") checkUserExist(userId) diff --git a/src/backend/common/common-security/src/main/kotlin/com/tencent/bkrepo/common/security/manager/AuthenticationManager.kt b/src/backend/common/common-security/src/main/kotlin/com/tencent/bkrepo/common/security/manager/AuthenticationManager.kt index e1ab509088..e75d79be2a 100644 --- a/src/backend/common/common-security/src/main/kotlin/com/tencent/bkrepo/common/security/manager/AuthenticationManager.kt +++ b/src/backend/common/common-security/src/main/kotlin/com/tencent/bkrepo/common/security/manager/AuthenticationManager.kt @@ -100,6 +100,13 @@ class AuthenticationManager( return serviceUserClient.userPwdById(userId).data } + /** + * 根据用户id[userId]查询用户token + */ + fun findUserToken(userId: String): List? { + return serviceUserClient.userTokenById(userId).data + } + fun findOauthToken(accessToken: String): OauthToken? { return serviceOauthAuthorizationClient.getToken(accessToken).data } diff --git a/src/backend/common/common-service/src/main/kotlin/com/tencent/bkrepo/common/service/util/HeaderUtils.kt b/src/backend/common/common-service/src/main/kotlin/com/tencent/bkrepo/common/service/util/HeaderUtils.kt index 4f4bb7c251..310dd360c6 100644 --- a/src/backend/common/common-service/src/main/kotlin/com/tencent/bkrepo/common/service/util/HeaderUtils.kt +++ b/src/backend/common/common-service/src/main/kotlin/com/tencent/bkrepo/common/service/util/HeaderUtils.kt @@ -39,6 +39,10 @@ import java.net.URLDecoder object HeaderUtils { + fun getHeaderNames(): List? { + return request()?.headerNames?.toList() + } + fun getHeader(name: String): String? { return request()?.getHeader(name) } diff --git a/src/backend/replication/biz-replication/src/main/kotlin/com/tencent/bkrepo/replication/controller/service/ArtifactReplicaController.kt b/src/backend/replication/biz-replication/src/main/kotlin/com/tencent/bkrepo/replication/controller/service/ArtifactReplicaController.kt index 9a78d36482..fd05ee964d 100644 --- a/src/backend/replication/biz-replication/src/main/kotlin/com/tencent/bkrepo/replication/controller/service/ArtifactReplicaController.kt +++ b/src/backend/replication/biz-replication/src/main/kotlin/com/tencent/bkrepo/replication/controller/service/ArtifactReplicaController.kt @@ -118,11 +118,13 @@ class ArtifactReplicaController( } override fun replicaNodeCopyRequest(request: NodeMoveCopyRequest): Response { - return nodeClient.copyNode(request) + nodeClient.copyNode(request) + return ResponseBuilder.success() } override fun replicaNodeMoveRequest(request: NodeMoveCopyRequest): Response { - return nodeClient.moveNode(request) + nodeClient.moveNode(request) + return ResponseBuilder.success() } override fun replicaNodeDeleteRequest(request: NodeDeleteRequest): Response { diff --git a/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/api/NodeClient.kt b/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/api/NodeClient.kt index aa9714f5ad..65ff3412ec 100644 --- a/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/api/NodeClient.kt +++ b/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/api/NodeClient.kt @@ -127,11 +127,11 @@ interface NodeClient { @ApiOperation("移动节点") @PostMapping("/move") - fun moveNode(@RequestBody nodeMoveRequest: NodeMoveCopyRequest): Response + fun moveNode(@RequestBody nodeMoveRequest: NodeMoveCopyRequest): Response @ApiOperation("复制节点") @PostMapping("/copy") - fun copyNode(@RequestBody nodeCopyRequest: NodeMoveCopyRequest): Response + fun copyNode(@RequestBody nodeCopyRequest: NodeMoveCopyRequest): Response @ApiOperation("删除节点") @DeleteMapping("/delete") diff --git a/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/metadata/MetadataSaveRequest.kt b/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/metadata/MetadataSaveRequest.kt index 8d16de5a00..6f0fb5ead1 100644 --- a/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/metadata/MetadataSaveRequest.kt +++ b/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/metadata/MetadataSaveRequest.kt @@ -53,6 +53,8 @@ data class MetadataSaveRequest( val metadata: Map? = null, @ApiModelProperty("需要创建或更新的元数据", required = true) val nodeMetadata: List? = null, + @ApiModelProperty("是否替换元数据,删除原有元数据再新增元数据", required = false) + val replace: Boolean = false, @ApiModelProperty("操作用户") override val operator: String = SYSTEM_USER ) : NodeRequest, ServiceRequest diff --git a/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/search/AbstractQueryBuilder.kt b/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/search/AbstractQueryBuilder.kt index 4bd0f04bd1..9841112e73 100644 --- a/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/search/AbstractQueryBuilder.kt +++ b/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/search/AbstractQueryBuilder.kt @@ -51,6 +51,22 @@ open class AbstractQueryBuilder> { private var rootRule: Rule.NestedRule = createNestedRule(Rule.NestedRule.RelationType.AND) private var currentRule: Rule.NestedRule = rootRule + fun newBuilder(): T { + val constructor = javaClass.getDeclaredConstructor() + val newInstance = constructor.newInstance() as T + newInstance.projectId = projectId + newInstance.repoNames = repoNames + newInstance.repoType = repoType + newInstance.fields = fields + newInstance.sort = sort?.let { Sort(it.properties, it.direction) } + newInstance.pageLimit = PageLimit(pageLimit.pageNumber, pageLimit.pageSize) + val newRootRule: MutableList = mutableListOf() + newRootRule.addAll(rootRule.rules) + newInstance.rootRule = Rule.NestedRule(newRootRule, rootRule.relation) + newInstance.currentRule = newInstance.rootRule + return newInstance + } + /** * 设置查询字段[fields] */ diff --git a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/controller/service/NodeController.kt b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/controller/service/NodeController.kt index f3df515ff4..213d3e64db 100644 --- a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/controller/service/NodeController.kt +++ b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/controller/service/NodeController.kt @@ -118,14 +118,12 @@ class NodeController( return ResponseBuilder.success() } - override fun moveNode(nodeMoveRequest: NodeMoveCopyRequest): Response { - nodeService.moveNode(nodeMoveRequest) - return ResponseBuilder.success() + override fun moveNode(nodeMoveRequest: NodeMoveCopyRequest): Response { + return ResponseBuilder.success(nodeService.moveNode(nodeMoveRequest)) } - override fun copyNode(nodeCopyRequest: NodeMoveCopyRequest): Response { - nodeService.copyNode(nodeCopyRequest) - return ResponseBuilder.success() + override fun copyNode(nodeCopyRequest: NodeMoveCopyRequest): Response { + return ResponseBuilder.success(nodeService.copyNode(nodeCopyRequest)) } override fun deleteNode(nodeDeleteRequest: NodeDeleteRequest): Response { diff --git a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/metadata/impl/MetadataServiceImpl.kt b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/metadata/impl/MetadataServiceImpl.kt index 4ccc37cb06..9b3be3df68 100644 --- a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/metadata/impl/MetadataServiceImpl.kt +++ b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/metadata/impl/MetadataServiceImpl.kt @@ -92,7 +92,11 @@ class MetadataServiceImpl( MetadataUtils.changeSystem(nodeMetadata, repositoryProperties.allowUserAddSystemMetadata) ) checkIfUpdateSystemMetadata(oldMetadata, newMetadata) - node.metadata = MetadataUtils.merge(oldMetadata, newMetadata) + node.metadata = if (replace) { + newMetadata + } else { + MetadataUtils.merge(oldMetadata, newMetadata) + } nodeDao.save(node) publishEvent(buildMetadataSavedEvent(request)) diff --git a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/NodeMoveCopyOperation.kt b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/NodeMoveCopyOperation.kt index 6855b96b14..cb467dfd1f 100644 --- a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/NodeMoveCopyOperation.kt +++ b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/NodeMoveCopyOperation.kt @@ -31,6 +31,7 @@ package com.tencent.bkrepo.repository.service.node +import com.tencent.bkrepo.repository.pojo.node.NodeDetail import com.tencent.bkrepo.repository.pojo.node.service.NodeMoveCopyRequest /** @@ -47,7 +48,7 @@ interface NodeMoveCopyOperation { * mv 目录名 目录名 目标目录已存在,将源目录(目录本身及子文件)移动到目标目录;目标目录不存在则改名 * mv 目录名 文件名 出错 */ - fun moveNode(moveRequest: NodeMoveCopyRequest) + fun moveNode(moveRequest: NodeMoveCopyRequest): NodeDetail /** * 拷贝文件或者文件夹 @@ -58,5 +59,5 @@ interface NodeMoveCopyOperation { * cp 目录名 目录名 目标目录已存在,将源目录(目录本身及子文件)拷贝到目标目录;目标目录不存在则将源目录下文件拷贝到目标目录 * cp 目录名 文件名 出错 */ - fun copyNode(copyRequest: NodeMoveCopyRequest) + fun copyNode(copyRequest: NodeMoveCopyRequest): NodeDetail } diff --git a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/NodeMoveCopySupport.kt b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/NodeMoveCopySupport.kt index f78ce5671c..938637a9a1 100644 --- a/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/NodeMoveCopySupport.kt +++ b/src/backend/repository/biz-repository/src/main/kotlin/com/tencent/bkrepo/repository/service/node/impl/NodeMoveCopySupport.kt @@ -48,6 +48,7 @@ import com.tencent.bkrepo.repository.dao.NodeDao import com.tencent.bkrepo.repository.dao.RepositoryDao import com.tencent.bkrepo.repository.model.TNode import com.tencent.bkrepo.repository.model.TRepository +import com.tencent.bkrepo.repository.pojo.node.NodeDetail import com.tencent.bkrepo.repository.pojo.node.NodeListOption import com.tencent.bkrepo.repository.pojo.node.service.NodeMoveCopyRequest import com.tencent.bkrepo.repository.service.node.NodeMoveCopyOperation @@ -71,24 +72,32 @@ open class NodeMoveCopySupport( private val storageCredentialService: StorageCredentialService = nodeBaseService.storageCredentialService private val quotaService: QuotaService = nodeBaseService.quotaService - override fun moveNode(moveRequest: NodeMoveCopyRequest) { - moveCopy(moveRequest, true) + override fun moveNode(moveRequest: NodeMoveCopyRequest): NodeDetail { + val dstNode = moveCopy(moveRequest, true) logger.info("Move node success: [$moveRequest]") + return dstNode } - override fun copyNode(copyRequest: NodeMoveCopyRequest) { - moveCopy(copyRequest, false) + override fun copyNode(copyRequest: NodeMoveCopyRequest): NodeDetail { + val dstNode = moveCopy(copyRequest, false) logger.info("Copy node success: [$copyRequest]") + return dstNode } /** * 处理节点操作请求 */ - private fun moveCopy(request: NodeMoveCopyRequest, move: Boolean) { + private fun moveCopy(request: NodeMoveCopyRequest, move: Boolean): NodeDetail { with(resolveContext(request, move)) { preCheck(this) if (canIgnore(this)) { - return + return NodeBaseService.convertToDetail( + nodeBaseService.nodeDao.findNode( + projectId = dstProjectId, + repoName = dstRepoName, + fullPath = dstFullPath + ) + )!! } if (srcNode.folder) { moveCopyFolder(this) @@ -100,6 +109,13 @@ open class NodeMoveCopySupport( } else { publishEvent(NodeEventFactory.buildCopiedEvent(request)) } + return NodeBaseService.convertToDetail( + nodeBaseService.nodeDao.findNode( + projectId = dstProjectId, + repoName = dstRepoName, + fullPath = dstFullPath + ) + )!! } } 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 dea0a23c72..a021c371c3 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 @@ -138,13 +138,13 @@ class NodeServiceImpl( } @Transactional(rollbackFor = [Throwable::class]) - override fun moveNode(moveRequest: NodeMoveCopyRequest) { - NodeMoveCopySupport(this).moveNode(moveRequest) + override fun moveNode(moveRequest: NodeMoveCopyRequest): NodeDetail { + return NodeMoveCopySupport(this).moveNode(moveRequest) } @Transactional(rollbackFor = [Throwable::class]) - override fun copyNode(copyRequest: NodeMoveCopyRequest) { - NodeMoveCopySupport(this).copyNode(copyRequest) + override fun copyNode(copyRequest: NodeMoveCopyRequest): NodeDetail { + return NodeMoveCopySupport(this).copyNode(copyRequest) } @Transactional(rollbackFor = [Throwable::class]) 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 1186273a36..df7d0131f5 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 @@ -227,11 +227,11 @@ class CommitEdgeCenterNodeServiceImpl( return CommitEdgeCenterNodeRestoreSupport(this).restoreNode(restoreContext) } - override fun copyNode(copyRequest: NodeMoveCopyRequest) { + override fun copyNode(copyRequest: NodeMoveCopyRequest): NodeDetail { return CommitEdgeCenterNodeMoveCopySupport(this, clusterProperties).copyNode(copyRequest) } - override fun moveNode(moveRequest: NodeMoveCopyRequest) { + override fun moveNode(moveRequest: NodeMoveCopyRequest): NodeDetail { return CommitEdgeCenterNodeMoveCopySupport(this, clusterProperties).moveNode(moveRequest) } 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 ea3e6a30b1..c4f48614b9 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 @@ -150,15 +150,15 @@ class EdgeNodeServiceImpl( } @Transactional(rollbackFor = [Throwable::class]) - override fun moveNode(moveRequest: NodeMoveCopyRequest) { + override fun moveNode(moveRequest: NodeMoveCopyRequest): NodeDetail { centerNodeClient.moveNode(moveRequest) - NodeMoveCopySupport(this).moveNode(moveRequest) + return NodeMoveCopySupport(this).moveNode(moveRequest) } @Transactional(rollbackFor = [Throwable::class]) - override fun copyNode(copyRequest: NodeMoveCopyRequest) { + override fun copyNode(copyRequest: NodeMoveCopyRequest): NodeDetail { centerNodeClient.copyNode(copyRequest) - NodeMoveCopySupport(this).copyNode(copyRequest) + return NodeMoveCopySupport(this).copyNode(copyRequest) } @Transactional(rollbackFor = [Throwable::class]) diff --git a/src/backend/s3/api-s3/src/main/kotlin/com/tencent/bkrepo/s3/constant/S3HttpHeaders.kt b/src/backend/s3/api-s3/src/main/kotlin/com/tencent/bkrepo/s3/constant/S3HttpHeaders.kt index ad848c5c21..f0e9388c02 100644 --- a/src/backend/s3/api-s3/src/main/kotlin/com/tencent/bkrepo/s3/constant/S3HttpHeaders.kt +++ b/src/backend/s3/api-s3/src/main/kotlin/com/tencent/bkrepo/s3/constant/S3HttpHeaders.kt @@ -32,4 +32,8 @@ object S3HttpHeaders { const val X_AMZ_REQUEST_ID = "X-Amz-Request-Id" const val X_AMZ_DATE = "X-Amz-Date" const val X_AMZ_CONTENT_SHA256 = "X-Amz-Content-Sha256" + const val X_AMZ_COPY_SOURCE = "X-Amz-Copy-Source" + const val X_AMZ_METADATA_DIRECTIVE = "X-Amz-Metadata-Directive" + const val X_AMZ_META_PREFIX = "X-Amz-Meta-" + const val X_AMZ_META_MTIME = "X-Amz-Meta-Mtime" } diff --git a/src/backend/s3/api-s3/src/main/kotlin/com/tencent/bkrepo/s3/pojo/CopyObjectResult.kt b/src/backend/s3/api-s3/src/main/kotlin/com/tencent/bkrepo/s3/pojo/CopyObjectResult.kt new file mode 100644 index 0000000000..e533cfab41 --- /dev/null +++ b/src/backend/s3/api-s3/src/main/kotlin/com/tencent/bkrepo/s3/pojo/CopyObjectResult.kt @@ -0,0 +1,52 @@ +/* + * 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.bkrepo.s3.pojo + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement + +@JacksonXmlRootElement(localName = "CopyObjectResult") +data class CopyObjectResult( + @JacksonXmlProperty(localName = "ETag") + val eTag: String, + + @JacksonXmlProperty(localName = "LastModified") + val lastModified: String, + + @JacksonXmlProperty(localName = "ChecksumCRC32") + val checksumCRC32: String, + + @JacksonXmlProperty(localName = "ChecksumCRC32C") + val checksumCRC32C: String, + + @JacksonXmlProperty(localName = "ChecksumSHA1") + val checksumSHA1: String, + + @JacksonXmlProperty(localName = "ChecksumSHA256") + val checksumSHA256: String +) diff --git a/src/backend/s3/api-s3/src/main/kotlin/com/tencent/bkrepo/s3/pojo/ListBucketResult.kt b/src/backend/s3/api-s3/src/main/kotlin/com/tencent/bkrepo/s3/pojo/ListBucketResult.kt new file mode 100644 index 0000000000..0cb5bd11e2 --- /dev/null +++ b/src/backend/s3/api-s3/src/main/kotlin/com/tencent/bkrepo/s3/pojo/ListBucketResult.kt @@ -0,0 +1,107 @@ +/* + * 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.bkrepo.s3.pojo + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement +import com.tencent.bkrepo.common.api.constant.StringPool +import com.tencent.bkrepo.repository.pojo.node.NodeDetail + +@JacksonXmlRootElement(localName = "ListBucketResult", namespace = "http://s3.amazonaws.com/doc/2006-03-01/") +data class ListBucketResult( + @JacksonXmlProperty(localName = "IsTruncated") + val isTruncated: Boolean, + @JacksonXmlProperty(localName = "Marker") + val marker: String? = null, + @JacksonXmlProperty(localName = "Contents") + @JacksonXmlElementWrapper(useWrapping = false) + val contents: List, + @JacksonXmlProperty(localName = "Name") + val name: String, + @JacksonXmlProperty(localName = "Prefix") + val prefix: String, + @JacksonXmlProperty(localName = "CommonPrefixes") + @JacksonXmlElementWrapper(useWrapping = false) + val commonPrefixes: List?, + @JacksonXmlProperty(localName = "MaxKeys") + val maxKeys: Int, +) { + constructor( + repoName: String, + nodeDetailList: List>, + maxKeys: Int, + prefix: String, + folders: List? + ) : this( + name = repoName, + prefix = prefix, + commonPrefixes = folders?.map { CommonPrefix(it) }.orEmpty(), + marker = null, + maxKeys = maxKeys, + isTruncated = false, + contents = nodeDetailList.map { + val owner = it[NodeDetail::createdBy.name].toString() + Content( + key = it[NodeDetail::fullPath.name].toString().removePrefix(StringPool.SLASH), + lastModified = it[NodeDetail::lastModifiedDate.name].toString(), + eTag = "\"${it[NodeDetail::md5.name].toString()}\"", + size = it[NodeDetail::size.name].toString().toLong(), + storageClass = "STANDARD", + owner = Owner(owner, owner) + ) + } + ) +} + +data class Content( + @JacksonXmlProperty(localName = "ETag") + val eTag: String, + @JacksonXmlProperty(localName = "Key") + val key: String, + @JacksonXmlProperty(localName = "LastModified") + val lastModified: String, + @JacksonXmlProperty(localName = "Owner") + val owner: Owner, + @JacksonXmlProperty(localName = "Size") + val size: Long, + @JacksonXmlProperty(localName = "StorageClass") + val storageClass: String +) + +data class Owner( + @JacksonXmlProperty(localName = "DisplayName") + val displayName: String, + @JacksonXmlProperty(localName = "ID") + val id: String +) + +data class CommonPrefix( + @JacksonXmlProperty(localName = "Prefix") + val prefix: String +) diff --git a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/S3LocalRepository.kt b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/S3LocalRepository.kt index 1e4c18bcca..cd47bb671a 100644 --- a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/S3LocalRepository.kt +++ b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/S3LocalRepository.kt @@ -31,10 +31,14 @@ import com.tencent.bkrepo.common.api.constant.HttpStatus import com.tencent.bkrepo.common.api.message.CommonMessageCode import com.tencent.bkrepo.common.artifact.metrics.ArtifactMetrics import com.tencent.bkrepo.common.artifact.repository.context.ArtifactDownloadContext +import com.tencent.bkrepo.common.artifact.repository.context.ArtifactUploadContext import com.tencent.bkrepo.common.artifact.repository.local.LocalRepository import com.tencent.bkrepo.common.artifact.resolve.response.ArtifactResource +import com.tencent.bkrepo.common.service.util.HttpContextHolder +import com.tencent.bkrepo.s3.artifact.utils.ContextUtil import com.tencent.bkrepo.s3.constant.INTERNAL_ERROR import com.tencent.bkrepo.s3.constant.NO_SUCH_KEY +import com.tencent.bkrepo.s3.constant.S3HttpHeaders import com.tencent.bkrepo.s3.constant.S3MessageCode import com.tencent.bkrepo.s3.exception.S3InternalException import com.tencent.bkrepo.s3.exception.S3NotFoundException @@ -71,4 +75,18 @@ class S3LocalRepository: LocalRepository() { } } + override fun onUpload(context: ArtifactUploadContext) { + with(context) { + val nodeCreateRequest = buildNodeCreateRequest(this).copy(overwrite = true) + storageManager.storeArtifactFile(nodeCreateRequest, getArtifactFile(), storageCredentials) + } + } + + override fun onUploadFinished(context: ArtifactUploadContext) { + super.onUploadFinished(context) + val response = HttpContextHolder.getResponse() + response.setHeader(S3HttpHeaders.X_AMZ_REQUEST_ID, ContextUtil.getTraceId()) + response.setHeader(S3HttpHeaders.X_AMZ_TRACE_ID, ContextUtil.getTraceId()) + } + } diff --git a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/AWS4AuthHandler.kt b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/AWS4AuthHandler.kt index 05e984100f..c8067151df 100644 --- a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/AWS4AuthHandler.kt +++ b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/AWS4AuthHandler.kt @@ -49,12 +49,14 @@ import javax.servlet.http.HttpServletRequest * AWS4 Http 认证方式 */ class AWS4AuthHandler( - private val authenticationManager: AuthenticationManager + authenticationManager: AuthenticationManager ) : HttpAuthHandler { @Value("\${spring.application.name}") private val applicationName: String = "s3" + private val authValidator = AbstractAuthValidator.getAuthValidator(authenticationManager) + override fun extractAuthCredentials(request: HttpServletRequest): HttpAuthCredentials { val authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION).orEmpty() return if (authorizationHeader.startsWith(AWS4_AUTH_PREFIX)) { @@ -64,7 +66,7 @@ class AWS4AuthHandler( * 用用户名去db中查出密码来,用客户端同样的算法计算签名, * 如果计算的签名与传进来的签名一样,则认证通过 */ - var userName = AWS4AuthUtil.getAccessKey(authorizationHeader) + val userName = AWS4AuthUtil.getAccessKey(authorizationHeader) buildAWS4AuthorizationInfo(request, userName) } catch (exception: Exception) { // 认证异常处理 @@ -78,13 +80,8 @@ class AWS4AuthHandler( @Throws(AWS4AuthenticationException::class) override fun onAuthenticate(request: HttpServletRequest, authCredentials: HttpAuthCredentials): String { require(authCredentials is AWS4AuthCredentials) - - var password: String = authenticationManager.findUserPwd(authCredentials.accessKeyId) - ?: throw AWS4AuthenticationException( - params = arrayOf(SIGN_NOT_MATCH, getRequestResource(request)) - ) - var authPassed = AWS4AuthUtil.validAuthorization(authCredentials, password) - if (authPassed) { + val pass = authValidator.validate(authCredentials) + if (pass) { return authCredentials.accessKeyId } logger.warn("s3 auth fail, request data:$authCredentials") @@ -102,7 +99,7 @@ class AWS4AuthHandler( accessKeyId = accessKeyId, requestDate = request.getHeader(S3HttpHeaders.X_AMZ_DATE) ?: "", contentHash = request.getHeader(S3HttpHeaders.X_AMZ_CONTENT_SHA256) ?: "", - /*uri = request.requestURI.split("?").toTypedArray()[0],*/ +// uri = request.requestURI.split("?").toTypedArray()[0], uri = "/$applicationName"+request.requestURI.split("?").toTypedArray()[0], host = request.getHeader(HttpHeaders.HOST) ?: "", queryString = request.queryString ?: "", diff --git a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/AbstractAuthValidator.kt b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/AbstractAuthValidator.kt new file mode 100644 index 0000000000..8e60612a39 --- /dev/null +++ b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/AbstractAuthValidator.kt @@ -0,0 +1,78 @@ +/* + * 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.bkrepo.s3.artifact.auth + +import com.google.common.cache.Cache +import com.google.common.cache.CacheBuilder +import com.tencent.bkrepo.common.security.manager.AuthenticationManager +import com.tencent.bkrepo.s3.artifact.utils.AWS4AuthUtil +import java.util.concurrent.TimeUnit + +/** + * 认证验证器 + */ +abstract class AbstractAuthValidator { + + private var nextValidator: AbstractAuthValidator? = null + + abstract fun loadSecretKey(accessKeyId: String): List + + fun setNext(nextValidator: AbstractAuthValidator) { + this.nextValidator = nextValidator + } + + fun validate(authCredentials: AWS4AuthCredentials): Boolean { + var pass: Boolean + val secretKeys = loadSecretKey(authCredentials.accessKeyId) + secretKeys.forEach { + pass = AWS4AuthUtil.validAuthorization(authCredentials, it) + if (pass) { + secretKeyCache.put(authCredentials.accessKeyId, it) + return true + } + } + return nextValidator?.validate(authCredentials) ?: false + } + + companion object { + @JvmStatic + protected val secretKeyCache: Cache = CacheBuilder.newBuilder() + .maximumSize(100) + .expireAfterWrite(10, TimeUnit.MINUTES) + .build() + fun getAuthValidator(authenticationManager: AuthenticationManager): AbstractAuthValidator { + val cacheAuthValidator = CacheAuthValidator() + val passwordAuthValidator = PasswordAuthValidator(authenticationManager) + val tokenAuthValidator = TokenAuthValidator(authenticationManager) + + cacheAuthValidator.setNext(passwordAuthValidator) + passwordAuthValidator.setNext(tokenAuthValidator) + return cacheAuthValidator + } + } +} diff --git a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/CacheAuthValidator.kt b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/CacheAuthValidator.kt new file mode 100644 index 0000000000..8d288f508e --- /dev/null +++ b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/CacheAuthValidator.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.bkrepo.s3.artifact.auth + +/** + * 缓存认证验证器 + */ +class CacheAuthValidator : AbstractAuthValidator() { + override fun loadSecretKey(accessKeyId: String): List { + return secretKeyCache.getIfPresent(accessKeyId)?.let { listOf(it) }.orEmpty() + } +} diff --git a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/PasswordAuthValidator.kt b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/PasswordAuthValidator.kt new file mode 100644 index 0000000000..bea35e426b --- /dev/null +++ b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/PasswordAuthValidator.kt @@ -0,0 +1,41 @@ +/* + * 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.bkrepo.s3.artifact.auth + +import com.tencent.bkrepo.common.security.manager.AuthenticationManager + +/** + * 密码认证验证器 + */ +class PasswordAuthValidator( + private val authenticationManager: AuthenticationManager +) : AbstractAuthValidator(){ + override fun loadSecretKey(accessKeyId: String): List { + return authenticationManager.findUserPwd(accessKeyId)?.let { listOf(it) }.orEmpty() + } +} diff --git a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/TokenAuthValidator.kt b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/TokenAuthValidator.kt new file mode 100644 index 0000000000..b15a0ec85a --- /dev/null +++ b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/auth/TokenAuthValidator.kt @@ -0,0 +1,41 @@ +/* + * 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.bkrepo.s3.artifact.auth + +import com.tencent.bkrepo.common.security.manager.AuthenticationManager + +/** + * 令牌认证验证器 + */ +class TokenAuthValidator( + private val authenticationManager: AuthenticationManager +) : AbstractAuthValidator() { + override fun loadSecretKey(accessKeyId: String): List { + return authenticationManager.findUserToken(accessKeyId).orEmpty() + } +} diff --git a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/response/S3ArtifactResourceWriter.kt b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/response/S3ArtifactResourceWriter.kt index 9ab3eec514..d9b2ffe640 100644 --- a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/response/S3ArtifactResourceWriter.kt +++ b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/response/S3ArtifactResourceWriter.kt @@ -40,16 +40,18 @@ import com.tencent.bkrepo.common.artifact.resolve.response.ArtifactResource import com.tencent.bkrepo.common.service.util.HttpContextHolder import com.tencent.bkrepo.common.storage.core.StorageProperties import com.tencent.bkrepo.common.storage.monitor.Throughput +import com.tencent.bkrepo.repository.pojo.node.NodeDetail import com.tencent.bkrepo.s3.artifact.utils.ContextUtil import com.tencent.bkrepo.s3.constant.DEFAULT_ENCODING import com.tencent.bkrepo.s3.constant.S3HttpHeaders +import com.tencent.bkrepo.s3.utils.TimeUtil import javax.servlet.http.HttpServletResponse /** * S3协议的响应输出 */ class S3ArtifactResourceWriter ( - private val storageProperties: StorageProperties + storageProperties: StorageProperties ) : AbstractArtifactResourceHandler(storageProperties) { @Throws(ArtifactResponseException::class) @@ -68,7 +70,7 @@ class S3ArtifactResourceWriter ( val status = resource.status?.value ?: HttpStatus.OK.value val totalSize = resource.getTotalSize().toString() - prepareResponseHeaders(response, totalSize.toLong(), node?.sha256!!, status, contentType, characterEncoding) + prepareResponseHeaders(response, totalSize.toLong(), node, status, contentType, characterEncoding) response.bufferSize = getBufferSize(range.length.toInt()) return writeRangeStream(resource, request, response) } @@ -76,7 +78,7 @@ class S3ArtifactResourceWriter ( private fun prepareResponseHeaders( response: HttpServletResponse, contentLength: Long, - eTag: String, + node: NodeDetail?, status: Int, contentType: String = MediaTypes.APPLICATION_OCTET_STREAM, characterEncoding: String = DEFAULT_ENCODING @@ -85,8 +87,12 @@ class S3ArtifactResourceWriter ( response.setHeader(S3HttpHeaders.X_AMZ_TRACE_ID, ContextUtil.getTraceId()) response.setHeader(HttpHeaders.CONTENT_TYPE, contentType) response.setHeader(HttpHeaders.CONTENT_LENGTH, contentLength.toString()) - response.setHeader(HttpHeaders.ETAG, eTag) - response.setCharacterEncoding(characterEncoding) + node?.let { + response.setHeader(HttpHeaders.ETAG, "\"${it.md5}\"") + // 本地时间转换为GMT时间 + response.setHeader(HttpHeaders.LAST_MODIFIED, TimeUtil.getLastModified(it)) + } + response.characterEncoding = characterEncoding response.status = status } -} \ No newline at end of file +} diff --git a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/utils/AWS4AuthUtil.kt b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/utils/AWS4AuthUtil.kt index 9df7fce733..6e869d218c 100644 --- a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/utils/AWS4AuthUtil.kt +++ b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/artifact/utils/AWS4AuthUtil.kt @@ -31,20 +31,15 @@ package com.tencent.bkrepo.s3.artifact.utils -import com.tencent.bkrepo.common.api.constant.HttpHeaders +import com.tencent.bkrepo.common.service.util.HeaderUtils import com.tencent.bkrepo.s3.artifact.auth.AWS4AuthCredentials -import com.tencent.bkrepo.s3.constant.S3HttpHeaders import com.tencent.bkrepo.s3.exception.AWS4AuthenticationException -import org.springframework.util.StringUtils import java.io.UnsupportedEncodingException import java.math.BigInteger import java.security.MessageDigest import java.security.NoSuchAlgorithmException -import java.util.Collections import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec -import kotlin.collections.ArrayList -import kotlin.collections.HashMap /** * AWS4签名验证 @@ -57,19 +52,14 @@ object AWS4AuthUtil { authCredentials: AWS4AuthCredentials, secretAccessKey: String ): Boolean { - val heardMap: MutableMap = HashMap() - heardMap[S3HttpHeaders.X_AMZ_CONTENT_SHA256.toLowerCase()] = authCredentials.contentHash - heardMap[S3HttpHeaders.X_AMZ_DATE.toLowerCase()] = authCredentials.requestDate - heardMap[HttpHeaders.HOST.toLowerCase()] = authCredentials.host // 解析签名信息 val authInfo = parseAuthorization(authCredentials.authorization) if (authCredentials.accessKeyId != authInfo.accessKey) { return false } // 待签名字符串 - var stringToSign: String = buildStringToSign( + val stringToSign: String = buildStringToSign( authCredentials, - heardMap, authInfo ) // 计算签名的key @@ -119,11 +109,10 @@ object AWS4AuthUtil { private fun buildStringToSign( authCredentials: AWS4AuthCredentials, - heardMap: Map, authInfo: AuthorizationInfo ): String { ///待签名字符串 - var stringToSign: String = "" + var stringToSign = "" //签名由4部分组成 //1-Algorithm – 用于创建规范请求的哈希的算法。对于 SHA-256,算法是 AWS4-HMAC-SHA256。 stringToSign += "AWS4-HMAC-SHA256\n" @@ -145,22 +134,21 @@ object AWS4AuthUtil { //4.2-Canonical URI hashedCanonicalRequest += "${authCredentials.uri}\n" //4.3-Canonical Query String - hashedCanonicalRequest += if (!StringUtils.isEmpty(authCredentials.queryString)) { + hashedCanonicalRequest += if (authCredentials.queryString.isNotEmpty()) { val queryStringMap = parseQueryParams(authCredentials.queryString) - val keyList: List = ArrayList(queryStringMap.keys) - Collections.sort(keyList) + val keyList = queryStringMap.keys.sorted() val queryStringBuilder = StringBuilder("") for (key in keyList) { queryStringBuilder.append(key).append("=").append(queryStringMap[key]).append("&") } queryStringBuilder.deleteCharAt(queryStringBuilder.lastIndexOf("&")) - "${queryStringBuilder.toString()}\n" + "$queryStringBuilder\n" } else { "${authCredentials.queryString}\n" } //4.4-Canonical Headers for (name in authInfo.signedHeaders) { - hashedCanonicalRequest += "$name:${heardMap[name]}\n" + hashedCanonicalRequest += "$name:${HeaderUtils.getHeader(name)}\n" } hashedCanonicalRequest += "\n" //4.5-Signed Headers @@ -209,7 +197,7 @@ object AWS4AuthUtil { return mac.doFinal(data!!.toByteArray(charset("UTF8"))) } - internal val hexArray = "0123456789ABCDEF".toCharArray() + private val hexArray = "0123456789ABCDEF".toCharArray() private fun doBytesToHex(bytes: ByteArray): String { val hexChars = CharArray(bytes.size * 2) for (j in bytes.indices) { @@ -238,7 +226,7 @@ object AWS4AuthUtil { private fun parseQueryParams(queryString: String?): Map { val queryParams: MutableMap = HashMap() if (!queryString.isNullOrEmpty()) { - val queryParamsArray = queryString!!.split("&") + val queryParamsArray = queryString.split("&") for (param in queryParamsArray) { val keyValue = param.split("=") val key = keyValue[0] @@ -248,4 +236,4 @@ object AWS4AuthUtil { } return queryParams } -} \ No newline at end of file +} diff --git a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/config/S3WebConfig.kt b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/config/S3WebConfig.kt new file mode 100644 index 0000000000..b2ca39a2d0 --- /dev/null +++ b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/config/S3WebConfig.kt @@ -0,0 +1,41 @@ +/* + * 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.bkrepo.s3.config + +import org.springframework.context.annotation.Configuration +import org.springframework.http.MediaType +import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer + +@Configuration +class S3WebConfig : WebMvcConfigurer { + + override fun configureContentNegotiation(configurer: ContentNegotiationConfigurer) { + configurer.defaultContentType(MediaType.APPLICATION_XML) + } +} diff --git a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/controller/S3ObjectController.kt b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/controller/S3ObjectController.kt index 35fb57f380..c4b2abf255 100644 --- a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/controller/S3ObjectController.kt +++ b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/controller/S3ObjectController.kt @@ -32,28 +32,62 @@ package com.tencent.bkrepo.s3.controller import com.tencent.bkrepo.auth.pojo.enums.PermissionAction +import com.tencent.bkrepo.auth.pojo.enums.ResourceType +import com.tencent.bkrepo.common.api.constant.StringPool +import com.tencent.bkrepo.common.artifact.api.ArtifactFile import com.tencent.bkrepo.common.artifact.api.ArtifactPathVariable -import com.tencent.bkrepo.common.security.manager.PermissionManager +import com.tencent.bkrepo.common.security.permission.Permission +import com.tencent.bkrepo.common.service.util.HeaderUtils import com.tencent.bkrepo.s3.artifact.S3ArtifactInfo import com.tencent.bkrepo.s3.artifact.S3ArtifactInfo.Companion.GENERIC_MAPPING_URI +import com.tencent.bkrepo.s3.constant.S3HttpHeaders.X_AMZ_COPY_SOURCE +import com.tencent.bkrepo.s3.pojo.CopyObjectResult +import com.tencent.bkrepo.s3.pojo.ListBucketResult import com.tencent.bkrepo.s3.service.S3ObjectService import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @RestController class S3ObjectController( private val s3ObjectService: S3ObjectService, - private val permissionManager: PermissionManager ) { @GetMapping(GENERIC_MAPPING_URI) - fun getObject(@ArtifactPathVariable artifactInfo: S3ArtifactInfo){ - permissionManager.checkNodePermission( - action = PermissionAction.READ, - projectId = artifactInfo.projectId, - repoName = artifactInfo.repoName, - path = *arrayOf(artifactInfo.getArtifactFullPath()) - ) - s3ObjectService.getObject(artifactInfo) + @Permission(type = ResourceType.NODE, action = PermissionAction.READ) + fun getOrListObject( + @ArtifactPathVariable artifactInfo: S3ArtifactInfo, + @RequestParam delimiter: String?, + @RequestParam("max-keys") maxKeys: Int?, + @RequestParam prefix: String?, + ): ListBucketResult? { + val queryParamsNotNull = delimiter != null || maxKeys != null || prefix != null + if (queryParamsNotNull || artifactInfo.getArtifactFullPath() == StringPool.ROOT) { + return s3ObjectService.listObjects( + artifactInfo = artifactInfo, + maxKeys = maxKeys ?: 1000, + delimiter = delimiter ?: StringPool.SLASH, + prefix = prefix ?: "" + ) + } else { + s3ObjectService.getObject(artifactInfo) + return null + } } + @PutMapping(GENERIC_MAPPING_URI) + @Permission(type = ResourceType.NODE, action = PermissionAction.WRITE) + fun putOrCopyObject(@ArtifactPathVariable artifactInfo: S3ArtifactInfo, file: ArtifactFile): CopyObjectResult? { + // 根目录不需要创建 + if (artifactInfo.getArtifactFullPath() == StringPool.ROOT) { + return null + } + return if (HeaderUtils.getHeader(X_AMZ_COPY_SOURCE).isNullOrEmpty()) { + s3ObjectService.putObject(artifactInfo, file) + null + } else { + s3ObjectService.copyObject(artifactInfo) + } + + } } diff --git a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/service/S3ObjectService.kt b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/service/S3ObjectService.kt index b2340e6fad..78157a1d13 100644 --- a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/service/S3ObjectService.kt +++ b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/service/S3ObjectService.kt @@ -32,50 +32,145 @@ package com.tencent.bkrepo.s3.service import com.tencent.bkrepo.common.api.constant.HttpStatus +import com.tencent.bkrepo.common.api.constant.StringPool +import com.tencent.bkrepo.common.api.constant.StringPool.SLASH +import com.tencent.bkrepo.common.api.constant.ensurePrefix +import com.tencent.bkrepo.common.api.constant.ensureSuffix +import com.tencent.bkrepo.common.artifact.api.ArtifactFile +import com.tencent.bkrepo.common.artifact.path.PathUtils import com.tencent.bkrepo.common.artifact.repository.context.ArtifactContextHolder import com.tencent.bkrepo.common.artifact.repository.context.ArtifactDownloadContext +import com.tencent.bkrepo.common.artifact.repository.context.ArtifactUploadContext import com.tencent.bkrepo.common.artifact.repository.core.ArtifactService import com.tencent.bkrepo.common.generic.configuration.AutoIndexRepositorySettings +import com.tencent.bkrepo.common.security.util.SecurityUtils +import com.tencent.bkrepo.common.service.util.HeaderUtils +import com.tencent.bkrepo.repository.api.MetadataClient +import com.tencent.bkrepo.repository.api.NodeClient +import com.tencent.bkrepo.repository.pojo.metadata.MetadataModel +import com.tencent.bkrepo.repository.pojo.metadata.MetadataSaveRequest +import com.tencent.bkrepo.repository.pojo.node.NodeDetail +import com.tencent.bkrepo.repository.pojo.node.service.NodeMoveCopyRequest +import com.tencent.bkrepo.repository.pojo.search.NodeQueryBuilder import com.tencent.bkrepo.s3.artifact.S3ArtifactInfo import com.tencent.bkrepo.s3.constant.NO_SUCH_ACCESS import com.tencent.bkrepo.s3.constant.NO_SUCH_KEY +import com.tencent.bkrepo.s3.constant.S3HttpHeaders.X_AMZ_COPY_SOURCE +import com.tencent.bkrepo.s3.constant.S3HttpHeaders.X_AMZ_METADATA_DIRECTIVE +import com.tencent.bkrepo.s3.constant.S3HttpHeaders.X_AMZ_META_PREFIX import com.tencent.bkrepo.s3.constant.S3MessageCode import com.tencent.bkrepo.s3.exception.S3AccessDeniedException import com.tencent.bkrepo.s3.exception.S3NotFoundException +import com.tencent.bkrepo.s3.pojo.CopyObjectResult +import com.tencent.bkrepo.s3.pojo.ListBucketResult import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import java.net.URLDecoder /** * S3对象服务类 */ @Service -class S3ObjectService: ArtifactService() { +class S3ObjectService( + private val nodeClient: NodeClient, + private val metadataClient: MetadataClient +) : ArtifactService() { fun getObject(artifactInfo: S3ArtifactInfo) { - with(artifactInfo) { - val node = ArtifactContextHolder.getNodeDetail(artifactInfo) ?: - throw S3NotFoundException( - HttpStatus.NOT_FOUND, - S3MessageCode.S3_NO_SUCH_KEY, - params = arrayOf(NO_SUCH_KEY, artifactInfo.getArtifactFullPath()) - ) - ArtifactContextHolder.getRepoDetail() - val context = ArtifactDownloadContext() - //仓库未开启自动创建目录索引时不允许访问目录 - val autoIndexSettings = AutoIndexRepositorySettings.from(context.repositoryDetail.configuration) - if (node.folder && autoIndexSettings?.enabled == false) { - logger.warn("${artifactInfo.getArtifactFullPath()} is folder " + - "or repository is not enabled for automatic directory index creation") - throw S3AccessDeniedException( - HttpStatus.FORBIDDEN, - S3MessageCode.S3_NO_SUCH_ACCESS, - params = arrayOf(NO_SUCH_ACCESS, artifactInfo.getArtifactFullPath()) - ) - } - repository.download(context) + val node = ArtifactContextHolder.getNodeDetail(artifactInfo) ?: + throw S3NotFoundException( + HttpStatus.NOT_FOUND, + S3MessageCode.S3_NO_SUCH_KEY, + params = arrayOf(NO_SUCH_KEY, artifactInfo.getArtifactFullPath()) + ) + ArtifactContextHolder.getRepoDetail() + val context = ArtifactDownloadContext() + //仓库未开启自动创建目录索引时不允许访问目录 + val autoIndexSettings = AutoIndexRepositorySettings.from(context.repositoryDetail.configuration) + if (node.folder && autoIndexSettings?.enabled == false) { + logger.warn("${artifactInfo.getArtifactFullPath()} is folder " + + "or repository is not enabled for automatic directory index creation") + throw S3AccessDeniedException( + HttpStatus.FORBIDDEN, + S3MessageCode.S3_NO_SUCH_ACCESS, + params = arrayOf(NO_SUCH_ACCESS, artifactInfo.getArtifactFullPath()) + ) } + repository.download(context) + } + + fun putObject(artifactInfo: S3ArtifactInfo, file: ArtifactFile) { + val context = ArtifactUploadContext(file) + repository.upload(context) } + fun listObjects(artifactInfo: S3ArtifactInfo, maxKeys: Int, delimiter: String, prefix: String): ListBucketResult { + val projectId = artifactInfo.projectId + val repoName = artifactInfo.repoName + val nodeQueryBuilder = NodeQueryBuilder() + .projectId(projectId).repoName(repoName) + .apply { + if (prefix.isEmpty()) { + path(StringPool.ROOT) + } else { + path(PathUtils.normalizePath(prefix.ensurePrefix(SLASH))) + } + } + val folderQueryBuilder = nodeQueryBuilder.newBuilder().excludeFile().select(NodeDetail::fullPath.name) + val folders = nodeClient.queryWithoutCount(folderQueryBuilder.build()).data!!.records + .map { + it[NodeDetail::fullPath.name].toString().removePrefix(SLASH).ensureSuffix(SLASH) + } + val fileQueryBuilder = nodeQueryBuilder.newBuilder().excludeFolder() + val files = nodeClient.queryWithoutCount(fileQueryBuilder.build()).data!!.records + return ListBucketResult(repoName, files, maxKeys, prefix, folders) + } + + fun copyObject(artifactInfo: S3ArtifactInfo): CopyObjectResult { + val source = HeaderUtils.getHeader(X_AMZ_COPY_SOURCE) ?: throw IllegalArgumentException(X_AMZ_COPY_SOURCE) + val delimiterIndex = source.indexOf(SLASH) + val srcRepoName = source.substring(0, delimiterIndex) + val srcFullPath = URLDecoder.decode(source.substring(delimiterIndex), Charsets.UTF_8.name()) + val copyRequest = NodeMoveCopyRequest( + srcProjectId = artifactInfo.projectId, + srcRepoName = srcRepoName, + srcFullPath = srcFullPath, + destProjectId = artifactInfo.projectId, + destRepoName = artifactInfo.repoName, + destFullPath = artifactInfo.getArtifactFullPath(), + overwrite = true, + operator = SecurityUtils.getUserId() + ) + var dstNode = nodeClient.copyNode(copyRequest).data!! + dstNode = replaceMetadata(dstNode) + return CopyObjectResult( + eTag = "\"${dstNode.md5}\"", + lastModified = dstNode.lastModifiedDate, + checksumCRC32 = "", + checksumCRC32C = "", + checksumSHA1 = "", + checksumSHA256 = "\"${dstNode.sha256}\"" + ) + } + + fun replaceMetadata(nodeDetail: NodeDetail): NodeDetail { + val directive = HeaderUtils.getHeader(X_AMZ_METADATA_DIRECTIVE) + if (directive.equals("REPLACE", true)) { + val metadataHeader = HeaderUtils.getHeaderNames()?.filter { + it.startsWith(X_AMZ_META_PREFIX, true) + }.orEmpty() + val saveRequest = MetadataSaveRequest( + projectId = nodeDetail.projectId, + repoName = nodeDetail.repoName, + fullPath = nodeDetail.fullPath, + nodeMetadata = metadataHeader.map { MetadataModel(it, HeaderUtils.getHeader(it).toString()) }, + replace = true + ) + metadataClient.saveMetadata(saveRequest) + return nodeDetail.copy(nodeMetadata = saveRequest.nodeMetadata!!) + } + return nodeDetail + } companion object { private val logger = LoggerFactory.getLogger(S3ObjectService::class.java) diff --git a/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/utils/TimeUtil.kt b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/utils/TimeUtil.kt new file mode 100644 index 0000000000..39614bbd91 --- /dev/null +++ b/src/backend/s3/biz-s3/src/main/kotlin/com/tencent/bkrepo/s3/utils/TimeUtil.kt @@ -0,0 +1,52 @@ +/* + * 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.bkrepo.s3.utils + +import com.tencent.bkrepo.repository.pojo.node.NodeDetail +import com.tencent.bkrepo.s3.constant.S3HttpHeaders.X_AMZ_META_MTIME +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter + +object TimeUtil { + + fun getLastModified(nodeDetail: NodeDetail): String { + val mTime = nodeDetail.metadata[X_AMZ_META_MTIME]?.toString() + ?: nodeDetail.metadata[X_AMZ_META_MTIME.toLowerCase()]?.toString() + return if (mTime.isNullOrEmpty()) { + LocalDateTime.parse(nodeDetail.lastModifiedDate) + .atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneId.of("GMT")) + .format(DateTimeFormatter.RFC_1123_DATE_TIME) + } else { + val instant = Instant.ofEpochSecond(mTime.toDouble().toLong()) + ZonedDateTime.ofInstant(instant, ZoneId.of("GMT")).format(DateTimeFormatter.RFC_1123_DATE_TIME) + } + } +} diff --git a/src/gateway/vhosts/bkrepo.backend.conf b/src/gateway/vhosts/bkrepo.backend.conf index 1513091299..3edbb9cb06 100644 --- a/src/gateway/vhosts/bkrepo.backend.conf +++ b/src/gateway/vhosts/bkrepo.backend.conf @@ -6,6 +6,7 @@ access_by_lua_file 'conf/lua/router_srv.lua'; + proxy_set_header HOST $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;