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");