feat: support user related query

This commit is contained in:
tjq 2024-02-13 16:22:56 +08:00
parent 31a7690844
commit c350607762
9 changed files with 83 additions and 5 deletions

View File

@ -47,9 +47,17 @@ public interface PowerJobPermissionService {
/** /**
* 获取有相关权限的用户 * 获取有相关权限的用户
* @param roleScope 权限范围 * @param roleScope 角色范围
* @param target 目标 * @param target 目标
* @return 角色对应的用户列表 * @return 角色对应的用户列表
*/ */
Map<Role, List<Long>> fetchUserWithPermissions(RoleScope roleScope, Long target); Map<Role, List<Long>> fetchUserWithPermissions(RoleScope roleScope, Long target);
/**
* 获取用户有权限的目标
* @param roleScope 角色范围
* @param userId 用户ID
* @return result
*/
Map<Role, List<Long>> fetchUserHadPermissionTargets(RoleScope roleScope, Long userId);
} }

View File

@ -123,6 +123,21 @@ public class PowerJobPermissionServiceImpl implements PowerJobPermissionService
return ret; return ret;
} }
@Override
public Map<Role, List<Long>> fetchUserHadPermissionTargets(RoleScope roleScope, Long userId) {
Map<Role, List<Long>> ret = Maps.newHashMap();
List<UserRoleDO> userRoleDOList = userRoleRepository.findAllByUserIdAndScope(userId, roleScope.getV());
Optional.ofNullable(userRoleDOList).orElse(Collections.emptyList()).forEach(r -> {
Role role = Role.of(r.getRole());
List<Long> targetIds = ret.computeIfAbsent(role, ignore -> Lists.newArrayList());
targetIds.add(r.getTarget());
});
return ret;
}
private boolean checkAppPermission(Long targetId, Permission requiredPermission, Multimap<Long, Role> appId2Role, Multimap<Long, Role> namespaceId2Role) { private boolean checkAppPermission(Long targetId, Permission requiredPermission, Multimap<Long, Role> appId2Role, Multimap<Long, Role> namespaceId2Role) {
final Collection<Role> appRoles = appId2Role.get(targetId); final Collection<Role> appRoles = appId2Role.get(targetId);

View File

@ -32,4 +32,6 @@ public interface AppInfoRepository extends JpaRepository<AppInfoDO, Long>, JpaSp
@Query(value = "select id from AppInfoDO where currentServer = :currentServer") @Query(value = "select id from AppInfoDO where currentServer = :currentServer")
List<Long> listAppIdByCurrentServer(@Param("currentServer")String currentServer); List<Long> listAppIdByCurrentServer(@Param("currentServer")String currentServer);
List<AppInfoDO> findAllByNamespaceId(Long namespaceId);
} }

View File

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

View File

@ -4,6 +4,8 @@ import tech.powerjob.server.auth.Permission;
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;
/** /**
* Web Auth 服务 * Web Auth 服务
* 写在 starter 包下抽取 controller 的公共逻辑 * 写在 starter 包下抽取 controller 的公共逻辑
@ -38,4 +40,6 @@ public interface WebAuthService {
* @return 是否有权限 * @return 是否有权限
*/ */
boolean hasPermission(RoleScope roleScope, Long target, Permission permission); boolean hasPermission(RoleScope roleScope, Long target, Permission permission);
Set<Long> fetchMyPermissionTargets(RoleScope roleScope);
} }

View File

@ -6,6 +6,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import tech.powerjob.common.serialize.JsonUtils; import tech.powerjob.common.serialize.JsonUtils;
import tech.powerjob.server.auth.*; import tech.powerjob.server.auth.*;
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.WebAuthService;
import tech.powerjob.server.auth.service.permission.PowerJobPermissionService; import tech.powerjob.server.auth.service.permission.PowerJobPermissionService;
import tech.powerjob.server.web.request.ComponentUserRoleInfo; import tech.powerjob.server.web.request.ComponentUserRoleInfo;
@ -59,6 +61,23 @@ public class WebAuthServiceImpl implements WebAuthService {
return powerJobPermissionService.hasPermission(powerJobUser.getId(), roleScope, target, permission); return powerJobPermissionService.hasPermission(powerJobUser.getId(), roleScope, target, permission);
} }
@Override
public Set<Long> fetchMyPermissionTargets(RoleScope roleScope) {
PowerJobUser powerJobUser = LoginUserHolder.get();
if (powerJobUser == null) {
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
return targetIds;
}
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) {
Set<Long> originUids = Sets.newHashSet(Optional.ofNullable(originRole2Uids.get(role)).orElse(Collections.emptyList())); Set<Long> originUids = Sets.newHashSet(Optional.ofNullable(originRole2Uids.get(role)).orElse(Collections.emptyList()));

View File

@ -30,9 +30,9 @@ public class ControllerExceptionHandler {
// 不是所有异常都需要打印完整堆栈后续可以定义内部的Exception便于判断 // 不是所有异常都需要打印完整堆栈后续可以定义内部的Exception便于判断
if (e instanceof PowerJobException) { if (e instanceof PowerJobException) {
ret.setCode(((PowerJobException) e).getCode()); ret.setCode(((PowerJobException) e).getCode());
log.warn("[ControllerException] PowerJobException, message is {}.", ExceptionUtils.getMessage(e)); log.warn("[ControllerException] PowerJobException, message is {}.", e.getMessage());
} else if (e instanceof IllegalArgumentException) { } else if (e instanceof IllegalArgumentException) {
log.warn("[ControllerException] http request failed, message is {}.", e.getMessage()); log.warn("[ControllerException] http request failed due to IllegalArgument, message is {}.", e.getMessage());
} else if (e instanceof HttpMessageNotReadableException || e instanceof MethodArgumentTypeMismatchException) { } else if (e instanceof HttpMessageNotReadableException || e instanceof MethodArgumentTypeMismatchException) {
log.warn("[ControllerException] invalid http request params, exception is {}.", e.getMessage()); log.warn("[ControllerException] invalid http request params, exception is {}.", e.getMessage());
} else if (e instanceof HttpRequestMethodNotSupportedException) { } else if (e instanceof HttpRequestMethodNotSupportedException) {

View File

@ -2,6 +2,7 @@ package tech.powerjob.server.web.controller;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.BooleanUtils;
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.data.domain.Page; import org.springframework.data.domain.Page;
@ -30,8 +31,10 @@ import tech.powerjob.server.web.request.QueryAppInfoRequest;
import tech.powerjob.server.web.response.AppInfoVO; import tech.powerjob.server.web.response.AppInfoVO;
import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Predicate;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -102,11 +105,18 @@ public class AppInfoController {
Pageable pageable = PageRequest.of(queryAppInfoRequest.getIndex(), queryAppInfoRequest.getPageSize()); Pageable pageable = PageRequest.of(queryAppInfoRequest.getIndex(), queryAppInfoRequest.getPageSize());
// TODO: 我有权限的列表 // 相关权限先查处关联 ids
Set<Long> queryAppIds;
Boolean showMyRelated = queryAppInfoRequest.getShowMyRelated();
if (BooleanUtils.isTrue(showMyRelated)) {
queryAppIds = webAuthService.fetchMyPermissionTargets(RoleScope.APP);
} else {
queryAppIds = Collections.emptySet();
}
Specification<AppInfoDO> specification = (root, query, criteriaBuilder) -> { Specification<AppInfoDO> specification = (root, query, criteriaBuilder) -> {
List<Predicate> predicates = Lists.newArrayList(); List<Predicate> predicates = Lists.newArrayList();
Long appId = queryAppInfoRequest.getAppId(); Long appId = queryAppInfoRequest.getAppId();
Long namespaceId = queryAppInfoRequest.getNamespaceId(); Long namespaceId = queryAppInfoRequest.getNamespaceId();
@ -122,6 +132,10 @@ public class AppInfoController {
predicates.add(criteriaBuilder.like(root.get("appName"), QueryConvertUtils.convertLikeParams(queryAppInfoRequest.getAppNameLike()))); predicates.add(criteriaBuilder.like(root.get("appName"), QueryConvertUtils.convertLikeParams(queryAppInfoRequest.getAppNameLike())));
} }
if (!queryAppIds.isEmpty()) {
predicates.add(criteriaBuilder.in(root.get("id")).value(queryAppIds));
}
return query.where(predicates.toArray(new Predicate[0])).getRestriction(); return query.where(predicates.toArray(new Predicate[0])).getRestriction();
}; };

View File

@ -2,12 +2,14 @@ package tech.powerjob.server.web.controller;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.Specification;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.common.response.ResultDTO; import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.server.auth.LoginUserHolder; import tech.powerjob.server.auth.LoginUserHolder;
import tech.powerjob.server.auth.Permission; import tech.powerjob.server.auth.Permission;
@ -17,10 +19,13 @@ import tech.powerjob.server.auth.interceptor.ApiPermission;
import tech.powerjob.server.auth.plugin.ModifyOrCreateDynamicPermission; import tech.powerjob.server.auth.plugin.ModifyOrCreateDynamicPermission;
import tech.powerjob.server.auth.plugin.SaveNamespaceGrantPermissionPlugin; import tech.powerjob.server.auth.plugin.SaveNamespaceGrantPermissionPlugin;
import tech.powerjob.server.auth.service.WebAuthService; import tech.powerjob.server.auth.service.WebAuthService;
import tech.powerjob.server.common.SJ;
import tech.powerjob.server.common.constants.SwitchableStatus; import tech.powerjob.server.common.constants.SwitchableStatus;
import tech.powerjob.server.persistence.PageResult; import tech.powerjob.server.persistence.PageResult;
import tech.powerjob.server.persistence.QueryConvertUtils; import tech.powerjob.server.persistence.QueryConvertUtils;
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
import tech.powerjob.server.persistence.remote.model.NamespaceDO; import tech.powerjob.server.persistence.remote.model.NamespaceDO;
import tech.powerjob.server.persistence.remote.repository.AppInfoRepository;
import tech.powerjob.server.persistence.remote.repository.NamespaceRepository; import tech.powerjob.server.persistence.remote.repository.NamespaceRepository;
import tech.powerjob.server.web.converter.NamespaceConverter; import tech.powerjob.server.web.converter.NamespaceConverter;
import tech.powerjob.server.web.request.ComponentUserRoleInfo; import tech.powerjob.server.web.request.ComponentUserRoleInfo;
@ -51,6 +56,8 @@ public class NamespaceController {
@Resource @Resource
private WebAuthService webAuthService; private WebAuthService webAuthService;
@Resource @Resource
private AppInfoRepository appInfoRepository;
@Resource
private NamespaceRepository namespaceRepository; private NamespaceRepository namespaceRepository;
@ResponseBody @ResponseBody
@ -102,6 +109,13 @@ public class NamespaceController {
@DeleteMapping("/delete") @DeleteMapping("/delete")
@ApiPermission(name = "Namespace-Delete", roleScope = RoleScope.NAMESPACE, requiredPermission = Permission.SU) @ApiPermission(name = "Namespace-Delete", roleScope = RoleScope.NAMESPACE, requiredPermission = Permission.SU)
public ResultDTO<Void> deleteNamespace(Long id) { public ResultDTO<Void> deleteNamespace(Long id) {
List<AppInfoDO> appInfosInNamespace = appInfoRepository.findAllByNamespaceId(id);
if (CollectionUtils.isNotEmpty(appInfosInNamespace)) {
List<String> relatedApps = appInfosInNamespace.stream().map(AppInfoDO::getAppName).collect(Collectors.toList());
throw new PowerJobException("Unable to delete due to associated apps: " + SJ.COMMA_JOINER.join(relatedApps));
}
namespaceRepository.deleteById(id); namespaceRepository.deleteById(id);
return ResultDTO.success(null); return ResultDTO.success(null);
} }