feat: support namespace

This commit is contained in:
tjq 2024-02-12 22:12:03 +08:00
parent 841c7891c8
commit 919a5c3b35
20 changed files with 381 additions and 240 deletions

View File

@ -0,0 +1,27 @@
package tech.powerjob.server.auth.common.utils;
import tech.powerjob.common.OmsConstant;
import javax.servlet.http.HttpServletRequest;
/**
* HttpServletUtils
*
* @author tjq
* @since 2024/2/12
*/
public class HttpServletUtils {
public static String fetchFromHeader(String key, HttpServletRequest httpServletRequest) {
// headercookie 都能获取
String v = httpServletRequest.getHeader(key);
// 解决 window.localStorage.getItem null 的问题
if (OmsConstant.NULL.equalsIgnoreCase(v)) {
return null;
}
return v;
}
}

View File

@ -1,13 +1,21 @@
package tech.powerjob.server.auth.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import tech.powerjob.common.exception.ImpossibleException;
import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.server.auth.LoginUserHolder;
import tech.powerjob.server.auth.Permission;
import tech.powerjob.server.auth.PowerJobUser;
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.common.utils.HttpServletUtils;
import tech.powerjob.server.auth.service.login.PowerJobLoginService;
import tech.powerjob.server.auth.service.permission.PowerJobPermissionService;
import tech.powerjob.server.common.Loggers;
@ -24,6 +32,7 @@ import java.util.Optional;
* @author tjq
* @since 2023/3/25
*/
@Slf4j
@Component
public class PowerJobAuthInterceptor implements HandlerInterceptor {
@Resource
@ -48,11 +57,9 @@ public class PowerJobAuthInterceptor implements HandlerInterceptor {
// 尝试直接解析登陆
final Optional<PowerJobUser> loginUserOpt = powerJobLoginService.ifLogin(request);
// 未登录前先使用302重定向到登录页面 TODO: 前端登录还是服务端直接跳转有待考虑
// 未登录直接报错返回固定状态码前端拦截后跳转到登录页
if (!loginUserOpt.isPresent()) {
response.setStatus(302);
response.setHeader("location", request.getContextPath() + "/login");
return false;
throw new PowerJobAuthException(AuthErrorCode.USER_NOT_LOGIN);
}
// 登陆用户进行权限校验
@ -61,7 +68,27 @@ public class PowerJobAuthInterceptor implements HandlerInterceptor {
// 写入上下文
LoginUserHolder.set(powerJobUser);
final boolean hasPermission = powerJobPermissionService.hasPermission(request, handler, powerJobUser, apiPermissionAnno);
Permission requiredPermission = parsePermission(request, handler, apiPermissionAnno);
RoleScope roleScope = apiPermissionAnno.roleScope();
Long targetId = null;
if (RoleScope.NAMESPACE.equals(roleScope)) {
final String namespaceIdStr = HttpServletUtils.fetchFromHeader("NamespaceId", request);
if (StringUtils.isNotEmpty(namespaceIdStr)) {
targetId = Long.valueOf(namespaceIdStr);
}
}
if (RoleScope.APP.equals(roleScope)) {
final String appIdStr = HttpServletUtils.fetchFromHeader("AppId", request);
if (StringUtils.isNotEmpty(appIdStr)) {
targetId = Long.valueOf(appIdStr);
}
}
final boolean hasPermission = powerJobPermissionService.hasPermission(powerJobUser.getId(), roleScope, targetId, requiredPermission);
if (hasPermission) {
return true;
}
@ -90,4 +117,19 @@ public class PowerJobAuthInterceptor implements HandlerInterceptor {
}
return "UNKNOWN";
}
private static Permission parsePermission(HttpServletRequest request, Object handler, ApiPermission apiPermission) {
Class<? extends DynamicPermissionPlugin> dynamicPermissionPlugin = apiPermission.dynamicPermissionPlugin();
if (EmptyPlugin.class.equals(dynamicPermissionPlugin)) {
return apiPermission.requiredPermission();
}
try {
DynamicPermissionPlugin dynamicPermission = dynamicPermissionPlugin.getDeclaredConstructor().newInstance();
return dynamicPermission.calculate(request, handler);
} catch (Throwable t) {
log.error("[PowerJobAuthService] process dynamicPermissionPlugin failed!", t);
ExceptionUtils.rethrow(t);
}
throw new ImpossibleException();
}
}

View File

@ -7,11 +7,11 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tech.powerjob.common.OmsConstant;
import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.server.auth.common.AuthConstants;
import tech.powerjob.server.auth.common.AuthErrorCode;
import tech.powerjob.server.auth.common.PowerJobAuthException;
import tech.powerjob.server.auth.common.utils.HttpServletUtils;
import tech.powerjob.server.auth.jwt.JwtService;
import tech.powerjob.server.auth.login.LoginTypeInfo;
import tech.powerjob.server.auth.login.ThirdPartyLoginRequest;
@ -140,12 +140,7 @@ public class PowerJobLoginServiceImpl implements PowerJobLoginService {
private Optional<String> parseUserName(HttpServletRequest httpServletRequest) {
// headercookie 都能获取
String jwtStr = httpServletRequest.getHeader(AuthConstants.JWT_NAME);
// 解决 window.localStorage.getItem null 的问题
if (OmsConstant.NULL.equalsIgnoreCase(jwtStr)) {
jwtStr = null;
}
String jwtStr = HttpServletUtils.fetchFromHeader(AuthConstants.JWT_NAME, httpServletRequest);
if (StringUtils.isEmpty(jwtStr)) {
for (Cookie cookie : Optional.ofNullable(httpServletRequest.getCookies()).orElse(new Cookie[]{})) {

View File

@ -1,11 +1,11 @@
package tech.powerjob.server.auth.service.permission;
import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.server.auth.Permission;
import tech.powerjob.server.auth.Role;
import tech.powerjob.server.auth.RoleScope;
import tech.powerjob.server.auth.interceptor.ApiPermission;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
/**
* PowerJob 鉴权服务
@ -15,15 +15,16 @@ import javax.servlet.http.HttpServletRequest;
*/
public interface PowerJobPermissionService {
/**
* 判断用户是否有访问权限
* @param request 上下文请求
* @param handler hander
* @param user 用户
* @param apiPermission 权限描述
* @return true or false
* @param userId userId
* @param roleScope 权限范围
* @param target 权限目标ID
* @param permission 要求的权限
* @return 是否有权限
*/
boolean hasPermission(HttpServletRequest request, Object handler, PowerJobUser user, ApiPermission apiPermission);
boolean hasPermission(Long userId, RoleScope roleScope, Long target, Permission permission);
/**
* 授予用户权限
@ -34,4 +35,21 @@ public interface PowerJobPermissionService {
* @param extra 其他
*/
void grantPermission(RoleScope roleScope, Long target, Long userId, Role role, String extra);
/**
* 回收用户权限
* @param roleScope 权限范围
* @param target 权限目标
* @param userId 用户ID
* @param role 角色
*/
void retrievePermission(RoleScope roleScope, Long target, Long userId, Role role);
/**
* 获取有相关权限的用户
* @param roleScope 权限范围
* @param target 目标
* @return 角色对应的用户列表
*/
Map<Role, List<Long>> fetchUserWithPermissions(RoleScope roleScope, Long target);
}

View File

@ -1,27 +1,20 @@
package tech.powerjob.server.auth.service.permission;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.stereotype.Service;
import tech.powerjob.common.exception.ImpossibleException;
import tech.powerjob.server.auth.Permission;
import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.server.auth.Role;
import tech.powerjob.server.auth.RoleScope;
import tech.powerjob.server.auth.interceptor.ApiPermission;
import tech.powerjob.server.auth.interceptor.DynamicPermissionPlugin;
import tech.powerjob.server.auth.interceptor.EmptyPlugin;
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
import tech.powerjob.server.persistence.remote.model.UserRoleDO;
import tech.powerjob.server.persistence.remote.repository.AppInfoRepository;
import tech.powerjob.server.persistence.remote.repository.UserRoleRepository;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
@ -40,8 +33,8 @@ public class PowerJobPermissionServiceImpl implements PowerJobPermissionService
private UserRoleRepository userRoleRepository;
@Override
public boolean hasPermission(HttpServletRequest request, Object handler, PowerJobUser user, ApiPermission apiPermission) {
final List<UserRoleDO> userRoleList = Optional.ofNullable(userRoleRepository.findAllByUserId(user.getId())).orElse(Collections.emptyList());
public boolean hasPermission(Long userId, RoleScope roleScope, Long target, Permission requiredPermission) {
final List<UserRoleDO> userRoleList = Optional.ofNullable(userRoleRepository.findAllByUserId(userId)).orElse(Collections.emptyList());
Multimap<Long, Role> appId2Role = ArrayListMultimap.create();
Multimap<Long, Role> namespaceId2Role = ArrayListMultimap.create();
@ -50,7 +43,6 @@ public class PowerJobPermissionServiceImpl implements PowerJobPermissionService
for (UserRoleDO userRole : userRoleList) {
final Role role = Role.of(userRole.getRole());
RoleScope roleScope = RoleScope.of(userRole.getScope());
// 处理全局权限
if (RoleScope.GLOBAL.equals(roleScope)) {
@ -69,7 +61,6 @@ public class PowerJobPermissionServiceImpl implements PowerJobPermissionService
}
// 前置判断需要的权限新增场景还没有 appId or namespaceId
final Permission requiredPermission = parsePermission(request, handler, apiPermission);
if (requiredPermission == Permission.NONE) {
return true;
}
@ -82,12 +73,12 @@ public class PowerJobPermissionServiceImpl implements PowerJobPermissionService
}
// 无超级管理员权限校验普通权限
if (RoleScope.APP.equals(apiPermission.roleScope())) {
return checkAppPermission(request, requiredPermission, appId2Role, namespaceId2Role);
if (RoleScope.APP.equals(roleScope)) {
return checkAppPermission(target, requiredPermission, appId2Role, namespaceId2Role);
}
if (RoleScope.NAMESPACE.equals(apiPermission.roleScope())) {
return checkNamespacePermission(request, requiredPermission, namespaceId2Role);
if (RoleScope.NAMESPACE.equals(roleScope)) {
return checkNamespacePermission(target, requiredPermission, namespaceId2Role);
}
return false;
@ -110,15 +101,31 @@ public class PowerJobPermissionServiceImpl implements PowerJobPermissionService
log.info("[PowerJobPermissionService] saveAndFlush userRole successfully: {}", userRoleDO);
}
private boolean checkAppPermission(HttpServletRequest request, Permission requiredPermission, Multimap<Long, Role> appId2Role, Multimap<Long, Role> namespaceId2Role) {
final String appIdStr = request.getHeader("appId");
if (StringUtils.isEmpty(appIdStr)) {
throw new IllegalArgumentException("can't find appId in header, please refresh and try again!");
}
@Override
public void retrievePermission(RoleScope roleScope, Long target, Long userId, Role role) {
List<UserRoleDO> originUserRole = userRoleRepository.findAllByScopeAndTargetAndRoleAndUserId(roleScope.getV(), target, role.getV(), userId);
log.info("[PowerJobPermissionService] [retrievePermission] origin rule: {}", originUserRole);
Optional.ofNullable(originUserRole).orElse(Collections.emptyList()).forEach(r -> {
userRoleRepository.deleteById(r.getId());
log.info("[PowerJobPermissionService] [retrievePermission] delete UserRole: {}", r);
});
}
Long appId = Long.valueOf(appIdStr);
@Override
public Map<Role, List<Long>> fetchUserWithPermissions(RoleScope roleScope, Long target) {
List<UserRoleDO> permissionUserList = userRoleRepository.findAllByScopeAndTarget(roleScope.getV(), target);
Map<Role, List<Long>> ret = Maps.newHashMap();
Optional.ofNullable(permissionUserList).orElse(Collections.emptyList()).forEach(userRoleDO -> {
Role role = Role.of(userRoleDO.getRole());
List<Long> userIds = ret.computeIfAbsent(role, ignore -> Lists.newArrayList());
userIds.add(userRoleDO.getUserId());
});
return ret;
}
final Collection<Role> appRoles = appId2Role.get(appId);
private boolean checkAppPermission(Long targetId, Permission requiredPermission, Multimap<Long, Role> appId2Role, Multimap<Long, Role> namespaceId2Role) {
final Collection<Role> appRoles = appId2Role.get(targetId);
for (Role role : appRoles) {
if (role.getPermissions().contains(requiredPermission)) {
return true;
@ -126,9 +133,9 @@ public class PowerJobPermissionServiceImpl implements PowerJobPermissionService
}
// 校验 namespace 穿透权限
Optional<AppInfoDO> appInfoOpt = appInfoRepository.findById(appId);
Optional<AppInfoDO> appInfoOpt = appInfoRepository.findById(targetId);
if (!appInfoOpt.isPresent()) {
throw new IllegalArgumentException("can't find appInfo by appId in permission check: " + appId);
throw new IllegalArgumentException("can't find appInfo by appId in permission check: " + targetId);
}
Long namespaceId = Optional.ofNullable(appInfoOpt.get().getNamespaceId()).orElse(-1L);
Collection<Role> namespaceRoles = namespaceId2Role.get(namespaceId);
@ -141,14 +148,8 @@ public class PowerJobPermissionServiceImpl implements PowerJobPermissionService
return false;
}
private boolean checkNamespacePermission(HttpServletRequest request, Permission requiredPermission, Multimap<Long, Role> namespaceId2Role) {
final String namespaceIdStr = request.getHeader("namespaceId");
if (StringUtils.isEmpty(namespaceIdStr)) {
throw new IllegalArgumentException("can't find namespace in header, please refresh and try again!");
}
Long namespaceId = Long.valueOf(namespaceIdStr);
Collection<Role> namespaceRoles = namespaceId2Role.get(namespaceId);
private boolean checkNamespacePermission(Long targetId, Permission requiredPermission, Multimap<Long, Role> namespaceId2Role) {
Collection<Role> namespaceRoles = namespaceId2Role.get(targetId);
for (Role role : namespaceRoles) {
if (role.getPermissions().contains(requiredPermission)) {
return true;
@ -158,20 +159,4 @@ public class PowerJobPermissionServiceImpl implements PowerJobPermissionService
return false;
}
private static Permission parsePermission(HttpServletRequest request, Object handler, ApiPermission apiPermission) {
Class<? extends DynamicPermissionPlugin> dynamicPermissionPlugin = apiPermission.dynamicPermissionPlugin();
if (EmptyPlugin.class.equals(dynamicPermissionPlugin)) {
return apiPermission.requiredPermission();
}
try {
DynamicPermissionPlugin dynamicPermission = dynamicPermissionPlugin.getDeclaredConstructor().newInstance();
return dynamicPermission.calculate(request, handler);
} catch (Throwable t) {
log.error("[PowerJobAuthService] process dynamicPermissionPlugin failed!", t);
ExceptionUtils.rethrow(t);
}
throw new ImpossibleException();
}
}

View File

@ -16,4 +16,6 @@ public interface UserRoleRepository extends JpaRepository<UserRoleDO, Long> {
List<UserRoleDO> findAllByUserId(Long userId);
List<UserRoleDO> findAllByScopeAndTarget(Integer scope, Long target);
List<UserRoleDO> findAllByScopeAndTargetAndRoleAndUserId(Integer scope, Long target, Integer role, Long userId);
}

View File

@ -1,43 +0,0 @@
package tech.powerjob.server.auth.dp;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StreamUtils;
import tech.powerjob.common.serialize.JsonUtils;
import tech.powerjob.server.auth.Permission;
import tech.powerjob.server.auth.Role;
import tech.powerjob.server.auth.interceptor.DynamicPermissionPlugin;
import tech.powerjob.server.web.request.GrantPermissionRequest;
import javax.servlet.http.HttpServletRequest;
/**
* 授权动态权限计算
* 授予权限要低于或等于授权人自身的权限
*
* @author tjq
* @since 2024/2/12
*/
@Slf4j
public class GrantDynamicPermission implements DynamicPermissionPlugin {
@Override
public Permission calculate(HttpServletRequest request, Object handler) {
try {
//获取请求body
byte[] bodyBytes = StreamUtils.copyToByteArray(request.getInputStream());
String body = new String(bodyBytes, request.getCharacterEncoding());
GrantPermissionRequest grantPermissionRequest = JsonUtils.parseObject(body, GrantPermissionRequest.class);
Role role = Role.of(grantPermissionRequest.getRole());
switch (role) {
case OBSERVER: return Permission.READ;
case QA: return Permission.OPS;
case DEVELOPER: return Permission.WRITE;
}
} catch (Exception e) {
log.error("[GrantDynamicPermission] check permission failed, please fix the bug!!!", e);
}
return Permission.SU;
}
}

View File

@ -1,4 +1,4 @@
package tech.powerjob.server.auth.dp;
package tech.powerjob.server.auth.plugin;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StreamUtils;

View File

@ -1,4 +1,4 @@
package tech.powerjob.server.auth.gp;
package tech.powerjob.server.auth.plugin;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;

View File

@ -0,0 +1,41 @@
package tech.powerjob.server.auth.service;
import tech.powerjob.server.auth.Permission;
import tech.powerjob.server.auth.RoleScope;
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
/**
* Web Auth 服务
* 写在 starter 包下抽取 controller 的公共逻辑
* powerjob service/core 包核心处理调度核心逻辑admin 部分代码收口在 stater
*
* @author tjq
* @since 2024/2/12
*/
public interface WebAuthService {
/**
* 处理授权
* @param roleScope 权限范围
* @param target 权限目标
* @param componentUserRoleInfo 人员角色信息
*/
void processPermissionOnSave(RoleScope roleScope, Long target, ComponentUserRoleInfo componentUserRoleInfo);
/**
* 获取目标相关权限人员列表
* @param roleScope 权限范围
* @param target 权限目标
* @return ComponentUserRoleInfo
*/
ComponentUserRoleInfo fetchComponentUserRoleInfo(RoleScope roleScope, Long target);
/**
* 判断当前用户是否有权限
* @param roleScope 权限范围
* @param target 权限目标
* @param permission 要求的权限
* @return 是否有权限
*/
boolean hasPermission(RoleScope roleScope, Long target, Permission permission);
}

View File

@ -0,0 +1,93 @@
package tech.powerjob.server.auth.service.impl;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import tech.powerjob.common.serialize.JsonUtils;
import tech.powerjob.server.auth.*;
import tech.powerjob.server.auth.service.WebAuthService;
import tech.powerjob.server.auth.service.permission.PowerJobPermissionService;
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
import javax.annotation.Resource;
import java.util.*;
/**
* WebAuthService
*
* @author tjq
* @since 2024/2/12
*/
@Slf4j
@Service
public class WebAuthServiceImpl implements WebAuthService {
@Resource
private PowerJobPermissionService powerJobPermissionService;
@Override
public void processPermissionOnSave(RoleScope roleScope, Long target, ComponentUserRoleInfo o) {
ComponentUserRoleInfo componentUserRoleInfo = Optional.ofNullable(o).orElse(new ComponentUserRoleInfo());
Map<Role, List<Long>> role2Uids = powerJobPermissionService.fetchUserWithPermissions(roleScope, target);
diffGrant(roleScope, target, Role.OBSERVER, componentUserRoleInfo.getObserver(), role2Uids);
diffGrant(roleScope, target, Role.QA, componentUserRoleInfo.getQa(), role2Uids);
diffGrant(roleScope, target, Role.DEVELOPER, componentUserRoleInfo.getDeveloper(), role2Uids);
diffGrant(roleScope, target, Role.ADMIN, componentUserRoleInfo.getAdmin(), role2Uids);
}
@Override
public ComponentUserRoleInfo fetchComponentUserRoleInfo(RoleScope roleScope, Long target) {
Map<Role, List<Long>> role2Uids = powerJobPermissionService.fetchUserWithPermissions(roleScope, target);
return new ComponentUserRoleInfo()
.setObserver(role2Uids.getOrDefault(Role.OBSERVER, Collections.emptyList()))
.setQa(role2Uids.getOrDefault(Role.QA, Collections.emptyList()))
.setDeveloper(role2Uids.getOrDefault(Role.DEVELOPER, Collections.emptyList()))
.setAdmin(role2Uids.getOrDefault(Role.ADMIN, Collections.emptyList()));
}
@Override
public boolean hasPermission(RoleScope roleScope, Long target, Permission permission) {
PowerJobUser powerJobUser = LoginUserHolder.get();
if (powerJobUser == null) {
return false;
}
powerJobPermissionService.hasPermission(powerJobUser.getId(), roleScope, target, permission);
return false;
}
private void diffGrant(RoleScope roleScope, Long target, Role role, List<Long> uids, Map<Role, List<Long>> originRole2Uids) {
Set<Long> orignUids = Sets.newHashSet(Optional.ofNullable(originRole2Uids.get(role)).orElse(Collections.emptyList()));
Set<Long> currentUids = Sets.newHashSet(Optional.ofNullable(uids).orElse(Collections.emptyList()));
Map<String, Object> extraInfo = Maps.newHashMap();
extraInfo.put("grantor", LoginUserHolder.getUserName());
extraInfo.put("source", "diffGrant");
String extra = JsonUtils.toJSONString(extraInfo);
Set<Long> allIds = Sets.newHashSet(orignUids);
allIds.addAll(currentUids);
Set<Long> allIds2 = Sets.newHashSet(allIds);
// orignUids 不在 currentUids需要取消授权
allIds.removeAll(currentUids);
allIds.forEach(cancelPermissionUid -> {
powerJobPermissionService.retrievePermission(roleScope, target, cancelPermissionUid, role);
log.info("[WebAuthService] [diffGrant] cancelPermission: roleScope={},target={},uid={},role={}", roleScope, target, cancelPermissionUid, role);
});
// currentUids 当不在 orignUids需要增加授权
allIds2.removeAll(orignUids);
allIds2.forEach(addPermissionUid -> {
powerJobPermissionService.grantPermission(roleScope, target, addPermissionUid, role, extra);
log.info("[WebAuthService] [diffGrant] grantPermission: roleScope={},target={},uid={},role={},extra={}", roleScope, target, addPermissionUid, role, extra);
});
}
}

View File

@ -1,26 +1,18 @@
package tech.powerjob.server.web.controller;
import com.google.common.collect.Maps;
import org.springframework.web.bind.annotation.*;
import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.common.serialize.JsonUtils;
import tech.powerjob.server.auth.*;
import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.server.auth.common.AuthConstants;
import tech.powerjob.server.auth.dp.GrantDynamicPermission;
import tech.powerjob.server.auth.interceptor.ApiPermission;
import tech.powerjob.server.auth.login.LoginTypeInfo;
import tech.powerjob.server.auth.service.login.LoginRequest;
import tech.powerjob.server.auth.service.login.PowerJobLoginService;
import tech.powerjob.server.auth.service.permission.PowerJobPermissionService;
import tech.powerjob.server.web.request.GrantPermissionRequest;
import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
@ -35,8 +27,6 @@ public class AuthController {
@Resource
private PowerJobLoginService powerJobLoginService;
@Resource
private PowerJobPermissionService powerJobPermissionService;
@GetMapping("/supportLoginTypes")
public ResultDTO<List<LoginTypeInfo>> listSupportLoginTypes() {
@ -94,40 +84,10 @@ public class AuthController {
@GetMapping(value = "/ifLogin")
public ResultDTO<PowerJobUser> ifLogin(HttpServletRequest httpServletRequest) {
final Optional<PowerJobUser> powerJobUser = powerJobLoginService.ifLogin(httpServletRequest);
return powerJobUser.map(ResultDTO::success).orElseGet(() -> ResultDTO.failed("LOGIN_FAILED"));
return powerJobUser.map(ResultDTO::success).orElseGet(() -> ResultDTO.success(null));
}
private void fillJwt4LoginUser(PowerJobUser powerJobUser, HttpServletResponse httpServletResponse) {
httpServletResponse.addCookie(new Cookie(AuthConstants.JWT_NAME, powerJobUser.getJwtToken()));
}
/* 授权相关 */
@PostMapping("/grantApp")
@ApiPermission(name = "Auth-GrantAppPermission", roleScope = RoleScope.APP, dynamicPermissionPlugin = GrantDynamicPermission.class)
public ResultDTO<Void> grantAppPermission(GrantPermissionRequest grantPermissionRequest) {
grantPermission(RoleScope.APP, grantPermissionRequest);
return ResultDTO.success(null);
}
@PostMapping("/grantNamespace")
@ApiPermission(name = "Auth-GrantNamespacePermission", roleScope = RoleScope.NAMESPACE, dynamicPermissionPlugin = GrantDynamicPermission.class)
public ResultDTO<Void> grantNamespacePermission(GrantPermissionRequest grantPermissionRequest) {
grantPermission(RoleScope.NAMESPACE, grantPermissionRequest);
return ResultDTO.success(null);
}
private void grantPermission(RoleScope roleScope, GrantPermissionRequest grantPermissionRequest) {
Role role = Role.of(grantPermissionRequest.getRole());
Optional.ofNullable(grantPermissionRequest.getUserIds()).orElse(Collections.emptyList()).forEach(uid -> {
// 记录授权人信息
Map<String, Object> extraInfo = Maps.newHashMap();
extraInfo.put("grantor", LoginUserHolder.getUserName());
String extra = JsonUtils.toJSONString(extraInfo);
powerJobPermissionService.grantPermission(roleScope, grantPermissionRequest.getTargetId(), uid, role, extra);
});
}
}

View File

@ -1,10 +1,8 @@
package tech.powerjob.server.web.controller;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
@ -13,31 +11,28 @@ import org.springframework.web.bind.annotation.*;
import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.server.auth.LoginUserHolder;
import tech.powerjob.server.auth.Permission;
import tech.powerjob.server.auth.Role;
import tech.powerjob.server.auth.RoleScope;
import tech.powerjob.server.auth.dp.ModifyOrCreateDynamicPermission;
import tech.powerjob.server.auth.gp.SaveNamespaceGrantPermissionPlugin;
import tech.powerjob.server.auth.interceptor.ApiPermission;
import tech.powerjob.server.auth.plugin.ModifyOrCreateDynamicPermission;
import tech.powerjob.server.auth.plugin.SaveNamespaceGrantPermissionPlugin;
import tech.powerjob.server.auth.service.WebAuthService;
import tech.powerjob.server.common.constants.SwitchableStatus;
import tech.powerjob.server.persistence.PageResult;
import tech.powerjob.server.persistence.QueryConvertUtils;
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
import tech.powerjob.server.persistence.remote.model.UserInfoDO;
import tech.powerjob.server.persistence.remote.model.UserRoleDO;
import tech.powerjob.server.persistence.remote.repository.NamespaceRepository;
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
import tech.powerjob.server.persistence.remote.repository.UserRoleRepository;
import tech.powerjob.server.web.converter.NamespaceConverter;
import tech.powerjob.server.web.converter.UserConverter;
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
import tech.powerjob.server.web.request.ModifyNamespaceRequest;
import tech.powerjob.server.web.request.QueryNamespaceRequest;
import tech.powerjob.server.web.response.NamespaceBaseVO;
import tech.powerjob.server.web.response.NamespaceDetailVO;
import tech.powerjob.server.web.response.UserBaseVO;
import tech.powerjob.server.web.response.NamespaceVO;
import javax.annotation.Resource;
import javax.persistence.criteria.Predicate;
import java.util.*;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
/**
@ -51,23 +46,24 @@ import java.util.stream.Collectors;
@RequestMapping("/namespace")
public class NamespaceController {
@Resource
private WebAuthService webAuthService;
@Resource
private NamespaceRepository namespaceRepository;
@Resource
private UserInfoRepository userInfoRepository;
@Resource
private UserRoleRepository userRoleRepository;
@ResponseBody
@PostMapping("/save")
@ApiPermission(name = "Namespace-Save", roleScope = RoleScope.NAMESPACE, dynamicPermissionPlugin = ModifyOrCreateDynamicPermission.class, grandPermissionPlugin = SaveNamespaceGrantPermissionPlugin.class)
public ResultDTO<NamespaceBaseVO> save(@RequestBody ModifyNamespaceRequest req) {
public ResultDTO<NamespaceVO> save(@RequestBody ModifyNamespaceRequest req) {
req.valid();
Long id = req.getId();
NamespaceDO namespaceDO;
if (id == null) {
boolean isCreate = id == null;
if (isCreate) {
namespaceDO = new NamespaceDO();
namespaceDO.setGmtCreate(new Date());
@ -80,20 +76,36 @@ public class NamespaceController {
} else {
namespaceDO = fetchById(id);
namespaceDO.setModifier(LoginUserHolder.getUserName());
if (!namespaceDO.getCode().equalsIgnoreCase(req.getCode())) {
throw new IllegalArgumentException("NOT_ALLOW_CHANGE_THE_NAMESPACE_CODE");
}
}
// 拷贝通用变更属性code 不允许更改
namespaceDO.setTags(req.getTags());
namespaceDO.setName(req.getName());
namespaceDO.setExtra(req.getExtra());
namespaceDO.setStatus(Optional.ofNullable(req.getStatus()).orElse(SwitchableStatus.ENABLE.getV()));
namespaceDO.setGmtModified(new Date());
NamespaceDO savedNamespace = namespaceRepository.save(namespaceDO);
// 授权
webAuthService.processPermissionOnSave(RoleScope.NAMESPACE, savedNamespace.getId(), req.getComponentUserRoleInfo());
return ResultDTO.success(NamespaceConverter.do2BaseVo(savedNamespace));
}
@DeleteMapping("/delete")
@ApiPermission(name = "Namespace-Delete", roleScope = RoleScope.NAMESPACE, requiredPermission = Permission.SU)
public ResultDTO<Void> deleteNamespace(Long id) {
namespaceRepository.deleteById(id);
return ResultDTO.success(null);
}
@PostMapping("/list")
public ResultDTO<PageResult<NamespaceBaseVO>> listNamespace(@RequestBody QueryNamespaceRequest queryNamespaceRequest) {
public ResultDTO<PageResult<NamespaceVO>> listNamespace(@RequestBody QueryNamespaceRequest queryNamespaceRequest) {
String codeLike = queryNamespaceRequest.getCodeLike();
String nameLike = queryNamespaceRequest.getNameLike();
@ -123,39 +135,29 @@ public class NamespaceController {
Page<NamespaceDO> namespacePageResult = namespaceRepository.findAll(specification, pageable);
PageResult<NamespaceBaseVO> ret = new PageResult<>(namespacePageResult);
ret.setData(namespacePageResult.get().map(NamespaceConverter::do2BaseVo).collect(Collectors.toList()));
PageResult<NamespaceVO> ret = new PageResult<>(namespacePageResult);
ret.setData(namespacePageResult.get().map(x -> {
NamespaceVO namespaceVO = NamespaceConverter.do2BaseVo(x);
fillPermissionInfo(x, namespaceVO);
return namespaceVO;
}).collect(Collectors.toList()));
return ResultDTO.success(ret);
}
@GetMapping("/detail")
@ApiPermission(name = "Namespace-GetDetail", roleScope = RoleScope.NAMESPACE, requiredPermission = Permission.READ)
public ResultDTO<NamespaceDetailVO> queryNamespaceDetail(Long id) {
private void fillPermissionInfo(NamespaceDO namespaceDO, NamespaceVO namespaceVO) {
NamespaceDO namespaceDO = fetchById(id);
NamespaceDetailVO namespaceDetailVO = new NamespaceDetailVO();
Long namespaceId = namespaceVO.getId();
// 拷贝基础字段
NamespaceBaseVO namespaceBaseVO = NamespaceConverter.do2BaseVo(namespaceDO);
BeanUtils.copyProperties(namespaceBaseVO, namespaceDetailVO);
// 权限用户关系
ComponentUserRoleInfo componentUserRoleInfo = webAuthService.fetchComponentUserRoleInfo(RoleScope.NAMESPACE, namespaceId);
namespaceVO.setComponentUserRoleInfo(componentUserRoleInfo);
// 处理 token
namespaceDetailVO.setToken(namespaceDO.getToken());
// 处理权限视图
Map<String, List<UserBaseVO>> privilegedUsers = Maps.newHashMap();
namespaceDetailVO.setPrivilegedUsers(privilegedUsers);
List<UserRoleDO> permissionUserList = userRoleRepository.findAllByScopeAndTarget(RoleScope.NAMESPACE.getV(), namespaceDO.getId());
permissionUserList.forEach(r -> {
Role role = Role.of(r.getRole());
List<UserBaseVO> userBaseVOList = privilegedUsers.computeIfAbsent(role.name(), ignore -> Lists.newArrayList());
Optional<UserInfoDO> userInfoDoOpt = userInfoRepository.findById(r.getUserId());
userInfoDoOpt.ifPresent(userInfoDO -> userBaseVOList.add(UserConverter.do2BaseVo(userInfoDO)));
});
return ResultDTO.success(namespaceDetailVO);
// 有权限用户填充 token
boolean hasPermission = webAuthService.hasPermission(RoleScope.NAMESPACE, namespaceId, Permission.READ);
if (hasPermission) {
namespaceVO.setToken(namespaceDO.getToken());
}
}
private NamespaceDO fetchById(Long id) {

View File

@ -4,7 +4,7 @@ import org.springframework.beans.BeanUtils;
import tech.powerjob.common.utils.CommonUtils;
import tech.powerjob.server.common.constants.SwitchableStatus;
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
import tech.powerjob.server.web.response.NamespaceBaseVO;
import tech.powerjob.server.web.response.NamespaceVO;
/**
* NamespaceConverter
@ -14,8 +14,8 @@ import tech.powerjob.server.web.response.NamespaceBaseVO;
*/
public class NamespaceConverter {
public static NamespaceBaseVO do2BaseVo(NamespaceDO d) {
NamespaceBaseVO v = new NamespaceBaseVO();
public static NamespaceVO do2BaseVo(NamespaceDO d) {
NamespaceVO v = new NamespaceVO();
BeanUtils.copyProperties(d, v);
v.setGmtCreateStr(CommonUtils.formatTime(d.getGmtCreate()));
v.setGmtModifiedStr(CommonUtils.formatTime(d.getGmtModified()));

View File

@ -0,0 +1,34 @@
package tech.powerjob.server.web.request;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 组件上的用户角色信息
*
* @author tjq
* @since 2024/2/12
*/
@Data
@Accessors(chain = true)
public class ComponentUserRoleInfo {
/**
* 观察者
*/
private List<Long> observer;
/**
* 测试
*/
private List<Long> qa;
/**
* 开发者
*/
private List<Long> developer;
/**
* 管理员
*/
private List<Long> admin;
}

View File

@ -39,6 +39,10 @@ public class ModifyNamespaceRequest {
* 扩展字段
*/
private String extra;
/**
* 权限表单
*/
private ComponentUserRoleInfo componentUserRoleInfo;
public void valid() {
CommonUtils.requireNonNull(code, "namespace code can't be empty");

View File

@ -1,29 +0,0 @@
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 2023/9/3
*/
@Getter
@Setter
@ToString(callSuper = true)
public class NamespaceDetailVO extends NamespaceBaseVO {
/**
* 访问 token
*/
private String token;
/**
* 有权限的用户
*/
private Map<String, List<UserBaseVO>> privilegedUsers;
}

View File

@ -3,6 +3,7 @@ package tech.powerjob.server.web.response;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
import java.io.Serializable;
import java.util.Date;
@ -16,7 +17,7 @@ import java.util.Date;
@Getter
@Setter
@ToString
public class NamespaceBaseVO implements Serializable {
public class NamespaceVO implements Serializable {
private Long id;
@ -52,4 +53,13 @@ public class NamespaceBaseVO implements Serializable {
private String creator;
private String modifier;
/**
* 访问 token
* 仅拥有当前 namespace 权限的访问者可见
*/
private String token;
private ComponentUserRoleInfo componentUserRoleInfo;
}