/* This file is part of VoltDB.
* Copyright (C) 2008-2014 VoltDB Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package org.voltdb;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.voltdb.VoltDB.Configuration;
import org.voltdb.catalog.Catalog;
import org.voltdb.client.ClientConfig;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.ProcCallException;
import org.voltdb.compiler.VoltProjectBuilder;
import org.voltdb.compiler.VoltProjectBuilder.GroupInfo;
import org.voltdb.compiler.VoltProjectBuilder.UserInfo;
import org.voltdb.utils.MiscUtils;
public class TestAdhocProcedureRoles extends AdhocDDLTestBase {
final static boolean VERBOSE = false;
final static String CATALOG_PATH = Configuration.getPathToCatalogForTest("adhocddl.jar");
final static String DEPLOYMENT_PATH = Configuration.getPathToCatalogForTest("adhocddl.xml");
final static GroupInfo ADMIN_TEMPLATE = new GroupInfo(null, false, false, true, false, false, false);
final static GroupInfo USER_TEMPLATE = new GroupInfo(null, false, false, false, false, false, false);
private class Tester
{
final VoltProjectBuilder builder;
Tester()
{
if (VERBOSE) {
System.out.println("================= Begin Test ==================");
}
this.builder = new VoltProjectBuilder();
this.builder.setSecurityEnabled(true);
this.builder.setUseDDLSchema(true);
}
void createTable(String name) throws IOException
{
this.builder.addLiteralSchema(String.format(
"create table %s (" +
"ID integer not null," +
"VAL bigint, " +
"constraint PK_TREE primary key (ID)" +
");", name));
}
void createRoles(final GroupInfo template, String... roles)
{
this.builder.addGroups(GroupInfo.fromTemplate(template, roles));
}
void createUser(String user, String password, String... roles)
{
this.builder.addUsers(new UserInfo[] {new UserInfo(user, password, roles)});
}
void createProcedureAdHoc(String procName, String role, String tableName, boolean drop) throws Exception
{
List<String> statements = new ArrayList<String>();
if (drop) {
statements.add(String.format("drop procedure %s;", procName));
}
statements.add(String.format("create procedure %s allow %s as select count(*) from %s;",
procName, role, tableName));
String ddl = StringUtils.join(statements, '\n');
try {
if (VERBOSE) {
System.out.println(String.format(":::DDL::: %s", ddl.toString()));
}
m_client.callProcedure("@AdHoc", ddl.toString());
}
catch (ProcCallException e) {
fail(String.format("Failed to create procedure\n%s\n%s", ddl, e.toString()));
}
assertTrue(findProcedureInSystemCatalog(procName));
}
void compile() throws Exception
{
if (VERBOSE) {
System.out.println(":::Deployment:::");
}
Catalog catalog = this.builder.compile(CATALOG_PATH, 2, 1, 0, null);
if (VERBOSE) {
for (String line : Files.readAllLines(Paths.get(this.builder.getPathToDeployment()), Charset.defaultCharset())) {
System.out.println(line);
}
}
assertNotNull("Schema compilation failed", catalog);
MiscUtils.copyFile(this.builder.getPathToDeployment(), DEPLOYMENT_PATH);
}
void start() throws Exception
{
VoltDB.Configuration config = new VoltDB.Configuration();
config.m_pathToCatalog = CATALOG_PATH;
config.m_pathToDeployment = DEPLOYMENT_PATH;
startServer(config);
}
void connect(String user, String password) throws Exception
{
startClient(new ClientConfig(user, password));
}
void stop() throws Exception
{
stopServer();
}
void disconnect() throws Exception
{
stopClient();
}
void callProcedure(String procName, boolean succeeds) throws Exception
{
try {
ClientResponse resp = m_client.callProcedure(procName);
if (succeeds) {
assertEquals(resp.getStatus(), ClientResponse.SUCCESS);
}
else {
assertTrue(resp.getStatus() != ClientResponse.SUCCESS);
}
}
catch (ProcCallException e) {
if (succeeds) {
e.printStackTrace();
fail(String.format("Failed to call procedure %s", procName));
}
}
}
}
public void testGoodUserCall() throws Exception
{
Tester tester = new Tester();
tester.createTable("FOO");
tester.createRoles(ADMIN_TEMPLATE, "ADMIN");
tester.createRoles(USER_TEMPLATE, "GOOD");
tester.createUser("ADMIN", "PASSWORD", "ADMIN");
tester.createUser("USER", "PASSWORD", "GOOD");
tester.compile();
try {
tester.start();
try {
tester.connect("ADMIN", "PASSWORD");
tester.createProcedureAdHoc("PROC", "GOOD", "FOO", false);
}
finally {
tester.disconnect();
}
try {
tester.connect("USER", "PASSWORD");
tester.callProcedure("PROC", true);
}
finally {
tester.disconnect();
}
}
finally {
tester.stop();
}
}
public void testBadUserCall() throws Exception
{
Tester tester = new Tester();
tester.createTable("FOO");
tester.createRoles(ADMIN_TEMPLATE, "ADMIN");
tester.createRoles(USER_TEMPLATE, "GOOD", "BAD");
tester.createUser("ADMIN", "PASSWORD", "ADMIN");
tester.createUser("USER", "PASSWORD", "BAD");
tester.compile();
try {
tester.start();
try {
tester.connect("ADMIN", "PASSWORD");
tester.createProcedureAdHoc("PROC", "GOOD", "FOO", false);
}
finally {
tester.disconnect();
}
try {
tester.connect("USER", "PASSWORD");
tester.callProcedure("PROC", false);
}
finally {
tester.disconnect();
}
}
finally {
tester.stop();
}
}
// Not that interesting since the procedure is dropped to change its permissions.
// When alter is supported it can become more interesting.
public void testGoodAndBadUserCall() throws Exception
{
Tester tester = new Tester();
tester.createTable("FOO");
tester.createRoles(ADMIN_TEMPLATE, "ADMIN");
tester.createRoles(USER_TEMPLATE, "GOOD", "BAD");
tester.createUser("ADMIN", "PASSWORD", "ADMIN");
tester.createUser("USER", "PASSWORD", "GOOD");
tester.compile();
try {
tester.start();
try {
tester.connect("ADMIN", "PASSWORD");
tester.createProcedureAdHoc("PROC", "GOOD", "FOO", false);
}
finally {
tester.disconnect();
}
try {
tester.connect("USER", "PASSWORD");
tester.callProcedure("PROC", true); // expect success
}
finally {
tester.disconnect();
}
try {
tester.connect("ADMIN", "PASSWORD");
tester.createProcedureAdHoc("PROC", "BAD", "FOO", true);
}
finally {
tester.disconnect();
}
try {
tester.connect("USER", "PASSWORD");
tester.callProcedure("PROC", false); // expect failure
}
finally {
tester.disconnect();
}
}
finally {
tester.stop();
}
}
}