From 18f2ab2fa0114753eb4e65c7875c5e4021ec6bb4 Mon Sep 17 00:00:00 2001 From: Lucio Lelii Date: Thu, 10 Sep 2026 11:35:19 +0200 Subject: [PATCH] Let a secure retriever answer the questions the editor asks it The editor asks every retriever-backed field whether its list is open, and whether the field is required, on endpoints suffixed onto the field's own URL. Only the unsecured retriever had them, so opening an MCP agent's shared session picker asked /secure-retriever/.../open and got a NoResourceFoundException stack in the log. The editor swallowed the failure and fell back to a closed list, which is why nothing looked wrong. Both questions now exist on the secure side too, defaulting the same way, so the answer comes from the retriever rather than from a 404. Co-Authored-By: Claude Opus 5 (1M context) --- .../SecureDynamicFieldRetriever.java | 22 +++++++++++++++++++ .../SecureRetrieverController.java | 22 +++++++++++++++++++ .../SecureRetrieverControllerTest.java | 12 ++++++++++ 3 files changed, 56 insertions(+) diff --git a/src/main/java/it/cnr/isti/workflow/manager/configurations/retrievers/SecureDynamicFieldRetriever.java b/src/main/java/it/cnr/isti/workflow/manager/configurations/retrievers/SecureDynamicFieldRetriever.java index b8e3e2f..074c3fa 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/configurations/retrievers/SecureDynamicFieldRetriever.java +++ b/src/main/java/it/cnr/isti/workflow/manager/configurations/retrievers/SecureDynamicFieldRetriever.java @@ -12,6 +12,28 @@ public interface SecureDynamicFieldRetriever { List retrieve(String parameter, Map params, LoginEntity user); + /** + * Whether the target field must be filled given the current parameters. + * + *

Answered here as well as on {@link DynamicFieldRetriever} because the editor asks the + * question of whichever retriever backs the field, and cannot know which of the two families it + * landed on. + */ + default boolean isRequired(String parameter, Map params, LoginEntity user) { + return false; + } + + /** + * Whether {@link #retrieve} returns a closed list or an incomplete one. + * + *

True tells the editor to accept a typed value instead of only what was listed. Closed by + * default, for the same reason as the unsecured retriever: a retriever that can enumerate its + * own values should keep the user inside them. + */ + default boolean isOpen(String parameter, Map params, LoginEntity user) { + return false; + } + record RetrieverItemDescriptor(String label, String description, Map meta) { } diff --git a/src/main/java/it/cnr/isti/workflow/manager/controllers/SecureRetrieverController.java b/src/main/java/it/cnr/isti/workflow/manager/controllers/SecureRetrieverController.java index f67bab9..9b5a101 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/controllers/SecureRetrieverController.java +++ b/src/main/java/it/cnr/isti/workflow/manager/controllers/SecureRetrieverController.java @@ -55,4 +55,26 @@ public class SecureRetrieverController { @AuthenticationPrincipal LoginEntity userDetails) { return resolveRetriever(category).retrieve(parameter, params, userDetails); } + + @GetMapping("/{category}/{parameter}/required") + @Operation(summary = "Check secure retriever required", + description = "Returns true if the target field should be required given the current parameters.") + public boolean isRequired( + @PathVariable String category, + @PathVariable String parameter, + @RequestParam Map params, + @AuthenticationPrincipal LoginEntity userDetails) { + return resolveRetriever(category).isRequired(parameter, params, userDetails); + } + + @GetMapping("/{category}/{parameter}/open") + @Operation(summary = "Check secure retriever open", + description = "Returns true if the values are an incomplete list and the target field accepts a typed value.") + public boolean isOpen( + @PathVariable String category, + @PathVariable String parameter, + @RequestParam Map params, + @AuthenticationPrincipal LoginEntity userDetails) { + return resolveRetriever(category).isOpen(parameter, params, userDetails); + } } diff --git a/src/test/java/it/cnr/isti/workflow/manager/controllers/SecureRetrieverControllerTest.java b/src/test/java/it/cnr/isti/workflow/manager/controllers/SecureRetrieverControllerTest.java index 36e8f25..5a89930 100644 --- a/src/test/java/it/cnr/isti/workflow/manager/controllers/SecureRetrieverControllerTest.java +++ b/src/test/java/it/cnr/isti/workflow/manager/controllers/SecureRetrieverControllerTest.java @@ -39,6 +39,18 @@ public class SecureRetrieverControllerTest { @Autowired private FlowRepository flowRepository; + @Test + public void answersTheOpenAndRequiredQuestionsEveryRetrieverBackedFieldAsks() { + // The editor asks these of whichever retriever backs a field. A secure one used to have no + // endpoint to answer on, so opening an MCP agent's shared session picker logged a 404 stack. + LoginEntity user = new LoginEntity("testuser", "ignored"); + + assertFalse(secureRetrieverController.isOpen("ExecutionVariables", "shared", + Map.of("kind", "MCP_SESSION"), user)); + assertFalse(secureRetrieverController.isRequired("ExecutionVariables", "shared", + Map.of("kind", "MCP_SESSION"), user)); + } + @Test public void flowSubFlowRetrieverReturnsOnlyValidFlowsWhenRequested() { LoginEntity user = new LoginEntity("testuser", "ignored");