From d30a82d04f6582c31eb846ada71744292cb70c10 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Wed, 24 Jul 2019 11:30:19 -0700 Subject: [PATCH] Add additional constraint to cf resource schema Details: username/password and client_id/client_secret should not be used together. We now check for this and show a warning in the concourse editor. --- .../yaml/schema/constraints/Constraints.java | 33 +++++++++++++++++++ .../vscode/concourse/PipelineYmlSchema.java | 5 +++ .../vscode/concourse/ConcourseEditorTest.java | 21 ++++++++++++ 3 files changed, 59 insertions(+) diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraints.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraints.java index 2b070069b..3800f8665 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraints.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraints.java @@ -37,6 +37,7 @@ import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.NodeTuple; import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; @@ -61,6 +62,14 @@ public class Constraints { }; } + public static Constraint and(Constraint... constraints) { + return (DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems) -> { + for (Constraint c : constraints) { + c.verify(dc, parent, node, type, problems); + } + }; + } + public static Constraint implies(String foundProperty, String requiredProperty) { return (DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems) -> { @@ -176,6 +185,30 @@ public class Constraints { }; } + public static Constraint mutuallyExclusive(List group1, List group2) { + return (DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems) -> { + if (node instanceof MappingNode) { + MappingNode map = (MappingNode) node; + Set defined = dc.getDefinedProperties(); + if (containsAny(defined, group1) && containsAny(defined, group2)) { + for (NodeTuple tup : map.getValue()) { + Node keyNode = tup.getKeyNode(); + String key = NodeUtil.asScalar(keyNode); + if (group1.contains(key) || group2.contains(key)) { + problems.accept(problem(EXTRA_PROPERTY, + "Properties "+group1+" should not be used together with "+group2+" for '"+type+"'", keyNode + )); + } + } + } + } + }; + } + + private static boolean containsAny(Set set, Collection lookFor) { + return lookFor.stream().filter(set::contains).findAny().isPresent(); + } + public static Constraint mutuallyExclusive(String p1, String p2) { return (DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems) -> { if (node instanceof MappingNode) { diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java index 89edfb4fc..e1647dd40 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java @@ -58,6 +58,7 @@ import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; /** * @author Kris De Volder @@ -736,6 +737,10 @@ public class PipelineYmlSchema implements YamlSchema { source.require(Constraints.together("username", "password")); source.require(Constraints.together("client_id", "client_secret")); source.require(Constraints.requireAtLeastOneOf("username", "password", "client_id", "client_secret")); + source.require(Constraints.mutuallyExclusive( + ImmutableList.of("username", "password"), + ImmutableList.of("client_id", "client_secret") + )); AbstractType get = f.ybean("CloudFoundryGetParams"); //get params deliberately left empty diff --git a/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java b/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java index 7d6e3af59..06ca50b37 100644 --- a/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java +++ b/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java @@ -4532,6 +4532,27 @@ public class ConcourseEditorTest { "not-bool-1|boolean", "not-bool-2|boolean" ); + + editor = harness.newEditor( + "resources:\n" + + "- name: pws\n" + + " type: cf\n" + + " source:\n" + + " api: https://api.run.pivotal.io\n" + + " organization: my-org\n" + + " space: my-space\n" + + " username: ((secret))\n" + + " password: ((secret))\n" + + " client_id: ((id)\n" + + " client_secret: ((secret)\n" + ); + editor.assertProblems( + "pws|Unused", + "username|Properties [username, password] should not be used together with [client_id, client_secret]", + "password|Properties [username, password] should not be used together with [client_id, client_secret]", + "client_id|Properties [username, password] should not be used together with [client_id, client_secret]", + "client_secret|Properties [username, password] should not be used together with [client_id, client_secret]" + ); } @Test public void cfResourceSourceHovers() throws Exception {