Skip to content

Commit 640922b

Browse files
committed
Update docs and tests according to PR feedback
1 parent e024c92 commit 640922b

5 files changed

Lines changed: 111 additions & 74 deletions

File tree

docs/src/reference/gremlin-applications.asciidoc

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,26 +1339,17 @@ Gremlin Server supports three mechanisms to configure authorization:
13391339
13401340
. With the `traversalSources` YAML configuration, one can declare `TraversalSource` instances with
13411341
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#traversalstrategy[TraversalStrategy instances] applied via
1342-
a Gremlin query, some of which can serve for authorization purposes (`ReadOnlyStrategy`,
1342+
a Gremlin expression, some of which can serve for authorization purposes (`ReadOnlyStrategy`,
13431343
`LambdaRestrictionStrategy`, `VertexProgramRestrictionStrategy`, `SubgraphStrategy`, `PartitionStrategy`,
13441344
`EdgeLabelVerificationStrategy`), provided that users are not allowed to remove or modify these `TraversalStrategy`
13451345
instances afterwards. For example:
1346-
+
1346+
13471347
[source,yaml]
13481348
----
13491349
traversalSources: {
1350-
g: {graph: graph, query: "g.withStrategies(ReadOnlyStrategy)"}}
1351-
----
1352-
+
1353-
Alternatively, the deprecated `ScriptFileGremlinPlugin` approach can still be used with a Groovy script:
1354-
+
1355-
[source,yaml]
1356-
----
1357-
scriptEngines: {
1358-
gremlin-groovy: {
1359-
plugins: {
1360-
org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/empty-sample.groovy]}}}}
1350+
g: {graph: graph, gremlinExpression: "g.withStrategies(ReadOnlyStrategy)"}}
13611351
----
1352+
13621353
. Administrators can configure an authorizer class, an implementation of the `Authorizer` interface. An authorizer receives
13631354
a request before it is executed and it can decide to pass or deny the request, based on the information it has available
13641355
on the requesting user or can seek externally.
@@ -1654,12 +1645,14 @@ groupsandbox: [usersandbox, marko]
16541645
16551646
16561647
[[script-execution]]
1657-
==== Protecting Script Execution
1648+
==== Protecting Groovy Script Execution
16581649
1659-
NOTE: As of 4.0.0, Groovy is no longer required for server initialization. The `scriptEngines` configuration
1660-
described in this section is only needed when Groovy script execution is explicitly enabled for remote script
1661-
submission. See <<server-lifecycle-hooks>> and <<server-traversal-sources>> for the preferred YAML-based
1662-
initialization approach.
1650+
NOTE: TinkerPop currently provides two `GremlinScriptEngine` implementations, the grammar-based `GremlinLangScriptEngine`,
1651+
and the Groovy-based `GremlinGroovyScriptEngine`. `GremlinLangScriptEngine` is the default implementation and
1652+
recommended for most usages. This engine evaluates scripts through our gremlin grammar and parser, and thus isn't prone
1653+
to the same arbitrary script evaluation risks as the `GremlinGroovyScriptEngine`. The following section details the
1654+
process of hardening the `GremlinGroovyScriptEngine` if necessary. The simplest and safest recommendation is to avoid
1655+
enabling `GremlinGroovyScriptEngine` in applications where hardening is warranted.
16631656
16641657
It is important to remember that Gremlin Server exposes `GremlinScriptEngine` instances that allows for remote execution
16651658
of arbitrary code on the server. Obviously, this situation can represent a security risk or, more minimally, provide
@@ -2059,14 +2052,7 @@ included. The relevant configuration from the Gremlin Server YAML looks like thi
20592052
[source,yaml]
20602053
----
20612054
traversalSources: {
2062-
g: {graph: graph, query: "g.withStrategies(ReferenceElementStrategy)"}}
2063-
----
2064-
2065-
Alternatively, using the deprecated Groovy init script approach:
2066-
2067-
[source,groovy]
2068-
----
2069-
globals << [g : traversal().with(graph).withStrategies(ReferenceElementStrategy)]
2055+
g: {graph: graph, gremlinExpression: "g.withStrategies(ReferenceElementStrategy)"}}
20702056
----
20712057
20722058
This configuration is global to Gremlin Server and therefore all methods of connection will always return elements

docs/src/upgrade/release-4.x.x.asciidoc

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -53,28 +53,38 @@ if they still need it.
5353
5454
==== Gremlin Server Initialization Without Groovy
5555
56-
Gremlin Server no longer requires Groovy for server initialization. Three new YAML configuration mechanisms replace
57-
the Groovy init scripts that were previously used to create `TraversalSource` bindings, load data, and run lifecycle
58-
hooks.
56+
Previous versions of Gremlin Server relied on the Groovy script engine for basic server initialization; binding
57+
traversal sources, loading data, and running lifecycle hooks all required Groovy init scripts. This meant the Groovy
58+
script engine was active by default, even for users who only needed to send standard Gremlin queries. An active
59+
scripting engine capable of executing arbitrary code introduces a security surface that is difficult to harden and
60+
easy to misconfigure.
5961
60-
===== Auto-Created TraversalSources
62+
As of this release, Groovy is completely disabled by default throughout the entire server lifecycle. Server
63+
initialization is handled through new declarative YAML configuration, and standard Gremlin queries are processed by the
64+
`gremlin-lang` engine. Users who explicitly need Groovy script execution can still opt in, but it is no longer
65+
required for any standard usage.
6166
62-
Any graph defined in the `graphs` section that does not have an explicit `traversalSources` entry will automatically
63-
get a `TraversalSource` binding. A graph named `graph` is bound to `g`; all others are bound to `g_<name>`.
67+
===== Simplified Server Configuration
6468
65-
A minimal server configuration is now:
69+
The most basic server setup, initializing a basic graph with a traversal source, previously required a Groovy init
70+
script just to create the `g` binding. Now, any graph defined in the `graphs` section automatically gets a
71+
`TraversalSource` according to these rules:
72+
73+
* A graph named `graph` is implicitly bound to `g`
74+
* All others are bound to `g_<graph-name>` (e.g. `modern` gets `g_modern`)
75+
76+
A fully functional minimal configuration is now simply:
6677
6778
[source,yaml]
6879
----
6980
graphs: {
7081
graph: conf/tinkergraph-empty.properties}
7182
----
7283
73-
This automatically creates a `g` binding — no init script needed.
74-
75-
===== Declarative `traversalSources`
84+
===== Strategy Configuration via `traversalSources`
7685
77-
A new `traversalSources` YAML section allows explicit `TraversalSource` creation with optional strategy configuration:
86+
For cases that require custom traversalSource naming or strategies on a `TraversalSource` (e.g. `ReadOnlyStrategy`), the
87+
`traversalSources` YAML section provides explicit control without scripting:
7888
7989
[source,yaml]
8090
----
@@ -85,14 +95,18 @@ traversalSources: {
8595
8696
Each entry specifies:
8797
88-
- `graph` (required) references a key in the `graphs` section
89-
- `gremlinExpression` (optional) a Gremlin expression evaluated with a base traversal source bound as `g`
90-
- `language` (optional) which script engine to use for the expression (defaults to `gremlin-lang`, or the sole
98+
- `graph` (required): references a key in the `graphs` section
99+
- `gremlinExpression` (optional): a Gremlin expression evaluated with a base traversal source bound as `g`
100+
- `language` (optional): which script engine to use for the expression (defaults to `gremlin-lang`, or the sole
91101
configured engine if only one is present)
92102
93-
===== Java-Based `lifecycleHooks`
103+
Graphs with explicit `traversalSources` entries are excluded from the implicitly defined traversal sources described in
104+
the previous section.
94105
95-
A new `lifecycleHooks` YAML section replaces Groovy-based `LifeCycleHook` creation:
106+
===== Java-Based Lifecycle Hooks
107+
108+
For startup and shutdown logic that goes beyond declarative configuration such as loading sample data, initializing
109+
caches, or custom setup, Java-based `LifeCycleHook` implementations can now replace Groovy init scripts:
96110
97111
[source,yaml]
98112
----
@@ -102,16 +116,14 @@ lifecycleHooks:
102116
----
103117
104118
Each entry specifies a `className` implementing `LifeCycleHook` and an optional `config` map passed to the hook's
105-
`init()` method. The built-in `TinkerFactoryDataLoader` supports datasets: `modern`, `classic`, `crew`, `grateful`,
106-
`sink`, and `airroutes`.
119+
`init()` method. The built-in `TinkerFactoryDataLoader` supports datasets: `airroutes`, `modern`, `classic`, `crew`,
120+
`grateful`, and `sink`.
107121
108-
===== Deprecation of Groovy Init Scripts
122+
===== Migrating from Groovy Init Scripts
109123
110124
Creating `TraversalSource` and `LifeCycleHook` instances via Groovy init scripts is now deprecated. Existing scripts
111-
continue to work, but a deprecation warning is logged at startup. Migrate to the YAML-based configuration described
112-
above.
113-
114-
===== Migration Examples
125+
continue to work when `GremlinGroovyScriptEngine` is explicitly configured, but a deprecation warning is logged at
126+
startup. Support for Groovy script initialization and customization may be dropped in a future release.
115127
116128
*Before (Groovy init script):*
117129
@@ -136,7 +148,7 @@ lifecycleHooks:
136148
- { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: graph, dataset: modern}}
137149
----
138150
139-
The `g` binding is auto-created from the `graph` entry. No `scriptEngines` section is needed.
151+
The `g` binding is implicitly created from the `graph` entry. No `scriptEngines` section is needed.
140152
141153
== TinkerPop 4.0.0-beta.2
142154

gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ public Settings overrideSettings(final Settings settings) {
177177
case "shouldTimeOutRemoteTraversal":
178178
settings.evaluationTimeout = 500;
179179
break;
180+
case "ensureScriptEngineDefaultsToGremlinLang":
181+
settings.scriptEngines = new HashMap<>();
182+
break;
180183
case "shouldBlowTheWorkQueueSize":
181184
settings.gremlinPool = 1;
182185
settings.maxWorkQueueSize = 1;
@@ -1088,4 +1091,54 @@ public void shouldParseFloatLiteralWithoutSuffixDependingOnLanguage() {
10881091
assertEquals(new BigDecimal("1.0"), client.submit("g.inject(1.0)", gremlinGroovy).one().getObject());
10891092
assertEquals(new BigDecimal("-123.456"), client.submit("g.inject(-123.456)", gremlinGroovy).one().getObject());
10901093
}
1094+
1095+
@Test
1096+
public void ensureScriptEngineDefaultsToGremlinLang() {
1097+
final Cluster cluster = TestClientFactory.open();
1098+
final Client client = cluster.connect();
1099+
1100+
try {
1101+
// should handle traversal with no explicit language
1102+
final RequestOptions withAlias = RequestOptions.build().addG("gmodern").create();
1103+
assertEquals(6L, client.submit("g.V().count()", withAlias).one().getLong());
1104+
1105+
// should handle script with explicit gremlin-lang
1106+
final RequestOptions langOptions = RequestOptions.build().language("gremlin-lang").addG("gmodern").create();
1107+
assertEquals(6L, client.submit("g.V().count()", langOptions).one().getLong());
1108+
1109+
// should reject non-Gremlin expressions
1110+
try {
1111+
client.submit("2+2", langOptions).all().get();
1112+
fail("Should have failed for non-Gremlin expression");
1113+
} catch (Exception ex) {
1114+
final ResponseException re = (ResponseException) ex.getCause();
1115+
assertEquals(HttpResponseStatus.BAD_REQUEST, re.getResponseStatusCode());
1116+
assertEquals("MalformedQueryException", re.getRemoteException());
1117+
}
1118+
1119+
// in gremlin-groovy '1g' is a valid BigDecimal but in gremlin-lang it should be '1m'
1120+
try {
1121+
client.submit("g.inject(1g)", langOptions).all().get();
1122+
fail("Should have failed for Groovy-specific syntax");
1123+
} catch (Exception ex) {
1124+
final ResponseException re = (ResponseException) ex.getCause();
1125+
assertEquals(HttpResponseStatus.BAD_REQUEST, re.getResponseStatusCode());
1126+
assertEquals("MalformedQueryException", re.getRemoteException());
1127+
}
1128+
1129+
// gremlin-lang BigDecimal syntax should work
1130+
assertEquals(BigDecimal.ONE, client.submit("g.inject(1m)", langOptions).one().getObject());
1131+
1132+
// explicit gremlin-groovy should fail when engine is not configured
1133+
try {
1134+
client.submit("2+2", groovyRequestOptions).all().get();
1135+
fail("Should have failed for unavailable gremlin-groovy engine");
1136+
} catch (Exception ex) {
1137+
final ResponseException re = (ResponseException) ex.getCause();
1138+
assertEquals(HttpResponseStatus.BAD_REQUEST, re.getResponseStatusCode());
1139+
}
1140+
} finally {
1141+
cluster.close();
1142+
}
1143+
}
10911144
}

gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/SettingsTest.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.yaml.snakeyaml.Yaml;
2323
import org.yaml.snakeyaml.constructor.Constructor;
2424

25+
import java.io.File;
26+
import java.io.FileInputStream;
2527
import java.io.InputStream;
2628

2729
import static org.hamcrest.MatcherAssert.assertThat;
@@ -33,6 +35,11 @@
3335

3436
public class SettingsTest {
3537

38+
private static InputStream getMinimalConfigStream() throws Exception {
39+
final File confDir = new File(System.getProperty("build.dir"), "../conf");
40+
return new FileInputStream(new File(confDir, "gremlin-server-min.yaml"));
41+
}
42+
3643
private static class CustomSettings extends Settings {
3744
public String customValue = "localhost";
3845

@@ -64,7 +71,7 @@ public void defaultCustomValuesAreHandledCorrectly() throws Exception {
6471

6572
@Test
6673
public void scriptEnginesDefaultsToGremlinLangWhenAbsentFromYaml() throws Exception {
67-
final InputStream stream = SettingsTest.class.getResourceAsStream("gremlin-server-minimal.yaml");
74+
final InputStream stream = getMinimalConfigStream();
6875
final Settings settings = Settings.read(stream);
6976

7077
assertThat(settings.scriptEngines, is(notNullValue()));
@@ -103,7 +110,7 @@ public void traversalSourcesParsedFromYaml() throws Exception {
103110

104111
@Test
105112
public void traversalSourcesDefaultsToEmptyMapWhenAbsentFromYaml() throws Exception {
106-
final InputStream stream = SettingsTest.class.getResourceAsStream("gremlin-server-minimal.yaml");
113+
final InputStream stream = getMinimalConfigStream();
107114
final Settings settings = Settings.read(stream);
108115

109116
assertThat(settings.traversalSources, is(notNullValue()));
@@ -128,7 +135,7 @@ public void lifecycleHooksParsedFromYaml() throws Exception {
128135

129136
@Test
130137
public void lifecycleHooksDefaultsToEmptyListWhenAbsentFromYaml() throws Exception {
131-
final InputStream stream = SettingsTest.class.getResourceAsStream("gremlin-server-minimal.yaml");
138+
final InputStream stream = getMinimalConfigStream();
132139
final Settings settings = Settings.read(stream);
133140

134141
assertThat(settings.lifecycleHooks, is(notNullValue()));

gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-minimal.yaml

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)