diff --git a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/enums/ScmCode.kt b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/enums/ScmCode.kt new file mode 100644 index 000000000000..b11db310a546 --- /dev/null +++ b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/enums/ScmCode.kt @@ -0,0 +1,45 @@ +/* + * 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.devops.common.api.enums + +/** + * 代码库类型 + */ +enum class ScmCode(val scmName: String, val value: String) { + TGIT("GIT", "TGIT"), // 内部工蜂 + GITHUB("GITHUB", "GITHUB"), // github + TGIT_CO("TGIT_CO", "TGIT-CO"); // github + + fun convertScmType(): ScmType { + return when (this) { + TGIT -> ScmType.CODE_GIT + GITHUB -> ScmType.GITHUB + TGIT_CO -> ScmType.CODE_TGIT + } + } +} diff --git a/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/ApigwRepositoryOauthResourceV4.kt b/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/ApigwRepositoryOauthResourceV4.kt new file mode 100644 index 000000000000..af16ca2cd198 --- /dev/null +++ b/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/ApigwRepositoryOauthResourceV4.kt @@ -0,0 +1,72 @@ +/* + * 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.devops.openapi.api.apigw.v4 + +import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_APP_CODE +import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_APP_CODE_DEFAULT_VALUE +import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_USER_ID +import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_USER_ID_DEFAULT_VALUE +import com.tencent.devops.common.api.pojo.Result +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.tags.Tag +import javax.ws.rs.Consumes +import javax.ws.rs.GET +import javax.ws.rs.HeaderParam +import javax.ws.rs.Path +import javax.ws.rs.PathParam +import javax.ws.rs.Produces +import javax.ws.rs.QueryParam +import javax.ws.rs.core.MediaType + +@Tag(name = "OPEN_API_REPOSITORY_V4", description = "OPEN-API-代码库OAUTH授权") +@Path("/{apigwType:apigw-user|apigw-app|apigw}/v4/repositories/oauth") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@SuppressWarnings("All") +interface ApigwRepositoryOauthResourceV4 { + @Operation( + summary = "校验用户是否已经OAUTH授权", + tags = ["v4_app_oauth_isOauth", "v4_user_oauth_isOauth"] + ) + @GET + @Path("/isOauth") + fun isOauth( + @Parameter(description = "appCode", required = true, example = AUTH_HEADER_DEVOPS_APP_CODE_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_DEVOPS_APP_CODE) + appCode: String?, + @Parameter(description = "apigw Type", required = true) + @PathParam("apigwType") + apigwType: String?, + @Parameter(description = "用户ID", required = true, example = AUTH_HEADER_DEVOPS_USER_ID_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_DEVOPS_USER_ID) + userId: String, + @Parameter(description = "代码库类型", required = true) + @QueryParam("scmCode") + scmCode: String + ): Result +} diff --git a/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/ApigwOauthResourceV4Impl.kt b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/ApigwOauthResourceV4Impl.kt new file mode 100644 index 000000000000..77b7f636ada5 --- /dev/null +++ b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/ApigwOauthResourceV4Impl.kt @@ -0,0 +1,73 @@ +/* + * 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.devops.openapi.resources.apigw.v4 + +import com.tencent.devops.common.api.enums.ScmCode +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.client.Client +import com.tencent.devops.common.web.RestResource +import com.tencent.devops.openapi.api.apigw.v4.ApigwRepositoryOauthResourceV4 +import com.tencent.devops.repository.api.ServiceOauthResource +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired + +@RestResource +class ApigwOauthResourceV4Impl @Autowired constructor(private val client: Client) : ApigwRepositoryOauthResourceV4 { + override fun isOauth( + appCode: String?, + apigwType: String?, + userId: String, + scmCode: String + ): Result { + logger.info("OPENAPI_OAUTH_V4|$userId|verify if $scmCode oauth authorization has been performed") + val result = when (scmCode) { + ScmCode.TGIT.name -> { + client.get(ServiceOauthResource::class).isOAuth( + userId = userId, + redirectUrl = null, + redirectUrlType = null + ).data?.status + } + + ScmCode.GITHUB.name -> { + client.get(ServiceOauthResource::class).githubOAuth( + userId = userId + ).data?.status + } + + else -> { + null + } + } + return Result(result == AUTHORIZED_STATUS) + } + + companion object { + private val logger = LoggerFactory.getLogger(ApigwOauthResourceV4Impl::class.java) + private const val AUTHORIZED_STATUS = 200 + } +} diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceGithubResource.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceGithubResource.kt index 6a287bde3b27..94de8eebe03f 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceGithubResource.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceGithubResource.kt @@ -69,7 +69,10 @@ interface ServiceGithubResource { tokenType: String, @Parameter(description = "accessToken范围", required = true) @QueryParam("scope") - scope: String + scope: String, + @Parameter(description = "蓝盾平台操作人", required = false) + @QueryParam("operator") + operator: String? ): Result @Operation(summary = "获取github代码库accessToken") diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceOauthResource.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceOauthResource.kt index dfd44d69a8d3..fff5b5240b80 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceOauthResource.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceOauthResource.kt @@ -123,4 +123,13 @@ interface ServiceOauthResource { @QueryParam("refreshToken") refreshToken: Boolean? = false ): Result + + @Operation(summary = "根据用户ID判断用户是否已经github oauth认证") + @GET + @Path("/github_oauth") + fun githubOAuth( + @Parameter(description = "用户ID", required = true, example = AUTH_HEADER_USER_ID_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String + ): Result } diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/UserOauthResource.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/UserOauthResource.kt new file mode 100644 index 000000000000..eb7280c11ab6 --- /dev/null +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/UserOauthResource.kt @@ -0,0 +1,108 @@ +/* + * 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.devops.repository.api + +import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID +import com.tencent.devops.common.api.pojo.Page +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.repository.pojo.OauthResetUrl +import com.tencent.devops.repository.pojo.RepoOauthRefVo +import com.tencent.devops.repository.pojo.UserOauthRepositoryInfo +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.tags.Tag +import javax.ws.rs.Consumes +import javax.ws.rs.DELETE +import javax.ws.rs.GET +import javax.ws.rs.HeaderParam +import javax.ws.rs.POST +import javax.ws.rs.Path +import javax.ws.rs.Produces +import javax.ws.rs.QueryParam +import javax.ws.rs.core.MediaType + +@Tag(name = "AUTH_RESOURCE", description = "用户态-iam资源映射") +@Path("/user/repositories/oauth/") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +interface UserOauthResource { + @GET + @Path("/") + @Operation(summary = "获取用户OAuth授权列表") + fun list( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String + ): Result> + + @GET + @Path("/relSource") + @Operation(summary = "获取授权关联的资源列表") + fun relSource( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "授权类型", required = true) + @QueryParam("scmCode") + scmCode: String, + @Parameter(description = "第几页", required = false, example = "1") + @QueryParam("page") + page: Int? = null, + @Parameter(description = "每页多少条", required = false, example = "20") + @QueryParam("pageSize") + pageSize: Int? = null + ): Result> + + @DELETE + @Path("/delete") + @Operation(summary = "删除oauth授权") + fun delete( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "授权类型", required = true) + @QueryParam("scmCode") + scmCode: String + ): Result + + @POST + @Path("/reset") + @Operation(summary = "重置授权") + fun reset( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "授权类型", required = true) + @QueryParam("scmCode") + scmCode: String, + @Parameter(description = "回调链接(授权完以后的链接地址)", required = true) + @QueryParam("redirectUrl") + redirectUrl: String + ): Result +} diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/github/ServiceGithubUserResource.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/github/ServiceGithubUserResource.kt index a34084593e0d..da2e959434a3 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/github/ServiceGithubUserResource.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/github/ServiceGithubUserResource.kt @@ -47,7 +47,7 @@ import javax.ws.rs.core.MediaType @Consumes(MediaType.APPLICATION_JSON) interface ServiceGithubUserResource { - @Operation(summary = "创建或者更新文件内容") + @Operation(summary = "获取用户信息") @GET @Path("/getUser") fun getUser( diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/constant/RepositoryMessageCode.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/constant/RepositoryMessageCode.kt index db1e4de5273b..35425ce90596 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/constant/RepositoryMessageCode.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/constant/RepositoryMessageCode.kt @@ -94,6 +94,7 @@ object RepositoryMessageCode { const val ERROR_USER_HAVE_NOT_DOWNLOAD_PEM = "2115043" // 用户({0})无({1})项目下载权限 const val NOT_GITHUB_AUTHORIZED_BY_OAUTH = "2115044" // 用户[{0}]尚未进行GITHUB OAUTH授权,请先授权。 const val REPOSITORY_NO_SUPPORT_OAUTH = "2115045" // ({0})类型代码库暂不支持OAUTH授权 + const val OAUTH_INFO_OCCUPIED_CANNOT_DELETE = "2115049" // OAUTH授权信息被占用,无法删除 const val USER_NOT_PERMISSIONS_OPERATE_REPOSITORY = "2115046" // 用户({0})无权限在工程({1})下{2}流水线{3} const val FAIL_TO_GET_OPEN_COPILOT_TOKEN = "2115048" // 获取open copilot token 失败, 失败详情: {0} diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/CodeGitRepository.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/CodeGitRepository.kt index 5490ee419cbf..4eeafb6eff6b 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/CodeGitRepository.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/CodeGitRepository.kt @@ -61,6 +61,8 @@ data class CodeGitRepository( ) : Repository { companion object { const val classType = "codeGit" + // 内部工蜂 + const val SCM_CODE = "TGIT" } override fun getStartPrefix(): String { diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/GithubRepository.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/GithubRepository.kt index b2d86a038da8..e8cf984b1c48 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/GithubRepository.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/GithubRepository.kt @@ -56,6 +56,7 @@ data class GithubRepository( ) : Repository { companion object { const val classType = "github" + const val SCM_CODE = "GITHUB" } override fun getStartPrefix() = "https://github.com/" diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/OauthResetUrl.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/OauthResetUrl.kt new file mode 100644 index 000000000000..b2f39bb691ed --- /dev/null +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/OauthResetUrl.kt @@ -0,0 +1,8 @@ +package com.tencent.devops.repository.pojo + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "重置Oauth授权信息") +data class OauthResetUrl( + val url: String +) diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/RepoOauthRefVo.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/RepoOauthRefVo.kt new file mode 100644 index 000000000000..3a778e518198 --- /dev/null +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/RepoOauthRefVo.kt @@ -0,0 +1,15 @@ +package com.tencent.devops.repository.pojo + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "用户关联的仓库") +data class RepoOauthRefVo( + @get:Schema(title = "仓库别名", required = true) + val aliasName: String, + @get:Schema(title = "仓库源URL", required = true) + val url: String, + @get:Schema(title = "蓝盾项目ID", required = true) + val projectId: String, + @get:Schema(title = "代码库HashId", required = true) + val hashId: String +) diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/UserOauthRepositoryInfo.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/UserOauthRepositoryInfo.kt new file mode 100644 index 000000000000..465a2acf2988 --- /dev/null +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/UserOauthRepositoryInfo.kt @@ -0,0 +1,26 @@ +package com.tencent.devops.repository.pojo + +import com.tencent.devops.common.api.enums.ScmType +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "用户代码库Oauth授权信息") +data class UserOauthRepositoryInfo( + @get:Schema(title = "授权账号") + val username: String, + @get:Schema(title = "授权代码库数量") + val repoCount: Long, + @get:Schema(title = "创建时间") + val createTime: Long? = null, + @get:Schema(title = "授权类型") + val scmCode: String, + @get:Schema(title = "是否过期") + val expired: Boolean = false, + @get:Schema(title = "是否已授权") + val authorized: Boolean = true, + @get:Schema(title = "操作人") + val operator: String, + @get:Schema(title = "操作人") + val scmType: ScmType, + @get:Schema(title = "代码库类型") + val name: String +) diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/github/GithubToken.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/github/GithubToken.kt index 013d7f3d900c..b73662790c03 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/github/GithubToken.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/github/GithubToken.kt @@ -39,5 +39,11 @@ data class GithubToken( @get:Schema(title = "token类型", description = "token_type") val tokenType: String, @get:Schema(title = "范围") - val scope: String + val scope: String, + @get:Schema(title = "创建时间(时间戳)") + val createTime: Long, + @get:Schema(title = "用户名 (github server端的用户名)") + val userId: String? = "", + @get:Schema(title = "操作者 (蓝盾平台用户名)") + val operator: String? = "" ) diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/GitToken.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/GitToken.kt index 05e4a34432f2..90d0097eb2ff 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/GitToken.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/GitToken.kt @@ -45,5 +45,9 @@ data class GitToken( @JsonProperty("expires_in") val expiresIn: Long = 0L, @get:Schema(title = "创建时间") - val createTime: Long? = 0L + val createTime: Long? = 0L, + @get:Schema(title = "更新时间") + val updateTime: Long? = 0L, + @get:Schema(title = "操作人") + var operator: String? = "" ) diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/Oauth2AccessToken.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/Oauth2AccessToken.kt new file mode 100644 index 000000000000..fdce6d41bf22 --- /dev/null +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/Oauth2AccessToken.kt @@ -0,0 +1,42 @@ +/* + * 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.devops.repository.pojo.oauth + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "Oauth2 Access Token") +data class Oauth2AccessToken( + val accessToken: String, + val tokenType: String, + val expiresIn: Long? = 0L, + val refreshToken: String? = null, + val createTime: Long? = 0L, + @get:Schema(title = "server端用户名") + val userId: String? = null, + @get:Schema(title = "操作者") + val operator: String? = null +) \ No newline at end of file diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GitTokenDao.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GitTokenDao.kt index 2376f6f7ae6a..e5939070e25f 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GitTokenDao.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GitTokenDao.kt @@ -45,6 +45,7 @@ class GitTokenDao { } fun saveAccessToken(dslContext: DSLContext, userId: String, token: GitToken): Int { + val now = LocalDateTime.now() with(TRepositoryGitToken.T_REPOSITORY_GIT_TOKEN) { return dslContext.insertInto( this, @@ -53,7 +54,9 @@ class GitTokenDao { REFRESH_TOKEN, TOKEN_TYPE, EXPIRES_IN, - CREATE_TIME + CREATE_TIME, + UPDATE_TIME, + OPERATOR ) .values( userId, @@ -61,14 +64,23 @@ class GitTokenDao { token.refreshToken, token.tokenType, token.expiresIn, - LocalDateTime.now() + now, + now, + token.operator ?: userId ) .onDuplicateKeyUpdate() .set(ACCESS_TOKEN, token.accessToken) .set(REFRESH_TOKEN, token.refreshToken) .set(TOKEN_TYPE, token.tokenType) .set(EXPIRES_IN, token.expiresIn) - .set(CREATE_TIME, LocalDateTime.now()) + .set(UPDATE_TIME, now) + .let { + // 避免空值覆盖操作人 + if (!token.operator.isNullOrBlank()) { + it.set(OPERATOR, token.operator) + } + it + } .execute() } } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GithubTokenDao.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GithubTokenDao.kt index e016c2c5f778..434842dfd84e 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GithubTokenDao.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GithubTokenDao.kt @@ -42,7 +42,8 @@ class GithubTokenDao { accessToken: String, tokenType: String, scope: String, - githubTokenType: GithubTokenType = GithubTokenType.GITHUB_APP + githubTokenType: GithubTokenType = GithubTokenType.GITHUB_APP, + operator: String ) { val now = LocalDateTime.now() with(TRepositoryGithubToken.T_REPOSITORY_GITHUB_TOKEN) { @@ -54,7 +55,8 @@ class GithubTokenDao { SCOPE, CREATE_TIME, UPDATE_TIME, - TYPE + TYPE, + OPERATOR ).values( userId, accessToken, @@ -62,7 +64,8 @@ class GithubTokenDao { scope, now, now, - githubTokenType.name + githubTokenType.name, + operator ).execute() } } @@ -73,13 +76,20 @@ class GithubTokenDao { accessToken: String, tokenType: String, scope: String, - githubTokenType: GithubTokenType = GithubTokenType.GITHUB_APP + githubTokenType: GithubTokenType = GithubTokenType.GITHUB_APP, + operator: String ) { with(TRepositoryGithubToken.T_REPOSITORY_GITHUB_TOKEN) { dslContext.update(this) .set(TOKEN_TYPE, tokenType) .set(ACCESS_TOKEN, accessToken) .set(SCOPE, scope) + .let { + if (operator.isNotBlank()) { + it.set(OPERATOR, operator) + } + it + } .where(USER_ID.eq(userId)).and(TYPE.eq(githubTokenType.name)) .execute() } @@ -91,7 +101,11 @@ class GithubTokenDao { githubTokenType: GithubTokenType? ): TRepositoryGithubTokenRecord? { with(TRepositoryGithubToken.T_REPOSITORY_GITHUB_TOKEN) { - return dslContext.selectFrom(this).where(USER_ID.eq(userId)) + return getByOperator( + dslContext = dslContext, + operator = userId, + githubTokenType = githubTokenType + ) ?: dslContext.selectFrom(this).where(USER_ID.eq(userId)) .let { if (githubTokenType != null) { it.and(TYPE.eq(githubTokenType.name)) @@ -101,12 +115,35 @@ class GithubTokenDao { } } + fun getByOperator( + dslContext: DSLContext, + operator: String, + githubTokenType: GithubTokenType? + ): TRepositoryGithubTokenRecord? { + with(TRepositoryGithubToken.T_REPOSITORY_GITHUB_TOKEN) { + return dslContext.selectFrom(this) + .where(OPERATOR.eq(operator)) + .let { + if (githubTokenType != null) { + it.and(TYPE.eq(githubTokenType.name)) + } else it + } + .orderBy(CREATE_TIME.desc()) + .fetch() + .firstOrNull() + } + } + + /** + * 删除token + * @param operator 蓝盾平台操作人用户名 + */ fun delete( dslContext: DSLContext, - userId: String + operator: String ) { with(TRepositoryGithubToken.T_REPOSITORY_GITHUB_TOKEN) { - dslContext.deleteFrom(this).where(USER_ID.eq(userId)) + dslContext.deleteFrom(this).where(OPERATOR.eq(operator)) } } } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryCodeGitDao.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryCodeGitDao.kt index 58cae5fe1be7..ab1071111e91 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryCodeGitDao.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryCodeGitDao.kt @@ -27,8 +27,11 @@ package com.tencent.devops.repository.dao +import com.tencent.devops.common.api.enums.ScmType +import com.tencent.devops.model.repository.tables.TRepository import com.tencent.devops.model.repository.tables.TRepositoryCodeGit import com.tencent.devops.model.repository.tables.records.TRepositoryCodeGitRecord +import com.tencent.devops.repository.pojo.RepoOauthRefVo import com.tencent.devops.repository.pojo.UpdateRepositoryInfoRequest import com.tencent.devops.repository.pojo.enums.RepoAuthType import org.jooq.DSLContext @@ -188,4 +191,58 @@ class RepositoryCodeGitDao { .fetch() } } + + fun countOauthRepo( + dslContext: DSLContext, + userId: String + ): Long { + val t1 = TRepository.T_REPOSITORY + val t2 = TRepositoryCodeGit.T_REPOSITORY_CODE_GIT + return dslContext.selectCount() + .from(t1) + .leftJoin(t2) + .on(t1.REPOSITORY_ID.eq(t2.REPOSITORY_ID)) + .where( + listOf( + t1.IS_DELETED.eq(false), + t1.TYPE.eq(ScmType.CODE_GIT.name), + t2.AUTH_TYPE.eq(RepoAuthType.OAUTH.name), + t2.USER_NAME.eq(userId) + ) + ) + .fetchOne(0, Long::class.java)!! + } + + fun listOauthRepo( + dslContext: DSLContext, + userId: String, + limit: Int, + offset: Int + ): List { + val t1 = TRepository.T_REPOSITORY + val t2 = TRepositoryCodeGit.T_REPOSITORY_CODE_GIT + return dslContext.select(t1.ALIAS_NAME, t1.URL, t1.PROJECT_ID, t1.REPOSITORY_HASH_ID) + .from(t1) + .leftJoin(t2) + .on(t1.REPOSITORY_ID.eq(t2.REPOSITORY_ID)) + .where( + listOf( + t1.IS_DELETED.eq(false), + t1.TYPE.eq(ScmType.CODE_GIT.name), + t2.AUTH_TYPE.eq(RepoAuthType.OAUTH.name), + t2.USER_NAME.eq(userId) + ) + ) + .limit(limit) + .offset(offset) + .fetch() + .map { + RepoOauthRefVo( + aliasName = it.get(0).toString(), + url = it.get(1).toString(), + projectId = it.get(2).toString(), + hashId = it.get(3).toString() + ) + } + } } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryGithubDao.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryGithubDao.kt index 015e79bb4be4..034d3a7522f4 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryGithubDao.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryGithubDao.kt @@ -27,9 +27,12 @@ package com.tencent.devops.repository.dao +import com.tencent.devops.common.api.enums.ScmType +import com.tencent.devops.model.repository.tables.TRepository import com.tencent.devops.model.repository.tables.TRepositoryCodeGitlab import com.tencent.devops.model.repository.tables.TRepositoryGithub import com.tencent.devops.model.repository.tables.records.TRepositoryGithubRecord +import com.tencent.devops.repository.pojo.RepoOauthRefVo import com.tencent.devops.repository.pojo.UpdateRepositoryInfoRequest import org.jooq.DSLContext import org.jooq.Result @@ -162,4 +165,56 @@ class RepositoryGithubDao { .execute() } } + + fun countOauthRepo( + dslContext: DSLContext, + userId: String + ): Long { + val t1 = TRepository.T_REPOSITORY + val t2 = TRepositoryGithub.T_REPOSITORY_GITHUB + return dslContext.selectCount() + .from(t1) + .leftJoin(t2) + .on(t1.REPOSITORY_ID.eq(t2.REPOSITORY_ID)) + .where( + listOf( + t1.IS_DELETED.eq(false), + t1.TYPE.eq(ScmType.GITHUB.name), + t2.USER_NAME.eq(userId) + ) + ) + .fetchOne(0, Long::class.java)!! + } + + fun listOauthRepo( + dslContext: DSLContext, + userId: String, + limit: Int, + offset: Int + ): List { + val t1 = TRepository.T_REPOSITORY + val t2 = TRepositoryGithub.T_REPOSITORY_GITHUB + return dslContext.select(t1.ALIAS_NAME, t1.URL, t1.PROJECT_ID, t1.REPOSITORY_HASH_ID) + .from(t1) + .leftJoin(t2) + .on(t1.REPOSITORY_ID.eq(t2.REPOSITORY_ID)) + .where( + listOf( + t1.IS_DELETED.eq(false), + t1.TYPE.eq(ScmType.GITHUB.name), + t2.USER_NAME.eq(userId) + ) + ) + .limit(limit) + .offset(offset) + .fetch() + .map { + RepoOauthRefVo( + aliasName = it.get(0).toString(), + url = it.get(1).toString(), + projectId = it.get(2).toString(), + hashId = it.get(3).toString() + ) + } + } } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceGithubResourceImpl.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceGithubResourceImpl.kt index 0c8beaf07399..bd0f4251c18f 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceGithubResourceImpl.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceGithubResourceImpl.kt @@ -52,9 +52,10 @@ class ServiceGithubResourceImpl @Autowired constructor( userId: String, accessToken: String, tokenType: String, - scope: String + scope: String, + operator: String? ): Result { - githubTokenService.createAccessToken(userId, accessToken, tokenType, scope) + githubTokenService.createAccessToken(userId, accessToken, tokenType, scope, operator = operator ?: userId) return Result(true) } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceOauthResourceImpl.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceOauthResourceImpl.kt index 6b6b08fa600e..a87ef8deb281 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceOauthResourceImpl.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceOauthResourceImpl.kt @@ -34,6 +34,7 @@ import com.tencent.devops.repository.pojo.AuthorizeResult import com.tencent.devops.repository.pojo.enums.RedirectUrlTypeEnum import com.tencent.devops.repository.pojo.oauth.GitOauthCallback import com.tencent.devops.repository.pojo.oauth.GitToken +import com.tencent.devops.repository.service.github.IGithubService import com.tencent.devops.repository.service.scm.IGitOauthService import com.tencent.devops.repository.service.tgit.TGitOAuthService import org.springframework.beans.factory.annotation.Autowired @@ -41,7 +42,8 @@ import org.springframework.beans.factory.annotation.Autowired @RestResource class ServiceOauthResourceImpl @Autowired constructor( private val gitOauthService: IGitOauthService, - private val tGitOAuthService: TGitOAuthService + private val tGitOAuthService: TGitOAuthService, + private val githubService: IGithubService ) : ServiceOauthResource { override fun gitGet(userId: String): Result { return Result(gitOauthService.getAccessToken(userId)) @@ -90,4 +92,19 @@ class ServiceOauthResourceImpl @Autowired constructor( ) ) } + + override fun githubOAuth( + userId: String + ): Result { + return Result( + githubService.isOAuth( + userId = userId, + redirectUrl = "", + redirectUrlType = null, + projectId = "", + resetType = "", + refreshToken = false + ) + ) + } } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/UserOauthResourceImpl.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/UserOauthResourceImpl.kt new file mode 100644 index 000000000000..88339cdc5049 --- /dev/null +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/UserOauthResourceImpl.kt @@ -0,0 +1,63 @@ +package com.tencent.devops.repository.resources + +import com.tencent.devops.common.api.enums.ScmCode +import com.tencent.devops.repository.api.UserOauthResource +import com.tencent.devops.repository.pojo.OauthResetUrl +import com.tencent.devops.repository.pojo.UserOauthRepositoryInfo +import com.tencent.devops.common.api.pojo.Page +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.web.RestResource +import com.tencent.devops.repository.pojo.RepoOauthRefVo +import com.tencent.devops.repository.service.OauthRepositoryService +import org.springframework.beans.factory.annotation.Autowired + +@RestResource +class UserOauthResourceImpl @Autowired constructor( + val oauthRepositoryService: OauthRepositoryService +) : UserOauthResource { + override fun list(userId: String): Result> { + return Result(oauthRepositoryService.list(userId)) + } + + override fun relSource( + userId: String, + scmCode: String, + page: Int?, + pageSize: Int? + ): Result> { + val targetPage = page ?: 1 + val targetPageSize = pageSize ?: 20 + val resources = oauthRepositoryService.relSource( + userId = userId, + scmCode = ScmCode.valueOf(scmCode), + page = targetPage, + pageSize = targetPageSize + ) + return Result(resources) + } + + override fun delete( + userId: String, + scmCode: String + ): Result { + oauthRepositoryService.delete( + userId = userId, + scmCode = ScmCode.valueOf(scmCode) + ) + return Result(true) + } + + override fun reset( + userId: String, + scmCode: String, + redirectUrl: String + ): Result { + return Result( + oauthRepositoryService.reset( + userId = userId, + scmCode = ScmCode.valueOf(scmCode), + redirectUrl = redirectUrl + ) + ) + } +} diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/OauthRepositoryService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/OauthRepositoryService.kt new file mode 100644 index 000000000000..78b2b247be71 --- /dev/null +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/OauthRepositoryService.kt @@ -0,0 +1,135 @@ +package com.tencent.devops.repository.service + +import com.tencent.devops.repository.pojo.OauthResetUrl +import com.tencent.devops.repository.pojo.UserOauthRepositoryInfo +import com.tencent.devops.common.api.enums.ScmCode +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.api.pojo.Page +import com.tencent.devops.common.api.util.PageUtil +import com.tencent.devops.repository.constant.RepositoryMessageCode +import com.tencent.devops.repository.pojo.RepoOauthRefVo +import com.tencent.devops.repository.pojo.enums.RedirectUrlTypeEnum +import com.tencent.devops.repository.service.github.GithubOAuthService +import com.tencent.devops.repository.service.oauth2.Oauth2TokenStoreManager +import com.tencent.devops.repository.service.scm.IGitOauthService +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Service + +/** + * OAUTH授权代码库服务类 + */ +@Service +class OauthRepositoryService @Autowired constructor( + val oauth2TokenStoreManager: Oauth2TokenStoreManager, + val gitOauthService: IGitOauthService, + val githubOAuthService: GithubOAuthService, + val repositoryService: RepositoryService +) { + fun list(userId: String): List { + val list = mutableListOf() + listOf( + ScmCode.TGIT, + ScmCode.GITHUB + ).forEach { scmCode -> + val userOauthInfo = oauth2TokenStoreManager.get( + userId = userId, + scmCode = scmCode.name + )?.let { + UserOauthRepositoryInfo( + username = it.userId ?: "", + repoCount = repositoryService.countOauthRepo( + userId = userId, + scmType = scmCode.convertScmType() + ), + scmCode = scmCode.value, + scmType = scmCode.convertScmType(), + name = scmCode.scmName, + operator = it.operator ?: "", + createTime = it.createTime, + expired = it.expiresIn?.let { expiresIn -> + (it.createTime ?: 0L) + expiresIn * 1000 <= System.currentTimeMillis() + } ?: false, + authorized = true + ) + } ?: UserOauthRepositoryInfo( + username = userId, + repoCount = 0L, + scmCode = scmCode.value, + expired = true, + authorized = false, + scmType = scmCode.convertScmType(), + name = scmCode.scmName, + operator = userId + ) + list.add(userOauthInfo) + } + return list + } + + fun relSource( + userId: String, + scmCode: ScmCode, + page: Int, + pageSize: Int + ): Page { + val limit = PageUtil.convertPageSizeToSQLLimit(page, pageSize) + val result = repositoryService.listOauthRepo( + userId = userId, + scmType = scmCode.convertScmType(), + limit = limit.limit, + offset = limit.offset + ) + return Page(page, pageSize, result.count, result.records) + } + + fun delete( + userId: String, + scmCode: ScmCode + ) { + // 检查是否还有关联代码库 + if (countOauthRepo(userId, scmCode) > 0) { + throw ErrorCodeException( + errorCode = RepositoryMessageCode.OAUTH_INFO_OCCUPIED_CANNOT_DELETE + ) + } + oauth2TokenStoreManager.delete(userId = userId, scmCode = scmCode.name) + } + + fun countOauthRepo(userId: String, scmCode: ScmCode) = repositoryService.countOauthRepo( + userId = userId, + scmType = scmCode.convertScmType() + ) + + fun reset( + userId: String, + scmCode: ScmCode, + redirectUrl: String + ): OauthResetUrl { + val url = when (scmCode) { + ScmCode.TGIT -> { + gitOauthService.getOauthUrl( + userId = userId, + redirectUrl = redirectUrl + ) + } + + ScmCode.GITHUB -> { + githubOAuthService.getGithubOauth( + userId = userId, + projectId = "", + redirectUrlTypeEnum = RedirectUrlTypeEnum.SPEC, + specRedirectUrl = redirectUrl, + repoHashId = null + ).redirectUrl + } + + else -> "" + } + return OauthResetUrl(url) + } + + companion object { + val logger = LoggerFactory.getLogger(OauthRepositoryService::class.java) + } +} diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryService.kt index a273036b64f2..c7d4fc8a3fc9 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryService.kt @@ -67,10 +67,12 @@ import com.tencent.devops.repository.constant.RepositoryMessageCode.REPOSITORY_N import com.tencent.devops.repository.constant.RepositoryMessageCode.USER_CREATE_PEM_ERROR import com.tencent.devops.repository.dao.RepositoryCodeGitDao import com.tencent.devops.repository.dao.RepositoryDao +import com.tencent.devops.repository.dao.RepositoryGithubDao import com.tencent.devops.repository.pojo.AtomRefRepositoryInfo import com.tencent.devops.repository.pojo.AuthorizeResult import com.tencent.devops.repository.pojo.CodeGitRepository import com.tencent.devops.repository.pojo.GithubRepository +import com.tencent.devops.repository.pojo.RepoOauthRefVo import com.tencent.devops.repository.pojo.RepoRename import com.tencent.devops.repository.pojo.Repository import com.tencent.devops.repository.pojo.RepositoryDetailInfo @@ -117,7 +119,8 @@ class RepositoryService @Autowired constructor( private val dslContext: DSLContext, private val repositoryPermissionService: RepositoryPermissionService, private val githubService: IGithubService, - private val client: Client + private val client: Client, + private val repositoryGithubDao: RepositoryGithubDao ) { @Value("\${repository.git.devopsPrivateToken}") @@ -1560,6 +1563,62 @@ class RepositoryService @Autowired constructor( ) } + fun listOauthRepo( + userId: String, + scmType: ScmType, + limit: Int, + offset: Int + ): SQLPage { + val list = when (scmType) { + ScmType.CODE_GIT -> { + repositoryCodeGitDao.listOauthRepo( + dslContext = dslContext, + userId = userId, + limit = limit, + offset = offset + ) + } + + ScmType.GITHUB -> { + repositoryGithubDao.listOauthRepo( + dslContext = dslContext, + userId = userId, + limit = limit, + offset = offset + ) + } + + else -> { + listOf() + } + } + val count = countOauthRepo(userId, scmType) + return SQLPage(count, list) + } + + fun countOauthRepo( + userId: String, + scmType: ScmType + ): Long { + return when (scmType) { + ScmType.CODE_GIT -> { + repositoryCodeGitDao.countOauthRepo( + dslContext = dslContext, + userId = userId + ) + } + + ScmType.GITHUB -> { + repositoryGithubDao.countOauthRepo( + dslContext = dslContext, + userId = userId + ) + } + + else -> 0L + } + } + companion object { private val logger = LoggerFactory.getLogger(RepositoryService::class.java) const val MAX_ALIAS_LENGTH = 255 diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubOAuthService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubOAuthService.kt index c9a94ddfa308..705f157955f5 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubOAuthService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubOAuthService.kt @@ -41,6 +41,7 @@ import com.tencent.devops.repository.pojo.github.GithubOauth import com.tencent.devops.repository.pojo.github.GithubOauthCallback import com.tencent.devops.repository.pojo.github.GithubToken import com.tencent.devops.repository.pojo.oauth.GithubTokenType +import com.tencent.devops.repository.sdk.github.response.GetUserResponse import com.tencent.devops.repository.sdk.github.service.GithubUserService import com.tencent.devops.repository.service.ScmUrlProxyService import com.tencent.devops.scm.config.GitConfig @@ -151,12 +152,15 @@ class GithubOAuthService @Autowired constructor( val specRedirectUrl = arrays.getOrNull(6) ?: "" // 重定向类型 val redirectUrlTypeEnum = RedirectUrlTypeEnum.getRedirectUrlType(arrays.getOrNull(7) ?: "") + // 获取授权server端用户信息(蓝盾平台用户名可能跟github用户名不一致) + val userResponse = getUser(githubToken.accessToken) githubTokenService.createAccessToken( - userId = userId, + userId = userResponse.login, accessToken = githubToken.accessToken, tokenType = githubToken.tokenType, scope = githubToken.scope, - githubTokenType = githubTokenType + githubTokenType = githubTokenType, + operator = userId ) return GithubOauthCallback( userId = userId, @@ -179,11 +183,12 @@ class GithubOAuthService @Autowired constructor( val userResponse = githubUserService.getUser(githubToken.accessToken) val stateMap = kotlin.runCatching { JsonUtil.toMap(state ?: "{}") }.getOrDefault(emptyMap()) githubTokenService.createAccessToken( - userId = stateMap["userId"]?.toString() ?: userResponse.login, + userId = userResponse.login, accessToken = githubToken.accessToken, tokenType = githubToken.tokenType, scope = githubToken.scope, - githubTokenType = githubTokenType + githubTokenType = githubTokenType, + operator = stateMap["userId"]?.toString() ?: userResponse.login ) return GithubOauthCallback( userId = userResponse.login, @@ -227,6 +232,10 @@ class GithubOAuthService @Autowired constructor( } } + fun getUser(accessToken: String): GetUserResponse { + return githubUserService.getUser(accessToken) + } + companion object { private val logger = LoggerFactory.getLogger(GithubOAuthService::class.java) private const val RANDOM_ALPHA_NUM = 8 diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubService.kt index 0d7d662b5010..b304b719e5dd 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubService.kt @@ -53,6 +53,7 @@ import com.tencent.devops.repository.constant.RepositoryMessageCode.OPERATION_UP import com.tencent.devops.repository.pojo.AuthorizeResult import com.tencent.devops.repository.pojo.GithubCheckRuns import com.tencent.devops.repository.pojo.GithubCheckRunsResponse +import com.tencent.devops.repository.pojo.enums.RedirectUrlTypeEnum import com.tencent.devops.repository.pojo.github.GithubBranch import com.tencent.devops.repository.pojo.github.GithubRepo import com.tencent.devops.repository.pojo.github.GithubRepoBranch @@ -406,7 +407,9 @@ class GithubService @Autowired constructor( userId: String, projectId: String, refreshToken: Boolean?, - resetType: String? + resetType: String?, + redirectUrlType: RedirectUrlTypeEnum?, + redirectUrl: String? ): AuthorizeResult { logger.info("isOAuth userId is: $userId,refreshToken is: $refreshToken") val accessToken = if (refreshToken == true) { @@ -420,7 +423,9 @@ class GithubService @Autowired constructor( userId = userId, repoHashId = null, popupTag = "", - resetType = resetType + resetType = resetType, + specRedirectUrl = redirectUrl, + redirectUrlTypeEnum = redirectUrlType ).redirectUrl ) // 校验token是否有效 @@ -434,7 +439,9 @@ class GithubService @Autowired constructor( userId = userId, repoHashId = null, popupTag = "", - resetType = resetType + resetType = resetType, + specRedirectUrl = redirectUrl, + redirectUrlTypeEnum = redirectUrlType ).redirectUrl ) } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubTokenService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubTokenService.kt index 6a20fdff16bc..9dd4c33f643a 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubTokenService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubTokenService.kt @@ -30,6 +30,7 @@ package com.tencent.devops.repository.service.github import com.tencent.devops.common.api.exception.CustomException import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.exception.RemoteServiceException +import com.tencent.devops.common.api.util.timestampmilli import com.tencent.devops.common.auth.api.AuthProjectApi import com.tencent.devops.common.auth.code.RepoAuthServiceCode import com.tencent.devops.common.client.Client @@ -57,18 +58,47 @@ class GithubTokenService @Autowired constructor( @Value("\${aes.github:#{null}}") private val aesKey = "" + /** + * 保存token + * @param userId server端的用户名 + * @param operator 蓝盾平台操作人用户名 + */ fun createAccessToken( userId: String, accessToken: String, tokenType: String, scope: String, - githubTokenType: GithubTokenType = GithubTokenType.GITHUB_APP + githubTokenType: GithubTokenType = GithubTokenType.GITHUB_APP, + operator: String ) { val encryptedAccessToken = BkCryptoUtil.encryptSm4ButAes(aesKey, accessToken) - if (githubTokenDao.getOrNull(dslContext, userId, githubTokenType) == null) { - githubTokenDao.create(dslContext, userId, encryptedAccessToken, tokenType, scope, githubTokenType) + val githubTokenRecord = githubTokenDao.getOrNull(dslContext, operator, githubTokenType) + if (githubTokenRecord == null) { + githubTokenDao.create( + dslContext = dslContext, + userId = userId, + accessToken = encryptedAccessToken, + tokenType = tokenType, + scope = scope, + githubTokenType = githubTokenType, + operator = operator + ) } else { - githubTokenDao.update(dslContext, userId, encryptedAccessToken, tokenType, scope, githubTokenType) + if (githubTokenRecord.operator != operator) { + logger.info( + "the operator of the gitHub token has changed|userId=$userId|" + + "operator=$operator|oldOperator=${githubTokenRecord.operator}" + ) + } + githubTokenDao.update( + dslContext = dslContext, + userId = userId, + accessToken = encryptedAccessToken, + tokenType = tokenType, + scope = scope, + githubTokenType = githubTokenType, + operator = operator + ) } } @@ -84,7 +114,10 @@ class GithubTokenService @Autowired constructor( return GithubToken( BkCryptoUtil.decryptSm4OrAes(aesKey, githubTokenRecord.accessToken), githubTokenRecord.tokenType, - githubTokenRecord.scope + githubTokenRecord.scope, + githubTokenRecord.createTime.timestampmilli(), + githubTokenRecord.userId, + githubTokenRecord.operator ) } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/IGithubService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/IGithubService.kt index 9aeed354215f..d66da0736ea9 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/IGithubService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/IGithubService.kt @@ -30,6 +30,7 @@ package com.tencent.devops.repository.service.github import com.tencent.devops.repository.pojo.AuthorizeResult import com.tencent.devops.repository.pojo.GithubCheckRuns import com.tencent.devops.repository.pojo.GithubCheckRunsResponse +import com.tencent.devops.repository.pojo.enums.RedirectUrlTypeEnum import com.tencent.devops.repository.pojo.github.GithubBranch import com.tencent.devops.repository.pojo.github.GithubTag import com.tencent.devops.repository.pojo.github.GithubToken @@ -69,7 +70,9 @@ interface IGithubService { userId: String, projectId: String, refreshToken: Boolean?, - resetType: String? + resetType: String?, + redirectUrlType: RedirectUrlTypeEnum? = null, + redirectUrl: String? = "" ): AuthorizeResult fun getAccessToken(userId: String): GithubToken? diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/CodeGitOauth2TokenStoreService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/CodeGitOauth2TokenStoreService.kt new file mode 100644 index 000000000000..f26bd22c940d --- /dev/null +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/CodeGitOauth2TokenStoreService.kt @@ -0,0 +1,73 @@ +/* + * 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.devops.repository.service.oauth2 + +import com.tencent.devops.common.api.util.timestampmilli +import com.tencent.devops.common.security.util.BkCryptoUtil +import com.tencent.devops.repository.dao.GitTokenDao +import com.tencent.devops.repository.pojo.CodeGitRepository +import com.tencent.devops.repository.pojo.oauth.Oauth2AccessToken +import org.jooq.DSLContext +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service + +/** + * 内部工蜂oauth存储管理 + */ +@Service +class CodeGitOauth2TokenStoreService @Autowired constructor( + private val dslContext: DSLContext, + private val gitTokenDao: GitTokenDao +) : IOauth2TokenStoreService { + + @Value("\${aes.git:#{null}}") + private val aesKey: String = "" + + override fun support(scmCode: String): Boolean { + return scmCode == CodeGitRepository.SCM_CODE + } + + override fun get(userId: String, scmCode: String): Oauth2AccessToken? { + return gitTokenDao.getAccessToken(dslContext, userId)?.let { + Oauth2AccessToken( + BkCryptoUtil.decryptSm4OrAes(aesKey, it.accessToken), + it.tokenType, + it.expiresIn, + BkCryptoUtil.decryptSm4OrAes(aesKey, it.refreshToken), + it.createTime.timestampmilli(), + userId = it.userId, + operator = it.operator + ) + } + } + + override fun delete(userId: String, scmCode: String) { + gitTokenDao.deleteToken(dslContext, userId) + } +} diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/CodeGithubOauth2TokenStoreService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/CodeGithubOauth2TokenStoreService.kt new file mode 100644 index 000000000000..0b3371a27805 --- /dev/null +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/CodeGithubOauth2TokenStoreService.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.devops.repository.service.oauth2 + +import com.tencent.devops.common.api.util.timestampmilli +import com.tencent.devops.common.security.util.BkCryptoUtil +import com.tencent.devops.repository.dao.GithubTokenDao +import com.tencent.devops.repository.pojo.GithubRepository +import com.tencent.devops.repository.pojo.oauth.GithubTokenType +import com.tencent.devops.repository.pojo.oauth.Oauth2AccessToken +import org.jooq.DSLContext +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service + +/** + * github oauth2 token存储管理 + */ +@Service +class CodeGithubOauth2TokenStoreService @Autowired constructor( + private val dslContext: DSLContext, + private val githubTokenDao: GithubTokenDao +) : IOauth2TokenStoreService { + + @Value("\${aes.github:#{null}}") + private val aesKey = "" + + override fun support(scmCode: String): Boolean { + return scmCode == GithubRepository.SCM_CODE + } + + override fun get(userId: String, scmCode: String): Oauth2AccessToken? { + return githubTokenDao.getOrNull( + dslContext = dslContext, + userId = userId, + githubTokenType = GithubTokenType.GITHUB_APP + )?.let { + Oauth2AccessToken( + BkCryptoUtil.decryptSm4OrAes(aesKey, it.accessToken), + it.tokenType, + null, + null, + it.createTime.timestampmilli(), + it.userId, + it.operator + ) + } + } + + override fun delete(userId: String, scmCode: String) { + githubTokenDao.delete(dslContext, userId) + } +} diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/IOauth2TokenStoreService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/IOauth2TokenStoreService.kt new file mode 100644 index 000000000000..5e10f3f3b244 --- /dev/null +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/IOauth2TokenStoreService.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.devops.repository.service.oauth2 + +import com.tencent.devops.repository.pojo.oauth.Oauth2AccessToken + +/** + * oauth2 token存储服务 + */ +interface IOauth2TokenStoreService { + fun support(scmCode: String): Boolean + + fun get(userId: String, scmCode: String): Oauth2AccessToken? + + fun delete(userId: String, scmCode: String) +} diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/Oauth2TokenStoreManager.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/Oauth2TokenStoreManager.kt new file mode 100644 index 000000000000..aa47ec37c592 --- /dev/null +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/Oauth2TokenStoreManager.kt @@ -0,0 +1,51 @@ +/* + * 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.devops.repository.service.oauth2 + +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.repository.pojo.oauth.Oauth2AccessToken +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Service + +@Service +class Oauth2TokenStoreManager @Autowired constructor( + private val oauth2TokenStoreServices: List +) { + + fun get(userId: String, scmCode: String): Oauth2AccessToken? { + return getTokenStoreService(scmCode).get(userId, scmCode) + } + + fun delete(userId: String, scmCode: String) { + getTokenStoreService(scmCode).delete(userId, scmCode) + } + + private fun getTokenStoreService(scmCode: String): IOauth2TokenStoreService { + return oauth2TokenStoreServices.find { it.support(scmCode) } ?: throw ErrorCodeException(errorCode = "") + } +} diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/GitOauthService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/GitOauthService.kt index 60801d6ce313..018d1b27e01a 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/GitOauthService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/GitOauthService.kt @@ -58,6 +58,7 @@ import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service import java.net.URLDecoder import java.net.URLEncoder +import java.time.LocalDateTime @Service @Suppress("ALL") @@ -205,8 +206,11 @@ class GitOauthService @Autowired constructor( val userId = authParams["userId"] as String val gitProjectId = authParams["gitProjectId"] as String? val token = gitService.getToken(userId, code) + // 保存当前操作用户 + token.operator = userId // 在oauth授权过程中,可以输入公共账号去鉴权,所以需要再验证token所属人 val oauthUserId = gitService.getUserInfoByToken(token.accessToken).username ?: userId + logger.info("save the git access token for user $oauthUserId, operated by $userId") saveAccessToken(oauthUserId, token) val redirectUrl = gitService.getRedirectUrl(state) logger.info("gitCallback redirectUrl is: $redirectUrl") @@ -273,13 +277,16 @@ class GitOauthService @Autowired constructor( refreshToken = BkCryptoUtil.decryptSm4OrAes(aesKey, it.refreshToken), tokenType = it.tokenType, expiresIn = it.expiresIn, - createTime = it.createTime.timestampmilli() + createTime = it.createTime.timestampmilli(), + updateTime = LocalDateTime.now().timestampmilli(), + operator = it.operator ?: userId ) } } private fun refreshToken(userId: String, gitToken: GitToken): GitToken { val token = gitService.refreshToken(userId, gitToken) + token.operator = gitToken.operator saveAccessToken(userId, token) token.accessToken = BkCryptoUtil.decryptSm4OrAes(aesKey, token.accessToken) token.refreshToken = BkCryptoUtil.decryptSm4OrAes(aesKey, token.refreshToken) diff --git a/support-files/i18n/repository/message_en_US.properties b/support-files/i18n/repository/message_en_US.properties index 76d84eaab51e..f09e13569c98 100644 --- a/support-files/i18n/repository/message_en_US.properties +++ b/support-files/i18n/repository/message_en_US.properties @@ -46,6 +46,7 @@ 2115043=User ({0}) does not have pull access to the ({2}) repository 2115044=User [{0}] has not authorized Github Oauth yet. Please authorize first 2115045=({0}) type of code repository does not currently support OAUTH authorization +2115049=OAuth auth info occupied, can't delete 2100054=User ({0}) does not have permission to {2} repository {3} under project ({1}). 2115048=Failed to get open copilot token, failure details: {0} bkRequestFileSizeLimit=The request file cannot exceed 1m diff --git a/support-files/i18n/repository/message_zh_CN.properties b/support-files/i18n/repository/message_zh_CN.properties index acfa170835c7..ad685d46a01e 100644 --- a/support-files/i18n/repository/message_zh_CN.properties +++ b/support-files/i18n/repository/message_zh_CN.properties @@ -47,6 +47,7 @@ 2115045=({0})类型代码库暂不支持OAUTH授权 2115046=用户({0})无权限在工程({1})下{2}代码库{3} 2115048=获取open copilot token 失败, 失败详情: {0} +2115049=OAUTH授权信息被占用, 无法删除 bkRequestFileSizeLimit=请求文件不能超过1M OperationAddCheckRuns=添加检测任务 OperationUpdateCheckRuns=更新检测任务 diff --git a/support-files/sql/1001_ci_repository_ddl_mysql.sql b/support-files/sql/1001_ci_repository_ddl_mysql.sql index 3d994cb272ac..9c7011677ff3 100644 --- a/support-files/sql/1001_ci_repository_ddl_mysql.sql +++ b/support-files/sql/1001_ci_repository_ddl_mysql.sql @@ -127,6 +127,7 @@ CREATE TABLE IF NOT EXISTS `T_REPOSITORY_GITHUB_TOKEN` ( `CREATE_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `UPDATE_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `TYPE` varchar(32) DEFAULT 'GITHUB_APP' COMMENT 'GitHub token类型(GITHUB_APP、OAUTH_APP)', + `OPERATOR` varchar(64) DEFAULT NULL COMMENT '操作人', PRIMARY KEY (`ID`), UNIQUE KEY `USER_ID` (`USER_ID`, `TYPE`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='github oauth token表'; @@ -143,6 +144,8 @@ CREATE TABLE IF NOT EXISTS `T_REPOSITORY_GIT_TOKEN` ( `TOKEN_TYPE` varchar(64) DEFAULT NULL COMMENT 'token类型', `EXPIRES_IN` bigint(20) DEFAULT NULL COMMENT '过期时间', `CREATE_TIME` datetime DEFAULT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'token的创建时间', + `UPDATE_TIME` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', + `OPERATOR` varchar(64) DEFAULT NULL COMMENT '操作人', PRIMARY KEY (`ID`), UNIQUE KEY `USER_ID` (`USER_ID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工蜂commit checker表'; @@ -259,6 +262,7 @@ CREATE TABLE IF NOT EXISTS `T_REPOSITORY_SCM_TOKEN` ( `EXPIRES_IN` bigint(20) DEFAULT NULL COMMENT '过期时间', `CREATE_TIME` datetime DEFAULT NULL COMMENT '创建时间', `UPDATE_TIME` datetime DEFAULT NULL COMMENT '更新时间', + `OPERATOR` varchar(64) DEFAULT NULL COMMENT '操作人', PRIMARY KEY (`ID`), UNIQUE KEY `UNIQ_USER_SCM_CODE_APP_TYPE` (`USER_ID`,`SCM_CODE`,`APP_TYPE`) ) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='代码仓库token表'; diff --git a/support-files/sql/2004_v3.x/2030_ci_repository-update_v3.0_mysql.sql b/support-files/sql/2004_v3.x/2030_ci_repository-update_v3.0_mysql.sql new file mode 100644 index 000000000000..a854bf65bb31 --- /dev/null +++ b/support-files/sql/2004_v3.x/2030_ci_repository-update_v3.0_mysql.sql @@ -0,0 +1,54 @@ +USE devops_ci_repository; +SET NAMES utf8mb4; + +DROP PROCEDURE IF EXISTS ci_repository_schema_update; + +DELIMITER + +CREATE PROCEDURE ci_repository_schema_update() +BEGIN + DECLARE db VARCHAR(100); + SET AUTOCOMMIT = 0; + SELECT DATABASE() INTO db; + + IF NOT EXISTS(SELECT 1 + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = db + AND TABLE_NAME = 'T_REPOSITORY_GIT_TOKEN' + AND COLUMN_NAME = 'UPDATE_TIME') THEN + ALTER TABLE T_REPOSITORY_GIT_TOKEN + ADD COLUMN `UPDATE_TIME` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间'; + END IF; + + IF NOT EXISTS(SELECT 1 + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = db + AND TABLE_NAME = 'T_REPOSITORY_GIT_TOKEN' + AND COLUMN_NAME = 'OPERATOR') THEN + ALTER TABLE T_REPOSITORY_GIT_TOKEN + ADD COLUMN `OPERATOR` varchar(64) DEFAULT NULL COMMENT '操作人'; + END IF; + + IF NOT EXISTS(SELECT 1 + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = db + AND TABLE_NAME = 'T_REPOSITORY_GITHUB_TOKEN' + AND COLUMN_NAME = 'OPERATOR') THEN + ALTER TABLE T_REPOSITORY_GITHUB_TOKEN + ADD COLUMN `OPERATOR` varchar(64) DEFAULT NULL COMMENT '操作人'; + END IF; + + IF NOT EXISTS(SELECT 1 + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = db + AND TABLE_NAME = 'T_REPOSITORY_SCM_TOKEN' + AND COLUMN_NAME = 'OPERATOR') THEN + ALTER TABLE T_REPOSITORY_SCM_TOKEN + ADD COLUMN `OPERATOR` varchar(64) DEFAULT NULL COMMENT '操作人'; + END IF; + + COMMIT; +END +DELIMITER ; +COMMIT; +CALL ci_repository_schema_update();