package jnpf.dmsService; import jnpf.base.UserInfo; import jnpf.dmsEntity.permission.entity.DmsFilePermissionEntity; import jnpf.dmsEntity.permission.enums.DmsPermissionAction; import jnpf.dmsEntity.permission.form.PermissionDecisionForm; import jnpf.dmsEntity.permission.model.DmsResourcePath; import jnpf.dmsEntity.permission.model.DmsSubjectSet; import jnpf.dmsEntity.permission.vo.DmsPermissionDecisionVO; import jnpf.dmsMapper.DmsFilePermissionMapper; import jnpf.dmsIntegration.audit.DmsAuditAdapter; import jnpf.dmsPermission.DmsAccessExclusionResolver; import jnpf.dmsPermission.DmsCurrentIdentity; import jnpf.dmsPermission.DmsPermissionError; import jnpf.dmsPermission.DmsPermissionException; import jnpf.dmsPermission.DmsPermissionGroupMembershipResolver; import jnpf.dmsPermission.DmsPermissionMatcher; import jnpf.dmsPermission.DmsPermissionProperties; import jnpf.dmsPermission.DmsResourceResolver; import jnpf.dmsPermission.DmsSubjectResolver; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class DmsPermissionDecisionService { private final DmsCurrentIdentity currentIdentity; private final DmsSubjectResolver subjectResolver; private final DmsResourceResolver resourceResolver; private final DmsFilePermissionMapper permissionMapper; private final DmsPermissionGroupMembershipResolver groupMembershipResolver; private final DmsPermissionMatcher matcher; private final DmsPermissionProperties properties; private final DmsAuditAdapter auditAdapter; private final DmsAccessExclusionResolver exclusionResolver; @Transactional(readOnly = true) public DmsPermissionDecisionVO evaluate(PermissionDecisionForm request) { return evaluate(Collections.singletonList(request)).get(0); } @Transactional(readOnly = true) public List evaluate(List requests) { if (requests == null || requests.isEmpty() || requests.size() > properties.getMaxDecisionBatchSize()) { throw DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST, "裁决批量大小必须在 1 到 " + properties.getMaxDecisionBatchSize() + " 之间"); } UserInfo user = currentIdentity.requireUser(); String tenantId = currentIdentity.tenantId(user); Map paths = resourceResolver.resolveAll(tenantId, requests); List readPaths = requests.stream() .filter(request -> request.getActionCode() == DmsPermissionAction.READ) .map(request -> paths.get(request.getResourceId())) .collect(Collectors.toList()); DmsAccessExclusionResolver.Snapshot exclusions = exclusionResolver.resolve( tenantId, user.getUserId().trim(), readPaths); List eligibleRequests = requests.stream() .filter(request -> !isReadExcluded(request, paths.get(request.getResourceId()), exclusions)) .collect(Collectors.toList()); Map> candidatesByAction = new java.util.HashMap<>(); Set permissionGroupIds = new LinkedHashSet<>(); DmsSubjectSet subjects = null; if (!eligibleRequests.isEmpty()) { subjects = subjectResolver.resolve(user); LinkedHashSet actionCodes = new LinkedHashSet<>(); LinkedHashSet resourceIds = new LinkedHashSet<>(); for (PermissionDecisionForm request : eligibleRequests) { actionCodes.add(request.getActionCode().name()); resourceIds.addAll(resourceResolver.candidateResourceIds(paths.get(request.getResourceId()))); } List candidates = permissionMapper.selectCandidates(tenantId, new ArrayList<>(actionCodes), new ArrayList<>(resourceIds)); for (DmsFilePermissionEntity candidate : candidates) { candidatesByAction.computeIfAbsent(candidate.getActionCode(), key -> new ArrayList<>()) .add(candidate); if ("GROUP".equals(candidate.getPrincipalType())) { permissionGroupIds.add(candidate.getPrincipalId()); } } groupMembershipResolver.resolve(permissionGroupIds, subjects); } List result = new ArrayList<>(); for (PermissionDecisionForm request : requests) { DmsPermissionDecisionVO decision = denied(request); DmsResourcePath path = paths.get(request.getResourceId()); if (isReadExcluded(request, path, exclusions)) { decision.setReasonCode("USER_READ_EXCLUDED"); } else { for (DmsFilePermissionEntity candidate : candidatesByAction.getOrDefault( request.getActionCode().name(), Collections.emptyList())) { if (matcher.matches(candidate, path, subjects)) { decision.setAllowed(true); decision.setReasonCode("ALLOWED"); decision.setMatchedPermissionId(candidate.getId()); if (candidate.getMaxUseCount() != null) { decision.setRemainingUseCount(candidate.getMaxUseCount() - candidate.getUsedCount()); } break; } } } auditAdapter.permissionDecision(decision.isAllowed(), request.getActionCode().name(), request.getResourceId().toString(), "{\"reasonCode\":\"" + decision.getReasonCode() + "\"}"); result.add(decision); } return result; } private boolean isReadExcluded(PermissionDecisionForm request, DmsResourcePath path, DmsAccessExclusionResolver.Snapshot exclusions) { return request.getActionCode() == DmsPermissionAction.READ && exclusions.excludes(path); } public void requireAllowed(jnpf.dmsEntity.permission.enums.DmsResourceType type, UUID id, jnpf.dmsEntity.permission.enums.DmsPermissionAction action) { PermissionDecisionForm request = new PermissionDecisionForm(); request.setResourceType(type); request.setResourceId(id); request.setActionCode(action); if (!evaluate(request).isAllowed()) { throw DmsPermissionException.of(DmsPermissionError.PERMISSION_DENIED, "当前用户没有所需的 " + action.name() + " 权限"); } } private DmsPermissionDecisionVO denied(PermissionDecisionForm request) { DmsPermissionDecisionVO result = new DmsPermissionDecisionVO(); result.setResourceId(request.getResourceId()); result.setResourceType(request.getResourceType().name()); result.setActionCode(request.getActionCode().name()); result.setAllowed(false); result.setReasonCode("NO_MATCHING_PERMISSION"); return result; } }