455 lines
19 KiB
Java
455 lines
19 KiB
Java
package com.cloudhandson.vpdbackoffice.web;
|
|
|
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyCreateCommand;
|
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView;
|
|
import com.cloudhandson.vpdbackoffice.service.AppException;
|
|
import com.cloudhandson.vpdbackoffice.service.VpdPolicyService;
|
|
import java.util.List;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.Map;
|
|
import org.springframework.dao.DataAccessException;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.springframework.stereotype.Controller;
|
|
import org.springframework.ui.Model;
|
|
import org.springframework.web.bind.annotation.GetMapping;
|
|
import org.springframework.web.bind.annotation.PostMapping;
|
|
import org.springframework.web.bind.annotation.RequestParam;
|
|
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
|
|
|
@Controller
|
|
public class VpdPolicyController {
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(VpdPolicyController.class);
|
|
private final VpdPolicyService vpdPolicyService;
|
|
|
|
public VpdPolicyController(VpdPolicyService vpdPolicyService) {
|
|
this.vpdPolicyService = vpdPolicyService;
|
|
}
|
|
|
|
@GetMapping("/vpd-policies")
|
|
public String policies(
|
|
@RequestParam(required = false) String schemaOwner,
|
|
Model model
|
|
) {
|
|
populatePolicyModel(schemaOwner, false, model);
|
|
return "vpd-policies";
|
|
}
|
|
|
|
@GetMapping("/vpd-filter-policies")
|
|
public String filterPolicies(
|
|
@RequestParam(required = false) String schemaOwner,
|
|
Model model
|
|
) {
|
|
populatePolicyModel(schemaOwner, true, model);
|
|
return "vpd-filter-policies";
|
|
}
|
|
|
|
/** Read-only operational view of the default dynamic permission filter. */
|
|
@GetMapping("/vpd-filter-runtime")
|
|
public String filterRuntime(Model model) {
|
|
try {
|
|
List<VpdPolicyView> policies = vpdPolicyService.findPolicies().stream()
|
|
.filter(VpdPolicyView::permissionSystemDefault)
|
|
.toList();
|
|
model.addAttribute("policies", policies);
|
|
if (!policies.isEmpty()) {
|
|
VpdPolicyView filter = policies.get(0);
|
|
model.addAttribute("source", vpdPolicyService.findFunctionSource(
|
|
filter.functionOwner(), filter.packageName(), filter.functionName()));
|
|
}
|
|
} catch (DataAccessException exception) {
|
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
|
model.addAttribute("runtimeError", message);
|
|
model.addAttribute("policies", List.of());
|
|
} catch (AppException exception) {
|
|
model.addAttribute("errorMessage", exception.getMessage());
|
|
model.addAttribute("policies", List.of());
|
|
}
|
|
return "vpd-filter-runtime";
|
|
}
|
|
|
|
private void populatePolicyModel(String schemaOwner, boolean includeFilterEditor, Model model) {
|
|
try {
|
|
long started = System.nanoTime();
|
|
String selectedSchemaOwner = schemaOwner == null ? "" : schemaOwner.trim().toUpperCase();
|
|
List<com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView> policies = vpdPolicyService.findPolicies();
|
|
long policiesAt = System.nanoTime();
|
|
Map<String, String> policyDescriptions = new LinkedHashMap<>();
|
|
if (!includeFilterEditor) {
|
|
policyDescriptions.putAll(vpdPolicyService.findPolicyDescriptionMap());
|
|
policies.forEach(policy -> policyDescriptions.put(
|
|
policy.objectDisplayName() + "|" + policy.policyName(),
|
|
policyDescriptions.getOrDefault(
|
|
policy.objectDisplayName() + "|" + policy.policyName(),
|
|
policy.objectDisplayName() + "에 요청마다 현재 권한체계의 행 접근 조건을 적용하는 "
|
|
+ policy.policyName() + " policy입니다."
|
|
)
|
|
));
|
|
}
|
|
long policyDescriptionsAt = System.nanoTime();
|
|
model.addAttribute("policies", policies);
|
|
model.addAttribute("policyDescriptions", policyDescriptions);
|
|
model.addAttribute("vpdTargets", includeFilterEditor
|
|
? List.of()
|
|
: vpdPolicyService.findVpdTargets(selectedSchemaOwner));
|
|
long targetsAt = System.nanoTime();
|
|
model.addAttribute("selectedSchemaOwner", selectedSchemaOwner);
|
|
var formOptions = vpdPolicyService.formOptions();
|
|
long formOptionsAt = System.nanoTime();
|
|
Map<String, String> filterDescriptions = new LinkedHashMap<>();
|
|
filterDescriptions.putAll(vpdPolicyService.findFilterDescriptionMap());
|
|
policies.forEach(policy -> filterDescriptions.putIfAbsent(
|
|
policy.functionOwner() + "|" + policy.functionName(),
|
|
defaultFilterDescription(policy.functionName())
|
|
));
|
|
if (includeFilterEditor) {
|
|
formOptions.functions().forEach(function -> filterDescriptions.putIfAbsent(
|
|
function.owner() + "|" + function.functionName(),
|
|
defaultFilterDescription(function.functionName())
|
|
));
|
|
}
|
|
long filterDescriptionsAt = System.nanoTime();
|
|
Map<String, String> filterPredicates = new LinkedHashMap<>();
|
|
if (includeFilterEditor) {
|
|
formOptions.functions().forEach(function -> filterPredicates.put(
|
|
function.owner() + "|" + function.functionName(),
|
|
vpdPolicyService.findFilterPredicate(function.owner(), function.packageName(), function.functionName())
|
|
));
|
|
}
|
|
long predicatesAt = System.nanoTime();
|
|
model.addAttribute("formOptions", formOptions);
|
|
model.addAttribute("filterDescriptions", filterDescriptions);
|
|
model.addAttribute("filterPredicates", filterPredicates);
|
|
log.info("vpd page timings: editor={} policies={}ms policyDescriptions={}ms targets={}ms formOptions={}ms filterDescriptions={}ms predicates={}ms total={}ms",
|
|
includeFilterEditor,
|
|
elapsedMillis(started, policiesAt),
|
|
elapsedMillis(policiesAt, policyDescriptionsAt),
|
|
elapsedMillis(policyDescriptionsAt, targetsAt),
|
|
elapsedMillis(targetsAt, formOptionsAt),
|
|
elapsedMillis(formOptionsAt, filterDescriptionsAt),
|
|
elapsedMillis(filterDescriptionsAt, predicatesAt),
|
|
elapsedMillis(started, predicatesAt));
|
|
} catch (DataAccessException exception) {
|
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
|
model.addAttribute("runtimeError", message);
|
|
model.addAttribute("policies", List.of());
|
|
model.addAttribute("vpdTargets", List.of());
|
|
model.addAttribute("selectedSchemaOwner", "");
|
|
model.addAttribute("formOptions", vpdPolicyService.emptyFormOptions());
|
|
model.addAttribute("policyDescriptions", Map.of());
|
|
model.addAttribute("filterDescriptions", Map.of());
|
|
model.addAttribute("filterPredicates", Map.of());
|
|
} catch (AppException exception) {
|
|
model.addAttribute("errorMessage", exception.getMessage());
|
|
model.addAttribute("policies", List.of());
|
|
model.addAttribute("vpdTargets", List.of());
|
|
model.addAttribute("selectedSchemaOwner", "");
|
|
model.addAttribute("formOptions", vpdPolicyService.emptyFormOptions());
|
|
model.addAttribute("policyDescriptions", Map.of());
|
|
model.addAttribute("filterDescriptions", Map.of());
|
|
model.addAttribute("filterPredicates", Map.of());
|
|
}
|
|
}
|
|
|
|
private static String defaultFilterDescription(String functionName) {
|
|
return "CB_AGENT_DOC_VPD_FILTER".equalsIgnoreCase(functionName)
|
|
? "사용자·그룹·역할·TAG 권한을 동적으로 합쳐 VPD predicate를 반환합니다."
|
|
: "이 Filter function이 반환하는 predicate로 조회 행을 제한합니다.";
|
|
}
|
|
|
|
private static long elapsedMillis(long started, long finished) {
|
|
return (finished - started) / 1_000_000;
|
|
}
|
|
|
|
@PostMapping("/vpd-policies")
|
|
public String createPolicy(
|
|
@RequestParam String objectKey,
|
|
@RequestParam String policyName,
|
|
@RequestParam(required = false) String functionKey,
|
|
@RequestParam(required = false) String functionOwner,
|
|
@RequestParam(required = false) String functionName,
|
|
@RequestParam(defaultValue = "SELECT") List<String> statementTypes,
|
|
@RequestParam(defaultValue = "false") boolean enabled,
|
|
@RequestParam(defaultValue = "false") boolean updateCheck,
|
|
@RequestParam(required = false) String filterPredicate,
|
|
RedirectAttributes redirectAttributes
|
|
) {
|
|
createPolicyInternal(objectKey, policyName, functionKey, functionOwner, functionName, statementTypes, enabled,
|
|
updateCheck, filterPredicate, redirectAttributes);
|
|
return "redirect:/vpd-policies";
|
|
}
|
|
|
|
@PostMapping("/vpd-policies/default")
|
|
public String createDefaultPolicy(
|
|
@RequestParam String objectKey,
|
|
RedirectAttributes redirectAttributes
|
|
) {
|
|
try {
|
|
vpdPolicyService.createDefaultPermissionPolicy(objectKey);
|
|
redirectAttributes.addFlashAttribute(
|
|
"successMessage",
|
|
"권한체계 자동 필터를 연결했습니다: " + objectKey
|
|
);
|
|
} catch (AppException exception) {
|
|
redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
|
|
} catch (DataAccessException exception) {
|
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
|
redirectAttributes.addFlashAttribute("errorMessage", message.message());
|
|
}
|
|
return "redirect:/vpd-policies";
|
|
}
|
|
|
|
@PostMapping("/vpd-filter-policies/filters")
|
|
public String saveFilter(
|
|
@RequestParam(required = false) String functionOwner,
|
|
@RequestParam String functionName,
|
|
@RequestParam String filterPredicate,
|
|
@RequestParam(defaultValue = "Filter function이 반환하는 predicate로 조회 행을 제한합니다.") String description,
|
|
RedirectAttributes redirectAttributes
|
|
) {
|
|
try {
|
|
vpdPolicyService.saveFilterFunction(functionOwner, functionName, filterPredicate);
|
|
vpdPolicyService.saveFilterDescription(
|
|
functionOwner == null || functionOwner.isBlank()
|
|
? vpdPolicyService.currentUser()
|
|
: functionOwner,
|
|
functionName,
|
|
description
|
|
);
|
|
redirectAttributes.addFlashAttribute("successMessage", "Filter function을 저장했습니다: " + functionName);
|
|
} catch (AppException exception) {
|
|
redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
|
|
} catch (DataAccessException exception) {
|
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
|
redirectAttributes.addFlashAttribute("errorMessage", message.message());
|
|
}
|
|
return "redirect:/vpd-filter-policies";
|
|
}
|
|
|
|
@PostMapping("/vpd-policies/description")
|
|
public String savePolicyDescription(
|
|
@RequestParam String objectOwner,
|
|
@RequestParam String objectName,
|
|
@RequestParam String policyName,
|
|
@RequestParam String description,
|
|
RedirectAttributes redirectAttributes
|
|
) {
|
|
try {
|
|
vpdPolicyService.savePolicyDescription(objectOwner, objectName, policyName, description);
|
|
redirectAttributes.addFlashAttribute("successMessage", "Policy 설명을 저장했습니다.");
|
|
} catch (AppException exception) {
|
|
redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
|
|
}
|
|
return "redirect:/vpd-policies";
|
|
}
|
|
|
|
@PostMapping("/vpd-filter-policies/replace")
|
|
public String replaceFilterPolicy(
|
|
@RequestParam String oldObjectKey,
|
|
@RequestParam String oldPolicyName,
|
|
@RequestParam String objectKey,
|
|
@RequestParam String policyName,
|
|
@RequestParam(required = false) String functionKey,
|
|
@RequestParam(required = false) String functionOwner,
|
|
@RequestParam(required = false) String functionName,
|
|
@RequestParam(defaultValue = "SELECT") List<String> statementTypes,
|
|
@RequestParam(defaultValue = "false") boolean enabled,
|
|
@RequestParam(defaultValue = "false") boolean updateCheck,
|
|
@RequestParam(required = false) String filterPredicate,
|
|
RedirectAttributes redirectAttributes
|
|
) {
|
|
try {
|
|
String[] objectParts = objectKey.split("\\.", 2);
|
|
if (objectParts.length != 2) {
|
|
throw new AppException("조회 대상 형식이 올바르지 않습니다: " + objectKey);
|
|
}
|
|
vpdPolicyService.replacePolicy(oldObjectKey, oldPolicyName, new VpdPolicyCreateCommand(
|
|
objectParts[0],
|
|
objectParts[1],
|
|
policyName,
|
|
functionKey,
|
|
functionOwner,
|
|
functionName,
|
|
String.join(",", statementTypes),
|
|
enabled,
|
|
updateCheck,
|
|
filterPredicate
|
|
));
|
|
redirectAttributes.addFlashAttribute("successMessage", "Policy를 수정했습니다: " + policyName);
|
|
} catch (AppException exception) {
|
|
redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
|
|
} catch (DataAccessException exception) {
|
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
|
redirectAttributes.addFlashAttribute("errorMessage", message.message());
|
|
}
|
|
return "redirect:/vpd-filter-policies";
|
|
}
|
|
|
|
private void createPolicyInternal(
|
|
String objectKey,
|
|
String policyName,
|
|
String functionKey,
|
|
String functionOwner,
|
|
String functionName,
|
|
List<String> statementTypes,
|
|
boolean enabled,
|
|
boolean updateCheck,
|
|
String filterPredicate,
|
|
RedirectAttributes redirectAttributes
|
|
) {
|
|
try {
|
|
String[] objectParts = objectKey.split("\\.", 2);
|
|
if (objectParts.length != 2) {
|
|
throw new AppException("조회 대상 형식이 올바르지 않습니다: " + objectKey);
|
|
}
|
|
vpdPolicyService.createPolicy(new VpdPolicyCreateCommand(
|
|
objectParts[0],
|
|
objectParts[1],
|
|
policyName,
|
|
functionKey,
|
|
functionOwner,
|
|
functionName,
|
|
String.join(",", statementTypes),
|
|
enabled,
|
|
updateCheck,
|
|
filterPredicate
|
|
));
|
|
redirectAttributes.addFlashAttribute("successMessage", "VPD policy를 등록했습니다: " + policyName);
|
|
} catch (AppException exception) {
|
|
redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
|
|
} catch (DataAccessException exception) {
|
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
|
redirectAttributes.addFlashAttribute("errorMessage", message.message());
|
|
}
|
|
}
|
|
|
|
@PostMapping("/vpd-policies/bulk")
|
|
public String bulkApplyPolicy(
|
|
@RequestParam String schemaOwner,
|
|
@RequestParam(defaultValue = "false") boolean includeTables,
|
|
@RequestParam(defaultValue = "false") boolean includeViews,
|
|
@RequestParam(required = false) String policyName,
|
|
@RequestParam(required = false) String functionKey,
|
|
@RequestParam(required = false) String functionOwner,
|
|
@RequestParam(required = false) String functionName,
|
|
@RequestParam(defaultValue = "SELECT") List<String> statementTypes,
|
|
@RequestParam(defaultValue = "false") boolean enabled,
|
|
@RequestParam(defaultValue = "false") boolean updateCheck,
|
|
@RequestParam(required = false) String filterPredicate,
|
|
RedirectAttributes redirectAttributes
|
|
) {
|
|
bulkApplyPolicyInternal(schemaOwner, includeTables, includeViews, policyName, functionKey, functionOwner, functionName,
|
|
statementTypes, enabled, updateCheck, filterPredicate, redirectAttributes);
|
|
return "redirect:/vpd-policies";
|
|
}
|
|
|
|
private void bulkApplyPolicyInternal(
|
|
String schemaOwner,
|
|
boolean includeTables,
|
|
boolean includeViews,
|
|
String policyName,
|
|
String functionKey,
|
|
String functionOwner,
|
|
String functionName,
|
|
List<String> statementTypes,
|
|
boolean enabled,
|
|
boolean updateCheck,
|
|
String filterPredicate,
|
|
RedirectAttributes redirectAttributes
|
|
) {
|
|
try {
|
|
var result = vpdPolicyService.bulkApplySchema(
|
|
schemaOwner,
|
|
includeTables,
|
|
includeViews,
|
|
policyName,
|
|
functionKey,
|
|
functionOwner,
|
|
functionName,
|
|
String.join(",", statementTypes),
|
|
enabled,
|
|
updateCheck,
|
|
filterPredicate
|
|
);
|
|
redirectAttributes.addFlashAttribute("successMessage", result.summary());
|
|
} catch (AppException exception) {
|
|
redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
|
|
} catch (DataAccessException exception) {
|
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
|
redirectAttributes.addFlashAttribute("errorMessage", message.message());
|
|
}
|
|
}
|
|
|
|
@GetMapping("/vpd-policies/function-source")
|
|
public String functionSource(
|
|
@RequestParam String owner,
|
|
@RequestParam(required = false) String packageName,
|
|
@RequestParam String functionName,
|
|
Model model
|
|
) {
|
|
try {
|
|
model.addAttribute("source", vpdPolicyService.findFunctionSource(owner, packageName, functionName));
|
|
} catch (AppException exception) {
|
|
model.addAttribute("errorMessage", exception.getMessage());
|
|
} catch (DataAccessException exception) {
|
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
|
model.addAttribute("errorMessage", message.message());
|
|
}
|
|
return "fragments/vpd-function-source :: source";
|
|
}
|
|
|
|
@GetMapping("/vpd-policies/policy-detail")
|
|
public String policyDetail(
|
|
@RequestParam String objectOwner,
|
|
@RequestParam String objectName,
|
|
@RequestParam String policyName,
|
|
Model model
|
|
) {
|
|
try {
|
|
model.addAttribute("detail", vpdPolicyService.findPolicyDetail(objectOwner, objectName, policyName));
|
|
} catch (AppException exception) {
|
|
model.addAttribute("errorMessage", exception.getMessage());
|
|
} catch (DataAccessException exception) {
|
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
|
model.addAttribute("errorMessage", message.message());
|
|
}
|
|
return "fragments/vpd-policy-detail :: detail";
|
|
}
|
|
|
|
@GetMapping("/vpd-policies/object-filter-detail")
|
|
public String objectFilterDetail(
|
|
@RequestParam String objectOwner,
|
|
@RequestParam String objectName,
|
|
Model model
|
|
) {
|
|
try {
|
|
model.addAttribute("detail", vpdPolicyService.findObjectFilterDetail(objectOwner, objectName));
|
|
} catch (AppException exception) {
|
|
model.addAttribute("errorMessage", exception.getMessage());
|
|
} catch (DataAccessException exception) {
|
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
|
model.addAttribute("errorMessage", message.message());
|
|
}
|
|
return "fragments/vpd-object-filter-detail :: detail";
|
|
}
|
|
|
|
@GetMapping("/vpd-policies/policy-explanation")
|
|
public String policyExplanation(
|
|
@RequestParam String objectOwner,
|
|
@RequestParam String objectName,
|
|
@RequestParam String policyName,
|
|
Model model
|
|
) {
|
|
try {
|
|
model.addAttribute("explanation", vpdPolicyService.explainPolicy(objectOwner, objectName, policyName));
|
|
} catch (AppException exception) {
|
|
model.addAttribute("errorMessage", exception.getMessage());
|
|
} catch (DataAccessException exception) {
|
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
|
model.addAttribute("errorMessage", message.message());
|
|
}
|
|
return "fragments/vpd-policy-explanation :: explanation";
|
|
}
|
|
}
|