fix #557: guide permission-driven VPD flow
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
package com.cloudhandson.vpdbackoffice.web;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.service.McpChatbotService;
|
||||
import com.cloudhandson.vpdbackoffice.service.BearerTokenService;
|
||||
import com.cloudhandson.vpdbackoffice.mapper.UserMapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -13,13 +17,27 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
public class McpChatbotController {
|
||||
|
||||
private final McpChatbotService chatbotService;
|
||||
private final BearerTokenService tokenService;
|
||||
private final UserMapper userMapper;
|
||||
|
||||
public McpChatbotController(McpChatbotService chatbotService) {
|
||||
public McpChatbotController(
|
||||
McpChatbotService chatbotService,
|
||||
BearerTokenService tokenService,
|
||||
UserMapper userMapper
|
||||
) {
|
||||
this.chatbotService = chatbotService;
|
||||
this.tokenService = tokenService;
|
||||
this.userMapper = userMapper;
|
||||
}
|
||||
|
||||
@GetMapping("/mcp-chatbot")
|
||||
public String page() {
|
||||
public String page(Model model) {
|
||||
try {
|
||||
model.addAttribute("users", userMapper.findAll());
|
||||
} catch (DataAccessException exception) {
|
||||
model.addAttribute("users", List.of());
|
||||
model.addAttribute("runtimeError", RuntimeErrorMessages.dataAccess(exception));
|
||||
}
|
||||
return "mcp-chatbot";
|
||||
}
|
||||
|
||||
@@ -28,6 +46,7 @@ public class McpChatbotController {
|
||||
@RequestParam(defaultValue = "vpd-live") String contextPath,
|
||||
@RequestParam(defaultValue = "") String question,
|
||||
@RequestParam(defaultValue = "") String bearerToken,
|
||||
@RequestParam(required = false) Long tempUserId,
|
||||
@RequestParam(defaultValue = "50") int limit,
|
||||
HttpServletRequest request,
|
||||
Model model
|
||||
@@ -38,7 +57,20 @@ public class McpChatbotController {
|
||||
.replaceQuery(null)
|
||||
.build()
|
||||
.toUriString();
|
||||
model.addAttribute("result", chatbotService.chat(serverOrigin, contextPath, question, bearerToken, limit));
|
||||
String effectiveToken = bearerToken;
|
||||
Long temporaryKeyId = null;
|
||||
if (tempUserId != null) {
|
||||
var issued = tokenService.issueTemporaryToken(tempUserId, "MCP Chatbot 임시 실행");
|
||||
effectiveToken = issued.plainToken();
|
||||
temporaryKeyId = issued.keyId();
|
||||
}
|
||||
try {
|
||||
model.addAttribute("result", chatbotService.chat(serverOrigin, contextPath, question, effectiveToken, limit));
|
||||
} finally {
|
||||
if (temporaryKeyId != null) {
|
||||
tokenService.revokeToken(temporaryKeyId, "temporary chatbot completed");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
model.addAttribute("errorMessage", e.getMessage());
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ 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.support.ServletUriComponentsBuilder;
|
||||
|
||||
@Controller
|
||||
@@ -31,10 +30,6 @@ public class McpClientDemoController {
|
||||
|
||||
@PostMapping("/mcp-client-demo")
|
||||
public String run(
|
||||
@RequestParam(defaultValue = "vpd-live") String contextPath,
|
||||
@RequestParam(defaultValue = "") String toolName,
|
||||
@RequestParam(defaultValue = "") String bearerToken,
|
||||
@RequestParam(defaultValue = "50") int limit,
|
||||
HttpServletRequest request,
|
||||
Model model
|
||||
) {
|
||||
@@ -45,7 +40,7 @@ public class McpClientDemoController {
|
||||
.replaceQuery(null)
|
||||
.build()
|
||||
.toUriString();
|
||||
model.addAttribute("result", demoService.run(serverOrigin, contextPath, toolName, bearerToken, limit));
|
||||
model.addAttribute("result", demoService.run(serverOrigin, "default", "", "", 50));
|
||||
} catch (Exception e) {
|
||||
model.addAttribute("errorMessage", e.getMessage());
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.cloudhandson.vpdbackoffice.web;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.domain.mcp.McpReasoningCommand;
|
||||
import com.cloudhandson.vpdbackoffice.mapper.UserMapper;
|
||||
import com.cloudhandson.vpdbackoffice.service.BearerTokenService;
|
||||
import com.cloudhandson.vpdbackoffice.service.McpReasoningService;
|
||||
import com.cloudhandson.vpdbackoffice.service.McpToolRegistry;
|
||||
import com.cloudhandson.vpdbackoffice.service.ProtectedObjectService;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -21,32 +21,30 @@ public class McpReasoningController {
|
||||
|
||||
private final McpToolRegistry toolRegistry;
|
||||
private final McpReasoningService reasoningService;
|
||||
private final ProtectedObjectService protectedObjectService;
|
||||
private final BearerTokenService tokenService;
|
||||
private final UserMapper userMapper;
|
||||
|
||||
public McpReasoningController(
|
||||
McpToolRegistry toolRegistry,
|
||||
McpReasoningService reasoningService,
|
||||
ProtectedObjectService protectedObjectService,
|
||||
BearerTokenService tokenService
|
||||
BearerTokenService tokenService,
|
||||
UserMapper userMapper
|
||||
) {
|
||||
this.toolRegistry = toolRegistry;
|
||||
this.reasoningService = reasoningService;
|
||||
this.protectedObjectService = protectedObjectService;
|
||||
this.tokenService = tokenService;
|
||||
this.userMapper = userMapper;
|
||||
}
|
||||
|
||||
@GetMapping("/mcp-reasoning")
|
||||
public String page(Model model) {
|
||||
try {
|
||||
model.addAttribute("objects", protectedObjectService.findEnabled());
|
||||
model.addAttribute("tools", toolRegistry.listTools());
|
||||
model.addAttribute("tokens", tokenService.findTokenContextOptions());
|
||||
model.addAttribute("users", userMapper.findAll());
|
||||
} catch (DataAccessException e) {
|
||||
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(e);
|
||||
model.addAttribute("objects", List.of());
|
||||
model.addAttribute("tools", List.of());
|
||||
model.addAttribute("tokens", List.of());
|
||||
model.addAttribute("users", List.of());
|
||||
model.addAttribute("runtimeError", message);
|
||||
}
|
||||
return "mcp-reasoning";
|
||||
@@ -82,15 +80,29 @@ public class McpReasoningController {
|
||||
|
||||
@PostMapping("/mcp-reasoning")
|
||||
public String reason(
|
||||
@RequestParam(required = false) Long tokenKeyId,
|
||||
@RequestParam long objectId,
|
||||
@RequestParam String bearerToken,
|
||||
@RequestParam(defaultValue = "") String bearerToken,
|
||||
@RequestParam(required = false) Long tempUserId,
|
||||
@RequestParam(defaultValue = "50") int limit,
|
||||
@RequestParam(defaultValue = "") String question,
|
||||
Model model
|
||||
) {
|
||||
model.addAttribute("result", reasoningService.reason(
|
||||
new McpReasoningCommand(tokenKeyId, objectId, bearerToken, limit, question)));
|
||||
String effectiveToken = bearerToken;
|
||||
Long temporaryKeyId = null;
|
||||
try {
|
||||
if (tempUserId != null) {
|
||||
var issued = tokenService.issueTemporaryToken(tempUserId, "MCP Reasoning 임시 실행");
|
||||
effectiveToken = issued.plainToken();
|
||||
temporaryKeyId = issued.keyId();
|
||||
}
|
||||
model.addAttribute("result", reasoningService.reason(
|
||||
new McpReasoningCommand(temporaryKeyId, effectiveToken, limit, question)));
|
||||
} catch (Exception exception) {
|
||||
model.addAttribute("errorMessage", exception.getMessage());
|
||||
} finally {
|
||||
if (temporaryKeyId != null) {
|
||||
tokenService.revokeToken(temporaryKeyId, "temporary reasoning completed");
|
||||
}
|
||||
}
|
||||
return "fragments/mcp-reasoning-result :: result";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.cloudhandson.vpdbackoffice.service.BearerTokenService;
|
||||
import com.cloudhandson.vpdbackoffice.service.OrdsProbeService;
|
||||
import com.cloudhandson.vpdbackoffice.service.ProtectedObjectService;
|
||||
import com.cloudhandson.vpdbackoffice.service.VpdPolicyService;
|
||||
import com.cloudhandson.vpdbackoffice.mapper.UserMapper;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@@ -23,17 +24,20 @@ public class ProbeController {
|
||||
private final ProtectedObjectService protectedObjectService;
|
||||
private final BearerTokenService tokenService;
|
||||
private final VpdPolicyService vpdPolicyService;
|
||||
private final UserMapper userMapper;
|
||||
|
||||
public ProbeController(
|
||||
OrdsProbeService probeService,
|
||||
ProtectedObjectService protectedObjectService,
|
||||
BearerTokenService tokenService,
|
||||
VpdPolicyService vpdPolicyService
|
||||
VpdPolicyService vpdPolicyService,
|
||||
UserMapper userMapper
|
||||
) {
|
||||
this.probeService = probeService;
|
||||
this.protectedObjectService = protectedObjectService;
|
||||
this.tokenService = tokenService;
|
||||
this.vpdPolicyService = vpdPolicyService;
|
||||
this.userMapper = userMapper;
|
||||
}
|
||||
|
||||
@GetMapping("/probe")
|
||||
@@ -48,19 +52,35 @@ public class ProbeController {
|
||||
.toList();
|
||||
model.addAttribute("objects", objects);
|
||||
model.addAttribute("defaultObjectKeys", defaultObjectKeys);
|
||||
model.addAttribute("users", userMapper.findAll());
|
||||
return "probe";
|
||||
}
|
||||
|
||||
@PostMapping("/probe")
|
||||
public String run(
|
||||
@RequestParam long objectId,
|
||||
@RequestParam String bearerToken,
|
||||
@RequestParam(defaultValue = "") String bearerToken,
|
||||
@RequestParam(required = false) Long tempUserId,
|
||||
@RequestParam(defaultValue = "50") int limit,
|
||||
@RequestParam(required = false) String requestBody,
|
||||
Model model
|
||||
) {
|
||||
String normalizedToken = bearerToken == null ? "" : bearerToken.trim();
|
||||
model.addAttribute("result", probeService.runProbe(new ProbeCommand(null, objectId, normalizedToken, limit)));
|
||||
model.addAttribute("tokenContext", tokenService.findTokenContextByPlainToken(normalizedToken));
|
||||
Long temporaryKeyId = null;
|
||||
if (tempUserId != null) {
|
||||
var issued = tokenService.issueTemporaryToken(tempUserId, "ORDS 검증 임시 실행");
|
||||
normalizedToken = issued.plainToken();
|
||||
temporaryKeyId = issued.keyId();
|
||||
}
|
||||
try {
|
||||
model.addAttribute("result", probeService.runProbe(
|
||||
new ProbeCommand(temporaryKeyId, objectId, normalizedToken, limit, requestBody)));
|
||||
model.addAttribute("tokenContext", tokenService.findTokenContextByPlainToken(normalizedToken));
|
||||
} finally {
|
||||
if (temporaryKeyId != null) {
|
||||
tokenService.revokeToken(temporaryKeyId, "temporary probe completed");
|
||||
}
|
||||
}
|
||||
model.addAttribute("selectedObject", protectedObjectService.findEnabled().stream()
|
||||
.filter(object -> object.objectId() == objectId)
|
||||
.findFirst()
|
||||
|
||||
@@ -44,12 +44,13 @@ public class ProtectedObjectController {
|
||||
public String create(
|
||||
@RequestParam String owner,
|
||||
@RequestParam String objectName,
|
||||
@RequestParam String ordsPath,
|
||||
@RequestParam(required = false) String ordsPath,
|
||||
@RequestParam(required = false) String description,
|
||||
RedirectAttributes redirectAttributes
|
||||
) {
|
||||
try {
|
||||
protectedObjectService.createObject(
|
||||
new ProtectedObjectCreateCommand(owner, objectName, ordsPath, null, null));
|
||||
new ProtectedObjectCreateCommand(owner, objectName, ordsPath, null, null, description));
|
||||
redirectAttributes.addFlashAttribute("message", "ORDS 조회 Handler 대상을 추가했습니다.");
|
||||
} catch (AppException exception) {
|
||||
redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
|
||||
@@ -57,6 +58,21 @@ public class ProtectedObjectController {
|
||||
return "redirect:/objects";
|
||||
}
|
||||
|
||||
@PostMapping("/objects/description")
|
||||
public String updateDescription(
|
||||
@RequestParam long objectId,
|
||||
@RequestParam(required = false) String description,
|
||||
RedirectAttributes redirectAttributes
|
||||
) {
|
||||
try {
|
||||
protectedObjectService.updateDescription(objectId, description);
|
||||
redirectAttributes.addFlashAttribute("message", "조회 대상 설명을 저장했습니다.");
|
||||
} catch (AppException exception) {
|
||||
redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
|
||||
}
|
||||
return "redirect:/objects";
|
||||
}
|
||||
|
||||
@PostMapping("/objects/ords-path")
|
||||
public String updateOrdsPath(
|
||||
@RequestParam long objectId,
|
||||
@@ -81,7 +97,7 @@ public class ProtectedObjectController {
|
||||
) {
|
||||
try {
|
||||
protectedObjectService.updateColumnPolicy(columnId, sensitivityLevel, redactionMethod);
|
||||
redirectAttributes.addFlashAttribute("message", "컬럼 민감도/마스킹 정책을 수정했습니다.");
|
||||
redirectAttributes.addFlashAttribute("message", "컬럼 표시 보호 정책을 수정했습니다. 행 접근 권한은 변경되지 않습니다.");
|
||||
} catch (AppException exception) {
|
||||
redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
|
||||
}
|
||||
|
||||
@@ -30,8 +30,12 @@ public class TokenController {
|
||||
}
|
||||
|
||||
@GetMapping("/tokens")
|
||||
public String tokens(Model model) {
|
||||
model.addAttribute("tokens", tokenService.findAll());
|
||||
public String tokens(
|
||||
@RequestParam(defaultValue = "false") boolean includeInactive,
|
||||
Model model
|
||||
) {
|
||||
model.addAttribute("tokens", tokenService.findAll(includeInactive));
|
||||
model.addAttribute("includeInactive", includeInactive);
|
||||
model.addAttribute("users", userMapper.findAll());
|
||||
model.addAttribute("defaultExpiresAt", defaultExpiresAt());
|
||||
return "tokens";
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyCreateCommand;
|
||||
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.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
@@ -42,10 +44,34 @@ public class VpdPolicyController {
|
||||
private void populatePolicyModel(String schemaOwner, Model model) {
|
||||
try {
|
||||
String selectedSchemaOwner = schemaOwner == null ? "" : schemaOwner.trim().toUpperCase();
|
||||
model.addAttribute("policies", vpdPolicyService.findPolicies());
|
||||
List<com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView> policies = vpdPolicyService.findPolicies();
|
||||
Map<String, String> policyDescriptions = new LinkedHashMap<>();
|
||||
policies.forEach(policy -> policyDescriptions.put(
|
||||
policy.objectDisplayName() + "|" + policy.policyName(),
|
||||
vpdPolicyService.findPolicyDescription(policy.objectOwner(), policy.objectName(), policy.policyName())
|
||||
));
|
||||
model.addAttribute("policies", policies);
|
||||
model.addAttribute("policyDescriptions", policyDescriptions);
|
||||
model.addAttribute("vpdTargets", vpdPolicyService.findVpdTargets(selectedSchemaOwner));
|
||||
model.addAttribute("selectedSchemaOwner", selectedSchemaOwner);
|
||||
model.addAttribute("formOptions", vpdPolicyService.formOptions());
|
||||
var formOptions = vpdPolicyService.formOptions();
|
||||
Map<String, String> filterDescriptions = new LinkedHashMap<>();
|
||||
formOptions.functions().forEach(function -> filterDescriptions.put(
|
||||
function.owner() + "|" + function.functionName(),
|
||||
vpdPolicyService.findFilterDescription(function.owner(), function.functionName())
|
||||
));
|
||||
policies.forEach(policy -> filterDescriptions.putIfAbsent(
|
||||
policy.functionOwner() + "|" + policy.functionName(),
|
||||
vpdPolicyService.findFilterDescription(policy.functionOwner(), policy.functionName())
|
||||
));
|
||||
Map<String, String> filterPredicates = new LinkedHashMap<>();
|
||||
formOptions.functions().forEach(function -> filterPredicates.put(
|
||||
function.owner() + "|" + function.functionName(),
|
||||
vpdPolicyService.findFilterPredicate(function.owner(), function.packageName(), function.functionName())
|
||||
));
|
||||
model.addAttribute("formOptions", formOptions);
|
||||
model.addAttribute("filterDescriptions", filterDescriptions);
|
||||
model.addAttribute("filterPredicates", filterPredicates);
|
||||
} catch (DataAccessException exception) {
|
||||
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
||||
model.addAttribute("runtimeError", message);
|
||||
@@ -53,12 +79,18 @@ public class VpdPolicyController {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,10 +137,18 @@ public class VpdPolicyController {
|
||||
@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());
|
||||
@@ -119,6 +159,23 @@ public class VpdPolicyController {
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user