diff --git a/scripts/render_tpl b/scripts/render_tpl index c2b21aed29d..754535c0ea0 100755 --- a/scripts/render_tpl +++ b/scripts/render_tpl @@ -13,7 +13,7 @@ sed_script=$(mktemp /tmp/XXXXXX.sed) usage () { echo "Usage: $0 [-c] [-n] [-E k=v, -E k=v] tpl_path ..." -} +} usage_and_exit () { usage @@ -34,7 +34,7 @@ target_file_path () { echo ${_target_file%.tpl} } -[[ $# -eq 0 ]] && usage_and_exit 1 +[[ $# -eq 0 ]] && usage_and_exit 1 declare -i DRY_RUN=0 CHECK=0 declare MODULE="" declare -a EXTRA_ENV=() diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/oauth2/Oauth2DesktopEndpointResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/oauth2/Oauth2DesktopEndpointResource.kt new file mode 100644 index 00000000000..8c8cdf9bd69 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/oauth2/Oauth2DesktopEndpointResource.kt @@ -0,0 +1,55 @@ +package com.tencent.devops.auth.api.oauth2 + +import com.tencent.devops.auth.pojo.dto.Oauth2AuthorizationCodeDTO +import com.tencent.devops.auth.pojo.vo.Oauth2AuthorizationInfoVo +import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_USER_ID +import com.tencent.devops.common.api.pojo.Result +import io.swagger.annotations.Api +import io.swagger.annotations.ApiOperation +import io.swagger.annotations.ApiParam +import javax.ws.rs.Consumes +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 + +@Api(tags = ["OAUTH2_ENDPOINT"], description = "oauth2相关") +@Path("/desktop/oauth2/endpoint") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +interface Oauth2DesktopEndpointResource { + @GET + @Path("/getAuthorizationInformation") + @ApiOperation("获取授权信息") + fun getAuthorizationInformation( + @HeaderParam(AUTH_HEADER_DEVOPS_USER_ID) + @ApiParam("待校验用户ID", required = true) + userId: String, + @QueryParam("clientId") + @ApiParam("客户端ID", required = true) + clientId: String, + @QueryParam("redirectUri") + @ApiParam("跳转链接", required = true) + redirectUri: String + ): Result + + @POST + @Path("/getAuthorizationCode") + @ApiOperation("获取授权码") + fun getAuthorizationCode( + @HeaderParam(AUTH_HEADER_DEVOPS_USER_ID) + @ApiParam("待校验用户ID", required = true) + userId: String, + @QueryParam("clientId") + @ApiParam("客户端ID", required = true) + clientId: String, + @QueryParam("redirectUri") + @ApiParam("跳转链接", required = true) + redirectUri: String, + @ApiParam("oauth2获取授权码请求报文体", required = true) + authorizationCodeDTO: Oauth2AuthorizationCodeDTO + ): Result +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/oauth2/Oauth2ServiceEndpointResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/oauth2/Oauth2ServiceEndpointResource.kt new file mode 100644 index 00000000000..98f448722eb --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/oauth2/Oauth2ServiceEndpointResource.kt @@ -0,0 +1,89 @@ +package com.tencent.devops.auth.api.oauth2 + +import com.tencent.devops.auth.pojo.Oauth2AccessTokenRequest +import com.tencent.devops.auth.pojo.dto.Oauth2AuthorizationCodeDTO +import com.tencent.devops.auth.pojo.vo.Oauth2AccessTokenVo +import com.tencent.devops.auth.pojo.vo.Oauth2AuthorizationInfoVo +import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_USER_ID +import com.tencent.devops.common.api.auth.AUTH_HEADER_OAUTH2_AUTHORIZATION +import com.tencent.devops.common.api.auth.AUTH_HEADER_OAUTH2_CLIENT_ID +import com.tencent.devops.common.api.auth.AUTH_HEADER_OAUTH2_CLIENT_SECRET +import com.tencent.devops.common.api.pojo.Result +import io.swagger.annotations.Api +import io.swagger.annotations.ApiOperation +import io.swagger.annotations.ApiParam +import javax.ws.rs.Consumes +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 + +@Api(tags = ["OAUTH2_ENDPOINT"], description = "oauth2相关") +@Path("/service/oauth2/endpoint") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +interface Oauth2ServiceEndpointResource { + @GET + @Path("/getAuthorizationInformation") + @ApiOperation("获取授权信息") + fun getAuthorizationInformation( + @HeaderParam(AUTH_HEADER_DEVOPS_USER_ID) + @ApiParam("待校验用户ID", required = true) + userId: String, + @QueryParam("clientId") + @ApiParam("客户端ID", required = true) + clientId: String, + @QueryParam("redirectUri") + @ApiParam("跳转链接", required = true) + redirectUri: String + ): Result + + @POST + @Path("/getAuthorizationCode") + @ApiOperation("获取授权码") + fun getAuthorizationCode( + @HeaderParam(AUTH_HEADER_DEVOPS_USER_ID) + @ApiParam("待校验用户ID", required = true) + userId: String, + @QueryParam("clientId") + @ApiParam("客户端ID", required = true) + clientId: String, + @QueryParam("redirectUri") + @ApiParam("跳转链接", required = true) + redirectUri: String, + @ApiParam("oauth2获取授权码请求报文体", required = true) + authorizationCodeDTO: Oauth2AuthorizationCodeDTO + ): Result + + @POST + @Path("/getAccessToken") + @ApiOperation("获取accessToken") + fun getAccessToken( + @HeaderParam(AUTH_HEADER_OAUTH2_CLIENT_ID) + @ApiParam("客户端id", required = true) + clientId: String, + @HeaderParam(AUTH_HEADER_OAUTH2_CLIENT_SECRET) + @ApiParam("客户端秘钥", required = true) + clientSecret: String, + @ApiParam("oauth2获取token请求报文体", required = true) + accessTokenRequest: Oauth2AccessTokenRequest + ): Result + + @POST + @Path("/verifyAccessToken") + @ApiOperation("校验accessToken") + fun verifyAccessToken( + @HeaderParam(AUTH_HEADER_OAUTH2_CLIENT_ID) + @ApiParam("客户端id", required = true) + clientId: String, + @HeaderParam(AUTH_HEADER_OAUTH2_CLIENT_SECRET) + @ApiParam("客户端秘钥", required = true) + clientSecret: String, + @HeaderParam(AUTH_HEADER_OAUTH2_AUTHORIZATION) + @ApiParam("access token", required = true) + accessToken: String + ): Result +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/oauth2/OpOauth2Resource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/oauth2/OpOauth2Resource.kt new file mode 100644 index 00000000000..708c03b9c5b --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/oauth2/OpOauth2Resource.kt @@ -0,0 +1,55 @@ +package com.tencent.devops.auth.api.oauth2 + +import com.tencent.devops.auth.pojo.dto.ClientDetailsDTO +import com.tencent.devops.auth.pojo.dto.ScopeOperationDTO +import io.swagger.annotations.Api +import io.swagger.annotations.ApiOperation +import io.swagger.annotations.ApiParam +import javax.ws.rs.Consumes +import javax.ws.rs.DELETE +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 +import com.tencent.devops.common.api.pojo.Result + +@Api(tags = ["OP_OAUTH2"], description = "oauth2相关-op接口") +@Path("/op/oauth2/") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +interface OpOauth2Resource { + @POST + @Path("/createClientDetails") + @ApiOperation("新增Oauth2客户端信息") + fun createClientDetails( + @ApiParam("Oauth2客户端请求实体", required = true) + clientDetailsDTO: ClientDetailsDTO + ): Result + + @DELETE + @Path("/deleteClientDetails") + @ApiOperation("删除Oauth2客户端信息") + fun deleteClientDetails( + @ApiParam("客户端ID", required = true) + @QueryParam("clientId") + clientId: String + ): Result + + @POST + @Path("/createScopeOperation") + @ApiOperation("新增Oauth2授权操作信息") + fun createScopeOperation( + @ApiParam("Oauth2授权操作信息请求实体", required = true) + scopeOperationDTO: ScopeOperationDTO + ): Result + + @DELETE + @Path("/deleteScopeOperation") + @ApiOperation("删除Oauth2授权操作信息") + fun deleteScopeOperation( + @ApiParam("授权操作ID", required = true) + @QueryParam("operationId") + operationId: String + ): Result +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/service/ServiceSecurityResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/service/ServiceSecurityResource.kt new file mode 100644 index 00000000000..9a6759c05ee --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/service/ServiceSecurityResource.kt @@ -0,0 +1,33 @@ +package com.tencent.devops.auth.api.service + +import com.tencent.devops.auth.pojo.vo.UserAndDeptInfoVo +import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_USER_ID +import com.tencent.devops.common.api.pojo.Result +import io.swagger.annotations.Api +import io.swagger.annotations.ApiOperation +import io.swagger.annotations.ApiParam +import javax.ws.rs.Consumes +import javax.ws.rs.GET +import javax.ws.rs.HeaderParam +import javax.ws.rs.Path +import javax.ws.rs.Produces +import javax.ws.rs.QueryParam +import javax.ws.rs.core.MediaType + +@Api(tags = ["SERVICE_SECURITY"], description = "安全相关") +@Path("/service/security") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +interface ServiceSecurityResource { + @GET + @Path("/getUserSecurityInfo") + @ApiOperation("获取安全相关信息") + fun getUserSecurityInfo( + @HeaderParam(AUTH_HEADER_DEVOPS_USER_ID) + @ApiParam("用户ID", required = true) + userId: String, + @ApiParam("项目ID", required = true) + @QueryParam("projectCode") + projectCode: String + ): Result +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthMessageCode.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthMessageCode.kt index 11d04628656..87d970799c4 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthMessageCode.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthMessageCode.kt @@ -110,8 +110,24 @@ object AuthMessageCode { const val ERROR_MIGRATE_AUTH_COMPARE_FAIL = "2121061" // 迁移项目{0}失败,权限策略对比失败 const val ERROR_CREATOR_NOT_EXIST = "2121062" // 创建人离职 const val ERROR_RESOURCE_CREATE_FAIL = "2121063" // 资源创建失败 + const val ERROR_CLIENT_NOT_EXIST = "2121064" // 客户端{0}不存在 + const val INVALID_AUTHORIZATION_TYPE = "2121065" // 授权类型{0}不合法 + const val INVALID_REDIRECT_URI = "2121066" // 跳转链接{0}不合法 + const val INVALID_CLIENT_SECRET = "2121067" // 客户端{0}密钥不合法 + const val INVALID_AUTHORIZATION_CODE = "2121068" // 授权码不合法 + const val INVALID_AUTHORIZATION_EXPIRED = "2121069" // 授权码已过期 + const val ERROR_REFRESH_TOKEN_NOT_FOUND = "2121070" // refresh_token不能为空 + const val INVALID_REFRESH_TOKEN = "2121071" // refresh_token不合法 + const val ERROR_REFRESH_TOKEN_EXPIRED = "2121072" // refresh token已过期 + const val ERROR_ACCESS_TOKEN_NOT_FOUND = "2121073" // access token不能为空 + const val INVALID_ACCESS_TOKEN = "2121074" // access token不合法 + const val ERROR_ACCESS_TOKEN_EXPIRED = "2121075" // access token已过期 + const val INVALID_SCOPE = "2121076" // scope不合法 const val ERROR_MONITOR_SPACE_NOT_EXIST = "2121077" // 监控空间不存在 const val ERROR_MONITOR_READ_ONLY_ACTIONS_NOT_EXIST = "2121078" // 业务只读组不存在 const val ERROR_MONITOR_OPS_ACTIONS_NOT_EXIST = "2121079" // 业务运维组不存在 + + const val ERROR_WATER_MARK_NOT_EXIST = "2121080" // 水印信息不存在 + const val ERROR_USER_NOT_EXIST = "2121081" // 用户不存在 } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/BkUserDeptInfo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/BkUserDeptInfo.kt new file mode 100644 index 00000000000..ff9027547ef --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/BkUserDeptInfo.kt @@ -0,0 +1,16 @@ +package com.tencent.devops.auth.pojo + +import com.fasterxml.jackson.annotation.JsonProperty +import io.swagger.annotations.ApiModel +import io.swagger.annotations.ApiModelProperty + +@ApiModel("用户部门详细信息") +data class BkUserDeptInfo( + @ApiModelProperty("id") + val id: String?, + @ApiModelProperty("部门名称") + val name: String?, + @ApiModelProperty("部门详细名称") + @JsonProperty("full_name") + val fullName: String? +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/BkUserExtras.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/BkUserExtras.kt new file mode 100644 index 00000000000..c0e2cc39c25 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/BkUserExtras.kt @@ -0,0 +1,14 @@ +package com.tencent.devops.auth.pojo + +import com.fasterxml.jackson.annotation.JsonProperty +import io.swagger.annotations.ApiModel +import io.swagger.annotations.ApiModelProperty + +@ApiModel("用户额外信息") +data class BkUserExtras( + @ApiModelProperty("性别") + val gender: String?, + @ApiModelProperty("postName") + @JsonProperty("postname") + val postName: String? +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/BkUserInfo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/BkUserInfo.kt index ba4f0a157b1..07fefdb22be 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/BkUserInfo.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/BkUserInfo.kt @@ -10,5 +10,9 @@ data class BkUserInfo( @ApiModelProperty("用户名") val username: String, @ApiModelProperty("是否启用") - val enabled: Boolean + val enabled: Boolean, + @ApiModelProperty("用户额外信息") + val extras: BkUserExtras?, + @ApiModelProperty("用户部门") + val departments: List? ) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/ClientDetailsInfo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/ClientDetailsInfo.kt new file mode 100644 index 00000000000..8e0433b8fc4 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/ClientDetailsInfo.kt @@ -0,0 +1,26 @@ +package com.tencent.devops.auth.pojo + +import io.swagger.annotations.ApiModel +import io.swagger.annotations.ApiModelProperty + +@ApiModel("Oauth2客户端详情") +data class ClientDetailsInfo( + @ApiModelProperty("客户端id", required = true) + val clientId: String, + @ApiModelProperty("客户端密钥", required = true) + val clientSecret: String, + @ApiModelProperty("客户端名称", required = true) + val clientName: String, + @ApiModelProperty("授权类型", required = true) + val authorizedGrantTypes: String, + @ApiModelProperty("跳转链接", required = true) + val redirectUri: String, + @ApiModelProperty("授权范围", required = true) + val scope: String, + @ApiModelProperty("accessToken有效期", required = true) + val accessTokenValidity: Long, + @ApiModelProperty("refreshToken有效期", required = true) + val refreshTokenValidity: Long, + @ApiModelProperty("图标", required = true) + val icon: String +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/Oauth2AccessTokenRequest.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/Oauth2AccessTokenRequest.kt new file mode 100644 index 00000000000..e17252781e2 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/Oauth2AccessTokenRequest.kt @@ -0,0 +1,14 @@ +package com.tencent.devops.auth.pojo + +import io.swagger.annotations.ApiModel +import io.swagger.annotations.ApiModelProperty + +@ApiModel("oauth2获取token请求报文体") +data class Oauth2AccessTokenRequest( + @ApiModelProperty("授权类型", required = true) + val grantType: String, + @ApiModelProperty("授权码,用于授权码模式", required = false) + val code: String? = null, + @ApiModelProperty("refreshToken,用于刷新授权码模式", required = false) + val refreshToken: String? = null +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/ClientDetailsDTO.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/ClientDetailsDTO.kt new file mode 100644 index 00000000000..7ecace510c6 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/ClientDetailsDTO.kt @@ -0,0 +1,30 @@ +package com.tencent.devops.auth.pojo.dto + +import io.swagger.annotations.ApiModel +import io.swagger.annotations.ApiModelProperty + +@ApiModel("Oauth2客户端请求实体") +data class ClientDetailsDTO( + @ApiModelProperty("客户端ID") + val clientId: String, + @ApiModelProperty("客户端秘钥") + val clientSecret: String, + @ApiModelProperty("客户端名称") + val clientName: String, + @ApiModelProperty("授权操作范围") + val scope: String, + @ApiModelProperty("图标") + val icon: String, + @ApiModelProperty("授权模式") + val authorizedGrantTypes: String, + @ApiModelProperty("跳转链接") + val webServerRedirectUri: String, + @ApiModelProperty("access_token有效时间") + val accessTokenValidity: Long, + @ApiModelProperty("refresh_token有效时间") + val refreshTokenValidity: Long, + @ApiModelProperty("创建人") + val createUser: String? = null, + @ApiModelProperty("更新人") + val updateUser: String? = null +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/Oauth2AccessTokenDTO.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/Oauth2AccessTokenDTO.kt new file mode 100644 index 00000000000..677db2cad8f --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/Oauth2AccessTokenDTO.kt @@ -0,0 +1,18 @@ +package com.tencent.devops.auth.pojo.dto + +import io.swagger.annotations.ApiModel +import io.swagger.annotations.ApiModelProperty + +@ApiModel("oauth2获取token中间处理态") +data class Oauth2AccessTokenDTO( + @ApiModelProperty("accessToken", required = true) + val accessToken: String? = null, + @ApiModelProperty("refreshToken", required = true) + val refreshToken: String? = null, + @ApiModelProperty("accessToken过期时间", required = true) + val expiredTime: Long? = null, + @ApiModelProperty("accessToken绑定的用户名称", required = true) + val userName: String? = null, + @ApiModelProperty("授权范围Id", required = true) + val scopeId: Int +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/Oauth2AuthorizationCodeDTO.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/Oauth2AuthorizationCodeDTO.kt new file mode 100644 index 00000000000..36e0084032c --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/Oauth2AuthorizationCodeDTO.kt @@ -0,0 +1,10 @@ +package com.tencent.devops.auth.pojo.dto + +import io.swagger.annotations.ApiModel +import io.swagger.annotations.ApiModelProperty + +@ApiModel("oauth2获取授权码请求报文体") +data class Oauth2AuthorizationCodeDTO( + @ApiModelProperty("授权范围", required = true) + val scope: List +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/ScopeOperationDTO.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/ScopeOperationDTO.kt new file mode 100644 index 00000000000..00c40245ea6 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/ScopeOperationDTO.kt @@ -0,0 +1,16 @@ +package com.tencent.devops.auth.pojo.dto + +import io.swagger.annotations.ApiModel +import io.swagger.annotations.ApiModelProperty + +@ApiModel("Oauth2授权操作信息请求实体") +data class ScopeOperationDTO( + @ApiModelProperty("主键ID") + val id: Int, + @ApiModelProperty("授权操作ID") + val operationId: String, + @ApiModelProperty("授权操作中文名称") + val operationNameCn: String, + @ApiModelProperty("授权操作英文名称") + val operationNameEn: String +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/SecOpsWaterMarkDTO.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/SecOpsWaterMarkDTO.kt new file mode 100644 index 00000000000..164f5a5d57b --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/SecOpsWaterMarkDTO.kt @@ -0,0 +1,12 @@ +package com.tencent.devops.auth.pojo.dto + +import io.swagger.annotations.ApiModel +import io.swagger.annotations.ApiModelProperty + +@ApiModel("安全水印") +data class SecOpsWaterMarkDTO( + @ApiModelProperty("场景token") + val token: String, + @ApiModelProperty("用户名称") + val username: String +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/Oauth2GrantType.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/Oauth2GrantType.kt new file mode 100644 index 00000000000..73fa82bcd28 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/Oauth2GrantType.kt @@ -0,0 +1,40 @@ +/* + * 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.auth.pojo.enum + +enum class Oauth2GrantType(val grantType: String) { + // 授权码模式 + AUTHORIZATION_CODE("authorization_code"), + + // 客户端模式 + CLIENT_CREDENTIALS("client_credentials"), + + // 刷新token模式 + REFRESH_TOKEN("refresh_token"); +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/Oauth2AccessTokenVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/Oauth2AccessTokenVo.kt new file mode 100644 index 00000000000..c58d363c51f --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/Oauth2AccessTokenVo.kt @@ -0,0 +1,14 @@ +package com.tencent.devops.auth.pojo.vo + +import io.swagger.annotations.ApiModel +import io.swagger.annotations.ApiModelProperty + +@ApiModel("oauth2获取token请求返回体") +data class Oauth2AccessTokenVo( + @ApiModelProperty("accessToken", required = true) + val accessToken: String, + @ApiModelProperty("accessToken过期时间", required = true) + val expiredTime: Long, + @ApiModelProperty("refreshToken", required = true) + val refreshToken: String? = null +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/Oauth2AuthorizationInfoVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/Oauth2AuthorizationInfoVo.kt new file mode 100644 index 00000000000..79ad3bffaa5 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/Oauth2AuthorizationInfoVo.kt @@ -0,0 +1,16 @@ +package com.tencent.devops.auth.pojo.vo + +import io.swagger.annotations.ApiModel +import io.swagger.annotations.ApiModelProperty + +@ApiModel("oauth2获取授权信息请求返回体") +data class Oauth2AuthorizationInfoVo( + @ApiModelProperty("用户名称", required = true) + val userName: String, + @ApiModelProperty("客户端名称", required = true) + val clientName: String, + @ApiModelProperty("图标", required = true) + val icon: String, + @ApiModelProperty("授权范围", required = true) + val scope: Map +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/SecOpsWaterMarkInfoVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/SecOpsWaterMarkInfoVo.kt new file mode 100644 index 00000000000..f2afd72d017 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/SecOpsWaterMarkInfoVo.kt @@ -0,0 +1,12 @@ +package com.tencent.devops.auth.pojo.vo + +import io.swagger.annotations.ApiModel +import io.swagger.annotations.ApiModelProperty + +@ApiModel("用户水印信息") +data class SecOpsWaterMarkInfoVo( + @ApiModelProperty("类型") + val type: String, + @ApiModelProperty("水印信息") + val data: String +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/UserAndDeptInfoVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/UserAndDeptInfoVo.kt index 8b3e763a7f2..171913b784f 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/UserAndDeptInfoVo.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/UserAndDeptInfoVo.kt @@ -1,10 +1,12 @@ package com.tencent.devops.auth.pojo.vo import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum +import com.tencent.devops.auth.pojo.BkUserDeptInfo +import com.tencent.devops.auth.pojo.BkUserExtras import io.swagger.annotations.ApiModel import io.swagger.annotations.ApiModelProperty -@ApiModel("用户和组织信息返回") +@ApiModel("用户和组织信息返回实体") data class UserAndDeptInfoVo( @ApiModelProperty("id") val id: Int, @@ -13,5 +15,13 @@ data class UserAndDeptInfoVo( @ApiModelProperty("信息类型") val type: ManagerScopesEnum, @ApiModelProperty("是否拥有子级") - val hasChild: Boolean? = false + val hasChild: Boolean? = false, + @ApiModelProperty("用户部门详细信息") + val deptInfo: List? = null, + @ApiModelProperty("用户额外详细信息") + val extras: BkUserExtras? = null, + @ApiModelProperty("水印信息") + val waterMark: String? = null, + @ApiModelProperty("是否是项目成员") + val belongProjectMember: Boolean? = null ) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/common/Constants.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/common/Constants.kt index 6b185670aff..9140e08e58b 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/common/Constants.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/common/Constants.kt @@ -30,7 +30,7 @@ package com.tencent.devops.auth.common object Constants { const val SUPER_MANAGER = -1 const val DEPT_LABEL = "id,name,parent,enabled,has_children" - const val USER_LABLE = "id,username,enabled" + const val USER_LABLE = "id,username,enabled,departments,extras" const val LEVEL = "level" const val PARENT = "parent" const val NAME = "name" diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/common/MockAuthCoreAutoConfiguration.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/common/MockAuthCoreAutoConfiguration.kt index f43ca622130..4755e45574c 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/common/MockAuthCoreAutoConfiguration.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/common/MockAuthCoreAutoConfiguration.kt @@ -44,6 +44,8 @@ import com.tencent.devops.auth.service.sample.SamplePermissionRoleMemberService import com.tencent.devops.auth.service.sample.SamplePermissionRoleService import com.tencent.devops.auth.service.sample.SamplePermissionSuperManagerService import com.tencent.devops.auth.service.sample.SamplePermissionUrlServiceImpl +import com.tencent.devops.auth.service.security.DefaultSecurityServiceImpl +import com.tencent.devops.auth.service.security.SecurityService import com.tencent.devops.common.client.Client import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.context.annotation.Bean @@ -141,4 +143,14 @@ class MockAuthCoreAutoConfiguration { @Bean @ConditionalOnMissingBean(AuthMonitorSpaceService::class) fun sampleAuthMonitorSpaceService() = SampleAuthMonitorSpaceService() + + @Bean + @ConditionalOnMissingBean(SecurityService::class) + fun defaultSecurityServiceImpl( + deptService: DeptService, + permissionProjectService: PermissionProjectService + ) = DefaultSecurityServiceImpl( + deptService = deptService, + permissionProjectService = permissionProjectService + ) } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2AccessTokenDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2AccessTokenDao.kt new file mode 100644 index 00000000000..9ec35bd5e19 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2AccessTokenDao.kt @@ -0,0 +1,86 @@ +package com.tencent.devops.auth.dao + +import com.tencent.devops.model.auth.tables.TAuthOauth2AccessToken +import com.tencent.devops.model.auth.tables.records.TAuthOauth2AccessTokenRecord +import org.jooq.DSLContext +import org.springframework.stereotype.Repository + +@Repository +class AuthOauth2AccessTokenDao { + @Suppress("LongParameterList") + fun get( + dslContext: DSLContext, + clientId: String, + accessToken: String? = null, + refreshToken: String? = null, + userName: String? = null, + grantType: String? = null + ): TAuthOauth2AccessTokenRecord? { + return with(TAuthOauth2AccessToken.T_AUTH_OAUTH2_ACCESS_TOKEN) { + dslContext.selectFrom(this) + .where(CLIENT_ID.eq(clientId)) + .apply { accessToken?.let { and(ACCESS_TOKEN.eq(it)) } } + .apply { userName?.let { and(USER_NAME.eq(it)) } } + .apply { grantType?.let { and(GRANT_TYPE.eq(it)) } } + .apply { refreshToken?.let { and(REFRESH_TOKEN.eq(it)) } } + .fetchOne() + } + } + + fun delete( + dslContext: DSLContext, + accessToken: String + ): Int { + return with(TAuthOauth2AccessToken.T_AUTH_OAUTH2_ACCESS_TOKEN) { + dslContext.deleteFrom(this) + .where(ACCESS_TOKEN.eq(accessToken)) + .execute() + } + } + + @Suppress("LongParameterList") + fun create( + dslContext: DSLContext, + clientId: String, + userName: String?, + grantType: String, + accessToken: String, + refreshToken: String? = null, + expiredTime: Long, + scopeId: Int + ): Int { + return with(TAuthOauth2AccessToken.T_AUTH_OAUTH2_ACCESS_TOKEN) { + dslContext.insertInto( + this, + CLIENT_ID, + USER_NAME, + GRANT_TYPE, + ACCESS_TOKEN, + REFRESH_TOKEN, + EXPIRED_TIME, + SCOPE_ID + ).values( + clientId, + userName, + grantType, + accessToken, + refreshToken, + expiredTime, + scopeId + ).execute() + } + } + + fun update( + dslContext: DSLContext, + accessToken: String, + scopeId: Int + ) { + return with(TAuthOauth2AccessToken.T_AUTH_OAUTH2_ACCESS_TOKEN) { + dslContext.update(this) + .set(SCOPE_ID, scopeId) + .where(ACCESS_TOKEN.eq(accessToken)) + .execute() + } + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2ClientDetailsDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2ClientDetailsDao.kt new file mode 100644 index 00000000000..0a5d0e6b0c9 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2ClientDetailsDao.kt @@ -0,0 +1,62 @@ +package com.tencent.devops.auth.dao + +import com.tencent.devops.auth.pojo.dto.ClientDetailsDTO +import com.tencent.devops.model.auth.tables.TAuthOauth2ClientDetails +import com.tencent.devops.model.auth.tables.records.TAuthOauth2ClientDetailsRecord +import org.jooq.DSLContext +import org.springframework.stereotype.Repository + +@Repository +class AuthOauth2ClientDetailsDao { + fun get( + dslContext: DSLContext, + clientId: String + ): TAuthOauth2ClientDetailsRecord? { + return with(TAuthOauth2ClientDetails.T_AUTH_OAUTH2_CLIENT_DETAILS) { + dslContext.selectFrom(this).where( + CLIENT_ID.eq(clientId) + ) + }.fetchOne() + } + + fun create( + dslContext: DSLContext, + clientDetailsDTO: ClientDetailsDTO + ) { + with(TAuthOauth2ClientDetails.T_AUTH_OAUTH2_CLIENT_DETAILS) { + dslContext.insertInto( + this, + CLIENT_ID, + CLIENT_SECRET, + CLIENT_NAME, + SCOPE, + ICON, + AUTHORIZED_GRANT_TYPES, + WEB_SERVER_REDIRECT_URI, + ACCESS_TOKEN_VALIDITY, + REFRESH_TOKEN_VALIDITY, + CREATE_USER + ).values( + clientDetailsDTO.clientId, + clientDetailsDTO.clientSecret, + clientDetailsDTO.clientName, + clientDetailsDTO.scope, + clientDetailsDTO.icon, + clientDetailsDTO.authorizedGrantTypes, + clientDetailsDTO.webServerRedirectUri, + clientDetailsDTO.accessTokenValidity, + clientDetailsDTO.refreshTokenValidity, + clientDetailsDTO.createUser + ).execute() + } + } + + fun delete( + dslContext: DSLContext, + clientId: String + ) { + with(TAuthOauth2ClientDetails.T_AUTH_OAUTH2_CLIENT_DETAILS) { + dslContext.deleteFrom(this).where(CLIENT_ID.eq(clientId)).execute() + } + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2CodeDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2CodeDao.kt new file mode 100644 index 00000000000..8f181ecd47c --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2CodeDao.kt @@ -0,0 +1,57 @@ +package com.tencent.devops.auth.dao + +import com.tencent.devops.model.auth.tables.TAuthOauth2Code +import com.tencent.devops.model.auth.tables.records.TAuthOauth2CodeRecord +import org.jooq.DSLContext +import org.springframework.stereotype.Repository + +@Repository +class AuthOauth2CodeDao { + fun get( + dslContext: DSLContext, + code: String + ): TAuthOauth2CodeRecord? { + return with(TAuthOauth2Code.T_AUTH_OAUTH2_CODE) { + dslContext.selectFrom(this).where(CODE.eq(code)) + .fetchOne() + } + } + + @Suppress("LongParameterList") + fun create( + dslContext: DSLContext, + code: String, + userId: String, + clientId: String, + scopeId: Int, + expiredTime: Long + ): Int { + return with(TAuthOauth2Code.T_AUTH_OAUTH2_CODE) { + dslContext.insertInto( + this, + CLIENT_ID, + CODE, + SCOPE_ID, + USER_NAME, + EXPIRED_TIME + ).values( + clientId, + code, + scopeId, + userId, + expiredTime + ).execute() + } + } + + fun delete( + dslContext: DSLContext, + code: String + ): Int { + return with(TAuthOauth2Code.T_AUTH_OAUTH2_CODE) { + dslContext.deleteFrom(this) + .where(CODE.eq(code)) + .execute() + } + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2RefreshTokenDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2RefreshTokenDao.kt new file mode 100644 index 00000000000..54dcfc15b04 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2RefreshTokenDao.kt @@ -0,0 +1,51 @@ +package com.tencent.devops.auth.dao + +import com.tencent.devops.model.auth.tables.TAuthOauth2RefreshToken +import com.tencent.devops.model.auth.tables.records.TAuthOauth2RefreshTokenRecord +import org.jooq.DSLContext +import org.springframework.stereotype.Repository + +@Repository +class AuthOauth2RefreshTokenDao { + fun get( + dslContext: DSLContext, + refreshToken: String + ): TAuthOauth2RefreshTokenRecord? { + return with(TAuthOauth2RefreshToken.T_AUTH_OAUTH2_REFRESH_TOKEN) { + dslContext.selectFrom(this) + .where(REFRESH_TOKEN.eq(refreshToken)) + .fetchOne() + } + } + + fun delete( + dslContext: DSLContext, + refreshToken: String + ): Int { + return with(TAuthOauth2RefreshToken.T_AUTH_OAUTH2_REFRESH_TOKEN) { + dslContext.deleteFrom(this) + .where(REFRESH_TOKEN.eq(refreshToken)) + .execute() + } + } + + fun create( + dslContext: DSLContext, + refreshToken: String, + clientId: String, + expiredTime: Long + ): Int { + return with(TAuthOauth2RefreshToken.T_AUTH_OAUTH2_REFRESH_TOKEN) { + dslContext.insertInto( + this, + CLIENT_ID, + REFRESH_TOKEN, + EXPIRED_TIME + ).values( + clientId, + refreshToken, + expiredTime + ).execute() + } + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2ScopeDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2ScopeDao.kt new file mode 100644 index 00000000000..5784cd3e822 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2ScopeDao.kt @@ -0,0 +1,23 @@ +package com.tencent.devops.auth.dao + +import com.tencent.devops.model.auth.tables.TAuthOauth2Scope +import org.jooq.DSLContext +import org.springframework.stereotype.Repository + +@Repository +class AuthOauth2ScopeDao { + fun create( + dslContext: DSLContext, + scope: String + ): Int { + return with(TAuthOauth2Scope.T_AUTH_OAUTH2_SCOPE) { + dslContext.insertInto( + this, + SCOPE + ).values( + scope + ).returning(ID) + .fetchOne()!!.id + } + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2ScopeOperationDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2ScopeOperationDao.kt new file mode 100644 index 00000000000..fb3ff2ad122 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthOauth2ScopeOperationDao.kt @@ -0,0 +1,49 @@ +package com.tencent.devops.auth.dao + +import com.tencent.devops.auth.pojo.dto.ScopeOperationDTO +import com.tencent.devops.model.auth.tables.TAuthOauth2ScopeOperation +import com.tencent.devops.model.auth.tables.records.TAuthOauth2ScopeOperationRecord +import org.jooq.DSLContext +import org.springframework.stereotype.Repository + +@Repository +class AuthOauth2ScopeOperationDao { + fun get( + dslContext: DSLContext, + operationId: String + ): TAuthOauth2ScopeOperationRecord? { + return with(TAuthOauth2ScopeOperation.T_AUTH_OAUTH2_SCOPE_OPERATION) { + dslContext.selectFrom(this).where(OPERATION_ID.eq(operationId)) + .fetchOne() + } + } + + fun create( + dslContext: DSLContext, + scopeOperationDTO: ScopeOperationDTO + ) { + with(TAuthOauth2ScopeOperation.T_AUTH_OAUTH2_SCOPE_OPERATION) { + dslContext.insertInto( + this, + ID, + OPERATION_ID, + OPERATION_NAME_CN, + OPERATION_NAME_EN + ).values( + scopeOperationDTO.id, + scopeOperationDTO.operationId, + scopeOperationDTO.operationNameCn, + scopeOperationDTO.operationNameEn + ).execute() + } + } + + fun delete( + dslContext: DSLContext, + operationId: String + ) { + with(TAuthOauth2ScopeOperation.T_AUTH_OAUTH2_SCOPE_OPERATION) { + dslContext.deleteFrom(this).where(OPERATION_ID.eq(operationId)).execute() + } + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/Oauth2DesktopEndpointResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/Oauth2DesktopEndpointResourceImpl.kt new file mode 100644 index 00000000000..aae663c0ef3 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/Oauth2DesktopEndpointResourceImpl.kt @@ -0,0 +1,43 @@ +package com.tencent.devops.auth.resources + +import com.tencent.devops.auth.api.oauth2.Oauth2DesktopEndpointResource +import com.tencent.devops.auth.pojo.dto.Oauth2AuthorizationCodeDTO +import com.tencent.devops.auth.pojo.vo.Oauth2AuthorizationInfoVo +import com.tencent.devops.auth.service.oauth2.Oauth2EndpointService +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.web.RestResource + +@RestResource +class Oauth2DesktopEndpointResourceImpl constructor( + private val endpointService: Oauth2EndpointService +) : Oauth2DesktopEndpointResource { + override fun getAuthorizationInformation( + userId: String, + clientId: String, + redirectUri: String + ): Result { + return Result( + endpointService.getAuthorizationInformation( + userId = userId, + clientId = clientId, + redirectUri = redirectUri + ) + ) + } + + override fun getAuthorizationCode( + userId: String, + clientId: String, + redirectUri: String, + authorizationCodeDTO: Oauth2AuthorizationCodeDTO + ): Result { + return Result( + endpointService.getAuthorizationCode( + userId = userId, + clientId = clientId, + redirectUri = redirectUri, + authorizationCodeDTO = authorizationCodeDTO + ) + ) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/Oauth2ServiceEndpointResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/Oauth2ServiceEndpointResourceImpl.kt new file mode 100644 index 00000000000..724017f236f --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/Oauth2ServiceEndpointResourceImpl.kt @@ -0,0 +1,73 @@ +package com.tencent.devops.auth.resources + +import com.tencent.devops.auth.api.oauth2.Oauth2ServiceEndpointResource +import com.tencent.devops.auth.pojo.Oauth2AccessTokenRequest +import com.tencent.devops.auth.pojo.dto.Oauth2AuthorizationCodeDTO +import com.tencent.devops.auth.pojo.vo.Oauth2AccessTokenVo +import com.tencent.devops.auth.pojo.vo.Oauth2AuthorizationInfoVo +import com.tencent.devops.auth.service.oauth2.Oauth2EndpointService +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.web.RestResource + +@RestResource +class Oauth2ServiceEndpointResourceImpl constructor( + private val endpointService: Oauth2EndpointService +) : Oauth2ServiceEndpointResource { + override fun getAuthorizationInformation( + userId: String, + clientId: String, + redirectUri: String + ): Result { + return Result( + endpointService.getAuthorizationInformation( + userId = userId, + clientId = clientId, + redirectUri = redirectUri + ) + ) + } + + override fun getAuthorizationCode( + userId: String, + clientId: String, + redirectUri: String, + authorizationCodeDTO: Oauth2AuthorizationCodeDTO + ): Result { + return Result( + endpointService.getAuthorizationCode( + userId = userId, + clientId = clientId, + redirectUri = redirectUri, + authorizationCodeDTO = authorizationCodeDTO + ) + ) + } + + override fun getAccessToken( + clientId: String, + clientSecret: String, + accessTokenRequest: Oauth2AccessTokenRequest + ): Result { + return Result( + endpointService.getAccessToken( + clientId = clientId, + clientSecret = clientSecret, + accessTokenRequest = accessTokenRequest + ) + ) + } + + override fun verifyAccessToken( + clientId: String, + clientSecret: String, + accessToken: String + ): Result { + return Result( + endpointService.verifyAccessToken( + clientId = clientId, + clientSecret = clientSecret, + accessToken = accessToken + ) + ) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/OpOauth2ResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/OpOauth2ResourceImpl.kt new file mode 100644 index 00000000000..cc5ae56a256 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/OpOauth2ResourceImpl.kt @@ -0,0 +1,31 @@ +package com.tencent.devops.auth.resources + +import com.tencent.devops.auth.api.oauth2.OpOauth2Resource +import com.tencent.devops.auth.pojo.dto.ClientDetailsDTO +import com.tencent.devops.auth.pojo.dto.ScopeOperationDTO +import com.tencent.devops.auth.service.oauth2.Oauth2ClientService +import com.tencent.devops.auth.service.oauth2.Oauth2ScopeOperationService +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.web.RestResource + +@RestResource +class OpOauth2ResourceImpl constructor( + val clientService: Oauth2ClientService, + val scopeOperationService: Oauth2ScopeOperationService +) : OpOauth2Resource { + override fun createClientDetails(clientDetailsDTO: ClientDetailsDTO): Result { + return Result(clientService.createClientDetails(clientDetailsDTO = clientDetailsDTO)) + } + + override fun deleteClientDetails(clientId: String): Result { + return Result(clientService.deleteClientDetails(clientId = clientId)) + } + + override fun createScopeOperation(scopeOperationDTO: ScopeOperationDTO): Result { + return Result(scopeOperationService.create(scopeOperationDTO = scopeOperationDTO)) + } + + override fun deleteScopeOperation(operationId: String): Result { + return Result(scopeOperationService.delete(operationId = operationId)) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceSecurityResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceSecurityResourceImpl.kt new file mode 100644 index 00000000000..ed24ccf2278 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceSecurityResourceImpl.kt @@ -0,0 +1,47 @@ +/* + * 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.auth.resources.service + +import com.tencent.devops.auth.api.service.ServiceSecurityResource +import com.tencent.devops.auth.pojo.vo.UserAndDeptInfoVo +import com.tencent.devops.auth.service.security.SecurityService +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.web.RestResource +import org.springframework.beans.factory.annotation.Autowired + +@RestResource +class ServiceSecurityResourceImpl @Autowired constructor( + val securityService: SecurityService +) : ServiceSecurityResource { + override fun getUserSecurityInfo( + userId: String, + projectCode: String + ): Result { + return Result(securityService.getUserSecurityInfo(userId, projectCode)) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/AuthDeptServiceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/AuthDeptServiceImpl.kt index b4c388a1cee..8670904205e 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/AuthDeptServiceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/AuthDeptServiceImpl.kt @@ -54,14 +54,14 @@ import com.tencent.devops.common.api.util.OkhttpUtils import com.tencent.devops.common.auth.api.pojo.EsbBaseReq import com.tencent.devops.common.redis.RedisOperation import com.tencent.devops.common.web.utils.I18nUtil -import java.util.Optional -import java.util.concurrent.TimeUnit import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request import okhttp3.RequestBody import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value +import java.util.Optional +import java.util.concurrent.TimeUnit class AuthDeptServiceImpl @Autowired constructor( val redisOperation: RedisOperation, @@ -138,8 +138,8 @@ class AuthDeptServiceImpl @Autowired constructor( accessToken = accessToken ) val userSearch = SearchUserAndDeptEntity( - bk_app_code = appCode!!, - bk_app_secret = appSecret!!, + bk_app_code = appCode, + bk_app_secret = appSecret, bk_username = userId, fields = USER_LABLE, lookupField = USERNAME, @@ -163,7 +163,9 @@ class AuthDeptServiceImpl @Autowired constructor( UserAndDeptInfoVo( id = it.id, name = it.username, - type = ManagerScopesEnum.USER + type = ManagerScopesEnum.USER, + deptInfo = it.departments, + extras = it.extras ) ) } @@ -188,12 +190,14 @@ class AuthDeptServiceImpl @Autowired constructor( UserAndDeptInfoVo( id = it.id, name = it.username, - type = ManagerScopesEnum.USER + type = ManagerScopesEnum.USER, + deptInfo = it.departments, + extras = it.extras ) ) } - val depteInfos = getDeptInfo(deptSearch) - depteInfos.results.forEach { + val deptInfos = getDeptInfo(deptSearch) + deptInfos.results.forEach { userAndDeptInfos.add( UserAndDeptInfoVo( id = it.id, @@ -205,7 +209,6 @@ class AuthDeptServiceImpl @Autowired constructor( } } } - return userAndDeptInfos } @@ -296,12 +299,12 @@ class AuthDeptServiceImpl @Autowired constructor( private fun getDeptInfo(searchDeptEnity: SearchUserAndDeptEntity): DeptInfoVo { val responseDTO = callUserCenter(LIST_DEPARTMENTS, searchDeptEnity) - return objectMapper.readValue(responseDTO) + return objectMapper.readValue(responseDTO) } private fun getUserInfo(searchUserEntity: SearchUserAndDeptEntity): BkUserInfoVo { val responseDTO = callUserCenter(USER_INFO, searchUserEntity) - return objectMapper.readValue(responseDTO) + return objectMapper.readValue(responseDTO) } private fun callUserCenter(url: String, searchEntity: EsbBaseReq): String { @@ -337,7 +340,8 @@ class AuthDeptServiceImpl @Autowired constructor( throw OperationException( I18nUtil.getCodeLanMessage( messageCode = AuthMessageCode.USER_NOT_EXIST - )) + ) + ) } logger.info("user center response:${objectMapper.writeValueAsString(responseDTO.data)}") return objectMapper.writeValueAsString(responseDTO.data) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2AccessTokenService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2AccessTokenService.kt new file mode 100644 index 00000000000..89aad1d245e --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2AccessTokenService.kt @@ -0,0 +1,84 @@ +package com.tencent.devops.auth.service.oauth2 + +import com.tencent.devops.auth.constant.AuthMessageCode +import com.tencent.devops.auth.dao.AuthOauth2AccessTokenDao +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.model.auth.tables.records.TAuthOauth2AccessTokenRecord +import org.jooq.DSLContext +import org.springframework.stereotype.Service + +@Service +class Oauth2AccessTokenService constructor( + private val oauth2AccessTokenDao: AuthOauth2AccessTokenDao, + private val dslContext: DSLContext +) { + fun get( + clientId: String, + accessToken: String + ): TAuthOauth2AccessTokenRecord { + return oauth2AccessTokenDao.get( + dslContext = dslContext, + clientId = clientId, + accessToken = accessToken + ) ?: throw ErrorCodeException( + errorCode = AuthMessageCode.INVALID_ACCESS_TOKEN, + params = arrayOf(clientId), + defaultMessage = "The access token invalid" + ) + } + + fun get( + clientId: String, + refreshToken: String? = null, + userName: String? = null, + grantType: String? = null + ): TAuthOauth2AccessTokenRecord? { + return oauth2AccessTokenDao.get( + dslContext = dslContext, + clientId = clientId, + refreshToken = refreshToken, + userName = userName, + grantType = grantType + ) + } + + @Suppress("LongParameterList") + fun create( + clientId: String, + userName: String?, + grantType: String, + accessToken: String, + refreshToken: String?, + expiredTime: Long, + scopeId: Int + ) { + oauth2AccessTokenDao.create( + dslContext = dslContext, + clientId = clientId, + userName = userName, + grantType = grantType, + accessToken = accessToken, + refreshToken = refreshToken, + expiredTime = expiredTime, + scopeId = scopeId + ) + } + + fun delete(accessToken: String) { + oauth2AccessTokenDao.delete( + dslContext = dslContext, + accessToken = accessToken + ) + } + + fun update( + accessToken: String, + scopeId: Int + ) { + oauth2AccessTokenDao.update( + dslContext = dslContext, + accessToken = accessToken, + scopeId = scopeId + ) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2ClientService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2ClientService.kt new file mode 100644 index 00000000000..7423136fb39 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2ClientService.kt @@ -0,0 +1,108 @@ +package com.tencent.devops.auth.service.oauth2 + +import com.tencent.devops.auth.constant.AuthMessageCode +import com.tencent.devops.auth.dao.AuthOauth2ClientDetailsDao +import com.tencent.devops.auth.pojo.ClientDetailsInfo +import com.tencent.devops.auth.pojo.dto.ClientDetailsDTO +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.model.auth.tables.records.TAuthOauth2ClientDetailsRecord +import org.jooq.DSLContext +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class Oauth2ClientService constructor( + private val dslContext: DSLContext, + private val authOauth2ClientDetailsDao: AuthOauth2ClientDetailsDao +) { + + companion object { + private val logger = LoggerFactory.getLogger(Oauth2ClientService::class.java) + } + + fun getClientDetails(clientId: String): ClientDetailsInfo { + return authOauth2ClientDetailsDao.get( + dslContext = dslContext, + clientId = clientId + )?.convert() ?: throw ErrorCodeException( + errorCode = AuthMessageCode.ERROR_CLIENT_NOT_EXIST, + params = arrayOf(clientId), + defaultMessage = "the client $clientId not exists" + ) + } + + fun TAuthOauth2ClientDetailsRecord.convert(): ClientDetailsInfo { + return ClientDetailsInfo( + clientId = clientId, + clientSecret = clientSecret, + clientName = clientName, + scope = scope, + authorizedGrantTypes = authorizedGrantTypes, + redirectUri = webServerRedirectUri, + accessTokenValidity = accessTokenValidity, + refreshTokenValidity = refreshTokenValidity, + icon = icon + ) + } + + fun createClientDetails(clientDetailsDTO: ClientDetailsDTO): Boolean { + authOauth2ClientDetailsDao.create( + dslContext = dslContext, + clientDetailsDTO = clientDetailsDTO + ) + return true + } + + fun deleteClientDetails(clientId: String): Boolean { + authOauth2ClientDetailsDao.delete( + dslContext = dslContext, + clientId = clientId + ) + return true + } + + @Suppress("ThrowsCount", "LongParameterList") + fun verifyClientInformation( + clientId: String, + clientDetails: ClientDetailsInfo, + clientSecret: String? = null, + redirectUri: String? = null, + grantType: String? = null, + scope: List? = null + ): Boolean { + val authorizedGrantTypes = clientDetails.authorizedGrantTypes.split(",") + if (grantType != null && !authorizedGrantTypes.contains(grantType)) { + logger.warn("The client($clientId) does not support $grantType type") + throw ErrorCodeException( + errorCode = AuthMessageCode.INVALID_AUTHORIZATION_TYPE, + params = arrayOf(clientId), + defaultMessage = "The client($clientId) does not support $grantType type" + ) + } + if (redirectUri != null && !clientDetails.redirectUri.split(",").contains(redirectUri)) { + logger.warn("The redirectUri is invalid|$clientId|$redirectUri") + throw ErrorCodeException( + errorCode = AuthMessageCode.INVALID_REDIRECT_URI, + params = arrayOf(redirectUri), + defaultMessage = "The redirect uri($redirectUri) is invalid" + ) + } + if (clientSecret != null && clientSecret != clientDetails.clientSecret) { + logger.warn("The client($clientId) secret is invalid") + throw ErrorCodeException( + errorCode = AuthMessageCode.INVALID_CLIENT_SECRET, + params = arrayOf(clientId), + defaultMessage = "The client($clientId) secret is invalid" + ) + } + if (scope != null && !clientDetails.scope.split(",").containsAll(scope)) { + logger.warn("The client($clientId) scope is invalid") + throw ErrorCodeException( + errorCode = AuthMessageCode.INVALID_SCOPE, + params = arrayOf(clientId), + defaultMessage = "The client($clientId) scope is invalid" + ) + } + return true + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2CodeService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2CodeService.kt new file mode 100644 index 00000000000..e3967406ae4 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2CodeService.kt @@ -0,0 +1,77 @@ +package com.tencent.devops.auth.service.oauth2 + +import com.tencent.devops.auth.constant.AuthMessageCode +import com.tencent.devops.auth.dao.AuthOauth2CodeDao +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.api.util.DateTimeUtil +import com.tencent.devops.common.auth.utils.AuthUtils +import com.tencent.devops.model.auth.tables.records.TAuthOauth2CodeRecord +import org.jooq.DSLContext +import org.springframework.stereotype.Service + +@Service +class Oauth2CodeService constructor( + private val authOauth2CodeDao: AuthOauth2CodeDao, + private val dslContext: DSLContext +) { + fun get( + code: String? + ): TAuthOauth2CodeRecord { + if (code == null) { + throw ErrorCodeException( + errorCode = AuthMessageCode.INVALID_AUTHORIZATION_CODE, + defaultMessage = "The authorization code must be provided" + ) + } + return authOauth2CodeDao.get( + dslContext = dslContext, + code = code + ) ?: throw ErrorCodeException( + errorCode = AuthMessageCode.INVALID_AUTHORIZATION_CODE, + defaultMessage = "The authorization code invalid" + ) + } + + fun consume(code: String) { + authOauth2CodeDao.delete( + dslContext = dslContext, + code = code + ) + } + + fun create( + userId: String, + code: String, + clientId: String, + scopeId: Int, + codeValiditySeconds: Long + ) { + authOauth2CodeDao.create( + dslContext = dslContext, + code = code, + userId = userId, + clientId = clientId, + scopeId = scopeId, + expiredTime = DateTimeUtil.getFutureTimestamp(codeValiditySeconds) + ) + } + + fun verifyCode( + clientId: String, + codeDetails: TAuthOauth2CodeRecord + ): Boolean { + if (codeDetails.clientId != clientId) { + throw ErrorCodeException( + errorCode = AuthMessageCode.INVALID_AUTHORIZATION_CODE, + defaultMessage = "The authorization code does not belong to the client($clientId)" + ) + } + if (AuthUtils.isExpired(codeDetails.expiredTime)) { + throw ErrorCodeException( + errorCode = AuthMessageCode.INVALID_AUTHORIZATION_EXPIRED, + defaultMessage = "The authorization code expired" + ) + } + return true + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2Config.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2Config.kt new file mode 100644 index 00000000000..1e3b730930e --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2Config.kt @@ -0,0 +1,47 @@ +package com.tencent.devops.auth.service.oauth2 + +import com.tencent.devops.auth.service.oauth2.grant.AuthorizationCodeTokenGranter +import com.tencent.devops.auth.service.oauth2.grant.ClientCredentialsTokenGranter +import com.tencent.devops.auth.service.oauth2.grant.CompositeTokenGranter +import com.tencent.devops.auth.service.oauth2.grant.RefreshTokenGranter +import com.tencent.devops.auth.service.oauth2.grant.TokenGranter +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +@Suppress("LongParameterList") +class Oauth2Config constructor( + private val oauth2ClientService: Oauth2ClientService, + private val codeService: Oauth2CodeService, + private val scopeService: Oauth2ScopeService, + private val accessTokenService: Oauth2AccessTokenService, + private val clientCredentialsTokenGranter: ClientCredentialsTokenGranter, + private val authorizationCodeTokenGranter: AuthorizationCodeTokenGranter, + private val refreshTokenGranter: RefreshTokenGranter, + private val scopeOperationService: Oauth2ScopeOperationService +) { + @Bean + fun oauth2EndpointService(): Oauth2EndpointService { + return Oauth2EndpointService( + tokenGranter = compositeTokenGranter(), + clientService = oauth2ClientService, + codeService = codeService, + scopeService = scopeService, + accessTokenService = accessTokenService, + scopeOperationService = scopeOperationService + ) + } + + @Bean + fun compositeTokenGranter(): TokenGranter { + return CompositeTokenGranter(getDefaultTokenGranters()) + } + + private fun getDefaultTokenGranters(): List { + val tokenGranters = mutableListOf() + tokenGranters.add(clientCredentialsTokenGranter) + tokenGranters.add(authorizationCodeTokenGranter) + tokenGranters.add(refreshTokenGranter) + return tokenGranters + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2EndpointService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2EndpointService.kt new file mode 100644 index 00000000000..79bc961aedf --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2EndpointService.kt @@ -0,0 +1,134 @@ +package com.tencent.devops.auth.service.oauth2 + +import com.tencent.devops.auth.constant.AuthMessageCode +import com.tencent.devops.auth.pojo.Oauth2AccessTokenRequest +import com.tencent.devops.auth.pojo.dto.Oauth2AuthorizationCodeDTO +import com.tencent.devops.auth.pojo.enum.Oauth2GrantType +import com.tencent.devops.auth.pojo.vo.Oauth2AccessTokenVo +import com.tencent.devops.auth.pojo.vo.Oauth2AuthorizationInfoVo +import com.tencent.devops.auth.service.oauth2.grant.TokenGranter +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.api.util.UUIDUtil +import com.tencent.devops.common.auth.utils.AuthUtils +import org.slf4j.LoggerFactory + +class Oauth2EndpointService constructor( + private val tokenGranter: TokenGranter, + private val clientService: Oauth2ClientService, + private val codeService: Oauth2CodeService, + private val scopeService: Oauth2ScopeService, + private val accessTokenService: Oauth2AccessTokenService, + private val scopeOperationService: Oauth2ScopeOperationService +) { + fun getAuthorizationInformation( + userId: String, + clientId: String, + redirectUri: String + ): Oauth2AuthorizationInfoVo { + logger.info("get authorization information:$userId|$clientId") + // 1、校验clientId是否存在 + val clientDetails = clientService.getClientDetails(clientId = clientId) + // 2、校验客户端信息是否正确 + clientService.verifyClientInformation( + clientId = clientId, + grantType = Oauth2GrantType.AUTHORIZATION_CODE.grantType, + redirectUri = redirectUri, + clientDetails = clientDetails + ) + val scopeOperations = clientDetails.scope.split(",") + return Oauth2AuthorizationInfoVo( + userName = userId, + clientName = clientDetails.clientName, + scope = scopeOperations.associateWith { scopeOperationService.get(it)!!.operationNameCn }, + icon = clientDetails.icon + ) + } + + fun getAuthorizationCode( + userId: String, + clientId: String, + redirectUri: String, + authorizationCodeDTO: Oauth2AuthorizationCodeDTO + ): String { + logger.info("get authorization code:$userId|$clientId|$redirectUri") + // 1、校验clientId是否存在 + val clientDetails = clientService.getClientDetails(clientId = clientId) + // 2、校验客户端信息是否正确 + clientService.verifyClientInformation( + clientId = clientId, + redirectUri = redirectUri, + grantType = Oauth2GrantType.AUTHORIZATION_CODE.grantType, + clientDetails = clientDetails, + scope = authorizationCodeDTO.scope + ) + // 3、存储scope信息 + val scopeId = scopeService.create(scope = authorizationCodeDTO.scope.joinToString(",")) + // 4、生成授权码并存储数据库,授权码有效期为10分钟 + val code = UUIDUtil.generate() + codeService.create( + userId = userId, + code = code, + clientId = clientId, + scopeId = scopeId, + codeValiditySeconds = codeValiditySeconds + ) + // 4、返回跳转链接及授权码 + return "$redirectUri?code=$code" + } + + fun getAccessToken( + clientId: String, + clientSecret: String, + accessTokenRequest: Oauth2AccessTokenRequest + ): Oauth2AccessTokenVo? { + val grantType = accessTokenRequest.grantType + logger.info("get access token:$clientId|$grantType|$accessTokenRequest") + val clientDetails = clientService.getClientDetails( + clientId = clientId + ) + clientService.verifyClientInformation( + clientId = clientId, + clientSecret = clientSecret, + grantType = grantType, + clientDetails = clientDetails + ) + return tokenGranter.grant( + grantType = grantType, + clientDetails = clientDetails, + accessTokenRequest = accessTokenRequest + ) + } + + fun verifyAccessToken( + clientId: String, + clientSecret: String, + accessToken: String + ): String { + val clientDetails = clientService.getClientDetails( + clientId = clientId + ) + clientService.verifyClientInformation( + clientId = clientId, + clientSecret = clientSecret, + clientDetails = clientDetails + ) + val accessTokenInfo = accessTokenService.get( + clientId = clientId, + accessToken = accessToken.substringAfter(OAUTH2_SCHEME).trim() + ) + if (AuthUtils.isExpired(accessTokenInfo.expiredTime)) { + throw ErrorCodeException( + errorCode = AuthMessageCode.ERROR_ACCESS_TOKEN_EXPIRED, + params = arrayOf(clientId), + defaultMessage = "The access token has expired!" + ) + } + return accessTokenInfo.userName + } + + companion object { + private val logger = LoggerFactory.getLogger(Oauth2EndpointService::class.java) + private const val codeValiditySeconds = 600L + private const val OAUTH2_SCHEME = "Bearer " + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2RefreshTokenService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2RefreshTokenService.kt new file mode 100644 index 00000000000..34202e60230 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2RefreshTokenService.kt @@ -0,0 +1,51 @@ +package com.tencent.devops.auth.service.oauth2 + +import com.tencent.devops.auth.constant.AuthMessageCode.ERROR_REFRESH_TOKEN_NOT_FOUND +import com.tencent.devops.auth.constant.AuthMessageCode.INVALID_REFRESH_TOKEN +import com.tencent.devops.auth.dao.AuthOauth2RefreshTokenDao +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.model.auth.tables.records.TAuthOauth2RefreshTokenRecord +import org.jooq.DSLContext +import org.springframework.stereotype.Service + +@Service +class Oauth2RefreshTokenService constructor( + private val authOauth2RefreshTokenDao: AuthOauth2RefreshTokenDao, + private val dslContext: DSLContext +) { + fun get(refreshToken: String?): TAuthOauth2RefreshTokenRecord? { + if (refreshToken == null) + throw ErrorCodeException( + errorCode = ERROR_REFRESH_TOKEN_NOT_FOUND, + defaultMessage = "The refresh token must be provided" + ) + return authOauth2RefreshTokenDao.get( + dslContext = dslContext, + refreshToken = refreshToken + ) ?: throw ErrorCodeException( + errorCode = INVALID_REFRESH_TOKEN, + defaultMessage = "The refresh token invalid" + ) + } + + fun delete(refreshToken: String?) { + refreshToken ?: return + authOauth2RefreshTokenDao.delete( + dslContext = dslContext, + refreshToken = refreshToken + ) + } + + fun create( + refreshToken: String, + clientId: String, + expiredTime: Long + ) { + authOauth2RefreshTokenDao.create( + dslContext = dslContext, + refreshToken = refreshToken, + clientId = clientId, + expiredTime = expiredTime + ) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2ScopeOperationService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2ScopeOperationService.kt new file mode 100644 index 00000000000..0159271a654 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2ScopeOperationService.kt @@ -0,0 +1,39 @@ +package com.tencent.devops.auth.service.oauth2 + +import com.tencent.devops.auth.dao.AuthOauth2ScopeOperationDao +import com.tencent.devops.auth.pojo.dto.ScopeOperationDTO +import com.tencent.devops.model.auth.tables.records.TAuthOauth2ScopeOperationRecord +import org.jooq.DSLContext +import org.springframework.stereotype.Service + +@Service +class Oauth2ScopeOperationService constructor( + private val dslContext: DSLContext, + private val oauth2ScopeOperationDao: AuthOauth2ScopeOperationDao +) { + fun get( + operationId: String + ): TAuthOauth2ScopeOperationRecord? { + return oauth2ScopeOperationDao.get( + dslContext = dslContext, operationId = operationId + ) + } + + fun create( + scopeOperationDTO: ScopeOperationDTO + ): Boolean { + oauth2ScopeOperationDao.create( + dslContext = dslContext, scopeOperationDTO = scopeOperationDTO + ) + return true + } + + fun delete( + operationId: String + ): Boolean { + oauth2ScopeOperationDao.delete( + dslContext = dslContext, operationId = operationId + ) + return true + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2ScopeService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2ScopeService.kt new file mode 100644 index 00000000000..6d7eb63abee --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2ScopeService.kt @@ -0,0 +1,20 @@ +package com.tencent.devops.auth.service.oauth2 + +import com.tencent.devops.auth.dao.AuthOauth2ScopeDao +import org.jooq.DSLContext +import org.springframework.stereotype.Service + +@Service +class Oauth2ScopeService constructor( + private val dslContext: DSLContext, + private val authOauth2ScopeDao: AuthOauth2ScopeDao +) { + fun create( + scope: String + ): Int { + return authOauth2ScopeDao.create( + dslContext = dslContext, + scope = scope + ) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/AbstractTokenGranter.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/AbstractTokenGranter.kt new file mode 100644 index 00000000000..2b453afddb4 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/AbstractTokenGranter.kt @@ -0,0 +1,78 @@ +package com.tencent.devops.auth.service.oauth2.grant + +import com.tencent.devops.auth.pojo.ClientDetailsInfo +import com.tencent.devops.auth.pojo.Oauth2AccessTokenRequest +import com.tencent.devops.auth.pojo.dto.Oauth2AccessTokenDTO +import com.tencent.devops.auth.pojo.vo.Oauth2AccessTokenVo +import com.tencent.devops.auth.service.oauth2.Oauth2AccessTokenService +import com.tencent.devops.common.api.util.DateTimeUtil +import com.tencent.devops.common.api.util.UUIDUtil +import com.tencent.devops.common.auth.utils.AuthUtils + +abstract class AbstractTokenGranter( + private val grantType: String, + private val accessTokenService: Oauth2AccessTokenService +) : TokenGranter { + override fun grant( + grantType: String, + clientDetails: ClientDetailsInfo, + accessTokenRequest: Oauth2AccessTokenRequest + ): Oauth2AccessTokenVo? { + if (this.grantType != grantType) { + return null + } + val accessTokenDTO = getAccessToken( + accessTokenRequest = accessTokenRequest, + clientDetails = clientDetails + ) + return handleAccessToken( + accessTokenRequest = accessTokenRequest, + accessTokenDTO = accessTokenDTO, + clientDetails = clientDetails + ) + } + + private fun handleAccessToken( + accessTokenRequest: Oauth2AccessTokenRequest, + accessTokenDTO: Oauth2AccessTokenDTO, + clientDetails: ClientDetailsInfo + ): Oauth2AccessTokenVo { + val clientId = clientDetails.clientId + val accessToken = accessTokenDTO.accessToken + val refreshToken = accessTokenDTO.refreshToken + // 若access_token为空或者已过期,则重新生成access_token + if (accessToken == null || AuthUtils.isExpired(accessTokenDTO.expiredTime!!)) { + val newAccessToken = UUIDUtil.generate() + val accessTokenValidity = clientDetails.accessTokenValidity + val accessTokenExpiredTime = DateTimeUtil.getFutureTimestamp(accessTokenValidity) + // 删除过期的access_token记录 + if (accessToken != null) { + accessTokenService.delete(accessToken) + } + // 创建新的 access_token记录 + accessTokenService.create( + clientId = clientId, + userName = accessTokenDTO.userName, + grantType = grantType, + accessToken = newAccessToken, + refreshToken = refreshToken, + expiredTime = accessTokenExpiredTime, + scopeId = accessTokenDTO.scopeId + ) + return Oauth2AccessTokenVo(newAccessToken, accessTokenExpiredTime, refreshToken) + } else { + // scope可能会变化,需要更新 + accessTokenService.update( + accessToken = accessToken, + scopeId = accessTokenDTO.scopeId + ) + // 返回未过期的 access_token + return Oauth2AccessTokenVo(accessToken, accessTokenDTO.expiredTime!!, refreshToken) + } + } + + abstract fun getAccessToken( + accessTokenRequest: Oauth2AccessTokenRequest, + clientDetails: ClientDetailsInfo + ): Oauth2AccessTokenDTO +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/AuthorizationCodeTokenGranter.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/AuthorizationCodeTokenGranter.kt new file mode 100644 index 00000000000..8e080517c77 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/AuthorizationCodeTokenGranter.kt @@ -0,0 +1,95 @@ +package com.tencent.devops.auth.service.oauth2.grant + +import com.tencent.devops.auth.pojo.ClientDetailsInfo +import com.tencent.devops.auth.pojo.Oauth2AccessTokenRequest +import com.tencent.devops.auth.pojo.dto.Oauth2AccessTokenDTO +import com.tencent.devops.auth.pojo.enum.Oauth2GrantType +import com.tencent.devops.auth.service.oauth2.Oauth2AccessTokenService +import com.tencent.devops.auth.service.oauth2.Oauth2CodeService +import com.tencent.devops.auth.service.oauth2.Oauth2RefreshTokenService +import com.tencent.devops.common.api.util.DateTimeUtil +import com.tencent.devops.common.api.util.UUIDUtil +import com.tencent.devops.common.auth.utils.AuthUtils +import com.tencent.devops.model.auth.tables.records.TAuthOauth2AccessTokenRecord +import com.tencent.devops.model.auth.tables.records.TAuthOauth2CodeRecord +import org.springframework.stereotype.Service + +@Service +class AuthorizationCodeTokenGranter constructor( + private val codeService: Oauth2CodeService, + private val accessTokenService: Oauth2AccessTokenService, + private val refreshTokenService: Oauth2RefreshTokenService +) : AbstractTokenGranter( + grantType = Oauth2GrantType.AUTHORIZATION_CODE.grantType, + accessTokenService = accessTokenService +) { + override fun getAccessToken( + accessTokenRequest: Oauth2AccessTokenRequest, + clientDetails: ClientDetailsInfo + ): Oauth2AccessTokenDTO { + val clientId = clientDetails.clientId + val code = accessTokenRequest.code + val codeDetails = handleAuthorizationCode( + code = code, + clientId = clientId + ) + val userName = codeDetails.userName + val accessTokenInfo = accessTokenService.get( + clientId = clientId, + userName = codeDetails.userName + ) + val refreshToken = generateRefreshToken( + clientId = clientId, + clientDetails = clientDetails, + accessTokenInfo = accessTokenInfo + ) + return Oauth2AccessTokenDTO( + accessToken = accessTokenInfo?.accessToken, + refreshToken = refreshToken, + expiredTime = accessTokenInfo?.expiredTime, + userName = userName, + scopeId = codeDetails.scopeId + ) + } + + private fun handleAuthorizationCode( + code: String?, + clientId: String + ): TAuthOauth2CodeRecord { + val codeDetails = codeService.get( + code = code + ) + codeService.verifyCode( + clientId = clientId, + codeDetails = codeDetails + ) + // 若授权码没有问题,则直接消费授权码,授权码单次有效 + codeService.consume(code = code!!) + return codeDetails + } + + private fun generateRefreshToken( + clientId: String, + clientDetails: ClientDetailsInfo, + accessTokenInfo: TAuthOauth2AccessTokenRecord? + ): String { + val isAccessTokenValid = accessTokenInfo != null && !AuthUtils.isExpired(accessTokenInfo.expiredTime) + return if (isAccessTokenValid) { + // 若accessToken未过期,refreshToken不变 + accessTokenInfo!!.refreshToken + } else { + val newRefreshToken = UUIDUtil.generate() + // 若accessToken过期,refreshToken重新生成 + refreshTokenService.delete( + refreshToken = accessTokenInfo?.refreshToken + ) + val refreshTokenValidity = clientDetails.refreshTokenValidity + refreshTokenService.create( + refreshToken = newRefreshToken, + clientId = clientId, + expiredTime = DateTimeUtil.getFutureTimestamp(refreshTokenValidity) + ) + newRefreshToken + } + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/ClientCredentialsTokenGranter.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/ClientCredentialsTokenGranter.kt new file mode 100644 index 00000000000..93305a34fae --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/ClientCredentialsTokenGranter.kt @@ -0,0 +1,41 @@ +package com.tencent.devops.auth.service.oauth2.grant + +import com.tencent.devops.auth.pojo.ClientDetailsInfo +import com.tencent.devops.auth.pojo.Oauth2AccessTokenRequest +import com.tencent.devops.auth.pojo.dto.Oauth2AccessTokenDTO +import com.tencent.devops.auth.pojo.enum.Oauth2GrantType +import com.tencent.devops.auth.service.oauth2.Oauth2AccessTokenService +import com.tencent.devops.auth.service.oauth2.Oauth2ScopeService +import org.springframework.stereotype.Service + +@Service +class ClientCredentialsTokenGranter constructor( + private val accessTokenService: Oauth2AccessTokenService, + private val oauth2ScopeService: Oauth2ScopeService +) : AbstractTokenGranter( + grantType = GRANT_TYPE, + accessTokenService = accessTokenService +) { + companion object { + private val GRANT_TYPE = Oauth2GrantType.CLIENT_CREDENTIALS.grantType + } + + override fun getAccessToken( + accessTokenRequest: Oauth2AccessTokenRequest, + clientDetails: ClientDetailsInfo + ): Oauth2AccessTokenDTO { + val accessTokenInfo = accessTokenService.get( + clientId = clientDetails.clientId, + grantType = GRANT_TYPE + ) + val scopeId = oauth2ScopeService.create( + scope = clientDetails.scope + ) + + return Oauth2AccessTokenDTO( + accessToken = accessTokenInfo?.accessToken, + expiredTime = accessTokenInfo?.expiredTime, + scopeId = scopeId + ) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/CompositeTokenGranter.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/CompositeTokenGranter.kt new file mode 100644 index 00000000000..ca2565d512c --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/CompositeTokenGranter.kt @@ -0,0 +1,23 @@ +package com.tencent.devops.auth.service.oauth2.grant + +import com.tencent.devops.auth.pojo.ClientDetailsInfo +import com.tencent.devops.auth.pojo.Oauth2AccessTokenRequest +import com.tencent.devops.auth.pojo.vo.Oauth2AccessTokenVo + +class CompositeTokenGranter constructor( + private val tokenGranters: List +) : TokenGranter { + override fun grant( + grantType: String, + clientDetails: ClientDetailsInfo, + accessTokenRequest: Oauth2AccessTokenRequest + ): Oauth2AccessTokenVo? { + for (granter in tokenGranters) { + val grant = granter.grant(grantType, clientDetails, accessTokenRequest) + if (grant != null) { + return grant + } + } + return null + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/RefreshTokenGranter.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/RefreshTokenGranter.kt new file mode 100644 index 00000000000..9ab95d12c51 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/RefreshTokenGranter.kt @@ -0,0 +1,66 @@ +package com.tencent.devops.auth.service.oauth2.grant + +import com.tencent.devops.auth.constant.AuthMessageCode +import com.tencent.devops.auth.constant.AuthMessageCode.ERROR_REFRESH_TOKEN_EXPIRED +import com.tencent.devops.auth.pojo.ClientDetailsInfo +import com.tencent.devops.auth.pojo.Oauth2AccessTokenRequest +import com.tencent.devops.auth.pojo.dto.Oauth2AccessTokenDTO +import com.tencent.devops.auth.pojo.enum.Oauth2GrantType +import com.tencent.devops.auth.service.oauth2.Oauth2AccessTokenService +import com.tencent.devops.auth.service.oauth2.Oauth2RefreshTokenService +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.auth.utils.AuthUtils +import org.springframework.stereotype.Service + +@Service +class RefreshTokenGranter( + private val accessTokenService: Oauth2AccessTokenService, + private val refreshTokenService: Oauth2RefreshTokenService +) : AbstractTokenGranter( + grantType = Oauth2GrantType.REFRESH_TOKEN.grantType, + accessTokenService = accessTokenService +) { + override fun getAccessToken( + accessTokenRequest: Oauth2AccessTokenRequest, + clientDetails: ClientDetailsInfo + ): Oauth2AccessTokenDTO { + val refreshToken = accessTokenRequest.refreshToken + // 1.校验refresh_token是否存在 + val refreshTokenInfo = refreshTokenService.get( + refreshToken = refreshToken + )!! + // 2.校验refresh_token是否跟client_id匹配 + if (refreshTokenInfo.clientId != clientDetails.clientId) { + throw ErrorCodeException( + errorCode = AuthMessageCode.INVALID_REFRESH_TOKEN, + defaultMessage = "The refresh token invalid" + ) + } + // 3.根据refresh_token获取access_token,获取access_token中的user_name + val accessTokenInfo = accessTokenService.get( + clientId = clientDetails.clientId, + refreshToken = accessTokenRequest.refreshToken + ) ?: throw ErrorCodeException( + errorCode = AuthMessageCode.INVALID_REFRESH_TOKEN, + defaultMessage = "The refresh token invalid" + ) + // 4.清除跟该refresh_token授权码相关的access_token + accessTokenService.delete( + accessToken = accessTokenInfo.accessToken + ) + // 5.校验refresh_token是否过期 + if (AuthUtils.isExpired(refreshTokenInfo.expiredTime)) { + // 6.删除掉refresh_token + refreshTokenService.delete(refreshToken = refreshToken) + throw ErrorCodeException( + errorCode = ERROR_REFRESH_TOKEN_EXPIRED, + defaultMessage = "The refresh token has expired!" + ) + } + return Oauth2AccessTokenDTO( + userName = accessTokenInfo.userName, + refreshToken = refreshToken, + scopeId = accessTokenInfo.scopeId + ) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/TokenGranter.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/TokenGranter.kt new file mode 100644 index 00000000000..0d798547f39 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/oauth2/grant/TokenGranter.kt @@ -0,0 +1,13 @@ +package com.tencent.devops.auth.service.oauth2.grant + +import com.tencent.devops.auth.pojo.ClientDetailsInfo +import com.tencent.devops.auth.pojo.Oauth2AccessTokenRequest +import com.tencent.devops.auth.pojo.vo.Oauth2AccessTokenVo + +interface TokenGranter { + fun grant( + grantType: String, + clientDetails: ClientDetailsInfo, + accessTokenRequest: Oauth2AccessTokenRequest + ): Oauth2AccessTokenVo? +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/security/DefaultSecurityServiceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/security/DefaultSecurityServiceImpl.kt new file mode 100644 index 00000000000..5f2263871ba --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/security/DefaultSecurityServiceImpl.kt @@ -0,0 +1,17 @@ +package com.tencent.devops.auth.service.security + +import com.tencent.devops.auth.pojo.vo.SecOpsWaterMarkInfoVo +import com.tencent.devops.auth.service.DeptService +import com.tencent.devops.auth.service.iam.PermissionProjectService + +class DefaultSecurityServiceImpl constructor( + deptService: DeptService, + permissionProjectService: PermissionProjectService +) : SecurityService(deptService, permissionProjectService) { + override fun getUserWaterMark(userId: String): SecOpsWaterMarkInfoVo { + return SecOpsWaterMarkInfoVo( + type = "", + data = "" + ) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/security/SecurityService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/security/SecurityService.kt new file mode 100644 index 00000000000..e350c14661b --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/security/SecurityService.kt @@ -0,0 +1,49 @@ +package com.tencent.devops.auth.service.security + +import com.tencent.devops.auth.constant.AuthMessageCode +import com.tencent.devops.auth.pojo.vo.SecOpsWaterMarkInfoVo +import com.tencent.devops.auth.pojo.vo.UserAndDeptInfoVo +import com.tencent.devops.auth.service.DeptService +import com.tencent.devops.auth.service.iam.PermissionProjectService +import com.tencent.devops.common.api.exception.ErrorCodeException + +/** + * 安全相关接口 + */ +abstract class SecurityService( + val deptService: DeptService, + val permissionProjectService: PermissionProjectService +) { + fun getUserSecurityInfo( + userId: String, + projectCode: String + ): UserAndDeptInfoVo { + val userInfo = deptService.getUserInfo( + userId = userId, + name = userId + ) ?: throw ErrorCodeException( + errorCode = AuthMessageCode.ERROR_USER_NOT_EXIST, + defaultMessage = "user not exist!$userId" + ) + val belongProjectMember = permissionProjectService.getProjectUsers( + projectCode = projectCode, + group = null + ).contains(userId) + val userWaterMark = getUserWaterMark(userId = userId) + return UserAndDeptInfoVo( + id = userInfo.id, + name = userInfo.name, + type = userInfo.type, + hasChild = userInfo.hasChild, + deptInfo = userInfo.deptInfo, + extras = userInfo.extras, + waterMark = userWaterMark.data, + belongProjectMember = belongProjectMember + ) + } + + /** + * 获取用户水印信息 + */ + abstract fun getUserWaterMark(userId: String): SecOpsWaterMarkInfoVo +} diff --git a/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/service/AuthDeptServiceImplTest.kt b/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/service/AuthDeptServiceImplTest.kt deleted file mode 100644 index 069932cfce0..00000000000 --- a/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/service/AuthDeptServiceImplTest.kt +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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.auth.service - -import com.tencent.devops.common.test.BkCiAbstractTest -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test - -class AuthDeptServiceImplTest : BkCiAbstractTest() { - private val authDeptServiceImpl = AuthDeptServiceImpl(redisOperation, objectMapper) - - @Test - fun test() { - val response = "{\n" + - " \"count\": 162,\n" + - " \"results\": [\n" + - " {\n" + - " \"status\": \"NORMAL\",\n" + - " \"domain\": \"XXX.com\",\n" + - " \"telephone\": \"XXX\",\n" + - " \"create_time\": \"2021-04-27T15:33:59.000000Z\",\n" + - " \"country_code\": \"86\",\n" + - " \"logo\": null,\n" + - " \"iso_code\": \"CN\",\n" + - " \"id\": 173169,\n" + - " \"display_name\": \"abc\",\n" + - " \"leader\": [\n" + - " {\n" + - " \"username\": \"abcv\",\n" + - " \"display_name\": \"abc\",\n" + - " \"id\": 2435\n" + - " }\n" + - " ],\n" + - " \"username\": \"abc\",\n" + - " \"update_time\": \"2021-04-27T15:33:59.000000Z\",\n" + - " \"wx_userid\": \"\",\n" + - " \"staff_status\": \"IN\",\n" + - " \"password_valid_days\": -1,\n" + - " \"qq\": \"\",\n" + - " \"language\": \"zh-cn\",\n" + - " \"enabled\": true,\n" + - " \"time_zone\": \"Asia/Shanghai\",\n" + - " \"departments\": [\n" + - " {\n" + - " \"order\": 1,\n" + - " \"id\": 6580,\n" + - " \"full_name\": \"123/345\",\n" + - " \"name\": \"应用开发组\"\n" + - " }\n" + - " ],\n" + - " \"email\": \"abc@XXX.com\",\n" + - " \"extras\": {\n" + - " \"gender\": \"男\",\n" + - " \"postname\": \"应用开发组员工\"\n" + - " },\n" + - " \"position\": 0,\n" + - " \"category_id\": 2\n" + - " },\n" + - " {\n" + - " \"status\": \"NORMAL\",\n" + - " \"domain\": \"XXX.com\",\n" + - " \"telephone\": \"XXX\",\n" + - " \"create_time\": \"2021-04-27T15:33:59.000000Z\",\n" + - " \"country_code\": \"86\",\n" + - " \"logo\": null,\n" + - " \"iso_code\": \"CN\",\n" + - " \"id\": 173168,\n" + - " \"display_name\": \"abc\",\n" + - " \"leader\": [\n" + - " {\n" + - " \"username\": \"abc\",\n" + - " \"display_name\": \"abc\",\n" + - " \"id\": 2435\n" + - " }\n" + - " ],\n" + - " \"username\": \"def\",\n" + - " \"update_time\": \"2021-04-27T15:33:59.000000Z\",\n" + - " \"wx_userid\": \"\",\n" + - " \"staff_status\": \"IN\",\n" + - " \"password_valid_days\": -1,\n" + - " \"qq\": \"\",\n" + - " \"language\": \"zh-cn\",\n" + - " \"enabled\": true,\n" + - " \"time_zone\": \"Asia/Shanghai\",\n" + - " \"departments\": [\n" + - " {\n" + - " \"order\": 1,\n" + - " \"id\": 6580,\n" + - " \"full_name\": \"123/455\",\n" + - " \"name\": \"应用开发组\"\n" + - " }\n" + - " ],\n" + - " \"email\": \"XXX@126.com\",\n" + - " \"extras\": {\n" + - " \"gender\": \"男\",\n" + - " \"postname\": \"应用开发组员工\"\n" + - " },\n" + - " \"position\": 0,\n" + - " \"category_id\": 2\n" + - " }\n" + - " ]\n" + - " }" - val users = authDeptServiceImpl.findUserName(response) - val mockUsers = mutableListOf() - mockUsers.add("abc") - mockUsers.add("def") - Assertions.assertEquals(users, mockUsers) - } - - @Test - fun test1() { - val response = "[ {\n" + - " \"id\" : \"29510\",\n" + - " \"family\" : [ {\n" + - " \"order\" : 1,\n" + - " \"id\" : 123,\n" + - " \"full_name\" : \"XXX公司\",\n" + - " \"name\" : \"XXX公司\"\n" + - " }, {\n" + - " \"order\" : 1,\n" + - " \"id\" : 12345,\n" + - " \"full_name\" : \"XXX公司/XXX事业群\",\n" + - " \"name\" : \"XXX事业群\"\n" + - " }, {\n" + - " \"order\" : 1,\n" + - " \"id\" : 456,\n" + - " \"full_name\" : \"XXX公司/XXX事业群/XXX部\",\n" + - " \"name\" : \"XXX部\"\n" + - " }, {\n" + - " \"order\" : 1,\n" + - " \"id\" : 9878,\n" + - " \"full_name\" : \"XXX公司/XXX事业群/XXX部/XXX中心\",\n" + - " \"name\" : \"XXX中心\"\n" + - " } ],\n" + - " \"name\" : \"XXX组\"\n" + - "} ]" - val users = authDeptServiceImpl.getUserDeptTreeIds(response) - val mockUsers = mutableSetOf() - mockUsers.add("29510") - mockUsers.add("123") - mockUsers.add("12345") - mockUsers.add("456") - mockUsers.add("9878") - Assertions.assertEquals(users, mockUsers) - } -} diff --git a/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/service/oauth2/AuthorizationCodeTokenGranterTest.kt b/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/service/oauth2/AuthorizationCodeTokenGranterTest.kt new file mode 100644 index 00000000000..0c8e1bdb6f9 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/service/oauth2/AuthorizationCodeTokenGranterTest.kt @@ -0,0 +1,92 @@ +package com.tencent.devops.auth.service.oauth2 + +import com.tencent.devops.auth.pojo.ClientDetailsInfo +import com.tencent.devops.auth.service.oauth2.grant.AuthorizationCodeTokenGranter +import com.tencent.devops.common.test.BkCiAbstractTest +import com.tencent.devops.model.auth.tables.records.TAuthOauth2AccessTokenRecord +import io.mockk.every +import io.mockk.junit5.MockKExtension +import io.mockk.mockk +import io.mockk.spyk +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import java.time.LocalDateTime + +@ExtendWith(MockKExtension::class) +class AuthorizationCodeTokenGranterTest : BkCiAbstractTest() { + + private val codeService = mockk() + + private val accessTokenService = mockk() + + private val refreshTokenService = mockk() + + private val self: AuthorizationCodeTokenGranter = spyk( + AuthorizationCodeTokenGranter( + codeService = codeService, + accessTokenService = accessTokenService, + refreshTokenService = refreshTokenService + ), + recordPrivateCalls = true + ) + private val clientDetails = ClientDetailsInfo( + clientId = "testClientId", + clientSecret = "testClientSecret", + clientName = "testClientName", + authorizedGrantTypes = "testGrantTypes", + redirectUri = "testRedirectUri", + scope = "testScope", + accessTokenValidity = 3600, + refreshTokenValidity = 3600, + icon = "icon" + ) + + @Test + fun `generateRefreshToken should return existing refreshToken when accessToken is valid`() { + val accessTokenInfo = TAuthOauth2AccessTokenRecord( + "testAccessToken", + "testClientId", + "testUserName", + "testGrantType", + System.currentTimeMillis() / 1000 + 1000, + "testRefreshToken", + 1, + LocalDateTime.now() + ) + + val refreshToken = self.invokePrivate( + "generateRefreshToken", + clientDetails.clientId, + clientDetails, + accessTokenInfo + ) + + assertEquals(accessTokenInfo.refreshToken, refreshToken) + } + + @Test + fun `generateRefreshToken should return new refreshToken when accessToken is expired`() { + val expiredAccessTokenInfo = TAuthOauth2AccessTokenRecord( + "testAccessToken", + "testClientId", + "testUserName", + "testGrantType", + System.currentTimeMillis() / 1000 - 1000, + "testRefreshToken", + 1, + LocalDateTime.now() + ) + every { refreshTokenService.create(any(), any(), any()) } returns Unit + every { refreshTokenService.delete(any()) } returns Unit + + val refreshToken = self.invokePrivate( + "generateRefreshToken", + clientDetails.clientId, + clientDetails, + expiredAccessTokenInfo + ) + assertNotEquals(expiredAccessTokenInfo.refreshToken, refreshToken) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2ClientServiceTest.kt b/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2ClientServiceTest.kt new file mode 100644 index 00000000000..9244eb7defe --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2ClientServiceTest.kt @@ -0,0 +1,98 @@ +import com.tencent.devops.auth.constant.AuthMessageCode +import com.tencent.devops.auth.dao.AuthOauth2ClientDetailsDao +import com.tencent.devops.auth.pojo.ClientDetailsInfo +import com.tencent.devops.auth.service.oauth2.Oauth2ClientService +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.test.BkCiAbstractTest +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertArrayEquals +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class Oauth2ClientServiceTest : BkCiAbstractTest() { + + private val authOauth2ClientDetailsDao = mockk() + + private val oauth2ClientService = Oauth2ClientService( + dslContext = dslContext, + authOauth2ClientDetailsDao = authOauth2ClientDetailsDao + ) + private val clientId = "testClient" + + private val clientDetails = ClientDetailsInfo( + clientId = "testClient", + clientSecret = "testSecret", + clientName = "Test Client", + scope = "read,write", + authorizedGrantTypes = "authorization_code,refresh_token", + redirectUri = "http://example.com/callback,http://example1.com/callback", + accessTokenValidity = 3600, + refreshTokenValidity = 86400, + icon = "icon" + ) + + @Test + fun `test verifyClientInformation with invalid grant type`() { + val invalidGrantType = "invalid_grant_type" + + val exception = assertThrows { + oauth2ClientService.verifyClientInformation( + clientId = clientId, + clientDetails = clientDetails, + grantType = invalidGrantType + ) + } + + assertEquals(AuthMessageCode.INVALID_AUTHORIZATION_TYPE, exception.errorCode) + assertArrayEquals(arrayOf(clientId), exception.params) + } + + @Test + fun `test verifyClientInformation with invalid redirect URI`() { + val invalidRedirectUri = "http://invalid.com/callback" + + val exception = assertThrows { + oauth2ClientService.verifyClientInformation( + clientId = clientId, + clientDetails = clientDetails, + redirectUri = invalidRedirectUri + ) + } + + assertEquals(AuthMessageCode.INVALID_REDIRECT_URI, exception.errorCode) + assertArrayEquals(arrayOf(invalidRedirectUri), exception.params) + } + + @Test + fun `test verifyClientInformation with invalid client secret`() { + val invalidClientSecret = "invalidSecret" + + val exception = assertThrows { + oauth2ClientService.verifyClientInformation( + clientId = clientId, + clientDetails = clientDetails, + clientSecret = invalidClientSecret + ) + } + + assertEquals(AuthMessageCode.INVALID_CLIENT_SECRET, exception.errorCode) + assertArrayEquals(arrayOf(clientId), exception.params) + } + + @Test + fun `test verifyClientInformation with invalid scope`() { + val invalidScope = listOf("invalidScope") + + val exception = assertThrows { + oauth2ClientService.verifyClientInformation( + clientId = clientId, + clientDetails = clientDetails, + scope = invalidScope + ) + } + + assertEquals(AuthMessageCode.INVALID_SCOPE, exception.errorCode) + assertArrayEquals(arrayOf(clientId), exception.params) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2CodeServiceTest.kt b/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2CodeServiceTest.kt new file mode 100644 index 00000000000..2f4c33dd32a --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/service/oauth2/Oauth2CodeServiceTest.kt @@ -0,0 +1,77 @@ +package com.tencent.devops.auth.service.oauth2 + +import com.tencent.devops.auth.constant.AuthMessageCode +import com.tencent.devops.auth.dao.AuthOauth2CodeDao +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.test.BkCiAbstractTest +import com.tencent.devops.model.auth.tables.records.TAuthOauth2CodeRecord +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.time.LocalDateTime + +class Oauth2CodeServiceTest : BkCiAbstractTest() { + private val authOauth2CodeDao = mockk() + private val oauth2CodeService = Oauth2CodeService( + dslContext = dslContext, + authOauth2CodeDao = authOauth2CodeDao + ) + + @Test + fun `test verifyCode with valid code`() { + val clientId = "testClient" + val codeDetails = TAuthOauth2CodeRecord( + clientId, + "testCode", + "testUser", + System.currentTimeMillis() / 1000 + 1000L, + 1, + LocalDateTime.now() + ) + + val result = oauth2CodeService.verifyCode(clientId, codeDetails) + + assertTrue(result) + } + + @Test + fun `test verifyCode with invalid client id`() { + val clientId = "testClient" + val invalidClientId = "invalidClient" + val codeDetails = TAuthOauth2CodeRecord( + clientId, + "testCode", + "testUser", + System.currentTimeMillis() / 1000 + 1000L, + 1, + LocalDateTime.now() + ) + + assertThrows { + oauth2CodeService.verifyCode(invalidClientId, codeDetails) + }.apply { + assertEquals(AuthMessageCode.INVALID_AUTHORIZATION_CODE, errorCode) + } + } + + @Test + fun `test verifyCode with expired code`() { + val clientId = "testClient" + val codeDetails = TAuthOauth2CodeRecord( + clientId, + "testCode", + "testUser", + System.currentTimeMillis() / 1000 - 1000L, + 1, + LocalDateTime.now() + ) + + assertThrows { + oauth2CodeService.verifyCode(clientId, codeDetails) + }.apply { + assertEquals(AuthMessageCode.INVALID_AUTHORIZATION_EXPIRED, errorCode) + } + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/service/oauth2/RefreshTokenGranterTest.kt b/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/service/oauth2/RefreshTokenGranterTest.kt new file mode 100644 index 00000000000..9ad9ac1b2b2 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/service/oauth2/RefreshTokenGranterTest.kt @@ -0,0 +1,110 @@ +package com.tencent.devops.auth.service.oauth2 + +import com.tencent.devops.auth.constant.AuthMessageCode.ERROR_REFRESH_TOKEN_EXPIRED +import com.tencent.devops.auth.pojo.ClientDetailsInfo +import com.tencent.devops.auth.pojo.Oauth2AccessTokenRequest +import com.tencent.devops.auth.service.oauth2.grant.RefreshTokenGranter +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.test.BkCiAbstractTest +import com.tencent.devops.model.auth.tables.records.TAuthOauth2AccessTokenRecord +import com.tencent.devops.model.auth.tables.records.TAuthOauth2RefreshTokenRecord +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.time.LocalDateTime + +class RefreshTokenGranterTest : BkCiAbstractTest() { + private val accessTokenService = mockk() + + private val refreshTokenService = mockk() + + private val self: RefreshTokenGranter = spyk( + RefreshTokenGranter( + accessTokenService = accessTokenService, + refreshTokenService = refreshTokenService + ), + recordPrivateCalls = true + ) + private val clientDetails = ClientDetailsInfo( + clientId = "testClientId", + clientSecret = "testClientSecret", + clientName = "testClientName", + authorizedGrantTypes = "testGrantTypes", + redirectUri = "testRedirectUri", + scope = "testScope", + accessTokenValidity = 3600, + refreshTokenValidity = 3600, + icon = "icon" + ) + + private val accessTokenInfo = TAuthOauth2AccessTokenRecord( + "testAccessToken", + "testClientId", + "testUserName", + "testGrantType", + System.currentTimeMillis() / 1000 + 1000, + "testRefreshToken", + 1, + LocalDateTime.now() + ) + + private val accessTokenRequest = Oauth2AccessTokenRequest( + refreshToken = "testRefreshToken", + grantType = "testGrantType" + ) + + @Test + fun `getAccessToken should return valid Oauth2AccessTokenDTO`() { + val refreshTokenInfo = TAuthOauth2RefreshTokenRecord( + "testRefreshToken", + "testClientId", + System.currentTimeMillis() / 1000 + 1000, + LocalDateTime.now() + ) + every { refreshTokenService.get(any()) } returns refreshTokenInfo + every { accessTokenService.get(any(), any(), any(), any()) } returns accessTokenInfo + every { accessTokenService.delete(any()) } returns Unit + + val accessTokenDto = self.getAccessToken( + accessTokenRequest = accessTokenRequest, + clientDetails = clientDetails + ) + + assertEquals(accessTokenInfo.userName, accessTokenDto.userName) + assertEquals(accessTokenInfo.refreshToken, accessTokenDto.refreshToken) + assertEquals(accessTokenInfo.scopeId, accessTokenDto.scopeId) + + verify { refreshTokenService.get(accessTokenRequest.refreshToken) } + verify { accessTokenService.get(clientDetails.clientId, refreshToken = accessTokenRequest.refreshToken) } + verify { accessTokenService.delete(accessTokenInfo.accessToken) } + } + + @Test + fun `getAccessToken should throw ErrorCodeException when refreshToken is expired`() { + val expiredRefreshTokenInfo = TAuthOauth2RefreshTokenRecord( + "testRefreshToken", + "testClientId", + System.currentTimeMillis() / 1000 - 1000, + LocalDateTime.now() + ) + every { refreshTokenService.get(any()) } returns expiredRefreshTokenInfo + every { accessTokenService.get(clientId = any(), refreshToken = any()) } returns accessTokenInfo + every { refreshTokenService.delete(any()) } returns Unit + every { accessTokenService.delete(any()) } returns Unit + + val exception = assertThrows { + self.getAccessToken( + accessTokenRequest = accessTokenRequest, + clientDetails = clientDetails + ) + } + + assertEquals(ERROR_REFRESH_TOKEN_EXPIRED, exception.errorCode) + verify { refreshTokenService.get(accessTokenRequest.refreshToken) } + verify { refreshTokenService.delete(expiredRefreshTokenInfo.refreshToken) } + } +} diff --git a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/auth/Header.kt b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/auth/Header.kt index f426f5c22d3..4fd63f6950b 100644 --- a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/auth/Header.kt +++ b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/auth/Header.kt @@ -103,3 +103,7 @@ const val AUTH_HEADER_BK_CI_LOGIN_TOKEN = "X-DEVOPS-CI-LOGIN-TOKEN" const val AUTH_HEADER_DEVOPS_SERVICE_NAME = "X-DEVOPS-SERVICE-NAME" const val AUTH_HEADER_CODECC_OPENAPI_TOKEN = "X-CODECC-OPENAPI-TOKEN" + +const val AUTH_HEADER_OAUTH2_CLIENT_ID: String = "X-DEVOPS-OAUTH2-CLIENT-ID" +const val AUTH_HEADER_OAUTH2_CLIENT_SECRET: String = "X-DEVOPS-OAUTH2-CLIENT-SECRET" +const val AUTH_HEADER_OAUTH2_AUTHORIZATION: String = "X-DEVOPS-OAUTH2-AUTHORIZATION" diff --git a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/DateTimeUtil.kt b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/DateTimeUtil.kt index 7ac4918a53c..7bed6524337 100644 --- a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/DateTimeUtil.kt +++ b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/DateTimeUtil.kt @@ -70,6 +70,8 @@ object DateTimeUtil { const val YYYYMMDD = "yyyyMMdd" + const val ONE_THOUSAND_MS = 1000L + /** * 单位转换,分钟转换秒 */ @@ -109,6 +111,10 @@ object DateTimeUtil { return cd.time } + fun getFutureTimestamp(seconds: Long): Long { + return System.currentTimeMillis() / 1000 + seconds + } + /** * 按指定日期时间格式格式化日期时间 * @param date 日期时间 diff --git a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/utils/AuthUtils.kt b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/utils/AuthUtils.kt index a8e3806f4ea..a8d455046e5 100644 --- a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/utils/AuthUtils.kt +++ b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/utils/AuthUtils.kt @@ -248,6 +248,10 @@ object AuthUtils { return instanceList } + fun isExpired(expirationTimestamp: Long): Boolean { + return System.currentTimeMillis() / 1000 > expirationTimestamp + } + private fun checkProject(projectId: String, expression: ExpressionDTO): Pair> { val instanceList = mutableSetOf() val values = expression.value.toString().split(",") diff --git a/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/ApigwOauth2EndpointResourceV4.kt b/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/ApigwOauth2EndpointResourceV4.kt new file mode 100644 index 00000000000..95444b49b17 --- /dev/null +++ b/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/ApigwOauth2EndpointResourceV4.kt @@ -0,0 +1,47 @@ +package com.tencent.devops.openapi.api.apigw.v4 + +import com.tencent.devops.auth.pojo.Oauth2AccessTokenRequest +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_OAUTH2_CLIENT_ID +import com.tencent.devops.common.api.auth.AUTH_HEADER_OAUTH2_CLIENT_SECRET +import com.tencent.devops.common.api.pojo.Result +import io.swagger.annotations.Api +import io.swagger.annotations.ApiOperation +import io.swagger.annotations.ApiParam +import javax.ws.rs.Consumes +import javax.ws.rs.HeaderParam +import javax.ws.rs.POST +import javax.ws.rs.Path +import javax.ws.rs.PathParam +import javax.ws.rs.Produces +import javax.ws.rs.core.MediaType + +@Api(tags = ["OPENAPI_OAUTH2_V4"], description = "OPENAPI-OAUTH2相关") +@Path("/{apigwType:apigw-user|apigw-app|apigw}/v4/auth/oauth2/endpoint/") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +interface ApigwOauth2EndpointResourceV4 { + @POST + @Path("/getAccessToken") + @ApiOperation( + "oauth2获取accessToken", + tags = ["v4_app_oauth2_access_token"] + ) + fun getAccessToken( + @ApiParam(value = "appCode", required = true, defaultValue = AUTH_HEADER_DEVOPS_APP_CODE_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_DEVOPS_APP_CODE) + appCode: String?, + @ApiParam(value = "apigw Type", required = true) + @PathParam("apigwType") + apigwType: String?, + @HeaderParam(AUTH_HEADER_OAUTH2_CLIENT_ID) + @ApiParam("客户端id", required = true) + clientId: String, + @HeaderParam(AUTH_HEADER_OAUTH2_CLIENT_SECRET) + @ApiParam("客户端秘钥", required = true) + clientSecret: String, + @ApiParam("oauth2获取token请求报文体", required = true) + accessTokenRequest: Oauth2AccessTokenRequest + ): Result +} diff --git a/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/ApigwUserManagementResourceV4.kt b/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/ApigwUserManagementResourceV4.kt new file mode 100644 index 00000000000..7c2cfb166d4 --- /dev/null +++ b/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/ApigwUserManagementResourceV4.kt @@ -0,0 +1,46 @@ +package com.tencent.devops.openapi.api.apigw.v4 + +import com.tencent.devops.auth.pojo.vo.UserAndDeptInfoVo +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_USER_ID +import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID_DEFAULT_VALUE +import com.tencent.devops.common.api.pojo.Result +import io.swagger.annotations.Api +import io.swagger.annotations.ApiOperation +import io.swagger.annotations.ApiParam +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 + +@Api(tags = ["OPENAPI_USER_MANAGEMENT_V4"], description = "OPENAPI-用户管理") +@Path("/{apigwType:apigw-user|apigw-app|apigw}/v4/auth/user/management/") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +interface ApigwUserManagementResourceV4 { + @GET + @Path("/getUserSecurityInfo") + @ApiOperation( + "获取用户安全相关信息", + tags = ["v4_app_user_info"] + ) + fun getUserSecurityInfo( + @ApiParam(value = "appCode", required = true, defaultValue = AUTH_HEADER_DEVOPS_APP_CODE_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_DEVOPS_APP_CODE) + appCode: String?, + @ApiParam(value = "apigw Type", required = true) + @PathParam("apigwType") + apigwType: String?, + @ApiParam("用户ID", required = true, defaultValue = AUTH_HEADER_USER_ID_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @ApiParam("项目ID", required = true) + @QueryParam("projectCode") + projectCode: String + ): Result +} diff --git a/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/ApigwOauth2EndpointResourceV4Impl.kt b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/ApigwOauth2EndpointResourceV4Impl.kt new file mode 100644 index 00000000000..49d28aefd80 --- /dev/null +++ b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/ApigwOauth2EndpointResourceV4Impl.kt @@ -0,0 +1,46 @@ +package com.tencent.devops.openapi.resources.apigw.v4 + +import com.tencent.devops.auth.api.oauth2.Oauth2ServiceEndpointResource +import com.tencent.devops.auth.pojo.Oauth2AccessTokenRequest +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.api.exception.RemoteServiceException +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.ApigwOauth2EndpointResourceV4 +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired + +@RestResource +class ApigwOauth2EndpointResourceV4Impl @Autowired constructor( + private val client: Client +) : ApigwOauth2EndpointResourceV4 { + + override fun getAccessToken( + appCode: String?, + apigwType: String?, + clientId: String, + clientSecret: String, + accessTokenRequest: Oauth2AccessTokenRequest + ): Result { + logger.info("OPENAPI_OAUTH2_ACCESS_TOKEN_V4|$appCode|$clientId") + return try { + client.get(Oauth2ServiceEndpointResource::class).getAccessToken( + clientId = clientId, + clientSecret = clientSecret, + accessTokenRequest = accessTokenRequest + ) + } catch (ex: ErrorCodeException) { + Result(status = ex.errorCode.toInt(), message = ex.defaultMessage) + } catch (rex: RemoteServiceException) { + Result(status = rex.errorCode ?: REMOTE_EXCEPTION_CODE, message = rex.errorMessage) + } catch (ignore: Exception) { + throw ignore + } + } + + companion object { + val logger = LoggerFactory.getLogger(ApigwOauth2EndpointResourceV4Impl::class.java) + private const val REMOTE_EXCEPTION_CODE = 500 + } +} diff --git a/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/ApigwUserManagementResourceV4Impl.kt b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/ApigwUserManagementResourceV4Impl.kt new file mode 100644 index 00000000000..4c9f6849814 --- /dev/null +++ b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/ApigwUserManagementResourceV4Impl.kt @@ -0,0 +1,33 @@ +package com.tencent.devops.openapi.resources.apigw.v4 + +import com.tencent.devops.auth.api.service.ServiceSecurityResource +import com.tencent.devops.auth.pojo.vo.UserAndDeptInfoVo +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.ApigwUserManagementResourceV4 +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired + +@RestResource +class ApigwUserManagementResourceV4Impl @Autowired constructor( + private val client: Client +) : ApigwUserManagementResourceV4 { + + override fun getUserSecurityInfo( + appCode: String?, + apigwType: String?, + userId: String, + projectCode: String + ): Result { + logger.info("OPENAPI_GET_USER_SECURITY_INFO_V4|$appCode|$userId") + return client.get(ServiceSecurityResource::class).getUserSecurityInfo( + userId = userId, + projectCode = projectCode + ) + } + + companion object { + val logger = LoggerFactory.getLogger(ApigwUserManagementResourceV4Impl::class.java) + } +} diff --git a/support-files/i18n/auth/message_en_US.properties b/support-files/i18n/auth/message_en_US.properties index ea402e2aa87..aead1ac2f95 100644 --- a/support-files/i18n/auth/message_en_US.properties +++ b/support-files/i18n/auth/message_en_US.properties @@ -64,6 +64,19 @@ 2121077=The monitoring space does not exist 2121078=Monitor read only actions does not exist 2121079=Monitor ops actions does not exist +2121064=The client not exists +2121065=The client does not support this grant type +2121066=The redirect uri is invalid +2121067=The client secret is invalid +2121068=The authorization code invalid +2121069=The authorization code expired +2121070=The refresh token must be provided +2121071=The refresh token invalid +2121072=The refresh token has expired +2121073=The access token must be provided +2121074=The access token invalid +2121075=The access token has expired +2121076=The scope invalid bkAdministratorNotExpired=Permission has not expired and no action is required bkAgreeRenew=Agree to renew bkApproverAgreeRenew=Approver agreed to your permission renewal diff --git a/support-files/i18n/auth/message_zh_CN.properties b/support-files/i18n/auth/message_zh_CN.properties index b6d10ced6ab..69f750efc8d 100644 --- a/support-files/i18n/auth/message_zh_CN.properties +++ b/support-files/i18n/auth/message_zh_CN.properties @@ -60,7 +60,20 @@ 2121060=用户组:{0},迁移于{1} 2121061=迁移项目{0}失败,权限策略对比失败 2121062=创建人已离职 -2121073=资源创建失败 +2121063=资源创建失败 +2121064=客户端{0}不存在 +2121065=授权类型{0}不合法 +2121066=跳转链接{0}不合法 +2121067=客户端{0}密钥不合法 +2121068=授权码不合法 +2121069=授权码已过期 +2121070=refresh token不能为空 +2121071=refresh token不合法 +2121072=refresh token已过期 +2121073=access token不能为空 +2121074=access token不合法 +2121075=access token已过期 +2121076=scope不合法 2121077=监控空间不存在 2121078=业务只读组不存在 2121079=业务运维组不存在 diff --git a/support-files/sql/1001_ci_auth_ddl_mysql.sql b/support-files/sql/1001_ci_auth_ddl_mysql.sql index faf5947ff6c..b53d3cf0592 100644 --- a/support-files/sql/1001_ci_auth_ddl_mysql.sql +++ b/support-files/sql/1001_ci_auth_ddl_mysql.sql @@ -295,4 +295,67 @@ CREATE TABLE IF NOT EXISTS `T_AUTH_MONITOR_SPACE` ( PRIMARY KEY (`PROJECT_CODE`,`SPACE_BIZ_ID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT= '蓝盾监控空间权限表'; +CREATE TABLE IF NOT EXISTS `T_AUTH_OAUTH2_CLIENT_DETAILS` ( + `CLIENT_ID` VARCHAR(32) NOT NULL COMMENT '客户端标识', + `CLIENT_SECRET` VARCHAR(64) NOT NULL COMMENT '客户端秘钥', + `CLIENT_NAME` VARCHAR(255) NOT NULL COMMENT '客户端名称', + `SCOPE` MEDIUMTEXT DEFAULT NULL COMMENT '授权操作范围', + `ICON` MEDIUMTEXT DEFAULT NULL COMMENT '图标', + `AUTHORIZED_GRANT_TYPES` VARCHAR(64) NOT NULL COMMENT '授权模式', + `WEB_SERVER_REDIRECT_URI` MEDIUMTEXT DEFAULT NULL COMMENT '跳转链接', + `ACCESS_TOKEN_VALIDITY` BIGINT(20) NOT NULL COMMENT 'access_token有效时间', + `REFRESH_TOKEN_VALIDITY` BIGINT(20) DEFAULT NULL COMMENT 'refresh_token有效时间', + `CREATE_USER` VARCHAR(32) NOT NULL DEFAULT '""' COMMENT '创建人', + `UPDATE_USER` VARCHAR(32) DEFAULT NULL COMMENT '修改人', + `CREATE_TIME` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `UPDATE_TIME` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`CLIENT_ID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户端信息表'; + +CREATE TABLE IF NOT EXISTS `T_AUTH_OAUTH2_CODE` ( + `CLIENT_ID` VARCHAR(32) NOT NULL COMMENT '客户端标识', + `CODE` VARCHAR(64) NOT NULL COMMENT '授权码', + `USER_NAME` VARCHAR(32) NOT NULL COMMENT '用户名', + `EXPIRED_TIME` BIGINT(20) NOT NULL COMMENT '过期时间', + `SCOPE_ID` INT NOT NULL COMMENT '授权范围ID', + `CREATE_TIME` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + INDEX `IDX_CODE` (`CODE`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='授权码表'; + +CREATE TABLE IF NOT EXISTS `T_AUTH_OAUTH2_ACCESS_TOKEN` ( + `ACCESS_TOKEN` VARCHAR(64) NOT NULL COMMENT 'ACCESS_TOKEN', + `CLIENT_ID` VARCHAR(32) NOT NULL COMMENT '客户端ID', + `USER_NAME` VARCHAR(32) DEFAULT NULL COMMENT '登录的用户名,客户端模式该值为空', + `GRANT_TYPE` VARCHAR(32) NOT NULL COMMENT '授权模式', + `EXPIRED_TIME` BIGINT(20) NOT NULL COMMENT '过期时间', + `REFRESH_TOKEN` VARCHAR(64) DEFAULT NULL COMMENT 'REFRESH_TOKEN,客户端模式该值为空', + `SCOPE_ID` INT NOT NULL COMMENT '授权范围ID', + `CREATE_TIME` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + INDEX `IDX_CLIENT_ID_USER_NAME` (`ACCESS_TOKEN`) +) ENGINE=INNODB DEFAULT CHARSET=UTF8 COMMENT='ACCESS_TOKEN表'; + +CREATE TABLE IF NOT EXISTS `T_AUTH_OAUTH2_REFRESH_TOKEN` ( + `REFRESH_TOKEN` VARCHAR(64) NOT NULL COMMENT 'REFRESH_TOKEN', + `CLIENT_ID` VARCHAR(32) NOT NULL COMMENT '客户端ID', + `EXPIRED_TIME` BIGINT(20) NOT NULL COMMENT '过期时间', + `CREATE_TIME` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + INDEX `IDX_REFRESH_TOKEN` (`REFRESH_TOKEN`) +) ENGINE=INNODB DEFAULT CHARSET=UTF8 COMMENT='REFRESH_TOKEN表'; + +CREATE TABLE IF NOT EXISTS `T_AUTH_OAUTH2_SCOPE` ( + `ID` INT(11) NOT NULL AUTO_INCREMENT COMMENT '主健ID', + `SCOPE` MEDIUMTEXT NOT NULL COMMENT '授权范围', + `CREATE_TIME` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`ID`) +) ENGINE=INNODB DEFAULT CHARSET=UTF8 COMMENT='授权范围表'; + +CREATE TABLE IF NOT EXISTS `T_AUTH_OAUTH2_SCOPE_OPERATION` ( + `ID` INT(11) NOT NULL AUTO_INCREMENT COMMENT '主健ID', + `OPERATION_ID` VARCHAR(64) NOT NULL COMMENT '授权操作ID', + `OPERATION_NAME_CN` VARCHAR(64) NOT NULL COMMENT '授权操作中文名称', + `OPERATION_NAME_EN` VARCHAR(64) NOT NULL COMMENT '授权操作英文名称', + `CREATE_TIME` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`ID`) +) ENGINE=INNODB DEFAULT CHARSET=UTF8 COMMENT='授权操作信息表'; + SET FOREIGN_KEY_CHECKS = 1; diff --git a/support-files/templates/#etc#ci#application-assembly.yml b/support-files/templates/#etc#ci#application-assembly.yml index 88f32aef67b..d8cbc969c09 100644 --- a/support-files/templates/#etc#ci#application-assembly.yml +++ b/support-files/templates/#etc#ci#application-assembly.yml @@ -412,7 +412,7 @@ wework: enableDuplicateCheck: # 表示是否开启重复消息检查,0表示否,1表示是,默认0 duplicateCheckInterval: # 表示是否重复消息检查的时间间隔,默认1800s,最大不超过4小时 enableIdTrans: # 表示是否开启id转译,0表示否,1表示是,默认0。仅第三方应用需要用到,企业自建应用可以忽略。 - + # 分布式ID配置 leaf: segment: