Skip to content

Commit

Permalink
Merge pull request #2274 from TencentBlueKing/github_feature/app_perm…
Browse files Browse the repository at this point in the history
…ission

feature: 支持蓝鲸应用级别的权限控制跳转 #2238
  • Loading branch information
jsonwan authored Jul 25, 2023
2 parents 08ea1fc + 12c7b9a commit 56ffb6f
Show file tree
Hide file tree
Showing 21 changed files with 314 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ public class LocaleUtils {
public static final String LANG_ZH = "zh";
public static final String LANG_EN = "en";
public static final String LANG_EN_US = "en_US";
/**
* 蓝鲸通用的LANG HEADER
*/
public static final String BLUEKING_LANG_HEADER = "blueking_language";
/**
* 定义项目通用的LANG HEADER
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@

##业务错误-用户服务、登录服务
1247001=用户不存在或者未登录
1247403=无该应用访问权限



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@

## Business error - User/Login
1247001=User does not exist or is not logged in
1247403=No access permission for this application

## Business error - Backup
1249001=Fail to get node info from artifactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@

## Business error - User/Login
1247001=User does not exist or is not logged in
1247403=No access permission for this application

## Business error - Backup
1249001=Fail to get node info from artifactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@

##业务错误-用户服务、登录服务
1247001=用户不存在或者未登录
1247403=无该应用访问权限



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@

##业务错误-用户服务、登录服务
1247001=用户不存在或者未登录
1247403=无该应用访问权限



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ public class ErrorCode {
// 用户服务 start
// 用户不存在或者未登录
public static final int USER_NOT_EXIST_OR_NOT_LOGIN_IN = 1247001;
// 用户认证成功,但用户无应用访问权限
public static final int USER_ACCESS_APP_FORBIDDEN = 1247403;
// 用户服务 end

// 业务网关 start
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class Response<T> {
@ApiModelProperty("错误信息")
private String errorMsg;

@ApiModelProperty("请求成功返回的数据")
@ApiModelProperty("请求成功/失败返回的数据")
private T data;

@ApiModelProperty("请求 ID")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
* ESB API 调用基础实现
*/
public abstract class AbstractEsbSdkClient {

// 请求成功
protected static final Integer ESB_CODE_OK = 0;

private final Logger log = LoggerFactory.getLogger(this.getClass());

private static final JsonMapper JSON_MAPPER = JsonMapper.nonDefaultMapper();
Expand Down Expand Up @@ -150,6 +154,10 @@ private <R> void requestEsbApi(BkApiContext<? extends EsbReq, R> apiContext,
}

esbResp = JSON_MAPPER.fromJson(respStr, typeReference);
if (esbResp == null) {
log.warn("[AbstractEsbSdkClient] warn:esbResp is null after JSON parse, respStr={}", respStr);
throw new InternalException("Esb api resp unexpected, fail to parse json data", ErrorCode.API_ERROR);
}
apiContext.setResp(esbResp);
if (!esbResp.getResult()) {
log.warn(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Tencent is pleased to support the open source community by making BK-JOB蓝鲸智云作业平台 available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* BK-JOB蓝鲸智云作业平台 is licensed under the MIT License.
*
* License for BK-JOB蓝鲸智云作业平台:
* --------------------------------------------------------------------
* 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.bk.job.common.paas.exception;

import com.tencent.bk.job.common.constant.ErrorCode;
import com.tencent.bk.job.common.exception.ServiceException;
import com.tencent.bk.job.common.model.error.ErrorType;
import lombok.Getter;
import lombok.ToString;

/**
* 应用权限不足异常
*/
@Getter
@ToString
public class AppPermissionDeniedException extends ServiceException {

// 源于用户管理接口:进一步的操作提示
private final String message;

public AppPermissionDeniedException(String message) {
super(ErrorType.PERMISSION_DENIED, ErrorCode.USER_ACCESS_APP_FORBIDDEN);
this.message = message;
}

public String getMessage() {
return message;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.tencent.bk.job.common.exception.InternalUserManageException;
import com.tencent.bk.job.common.metrics.CommonMetricNames;
import com.tencent.bk.job.common.model.dto.BkUserDTO;
import com.tencent.bk.job.common.paas.exception.AppPermissionDeniedException;
import com.tencent.bk.job.common.paas.model.EsbUserDto;
import com.tencent.bk.job.common.util.http.HttpMetricUtil;
import io.micrometer.core.instrument.Tag;
Expand All @@ -40,6 +41,14 @@

@Slf4j
public class EELoginClient extends AbstractEsbSdkClient implements ILoginClient {

// 用户认证失败,即用户登录态无效
private static final Integer ESB_CODE_USER_NOT_LOGIN = 1302100;
// 用户不存在
private static final Integer ESB_CODE_USER_NOT_EXIST = 1302103;
// 用户认证成功,但用户无应用访问权限
private static final Integer ESB_CODE_USER_NO_APP_PERMISSION = 1302403;

private static final String API_GET_USER_INFO = "/api/c/compapi/v2/bk_login/get_user/";

public EELoginClient(String esbHostUrl, String appCode, String appSecret, String lang, boolean useEsbTestEnv) {
Expand Down Expand Up @@ -81,7 +90,13 @@ private BkUserDTO getUserInfo(EsbReq esbReq) {
new TypeReference<EsbResp<EsbUserDto>>() {
}
);
return convertToBkUserDTO(esbResp.getData());
Integer code = esbResp.getCode();
if (ESB_CODE_OK.equals(code)) {
return convertToBkUserDTO(esbResp.getData());
} else {
handleNotOkResp(esbResp);
return null;
}
} catch (Exception e) {
String errorMsg = "Get " + API_GET_USER_INFO + " error";
log.error(errorMsg, e);
Expand All @@ -91,6 +106,17 @@ private BkUserDTO getUserInfo(EsbReq esbReq) {
}
}

private void handleNotOkResp(EsbResp<EsbUserDto> esbResp) {
Integer code = esbResp.getCode();
if (ESB_CODE_USER_NO_APP_PERMISSION.equals(code)) {
throw new AppPermissionDeniedException(esbResp.getMessage());
} else if (ESB_CODE_USER_NOT_LOGIN.equals(code)) {
log.info("User not login, esbResp.code={}, esbResp.message={}", esbResp.getCode(), esbResp.getMessage());
} else if (ESB_CODE_USER_NOT_EXIST.equals(code)) {
log.info("User not exist, esbResp.code={}, esbResp.message={}", esbResp.getCode(), esbResp.getMessage());
}
}

private BkUserDTO convertToBkUserDTO(EsbUserDto esbUserDto) {
BkUserDTO bkUserDTO = new BkUserDTO();
bkUserDTO.setUsername(esbUserDto.getUsername());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

import java.util.List;

import static com.tencent.bk.job.common.i18n.locale.LocaleUtils.BLUEKING_LANG_HEADER;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;

@Slf4j
Expand Down Expand Up @@ -95,6 +96,7 @@ Mono<ServerResponse> getUserByBkToken(ServerRequest request) {

String tokenCookieName = loginService.getCookieNameForToken();
List<String> cookieList = request.headers().header("cookie");
String lang = request.headers().firstHeader(BLUEKING_LANG_HEADER);

List<String> bkTokenList = RequestUtil.getCookieValuesFromCookies(cookieList, tokenCookieName);
if (CollectionUtils.isEmpty(bkTokenList)) {
Expand All @@ -107,7 +109,7 @@ Mono<ServerResponse> getUserByBkToken(ServerRequest request) {
BkUserDTO user = null;
// 遍历所有传入token找出当前环境的
for (String bkToken : bkTokenList) {
user = loginService.getUser(bkToken);
user = loginService.getUser(bkToken, lang);
if (user != null) {
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

package com.tencent.bk.job.gateway.config;

import com.tencent.bk.job.common.i18n.locale.LocaleUtils;
import com.tencent.bk.job.common.paas.login.CustomLoginClient;
import com.tencent.bk.job.common.paas.login.EELoginClient;
import com.tencent.bk.job.common.paas.login.ILoginClient;
Expand All @@ -44,12 +45,31 @@ public ILoginClient innerLoginClient(@Autowired BkConfig bkConfig) {
return new CustomLoginClient(bkConfig.getCustomLoginApiUrl());
}

@Bean
@Bean(name = "enLoginClient")
@ConditionalOnProperty(value = "paas.login.custom.enabled", havingValue = "false", matchIfMissing = true)
@Primary
public ILoginClient enLoginClient(@Autowired BkConfig bkConfig) {
log.info("Init standard en login client");
return new EELoginClient(
bkConfig.getEsbUrl(),
bkConfig.getAppCode(),
bkConfig.getAppSecret(),
LocaleUtils.LANG_EN,
bkConfig.isUseEsbTestEnv()
);
}

@Bean(name = "cnLoginClient")
@ConditionalOnProperty(value = "paas.login.custom.enabled", havingValue = "false", matchIfMissing = true)
@Primary
public ILoginClient standardLoginClient(@Autowired BkConfig bkConfig) {
log.info("Init standard login client");
return new EELoginClient(bkConfig.getEsbUrl(), bkConfig.getAppCode(), bkConfig.getAppSecret(),
bkConfig.isUseEsbTestEnv());
public ILoginClient cnLoginClient(@Autowired BkConfig bkConfig) {
log.info("Init standard cn login client");
return new EELoginClient(
bkConfig.getEsbUrl(),
bkConfig.getAppCode(),
bkConfig.getAppSecret(),
LocaleUtils.LANG_ZH_CN,
bkConfig.isUseEsbTestEnv()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

import static com.tencent.bk.job.common.i18n.locale.LocaleUtils.BLUEKING_LANG_HEADER;
import static com.tencent.bk.job.common.i18n.locale.LocaleUtils.COMMON_LANG_HEADER;

/**
Expand All @@ -54,7 +55,7 @@ public AddWebLangHeaderGatewayFilterFactory() {
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String webLangCookieValue = RequestUtil.getCookieValue(request, "blueking_language");
String webLangCookieValue = RequestUtil.getCookieValue(request, BLUEKING_LANG_HEADER);
String commonLang = LocaleUtils.LANG_ZH_CN;
if (!StringUtils.isEmpty(webLangCookieValue)) {
if (webLangCookieValue.equalsIgnoreCase(WebLangCookie.EN)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,36 @@

package com.tencent.bk.job.gateway.filter.web;

import com.tencent.bk.job.common.constant.ErrorCode;
import com.tencent.bk.job.common.exception.InternalUserManageException;
import com.tencent.bk.job.common.i18n.locale.LocaleUtils;
import com.tencent.bk.job.common.model.Response;
import com.tencent.bk.job.common.model.dto.BkUserDTO;
import com.tencent.bk.job.common.paas.exception.AppPermissionDeniedException;
import com.tencent.bk.job.common.util.RequestUtil;
import com.tencent.bk.job.common.util.json.JsonUtils;
import com.tencent.bk.job.gateway.config.LoginExemptionConfig;
import com.tencent.bk.job.gateway.web.service.LoginService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;

import static com.tencent.bk.job.common.i18n.locale.LocaleUtils.BLUEKING_LANG_HEADER;

/**
* 用户token校验
*/
Expand Down Expand Up @@ -74,6 +87,12 @@ private GatewayFilter getLoginFilter() {
ServerHttpResponse response = exchange.getResponse();
String tokenCookieName = loginService.getCookieNameForToken();
List<String> bkTokenList = RequestUtil.getCookieValuesFromHeader(request, tokenCookieName);
String lang = RequestUtil.getCookieValue(request, BLUEKING_LANG_HEADER);
if (StringUtils.isBlank(lang)) {
lang = LocaleUtils.LANG_EN;
log.warn("Cannot find blueking_language in cookie, use en");
}
LocaleContextHolder.setLocale(LocaleUtils.getLocale(lang), true);
if (CollectionUtils.isEmpty(bkTokenList)) {
log.warn("Fail to parse token from headers, please check");
String bkToken = RequestUtil.getCookieValue(request, tokenCookieName);
Expand All @@ -87,12 +106,15 @@ private GatewayFilter getLoginFilter() {
response.getHeaders().add("x-login-url", loginService.getLoginRedirectUrl());
return response.setComplete();
}
BkUserDTO user = null;
// 遍历所有传入token找出当前环境的
for (String bkToken : bkTokenList) {
user = loginService.getUser(bkToken);
if (user != null) {
break;
BkUserDTO user;
try {
user = getUserByTokenList(bkTokenList, lang);
} catch (InternalUserManageException e) {
Throwable cause = e.getCause();
if (cause instanceof AppPermissionDeniedException) {
return getUserAccessAppForbiddenResp(response, cause.getMessage());
} else {
throw e;
}
}
if (user == null) {
Expand All @@ -108,6 +130,29 @@ private GatewayFilter getLoginFilter() {
};
}

private Mono<Void> getUserAccessAppForbiddenResp(ServerHttpResponse response, String data) {
Response<?> resp = new Response<>(ErrorCode.USER_ACCESS_APP_FORBIDDEN, data);
response.setStatusCode(HttpStatus.FORBIDDEN);
String body = JsonUtils.toJson(resp);
byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
DataBuffer dataBuffer = response.bufferFactory().wrap(bodyBytes);
response.getHeaders().setContentLength(bodyBytes.length);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
response.writeWith(Mono.just(dataBuffer)).subscribe();
return response.setComplete();
}

private BkUserDTO getUserByTokenList(List<String> bkTokenList, String lang) {
// 遍历所有传入token找出当前环境的
for (String bkToken : bkTokenList) {
BkUserDTO user = loginService.getUser(bkToken, lang);
if (user != null) {
return user;
}
}
return null;
}

@Override
public GatewayFilter apply(Config config) {
if (loginExemptionConfig.isEnableLoginExemption()) {
Expand Down
Loading

0 comments on commit 56ffb6f

Please sign in to comment.