diff --git a/docs/asciidoc/modules/ROOT/partials/usage/allowed.configs.adoc b/docs/asciidoc/modules/ROOT/partials/usage/allowed.configs.adoc new file mode 100644 index 0000000000..ff21b3a71b --- /dev/null +++ b/docs/asciidoc/modules/ROOT/partials/usage/allowed.configs.adoc @@ -0,0 +1,25 @@ +[NOTE] +==== +Please note that this procedure only gets the following configurations, to avoid retrieving sensitive data: + +- `apoc.import.file.enabled`, +- `apoc.import.file.use_neo4j_config`, +- `apoc.import.file.allow_read_from_filesystem`, +- `apoc.export.file.enabled`, +- `apoc.trigger.enabled`, +- `apoc.trigger.refresh`, +- `apoc.uuid.enabled`, +- `apoc.uuid.enabled.`, +- `apoc.ttl.enabled`, +- `apoc.ttl.enabled.`, +- `apoc.ttl.schedule` +- `apoc.ttl.limit` +- `apoc.jobs.scheduled.num_threads`, +- `apoc.jobs.pool.num_threads`, +- `apoc.jobs.queue.size` +- `apoc.http.timeout.connect` +- `apoc.http.timeout.read` +- `apoc.custom.procedures.refresh` +- `apoc.spatial.geocode.osm.throttle` +- `apoc.spatial.geocode.google.throttle` +==== \ No newline at end of file diff --git a/docs/asciidoc/modules/ROOT/partials/usage/apoc.config.list.adoc b/docs/asciidoc/modules/ROOT/partials/usage/apoc.config.list.adoc index 2c555daeac..5c2a58b857 100644 --- a/docs/asciidoc/modules/ROOT/partials/usage/apoc.config.list.adoc +++ b/docs/asciidoc/modules/ROOT/partials/usage/apoc.config.list.adoc @@ -21,4 +21,6 @@ RETURN key, value; | "apoc.import.file.allow_read_from_filesystem" | "true" | "apoc.trigger.enabled" | "false" | "apoc.uuid.enabled" | "false" -|=== \ No newline at end of file +|=== + +include::partial$usage/allowed.configs.adoc[] \ No newline at end of file diff --git a/docs/asciidoc/modules/ROOT/partials/usage/apoc.config.map.adoc b/docs/asciidoc/modules/ROOT/partials/usage/apoc.config.map.adoc index 826095c814..4aa67563dd 100644 --- a/docs/asciidoc/modules/ROOT/partials/usage/apoc.config.map.adoc +++ b/docs/asciidoc/modules/ROOT/partials/usage/apoc.config.map.adoc @@ -12,4 +12,7 @@ RETURN apoc.map.clean(value, keys, []) AS value; |=== | value | {`apoc.uuid.enabled`: "false", `apoc.import.file.allow_read_from_filesystem`: "true", `apoc.ttl.enabled`: "true", `apoc.trigger.enabled`: "false", `apoc.ttl.limit`: "1000", `apoc.import.file.enabled`: "false", `apoc.ttl.schedule`: "PT1M", `apoc.export.file.enabled`: "false", apoc_ttl_enabled: "true", `apoc.import.file.use_neo4j_config`: "true"} -|=== \ No newline at end of file +|=== + + +include::partial$usage/allowed.configs.adoc[] \ No newline at end of file diff --git a/extended/src/main/java/apoc/config/Config.java b/extended/src/main/java/apoc/config/Config.java index 2fe662b4bc..07df825d0d 100644 --- a/extended/src/main/java/apoc/config/Config.java +++ b/extended/src/main/java/apoc/config/Config.java @@ -12,16 +12,72 @@ import org.neo4j.procedure.Procedure; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import static apoc.ApocConfig.APOC_CONFIG_JOBS_POOL_NUM_THREADS; +import static apoc.ApocConfig.APOC_CONFIG_JOBS_QUEUE_SIZE; +import static apoc.ApocConfig.APOC_CONFIG_JOBS_SCHEDULED_NUM_THREADS; +import static apoc.ApocConfig.APOC_EXPORT_FILE_ENABLED; +import static apoc.ApocConfig.APOC_IMPORT_FILE_ALLOW__READ__FROM__FILESYSTEM; +import static apoc.ApocConfig.APOC_IMPORT_FILE_ENABLED; +import static apoc.ApocConfig.APOC_IMPORT_FILE_USE_NEO4J_CONFIG; +import static apoc.ApocConfig.APOC_TRIGGER_ENABLED; +import static apoc.ExtendedApocConfig.APOC_TTL_ENABLED; +import static apoc.ExtendedApocConfig.APOC_TTL_LIMIT; +import static apoc.ExtendedApocConfig.APOC_TTL_SCHEDULE; +import static apoc.ExtendedApocConfig.APOC_UUID_ENABLED; +import static apoc.ExtendedApocConfig.APOC_UUID_FORMAT; +import static apoc.custom.CypherProceduresHandler.CUSTOM_PROCEDURES_REFRESH; + /** * @author mh * @since 28.10.16 */ @Extended public class Config { + + // some config keys are hard-coded because belong to `core`, which is no longer accessed from `extended` + private static final Set WHITELIST_CONFIGS = Set.of( + // apoc.import. + APOC_IMPORT_FILE_ENABLED, + APOC_IMPORT_FILE_USE_NEO4J_CONFIG, + APOC_IMPORT_FILE_ALLOW__READ__FROM__FILESYSTEM, + + // apoc.export. + APOC_EXPORT_FILE_ENABLED, + + // apoc.trigger. + APOC_TRIGGER_ENABLED, + "apoc.trigger.refresh", + + // apoc.uuid. + APOC_UUID_ENABLED, + APOC_UUID_FORMAT, + + // apoc.ttl. + APOC_TTL_SCHEDULE, + APOC_TTL_ENABLED, + APOC_TTL_LIMIT, + + // apoc.jobs. + APOC_CONFIG_JOBS_SCHEDULED_NUM_THREADS, + APOC_CONFIG_JOBS_POOL_NUM_THREADS, + APOC_CONFIG_JOBS_QUEUE_SIZE, + + // apoc.http. + "apoc.http.timeout.connect", + "apoc.http.timeout.read", + + // apoc.custom. + CUSTOM_PROCEDURES_REFRESH, + // apoc.spatial. - other configs can have sensitive credentials + "apoc.spatial.geocode.osm.throttle", + "apoc.spatial.geocode.google.throttle" + ); + public static class ConfigResult { public final String key; public final Object value; @@ -40,7 +96,8 @@ public ConfigResult(String key, Object value) { @Procedure public Stream list() { Configuration config = dependencyResolver.resolveDependency(ApocConfig.class).getConfig(); - return Iterators.stream(config.getKeys()).map(s -> new ConfigResult(s, config.getString(s))); + return getApocConfigs(config) + .map(s -> new ConfigResult(s, config.getString(s))); } @Admin @@ -48,8 +105,14 @@ public Stream list() { @Procedure public Stream map() { Configuration config = dependencyResolver.resolveDependency(ApocConfig.class).getConfig(); - Map configMap = Iterators.stream(config.getKeys()) + Map configMap = getApocConfigs(config) .collect(Collectors.toMap(s -> s, s -> config.getString(s))); return Stream.of(new MapResult(configMap)); } + + private static Stream getApocConfigs(Configuration config) { + // we use startsWith(..) because we can have e.g. a config `apoc.uuid.enabled.` + return Iterators.stream(config.getKeys()) + .filter(conf -> WHITELIST_CONFIGS.stream().anyMatch(conf::startsWith)); + } } diff --git a/extended/src/test/java/apoc/config/ConfigTest.java b/extended/src/test/java/apoc/config/ConfigTest.java index 2ce497d9d2..0c82048449 100644 --- a/extended/src/test/java/apoc/config/ConfigTest.java +++ b/extended/src/test/java/apoc/config/ConfigTest.java @@ -1,7 +1,7 @@ package apoc.config; import apoc.util.TestUtil; -import org.junit.Assert; +import apoc.util.collection.Iterators; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -11,15 +11,41 @@ import org.neo4j.test.rule.DbmsRule; import org.neo4j.test.rule.ImpermanentDbmsRule; +import java.util.Map; +import java.util.Set; + +import static apoc.ApocConfig.APOC_CONFIG_JOBS_SCHEDULED_NUM_THREADS; +import static apoc.ApocConfig.APOC_EXPORT_FILE_ENABLED; +import static apoc.ApocConfig.APOC_IMPORT_FILE_ALLOW__READ__FROM__FILESYSTEM; +import static apoc.ApocConfig.APOC_IMPORT_FILE_ENABLED; +import static apoc.ApocConfig.APOC_IMPORT_FILE_USE_NEO4J_CONFIG; +import static apoc.ApocConfig.APOC_TRIGGER_ENABLED; +import static apoc.trigger.TriggerHandler.TRIGGER_REFRESH; +import static org.junit.Assert.assertEquals; + /** * @author mh * @since 28.10.16 */ public class ConfigTest { + // apoc.export.file.enabled, apoc.import.file.enabled, apoc.import.file.use_neo4j_config, apoc.trigger.enabled and apoc.import.file.allow_read_from_filesystem + // are always set by default + private static final Map EXPECTED_APOC_CONFS = Map.of(APOC_EXPORT_FILE_ENABLED, "false", + APOC_IMPORT_FILE_ALLOW__READ__FROM__FILESYSTEM, "true", + APOC_IMPORT_FILE_ENABLED, "false", + APOC_IMPORT_FILE_USE_NEO4J_CONFIG, "true", + APOC_CONFIG_JOBS_SCHEDULED_NUM_THREADS, "4", + TRIGGER_REFRESH, "2000", + APOC_TRIGGER_ENABLED, "false"); @Rule - public final ProvideSystemProperty systemPropertyRule - = new ProvideSystemProperty("foo", "bar"); + public final ProvideSystemProperty systemPropertyRule + = new ProvideSystemProperty("foo", "bar") + .and("apoc.import.enabled", "true") + .and("apoc.trigger.refresh", "2000") + .and("apoc.jobs.scheduled.num_threads", "4") + .and("apoc.static.test", "one") + .and("apoc.jdbc.cassandra_songs.url", "jdbc:cassandra://localhost:9042/playlist"); @Rule public DbmsRule db = new ImpermanentDbmsRule(); @@ -35,8 +61,18 @@ public void tearDown() { } @Test - public void listTest(){ - TestUtil.testCall(db, "CALL apoc.config.list() yield key with * where key STARTS WITH 'foo' RETURN *",(row) -> Assert.assertEquals("foo",row.get("key"))); + public void configListTest(){ + TestUtil.testResult(db, "CALL apoc.config.list() YIELD key RETURN * ORDER BY key", r -> { + final Set actualConfs = Iterators.asSet(r.columnAs("key")); + assertEquals(EXPECTED_APOC_CONFS.keySet(), actualConfs); + }); + } + + @Test + public void configMapTest(){ + TestUtil.testCall(db, "CALL apoc.config.map()", r -> { + assertEquals(EXPECTED_APOC_CONFS, r.get("value")); + }); } }