build: 增加shiro相关配置

This commit is contained in:
CaptainB 2023-06-01 19:27:29 +08:00 committed by 刘瑞斌
parent 3db8b599c9
commit 64d3aa2ad6
46 changed files with 2032 additions and 68 deletions

View File

@ -23,7 +23,7 @@ jobs:
distribution: 'temurin' distribution: 'temurin'
java-version: 17 java-version: 17
- name: Build with Maven - name: Build with Maven
run: mvn -B package --file pom.xml run: mvn -B package -DskipAntRunForJenkins --file backend/pom.xml
- name: Upload coverage reports to Codecov - name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3
with: with:

View File

@ -1,5 +1,8 @@
-- set innodb lock wait timeout -- set innodb lock wait timeout
SET SESSION innodb_lock_wait_timeout = 7200; SET SESSION innodb_lock_wait_timeout = 7200;
insert into user(id, name, email, password, status, create_time, update_time, language, last_workspace_id, phone, source, last_project_id, create_user)
VALUES ('admin', 'Administrator', 'admin@metersphere.io', MD5('metersphere'), '1', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, NULL, NUll, '', 'LOCAL', NULL, 'admin');
-- set innodb lock wait timeout to default -- set innodb lock wait timeout to default
SET SESSION innodb_lock_wait_timeout = DEFAULT; SET SESSION innodb_lock_wait_timeout = DEFAULT;

View File

@ -1,5 +1,6 @@
package io.metersphere.sdk.config; package io.metersphere.sdk.config;
import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.Translator; import io.metersphere.sdk.util.Translator;
import jakarta.validation.Validator; import jakarta.validation.Validator;
import org.hibernate.validator.HibernateValidator; import org.hibernate.validator.HibernateValidator;
@ -38,4 +39,10 @@ public class I18nConfig {
return localValidatorFactoryBean.getValidator(); return localValidatorFactoryBean.getValidator();
} }
@Bean
@ConditionalOnMissingBean
public CommonBeanFactory commonBeanFactory() {
return new CommonBeanFactory();
}
} }

View File

@ -0,0 +1,15 @@
package io.metersphere.sdk.config;
import io.metersphere.sdk.constants.SessionConstants;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
@Configuration
public class SessionConfig {
@Bean
public HeaderHttpSessionIdResolver sessionIdResolver() {
return new HeaderHttpSessionIdResolver(SessionConstants.HEADER_TOKEN);
}
}

View File

@ -0,0 +1,125 @@
package io.metersphere.sdk.config;
import io.metersphere.sdk.security.CsrfFilter;
import io.metersphere.sdk.security.MsPermissionAnnotationMethodInterceptor;
import io.metersphere.sdk.security.realm.LocalRealm;
import io.metersphere.sdk.util.FilterChainUtils;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;
import org.apache.shiro.aop.AnnotationResolver;
import org.apache.shiro.authz.aop.*;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.aop.SpringAnnotationResolver;
import org.apache.shiro.spring.security.interceptor.AopAllianceAnnotationsAuthorizingMethodInterceptor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.*;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager sessionManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setLoginUrl("/");
shiroFilterFactoryBean.setSecurityManager(sessionManager);
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setSuccessUrl("/");
// shiroFilterFactoryBean.getFilters().put("apikey", new ApiKeyFilter());
shiroFilterFactoryBean.getFilters().put("csrf", new CsrfFilter());
Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
filterChainDefinitionMap.putAll(FilterChainUtils.loadBaseFilterChain());
// todo ignore csrf
// filterChainDefinitionMap.putAll(FilterChainUtils.ignoreCsrfFilter());
filterChainDefinitionMap.put("/**", "csrf, authc");
// todo
// filterChainDefinitionMap.put("/**", "apikey, csrf, authc");
return shiroFilterFactoryBean;
}
@Bean(name = "shiroFilter")
public FilterRegistrationBean<Filter> shiroFilter(ShiroFilterFactoryBean shiroFilterFactoryBean) throws Exception {
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
registration.setFilter((Filter) Objects.requireNonNull(shiroFilterFactoryBean.getObject()));
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
return registration;
}
@Bean
public MemoryConstrainedCacheManager memoryConstrainedCacheManager() {
return new MemoryConstrainedCacheManager();
}
@Bean
public SessionManager sessionManager() {
return new ServletContainerSessionManager();
}
/**
* securityManager 不用直接注入 Realm可能会导致事务失效
* 解决方法见 handleContextRefresh
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager(SessionManager sessionManager, CacheManager cacheManager, Realm localRealm) {
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
dwsm.setSessionManager(sessionManager);
dwsm.setCacheManager(cacheManager);
dwsm.setRealm(localRealm);
return dwsm;
}
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public LocalRealm localRealm() {
return new LocalRealm();
}
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager sessionManager) {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(sessionManager);
AopAllianceAnnotationsAuthorizingMethodInterceptor advice = new AopAllianceAnnotationsAuthorizingMethodInterceptor();
List<AuthorizingAnnotationMethodInterceptor> interceptors = new ArrayList<>(5);
AnnotationResolver resolver = new SpringAnnotationResolver();
interceptors.add(new RoleAnnotationMethodInterceptor(resolver));
interceptors.add(new MsPermissionAnnotationMethodInterceptor(resolver));
interceptors.add(new AuthenticatedAnnotationMethodInterceptor(resolver));
interceptors.add(new UserAnnotationMethodInterceptor(resolver));
interceptors.add(new GuestAnnotationMethodInterceptor(resolver));
advice.setMethodInterceptors(interceptors);
aasa.setAdvice(advice);
return aasa;
}
}

View File

@ -0,0 +1,16 @@
package io.metersphere.sdk.constants;
/**
* 系统内置用户组常量
*/
public class UserRoleConstants {
public static final String SUPER_ROLE = "super_role";
public static final String ADMIN = "admin";
public static final String ORG_ADMIN = "org_admin";
public static final String ORG_MEMBER = "org_member";
public static final String WS_ADMIN = "ws_admin";
public static final String WS_MEMBER = "ws_member";
public static final String PROJECT_ADMIN = "project_admin";
public static final String PROJECT_MEMBER = "project_member";
public static final String READ_ONLY = "read_only";
}

View File

@ -0,0 +1,8 @@
package io.metersphere.sdk.constants;
public class UserRoleType {
public static final String SYSTEM = "SYSTEM";
public static final String ORGANIZATION = "ORGANIZATION";
public static final String WORKSPACE = "WORKSPACE";
public static final String PROJECT = "PROJECT";
}

View File

@ -0,0 +1,5 @@
package io.metersphere.sdk.constants;
public enum UserSource {
LOCAL, LDAP, CAS, OIDC, OAuth2
}

View File

@ -0,0 +1,6 @@
package io.metersphere.sdk.constants;
public class UserStatus {
public static final String NORMAL = "1";
public static final String DISABLED = "0";
}

View File

@ -0,0 +1,76 @@
package io.metersphere.sdk.controller;
import io.metersphere.sdk.constants.UserSource;
import io.metersphere.sdk.controller.handler.ResultHolder;
import io.metersphere.sdk.dto.LoginRequest;
import io.metersphere.sdk.dto.SessionUser;
import io.metersphere.sdk.dto.UserDTO;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.service.BaseUserService;
import io.metersphere.sdk.util.RsaKey;
import io.metersphere.sdk.util.RsaUtil;
import io.metersphere.sdk.util.SessionUtils;
import io.metersphere.sdk.util.Translator;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.shiro.SecurityUtils;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping
public class LoginController {
@Resource
private BaseUserService baseUserService;
@GetMapping(value = "/is-login")
public ResultHolder isLogin() throws Exception {
RsaKey rsaKey = RsaUtil.getRsaKey();
SessionUser user = SessionUtils.getUser();
if (user != null) {
UserDTO userDTO = baseUserService.getUserDTO((String) MethodUtils.invokeMethod(user, "getId"));
if (StringUtils.isBlank(userDTO.getLanguage())) {
userDTO.setLanguage(LocaleContextHolder.getLocale().toString());
}
// todo 跳转用户
// baseUserService.autoSwitch(userDTO);
SessionUser sessionUser = SessionUser.fromUser(userDTO, SessionUtils.getSessionId());
SessionUtils.putUser(sessionUser);
// 用户只有工作空间权限
if (StringUtils.isBlank(sessionUser.getLastProjectId())) {
sessionUser.setLastProjectId("no_such_project");
}
return ResultHolder.success(sessionUser);
}
MSException.throwException(rsaKey.getPublicKey());
//
return null;
}
@PostMapping(value = "/signin")
public ResultHolder login(@RequestBody LoginRequest request) {
SessionUser sessionUser = SessionUtils.getUser();
if (sessionUser != null) {
if (!StringUtils.equals(sessionUser.getId(), request.getUsername())) {
MSException.throwException(Translator.get("please_logout_current_user"));
}
}
SecurityUtils.getSubject().getSession().setAttribute("authenticate", UserSource.LOCAL.name());
ResultHolder result = baseUserService.login(request);
// todo 登录是否提示修改密码
// boolean changePassword = baseUserService.checkWhetherChangePasswordOrNot(request);
// result.setMessage(BooleanUtils.toStringTrueFalse(changePassword));
return result;
}
@GetMapping(value = "/signout")
public void logout(HttpServletResponse response) throws Exception {
SecurityUtils.getSubject().logout();
}
}

View File

@ -3,6 +3,10 @@ package io.metersphere.sdk.controller.handler;
import io.metersphere.sdk.controller.handler.result.IResultCode; import io.metersphere.sdk.controller.handler.result.IResultCode;
import io.metersphere.sdk.controller.handler.result.MsHttpResultCode; import io.metersphere.sdk.controller.handler.result.MsHttpResultCode;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.shiro.ShiroException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError; import org.springframework.validation.FieldError;
@ -22,6 +26,7 @@ public class RestControllerExceptionHandler {
* 处理数据校验异常 * 处理数据校验异常
* 返回具体字段的校验信息 * 返回具体字段的校验信息
* http 状态码返回 400 * http 状态码返回 400
*
* @param ex * @param ex
* @return * @return
*/ */
@ -41,6 +46,7 @@ public class RestControllerExceptionHandler {
/** /**
* 根据 MSException 中的 errorCode * 根据 MSException 中的 errorCode
* 设置对应的 Http 状态码以及业务状态码和错误提示 * 设置对应的 Http 状态码以及业务状态码和错误提示
*
* @param e * @param e
* @return * @return
*/ */
@ -64,4 +70,20 @@ public class RestControllerExceptionHandler {
.body(new ResultHolder(errorCode.getCode(), errorCode.getMessage(), e.getMessage())); .body(new ResultHolder(errorCode.getCode(), errorCode.getMessage(), e.getMessage()));
} }
} }
/*=========== Shiro 异常拦截==============*/
@ExceptionHandler(ShiroException.class)
public ResultHolder exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return ResultHolder.error(HttpStatus.UNAUTHORIZED.value(), exception.getMessage());
}
/*=========== Shiro 异常拦截==============*/
@ExceptionHandler(UnauthorizedException.class)
public ResultHolder unauthorizedExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) {
response.setStatus(HttpStatus.FORBIDDEN.value());
return ResultHolder.error(HttpStatus.FORBIDDEN.value(), exception.getMessage());
}
} }

View File

@ -0,0 +1,20 @@
package io.metersphere.sdk.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class GroupResource implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String name;
private Boolean license = false;
/**
* 系统设置工作空间项目类型 公用的权限模块
* e.g. 个人信息
*/
private boolean global = false;
}

View File

@ -0,0 +1,36 @@
package io.metersphere.sdk.dto;
import io.metersphere.sdk.util.RsaKey;
import io.metersphere.sdk.util.RsaUtil;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class LoginRequest {
@NotBlank(message = "{user_name_is_null}")
private String username;
@NotBlank(message = "{password_is_null}")
private String password;
private String authenticate;
public String getUsername() {
try {
RsaKey rsaKey = RsaUtil.getRsaKey();
return RsaUtil.privateDecrypt(username, rsaKey.getPrivateKey());
} catch (Exception e) {
return username;
}
}
public String getPassword() {
try {
RsaKey rsaKey = RsaUtil.getRsaKey();
return RsaUtil.privateDecrypt(password, rsaKey.getPrivateKey());
} catch (Exception e) {
return password;
}
}
}

View File

@ -0,0 +1,38 @@
package io.metersphere.sdk.dto;
import io.metersphere.sdk.util.CodingUtil;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
@Setter
@Getter
public class SessionUser extends UserDTO implements Serializable {
public static final String secret = "9a9rdqPlTqhpZzkq";
public static final String iv = "1Av7hf9PgHusUHRm";
private static final long serialVersionUID = -7149638440406959033L;
private String csrfToken;
private String sessionId;
private String loginUrl; // 三方登录地址如果有值前端跳转到该地址
private SessionUser() {
}
public static SessionUser fromUser(UserDTO user, String sessionId) {
SessionUser sessionUser = new SessionUser();
BeanUtils.copyProperties(user, sessionUser);
List<String> infos = Arrays.asList(user.getId(), RandomStringUtils.randomAlphabetic(6), sessionId, StringUtils.EMPTY + System.currentTimeMillis());
sessionUser.csrfToken = CodingUtil.aesEncrypt(StringUtils.join(infos, "|"), secret, iv);
sessionUser.sessionId = sessionId;
return sessionUser;
}
}

View File

@ -1,14 +1,23 @@
package io.metersphere.sdk.dto; package io.metersphere.sdk.dto;
import io.metersphere.system.domain.User; import io.metersphere.system.domain.User;
import io.metersphere.system.domain.UserRole;
import io.metersphere.system.domain.UserRoleRelation;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.util.ArrayList;
import java.util.List;
@Data @Data
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
public class UserDTO extends User { public class UserDTO extends User {
private List<UserRole> userRoles = new ArrayList<>();
private List<UserRoleRelation> userRoleRelations = new ArrayList<>();
private List<UserRoleResourceDTO> userRolePermissions = new ArrayList<>();
@Schema(title = "其他平台对接信息", requiredMode = Schema.RequiredMode.NOT_REQUIRED) @Schema(title = "其他平台对接信息", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private byte[] platformInfo; private byte[] platformInfo;

View File

@ -0,0 +1,15 @@
package io.metersphere.sdk.dto;
import io.metersphere.system.domain.UserRole;
import io.metersphere.system.domain.UserRoleRelation;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class UserRolePermissionDTO {
List<UserRoleResourceDTO> list = new ArrayList<>();
List<UserRole> userRoles = new ArrayList<>();
List<UserRoleRelation> userRoleRelations = new ArrayList<>();
}

View File

@ -0,0 +1,20 @@
package io.metersphere.sdk.dto;
import io.metersphere.system.domain.UserRole;
import io.metersphere.system.domain.UserRolePermission;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class UserRoleResourceDTO implements Serializable {
private static final long serialVersionUID = 1L;
private GroupResource resource;
private List<UserRolePermission> permissions;
private String type;
private UserRole userRole;
private List<UserRolePermission> userRolePermissions;
}

View File

@ -7,16 +7,16 @@ public class MSException extends RuntimeException {
protected IResultCode errorCode; protected IResultCode errorCode;
protected MSException(String message) { public MSException(String message) {
super(message); super(message);
} }
protected MSException(IResultCode errorCode, String message) { public MSException(IResultCode errorCode, String message) {
super(message); super(message);
this.errorCode = errorCode; this.errorCode = errorCode;
} }
protected MSException(IResultCode errorCode, Throwable t) { public MSException(IResultCode errorCode, Throwable t) {
super(t); super(t);
this.errorCode = errorCode; this.errorCode = errorCode;
} }

View File

@ -0,0 +1,8 @@
package io.metersphere.sdk.mapper;
import io.metersphere.project.domain.Project;
public interface BaseProjectMapper {
Project selectOne();
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.sdk.mapper.BaseProjectMapper">
<select id="selectOne" resultType="io.metersphere.project.domain.Project">
SELECT *
FROM project
LIMIT 1
</select>
</mapper>

View File

@ -14,4 +14,6 @@ public interface BaseUserMapper {
void insert(User user); void insert(User user);
void batchSave(@Param("users") List<User> users); void batchSave(@Param("users") List<User> users);
boolean isSuperUser(String userId);
} }

View File

@ -1,33 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.sdk.mapper.BaseUserMapper"> <mapper namespace="io.metersphere.sdk.mapper.BaseUserMapper">
<select id="selectById" resultType="io.metersphere.sdk.dto.UserDTO">
<select id="selectById" resultType="io.metersphere.sdk.dto.UserDTO">
SELECT * SELECT *
FROM user FROM user
LEFT JOIN user_extend ON user.id = user_extend.id LEFT JOIN user_extend ON user.id = user_extend.id
WHERE user.id = #{id} WHERE user.id = #{id}
</select> </select>
<select id="findAll" resultType="io.metersphere.system.domain.User"> <select id="findAll" resultType="io.metersphere.system.domain.User">
SELECT * SELECT *
FROM user FROM user
</select> </select>
<insert id="insert"> <insert id="insert">
INSERT INTO user(id, name, email, password, status, create_time, update_time, language, last_workspace_id, phone, INSERT INTO user(id, name, email, password, status, create_time, update_time, language, last_workspace_id,
phone,
source, last_project_id, create_user) source, last_project_id, create_user)
VALUES (#{id}, #{name}, #{email}, #{password}, #{status}, #{createTime}, #{updateTime}, #{language}, VALUES (#{id}, #{name}, #{email}, #{password}, #{status}, #{createTime}, #{updateTime}, #{language},
#{lastWorkspaceId}, #{phone}, #{source}, #{lastProjectId}, #{createUser}) #{lastWorkspaceId}, #{phone}, #{source}, #{lastProjectId}, #{createUser})
</insert> </insert>
<insert id="batchSave"> <insert id="batchSave">
INSERT INTO user(id, name, email, password, status, create_time, update_time, language, last_workspace_id, phone, INSERT INTO user(id, name, email, password, status, create_time, update_time, language, last_workspace_id,
phone,
source, last_project_id, create_user) source, last_project_id, create_user)
VALUES VALUES
<foreach collection="users" item="user" separator=","> <foreach collection="users" item="user" separator=",">
(#{user.id}, #{user.name}, #{user.email}, #{user.password}, #{user.status}, #{user.createTime}, #{user.updateTime}, #{user.language}, (#{user.id}, #{user.name}, #{user.email}, #{user.password}, #{user.status}, #{user.createTime},
#{user.updateTime}, #{user.language},
#{user.lastWorkspaceId}, #{user.phone}, #{user.source}, #{user.lastProjectId}, #{user.createUser}) #{user.lastWorkspaceId}, #{user.phone}, #{user.source}, #{user.lastProjectId}, #{user.createUser})
</foreach> </foreach>
</insert> </insert>
<select id="isSuperUser" resultType="java.lang.Boolean">
SELECT COUNT(*)
FROM user_role_relation
WHERE user_id = #{userId}
AND role_id = 'super_group'
</select>
</mapper> </mapper>

View File

@ -0,0 +1,90 @@
package io.metersphere.sdk.security;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.dto.SessionUser;
import io.metersphere.sdk.util.CodingUtil;
import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.SessionUtils;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
public class CsrfFilter extends AnonymousFilter {
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
if (!SecurityUtils.getSubject().isAuthenticated()) {
((HttpServletResponse) response).setHeader(SessionConstants.AUTHENTICATION_STATUS, SessionConstants.AUTHENTICATION_INVALID);
return true;
}
// 错误页面不需要 csrf
if (WebUtils.toHttp(request).getRequestURI().equals("/error")) {
return true;
}
// todo api 过来的请求
// if (ApiKeyHandler.isApiKeyCall(WebUtils.toHttp(request))) {
// return true;
// }
// websocket 不需要csrf
String websocketKey = httpServletRequest.getHeader("Sec-WebSocket-Key");
if (StringUtils.isNotBlank(websocketKey)) {
return true;
}
// 请求头取出的token value
String csrfToken = httpServletRequest.getHeader(SessionConstants.CSRF_TOKEN);
String xAuthToken = httpServletRequest.getHeader(SessionConstants.HEADER_TOKEN);
// 校验 token
validateToken(csrfToken, xAuthToken);
// 校验 referer
validateReferer(httpServletRequest);
return true;
}
private void validateReferer(HttpServletRequest request) {
Environment env = CommonBeanFactory.getBean(Environment.class);
String domains = env.getProperty("referer.urls");
if (StringUtils.isBlank(domains)) {
// 没有配置不校验
return;
}
String[] split = StringUtils.split(domains, ",");
String referer = request.getHeader(HttpHeaders.REFERER);
if (split != null) {
if (!ArrayUtils.contains(split, referer)) {
throw new RuntimeException("csrf error");
}
}
}
private void validateToken(String csrfToken, String xAuthToken) {
if (StringUtils.isBlank(csrfToken)) {
throw new RuntimeException("csrf token is empty");
}
csrfToken = CodingUtil.aesDecrypt(csrfToken, SessionUser.secret, SessionUser.iv);
String[] signatureArray = StringUtils.split(StringUtils.trimToNull(csrfToken), "|");
if (signatureArray.length != 4) {
throw new RuntimeException("invalid token");
}
if (!StringUtils.equals(SessionUtils.getUserId(), signatureArray[0])) {
throw new RuntimeException("Please check csrf token.");
}
if (!StringUtils.equals(SessionUtils.getSessionId(), signatureArray[2]) &&
!StringUtils.equals(xAuthToken, signatureArray[2])) {
throw new RuntimeException("Please check csrf token.");
}
}
}

View File

@ -0,0 +1,84 @@
package io.metersphere.sdk.security;
import io.metersphere.project.domain.Project;
import io.metersphere.sdk.util.SessionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.shiro.aop.AnnotationResolver;
import org.apache.shiro.aop.MethodInvocation;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.aop.PermissionAnnotationMethodInterceptor;
import java.lang.reflect.Field;
import java.lang.reflect.Parameter;
public class MsPermissionAnnotationMethodInterceptor extends PermissionAnnotationMethodInterceptor {
public MsPermissionAnnotationMethodInterceptor(AnnotationResolver resolver) {
super(resolver);
}
@Override
public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
String projectId = null;
String workspaceId = null;
Object[] arguments = mi.getArguments();
if (ArrayUtils.isNotEmpty(arguments)) {
Parameter[] parameters = mi.getMethod().getParameters();
for (int i = 0; i < arguments.length; i++) {
Object argument = arguments[i];
if (argument instanceof String) {
if (StringUtils.equals(parameters[i].getName(), "projectId")) {
projectId = (String) argument;
}
if (StringUtils.equals(parameters[i].getName(), "workspaceId")) {
workspaceId = (String) argument;
}
} else {
try {
if (StringUtils.isEmpty(projectId) && isExistField(argument, "projectId")) {
projectId = (String) MethodUtils.invokeMethod(argument, "getProjectId");
}
if (StringUtils.equals(parameters[i].getName(), "project") && argument instanceof Project) {
projectId = ((Project) argument).getId();
}
if (StringUtils.isEmpty(workspaceId) && isExistField(argument, "workspaceId")) {
workspaceId = (String) MethodUtils.invokeMethod(argument, "getWorkspaceId");
}
} catch (Exception e) {
}
}
}
}
try {
SessionUtils.setCurrentWorkspaceId(workspaceId);
SessionUtils.setCurrentProjectId(projectId);
super.assertAuthorized(mi);
} finally {
SessionUtils.clearCurrentWorkspaceId();
SessionUtils.clearCurrentProjectId();
}
}
public static boolean isExistField(Object obj, String fieldName) {
if (obj == null || StringUtils.isEmpty(fieldName)) {
return false;
}
//获取这个类的所有属性
Field[] fields = FieldUtils.getAllFields(obj.getClass());
boolean flag = false;
//循环遍历所有的fields
for (Field field : fields) {
if (field.getName().equals(fieldName)) {
flag = true;
break;
}
}
return flag;
}
}

View File

@ -0,0 +1,36 @@
package io.metersphere.sdk.security;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.dto.SessionUser;
import io.metersphere.sdk.util.CodingUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import java.util.UUID;
public class SSOSessionHandler {
public static String random = UUID.randomUUID() + UUID.randomUUID().toString();
public static String validate(HttpServletRequest request) {
try {
String v = request.getHeader(SessionConstants.CSRF_TOKEN);
if (StringUtils.isNotBlank(v)) {
return validate(v);
}
} catch (Exception e) {
// LogUtil.error("failed to validate", e);
}
return null;
}
private static String validate(String csrfToken) {
csrfToken = CodingUtil.aesDecrypt(csrfToken, SessionUser.secret, SessionUser.iv);
String[] signatureArray = StringUtils.split(StringUtils.trimToNull(csrfToken), "|");
if (signatureArray.length != 4) {
throw new RuntimeException("invalid token");
}
return signatureArray[0];
}
}

View File

@ -0,0 +1,115 @@
package io.metersphere.sdk.security.realm;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.constants.UserSource;
import io.metersphere.sdk.dto.SessionUser;
import io.metersphere.sdk.dto.UserDTO;
import io.metersphere.sdk.service.BaseUserService;
import io.metersphere.sdk.util.SessionUtils;
import io.metersphere.sdk.util.Translator;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 自定义Realm 注入service 可能会导致在 service的aop 失效例如@Transactional,
* 解决方法
* <p>
* 1. 这里改成注入mapper这样mapper 中的事务失效<br/>
* 2. 这里仍然注入service在配置ShiroConfig 的时候不去set realm, 等到spring 初始化完成之后
* set realm
* </p>
*/
public class LocalRealm extends AuthorizingRealm {
private Logger logger = LoggerFactory.getLogger(LocalRealm.class);
@Resource
private BaseUserService baseUserService;
@Override
public String getName() {
return "LOCAL";
}
/**
* 角色认证
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
/**
* 登录认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
Session session = SecurityUtils.getSubject().getSession();
String login = (String) session.getAttribute("authenticate");
String userId = token.getUsername();
String password = String.valueOf(token.getPassword());
if (StringUtils.equals(login, UserSource.LOCAL.name())) {
return loginLocalMode(userId, password);
}
UserDTO user = getUserWithOutAuthenticate(userId);
userId = user.getId();
SessionUser sessionUser = SessionUser.fromUser(user, (String) session.getId());
session.setAttribute(SessionConstants.ATTR_USER, sessionUser);
return new SimpleAuthenticationInfo(userId, password, getName());
}
@Override
public boolean isPermitted(PrincipalCollection principals, String permission) {
return SessionUtils.hasPermission(SessionUtils.getCurrentWorkspaceId(), SessionUtils.getCurrentProjectId(), permission);
}
private UserDTO getUserWithOutAuthenticate(String userId) {
UserDTO user = baseUserService.getUserDTO(userId);
String msg;
if (user == null) {
user = baseUserService.getUserDTOByEmail(userId);
if (user == null) {
msg = "The user does not exist: " + userId;
logger.warn(msg);
throw new UnknownAccountException(Translator.get("password_is_incorrect"));
}
}
return user;
}
private AuthenticationInfo loginLocalMode(String userId, String password) {
UserDTO user = baseUserService.getUserDTO(userId);
String msg;
if (user == null) {
user = baseUserService.getUserDTOByEmail(userId, UserSource.LOCAL.name());
if (user == null) {
msg = "The user does not exist: " + userId;
logger.warn(msg);
throw new UnknownAccountException(Translator.get("password_is_incorrect"));
}
userId = user.getId();
}
// 密码验证
if (!baseUserService.checkUserPassword(userId, password)) {
throw new IncorrectCredentialsException(Translator.get("password_is_incorrect"));
}
SessionUser sessionUser = SessionUser.fromUser(user, SessionUtils.getSessionId());
SessionUtils.putUser(sessionUser);
return new SimpleAuthenticationInfo(userId, password, getName());
}
}

View File

@ -1,8 +1,399 @@
package io.metersphere.sdk.service; package io.metersphere.sdk.service;
import io.metersphere.project.domain.Project;
import io.metersphere.project.domain.ProjectExample;
import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.sdk.constants.UserRoleConstants;
import io.metersphere.sdk.constants.UserRoleType;
import io.metersphere.sdk.constants.UserSource;
import io.metersphere.sdk.constants.UserStatus;
import io.metersphere.sdk.controller.handler.ResultHolder;
import io.metersphere.sdk.dto.*;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.mapper.BaseProjectMapper;
import io.metersphere.sdk.mapper.BaseUserMapper;
import io.metersphere.sdk.util.CodingUtil;
import io.metersphere.sdk.util.SessionUtils;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.*;
import io.metersphere.system.mapper.UserMapper;
import io.metersphere.system.mapper.UserRoleMapper;
import io.metersphere.system.mapper.UserRolePermissionMapper;
import io.metersphere.system.mapper.UserRoleRelationMapper;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Service @Service
@Transactional(rollbackFor = Exception.class)
public class BaseUserService { public class BaseUserService {
@Resource
private BaseUserMapper baseUserMapper;
@Resource
private UserMapper userMapper;
@Resource
private UserRolePermissionMapper userRolePermissionMapper;
@Resource
private UserRoleMapper userRoleMapper;
@Resource
private UserRoleRelationMapper userRoleRelationMapper;
@Resource
private ProjectMapper projectMapper;
@Resource
private BaseProjectMapper baseProjectMapper;
public UserDTO getUserDTO(String userId) {
UserDTO userDTO = baseUserMapper.selectById(userId);
if (userDTO == null) {
return null;
}
if (StringUtils.equals(userDTO.getStatus(), UserStatus.DISABLED)) {
throw new DisabledAccountException();
}
UserRolePermissionDTO dto = getUserRolePermission(userId);
userDTO.setUserRoleRelations(dto.getUserRoleRelations());
userDTO.setUserRoles(dto.getUserRoles());
userDTO.setUserRolePermissions(dto.getList());
return userDTO;
}
public ResultHolder login(LoginRequest request) {
String login = (String) SecurityUtils.getSubject().getSession().getAttribute("authenticate");
String username = StringUtils.trim(request.getUsername());
String password = StringUtils.EMPTY;
if (!StringUtils.equals(login, UserSource.LDAP.name())) {
password = StringUtils.trim(request.getPassword());
}
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
if (subject.isAuthenticated()) {
SessionUser sessionUser = SessionUtils.getUser();
autoSwitch(sessionUser);
// 放入session中
SessionUtils.putUser(sessionUser);
return ResultHolder.success(sessionUser);
} else {
throw new MSException(Translator.get("login_fail"));
}
} catch (ExcessiveAttemptsException e) {
throw new ExcessiveAttemptsException(Translator.get("excessive_attempts"));
} catch (LockedAccountException e) {
throw new LockedAccountException(Translator.get("user_locked"));
} catch (DisabledAccountException e) {
throw new DisabledAccountException(Translator.get("user_has_been_disabled"));
} catch (ExpiredCredentialsException e) {
throw new ExpiredCredentialsException(Translator.get("user_expires"));
} catch (AuthenticationException e) {
throw new AuthenticationException(e.getMessage());
} catch (UnauthorizedException e) {
throw new UnauthorizedException(Translator.get("not_authorized") + e.getMessage());
}
}
private void autoSwitch(UserDTO user) {
// 用户有 last_project_id 权限
if (hasLastProjectPermission(user)) {
return;
}
// 用户有 last_workspace_id 权限
if (hasLastWorkspacePermission(user)) {
return;
}
// 判断其他权限
checkNewWorkspaceAndProject(user);
}
private void checkNewWorkspaceAndProject(UserDTO user) {
List<UserRoleRelation> userRoleRelations = user.getUserRoleRelations();
List<String> projectRoleIds = user.getUserRoles()
.stream().filter(ug -> StringUtils.equals(ug.getType(), UserRoleType.PROJECT))
.map(UserRole::getId)
.toList();
List<UserRoleRelation> project = userRoleRelations.stream().filter(ug -> projectRoleIds.contains(ug.getRoleId()))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(project)) {
List<String> workspaceIds = user.getUserRoles()
.stream()
.filter(ug -> StringUtils.equals(ug.getType(), UserRoleType.WORKSPACE))
.map(UserRole::getId)
.toList();
List<UserRoleRelation> workspaces = userRoleRelations.stream().filter(ug -> workspaceIds.contains(ug.getRoleId()))
.toList();
if (workspaces.size() > 0) {
String wsId = workspaces.get(0).getSourceId();
switchUserResource("workspace", wsId, user);
} else {
List<String> superRoleIds = user.getUserRoles()
.stream()
.map(UserRole::getId)
.filter(id -> StringUtils.equals(id, UserRoleConstants.SUPER_ROLE))
.collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(superRoleIds)) {
Project p = baseProjectMapper.selectOne();
if (p != null) {
switchSuperUserResource(p.getId(), p.getWorkspaceId(), user);
}
} else {
// 用户登录之后没有项目和工作空间的权限就把值清空
user.setLastWorkspaceId(StringUtils.EMPTY);
user.setLastProjectId(StringUtils.EMPTY);
updateUser(user);
}
}
} else {
UserRoleRelation userRoleRelation = project.stream().filter(p -> StringUtils.isNotBlank(p.getSourceId()))
.toList().get(0);
String projectId = userRoleRelation.getSourceId();
Project p = projectMapper.selectByPrimaryKey(projectId);
String wsId = p.getWorkspaceId();
user.setId(user.getId());
user.setLastProjectId(projectId);
user.setLastWorkspaceId(wsId);
updateUser(user);
}
}
private boolean hasLastProjectPermission(UserDTO user) {
if (StringUtils.isNotBlank(user.getLastProjectId())) {
List<UserRoleRelation> userRoleRelations = user.getUserRoleRelations().stream()
.filter(ug -> StringUtils.equals(user.getLastProjectId(), ug.getSourceId()))
.collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(userRoleRelations)) {
Project project = projectMapper.selectByPrimaryKey(user.getLastProjectId());
if (StringUtils.equals(project.getWorkspaceId(), user.getLastWorkspaceId())) {
return true;
}
// last_project_id last_workspace_id 对应不上了
user.setLastWorkspaceId(project.getWorkspaceId());
updateUser(user);
return true;
} else {
return baseUserMapper.isSuperUser(user.getId());
}
}
return false;
}
private boolean hasLastWorkspacePermission(UserDTO user) {
if (StringUtils.isNotBlank(user.getLastWorkspaceId())) {
List<UserRoleRelation> userRoleRelations = user.getUserRoleRelations().stream()
.filter(ug -> StringUtils.equals(user.getLastWorkspaceId(), ug.getSourceId()))
.collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(userRoleRelations)) {
ProjectExample example = new ProjectExample();
example.createCriteria().andWorkspaceIdEqualTo(user.getLastWorkspaceId());
List<Project> projects = projectMapper.selectByExample(example);
// 工作空间下没有项目
if (CollectionUtils.isEmpty(projects)) {
return true;
}
// 工作空间下有项目选中有权限的项目
List<String> projectIds = projects.stream()
.map(Project::getId)
.toList();
List<UserRoleRelation> roleRelations = user.getUserRoleRelations();
List<String> projectRoleIds = user.getUserRoles()
.stream().filter(ug -> StringUtils.equals(ug.getType(), UserRoleType.PROJECT))
.map(UserRole::getId)
.toList();
List<String> projectIdsWithPermission = roleRelations.stream().filter(ug -> projectRoleIds.contains(ug.getRoleId()))
.map(UserRoleRelation::getSourceId)
.filter(StringUtils::isNotBlank)
.filter(projectIds::contains)
.toList();
List<String> intersection = projectIds.stream().filter(projectIdsWithPermission::contains).collect(Collectors.toList());
// 当前工作空间下的所有项目都没有权限
if (CollectionUtils.isEmpty(intersection)) {
return true;
}
Project project = projects.stream().filter(p -> StringUtils.equals(intersection.get(0), p.getId())).findFirst().get();
String wsId = project.getWorkspaceId();
user.setId(user.getId());
user.setLastProjectId(project.getId());
user.setLastWorkspaceId(wsId);
updateUser(user);
return true;
} else {
return baseUserMapper.isSuperUser(user.getId());
}
}
return false;
}
public void switchUserResource(String sign, String sourceId, UserDTO sessionUser) {
// 获取最新UserDTO
UserDTO user = getUserDTO(sessionUser.getId());
User newUser = new User();
boolean isSuper = baseUserMapper.isSuperUser(sessionUser.getId());
if (StringUtils.equals("workspace", sign)) {
user.setLastWorkspaceId(sourceId);
sessionUser.setLastWorkspaceId(sourceId);
List<Project> projects = getProjectListByWsAndUserId(sessionUser.getId(), sourceId);
if (CollectionUtils.isNotEmpty(projects)) {
user.setLastProjectId(projects.get(0).getId());
} else {
if (isSuper) {
ProjectExample example = new ProjectExample();
example.createCriteria().andWorkspaceIdEqualTo(sourceId);
List<Project> allWsProject = projectMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(allWsProject)) {
user.setLastProjectId(allWsProject.get(0).getId());
}
} else {
user.setLastProjectId(StringUtils.EMPTY);
}
}
}
BeanUtils.copyProperties(user, newUser);
// 切换工作空间或组织之后更新 session 里的 user
SessionUtils.putUser(SessionUser.fromUser(user, SessionUtils.getSessionId()));
userMapper.updateByPrimaryKeySelective(newUser);
}
private void switchSuperUserResource(String projectId, String workspaceId, UserDTO sessionUser) {
// 获取最新UserDTO
UserDTO user = getUserDTO(sessionUser.getId());
User newUser = new User();
user.setLastWorkspaceId(workspaceId);
sessionUser.setLastWorkspaceId(workspaceId);
user.setLastProjectId(projectId);
BeanUtils.copyProperties(user, newUser);
// 切换工作空间或组织之后更新 session 里的 user
SessionUtils.putUser(SessionUser.fromUser(user, SessionUtils.getSessionId()));
userMapper.updateByPrimaryKeySelective(newUser);
}
public void updateUser(User user) {
// todo 提取重复代码
if (StringUtils.isNotBlank(user.getEmail())) {
UserExample example = new UserExample();
UserExample.Criteria criteria = example.createCriteria();
criteria.andEmailEqualTo(user.getEmail());
criteria.andIdNotEqualTo(user.getId());
if (userMapper.countByExample(example) > 0) {
MSException.throwException(Translator.get("user_email_already_exists"));
}
}
user.setPassword(null);
user.setUpdateTime(System.currentTimeMillis());
// 变更前
User userFromDB = userMapper.selectByPrimaryKey(user.getId());
// last workspace id 变了
if (user.getLastWorkspaceId() != null && !StringUtils.equals(user.getLastWorkspaceId(), userFromDB.getLastWorkspaceId())) {
List<Project> projects = getProjectListByWsAndUserId(user.getId(), user.getLastWorkspaceId());
if (projects.size() > 0) {
// 如果传入的 last_project_id last_workspace_id 下面的
boolean present = projects.stream().anyMatch(p -> StringUtils.equals(p.getId(), user.getLastProjectId()));
if (!present) {
user.setLastProjectId(projects.get(0).getId());
}
} else {
user.setLastProjectId(StringUtils.EMPTY);
}
}
// 执行变更
userMapper.updateByPrimaryKeySelective(user);
if (StringUtils.equals(user.getStatus(), UserStatus.DISABLED)) {
SessionUtils.kickOutUser(user.getId());
}
}
private List<Project> getProjectListByWsAndUserId(String userId, String workspaceId) {
ProjectExample projectExample = new ProjectExample();
projectExample.createCriteria().andWorkspaceIdEqualTo(workspaceId);
List<Project> projects = projectMapper.selectByExample(projectExample);
UserRoleRelationExample userRoleRelationExample = new UserRoleRelationExample();
userRoleRelationExample.createCriteria().andUserIdEqualTo(userId);
List<UserRoleRelation> userRoleRelations = userRoleRelationMapper.selectByExample(userRoleRelationExample);
List<Project> projectList = new ArrayList<>();
userRoleRelations.forEach(userRoleRelation -> projects.forEach(project -> {
if (StringUtils.equals(userRoleRelation.getSourceId(), project.getId())) {
if (!projectList.contains(project)) {
projectList.add(project);
}
}
}));
return projectList;
}
public UserDTO getUserDTOByEmail(String email, String... source) {
UserExample example = new UserExample();
UserExample.Criteria criteria = example.createCriteria();
criteria.andEmailEqualTo(email);
if (!CollectionUtils.isEmpty(Arrays.asList(source))) {
criteria.andSourceIn(Arrays.asList(source));
}
List<User> users = userMapper.selectByExample(example);
if (users == null || users.size() == 0) {
return null;
}
return getUserDTO(users.get(0).getId());
}
public boolean checkUserPassword(String userId, String password) {
if (StringUtils.isBlank(userId)) {
MSException.throwException(Translator.get("user_name_is_null"));
}
if (StringUtils.isBlank(password)) {
MSException.throwException(Translator.get("password_is_null"));
}
UserExample example = new UserExample();
example.createCriteria().andIdEqualTo(userId).andPasswordEqualTo(CodingUtil.md5(password));
return userMapper.countByExample(example) > 0;
}
private UserRolePermissionDTO getUserRolePermission(String userId) {
UserRolePermissionDTO permissionDTO = new UserRolePermissionDTO();
List<UserRoleResourceDTO> list = new ArrayList<>();
UserRoleRelationExample userRoleRelationExample = new UserRoleRelationExample();
userRoleRelationExample.createCriteria().andUserIdEqualTo(userId);
List<UserRoleRelation> userRoleRelations = userRoleRelationMapper.selectByExample(userRoleRelationExample);
if (CollectionUtils.isEmpty(userRoleRelations)) {
return permissionDTO;
}
permissionDTO.setUserRoleRelations(userRoleRelations);
List<String> roleList = userRoleRelations.stream().map(UserRoleRelation::getRoleId).collect(Collectors.toList());
UserRoleExample userRoleExample = new UserRoleExample();
userRoleExample.createCriteria().andIdIn(roleList);
List<UserRole> userRoles = userRoleMapper.selectByExample(userRoleExample);
permissionDTO.setUserRoles(userRoles);
for (UserRole gp : userRoles) {
UserRoleResourceDTO dto = new UserRoleResourceDTO();
dto.setUserRole(gp);
UserRolePermissionExample userRolePermissionExample = new UserRolePermissionExample();
userRolePermissionExample.createCriteria().andRoleIdEqualTo(gp.getId());
List<UserRolePermission> userRolePermissions = userRolePermissionMapper.selectByExample(userRolePermissionExample);
dto.setUserRolePermissions(userRolePermissions);
list.add(dto);
}
permissionDTO.setList(list);
return permissionDTO;
}
} }

View File

@ -0,0 +1,50 @@
package io.metersphere.sdk.util;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.function.Function;
public class CommonBeanFactory implements ApplicationContextAware {
private static ApplicationContext context;
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
context = ctx;
}
public static Object getBean(String beanName) {
try {
return context != null && !StringUtils.isBlank(beanName) ? context.getBean(beanName) : null;
} catch (BeansException e) {
return null;
}
}
public static <T> T getBean(Class<T> className) {
try {
return context != null && className != null ? context.getBean(className) : null;
} catch (BeansException e) {
return null;
}
}
public static <T> Map<String, T> getBeansOfType(Class<T> className) {
return context.getBeansOfType(className);
}
public static Object invoke(String beanName, Function<Class, Method> methodFunction, Object... args) {
Object bean = getBean(beanName);
try {
Class<?> clazz = bean.getClass();
return methodFunction.apply(clazz).invoke(bean, args);
} catch (Exception e) {
// LogUtil.error(e);
}
return null;
}
}

View File

@ -0,0 +1,67 @@
package io.metersphere.sdk.util;
import java.util.HashMap;
import java.util.Map;
public class FilterChainUtils {
public static Map<String, String> loadBaseFilterChain() {
Map<String, String> filterChainDefinitionMap = new HashMap<>();
filterChainDefinitionMap.put("/*.html", "anon");
filterChainDefinitionMap.put("/signin", "anon");
filterChainDefinitionMap.put("/ldap/signin", "anon");
filterChainDefinitionMap.put("/ldap/open", "anon");
filterChainDefinitionMap.put("/signout", "anon");
filterChainDefinitionMap.put("/is-login", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/assets/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/display/info", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/display/file/**", "anon");
filterChainDefinitionMap.put("/jmeter/download/**", "anon");
filterChainDefinitionMap.put("/jmeter/ping", "anon");
filterChainDefinitionMap.put("/jmeter/ready/**", "anon");
filterChainDefinitionMap.put("/authsource/list/allenable", "anon");
filterChainDefinitionMap.put("/sso/callback/**", "anon");
filterChainDefinitionMap.put("/license/validate", "anon");
// for swagger
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger-ui/**", "anon");
filterChainDefinitionMap.put("/v3/api-docs/**", "anon");
filterChainDefinitionMap.put("/403", "anon");
filterChainDefinitionMap.put("/anonymous/**", "anon");
//分享相关接口
filterChainDefinitionMap.put("/system/theme", "anon");
filterChainDefinitionMap.put("/system/save/baseurl/**", "anon");
filterChainDefinitionMap.put("/system/timeout", "anon");
filterChainDefinitionMap.put("/file/metadata/info/**", "anon");
// consul
filterChainDefinitionMap.put("/v1/catalog/**", "anon");
filterChainDefinitionMap.put("/v1/agent/**", "anon");
filterChainDefinitionMap.put("/v1/health/**", "anon");
//mock接口
filterChainDefinitionMap.put("/mock/**", "anon");
filterChainDefinitionMap.put("/ws/**", "anon");
//
filterChainDefinitionMap.put("/performance/update/cache", "anon");
// websocket
filterChainDefinitionMap.put("/websocket/**", "csrf");
return filterChainDefinitionMap;
}
public static Map<String, String> ignoreCsrfFilter() {
Map<String, String> filterChainDefinitionMap = new HashMap<>();
filterChainDefinitionMap.put("/", "apikey, authc"); // 跳转到 / 不用校验 csrf
filterChainDefinitionMap.put("/language", "apikey, authc");// 跳转到 /language 不用校验 csrf
filterChainDefinitionMap.put("/mock", "apikey, authc"); // 跳转到 /mock接口 不用校验 csrf
return filterChainDefinitionMap;
}
}

View File

@ -0,0 +1,18 @@
package io.metersphere.sdk.util;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
@Setter
@Getter
public class RsaKey implements Serializable {
//公钥
private String publicKey;
//私钥
private String privateKey;
}

View File

@ -0,0 +1,236 @@
package io.metersphere.sdk.util;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class RsaUtil {
public static final String CHARSET = StandardCharsets.UTF_8.name();
public static final String RSA_ALGORITHM = "RSA";
private static RsaKey rsaKey;
/**
* 创建RSA 公钥-私钥
*/
public static RsaKey getRsaKey() throws NoSuchAlgorithmException {
if (rsaKey == null) {
rsaKey = createKeys();
}
return rsaKey;
}
public static void setRsaKey(RsaKey rsaKey) throws NoSuchAlgorithmException {
// 放到缓存里
RsaUtil.rsaKey = rsaKey;
}
public static RsaKey createKeys() throws NoSuchAlgorithmException {
return createKeys(1024);
}
/**
* 创建RSA 公钥-私钥
*/
public static RsaKey createKeys(int keySize) throws NoSuchAlgorithmException {
//为RSA算法创建一个KeyPairGenerator对象
KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
//初始化KeyPairGenerator对象,密钥长度
kpg.initialize(keySize);
//生成密匙对
KeyPair keyPair = kpg.generateKeyPair();
//得到公钥
Key publicKey = keyPair.getPublic();
String publicKeyStr = new String(Base64.encodeBase64(publicKey.getEncoded()));
//得到私钥
Key privateKey = keyPair.getPrivate();
String privateKeyStr = new String(Base64.encodeBase64(privateKey.getEncoded()));
RsaKey rsaKey = new RsaKey();
rsaKey.setPublicKey(publicKeyStr);
rsaKey.setPrivateKey(privateKeyStr);
return rsaKey;
}
/**
* 公钥加密
*
* @param originalText 原文
* @param publicKey 公钥
*/
public static String publicEncrypt(String originalText, String publicKey) throws NoSuchAlgorithmException {
RSAPublicKey rsaPublicKey = getPublicKey(publicKey);
return publicEncrypt(originalText, rsaPublicKey);
}
/**
* 公钥解密
*
* @param cipherText 密文
* @param publicKey 公钥
*/
public static String publicDecrypt(String cipherText, String publicKey) throws NoSuchAlgorithmException {
RSAPublicKey rsaPublicKey = getPublicKey(publicKey);
return publicDecrypt(cipherText, rsaPublicKey);
}
/**
* 私钥加密
*
* @param originalText 原文
* @param privateKey 私钥
*/
public static String privateEncrypt(String originalText, String privateKey) throws NoSuchAlgorithmException {
RSAPrivateKey rsaPrivateKey = getPrivateKey(privateKey);
return privateEncrypt(originalText, rsaPrivateKey);
}
/**
* 私钥解密
*
* @param cipherText 密文
* @param privateKey 私钥
*/
public static String privateDecrypt(String cipherText, String privateKey) throws NoSuchAlgorithmException {
RSAPrivateKey rsaPrivateKey = getPrivateKey(privateKey);
return privateDecrypt(cipherText, rsaPrivateKey);
}
/**
* 得到公钥
*
* @param publicKey 密钥字符串经过base64编码
*/
private static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException {
//通过X509编码的Key指令获得公钥对象
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
RSAPublicKey key = null;
try {
key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
} catch (InvalidKeySpecException e) {
}
return key;
}
/**
* 公钥加密
*
* @param originalText 原文
* @param publicKey 公钥
*/
private static String publicEncrypt(String originalText, RSAPublicKey publicKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, originalText.getBytes(CHARSET), publicKey.getModulus().bitLength()));
} catch (Exception e) {
throw new RuntimeException("加密字符串[" + originalText + "]时遇到异常", e);
}
}
/**
* 得到私钥
*
* @param privateKey 密钥字符串经过base64编码
*/
private static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException {
//通过PKCS#8编码的Key指令获得私钥对象
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
RSAPrivateKey key = null;
try {
key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
} catch (InvalidKeySpecException e) {
}
return key;
}
/**
* 私钥解密
*
* @param cipherText 密文
* @param privateKey 私钥
*/
private static String privateDecrypt(String cipherText, RSAPrivateKey privateKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(cipherText), privateKey.getModulus().bitLength()), CHARSET);
} catch (Exception e) {
throw new RuntimeException("解密字符串[" + cipherText + "]时遇到异常", e);
}
}
private static String privateEncrypt(String originalText, RSAPrivateKey privateKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, originalText.getBytes(CHARSET), privateKey.getModulus().bitLength()));
} catch (Exception e) {
throw new RuntimeException("加密字符串[" + originalText + "]时遇到异常", e);
}
}
private static String publicDecrypt(String cipherText, RSAPublicKey publicKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(cipherText), publicKey.getModulus().bitLength()), CHARSET);
} catch (Exception e) {
throw new RuntimeException("解密字符串[" + cipherText + "]时遇到异常", e);
}
}
private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
int maxBlock;
if (opmode == Cipher.DECRYPT_MODE) {
maxBlock = keySize / 8;
} else {
maxBlock = keySize / 8 - 11;
}
int offSet = 0;
byte[] buff;
int i = 0;
try (
ByteArrayOutputStream out = new ByteArrayOutputStream()
) {
while (datas.length > offSet) {
if (datas.length - offSet > maxBlock) {
buff = cipher.doFinal(datas, offSet, maxBlock);
} else {
buff = cipher.doFinal(datas, offSet, datas.length - offSet);
}
out.write(buff, 0, buff.length);
i++;
offSet = i * maxBlock;
}
return out.toByteArray();
} catch (Exception e) {
throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);
}
}
}

View File

@ -0,0 +1,202 @@
package io.metersphere.sdk.util;
import io.metersphere.sdk.constants.UserRoleConstants;
import io.metersphere.sdk.dto.SessionUser;
import io.metersphere.system.domain.UserRole;
import io.metersphere.system.domain.UserRolePermission;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.*;
import java.util.stream.Collectors;
import static io.metersphere.sdk.constants.SessionConstants.ATTR_USER;
public class SessionUtils {
private static final ThreadLocal<String> projectId = new ThreadLocal<>();
private static final ThreadLocal<String> workspaceId = new ThreadLocal<>();
public static String getUserId() {
SessionUser user = getUser();
return user == null ? null : user.getId();
}
public static SessionUser getUser() {
try {
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
return (SessionUser) session.getAttribute(ATTR_USER);
} catch (Exception e) {
LogUtils.warn("后台获取在线用户失败: " + e.getMessage());
return null;
}
}
public static String getSessionId() {
return (String) SecurityUtils.getSubject().getSession().getId();
}
/**
* 踢除用户
*
* @param username
*/
public static void kickOutUser(String username) {
// redis session
RedisIndexedSessionRepository sessionRepository = CommonBeanFactory.getBean(RedisIndexedSessionRepository.class);
if (sessionRepository == null) {
return;
}
Map<String, ?> users = sessionRepository.findByPrincipalName(username);
if (MapUtils.isNotEmpty(users)) {
users.keySet().forEach(k -> {
sessionRepository.deleteById(k);
sessionRepository.getSessionRedisOperations().delete("spring:session:sessions:" + k);
});
}
}
//
public static void putUser(SessionUser sessionUser) {
SecurityUtils.getSubject().getSession().setAttribute(ATTR_USER, sessionUser);
SecurityUtils.getSubject().getSession().setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, sessionUser.getId());
}
/**
* 权限验证时从 controller 参数列表中找到 workspaceId 传入
*/
public static void setCurrentWorkspaceId(String workspaceId) {
SessionUtils.workspaceId.set(workspaceId);
}
/**
* 权限验证时从 controller 参数列表中找到 projectId 传入
*/
public static void setCurrentProjectId(String projectId) {
SessionUtils.projectId.set(projectId);
}
public static String getCurrentWorkspaceId() {
if (StringUtils.isNotEmpty(workspaceId.get())) {
return workspaceId.get();
}
try {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
LogUtils.debug("WORKSPACE: {}", request.getHeader("WORKSPACE"));
if (request.getHeader("WORKSPACE") != null) {
return request.getHeader("WORKSPACE");
}
} catch (Exception e) {
LogUtils.error(e.getMessage(), e);
}
return getUser().getLastWorkspaceId();
}
public static String getCurrentProjectId() {
if (StringUtils.isNotEmpty(projectId.get())) {
return projectId.get();
}
try {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
LogUtils.debug("PROJECT: {}", request.getHeader("PROJECT"));
if (request.getHeader("PROJECT") != null) {
return request.getHeader("PROJECT");
}
} catch (Exception e) {
LogUtils.error(e.getMessage(), e);
}
return getUser().getLastProjectId();
}
public static String getHttpHeader(String headerName) {
if (StringUtils.isBlank(headerName)) {
return null;
}
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
return request.getHeader(headerName);
} catch (Exception e) {
return null;
}
}
public static boolean hasPermission(String workspaceId, String projectId, String permission) {
Map<String, List<UserRolePermission>> userRolePermissions = new HashMap<>();
Map<String, UserRole> group = new HashMap<>();
SessionUser user = Objects.requireNonNull(SessionUtils.getUser());
user.getUserRoleRelations().forEach(ug -> user.getUserRolePermissions().forEach(gp -> {
if (StringUtils.equals(gp.getUserRole().getId(), ug.getRoleId())) {
userRolePermissions.put(ug.getId(), gp.getUserRolePermissions());
group.put(ug.getId(), gp.getUserRole());
}
}));
long count = user.getUserRoles()
.stream()
.filter(g -> StringUtils.equals(g.getId(), UserRoleConstants.SUPER_ROLE))
.count();
if (count > 0) {
return true;
}
Set<String> currentProjectPermissions = getCurrentProjectPermissions(userRolePermissions, projectId, group, user);
if (currentProjectPermissions.contains(permission)) {
return true;
}
Set<String> currentWorkspacePermissions = getCurrentWorkspacePermissions(userRolePermissions, workspaceId, group, user);
if (currentWorkspacePermissions.contains(permission)) {
return true;
}
Set<String> systemPermissions = getSystemPermissions(userRolePermissions, group, user);
return systemPermissions.contains(permission);
}
private static Set<String> getSystemPermissions(Map<String, List<UserRolePermission>> userRolePermissions, Map<String, UserRole> group, SessionUser user) {
return user.getUserRoleRelations().stream()
.filter(ug -> group.get(ug.getId()) != null && StringUtils.equals(group.get(ug.getId()).getType(), "SYSTEM"))
.filter(ug -> StringUtils.equals(ug.getSourceId(), "system") || StringUtils.equals(ug.getSourceId(), "'adminSourceId'"))
.flatMap(ug -> userRolePermissions.get(ug.getId()).stream())
.map(UserRolePermission::getPermissionId)
.collect(Collectors.toSet());
}
private static Set<String> getCurrentWorkspacePermissions(Map<String, List<UserRolePermission>> userRolePermissions, String workspaceId, Map<String, UserRole> group, SessionUser user) {
return user.getUserRoleRelations().stream()
.filter(ug -> group.get(ug.getId()) != null && StringUtils.equals(group.get(ug.getId()).getType(), "WORKSPACE"))
.filter(ug -> StringUtils.equals(ug.getSourceId(), workspaceId))
.flatMap(ug -> userRolePermissions.get(ug.getId()).stream())
.map(UserRolePermission::getPermissionId)
.collect(Collectors.toSet());
}
private static Set<String> getCurrentProjectPermissions(Map<String, List<UserRolePermission>> userRolePermissions, String projectId, Map<String, UserRole> group, SessionUser user) {
return user.getUserRoleRelations().stream()
.filter(ug -> group.get(ug.getId()) != null && StringUtils.equals(group.get(ug.getId()).getType(), "PROJECT"))
.filter(ug -> StringUtils.equals(ug.getSourceId(), projectId))
.flatMap(ug -> userRolePermissions.get(ug.getId()).stream())
.map(UserRolePermission::getPermissionId)
.collect(Collectors.toSet());
}
public static void clearCurrentWorkspaceId() {
workspaceId.remove();
}
public static void clearCurrentProjectId() {
projectId.remove();
}
}

View File

@ -1,8 +1,10 @@
package io.metersphere.api.controller; package io.metersphere.api.controller;
import com.jayway.jsonpath.JsonPath;
import io.metersphere.api.domain.ApiDefinition; import io.metersphere.api.domain.ApiDefinition;
import io.metersphere.api.dto.ApiDefinitionDTO; import io.metersphere.api.dto.ApiDefinitionDTO;
import io.metersphere.api.dto.ApiDefinitionListRequest; import io.metersphere.api.dto.ApiDefinitionListRequest;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.controller.handler.ResultHolder; import io.metersphere.sdk.controller.handler.ResultHolder;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils; import io.metersphere.sdk.util.LogUtils;
@ -25,7 +27,7 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import java.util.List; import java.util.List;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest @SpringBootTest
@ -35,12 +37,29 @@ public class ApiDefinitionControllerTests {
@Resource @Resource
private MockMvc mockMvc; private MockMvc mockMvc;
private final static String prefix = "/api/definition"; private final static String prefix = "/api/definition";
private static String sessionId;
private static String csrfToken;
@Before @Before
public void init() { public void init() {
LogUtils.info("init base api test"); LogUtils.info("init base api test");
} }
@Test
@Order(0)
public void login() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/signin")
.content("{\"username\":\"admin\",\"password\":\"metersphere\"}")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
sessionId = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.sessionId");
csrfToken = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.csrfToken");
}
@Test @Test
@Order(1) @Order(1)
public void testCreate() throws Exception { public void testCreate() throws Exception {
@ -71,9 +90,11 @@ public class ApiDefinitionControllerTests {
mockMvc.perform(MockMvcRequestBuilders.multipart(prefix + "/add") mockMvc.perform(MockMvcRequestBuilders.multipart(prefix + "/add")
.file(file) .file(file)
.contentType(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE)
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JsonUtils.toJSONString(request))) .content(JsonUtils.toJSONString(request)))
.andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.data.id").value("test-api-id")); .andExpect(jsonPath("$.data.id").value("test-api-id"));
} }
@ -93,8 +114,10 @@ public class ApiDefinitionControllerTests {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.multipart(prefix + "/page") MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.multipart(prefix + "/page")
.contentType(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE)
.content(JsonUtils.toJSONString(request))) .content(JsonUtils.toJSONString(request))
.andExpect(MockMvcResultMatchers.status().isOk()) .header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken))
.andExpect(status().isOk())
.andReturn(); .andReturn();
MockHttpServletResponse mockResponse = mvcResult.getResponse(); MockHttpServletResponse mockResponse = mvcResult.getResponse();
@ -125,8 +148,10 @@ public class ApiDefinitionControllerTests {
request.setPageSize(20); request.setPageSize(20);
mockMvc.perform(MockMvcRequestBuilders.multipart(prefix + "/page") mockMvc.perform(MockMvcRequestBuilders.multipart(prefix + "/page")
.contentType(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE)
.content(JsonUtils.toJSONString(request))) .content(JsonUtils.toJSONString(request))
.andExpect(MockMvcResultMatchers.status().isBadRequest()); .header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken))
.andExpect(status().isBadRequest());
//pageSize为空 //pageSize为空
request = new ApiDefinitionListRequest(); request = new ApiDefinitionListRequest();
@ -134,8 +159,10 @@ public class ApiDefinitionControllerTests {
request.setProjectId("test-project-id"); request.setProjectId("test-project-id");
mockMvc.perform(MockMvcRequestBuilders.multipart(prefix + "/page") mockMvc.perform(MockMvcRequestBuilders.multipart(prefix + "/page")
.contentType(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE)
.content(JsonUtils.toJSONString(request))) .content(JsonUtils.toJSONString(request))
.andExpect(MockMvcResultMatchers.status().isBadRequest()); .header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken))
.andExpect(status().isBadRequest());
//current为空 //current为空
request = new ApiDefinitionListRequest(); request = new ApiDefinitionListRequest();
@ -143,8 +170,10 @@ public class ApiDefinitionControllerTests {
request.setProjectId("test-project-id"); request.setProjectId("test-project-id");
mockMvc.perform(MockMvcRequestBuilders.multipart(prefix + "/page") mockMvc.perform(MockMvcRequestBuilders.multipart(prefix + "/page")
.contentType(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE)
.content(JsonUtils.toJSONString(request))) .content(JsonUtils.toJSONString(request))
.andExpect(MockMvcResultMatchers.status().isBadRequest()); .header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken))
.andExpect(status().isBadRequest());
} }
} }

View File

@ -1,6 +1,8 @@
package io.metersphere.functional.controller; package io.metersphere.functional.controller;
import com.jayway.jsonpath.JsonPath;
import io.metersphere.functional.domain.CaseReview; import io.metersphere.functional.domain.CaseReview;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.MethodOrderer;
@ -11,6 +13,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
@ -25,6 +28,23 @@ public class CaseReviewControllerTests {
@Resource @Resource
private MockMvc mockMvc; private MockMvc mockMvc;
private static String sessionId;
private static String csrfToken;
@Test
@Order(0)
public void login() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/signin")
.content("{\"username\":\"admin\",\"password\":\"metersphere\"}")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
sessionId = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.sessionId");
csrfToken = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.csrfToken");
}
@Test @Test
@Order(1) @Order(1)
public void testAddCaseReview() throws Exception { public void testAddCaseReview() throws Exception {
@ -40,6 +60,8 @@ public class CaseReviewControllerTests {
caseReview.setReviewPassRule("SINGLE"); caseReview.setReviewPassRule("SINGLE");
mockMvc.perform(MockMvcRequestBuilders.post("/case/review/add") mockMvc.perform(MockMvcRequestBuilders.post("/case/review/add")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(caseReview)) .content(JSON.toJSONString(caseReview))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -53,6 +75,8 @@ public class CaseReviewControllerTests {
caseReview.setName("test"); caseReview.setName("test");
mockMvc.perform(MockMvcRequestBuilders.post("/case/review/add") mockMvc.perform(MockMvcRequestBuilders.post("/case/review/add")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(caseReview)) .content(JSON.toJSONString(caseReview))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
@ -65,6 +89,8 @@ public class CaseReviewControllerTests {
CaseReview caseReview = new CaseReview(); CaseReview caseReview = new CaseReview();
caseReview.setId("case-review-id"); caseReview.setId("case-review-id");
mockMvc.perform(MockMvcRequestBuilders.post("/case/review/delete") mockMvc.perform(MockMvcRequestBuilders.post("/case/review/delete")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(caseReview)) .content(JSON.toJSONString(caseReview))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
@ -74,7 +100,9 @@ public class CaseReviewControllerTests {
@Test @Test
@Order(4) @Order(4)
public void testGetCaseReview() throws Exception { public void testGetCaseReview() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/case/review/get/case-review-id")) mockMvc.perform(MockMvcRequestBuilders.get("/case/review/get/case-review-id")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken))
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
} }

View File

@ -1,13 +1,19 @@
package io.metersphere.issue.controller; package io.metersphere.issue.controller;
import com.jayway.jsonpath.JsonPath;
import io.metersphere.issue.domain.Issue; import io.metersphere.issue.domain.Issue;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.util.UUID; import java.util.UUID;
@ -18,11 +24,29 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@SpringBootTest @SpringBootTest
@AutoConfigureMockMvc @AutoConfigureMockMvc
class IssueControllerTest { @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class IssueControllerTest {
@Resource @Resource
private MockMvc mockMvc; private MockMvc mockMvc;
private static String sessionId;
private static String csrfToken;
@Test
@Order(0)
public void login() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/signin")
.content("{\"username\":\"admin\",\"password\":\"metersphere\"}")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
sessionId = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.sessionId");
csrfToken = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.csrfToken");
}
@Test @Test
void listAll() { void listAll() {
} }
@ -45,6 +69,8 @@ class IssueControllerTest {
mockMvc.perform( mockMvc.perform(
MockMvcRequestBuilders.post("/issue/add") MockMvcRequestBuilders.post("/issue/add")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(issue)) .content(JSON.toJSONString(issue))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
// 检查状态 // 检查状态
@ -58,6 +84,8 @@ class IssueControllerTest {
// 缺陷已存在校验 // 缺陷已存在校验
mockMvc.perform( mockMvc.perform(
MockMvcRequestBuilders.post("/issue/add") MockMvcRequestBuilders.post("/issue/add")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(issue)) .content(JSON.toJSONString(issue))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
// 检查失败状态码 // 检查失败状态码

View File

@ -1,6 +1,8 @@
package io.metersphere.project.controller; package io.metersphere.project.controller;
import com.jayway.jsonpath.JsonPath;
import io.metersphere.project.domain.ProjectApplication; import io.metersphere.project.domain.ProjectApplication;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.MethodOrderer;
@ -11,9 +13,9 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@ -23,6 +25,22 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
public class ProjectApplicationControllerTests { public class ProjectApplicationControllerTests {
@Resource @Resource
private MockMvc mockMvc; private MockMvc mockMvc;
private static String sessionId;
private static String csrfToken;
@Test
@Order(0)
public void login() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/signin")
.content("{\"username\":\"admin\",\"password\":\"metersphere\"}")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
sessionId = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.sessionId");
csrfToken = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.csrfToken");
}
@Test @Test
@Order(1) @Order(1)
@ -32,6 +50,8 @@ public class ProjectApplicationControllerTests {
projectApplication.setType("1"); projectApplication.setType("1");
projectApplication.setTypeValue("1"); projectApplication.setTypeValue("1");
mockMvc.perform(MockMvcRequestBuilders.post("/project/application/save") mockMvc.perform(MockMvcRequestBuilders.post("/project/application/save")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(projectApplication)) .content(JSON.toJSONString(projectApplication))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -47,6 +67,8 @@ public class ProjectApplicationControllerTests {
projectApplication.setType("1"); projectApplication.setType("1");
projectApplication.setTypeValue("2"); projectApplication.setTypeValue("2");
mockMvc.perform(MockMvcRequestBuilders.post("/project/application/update") mockMvc.perform(MockMvcRequestBuilders.post("/project/application/update")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(projectApplication)) .content(JSON.toJSONString(projectApplication))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -57,7 +79,9 @@ public class ProjectApplicationControllerTests {
@Test @Test
@Order(3) @Order(3)
public void testListApp() throws Exception { public void testListApp() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/project/application/list/1")) mockMvc.perform(MockMvcRequestBuilders.get("/project/application/list/1")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.data[0].typeValue").value("2")); .andExpect(jsonPath("$.data[0].typeValue").value("2"));

View File

@ -2,6 +2,7 @@ package io.metersphere.project.controller;
import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.JsonPath;
import io.metersphere.project.domain.Project; import io.metersphere.project.domain.Project;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.MethodOrderer;
@ -12,6 +13,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
@ -27,6 +29,22 @@ public class ProjectControllerTests {
private MockMvc mockMvc; private MockMvc mockMvc;
private static String projectId; private static String projectId;
private static String sessionId;
private static String csrfToken;
@Test
@Order(0)
public void login() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/signin")
.content("{\"username\":\"admin\",\"password\":\"metersphere\"}")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
sessionId = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.sessionId");
csrfToken = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.csrfToken");
}
// 添加项目 // 添加项目
@Test @Test
@ -38,6 +56,8 @@ public class ProjectControllerTests {
project.setWorkspaceId("default"); project.setWorkspaceId("default");
var result = mockMvc.perform(MockMvcRequestBuilders.post("/project/add") var result = mockMvc.perform(MockMvcRequestBuilders.post("/project/add")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(project)) .content(JSON.toJSONString(project))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -56,6 +76,8 @@ public class ProjectControllerTests {
project.setWorkspaceId("default"); project.setWorkspaceId("default");
mockMvc.perform(MockMvcRequestBuilders.post("/project/edit") mockMvc.perform(MockMvcRequestBuilders.post("/project/edit")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(project)) .content(JSON.toJSONString(project))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -66,7 +88,9 @@ public class ProjectControllerTests {
@Test @Test
@Order(3) @Order(3)
public void testSelectAll() throws Exception { public void testSelectAll() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/project/list-all")) mockMvc.perform(MockMvcRequestBuilders.get("/project/list-all")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().contentType(MediaType.APPLICATION_JSON))
// .andExpect(jsonPath("$.person.name").value("Jason")) // .andExpect(jsonPath("$.person.name").value("Jason"))

View File

@ -1,5 +1,7 @@
package io.metersphere.system.controller; package io.metersphere.system.controller;
import com.jayway.jsonpath.JsonPath;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.dto.UserDTO; import io.metersphere.sdk.dto.UserDTO;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import io.metersphere.system.domain.User; import io.metersphere.system.domain.User;
@ -12,6 +14,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.util.ArrayList; import java.util.ArrayList;
@ -25,13 +28,28 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
public class UserControllerTests { public class UserControllerTests {
@Resource @Resource
private MockMvc mockMvc; private MockMvc mockMvc;
private static String sessionId;
private static String csrfToken;
@Test
@Order(0)
public void login() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/signin")
.content("{\"username\":\"admin\",\"password\":\"metersphere\"}")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
sessionId = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.sessionId");
csrfToken = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.csrfToken");
}
@Test @Test
@Order(1) @Order(1)
public void testAddUser() throws Exception { public void testAddUser() throws Exception {
UserDTO user = new UserDTO(); UserDTO user = new UserDTO();
user.setId("admin"); user.setId("user");
user.setName("admin"); user.setName("user");
user.setSource("LOCAL"); user.setSource("LOCAL");
user.setEmail("bin@fit2cloud.com"); user.setEmail("bin@fit2cloud.com");
user.setStatus("enabled"); user.setStatus("enabled");
@ -39,6 +57,8 @@ public class UserControllerTests {
user.setSeleniumServer("http://localhost:4444"); user.setSeleniumServer("http://localhost:4444");
mockMvc.perform(MockMvcRequestBuilders.post("/user/add") mockMvc.perform(MockMvcRequestBuilders.post("/user/add")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(user)) .content(JSON.toJSONString(user))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -49,11 +69,13 @@ public class UserControllerTests {
@Order(2) @Order(2)
public void testAddUserFailed() throws Exception { public void testAddUserFailed() throws Exception {
UserDTO user = new UserDTO(); UserDTO user = new UserDTO();
user.setId("admin2"); user.setId("user2");
user.setSeleniumServer("http://localhost:4444"); user.setSeleniumServer("http://localhost:4444");
mockMvc.perform(MockMvcRequestBuilders.post("/user/add") mockMvc.perform(MockMvcRequestBuilders.post("/user/add")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(user)) .content(JSON.toJSONString(user))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
@ -63,10 +85,12 @@ public class UserControllerTests {
@Test @Test
@Order(3) @Order(3)
public void testGetUser() throws Exception { public void testGetUser() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/user/get/admin")) mockMvc.perform(MockMvcRequestBuilders.get("/user/get/user")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.data.id").value("admin")); .andExpect(jsonPath("$.data.id").value("user"));
} }
@ -74,11 +98,13 @@ public class UserControllerTests {
@Order(4) @Order(4)
public void testUpdateUser() throws Exception { public void testUpdateUser() throws Exception {
UserDTO user = new UserDTO(); UserDTO user = new UserDTO();
user.setId("admin"); user.setId("user");
user.setName("Administrator"); user.setName("useristrator");
mockMvc.perform(MockMvcRequestBuilders.post("/user/update") mockMvc.perform(MockMvcRequestBuilders.post("/user/update")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(user)) .content(JSON.toJSONString(user))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -89,10 +115,12 @@ public class UserControllerTests {
@Order(5) @Order(5)
public void testDeleteUser() throws Exception { public void testDeleteUser() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/user/delete/admin")) mockMvc.perform(MockMvcRequestBuilders.get("/user/delete/user")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.data.id").value("admin")); .andExpect(jsonPath("$.data.id").value("user"));
} }
@Test @Test
@ -111,15 +139,19 @@ public class UserControllerTests {
} }
mockMvc.perform(MockMvcRequestBuilders.post("/user/batch-add3") mockMvc.perform(MockMvcRequestBuilders.post("/user/batch-add3")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(users)) .content(JSON.toJSONString(users))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON)); .andExpect(content().contentType(MediaType.APPLICATION_JSON));
mockMvc.perform(MockMvcRequestBuilders.get("/user/count") // mockMvc.perform(MockMvcRequestBuilders.get("/user/count")
.contentType(MediaType.APPLICATION_JSON)) // .header(SessionConstants.HEADER_TOKEN, sessionId)
.andExpect(status().isOk()) // .header(SessionConstants.CSRF_TOKEN, csrfToken)
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) // .contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.data").value(size)); // .andExpect(status().isOk())
// .andExpect(content().contentType(MediaType.APPLICATION_JSON))
// .andExpect(jsonPath("$.data").value(size));
} }
} }

View File

@ -1,6 +1,8 @@
package io.metersphere.plan.controller; package io.metersphere.plan.controller;
import com.jayway.jsonpath.JsonPath;
import io.metersphere.plan.dto.TestPlanApiCaseDTO; import io.metersphere.plan.dto.TestPlanApiCaseDTO;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.MethodOrderer;
@ -13,12 +15,14 @@ import org.springframework.http.MediaType;
import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import java.util.UUID; import java.util.UUID;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest @SpringBootTest
@ -35,6 +39,22 @@ public class TestPlanApiCaseControllerTests {
public static final String PARAM_TEST_ID = "test-plan-api-case-id"; public static final String PARAM_TEST_ID = "test-plan-api-case-id";
private static String sessionId;
private static String csrfToken;
@Test
@Order(0)
public void login() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/signin")
.content("{\"username\":\"admin\",\"password\":\"metersphere\"}")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
sessionId = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.sessionId");
csrfToken = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.csrfToken");
}
@Test @Test
@Order(1) @Order(1)
@ -45,6 +65,8 @@ public class TestPlanApiCaseControllerTests {
testPlanApiCaseDTO.setApiCaseId(UUID.randomUUID().toString()); testPlanApiCaseDTO.setApiCaseId(UUID.randomUUID().toString());
testPlanApiCaseDTO.setPos(10001L); testPlanApiCaseDTO.setPos(10001L);
mockMvc.perform(MockMvcRequestBuilders.post(REQ_PREFIX + "/add") mockMvc.perform(MockMvcRequestBuilders.post(REQ_PREFIX + "/add")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(testPlanApiCaseDTO)) .content(JSON.toJSONString(testPlanApiCaseDTO))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -54,7 +76,9 @@ public class TestPlanApiCaseControllerTests {
@Test @Test
@Order(2) @Order(2)
public void testGet() throws Exception { public void testGet() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get(REQ_PREFIX + "/get/" + PARAM_TEST_ID)) mockMvc.perform(MockMvcRequestBuilders.get(REQ_PREFIX + "/get/" + PARAM_TEST_ID)
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andDo(MockMvcResultHandlers.print()); .andDo(MockMvcResultHandlers.print());
} }
@ -66,11 +90,15 @@ public class TestPlanApiCaseControllerTests {
testPlanApiCaseDTO.setId(PARAM_TEST_ID); testPlanApiCaseDTO.setId(PARAM_TEST_ID);
testPlanApiCaseDTO.setPos(15001L); testPlanApiCaseDTO.setPos(15001L);
mockMvc.perform(MockMvcRequestBuilders.put(REQ_PREFIX + "/update") mockMvc.perform(MockMvcRequestBuilders.put(REQ_PREFIX + "/update")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(testPlanApiCaseDTO)) .content(JSON.toJSONString(testPlanApiCaseDTO))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andDo(MockMvcResultHandlers.print()); .andDo(MockMvcResultHandlers.print());
mockMvc.perform(MockMvcRequestBuilders.get(REQ_PREFIX + "/get/" + PARAM_TEST_ID)) mockMvc.perform(MockMvcRequestBuilders.get(REQ_PREFIX + "/get/" + PARAM_TEST_ID)
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken))
.andExpect(status().isOk()).andExpect(MockMvcResultMatchers.jsonPath("$.data.pos").value(15001L)) .andExpect(status().isOk()).andExpect(MockMvcResultMatchers.jsonPath("$.data.pos").value(15001L))
.andDo(MockMvcResultHandlers.print()); .andDo(MockMvcResultHandlers.print());
} }
@ -78,7 +106,9 @@ public class TestPlanApiCaseControllerTests {
@Test @Test
@Order(4) @Order(4)
public void testDelete() throws Exception { public void testDelete() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.delete(REQ_PREFIX + "/delete/" + PARAM_TEST_ID)) mockMvc.perform(MockMvcRequestBuilders.delete(REQ_PREFIX + "/delete/" + PARAM_TEST_ID)
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andDo(MockMvcResultHandlers.print()); .andDo(MockMvcResultHandlers.print());
} }

View File

@ -1,7 +1,9 @@
package io.metersphere.plan.controller; package io.metersphere.plan.controller;
import com.jayway.jsonpath.JsonPath;
import io.metersphere.plan.domain.TestPlan; import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.dto.TestPlanDTO; import io.metersphere.plan.dto.TestPlanDTO;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.MethodOrderer;
@ -12,6 +14,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.util.ArrayList; import java.util.ArrayList;
@ -29,6 +32,10 @@ public class TestPlanControllerTests {
@Resource @Resource
private MockMvc mockMvc; private MockMvc mockMvc;
private static String sessionId;
private static String csrfToken;
private TestPlanDTO getSimpleTestPlan() { private TestPlanDTO getSimpleTestPlan() {
TestPlanDTO testPlan = new TestPlanDTO(); TestPlanDTO testPlan = new TestPlanDTO();
testPlan.setId("test"); testPlan.setId("test");
@ -42,6 +49,20 @@ public class TestPlanControllerTests {
return testPlan; return testPlan;
} }
@Test
@Order(0)
public void login() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/signin")
.content("{\"username\":\"admin\",\"password\":\"metersphere\"}")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
sessionId = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.sessionId");
csrfToken = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.csrfToken");
}
@Test @Test
@Order(1) @Order(1)
public void testAdd1() throws Exception { public void testAdd1() throws Exception {
@ -60,6 +81,8 @@ public class TestPlanControllerTests {
testPlan.setPrincipals(participantList); testPlan.setPrincipals(participantList);
mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(testPlan)) .content(JSON.toJSONString(testPlan))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -79,6 +102,8 @@ public class TestPlanControllerTests {
testPlan.setFollowers(followerList); testPlan.setFollowers(followerList);
mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(testPlan)) .content(JSON.toJSONString(testPlan))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -96,6 +121,8 @@ public class TestPlanControllerTests {
testPlan.setPrincipals(participantList); testPlan.setPrincipals(participantList);
mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(testPlan)) .content(JSON.toJSONString(testPlan))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -108,6 +135,8 @@ public class TestPlanControllerTests {
TestPlanDTO testPlan = this.getSimpleTestPlan(); TestPlanDTO testPlan = this.getSimpleTestPlan();
mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(testPlan)) .content(JSON.toJSONString(testPlan))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
@ -121,6 +150,8 @@ public class TestPlanControllerTests {
testPlan.setName("test"); testPlan.setName("test");
mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add")
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.content(JSON.toJSONString(testPlan)) .content(JSON.toJSONString(testPlan))
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())

1
lombok.config Normal file
View File

@ -0,0 +1 @@
lombok.addLombokGeneratedAnnotation = true

View File

@ -219,13 +219,12 @@
<artifactId>jacoco-maven-plugin</artifactId> <artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version> <version>${jacoco.version}</version>
<configuration> <configuration>
<includes>
<include>io/metersphere/*/controller/*Controller.*</include>
</includes>
<excludes> <excludes>
<exclude>io/metersphere/*/service/**</exclude>
<exclude>io/metersphere/*/mapper/**</exclude> <exclude>io/metersphere/*/mapper/**</exclude>
<exclude>io/metersphere/*/domain/**</exclude> <exclude>io/metersphere/*/domain/**</exclude>
<exclude>io/metersphere/**/dto/**</exclude>
<exclude>io/metersphere/**/config/**</exclude>
<exclude>io/metersphere/**/constants/**</exclude>
</excludes> </excludes>
</configuration> </configuration>
<executions> <executions>