fix: [auth] Extracting PwjbUserInfo to resolve user contamination

This commit is contained in:
tjq 2024-02-13 18:23:29 +08:00
parent c350607762
commit 6539c66226
23 changed files with 465 additions and 80 deletions

View File

@ -3,6 +3,7 @@ package tech.powerjob.common.serialize;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.json.JsonMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -28,6 +29,7 @@ public class JsonUtils {
.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true) .configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true)
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true)
.configure(JsonParser.Feature.IGNORE_UNDEFINED, true) .configure(JsonParser.Feature.IGNORE_UNDEFINED, true)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.build(); .build();
private static final TypeReference<Map<String, Object>> MAP_TYPE_REFERENCE = new TypeReference<Map<String, Object>> () {}; private static final TypeReference<Map<String, Object>> MAP_TYPE_REFERENCE = new TypeReference<Map<String, Object>> () {};

View File

@ -8,6 +8,26 @@ package tech.powerjob.server.auth.common;
*/ */
public class AuthConstants { public class AuthConstants {
/* ********** 账号体系唯一标识推荐开发者接入第三方登录体系时也使用4位编码便于前端统一做样式 ********** */
/**
* PowerJob自建账号体系
*/
public static final String ACCOUNT_TYPE_POWER_JOB = "PWJB";
/**
* 钉钉
*/
public static final String ACCOUNT_TYPE_DING = "DING";
/**
* 企业微信预留蹲一个 contributor
*/
public static final String ACCOUNT_TYPE_WX = "QYWX";
/**
* 飞书预留蹲一个 contributor +1
*/
public static final String ACCOUNT_LARK = "LARK";
/* ********** 账号体系 ********** */
/** /**
* JWT key * JWT key
* 前端 header 默认首字母大写保持一致方便处理 * 前端 header 默认首字母大写保持一致方便处理

View File

@ -22,7 +22,9 @@ public enum AuthErrorCode {
/** /**
* 无效请求一般是参数问题 * 无效请求一般是参数问题
*/ */
INVALID_REQUEST("-300", "INVALID_REQUEST") INVALID_REQUEST("-300", "INVALID_REQUEST"),
INCORRECT_PASSWORD("-400", "INCORRECT_PASSWORD")
; ;

View File

@ -31,6 +31,10 @@ public class ThirdPartyUser {
* 邮箱地址 * 邮箱地址
*/ */
private String email; private String email;
/**
* web 回调地址
*/
private String webHook;
/** /**
* 扩展字段 * 扩展字段
*/ */

View File

@ -11,6 +11,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import tech.powerjob.common.exception.PowerJobException; import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.server.auth.common.AuthConstants;
import tech.powerjob.server.auth.login.*; import tech.powerjob.server.auth.login.*;
import tech.powerjob.server.common.Loggers; import tech.powerjob.server.common.Loggers;
@ -56,13 +57,11 @@ public class DingTalkLoginService implements ThirdPartyLoginService {
@Value("${oms.auth.dingtalk.callbackUrl:#{null}}") @Value("${oms.auth.dingtalk.callbackUrl:#{null}}")
private String dingTalkCallbackUrl; private String dingTalkCallbackUrl;
private static final String DING_TALK = "DingTalk";
@Override @Override
public LoginTypeInfo loginType() { public LoginTypeInfo loginType() {
return new LoginTypeInfo() return new LoginTypeInfo()
.setType(DING_TALK) .setType(AuthConstants.ACCOUNT_TYPE_DING)
.setName("钉钉登录") .setName("DingTalkLogin")
; ;
} }
@ -79,7 +78,7 @@ public class DingTalkLoginService implements ThirdPartyLoginService {
"&response_type=code" + "&response_type=code" +
"&client_id=" + dingTalkAppKey + "&client_id=" + dingTalkAppKey +
"&scope=openid" + "&scope=openid" +
"&state=" + DING_TALK + "&state=" + AuthConstants.ACCOUNT_TYPE_DING +
"&prompt=consent"; "&prompt=consent";
Loggers.WEB.info("[DingTalkBizLoginService] login url: {}", url); Loggers.WEB.info("[DingTalkBizLoginService] login url: {}", url);
return url; return url;

View File

@ -14,8 +14,8 @@ import tech.powerjob.server.auth.login.ThirdPartyLoginService;
import tech.powerjob.server.auth.login.ThirdPartyUser; import tech.powerjob.server.auth.login.ThirdPartyUser;
import tech.powerjob.server.common.Loggers; import tech.powerjob.server.common.Loggers;
import tech.powerjob.server.common.utils.DigestUtils; import tech.powerjob.server.common.utils.DigestUtils;
import tech.powerjob.server.persistence.remote.model.UserInfoDO; import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO;
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository; import tech.powerjob.server.persistence.remote.repository.PwjbUserInfoRepository;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -33,9 +33,7 @@ import java.util.Optional;
public class PowerJobThirdPartyLoginService implements ThirdPartyLoginService { public class PowerJobThirdPartyLoginService implements ThirdPartyLoginService {
@Resource @Resource
private UserInfoRepository userInfoRepository; private PwjbUserInfoRepository pwjbUserInfoRepository;
private static final String POWER_JOB_LOGIN_SERVICE = "PowerJob";
private static final String KEY_USERNAME = "username"; private static final String KEY_USERNAME = "username";
private static final String KEY_PASSWORD = "password"; private static final String KEY_PASSWORD = "password";
@ -45,8 +43,8 @@ public class PowerJobThirdPartyLoginService implements ThirdPartyLoginService {
@Override @Override
public LoginTypeInfo loginType() { public LoginTypeInfo loginType() {
return new LoginTypeInfo() return new LoginTypeInfo()
.setType(POWER_JOB_LOGIN_SERVICE) .setType(AuthConstants.ACCOUNT_TYPE_POWER_JOB)
.setName("PowerJob") .setName("PowerJob Built-in Login")
; ;
} }
@ -74,17 +72,30 @@ public class PowerJobThirdPartyLoginService implements ThirdPartyLoginService {
throw new PowerJobAuthException(AuthErrorCode.INVALID_REQUEST); throw new PowerJobAuthException(AuthErrorCode.INVALID_REQUEST);
} }
final Optional<UserInfoDO> userInfoOpt = userInfoRepository.findByUsername(username); final Optional<PwjbUserInfoDO> userInfoOpt = pwjbUserInfoRepository.findByUsername(username);
if (!userInfoOpt.isPresent()) { if (!userInfoOpt.isPresent()) {
Loggers.WEB.debug("[PowerJobLoginService] can't find user by username: {}", username); Loggers.WEB.debug("[PowerJobLoginService] can't find user by username: {}", username);
throw new PowerJobAuthException(AuthErrorCode.USER_NOT_EXIST); throw new PowerJobAuthException(AuthErrorCode.USER_NOT_EXIST);
} }
final UserInfoDO dbUser = userInfoOpt.get(); final PwjbUserInfoDO dbUser = userInfoOpt.get();
if (DigestUtils.rePassword(password, username).equals(dbUser.getPassword())) { if (DigestUtils.rePassword(password, username).equals(dbUser.getPassword())) {
ThirdPartyUser bizUser = new ThirdPartyUser(); ThirdPartyUser bizUser = new ThirdPartyUser();
bizUser.setUsername(username); bizUser.setUsername(username);
// 回填第一次创建的信息
String extra = dbUser.getExtra();
if (StringUtils.isNotEmpty(extra)) {
ThirdPartyUser material = JsonUtils.parseObjectIgnoreException(extra, ThirdPartyUser.class);
if (material != null) {
bizUser.setEmail(material.getEmail());
bizUser.setNick(material.getNick());
bizUser.setPhone(material.getPhone());
bizUser.setWebHook(material.getWebHook());
}
}
return bizUser; return bizUser;
} }

View File

@ -7,6 +7,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import tech.powerjob.server.auth.LoginUserHolder;
import tech.powerjob.server.auth.PowerJobUser; import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.server.auth.common.AuthConstants; import tech.powerjob.server.auth.common.AuthConstants;
import tech.powerjob.server.auth.common.AuthErrorCode; import tech.powerjob.server.auth.common.AuthErrorCode;
@ -91,6 +92,16 @@ public class PowerJobLoginServiceImpl implements PowerJobLoginService {
if (!powerJobUserOpt.isPresent()) { if (!powerJobUserOpt.isPresent()) {
UserInfoDO newUser = new UserInfoDO(); UserInfoDO newUser = new UserInfoDO();
newUser.setUsername(dbUserName); newUser.setUsername(dbUserName);
// 写入账号体系类型
newUser.setAccountType(loginType);
// 同步素材
newUser.setEmail(bizUser.getEmail());
newUser.setPhone(bizUser.getPhone());
newUser.setNick(bizUser.getNick());
newUser.setWebHook(bizUser.getWebHook());
newUser.setExtra(bizUser.getExtra());
Loggers.WEB.info("[PowerJobLoginService] sync user to PowerJobUserSystem: {}", dbUserName); Loggers.WEB.info("[PowerJobLoginService] sync user to PowerJobUserSystem: {}", dbUserName);
userInfoRepository.saveAndFlush(newUser); userInfoRepository.saveAndFlush(newUser);
@ -117,6 +128,10 @@ public class PowerJobLoginServiceImpl implements PowerJobLoginService {
return userNameOpt.flatMap(uname -> userInfoRepository.findByUsername(uname).map(userInfoDO -> { return userNameOpt.flatMap(uname -> userInfoRepository.findByUsername(uname).map(userInfoDO -> {
PowerJobUser powerJobUser = new PowerJobUser(); PowerJobUser powerJobUser = new PowerJobUser();
BeanUtils.copyProperties(userInfoDO, powerJobUser); BeanUtils.copyProperties(userInfoDO, powerJobUser);
// 兼容某些直接通过 ifLogin 判断登录的场景
LoginUserHolder.set(powerJobUser);
return powerJobUser; return powerJobUser;
})); }));
} }

View File

@ -1,16 +1,13 @@
package tech.powerjob.server.core.service; package tech.powerjob.server.core.service;
import tech.powerjob.common.utils.CommonUtils;
import tech.powerjob.server.common.utils.DigestUtils;
import tech.powerjob.server.persistence.remote.model.UserInfoDO;
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.springframework.stereotype.Service;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import tech.powerjob.server.persistence.remote.model.UserInfoDO;
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -27,26 +24,6 @@ public class UserService {
@Resource @Resource
private UserInfoRepository userInfoRepository; private UserInfoRepository userInfoRepository;
/**
* 保存/修改 用户
* @param userInfoDO user
*/
public void save(UserInfoDO userInfoDO) {
CommonUtils.requireNonNull(userInfoDO.getUsername(), "userName can't be null or empty!");
userInfoDO.setGmtCreate(new Date());
userInfoDO.setGmtModified(userInfoDO.getGmtCreate());
// 二次加密密码
final String password = userInfoDO.getPassword();
if (StringUtils.isNotEmpty(password)) {
userInfoDO.setPassword(DigestUtils.rePassword(password, userInfoDO.getUsername()));
}
userInfoRepository.saveAndFlush(userInfoDO);
}
/** /**
* 根据用户ID字符串获取用户信息详细列表 * 根据用户ID字符串获取用户信息详细列表
* @param userIds 逗号分割的用户ID信息 * @param userIds 逗号分割的用户ID信息

View File

@ -0,0 +1,36 @@
package tech.powerjob.server.persistence.remote.model;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.Date;
/**
* PowerJob 自建登录体系的用户表只存储使用 PowerJob 自带登录方式登录的用户信息
*
* @author tjq
* @since 2024/2/13
*/
@Data
@Entity
@Table(uniqueConstraints = {
@UniqueConstraint(name = "uidx01_username", columnNames = {"username"})
})
public class PwjbUserInfoDO {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
@GenericGenerator(name = "native", strategy = "native")
private Long id;
private String username;
private String password;
private String extra;
private Date gmtCreate;
private Date gmtModified;
}

View File

@ -8,6 +8,7 @@ import java.util.Date;
/** /**
* 用户信息表 * 用户信息表
* PowerJob 自身维护的全部用户体系数据
* 5.0.0 可能不兼容改动为了支持第三方登录需要通过 username 与第三方登录系统做匹配该列需要声明为唯一索引确保全局唯一 * 5.0.0 可能不兼容改动为了支持第三方登录需要通过 username 与第三方登录系统做匹配该列需要声明为唯一索引确保全局唯一
* *
* @author tjq * @author tjq
@ -28,6 +29,11 @@ public class UserInfoDO {
@GenericGenerator(name = "native", strategy = "native") @GenericGenerator(name = "native", strategy = "native")
private Long id; private Long id;
/**
* 账号类型
*/
private String accountType;
private String username; private String username;
/** /**
* since 5.0.0 * since 5.0.0

View File

@ -8,6 +8,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import tech.powerjob.server.persistence.remote.model.AppInfoDO; import tech.powerjob.server.persistence.remote.model.AppInfoDO;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -34,4 +35,6 @@ public interface AppInfoRepository extends JpaRepository<AppInfoDO, Long>, JpaSp
List<AppInfoDO> findAllByNamespaceId(Long namespaceId); List<AppInfoDO> findAllByNamespaceId(Long namespaceId);
List<AppInfoDO> findAllByIdIn(Collection<Long> ids);
} }

View File

@ -4,6 +4,8 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import tech.powerjob.server.persistence.remote.model.NamespaceDO; import tech.powerjob.server.persistence.remote.model.NamespaceDO;
import java.util.Collection;
import java.util.List;
import java.util.Optional; import java.util.Optional;
/** /**
@ -15,4 +17,6 @@ import java.util.Optional;
public interface NamespaceRepository extends JpaRepository<NamespaceDO, Long>, JpaSpecificationExecutor<NamespaceDO> { public interface NamespaceRepository extends JpaRepository<NamespaceDO, Long>, JpaSpecificationExecutor<NamespaceDO> {
Optional<NamespaceDO> findByCode(String code); Optional<NamespaceDO> findByCode(String code);
List<NamespaceDO> findAllByIdIn(Collection<Long> ids);
} }

View File

@ -0,0 +1,17 @@
package tech.powerjob.server.persistence.remote.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO;
import java.util.Optional;
/**
* PwjbUserInfoRepository
*
* @author tjq
* @since 2024/2/13
*/
public interface PwjbUserInfoRepository extends JpaRepository<PwjbUserInfoDO, Long> {
Optional<PwjbUserInfoDO> findByUsername(String username);
}

View File

@ -1,10 +1,12 @@
package tech.powerjob.server.auth.service; package tech.powerjob.server.auth.service;
import tech.powerjob.server.auth.Permission; import tech.powerjob.server.auth.Permission;
import tech.powerjob.server.auth.Role;
import tech.powerjob.server.auth.RoleScope; import tech.powerjob.server.auth.RoleScope;
import tech.powerjob.server.web.request.ComponentUserRoleInfo; import tech.powerjob.server.web.request.ComponentUserRoleInfo;
import java.util.Set; import java.util.List;
import java.util.Map;
/** /**
* Web Auth 服务 * Web Auth 服务
@ -41,5 +43,5 @@ public interface WebAuthService {
*/ */
boolean hasPermission(RoleScope roleScope, Long target, Permission permission); boolean hasPermission(RoleScope roleScope, Long target, Permission permission);
Set<Long> fetchMyPermissionTargets(RoleScope roleScope); Map<Role, List<Long>> fetchMyPermissionTargets(RoleScope roleScope);
} }

View File

@ -62,20 +62,15 @@ public class WebAuthServiceImpl implements WebAuthService {
} }
@Override @Override
public Set<Long> fetchMyPermissionTargets(RoleScope roleScope) { public Map<Role, List<Long>> fetchMyPermissionTargets(RoleScope roleScope) {
PowerJobUser powerJobUser = LoginUserHolder.get(); PowerJobUser powerJobUser = LoginUserHolder.get();
if (powerJobUser == null) { if (powerJobUser == null) {
throw new PowerJobAuthException(AuthErrorCode.USER_NOT_LOGIN); throw new PowerJobAuthException(AuthErrorCode.USER_NOT_LOGIN);
} }
Map<Role, List<Long>> role2TargetIds = powerJobPermissionService.fetchUserHadPermissionTargets(roleScope, powerJobUser.getId());
Set<Long> targetIds = Sets.newHashSet();
role2TargetIds.values().forEach(targetIds::addAll);
// 展示不考虑穿透权限的问题即拥有 namespace 权限也可以看到全部的 apps // 展示不考虑穿透权限的问题即拥有 namespace 权限也可以看到全部的 apps
return targetIds; return powerJobPermissionService.fetchUserHadPermissionTargets(roleScope, powerJobUser.getId());
} }
private void diffGrant(RoleScope roleScope, Long target, Role role, List<Long> uids, Map<Role, List<Long>> originRole2Uids) { private void diffGrant(RoleScope roleScope, Long target, Role role, List<Long> uids, Map<Role, List<Long>> originRole2Uids) {

View File

@ -1,6 +1,7 @@
package tech.powerjob.server.web.controller; package tech.powerjob.server.web.controller;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -109,7 +110,9 @@ public class AppInfoController {
Set<Long> queryAppIds; Set<Long> queryAppIds;
Boolean showMyRelated = queryAppInfoRequest.getShowMyRelated(); Boolean showMyRelated = queryAppInfoRequest.getShowMyRelated();
if (BooleanUtils.isTrue(showMyRelated)) { if (BooleanUtils.isTrue(showMyRelated)) {
queryAppIds = webAuthService.fetchMyPermissionTargets(RoleScope.APP); Set<Long> targetIds = Sets.newHashSet();
webAuthService.fetchMyPermissionTargets(RoleScope.APP).values().forEach(targetIds::addAll);
queryAppIds = targetIds;
} else { } else {
queryAppIds = Collections.emptySet(); queryAppIds = Collections.emptySet();
} }

View File

@ -0,0 +1,97 @@
package tech.powerjob.server.web.controller;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.common.serialize.JsonUtils;
import tech.powerjob.common.utils.CommonUtils;
import tech.powerjob.server.auth.common.AuthErrorCode;
import tech.powerjob.server.auth.common.PowerJobAuthException;
import tech.powerjob.server.common.utils.DigestUtils;
import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO;
import tech.powerjob.server.persistence.remote.repository.PwjbUserInfoRepository;
import tech.powerjob.server.web.request.ChangePasswordRequest;
import tech.powerjob.server.web.request.ModifyUserInfoRequest;
import javax.annotation.Resource;
import java.util.Date;
import java.util.Optional;
/**
* PowerJob 自带的登录体系
* 同样视为第三方服务与主框架没有任何关系
*
* @author tjq
* @since 2024/2/13
*/
@RestController
@RequestMapping("/pwjbUser")
public class PwjbUserInfoController {
@Resource
private PwjbUserInfoRepository pwjbUserInfoRepository;
/**
* 创建第三方登录体系PowerJob 的账户不允许修改
* @param request 请求此处复用了主框架请求便于用户一次性把所有参数都填入
* @return 创建结果
*/
@PostMapping("/create")
public ResultDTO<Void> save(@RequestBody ModifyUserInfoRequest request) {
String username = request.getUsername();
CommonUtils.requireNonNull(username, "userName can't be null or empty!");
Optional<PwjbUserInfoDO> oldUserOpt = pwjbUserInfoRepository.findByUsername(username);
if (oldUserOpt.isPresent()) {
throw new IllegalArgumentException("username already exist, please change one!");
}
PwjbUserInfoDO pwjbUserInfoDO = new PwjbUserInfoDO();
pwjbUserInfoDO.setUsername(username);
pwjbUserInfoDO.setGmtCreate(new Date());
pwjbUserInfoDO.setGmtModified(new Date());
// 二次加密密码
final String password = pwjbUserInfoDO.getPassword();
if (StringUtils.isNotEmpty(password)) {
pwjbUserInfoDO.setPassword(DigestUtils.rePassword(password, pwjbUserInfoDO.getUsername()));
}
// 其他参数存入 extra在回调创建真正的内部 USER 时回填
request.setPassword(null);
pwjbUserInfoDO.setExtra(JsonUtils.toJSONString(request));
pwjbUserInfoRepository.save(pwjbUserInfoDO);
return ResultDTO.success(null);
}
@PostMapping("/changePassword")
public ResultDTO<Void> changePassword(@RequestBody ChangePasswordRequest changePasswordRequest) {
if (StringUtils.equals(changePasswordRequest.getNewPassword(), changePasswordRequest.getNewPassword2())) {
throw new IllegalArgumentException("Inconsistent passwords");
}
Optional<PwjbUserInfoDO> userOpt = pwjbUserInfoRepository.findById(changePasswordRequest.getUserId());
if (!userOpt.isPresent()) {
throw new IllegalArgumentException("can't find user by userId: " + changePasswordRequest.getUserId());
}
PwjbUserInfoDO dbUser = userOpt.get();
String oldPasswordInDb = dbUser.getPassword();
String oldPasswordInReq = DigestUtils.rePassword(changePasswordRequest.getOldPassword(), dbUser.getUsername());
if (!StringUtils.equals(oldPasswordInDb, oldPasswordInReq)) {
throw new PowerJobAuthException(AuthErrorCode.INCORRECT_PASSWORD);
}
dbUser.setPassword(DigestUtils.rePassword(changePasswordRequest.getNewPassword(), dbUser.getUsername()));
dbUser.setGmtModified(new Date());
pwjbUserInfoRepository.saveAndFlush(dbUser);
return ResultDTO.success(null);
}
}

View File

@ -1,20 +1,44 @@
package tech.powerjob.server.web.controller; package tech.powerjob.server.web.controller;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import tech.powerjob.common.response.ResultDTO; import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.common.serialize.JsonUtils;
import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.server.auth.Role;
import tech.powerjob.server.auth.RoleScope;
import tech.powerjob.server.auth.common.AuthErrorCode;
import tech.powerjob.server.auth.common.PowerJobAuthException;
import tech.powerjob.server.auth.service.WebAuthService;
import tech.powerjob.server.auth.service.login.PowerJobLoginService;
import tech.powerjob.server.core.service.UserService; import tech.powerjob.server.core.service.UserService;
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
import tech.powerjob.server.persistence.remote.model.UserInfoDO; import tech.powerjob.server.persistence.remote.model.UserInfoDO;
import tech.powerjob.server.persistence.remote.repository.AppInfoRepository;
import tech.powerjob.server.persistence.remote.repository.NamespaceRepository;
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository; import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
import tech.powerjob.server.web.converter.NamespaceConverter;
import tech.powerjob.server.web.converter.UserConverter; import tech.powerjob.server.web.converter.UserConverter;
import tech.powerjob.server.web.request.ModifyUserInfoRequest; import tech.powerjob.server.web.response.AppBaseVO;
import tech.powerjob.server.web.response.NamespaceBaseVO;
import tech.powerjob.server.web.response.UserBaseVO; import tech.powerjob.server.web.response.UserBaseVO;
import tech.powerjob.server.web.response.UserDetailVO;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -30,16 +54,16 @@ public class UserInfoController {
private UserService userService; private UserService userService;
@Resource @Resource
private UserInfoRepository userInfoRepository; private UserInfoRepository userInfoRepository;
@Resource
private PowerJobLoginService powerJobLoginService;
@Resource
private WebAuthService webAuthService;
@Resource
private NamespaceRepository namespaceRepository;
@Resource
private AppInfoRepository appInfoRepository;
@PostMapping("save") @GetMapping("/list")
public ResultDTO<Void> save(@RequestBody ModifyUserInfoRequest request) {
UserInfoDO userInfoDO = new UserInfoDO();
BeanUtils.copyProperties(request, userInfoDO);
userService.save(userInfoDO);
return ResultDTO.success(null);
}
@GetMapping("list")
public ResultDTO<List<UserBaseVO>> list(@RequestParam(required = false) String name) { public ResultDTO<List<UserBaseVO>> list(@RequestParam(required = false) String name) {
List<UserInfoDO> result; List<UserInfoDO> result;
@ -51,10 +75,76 @@ public class UserInfoController {
return ResultDTO.success(convert(result)); return ResultDTO.success(convert(result));
} }
@GetMapping("/detail")
public ResultDTO<UserDetailVO> getUserDetail(HttpServletRequest httpServletRequest) {
Optional<PowerJobUser> powerJobUserOpt = powerJobLoginService.ifLogin(httpServletRequest);
if (!powerJobUserOpt.isPresent()) {
throw new PowerJobAuthException(AuthErrorCode.USER_NOT_LOGIN);
}
Optional<UserInfoDO> userinfoDoOpt = userInfoRepository.findById(powerJobUserOpt.get().getId());
if (!userinfoDoOpt.isPresent()) {
throw new IllegalArgumentException("can't find user by id: " + powerJobUserOpt.get().getId());
}
UserDetailVO userDetailVO = new UserDetailVO();
BeanUtils.copyProperties(userinfoDoOpt.get(), userDetailVO);
// 权限信息
Map<Role, List<Long>> globalPermissions = webAuthService.fetchMyPermissionTargets(RoleScope.GLOBAL);
userDetailVO.setGlobalRoles(globalPermissions.keySet().stream().map(Enum::name).collect(Collectors.toList()));
Map<Role, List<Long>> namespacePermissions = webAuthService.fetchMyPermissionTargets(RoleScope.NAMESPACE);
List<NamespaceDO> nsList = namespaceRepository.findAllByIdIn(mergeIds(namespacePermissions));
Map<Long, NamespaceDO> id2NamespaceDo = Maps.newHashMap();
nsList.forEach(x -> id2NamespaceDo.put(x.getId(), x));
Map<String, List<NamespaceBaseVO>> role2NamespaceBaseVo = Maps.newHashMap();
namespacePermissions.forEach((k, v) -> {
List<NamespaceBaseVO> namespaceBaseVOS = Lists.newArrayList();
role2NamespaceBaseVo.put(k.name(), namespaceBaseVOS);
v.forEach(nId -> {
NamespaceDO namespaceDO = id2NamespaceDo.get(nId);
if (namespaceDO == null) {
return;
}
NamespaceBaseVO namespaceBaseVO = JsonUtils.parseObjectIgnoreException(JsonUtils.toJSONString(NamespaceConverter.do2BaseVo(namespaceDO)), NamespaceBaseVO.class);
namespaceBaseVO.genFrontName();
namespaceBaseVOS.add(namespaceBaseVO);
});
});
userDetailVO.setRole2NamespaceList(role2NamespaceBaseVo);
Map<Role, List<Long>> appPermissions = webAuthService.fetchMyPermissionTargets(RoleScope.APP);
List<AppInfoDO> appList = appInfoRepository.findAllByIdIn(mergeIds(appPermissions));
Map<Long, AppInfoDO> id2AppInfo = Maps.newHashMap();
appList.forEach(x -> id2AppInfo.put(x.getId(), x));
Map<String, List<AppBaseVO>> role2AppBaseVo = Maps.newHashMap();
appPermissions.forEach((k, v) -> {
List<AppBaseVO> appBaseVOS = Lists.newArrayList();
role2AppBaseVo.put(k.name(), appBaseVOS);
v.forEach(nId -> {
AppInfoDO appInfoDO = id2AppInfo.get(nId);
if (appInfoDO == null) {
return;
}
AppBaseVO appBaseVO = new AppBaseVO();
BeanUtils.copyProperties(appInfoDO, appBaseVO);
appBaseVOS.add(appBaseVO);
});
});
userDetailVO.setRole2AppList(role2AppBaseVo);
return ResultDTO.success(userDetailVO);
}
private static List<UserBaseVO> convert(List<UserInfoDO> data) { private static List<UserBaseVO> convert(List<UserInfoDO> data) {
if (CollectionUtils.isEmpty(data)) { if (CollectionUtils.isEmpty(data)) {
return Lists.newLinkedList(); return Lists.newLinkedList();
} }
return data.stream().map(UserConverter::do2BaseVo).collect(Collectors.toList()); return data.stream().map(UserConverter::do2BaseVo).collect(Collectors.toList());
} }
private static Set<Long> mergeIds(Map<?, List<Long>> map) {
Set<Long> ids = Sets.newHashSet();
map.values().forEach(ids::addAll);
return ids;
}
} }

View File

@ -0,0 +1,23 @@
package tech.powerjob.server.web.request;
import lombok.Data;
import java.io.Serializable;
/**
* 修改密码
*
* @author tjq
* @since 2024/2/13
*/
@Data
public class ChangePasswordRequest implements Serializable {
private Long userId;
private String oldPassword;
private String newPassword;
private String newPassword2;
}

View File

@ -0,0 +1,27 @@
package tech.powerjob.server.web.response;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* AppBaseVO
*
* @author tjq
* @since 2024/2/13
*/
@Getter
@Setter
public class AppBaseVO implements Serializable {
protected Long id;
protected String appName;
protected Long namespaceId;
/**
* 描述
*/
protected String title;
}

View File

@ -1,9 +1,10 @@
package tech.powerjob.server.web.response; package tech.powerjob.server.web.response;
import lombok.Data; import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import tech.powerjob.server.web.request.ComponentUserRoleInfo; import tech.powerjob.server.web.request.ComponentUserRoleInfo;
import java.io.Serializable;
import java.util.Date; import java.util.Date;
/** /**
@ -12,19 +13,10 @@ import java.util.Date;
* @author tjq * @author tjq
* @since 2024/2/12 * @since 2024/2/12
*/ */
@Data @Getter
public class AppInfoVO implements Serializable { @Setter
@ToString
private Long id; public class AppInfoVO extends AppBaseVO {
private String appName;
private Long namespaceId;
/**
* 描述
*/
private String title;
private String password; private String password;

View File

@ -14,7 +14,7 @@ import lombok.Setter;
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
public class UserBaseVO { public class UserBaseVO {
private Long id; protected Long id;
private String username; protected String username;
private String nick; protected String nick;
} }

View File

@ -0,0 +1,60 @@
package tech.powerjob.server.web.response;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.List;
import java.util.Map;
/**
* 用户详细信息
*
* @author tjq
* @since 2024/2/13
*/
@Getter
@Setter
@ToString
public class UserDetailVO extends UserBaseVO {
/**
* 账户类型
*/
private String accountType;
/**
* 密码
*/
private String password;
/**
* 手机号
*/
private String phone;
/**
* 邮箱地址
*/
private String email;
/**
* webHook
*/
private String webHook;
/**
* 扩展字段
*/
private String extra;
/**
* 拥有的全局权限
*/
private List<String> globalRoles;
/**
* 拥有的 namespace 权限
*/
private Map<String, List<NamespaceBaseVO>> role2NamespaceList;
/**
* 拥有的 app 权限
*/
private Map<String, List<AppBaseVO>> role2AppList;
}