http://git-wip-us.apache.org/repos/asf/cassandra/blob/3aefe356/test/unit/org/apache/cassandra/cql3/validation/entities/UFScriptTest.java ---------------------------------------------------------------------- diff --cc test/unit/org/apache/cassandra/cql3/validation/entities/UFScriptTest.java index 0000000,d3050a5..af3c894 mode 000000,100644..100644 --- a/test/unit/org/apache/cassandra/cql3/validation/entities/UFScriptTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/UFScriptTest.java @@@ -1,0 -1,504 +1,428 @@@ + /* + * 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.cassandra.cql3.validation.entities; + + import java.math.BigDecimal; + import java.math.BigInteger; + import java.util.Arrays; + import java.util.Date; + import java.util.List; + import java.util.Map; + import java.util.Set; + import java.util.TreeMap; + import java.util.TreeSet; + import java.util.UUID; + + import org.junit.Assert; -import org.junit.BeforeClass; + import org.junit.Test; + -import com.datastax.driver.core.DataType; -import com.datastax.driver.core.TupleType; -import com.datastax.driver.core.TupleValue; + import org.apache.cassandra.config.DatabaseDescriptor; + import org.apache.cassandra.cql3.CQLTester; + import org.apache.cassandra.cql3.UntypedResultSet; + import org.apache.cassandra.cql3.functions.FunctionName; -import org.apache.cassandra.dht.ByteOrderedPartitioner; + import org.apache.cassandra.exceptions.FunctionExecutionException; -import org.apache.cassandra.transport.Server; + import org.apache.cassandra.utils.UUIDGen; + + public class UFScriptTest extends CQLTester + { - @BeforeClass - public static void setUp() - { - DatabaseDescriptor.setPartitioner(ByteOrderedPartitioner.instance); - } ++ // Just JavaScript UDFs to check how UDF - especially security/class-loading/sandboxing stuff - ++ // behaves, if no Java UDF has been executed before. ++ ++ // Do not add any other test here - especially none using Java UDFs + + @Test + public void testJavascriptSimpleCollections() throws Throwable + { + createTable("CREATE TABLE %s (key int primary key, lst list<double>, st set<text>, mp map<int, boolean>)"); + + String fName1 = createFunction(KEYSPACE_PER_TEST, "list<double>", + "CREATE FUNCTION %s( lst list<double> ) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS list<double> " + + "LANGUAGE javascript\n" + + "AS 'lst;';"); + String fName2 = createFunction(KEYSPACE_PER_TEST, "set<text>", + "CREATE FUNCTION %s( st set<text> ) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS set<text> " + + "LANGUAGE javascript\n" + + "AS 'st;';"); + String fName3 = createFunction(KEYSPACE_PER_TEST, "map<int, boolean>", + "CREATE FUNCTION %s( mp map<int, boolean> ) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS map<int, boolean> " + + "LANGUAGE javascript\n" + + "AS 'mp;';"); + + List<Double> list = Arrays.asList(1d, 2d, 3d); + Set<String> set = new TreeSet<>(Arrays.asList("one", "three", "two")); + Map<Integer, Boolean> map = new TreeMap<>(); + map.put(1, true); + map.put(2, false); + map.put(3, true); + + execute("INSERT INTO %s (key, lst, st, mp) VALUES (1, ?, ?, ?)", list, set, map); + + assertRows(execute("SELECT lst, st, mp FROM %s WHERE key = 1"), + row(list, set, map)); + + assertRows(execute("SELECT " + fName1 + "(lst), " + fName2 + "(st), " + fName3 + "(mp) FROM %s WHERE key = 1"), + row(list, set, map)); + - for (int version = Server.VERSION_2; version <= maxProtocolVersion; version++) ++ for (int version : PROTOCOL_VERSIONS) + assertRowsNet(version, + executeNet(version, "SELECT " + fName1 + "(lst), " + fName2 + "(st), " + fName3 + "(mp) FROM %s WHERE key = 1"), + row(list, set, map)); + } + + @Test + public void testJavascriptTupleType() throws Throwable + { + createTable("CREATE TABLE %s (key int primary key, tup frozen<tuple<double, text, int, boolean>>)"); + + String fName = createFunction(KEYSPACE_PER_TEST, "tuple<double, text, int, boolean>", + "CREATE FUNCTION %s( tup tuple<double, text, int, boolean> ) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS tuple<double, text, int, boolean> " + + "LANGUAGE javascript\n" + + "AS $$tup;$$;"); + + Object t = tuple(1d, "foo", 2, true); + + execute("INSERT INTO %s (key, tup) VALUES (1, ?)", t); + + assertRows(execute("SELECT tup FROM %s WHERE key = 1"), + row(t)); + + assertRows(execute("SELECT " + fName + "(tup) FROM %s WHERE key = 1"), + row(t)); + } + + @Test - public void testJavascriptTupleTypeCollection() throws Throwable - { - String tupleTypeDef = "tuple<double, list<double>, set<text>, map<int, boolean>>"; - createTable("CREATE TABLE %s (key int primary key, tup frozen<" + tupleTypeDef + ">)"); - - String fTup1 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, - "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS tuple<double, list<double>, set<text>, map<int, boolean>> " + - "LANGUAGE javascript\n" + - "AS $$" + - " tup;$$;"); - String fTup2 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, - "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS double " + - "LANGUAGE javascript\n" + - "AS $$" + - " tup.getDouble(0);$$;"); - String fTup3 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, - "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS list<double> " + - "LANGUAGE javascript\n" + - "AS $$" + - " tup.getList(1, java.lang.Class.forName(\"java.lang.Double\"));$$;"); - String fTup4 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, - "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS set<text> " + - "LANGUAGE javascript\n" + - "AS $$" + - " tup.getSet(2, java.lang.Class.forName(\"java.lang.String\"));$$;"); - String fTup5 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, - "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS map<int, boolean> " + - "LANGUAGE javascript\n" + - "AS $$" + - " tup.getMap(3, java.lang.Class.forName(\"java.lang.Integer\"), java.lang.Class.forName(\"java.lang.Boolean\"));$$;"); - - List<Double> list = Arrays.asList(1d, 2d, 3d); - Set<String> set = new TreeSet<>(Arrays.asList("one", "three", "two")); - Map<Integer, Boolean> map = new TreeMap<>(); - map.put(1, true); - map.put(2, false); - map.put(3, true); - - Object t = tuple(1d, list, set, map); - - execute("INSERT INTO %s (key, tup) VALUES (1, ?)", t); - - assertRows(execute("SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"), - row(t)); - assertRows(execute("SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"), - row(1d)); - assertRows(execute("SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"), - row(list)); - assertRows(execute("SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"), - row(set)); - assertRows(execute("SELECT " + fTup5 + "(tup) FROM %s WHERE key = 1"), - row(map)); - - // same test - but via native protocol - TupleType tType = TupleType.of(DataType.cdouble(), - DataType.list(DataType.cdouble()), - DataType.set(DataType.text()), - DataType.map(DataType.cint(), DataType.cboolean())); - TupleValue tup = tType.newValue(1d, list, set, map); - for (int version = Server.VERSION_2; version <= maxProtocolVersion; version++) - { - assertRowsNet(version, - executeNet(version, "SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"), - row(tup)); - assertRowsNet(version, - executeNet(version, "SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"), - row(1d)); - assertRowsNet(version, - executeNet(version, "SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"), - row(list)); - assertRowsNet(version, - executeNet(version, "SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"), - row(set)); - assertRowsNet(version, - executeNet(version, "SELECT " + fTup5 + "(tup) FROM %s WHERE key = 1"), - row(map)); - } - } - - @Test + public void testJavascriptUserType() throws Throwable + { + String type = createType("CREATE TYPE %s (txt text, i int)"); + + createTable("CREATE TABLE %s (key int primary key, udt frozen<" + type + ">)"); + + String fUdt1 = createFunction(KEYSPACE, type, + "CREATE FUNCTION %s( udt " + type + " ) " + + "RETURNS NULL ON NULL INPUT " + - "RETURNS " + type + " " + ++ "RETURNS " + type + ' ' + + "LANGUAGE javascript\n" + + "AS $$" + + " udt;$$;"); + String fUdt2 = createFunction(KEYSPACE, type, + "CREATE FUNCTION %s( udt " + type + " ) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS text " + + "LANGUAGE javascript\n" + + "AS $$" + + " udt.getString(\"txt\");$$;"); + String fUdt3 = createFunction(KEYSPACE, type, + "CREATE FUNCTION %s( udt " + type + " ) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS int " + + "LANGUAGE javascript\n" + + "AS $$" + + " udt.getInt(\"i\");$$;"); + + execute("INSERT INTO %s (key, udt) VALUES (1, {txt: 'one', i:1})"); + + UntypedResultSet rows = execute("SELECT " + fUdt1 + "(udt) FROM %s WHERE key = 1"); + Assert.assertEquals(1, rows.size()); + assertRows(execute("SELECT " + fUdt2 + "(udt) FROM %s WHERE key = 1"), + row("one")); + assertRows(execute("SELECT " + fUdt3 + "(udt) FROM %s WHERE key = 1"), + row(1)); + } + + @Test + public void testJavascriptUTCollections() throws Throwable + { + String type = createType("CREATE TYPE %s (txt text, i int)"); + + createTable(String.format("CREATE TABLE %%s " + + "(key int primary key, lst list<frozen<%s>>, st set<frozen<%s>>, mp map<int, frozen<%s>>)", + type, type, type)); + + String fName = createFunction(KEYSPACE, "list<frozen<" + type + ">>", + "CREATE FUNCTION %s( lst list<frozen<" + type + ">> ) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS text " + + "LANGUAGE javascript\n" + + "AS $$" + + " lst.get(1).getString(\"txt\");$$;"); + createFunctionOverload(fName, "set<frozen<" + type + ">>", + "CREATE FUNCTION %s( st set<frozen<" + type + ">> ) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS text " + + "LANGUAGE javascript\n" + + "AS $$" + + " st.iterator().next().getString(\"txt\");$$;"); + createFunctionOverload(fName, "map<int, frozen<" + type + ">>", + "CREATE FUNCTION %s( mp map<int, frozen<" + type + ">> ) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS text " + + "LANGUAGE javascript\n" + + "AS $$" + + " mp.get(java.lang.Integer.valueOf(3)).getString(\"txt\");$$;"); + + execute("INSERT INTO %s (key, lst, st, mp) values (1, " + + // list<frozen<UDT>> + "[ {txt: 'one', i:1}, {txt: 'three', i:1}, {txt: 'one', i:1} ] , " + + // set<frozen<UDT>> + "{ {txt: 'one', i:1}, {txt: 'three', i:3}, {txt: 'two', i:2} }, " + + // map<int, frozen<UDT>> + "{ 1: {txt: 'one', i:1}, 2: {txt: 'one', i:3}, 3: {txt: 'two', i:2} })"); + + assertRows(execute("SELECT " + fName + "(lst) FROM %s WHERE key = 1"), + row("three")); + assertRows(execute("SELECT " + fName + "(st) FROM %s WHERE key = 1"), + row("one")); + assertRows(execute("SELECT " + fName + "(mp) FROM %s WHERE key = 1"), + row("two")); + + String cqlSelect = "SELECT " + fName + "(lst), " + fName + "(st), " + fName + "(mp) FROM %s WHERE key = 1"; + assertRows(execute(cqlSelect), + row("three", "one", "two")); + + // same test - but via native protocol - for (int version = Server.VERSION_2; version <= maxProtocolVersion; version++) ++ for (int version : PROTOCOL_VERSIONS) + assertRowsNet(version, + executeNet(version, cqlSelect), + row("three", "one", "two")); + } + + @Test + public void testJavascriptFunction() throws Throwable + { + createTable("CREATE TABLE %s (key int primary key, val double)"); + + String functionBody = '\n' + + " Math.sin(val);\n"; + + String fName = createFunction(KEYSPACE, "double", + "CREATE OR REPLACE FUNCTION %s(val double) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS double " + + "LANGUAGE javascript\n" + + "AS '" + functionBody + "';"); + + FunctionName fNameName = parseFunctionName(fName); + - assertRows(execute("SELECT language, body FROM system.schema_functions WHERE keyspace_name=? AND function_name=?", ++ assertRows(execute("SELECT language, body FROM system_schema.functions WHERE keyspace_name=? AND function_name=?", + fNameName.keyspace, fNameName.name), + row("javascript", functionBody)); + + execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d); + execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d); + execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d); + assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"), + row(1, 1d, Math.sin(1d)), + row(2, 2d, Math.sin(2d)), + row(3, 3d, Math.sin(3d)) + ); + } + + @Test + public void testJavascriptBadReturnType() throws Throwable + { + createTable("CREATE TABLE %s (key int primary key, val double)"); + + String fName = createFunction(KEYSPACE, "double", + "CREATE OR REPLACE FUNCTION %s(val double) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS double " + + "LANGUAGE javascript\n" + + "AS '\"string\";';"); + + execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d); + // throws IRE with ClassCastException + assertInvalidMessage("Invalid value for CQL type double", "SELECT key, val, " + fName + "(val) FROM %s"); + } + + @Test + public void testJavascriptThrow() throws Throwable + { + createTable("CREATE TABLE %s (key int primary key, val double)"); + + String fName = createFunction(KEYSPACE, "double", + "CREATE OR REPLACE FUNCTION %s(val double) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS double " + + "LANGUAGE javascript\n" + + "AS 'throw \"fool\";';"); + + execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d); + // throws IRE with ScriptException + assertInvalidThrowMessage("fool", FunctionExecutionException.class, + "SELECT key, val, " + fName + "(val) FROM %s"); + } + + @Test - public void testJavascriptCompileFailure() throws Throwable - { - assertInvalidMessage("Failed to compile function 'cql_test_keyspace.scrinv'", - "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS double " + - "LANGUAGE javascript\n" + - "AS 'foo bar';"); - } - - @Test - public void testScriptInvalidLanguage() throws Throwable - { - assertInvalidMessage("Invalid language 'artificial_intelligence' for function 'cql_test_keyspace.scrinv'", - "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS double " + - "LANGUAGE artificial_intelligence\n" + - "AS 'question for 42?';"); - } - - @Test + public void testScriptReturnTypeCasting() throws Throwable + { + createTable("CREATE TABLE %s (key int primary key, val double)"); + execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d); + + Object[][] variations = { + new Object[] { "true", "boolean", true }, + new Object[] { "false", "boolean", false }, + new Object[] { "100", "tinyint", (byte)100 }, + new Object[] { "100.", "tinyint", (byte)100 }, + new Object[] { "100", "smallint", (short)100 }, + new Object[] { "100.", "smallint", (short)100 }, + new Object[] { "100", "int", 100 }, + new Object[] { "100.", "int", 100 }, + new Object[] { "100", "double", 100d }, + new Object[] { "100.", "double", 100d }, + new Object[] { "100", "bigint", 100L }, + new Object[] { "100.", "bigint", 100L }, + new Object[] { "100", "varint", BigInteger.valueOf(100L) }, + new Object[] { "100.", "varint", BigInteger.valueOf(100L) }, + new Object[] { "parseInt(\"100\");", "decimal", BigDecimal.valueOf(100d) }, + new Object[] { "100.", "decimal", BigDecimal.valueOf(100d) }, + }; + + for (Object[] variation : variations) + { + Object functionBody = variation[0]; + Object returnType = variation[1]; + Object expectedResult = variation[2]; + + String fName = createFunction(KEYSPACE, "double", + "CREATE OR REPLACE FUNCTION %s(val double) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS " +returnType + ' ' + + "LANGUAGE javascript " + + "AS '" + functionBody + ";';"); + assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"), + row(1, 1d, expectedResult)); + } + } + + @Test + public void testScriptParamReturnTypes() throws Throwable + { + UUID ruuid = UUID.randomUUID(); + UUID tuuid = UUIDGen.getTimeUUID(); + + createTable("CREATE TABLE %s (key int primary key, " + + "tival tinyint, sival smallint, ival int, lval bigint, fval float, dval double, vval varint, ddval decimal, " + + "timval time, dtval date, tsval timestamp, uval uuid, tuval timeuuid)"); + execute("INSERT INTO %s (key, tival, sival, ival, lval, fval, dval, vval, ddval, timval, dtval, tsval, uval, tuval) VALUES " + + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", 1, + (byte)1, (short)1, 1, 1L, 1f, 1d, BigInteger.valueOf(1L), BigDecimal.valueOf(1d), 1L, Integer.MAX_VALUE, new Date(1), ruuid, tuuid); + + Object[][] variations = { + new Object[] { "tinyint", "tival", (byte)1, (byte)2 }, + new Object[] { "smallint", "sival", (short)1, (short)2 }, + new Object[] { "int", "ival", 1, 2 }, + new Object[] { "bigint", "lval", 1L, 2L }, + new Object[] { "float", "fval", 1f, 2f }, + new Object[] { "double", "dval", 1d, 2d }, + new Object[] { "varint", "vval", BigInteger.valueOf(1L), BigInteger.valueOf(2L) }, + new Object[] { "decimal", "ddval", BigDecimal.valueOf(1d), BigDecimal.valueOf(2d) }, + new Object[] { "time", "timval", 1L, 2L }, + }; + + for (Object[] variation : variations) + { + Object type = variation[0]; + Object col = variation[1]; + Object expected1 = variation[2]; + Object expected2 = variation[3]; + String fName = createFunction(KEYSPACE, type.toString(), + "CREATE OR REPLACE FUNCTION %s(val " + type + ") " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS " + type + ' ' + + "LANGUAGE javascript " + + "AS 'val+1;';"); + assertRows(execute("SELECT key, " + col + ", " + fName + '(' + col + ") FROM %s"), + row(1, expected1, expected2)); + } + + variations = new Object[][] { + new Object[] { "timestamp","tsval", new Date(1), new Date(1) }, + new Object[] { "uuid", "uval", ruuid, ruuid }, + new Object[] { "timeuuid", "tuval", tuuid, tuuid }, + new Object[] { "date", "dtval", Integer.MAX_VALUE, Integer.MAX_VALUE }, + }; + + for (Object[] variation : variations) + { + Object type = variation[0]; + Object col = variation[1]; + Object expected1 = variation[2]; + Object expected2 = variation[3]; + String fName = createFunction(KEYSPACE, type.toString(), + "CREATE OR REPLACE FUNCTION %s(val " + type + ") " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS " + type + ' ' + + "LANGUAGE javascript " + + "AS 'val;';"); + assertRows(execute("SELECT key, " + col + ", " + fName + '(' + col + ") FROM %s"), + row(1, expected1, expected2)); + } + } ++ ++ @Test ++ public void testJavascriptDisabled() throws Throwable ++ { ++ createTable("CREATE TABLE %s (key int primary key, val double)"); ++ ++ DatabaseDescriptor.enableScriptedUserDefinedFunctions(false); ++ try ++ { ++ assertInvalid("double", ++ "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".assertNotEnabled(val double) " + ++ "RETURNS NULL ON NULL INPUT " + ++ "RETURNS double " + ++ "LANGUAGE javascript\n" + ++ "AS 'Math.sin(val);';"); ++ } ++ finally ++ { ++ DatabaseDescriptor.enableScriptedUserDefinedFunctions(true); ++ } ++ } ++ ++ @Test ++ public void testJavascriptCompileFailure() throws Throwable ++ { ++ assertInvalidMessage("Failed to compile function 'cql_test_keyspace.scrinv'", ++ "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double) " + ++ "RETURNS NULL ON NULL INPUT " + ++ "RETURNS double " + ++ "LANGUAGE javascript\n" + ++ "AS 'foo bar';"); ++ } ++ ++ @Test ++ public void testScriptInvalidLanguage() throws Throwable ++ { ++ assertInvalidMessage("Invalid language 'artificial_intelligence' for function 'cql_test_keyspace.scrinv'", ++ "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double) " + ++ "RETURNS NULL ON NULL INPUT " + ++ "RETURNS double " + ++ "LANGUAGE artificial_intelligence\n" + ++ "AS 'question for 42?';"); ++ } + }
http://git-wip-us.apache.org/repos/asf/cassandra/blob/3aefe356/test/unit/org/apache/cassandra/cql3/validation/entities/UFSecurityTest.java ---------------------------------------------------------------------- diff --cc test/unit/org/apache/cassandra/cql3/validation/entities/UFSecurityTest.java index 0000000,0000000..5c7ca2b new file mode 100644 --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/UFSecurityTest.java @@@ -1,0 -1,0 +1,258 @@@ ++/* ++ * 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.cassandra.cql3.validation.entities; ++ ++import java.security.AccessControlException; ++import java.util.List; ++ ++import org.junit.Assert; ++import org.junit.Test; ++ ++import org.apache.cassandra.config.Config; ++import org.apache.cassandra.config.DatabaseDescriptor; ++import org.apache.cassandra.cql3.CQLTester; ++import org.apache.cassandra.cql3.functions.UDHelper; ++import org.apache.cassandra.exceptions.FunctionExecutionException; ++import org.apache.cassandra.service.ClientWarn; ++ ++public class UFSecurityTest extends CQLTester ++{ ++ @Test ++ public void testSecurityPermissions() throws Throwable ++ { ++ createTable("CREATE TABLE %s (key int primary key, dval double)"); ++ execute("INSERT INTO %s (key, dval) VALUES (?, ?)", 1, 1d); ++ ++ // Java UDFs ++ ++ try ++ { ++ String fName = createFunction(KEYSPACE_PER_TEST, "double", ++ "CREATE OR REPLACE FUNCTION %s(val double) " + ++ "RETURNS NULL ON NULL INPUT " + ++ "RETURNS double " + ++ "LANGUAGE JAVA\n" + ++ "AS 'System.getProperty(\"foo.bar.baz\"); return 0d;';"); ++ execute("SELECT " + fName + "(dval) FROM %s WHERE key=1"); ++ Assert.fail(); ++ } ++ catch (FunctionExecutionException e) ++ { ++ assertAccessControlException("System.getProperty(\"foo.bar.baz\"); return 0d;", e); ++ } ++ ++ String[][] typesAndSources = ++ { ++ {"", "try { Class.forName(\"" + UDHelper.class.getName() + "\"); } catch (Exception e) { throw new RuntimeException(e); } return 0d;"}, ++ {"sun.misc.Unsafe", "sun.misc.Unsafe.getUnsafe(); return 0d;"}, ++ {"", "try { Class.forName(\"sun.misc.Unsafe\"); } catch (Exception e) { throw new RuntimeException(e); } return 0d;"}, ++ {"java.nio.file.FileSystems", "try {" + ++ " java.nio.file.FileSystems.getDefault(); return 0d;" + ++ "} catch (Exception t) {" + ++ " throw new RuntimeException(t);" + ++ '}'}, ++ {"java.nio.channels.FileChannel", "try {" + ++ " java.nio.channels.FileChannel.open(java.nio.file.FileSystems.getDefault().getPath(\"/etc/passwd\")).close(); return 0d;" + ++ "} catch (Exception t) {" + ++ " throw new RuntimeException(t);" + ++ '}'}, ++ {"java.nio.channels.SocketChannel", "try {" + ++ " java.nio.channels.SocketChannel.open().close(); return 0d;" + ++ "} catch (Exception t) {" + ++ " throw new RuntimeException(t);" + ++ '}'}, ++ {"java.io.FileInputStream", "try {" + ++ " new java.io.FileInputStream(\"./foobar\").close(); return 0d;" + ++ "} catch (Exception t) {" + ++ " throw new RuntimeException(t);" + ++ '}'}, ++ {"java.lang.Runtime", "try {" + ++ " java.lang.Runtime.getRuntime(); return 0d;" + ++ "} catch (Exception t) {" + ++ " throw new RuntimeException(t);" + ++ '}'}, ++ {"org.apache.cassandra.service.StorageService", ++ "try {" + ++ " org.apache.cassandra.service.StorageService v = org.apache.cassandra.service.StorageService.instance; v.isShutdown(); return 0d;" + ++ "} catch (Exception t) {" + ++ " throw new RuntimeException(t);" + ++ '}'}, ++ {"java.net.ServerSocket", "try {" + ++ " new java.net.ServerSocket().bind(); return 0d;" + ++ "} catch (Exception t) {" + ++ " throw new RuntimeException(t);" + ++ '}'}, ++ {"java.io.FileOutputStream","try {" + ++ " new java.io.FileOutputStream(\".foo\"); return 0d;" + ++ "} catch (Exception t) {" + ++ " throw new RuntimeException(t);" + ++ '}'}, ++ {"java.lang.Runtime", "try {" + ++ " java.lang.Runtime.getRuntime().exec(\"/tmp/foo\"); return 0d;" + ++ "} catch (Exception t) {" + ++ " throw new RuntimeException(t);" + ++ '}'} ++ }; ++ ++ for (String[] typeAndSource : typesAndSources) ++ { ++ assertInvalidMessage(typeAndSource[0] + " cannot be resolved", ++ "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".invalid_class_access(val double) " + ++ "RETURNS NULL ON NULL INPUT " + ++ "RETURNS double " + ++ "LANGUAGE JAVA\n" + ++ "AS '" + typeAndSource[1] + "';"); ++ } ++ ++ // JavaScript UDFs ++ ++ try ++ { ++ String fName = createFunction(KEYSPACE_PER_TEST, "double", ++ "CREATE OR REPLACE FUNCTION %s(val double) " + ++ "RETURNS NULL ON NULL INPUT " + ++ "RETURNS double " + ++ "LANGUAGE javascript\n" + ++ "AS 'org.apache.cassandra.service.StorageService.instance.isShutdown(); 0;';"); ++ execute("SELECT " + fName + "(dval) FROM %s WHERE key=1"); ++ Assert.fail("Javascript security check failed"); ++ } ++ catch (FunctionExecutionException e) ++ { ++ assertAccessControlException("", e); ++ } ++ ++ String[] javascript = ++ { ++ "java.lang.management.ManagmentFactory.getThreadMXBean(); 0;", ++ "new java.io.FileInputStream(\"/tmp/foo\"); 0;", ++ "new java.io.FileOutputStream(\"/tmp/foo\"); 0;", ++ "java.nio.file.FileSystems.getDefault().createFileExclusively(\"./foo_bar_baz\"); 0;", ++ "java.nio.channels.FileChannel.open(java.nio.file.FileSystems.getDefault().getPath(\"/etc/passwd\")); 0;", ++ "java.nio.channels.SocketChannel.open(); 0;", ++ "new java.net.ServerSocket().bind(null); 0;", ++ "var thread = new java.lang.Thread(); thread.start(); 0;", ++ "java.lang.System.getProperty(\"foo.bar.baz\"); 0;", ++ "java.lang.Class.forName(\"java.lang.System\"); 0;", ++ "java.lang.Runtime.getRuntime().exec(\"/tmp/foo\"); 0;", ++ "java.lang.Runtime.getRuntime().loadLibrary(\"foobar\"); 0;", ++ "java.lang.Runtime.getRuntime().loadLibrary(\"foobar\"); 0;", ++ // TODO these (ugly) calls are still possible - these can consume CPU (as one could do with an evil loop, too) ++// "java.lang.Runtime.getRuntime().traceMethodCalls(true); 0;", ++// "java.lang.Runtime.getRuntime().gc(); 0;", ++// "java.lang.Runtime.getRuntime(); 0;", ++ }; ++ ++ for (String script : javascript) ++ { ++ try ++ { ++ String fName = createFunction(KEYSPACE_PER_TEST, "double", ++ "CREATE OR REPLACE FUNCTION %s(val double) " + ++ "RETURNS NULL ON NULL INPUT " + ++ "RETURNS double " + ++ "LANGUAGE javascript\n" + ++ "AS '" + script + "';"); ++ execute("SELECT " + fName + "(dval) FROM %s WHERE key=1"); ++ Assert.fail("Javascript security check failed: " + script); ++ } ++ catch (FunctionExecutionException e) ++ { ++ assertAccessControlException(script, e); ++ } ++ } ++ } ++ ++ private static void assertAccessControlException(String script, FunctionExecutionException e) ++ { ++ for (Throwable t = e; t != null && t != t.getCause(); t = t.getCause()) ++ if (t instanceof AccessControlException) ++ return; ++ Assert.fail("no AccessControlException for " + script + " (got " + e + ')'); ++ } ++ ++ @Test ++ public void testAmokUDF() throws Throwable ++ { ++ createTable("CREATE TABLE %s (key int primary key, dval double)"); ++ execute("INSERT INTO %s (key, dval) VALUES (?, ?)", 1, 1d); ++ ++ long udfWarnTimeout = DatabaseDescriptor.getUserDefinedFunctionWarnTimeout(); ++ long udfFailTimeout = DatabaseDescriptor.getUserDefinedFunctionFailTimeout(); ++ int maxTries = 5; ++ for (int i = 1; i <= maxTries; i++) ++ { ++ try ++ { ++ // short timeout ++ DatabaseDescriptor.setUserDefinedFunctionWarnTimeout(10); ++ DatabaseDescriptor.setUserDefinedFunctionFailTimeout(250); ++ // don't kill the unit test... - default policy is "die" ++ DatabaseDescriptor.setUserFunctionTimeoutPolicy(Config.UserFunctionTimeoutPolicy.ignore); ++ ++ ClientWarn.instance.captureWarnings(); ++ String fName = createFunction(KEYSPACE_PER_TEST, "double", ++ "CREATE OR REPLACE FUNCTION %s(val double) " + ++ "RETURNS NULL ON NULL INPUT " + ++ "RETURNS double " + ++ "LANGUAGE JAVA\n" + ++ "AS 'long t=System.currentTimeMillis()+110; while (t>System.currentTimeMillis()) { }; return 0d;'"); ++ execute("SELECT " + fName + "(dval) FROM %s WHERE key=1"); ++ List<String> warnings = ClientWarn.instance.getWarnings(); ++ Assert.assertNotNull(warnings); ++ Assert.assertFalse(warnings.isEmpty()); ++ ClientWarn.instance.resetWarnings(); ++ ++ // Java UDF ++ ++ fName = createFunction(KEYSPACE_PER_TEST, "double", ++ "CREATE OR REPLACE FUNCTION %s(val double) " + ++ "RETURNS NULL ON NULL INPUT " + ++ "RETURNS double " + ++ "LANGUAGE JAVA\n" + ++ "AS 'long t=System.currentTimeMillis()+500; while (t>System.currentTimeMillis()) { }; return 0d;';"); ++ assertInvalidMessage("ran longer than 250ms", "SELECT " + fName + "(dval) FROM %s WHERE key=1"); ++ ++ // Javascript UDF ++ ++ fName = createFunction(KEYSPACE_PER_TEST, "double", ++ "CREATE OR REPLACE FUNCTION %s(val double) " + ++ "RETURNS NULL ON NULL INPUT " + ++ "RETURNS double " + ++ "LANGUAGE JAVASCRIPT\n" + ++ "AS 'var t=java.lang.System.currentTimeMillis()+500; while (t>java.lang.System.currentTimeMillis()) { }; 0;';"); ++ assertInvalidMessage("ran longer than 250ms", "SELECT " + fName + "(dval) FROM %s WHERE key=1"); ++ ++ return; ++ } ++ catch (Error | RuntimeException e) ++ { ++ if (i == maxTries) ++ throw e; ++ } ++ finally ++ { ++ // reset to defaults ++ DatabaseDescriptor.setUserDefinedFunctionWarnTimeout(udfWarnTimeout); ++ DatabaseDescriptor.setUserDefinedFunctionFailTimeout(udfFailTimeout); ++ } ++ } ++ } ++ ++}
