You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by lb...@apache.org on 2020/10/12 10:37:57 UTC
[camel] 01/02: CAMEL-15664: Automatically wrap secret properites
with RAW when computing the URI
This is an automated email from the ASF dual-hosted git repository.
lburgazzoli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 24a6e274347be6a8b0819b278e1e0321accc54be
Author: Luca Burgazzoli <lb...@gmail.com>
AuthorDate: Fri Oct 9 23:50:03 2020 +0200
CAMEL-15664: Automatically wrap secret properites with RAW when computing the URI
---
.../org/apache/camel/spi/EndpointUriFactory.java | 5 ++
.../camel/catalog/impl/AbstractCamelCatalog.java | 41 ++++++++++-
.../catalog/CustomEndpointUriFactoryTest.java | 80 ++++++++++++++++++++--
.../camel/catalog/RuntimeCamelCatalogTest.java | 32 +++++++++
.../component/EndpointUriFactorySupport.java | 10 +++
.../packaging/EndpointUriFactoryGenerator.java | 75 +++++++++++++++-----
6 files changed, 220 insertions(+), 23 deletions(-)
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/EndpointUriFactory.java b/core/camel-api/src/main/java/org/apache/camel/spi/EndpointUriFactory.java
index a5f2dbf..1eb4d99 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/EndpointUriFactory.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/EndpointUriFactory.java
@@ -50,6 +50,11 @@ public interface EndpointUriFactory extends CamelContextAware {
Set<String> propertyNames();
/**
+ * Returns the names of the secret properties this endpoin supports.
+ */
+ Set<String> secretPropertyNames();
+
+ /**
* Whether the endpoint is lenient or not.
*
* @see Endpoint#isLenientProperties()
diff --git a/core/camel-core-catalog/src/main/java/org/apache/camel/catalog/impl/AbstractCamelCatalog.java b/core/camel-core-catalog/src/main/java/org/apache/camel/catalog/impl/AbstractCamelCatalog.java
index c765f49..1a7571a 100644
--- a/core/camel-core-catalog/src/main/java/org/apache/camel/catalog/impl/AbstractCamelCatalog.java
+++ b/core/camel-core-catalog/src/main/java/org/apache/camel/catalog/impl/AbstractCamelCatalog.java
@@ -32,6 +32,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -799,6 +800,7 @@ public abstract class AbstractCamelCatalog {
// build at first according to syntax (use a tree map as we want the uri options sorted)
Map<String, String> copy = new TreeMap<>(properties);
+
Matcher syntaxMatcher = COMPONENT_SYNTAX_PARSER.matcher(originalSyntax);
while (syntaxMatcher.find()) {
syntax += syntaxMatcher.group(1);
@@ -821,6 +823,23 @@ public abstract class AbstractCamelCatalog {
sb.append(syntax);
if (!copy.isEmpty()) {
+ // wrap secret values with RAW to avoid breaking URI encoding in case of encoded values
+ copy.replaceAll((key, val) -> {
+ if (val == null) {
+ return val;
+ }
+ BaseOptionModel option = rows.get(key);
+ if (option == null) {
+ return val;
+ }
+
+ if (option.isSecret() && !val.startsWith("#") && !val.startsWith("RAW(")) {
+ return "RAW(" + val + ")";
+ }
+
+ return val;
+ });
+
boolean hasQuestionMark = sb.toString().contains("?");
// the last option may already contain a ? char, if so we should use & instead of ?
sb.append(hasQuestionMark ? ampersand : '?');
@@ -905,8 +924,28 @@ public abstract class AbstractCamelCatalog {
range++;
}
-
if (!copy.isEmpty()) {
+ // wrap secret values with RAW to avoid breaking URI encoding in case of encoded values
+ copy.replaceAll(new BiFunction<String, String, String>() {
+ @Override
+ public String apply(String key, String val) {
+
+ if (val == null) {
+ return val;
+ }
+ BaseOptionModel option = rows.get(key);
+ if (option == null) {
+ return val;
+ }
+
+ if (option.isSecret() && !val.startsWith("#") && !val.startsWith("RAW(")) {
+ return "RAW(" + val + ")";
+ }
+
+ return val;
+ }
+ });
+
// the last option may already contain a ? char, if so we should use & instead of ?
sb.append(hasQuestionmark ? ampersand : '?');
String query = URISupport.createQueryString(copy, ampersand, encode);
diff --git a/core/camel-core/src/test/java/org/apache/camel/catalog/CustomEndpointUriFactoryTest.java b/core/camel-core/src/test/java/org/apache/camel/catalog/CustomEndpointUriFactoryTest.java
index c9f37ee..a6eafaf 100644
--- a/core/camel-core/src/test/java/org/apache/camel/catalog/CustomEndpointUriFactoryTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/catalog/CustomEndpointUriFactoryTest.java
@@ -17,7 +17,10 @@
package org.apache.camel.catalog;
import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@@ -211,7 +214,22 @@ public class CustomEndpointUriFactoryTest extends ContextTestSupport {
Assertions.assertEquals("jms2:foo?deliveryPersistent=true", uri);
}
- private class MyAssembler extends EndpointUriFactorySupport implements EndpointUriFactory {
+ @Test
+ public void testJmsSecrets() throws Exception {
+ EndpointUriFactory assembler = new MyJmsxAssembler();
+ assembler.setCamelContext(context);
+
+ Map<String, Object> params = new LinkedHashMap<>();
+ params.put("destinationName", "foo");
+ params.put("deliveryPersistent", true);
+ params.put("username", "usr");
+ params.put("password", "pwd");
+
+ String uri = assembler.buildUri("jmsx", params);
+ Assertions.assertEquals("jmsx:foo?deliveryPersistent=true&password=RAW(pwd)&username=RAW(usr)", uri);
+ }
+
+ private static class MyAssembler extends EndpointUriFactorySupport implements EndpointUriFactory {
private static final String SYNTAX = "acme:name:port";
@@ -237,7 +255,12 @@ public class CustomEndpointUriFactoryTest extends ContextTestSupport {
@Override
public Set<String> propertyNames() {
- return null;
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<String> secretPropertyNames() {
+ return Collections.emptySet();
}
@Override
@@ -247,7 +270,7 @@ public class CustomEndpointUriFactoryTest extends ContextTestSupport {
}
- private class MySecondAssembler extends EndpointUriFactorySupport implements EndpointUriFactory {
+ private static class MySecondAssembler extends EndpointUriFactorySupport implements EndpointUriFactory {
private static final String SYNTAX = "acme2:name/path:port";
@@ -274,7 +297,12 @@ public class CustomEndpointUriFactoryTest extends ContextTestSupport {
@Override
public Set<String> propertyNames() {
- return null;
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<String> secretPropertyNames() {
+ return Collections.emptySet();
}
@Override
@@ -284,7 +312,7 @@ public class CustomEndpointUriFactoryTest extends ContextTestSupport {
}
- private class MyJmsAssembler extends EndpointUriFactorySupport implements EndpointUriFactory {
+ private static class MyJmsAssembler extends EndpointUriFactorySupport implements EndpointUriFactory {
private static final String SYNTAX = "jms2:destinationType:destinationName";
@@ -307,7 +335,47 @@ public class CustomEndpointUriFactoryTest extends ContextTestSupport {
@Override
public Set<String> propertyNames() {
- return null;
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<String> secretPropertyNames() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public boolean isLenientProperties() {
+ return false;
+ }
+
+ }
+
+ private static class MyJmsxAssembler extends EndpointUriFactorySupport implements EndpointUriFactory {
+ private static final String SYNTAX = "jmsx:destinationType:destinationName";
+
+ @Override
+ public boolean isEnabled(String scheme) {
+ return "jmsx".equals(scheme);
+ }
+
+ @Override
+ public String buildUri(String scheme, Map<String, Object> properties) throws URISyntaxException {
+ String uri = SYNTAX;
+ uri = buildPathParameter(SYNTAX, uri, "destinationType", "queue", false, properties);
+ uri = buildPathParameter(SYNTAX, uri, "destinationName", null, true, properties);
+ uri = buildQueryParameters(uri, properties);
+
+ return uri;
+ }
+
+ @Override
+ public Set<String> propertyNames() {
+ return new HashSet<>(Arrays.asList("destinationType", "destinationName", "username", "password"));
+ }
+
+ @Override
+ public Set<String> secretPropertyNames() {
+ return new HashSet<>(Arrays.asList("username", "password"));
}
@Override
diff --git a/core/camel-core/src/test/java/org/apache/camel/catalog/RuntimeCamelCatalogTest.java b/core/camel-core/src/test/java/org/apache/camel/catalog/RuntimeCamelCatalogTest.java
index 0a662d7..a831b61 100644
--- a/core/camel-core/src/test/java/org/apache/camel/catalog/RuntimeCamelCatalogTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/catalog/RuntimeCamelCatalogTest.java
@@ -18,12 +18,15 @@ package org.apache.camel.catalog;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
import org.apache.camel.catalog.impl.DefaultRuntimeCamelCatalog;
import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.tooling.model.ComponentModel;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import static org.apache.camel.util.CollectionHelper.mapOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -113,6 +116,35 @@ public class RuntimeCamelCatalogTest {
}
@Test
+ public void testAsEndpointUriSecrets() throws Exception {
+ // create a custom instance of the catalog to hack the model to post
+ // process options.
+ catalog = new DefaultRuntimeCamelCatalog() {
+ @Override
+ public ComponentModel componentModel(String name) {
+ ComponentModel model = super.componentModel(name);
+ if (model != null && "timer".equals(name)) {
+ model.getEndpointParameterOptions().stream()
+ .filter(o -> Objects.equals(o.getName(), "period"))
+ .forEach(o -> o.setSecret(true));
+ model.getEndpointPathOptions().stream()
+ .filter(o -> Objects.equals(o.getName(), "timerName"))
+ .forEach(o -> o.setDefaultValue("defaultName"));
+ }
+ return model;
+ }
+ };
+ catalog.setCamelContext(new DefaultCamelContext());
+
+ assertEquals(
+ "timer:foo?period=RAW(5000)",
+ catalog.asEndpointUri("timer", mapOf("timerName", "foo", "period", "5000"), false));
+ assertEquals(
+ "timer:defaultName?period=RAW(5000)",
+ catalog.asEndpointUri("timer", mapOf("period", "5000"), false));
+ }
+
+ @Test
public void testAsEndpointUriLogShort() throws Exception {
Map<String, String> map = new HashMap<>();
map.put("loggerName", "foo");
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/component/EndpointUriFactorySupport.java b/core/camel-support/src/main/java/org/apache/camel/support/component/EndpointUriFactorySupport.java
index 7e944a2..bcc3811 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/component/EndpointUriFactorySupport.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/component/EndpointUriFactorySupport.java
@@ -78,6 +78,16 @@ public abstract class EndpointUriFactorySupport implements CamelContextAware, En
throws URISyntaxException {
// we want sorted parameters
Map<String, Object> map = new TreeMap<>(parameters);
+ for (String secretParameter : secretPropertyNames()) {
+ Object val = map.get(secretParameter);
+ if (val instanceof String) {
+ String answer = (String) val;
+ if (!answer.startsWith("#") && !answer.startsWith("RAW(")) {
+ map.put(secretParameter, "RAW(" + val + ")");
+ }
+ }
+ }
+
String query = URISupport.createQueryString(map);
if (ObjectHelper.isNotEmpty(query)) {
// there may be a ? sign in the context path then use & instead
diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointUriFactoryGenerator.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointUriFactoryGenerator.java
index 3103aa3..228dcd32 100644
--- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointUriFactoryGenerator.java
+++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointUriFactoryGenerator.java
@@ -18,9 +18,9 @@ package org.apache.camel.maven.packaging;
import java.io.IOException;
import java.io.Writer;
+import java.util.HashSet;
import java.util.Set;
import java.util.StringJoiner;
-import java.util.TreeSet;
import org.apache.camel.tooling.model.BaseOptionModel;
import org.apache.camel.tooling.model.ComponentModel;
@@ -39,6 +39,7 @@ public final class EndpointUriFactoryGenerator {
w.write("package " + pn + ";\n");
w.write("\n");
w.write("import java.net.URISyntaxException;\n");
+ w.write("import java.util.Collections;\n");
w.write("import java.util.HashMap;\n");
w.write("import java.util.HashSet;\n");
w.write("import java.util.Map;\n");
@@ -59,7 +60,11 @@ public final class EndpointUriFactoryGenerator {
}
w.write("\n");
w.write(" private static final Set<String> PROPERTY_NAMES;\n");
+ w.write(" private static final Set<String> SECRET_PROPERTY_NAMES;\n");
+ w.write(" static {\n");
w.write(generatePropertyNames(model));
+ w.write(generateSecretPropertyNames(model));
+ w.write(" }\n");
w.write("\n");
w.write(" @Override\n");
w.write(" public boolean isEnabled(String scheme) {\n");
@@ -96,6 +101,11 @@ public final class EndpointUriFactoryGenerator {
w.write(" }\n");
w.write("\n");
w.write(" @Override\n");
+ w.write(" public Set<String> secretPropertyNames() {\n");
+ w.write(" return SECRET_PROPERTY_NAMES;\n");
+ w.write(" }\n");
+ w.write("\n");
+ w.write(" @Override\n");
w.write(" public boolean isLenientProperties() {\n");
w.write(" return " + model.isLenientProperties() + ";\n");
w.write(" }\n");
@@ -104,25 +114,58 @@ public final class EndpointUriFactoryGenerator {
}
private static String generatePropertyNames(ComponentModel model) {
- int size = model.getEndpointOptions().size();
- // use sorted set so the code is always generated the same way
- Set<String> apis = new TreeSet<>();
- if (model.isApi()) {
- // gather all the option names from the api (they can be duplicated as the same name can be used by multiple methods)
- model.getApiOptions().forEach(a -> a.getMethods().forEach(m -> m.getOptions().forEach(o -> apis.add(o.getName()))));
- size += apis.size();
+ Set<String> properties = new HashSet<>();
+ model.getEndpointOptions().stream()
+ .map(ComponentModel.EndpointOptionModel::getName)
+ .forEach(properties::add);
+
+ // gather all the option names from the api (they can be duplicated as the same name
+ // can be used by multiple methods)
+ model.getApiOptions().stream()
+ .flatMap(a -> a.getMethods().stream())
+ .flatMap(m -> m.getOptions().stream())
+ .map(ComponentModel.ApiOptionModel::getName)
+ .forEach(properties::add);
+
+ if (properties.isEmpty()) {
+ return " PROPERTY_NAMES = Collections.emptySet();\n";
}
+
StringBuilder sb = new StringBuilder();
- sb.append(" static {\n");
- sb.append(" Set<String> set = new HashSet<>(").append(size).append(");\n");
- for (ComponentModel.EndpointOptionModel option : model.getEndpointOptions()) {
- sb.append(" set.add(\"").append(option.getName()).append("\");\n");
+ sb.append(" Set<String> props = new HashSet<>(").append(properties.size()).append(");\n");
+ for (String property : properties) {
+ sb.append(" props.add(\"").append(property).append("\");\n");
}
- for (String name : apis) {
- sb.append(" set.add(\"").append(name).append("\");\n");
+ sb.append(" PROPERTY_NAMES = Collections.unmodifiableSet(props);\n");
+ return sb.toString();
+ }
+
+ private static String generateSecretPropertyNames(ComponentModel model) {
+ Set<String> properties = new HashSet<>();
+ model.getEndpointOptions().stream()
+ .filter(ComponentModel.EndpointOptionModel::isSecret)
+ .map(ComponentModel.EndpointOptionModel::getName)
+ .forEach(properties::add);
+
+ // gather all the option names from the api (they can be duplicated as the same name
+ // can be used by multiple methods)
+ model.getApiOptions().stream()
+ .flatMap(a -> a.getMethods().stream())
+ .flatMap(m -> m.getOptions().stream())
+ .filter(ComponentModel.ApiOptionModel::isSecret)
+ .map(ComponentModel.ApiOptionModel::getName)
+ .forEach(properties::add);
+
+ if (properties.isEmpty()) {
+ return " SECRET_PROPERTY_NAMES = Collections.emptySet();\n";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(" Set<String> secretProps = new HashSet<>(").append(properties.size()).append(");\n");
+ for (String property : properties) {
+ sb.append(" secretProps.add(\"").append(property).append("\");\n");
}
- sb.append(" PROPERTY_NAMES = set;\n");
- sb.append(" }\n");
+ sb.append(" SECRET_PROPERTY_NAMES = Collections.unmodifiableSet(secretProps);\n");
return sb.toString();
}