blob: 1b30c243eb773cf35ed0482adce6dfd7eeb0fd61 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.tinkerpop.gremlin.server.authz;
import org.apache.tinkerpop.gremlin.driver.Tokens;
import org.apache.tinkerpop.gremlin.driver.message.RequestMessage;
import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.verification.VertexProgramRestrictionStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SubgraphStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy;
import org.apache.tinkerpop.gremlin.server.auth.AuthenticatedUser;
import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
import org.apache.tinkerpop.gremlin.util.function.Lambda;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import static org.apache.tinkerpop.gremlin.server.authz.AllowListAuthorizer.REJECT_BYTECODE;
import static org.apache.tinkerpop.gremlin.server.authz.AllowListAuthorizer.REJECT_LAMBDA;
import static org.apache.tinkerpop.gremlin.server.authz.AllowListAuthorizer.REJECT_MUTATE;
import static org.apache.tinkerpop.gremlin.server.authz.AllowListAuthorizer.REJECT_OLAP;
import static org.apache.tinkerpop.gremlin.server.authz.AllowListAuthorizer.REJECT_SUBGRAPH;
import static org.apache.tinkerpop.gremlin.server.authz.AllowListAuthorizer.REJECT_STRING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* @author Marc de Lignie
*
* Run with:
* mvn test --projects gremlin-server -Dtest=AuthorizerTest
*/
public class AuthorizerTest {
final String BYTECODE = "bytecode";
final String BYTECODE_LAMBDA = "bytecode-lambda";
final String BYTECODE_MUTATE = "bytecode-mutate";
final String BYTECODE_OLAP = "bytecode-OLAP";
final String BYTECODE_SUBGRAPH = "bytecode-subgraph";
AllowListAuthorizer authorizer;
SubgraphTraversals subgraphTraversals;
@Rule
public TestName name = new TestName();
@Before
public void setup() {
final Map<String, Object> config = new HashMap<>();
final String yamlName = "org/apache/tinkerpop/gremlin/server/allow-list.yaml";
String file = Objects.requireNonNull(getClass().getClassLoader().getResource(yamlName)).getFile();
config.put(AllowListAuthorizer.KEY_AUTHORIZATION_ALLOWLIST, file);
authorizer = new AllowListAuthorizer();
authorizer.setup(config);
}
@Test
public void shouldAuthorizeBytecodeRequest() throws AuthorizationException {
positiveBytecode("userclassic", "gclassic", BYTECODE);
positiveBytecode("usermodern", "gmodern", BYTECODE);
positiveBytecode("stephen", "gmodern", BYTECODE);
positiveBytecode("userclassic", "gcrew", BYTECODE);
positiveBytecode("usermodern", "gcrew", BYTECODE);
positiveBytecode("stephen", "gcrew", BYTECODE);
positiveBytecode("userclassic", "ggrateful", BYTECODE);
positiveBytecode("usersink", "ggrateful", BYTECODE);
positiveBytecode("anyuser", "ggrateful", BYTECODE);
positiveBytecode("usersandbox", "gclassic", BYTECODE);
positiveBytecode("marko", "gcrew", BYTECODE);
}
@Test
public void shouldNotAuthorizeBytecodeRequest() {
negativeBytecode("usersink", "gclassic", BYTECODE);
negativeBytecode("usersink", "gmodern", BYTECODE);
negativeBytecode("usersink", "gcrew", BYTECODE);
negativeBytecode("anyuser", "gclassic", BYTECODE);
negativeBytecode("anyuser", "gmodern", BYTECODE);
negativeBytecode("anyuser", "gcrew", BYTECODE);
}
@Test
public void shouldAuthorizeLambdaBytecodeRequest() throws AuthorizationException {
positiveBytecode("usersandbox", "gclassic", BYTECODE_LAMBDA);
positiveBytecode("marko", "gcrew", BYTECODE_LAMBDA);
}
@Test
public void shouldNotAuthorizeLambdaBytecodeRequest() {
negativeBytecode("userclassic", "gclassic", BYTECODE_LAMBDA);
negativeBytecode("usermodern", "gmodern", BYTECODE_LAMBDA);
negativeBytecode("stephen", "gmodern", BYTECODE_LAMBDA);
negativeBytecode("userclassic", "gcrew", BYTECODE_LAMBDA);
negativeBytecode("usermodern", "gcrew", BYTECODE_LAMBDA);
negativeBytecode("stephen", "gcrew", BYTECODE_LAMBDA);
negativeBytecode("userclassic", "ggrateful", BYTECODE_LAMBDA);
negativeBytecode("usersink", "ggrateful", BYTECODE_LAMBDA);
negativeBytecode("anyuser", "ggrateful", BYTECODE_LAMBDA);
}
@Test
public void shouldAuthorizeMutatingBytecodeRequest() throws AuthorizationException {
positiveBytecode("usersandbox", "gclassic", BYTECODE_MUTATE);
positiveBytecode("marko", "gcrew", BYTECODE_MUTATE);
}
@Test
public void shouldNotAuthorizeMutatingBytecodeRequest() {
negativeBytecode("userclassic", "gclassic", BYTECODE_MUTATE);
negativeBytecode("usermodern", "gmodern", BYTECODE_MUTATE);
negativeBytecode("stephen", "gmodern", BYTECODE_MUTATE);
negativeBytecode("userclassic", "gcrew", BYTECODE_MUTATE);
negativeBytecode("usermodern", "gcrew", BYTECODE_MUTATE);
negativeBytecode("stephen", "gcrew", BYTECODE_MUTATE);
negativeBytecode("userclassic", "ggrateful", BYTECODE_MUTATE);
negativeBytecode("usersink", "ggrateful", BYTECODE_MUTATE);
negativeBytecode("anyuser", "ggrateful", BYTECODE_MUTATE);
}
@Test
public void shouldAuthorizeOLAPBytecodeRequest() throws AuthorizationException {
positiveBytecode("usersandbox", "gclassic", BYTECODE_OLAP);
positiveBytecode("marko", "gcrew", BYTECODE_OLAP);
}
@Test
public void shouldNotAuthorizeOLAPBytecodeRequest() {
negativeBytecode("userclassic", "gclassic", BYTECODE_OLAP);
negativeBytecode("usermodern", "gmodern", BYTECODE_OLAP);
negativeBytecode("stephen", "gmodern", BYTECODE_OLAP);
negativeBytecode("userclassic", "gcrew", BYTECODE_OLAP);
negativeBytecode("usermodern", "gcrew", BYTECODE_OLAP);
negativeBytecode("stephen", "gcrew", BYTECODE_OLAP);
negativeBytecode("userclassic", "ggrateful", BYTECODE_OLAP);
negativeBytecode("usersink", "ggrateful", BYTECODE_OLAP);
negativeBytecode("anyuser", "ggrateful", BYTECODE_OLAP);
}
@Test
public void shouldAuthorizeSubgraphStrategyBytecodeRequest() throws AuthorizationException {
subgraphTraversals = new SubgraphTraversals();
positiveBytecode("usersandbox", "gclassic", BYTECODE_SUBGRAPH);
positiveBytecode("marko", "gcrew", BYTECODE_SUBGRAPH);
}
@Test
public void shouldNotAuthorizeSubgraphStrategyBytecodeRequest() {
subgraphTraversals = new SubgraphTraversals();
negativeBytecode("userclassic", "gclassic", BYTECODE_SUBGRAPH);
negativeBytecode("usermodern", "gmodern", BYTECODE_SUBGRAPH);
negativeBytecode("stephen", "gmodern", BYTECODE_SUBGRAPH);
negativeBytecode("userclassic", "gcrew", BYTECODE_SUBGRAPH);
negativeBytecode("usermodern", "gcrew", BYTECODE_SUBGRAPH);
negativeBytecode("stephen", "gcrew", BYTECODE_SUBGRAPH);
negativeBytecode("userclassic", "ggrateful", BYTECODE_SUBGRAPH);
negativeBytecode("usersink", "ggrateful", BYTECODE_SUBGRAPH);
negativeBytecode("anyuser", "ggrateful", BYTECODE_SUBGRAPH);
}
@Test
public void shouldAuthorizeStringRequest() throws AuthorizationException {
authorizer.authorize(new AuthenticatedUser("usersandbox"), buildRequestMessage("gclassic"));
authorizer.authorize(new AuthenticatedUser("marko"), buildRequestMessage("gcrew"));
}
@Test
public void shouldNotAuthorizeStringReques() {
negativeString("userclassic", "gclassic");
negativeString("stephen", "gmodern");
negativeString("userclassic", "gmodern");
negativeString("usersink", "gclassic");
negativeString("anyuser", "ggrateful");
}
private void positiveBytecode(final String username, final String traversalSource, final String requestType) throws AuthorizationException {
final Map<String, String> aliases = new HashMap<>();
aliases.put("g", traversalSource);
authorizer.authorize(new AuthenticatedUser(username), bytecodeRequest(requestType), aliases);
}
private void negativeBytecode(final String username, final String traversalSource, final String requestType) {
final Map<String, String> aliases = new HashMap<>();
aliases.put("g", traversalSource);
final String message;
switch (requestType) {
case BYTECODE: message = String.format(REJECT_BYTECODE + ".", "[" + traversalSource + "]"); break;
case BYTECODE_LAMBDA: message = String.format(REJECT_BYTECODE + " using " + REJECT_LAMBDA + ".", "[" + traversalSource + "]"); break;
case BYTECODE_MUTATE: message = String.format(REJECT_BYTECODE + " using " + REJECT_MUTATE + ".", "[" + traversalSource + "]"); break;
case BYTECODE_OLAP: message = String.format(REJECT_BYTECODE + " using " + REJECT_OLAP + ".", "[" + traversalSource + "]"); break;
case BYTECODE_SUBGRAPH: message = String.format(REJECT_BYTECODE + " using " + REJECT_SUBGRAPH + ".", "[" + traversalSource + "]"); break;
default: throw new IllegalArgumentException();
}
try {
authorizer.authorize(new AuthenticatedUser(username), bytecodeRequest(requestType), aliases);
fail("Test code did not fail while it should have failed!");
} catch(AuthorizationException e) {
assertEquals(message, e.getMessage());
}
}
private Bytecode bytecodeRequest(final String requestType) {
final GraphTraversalSource g = TinkerGraph.open().traversal();
final Bytecode bytecode;
switch (requestType) {
case BYTECODE: bytecode = g.V().asAdmin().getBytecode(); break;
case BYTECODE_LAMBDA: bytecode = g.V().map(Lambda.function("it.get()")).asAdmin().getBytecode(); break;
case BYTECODE_MUTATE: bytecode = g.withoutStrategies(ReadOnlyStrategy.class).V().addV().asAdmin().getBytecode(); break;
case BYTECODE_OLAP: bytecode = g.withoutStrategies(VertexProgramRestrictionStrategy.class).withComputer().V().asAdmin().getBytecode(); break;
case BYTECODE_SUBGRAPH: bytecode = subgraphTraversals.get(); break;
default: throw new IllegalArgumentException();
}
return bytecode;
}
private void negativeString(final String username, final String traversalSource) {
try {
authorizer.authorize(new AuthenticatedUser(username), buildRequestMessage(traversalSource));
fail("Test code did not fail while it should have failed!");
} catch(AuthorizationException e) {
assertEquals(REJECT_STRING, e.getMessage());
}
}
private RequestMessage buildRequestMessage(final String traversalSource) {
final String script = String.format("1+1; %s.V().map{it.get()}", traversalSource);
return RequestMessage.build(Tokens.OPS_EVAL).addArg(Tokens.ARGS_GREMLIN, script).create();
}
private static class SubgraphTraversals implements Supplier<Bytecode> {
final GraphTraversalSource g = TinkerGraph.open().traversal();
final Iterator<Bytecode> mutatingBytecodes = Arrays.asList(
g.withoutStrategies(SubgraphStrategy.class).V().asAdmin().getBytecode(),
g.withStrategies(SubgraphStrategy.build().vertices(__.bothE()).create()).V().asAdmin().getBytecode(),
g.withoutStrategies(SubgraphStrategy.class).V().asAdmin().getBytecode(),
g.withStrategies(SubgraphStrategy.build().vertices(__.bothE()).create()).V().asAdmin().getBytecode(),
g.withoutStrategies(SubgraphStrategy.class).V().asAdmin().getBytecode(),
g.withStrategies(SubgraphStrategy.build().vertices(__.bothE()).create()).V().asAdmin().getBytecode(),
g.withoutStrategies(SubgraphStrategy.class).V().asAdmin().getBytecode(),
g.withStrategies(SubgraphStrategy.build().vertices(__.bothE()).create()).V().asAdmin().getBytecode(),
g.withoutStrategies(SubgraphStrategy.class).V().asAdmin().getBytecode(),
g.withStrategies(SubgraphStrategy.build().vertices(__.bothE()).create()).V().asAdmin().getBytecode(),
g.withoutStrategies(SubgraphStrategy.class).V().asAdmin().getBytecode(),
g.withStrategies(SubgraphStrategy.build().vertices(__.bothE()).create()).V().asAdmin().getBytecode()
).iterator();
public Bytecode get() {
return mutatingBytecodes.next();
}
}
}