Skip to content

Commit 7835893

Browse files
committed
Update docs and tests according to PR feedback
1 parent 6bb8a62 commit 7835893

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
@@ -34,28 +34,38 @@ complete list of all the modifications that are part of this release.
3434
3535
==== Gremlin Server Initialization Without Groovy
3636
37-
Gremlin Server no longer requires Groovy for server initialization. Three new YAML configuration mechanisms replace
38-
the Groovy init scripts that were previously used to create `TraversalSource` bindings, load data, and run lifecycle
39-
hooks.
37+
Previous versions of Gremlin Server relied on the Groovy script engine for basic server initialization; binding
38+
traversal sources, loading data, and running lifecycle hooks all required Groovy init scripts. This meant the Groovy
39+
script engine was active by default, even for users who only needed to send standard Gremlin queries. An active
40+
scripting engine capable of executing arbitrary code introduces a security surface that is difficult to harden and
41+
easy to misconfigure.
4042
41-
===== Auto-Created TraversalSources
43+
As of this release, Groovy is completely disabled by default throughout the entire server lifecycle. Server
44+
initialization is handled through new declarative YAML configuration, and standard Gremlin queries are processed by the
45+
`gremlin-lang` engine. Users who explicitly need Groovy script execution can still opt in, but it is no longer
46+
required for any standard usage.
4247
43-
Any graph defined in the `graphs` section that does not have an explicit `traversalSources` entry will automatically
44-
get a `TraversalSource` binding. A graph named `graph` is bound to `g`; all others are bound to `g_<name>`.
48+
===== Simplified Server Configuration
4549
46-
A minimal server configuration is now:
50+
The most basic server setup, initializing a basic graph with a traversal source, previously required a Groovy init
51+
script just to create the `g` binding. Now, any graph defined in the `graphs` section automatically gets a
52+
`TraversalSource` according to these rules:
53+
54+
* A graph named `graph` is implicitly bound to `g`
55+
* All others are bound to `g_<graph-name>` (e.g. `modern` gets `g_modern`)
56+
57+
A fully functional minimal configuration is now simply:
4758
4859
[source,yaml]
4960
----
5061
graphs: {
5162
graph: conf/tinkergraph-empty.properties}
5263
----
5364
54-
This automatically creates a `g` binding — no init script needed.
55-
56-
===== Declarative `traversalSources`
65+
===== Strategy Configuration via `traversalSources`
5766
58-
A new `traversalSources` YAML section allows explicit `TraversalSource` creation with optional strategy configuration:
67+
For cases that require custom traversalSource naming or strategies on a `TraversalSource` (e.g. `ReadOnlyStrategy`), the
68+
`traversalSources` YAML section provides explicit control without scripting:
5969
6070
[source,yaml]
6171
----
@@ -66,14 +76,18 @@ traversalSources: {
6676
6777
Each entry specifies:
6878
69-
- `graph` (required) references a key in the `graphs` section
70-
- `gremlinExpression` (optional) a Gremlin expression evaluated with a base traversal source bound as `g`
71-
- `language` (optional) which script engine to use for the expression (defaults to `gremlin-lang`, or the sole
79+
- `graph` (required): references a key in the `graphs` section
80+
- `gremlinExpression` (optional): a Gremlin expression evaluated with a base traversal source bound as `g`
81+
- `language` (optional): which script engine to use for the expression (defaults to `gremlin-lang`, or the sole
7282
configured engine if only one is present)
7383
74-
===== Java-Based `lifecycleHooks`
84+
Graphs with explicit `traversalSources` entries are excluded from the implicitly defined traversal sources described in
85+
the previous section.
7586
76-
A new `lifecycleHooks` YAML section replaces Groovy-based `LifeCycleHook` creation:
87+
===== Java-Based Lifecycle Hooks
88+
89+
For startup and shutdown logic that goes beyond declarative configuration such as loading sample data, initializing
90+
caches, or custom setup, Java-based `LifeCycleHook` implementations can now replace Groovy init scripts:
7791
7892
[source,yaml]
7993
----
@@ -83,16 +97,14 @@ lifecycleHooks:
8397
----
8498
8599
Each entry specifies a `className` implementing `LifeCycleHook` and an optional `config` map passed to the hook's
86-
`init()` method. The built-in `TinkerFactoryDataLoader` supports datasets: `modern`, `classic`, `crew`, `grateful`,
87-
`sink`, and `airroutes`.
100+
`init()` method. The built-in `TinkerFactoryDataLoader` supports datasets: `airroutes`, `modern`, `classic`, `crew`,
101+
`grateful`, and `sink`.
88102
89-
===== Deprecation of Groovy Init Scripts
103+
===== Migrating from Groovy Init Scripts
90104
91105
Creating `TraversalSource` and `LifeCycleHook` instances via Groovy init scripts is now deprecated. Existing scripts
92-
continue to work, but a deprecation warning is logged at startup. Migrate to the YAML-based configuration described
93-
above.
94-
95-
===== Migration Examples
106+
continue to work when `GremlinGroovyScriptEngine` is explicitly configured, but a deprecation warning is logged at
107+
startup. Support for Groovy script initialization and customization may be dropped in a future release.
96108
97109
*Before (Groovy init script):*
98110
@@ -117,7 +129,7 @@ lifecycleHooks:
117129
- { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: graph, dataset: modern}}
118130
----
119131
120-
The `g` binding is auto-created from the `graph` entry. No `scriptEngines` section is needed.
132+
The `g` binding is implicitly created from the `graph` entry. No `scriptEngines` section is needed.
121133
122134
== TinkerPop 4.0.0-beta.2
123135

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)