http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb5897f3/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java index df7e87e..021077e 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java @@ -22,20 +22,16 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.auth.*; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.Schema; -import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.ColumnIdentifier; -import org.apache.cassandra.cql3.ColumnSpecification; -import org.apache.cassandra.cql3.QueryOptions; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.*; import org.apache.cassandra.cql3.functions.*; import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.exceptions.RequestValidationException; -import org.apache.cassandra.exceptions.UnauthorizedException; +import org.apache.cassandra.exceptions.*; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.MigrationManager; +import org.apache.cassandra.service.QueryState; import org.apache.cassandra.thrift.ThriftValidation; import org.apache.cassandra.transport.Event; @@ -57,6 +53,12 @@ public final class CreateAggregateStatement extends SchemaAlteringStatement private UDAggregate udAggregate; private boolean replaced; + private List<AbstractType<?>> argTypes; + private AbstractType<?> returnType; + private ScalarFunction stateFunction; + private ScalarFunction finalFunction; + private ByteBuffer initcond; + public CreateAggregateStatement(FunctionName functionName, List<CQL3Type.Raw> argRawTypes, String stateFunc, @@ -76,6 +78,44 @@ public final class CreateAggregateStatement extends SchemaAlteringStatement this.ifNotExists = ifNotExists; } + public Prepared prepare() + { + argTypes = new ArrayList<>(argRawTypes.size()); + for (CQL3Type.Raw rawType : argRawTypes) + argTypes.add(rawType.prepare(functionName.keyspace).getType()); + + AbstractType<?> stateType = stateTypeRaw.prepare(functionName.keyspace).getType(); + FunctionName stateFuncName = new FunctionName(functionName.keyspace, stateFunc); + Function f = Functions.find(stateFuncName, stateArguments(stateType, argTypes)); + if (!(f instanceof ScalarFunction)) + throw new InvalidRequestException("State function " + stateFuncSig(stateFuncName, stateTypeRaw, argRawTypes) + " does not exist or is not a scalar function"); + stateFunction = (ScalarFunction)f; + + if (finalFunc != null) + { + FunctionName finalFuncName = new FunctionName(functionName.keyspace, finalFunc); + f = Functions.find(finalFuncName, Collections.<AbstractType<?>>singletonList(stateType)); + if (!(f instanceof ScalarFunction)) + throw new InvalidRequestException("Final function " + finalFuncName + "(" + stateTypeRaw + ") does not exist or is not a scalar function"); + finalFunction = (ScalarFunction) f; + returnType = finalFunction.returnType(); + } + else + { + returnType = stateFunction.returnType(); + if (!returnType.equals(stateType)) + throw new InvalidRequestException("State function " + stateFuncSig(stateFunction.name(), stateTypeRaw, argRawTypes) + " return type must be the same as the first argument type (if no final function is used)"); + } + + if (ival != null) + { + ColumnSpecification receiver = new ColumnSpecification(functionName.keyspace, "--dummy--", new ColumnIdentifier("(aggregate_initcond)", true), stateType); + initcond = ival.prepare(functionName.keyspace, receiver).bindAndGet(QueryOptions.DEFAULT); + } + + return super.prepare(); + } + public void prepareKeyspace(ClientState state) throws InvalidRequestException { if (!functionName.hasKeyspace() && state.getRawKeyspace() != null) @@ -87,11 +127,37 @@ public final class CreateAggregateStatement extends SchemaAlteringStatement ThriftValidation.validateKeyspaceNotSystem(functionName.keyspace); } + protected void grantPermissionsToCreator(QueryState state) + { + try + { + IResource resource = FunctionResource.function(functionName.keyspace, functionName.name, argTypes); + DatabaseDescriptor.getAuthorizer().grant(AuthenticatedUser.SYSTEM_USER, + resource.applicablePermissions(), + resource, + RoleResource.role(state.getClientState().getUser().getName())); + } + catch (RequestExecutionException e) + { + throw new RuntimeException(e); + } + } + public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException { - // TODO CASSANDRA-7557 (function DDL permission) + if (Functions.find(functionName, argTypes) != null && orReplace) + state.ensureHasPermission(Permission.ALTER, FunctionResource.function(functionName.keyspace, + functionName.name, + argTypes)); + else + state.ensureHasPermission(Permission.CREATE, FunctionResource.keyspace(functionName.keyspace)); - state.hasKeyspaceAccess(functionName.keyspace, Permission.CREATE); + for (Function referencedFunction : stateFunction.getFunctions()) + state.ensureHasPermission(Permission.EXECUTE, referencedFunction); + + if (finalFunction != null) + for (Function referencedFunction : finalFunction.getFunctions()) + state.ensureHasPermission(Permission.EXECUTE, referencedFunction); } public void validate(ClientState state) throws InvalidRequestException @@ -112,37 +178,6 @@ public final class CreateAggregateStatement extends SchemaAlteringStatement public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException { - List<AbstractType<?>> argTypes = new ArrayList<>(argRawTypes.size()); - for (CQL3Type.Raw rawType : argRawTypes) - argTypes.add(rawType.prepare(functionName.keyspace).getType()); - - FunctionName stateFuncName = new FunctionName(functionName.keyspace, stateFunc); - FunctionName finalFuncName; - - ScalarFunction fFinal = null; - AbstractType<?> stateType = stateTypeRaw.prepare(functionName.keyspace).getType(); - Function f = Functions.find(stateFuncName, stateArguments(stateType, argTypes)); - if (!(f instanceof ScalarFunction)) - throw new InvalidRequestException("State function " + stateFuncSig(stateFuncName, stateTypeRaw, argRawTypes) + " does not exist or is not a scalar function"); - ScalarFunction fState = (ScalarFunction)f; - - AbstractType<?> returnType; - if (finalFunc != null) - { - finalFuncName = new FunctionName(functionName.keyspace, finalFunc); - f = Functions.find(finalFuncName, Collections.<AbstractType<?>>singletonList(stateType)); - if (!(f instanceof ScalarFunction)) - throw new InvalidRequestException("Final function " + finalFuncName + "(" + stateTypeRaw + ") does not exist or is not a scalar function"); - fFinal = (ScalarFunction) f; - returnType = fFinal.returnType(); - } - else - { - returnType = fState.returnType(); - if (!returnType.equals(stateType)) - throw new InvalidRequestException("State function " + stateFuncSig(stateFuncName, stateTypeRaw, argRawTypes) + " return type must be the same as the first argument type (if no final function is used)"); - } - Function old = Functions.find(functionName, argTypes); if (old != null) { @@ -162,16 +197,9 @@ public final class CreateAggregateStatement extends SchemaAlteringStatement functionName, returnType.asCQL3Type(), old.returnType().asCQL3Type())); } - ByteBuffer initcond = null; - if (ival != null) - { - ColumnSpecification receiver = new ColumnSpecification(functionName.keyspace, "--dummy--", new ColumnIdentifier("(aggregate_initcond)", true), stateType); - initcond = ival.prepare(functionName.keyspace, receiver).bindAndGet(QueryOptions.DEFAULT); - } - udAggregate = new UDAggregate(functionName, argTypes, returnType, - fState, - fFinal, + stateFunction, + finalFunction, initcond); replaced = old != null;
http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb5897f3/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java index c49f80c..4e1e03a 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java @@ -21,17 +21,17 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.auth.*; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.functions.*; import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.exceptions.RequestValidationException; -import org.apache.cassandra.exceptions.UnauthorizedException; +import org.apache.cassandra.exceptions.*; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.MigrationManager; +import org.apache.cassandra.service.QueryState; import org.apache.cassandra.thrift.ThriftValidation; import org.apache.cassandra.transport.Event; @@ -51,6 +51,8 @@ public final class CreateFunctionStatement extends SchemaAlteringStatement private final List<CQL3Type.Raw> argRawTypes; private final CQL3Type.Raw rawReturnType; + private List<AbstractType<?>> argTypes; + private AbstractType<?> returnType; private UDFunction udFunction; private boolean replaced; @@ -75,6 +77,20 @@ public final class CreateFunctionStatement extends SchemaAlteringStatement this.ifNotExists = ifNotExists; } + public Prepared prepare() throws InvalidRequestException + { + if (new HashSet<>(argNames).size() != argNames.size()) + throw new InvalidRequestException(String.format("duplicate argument names for given function %s with argument names %s", + functionName, argNames)); + + argTypes = new ArrayList<>(argRawTypes.size()); + for (CQL3Type.Raw rawType : argRawTypes) + argTypes.add(rawType.prepare(typeKeyspace(rawType)).getType()); + + returnType = rawReturnType.prepare(typeKeyspace(rawReturnType)).getType(); + return super.prepare(); + } + public void prepareKeyspace(ClientState state) throws InvalidRequestException { if (!functionName.hasKeyspace() && state.getRawKeyspace() != null) @@ -86,11 +102,30 @@ public final class CreateFunctionStatement extends SchemaAlteringStatement ThriftValidation.validateKeyspaceNotSystem(functionName.keyspace); } - public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException + protected void grantPermissionsToCreator(QueryState state) { - // TODO CASSANDRA-7557 (function DDL permission) + try + { + IResource resource = FunctionResource.function(functionName.keyspace, functionName.name, argTypes); + DatabaseDescriptor.getAuthorizer().grant(AuthenticatedUser.SYSTEM_USER, + resource.applicablePermissions(), + resource, + RoleResource.role(state.getClientState().getUser().getName())); + } + catch (RequestExecutionException e) + { + throw new RuntimeException(e); + } + } - state.hasKeyspaceAccess(functionName.keyspace, Permission.CREATE); + public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException + { + if (Functions.find(functionName, argTypes) != null && orReplace) + state.ensureHasPermission(Permission.ALTER, FunctionResource.function(functionName.keyspace, + functionName.name, + argTypes)); + else + state.ensureHasPermission(Permission.CREATE, FunctionResource.keyspace(functionName.keyspace)); } public void validate(ClientState state) throws InvalidRequestException @@ -111,16 +146,6 @@ public final class CreateFunctionStatement extends SchemaAlteringStatement public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException { - if (new HashSet<>(argNames).size() != argNames.size()) - throw new InvalidRequestException(String.format("duplicate argument names for given function %s with argument names %s", - functionName, argNames)); - - List<AbstractType<?>> argTypes = new ArrayList<>(argRawTypes.size()); - for (CQL3Type.Raw rawType : argRawTypes) - argTypes.add(rawType.prepare(typeKeyspace(rawType)).getType()); - - AbstractType<?> returnType = rawReturnType.prepare(typeKeyspace(rawReturnType)).getType(); - Function old = Functions.find(functionName, argTypes); if (old != null) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb5897f3/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java index 2dc9c44..a3e27e4 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java @@ -115,11 +115,17 @@ public class CreateKeyspaceStatement extends SchemaAlteringStatement { try { - DataResource resource = DataResource.keyspace(keyspace()); + RoleResource role = RoleResource.role(state.getClientState().getUser().getName()); + DataResource keyspace = DataResource.keyspace(keyspace()); DatabaseDescriptor.getAuthorizer().grant(AuthenticatedUser.SYSTEM_USER, - resource.applicablePermissions(), - resource, - RoleResource.role(state.getClientState().getUser().getName())); + keyspace.applicablePermissions(), + keyspace, + role); + FunctionResource functions = FunctionResource.keyspace(keyspace()); + DatabaseDescriptor.getAuthorizer().grant(AuthenticatedUser.SYSTEM_USER, + functions.applicablePermissions(), + functions, + role); } catch (RequestExecutionException e) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb5897f3/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java index 8863ffe..0f9e74c 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java @@ -20,6 +20,9 @@ package org.apache.cassandra.cql3.statements; import java.util.ArrayList; import java.util.List; +import com.google.common.base.Joiner; + +import org.apache.cassandra.auth.FunctionResource; import org.apache.cassandra.auth.Permission; import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.functions.*; @@ -43,6 +46,7 @@ public final class DropFunctionStatement extends SchemaAlteringStatement private final boolean argsPresent; private Function old; + private List<AbstractType<?>> argTypes; public DropFunctionStatement(FunctionName functionName, List<CQL3Type.Raw> argRawTypes, @@ -56,6 +60,15 @@ public final class DropFunctionStatement extends SchemaAlteringStatement } @Override + public Prepared prepare() throws InvalidRequestException + { + argTypes = new ArrayList<>(argRawTypes.size()); + for (CQL3Type.Raw rawType : argRawTypes) + argTypes.add(rawType.prepare(typeKeyspace(rawType)).getType()); + return super.prepare(); + } + + @Override public void prepareKeyspace(ClientState state) throws InvalidRequestException { if (!functionName.hasKeyspace() && state.getRawKeyspace() != null) @@ -70,14 +83,34 @@ public final class DropFunctionStatement extends SchemaAlteringStatement @Override public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException { - // TODO CASSANDRA-7557 (function DDL permission) - - state.hasKeyspaceAccess(functionName.keyspace, Permission.DROP); + Function function = findFunction(); + if (function == null) + { + if (!ifExists) + throw new InvalidRequestException(String.format("Unconfigured function %s.%s(%s)", + functionName.keyspace, + functionName.name, + Joiner.on(",").join(argRawTypes))); + } + else + { + state.ensureHasPermission(Permission.DROP, FunctionResource.function(function.name().keyspace, + function.name().name, + function.argTypes())); + } } @Override public void validate(ClientState state) { + List<Function> olds = Functions.find(functionName); + + if (!argsPresent && olds != null && olds.size() > 1) + throw new InvalidRequestException(String.format("'DROP FUNCTION %s' matches multiple function definitions; " + + "specify the argument types by issuing a statement like " + + "'DROP FUNCTION %s (type, type, ...)'. Hint: use cqlsh " + + "'DESCRIBE FUNCTION %s' command to find all overloads", + functionName, functionName, functionName)); } @Override @@ -90,61 +123,35 @@ public final class DropFunctionStatement extends SchemaAlteringStatement @Override public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException { - List<Function> olds = Functions.find(functionName); - - if (!argsPresent && olds != null && olds.size() > 1) - throw new InvalidRequestException(String.format("'DROP FUNCTION %s' matches multiple function definitions; " + - "specify the argument types by issuing a statement like " + - "'DROP FUNCTION %s (type, type, ...)'. Hint: use cqlsh " + - "'DESCRIBE FUNCTION %s' command to find all overloads", - functionName, functionName, functionName)); - - List<AbstractType<?>> argTypes = new ArrayList<>(argRawTypes.size()); - for (CQL3Type.Raw rawType : argRawTypes) - argTypes.add(rawType.prepare(typeKeyspace(rawType)).getType()); - - Function old; - if (argsPresent) + old = findFunction(); + if (old == null) { - old = Functions.find(functionName, argTypes); - if (old == null || !(old instanceof ScalarFunction)) - { - if (ifExists) - return false; - // just build a nicer error message - StringBuilder sb = new StringBuilder(); - for (CQL3Type.Raw rawType : argRawTypes) - { - if (sb.length() > 0) - sb.append(", "); - sb.append(rawType); - } - throw new InvalidRequestException(String.format("Cannot drop non existing function '%s(%s)'", - functionName, sb)); - } - } - else - { - if (olds == null || olds.isEmpty() || !(olds.get(0) instanceof ScalarFunction)) - { - if (ifExists) - return false; - throw new InvalidRequestException(String.format("Cannot drop non existing function '%s'", functionName)); - } - old = olds.get(0); + if (ifExists) + return false; + else + throw new InvalidRequestException(getMissingFunctionError()); } List<Function> references = Functions.getReferencesTo(old); if (!references.isEmpty()) throw new InvalidRequestException(String.format("Function '%s' still referenced by %s", old, references)); - this.old = old; - MigrationManager.announceFunctionDrop((UDFunction) old, isLocalOnly); return true; } + private String getMissingFunctionError() + { + // just build a nicer error message + StringBuilder sb = new StringBuilder("Cannot drop non existing function '"); + sb.append(functionName); + if (argsPresent) + sb.append(Joiner.on(", ").join(argRawTypes)); + sb.append("'"); + return sb.toString(); + } + private String typeKeyspace(CQL3Type.Raw rawType) { String ks = rawType.keyspace(); @@ -152,4 +159,26 @@ public final class DropFunctionStatement extends SchemaAlteringStatement return ks; return functionName.keyspace; } + + private Function findFunction() + { + Function old; + if (argsPresent) + { + old = Functions.find(functionName, argTypes); + if (old == null || !(old instanceof ScalarFunction)) + { + return null; + } + } + else + { + List<Function> olds = Functions.find(functionName); + if (olds == null || olds.isEmpty() || !(olds.get(0) instanceof ScalarFunction)) + return null; + + old = olds.get(0); + } + return old; + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb5897f3/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java index af8947d..657e6e0 100644 --- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java @@ -20,7 +20,7 @@ package org.apache.cassandra.cql3.statements; import java.nio.ByteBuffer; import java.util.*; -import com.google.common.base.Function; +import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -28,12 +28,12 @@ import org.apache.cassandra.auth.Permission; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.cql3.*; +import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.cql3.restrictions.Restriction; import org.apache.cassandra.cql3.restrictions.SingleColumnRestriction; import org.apache.cassandra.cql3.selection.Selection; import org.apache.cassandra.db.*; import org.apache.cassandra.db.composites.Composite; -import org.apache.cassandra.db.composites.Composites; import org.apache.cassandra.db.composites.CompositesBuilder; import org.apache.cassandra.db.filter.ColumnSlice; import org.apache.cassandra.db.filter.SliceQueryFilter; @@ -47,10 +47,8 @@ import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.Pair; import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse; - -import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; - import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull; +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; /* * Abstract parent class of individual modifications, i.e. INSERT, UPDATE and DELETE. @@ -80,7 +78,8 @@ public abstract class ModificationStatement implements CQLStatement private boolean setsStaticColumns; private boolean setsRegularColumns; - private final Function<ColumnCondition, ColumnDefinition> getColumnForCondition = new Function<ColumnCondition, ColumnDefinition>() + private final com.google.common.base.Function<ColumnCondition, ColumnDefinition> getColumnForCondition = + new com.google.common.base.Function<ColumnCondition, ColumnDefinition>() { public ColumnDefinition apply(ColumnCondition cond) { @@ -96,25 +95,55 @@ public abstract class ModificationStatement implements CQLStatement this.attrs = attrs; } - public boolean usesFunction(String ksName, String functionName) + public boolean usesFunction(String ksName, final String functionName) { if (attrs.usesFunction(ksName, functionName)) return true; + for (Restriction restriction : processedKeys.values()) - if (restriction != null && restriction.usesFunction(ksName, functionName)) - return true; - for (Operation operation : columnOperations) - if (operation != null && operation.usesFunction(ksName, functionName)) - return true; - for (ColumnCondition condition : columnConditions) - if (condition != null && condition.usesFunction(ksName, functionName)) - return true; - for (ColumnCondition condition : staticConditions) - if (condition != null && condition.usesFunction(ksName, functionName)) + if (restriction.usesFunction(ksName, functionName)) return true; + + if (columnOperations != null) + for (Operation operation : columnOperations) + if (operation.usesFunction(ksName, functionName)) + return true; + + if (columnConditions != null) + for (ColumnCondition condition : columnConditions) + if (condition.usesFunction(ksName, functionName)) + return true; + + if (staticConditions != null) + for (ColumnCondition condition : staticConditions) + if (condition.usesFunction(ksName, functionName)) + return true; + return false; } + public Iterable<Function> getFunctions() + { + Iterable<Function> functions = attrs.getFunctions(); + + for (Restriction restriction : processedKeys.values()) + functions = Iterables.concat(functions, restriction.getFunctions()); + + if (columnOperations != null) + for (Operation operation : columnOperations) + functions = Iterables.concat(functions, operation.getFunctions()); + + if (columnConditions != null) + for (ColumnCondition condition : columnConditions) + functions = Iterables.concat(functions, condition.getFunctions()); + + if (staticConditions != null) + for (ColumnCondition condition : staticConditions) + functions = Iterables.concat(functions, condition.getFunctions()); + + return functions; + } + public abstract boolean requireFullClusteringKey(); public abstract void addUpdateForKey(ColumnFamily updates, ByteBuffer key, Composite prefix, UpdateParameters params) throws InvalidRequestException; @@ -160,6 +189,9 @@ public abstract class ModificationStatement implements CQLStatement // CAS updates can be used to simulate a SELECT query, so should require Permission.SELECT as well. if (hasConditions()) state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.SELECT); + + for (Function function : getFunctions()) + state.ensureHasPermission(Permission.EXECUTE, function); } public void validate(ClientState state) throws InvalidRequestException @@ -302,7 +334,7 @@ public abstract class ModificationStatement implements CQLStatement r.appendTo(keyBuilder, options); } - return Lists.transform(keyBuilder.build(), new Function<Composite, ByteBuffer>() + return Lists.transform(keyBuilder.build(), new com.google.common.base.Function<Composite, ByteBuffer>() { @Override public ByteBuffer apply(Composite composite) http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb5897f3/src/java/org/apache/cassandra/cql3/statements/ParsedStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/ParsedStatement.java b/src/java/org/apache/cassandra/cql3/statements/ParsedStatement.java index c3e0639..b848df4 100644 --- a/src/java/org/apache/cassandra/cql3/statements/ParsedStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ParsedStatement.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.List; import org.apache.cassandra.cql3.*; +import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.exceptions.RequestValidationException; public abstract class ParsedStatement @@ -68,4 +69,9 @@ public abstract class ParsedStatement { return false; } + + public List<Function> getFunctions() + { + return Collections.emptyList(); + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb5897f3/src/java/org/apache/cassandra/cql3/statements/PermissionsManagementStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/PermissionsManagementStatement.java b/src/java/org/apache/cassandra/cql3/statements/PermissionsManagementStatement.java index f360743..b22e400 100644 --- a/src/java/org/apache/cassandra/cql3/statements/PermissionsManagementStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/PermissionsManagementStatement.java @@ -19,11 +19,10 @@ package org.apache.cassandra.cql3.statements; import java.util.Set; -import org.apache.cassandra.auth.IResource; -import org.apache.cassandra.auth.Permission; -import org.apache.cassandra.auth.RoleResource; +import org.apache.cassandra.auth.*; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.RoleName; +import org.apache.cassandra.db.SystemKeyspace; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.exceptions.UnauthorizedException; @@ -52,6 +51,14 @@ public abstract class PermissionsManagementStatement extends AuthorizationStatem // if a keyspace is omitted when GRANT/REVOKE ON TABLE <table>, we need to correct the resource. resource = maybeCorrectResource(resource, state); + + // altering permissions on builtin functions is not supported + if (resource instanceof FunctionResource + && SystemKeyspace.NAME.equals(((FunctionResource)resource).getKeyspace())) + { + throw new InvalidRequestException("Altering permissions on builtin functions is not supported"); + } + if (!resource.exists()) throw new InvalidRequestException(String.format("Resource %s doesn't exist", resource)); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb5897f3/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index 35c8d70..c7791fb 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@ -29,18 +29,13 @@ import org.apache.cassandra.auth.Permission; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.cql3.*; +import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.cql3.restrictions.StatementRestrictions; import org.apache.cassandra.cql3.selection.RawSelector; import org.apache.cassandra.cql3.selection.Selection; import org.apache.cassandra.db.*; -import org.apache.cassandra.db.composites.CellName; -import org.apache.cassandra.db.composites.CellNameType; -import org.apache.cassandra.db.composites.Composite; -import org.apache.cassandra.db.composites.Composites; -import org.apache.cassandra.db.filter.ColumnSlice; -import org.apache.cassandra.db.filter.IDiskAtomFilter; -import org.apache.cassandra.db.filter.NamesQueryFilter; -import org.apache.cassandra.db.filter.SliceQueryFilter; +import org.apache.cassandra.db.composites.*; +import org.apache.cassandra.db.filter.*; import org.apache.cassandra.db.index.SecondaryIndexManager; import org.apache.cassandra.db.marshal.CollectionType; import org.apache.cassandra.db.marshal.CompositeType; @@ -118,6 +113,14 @@ public class SelectStatement implements CQLStatement || (limit != null && limit.usesFunction(ksName, functionName)); } + @Override + public Iterable<Function> getFunctions() + { + return Iterables.concat(selection.getFunctions(), + restrictions.getFunctions(), + limit != null ? limit.getFunctions() : Collections.<Function>emptySet()); + } + // Creates a simple select based on the given selection. // Note that the results select statement should not be used for actual queries, but only for processing already // queried data through processColumnFamily. @@ -146,6 +149,8 @@ public class SelectStatement implements CQLStatement public void checkAccess(ClientState state) throws InvalidRequestException, UnauthorizedException { state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.SELECT); + for (Function function : getFunctions()) + state.ensureHasPermission(Permission.EXECUTE, function); } public void validate(ClientState state) throws InvalidRequestException http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb5897f3/src/java/org/apache/cassandra/service/ClientState.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/service/ClientState.java b/src/java/org/apache/cassandra/service/ClientState.java index e2df4ff..b171f08 100644 --- a/src/java/org/apache/cassandra/service/ClientState.java +++ b/src/java/org/apache/cassandra/service/ClientState.java @@ -33,6 +33,7 @@ import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.QueryHandler; import org.apache.cassandra.cql3.QueryProcessor; +import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.db.SystemKeyspace; import org.apache.cassandra.exceptions.AuthenticationException; import org.apache.cassandra.exceptions.InvalidRequestException; @@ -260,6 +261,36 @@ public class ClientState public void ensureHasPermission(Permission perm, IResource resource) throws UnauthorizedException { + if (DatabaseDescriptor.getAuthorizer() instanceof AllowAllAuthorizer) + return; + + // Access to built in functions is unrestricted + if(resource instanceof FunctionResource && resource.hasParent()) + if (((FunctionResource)resource).getKeyspace().equals(SystemKeyspace.NAME)) + return; + + checkPermissionOnResourceChain(perm, resource); + } + + // Convenience method called from checkAccess method of CQLStatement + // Also avoids needlessly creating lots of FunctionResource objects + public void ensureHasPermission(Permission permission, Function function) + { + // Save creating a FunctionResource is we don't need to + if (DatabaseDescriptor.getAuthorizer() instanceof AllowAllAuthorizer) + return; + + // built in functions are always available to all + if (function.isNative()) + return; + + checkPermissionOnResourceChain(permission, FunctionResource.function(function.name().keyspace, + function.name().name, + function.argTypes())); + } + + private void checkPermissionOnResourceChain(Permission perm, IResource resource) + { for (IResource r : Resources.chain(resource)) if (authorize(r).contains(perm)) return; http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb5897f3/test/unit/org/apache/cassandra/cql3/UFAuthTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/UFAuthTest.java b/test/unit/org/apache/cassandra/cql3/UFAuthTest.java new file mode 100644 index 0000000..3f0768e --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/UFAuthTest.java @@ -0,0 +1,945 @@ +/* + * 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; + +import java.lang.reflect.Field; +import java.util.*; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.auth.*; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.cql3.functions.FunctionName; +import org.apache.cassandra.cql3.functions.Functions; +import org.apache.cassandra.cql3.statements.BatchStatement; +import org.apache.cassandra.cql3.statements.ModificationStatement; +import org.apache.cassandra.exceptions.*; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.utils.Pair; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class UFAuthTest extends CQLTester +{ + private static final Logger logger = LoggerFactory.getLogger(UFAuthTest.class); + + String roleName = "test_role"; + AuthenticatedUser user; + RoleResource role; + ClientState clientState; + + @BeforeClass + public static void setupAuthorizer() + { + try + { + IAuthorizer authorizer = new StubAuthorizer(); + Field authorizerField = DatabaseDescriptor.class.getDeclaredField("authorizer"); + authorizerField.setAccessible(true); + authorizerField.set(null, authorizer); + DatabaseDescriptor.setPermissionsValidity(0); + } + catch (IllegalAccessException | NoSuchFieldException e) + { + throw new RuntimeException(e); + } + } + + @Before + public void setup() throws Throwable + { + ((StubAuthorizer) DatabaseDescriptor.getAuthorizer()).clear(); + setupClientState(); + setupTable("CREATE TABLE %s (k int, v1 int, v2 int, PRIMARY KEY (k, v1))"); + } + + @Test + public void nonDeterministicFunctionInSelection() throws Throwable + { + String functionName = createSimpleFunction(false); + String cql = String.format("SELECT k, %s FROM %s WHERE k = 1;", + functionCall(functionName), + KEYSPACE + "." + currentTable()); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInSelection() throws Throwable + { + String functionName = createSimpleFunction(true); + String cql = String.format("SELECT k, %s FROM %s WHERE k = 1;", + functionCall(functionName), + KEYSPACE + "." + currentTable()); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void nonDeterministicFunctionInSelectPKRestriction() throws Throwable + { + String functionName = createSimpleFunction(false); + String cql = String.format("SELECT * FROM %s WHERE k = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInSelectPKRestriction() throws Throwable + { + String functionName = createSimpleFunction(true); + String cql = String.format("SELECT * FROM %s WHERE k = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void nonDeterministicFunctionInSelectClusteringRestriction() throws Throwable + { + String functionName = createSimpleFunction(false); + String cql = String.format("SELECT * FROM %s WHERE k = 0 AND v1 = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInSelectClusteringRestriction() throws Throwable + { + String functionName = createSimpleFunction(true); + String cql = String.format("SELECT * FROM %s WHERE k = 0 AND v1 = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void nonDeterministicFunctionInSelectInRestriction() throws Throwable + { + String functionName = createSimpleFunction(false); + String cql = String.format("SELECT * FROM %s WHERE k IN (%s, %s)", + KEYSPACE + "." + currentTable(), + functionCall(functionName), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInSelectInRestriction() throws Throwable + { + String functionName = createSimpleFunction(true); + String cql = String.format("SELECT * FROM %s WHERE k IN (%s, %s)", + KEYSPACE + "." + currentTable(), + functionCall(functionName), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void nonDeterministicFunctionInSelectMultiColumnInRestriction() throws Throwable + { + setupTable("CREATE TABLE %s (k int, v1 int, v2 int, v3 int, PRIMARY KEY (k, v1, v2))"); + String functionName = createSimpleFunction(false); + String cql = String.format("SELECT * FROM %s WHERE k=0 AND (v1, v2) IN ((%s, %s))", + KEYSPACE + "." + currentTable(), + functionCall(functionName), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInSelectMultiColumnInRestriction() throws Throwable + { + setupTable("CREATE TABLE %s (k int, v1 int, v2 int, v3 int, PRIMARY KEY (k, v1, v2))"); + String functionName = createSimpleFunction(true); + String cql = String.format("SELECT * FROM %s WHERE k=0 AND (v1, v2) IN ((%s, %s))", + KEYSPACE + "." + currentTable(), + functionCall(functionName), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + + @Test + public void nonDeterministicFunctionInSelectMultiColumnEQRestriction() throws Throwable + { + setupTable("CREATE TABLE %s (k int, v1 int, v2 int, v3 int, PRIMARY KEY (k, v1, v2))"); + String functionName = createSimpleFunction(false); + String cql = String.format("SELECT * FROM %s WHERE k=0 AND (v1, v2) = (%s, %s)", + KEYSPACE + "." + currentTable(), + functionCall(functionName), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInSelectMultiColumnEQRestriction() throws Throwable + { + setupTable("CREATE TABLE %s (k int, v1 int, v2 int, v3 int, PRIMARY KEY (k, v1, v2))"); + String functionName = createSimpleFunction(true); + String cql = String.format("SELECT * FROM %s WHERE k=0 AND (v1, v2) = (%s, %s)", + KEYSPACE + "." + currentTable(), + functionCall(functionName), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void nonDeterministicFunctionInSelectMultiColumnSliceRestriction() throws Throwable + { + setupTable("CREATE TABLE %s (k int, v1 int, v2 int, v3 int, PRIMARY KEY (k, v1, v2))"); + String functionName = createSimpleFunction(false); + String cql = String.format("SELECT * FROM %s WHERE k=0 AND (v1, v2) > (%s, %s)", + KEYSPACE + "." + currentTable(), + functionCall(functionName), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInSelectMultiColumnSliceRestriction() throws Throwable + { + setupTable("CREATE TABLE %s (k int, v1 int, v2 int, v3 int, PRIMARY KEY (k, v1, v2))"); + String functionName = createSimpleFunction(true); + String cql = String.format("SELECT * FROM %s WHERE k=0 AND (v1, v2) < (%s, %s)", + KEYSPACE + "." + currentTable(), + functionCall(functionName), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void nonDeterministicFunctionInSelectTokenEQRestriction() throws Throwable + { + String functionName = createSimpleFunction(false); + String cql = String.format("SELECT * FROM %s WHERE token(k) = token(%s)", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInSelectTokenEQRestriction() throws Throwable + { + String functionName = createSimpleFunction(true); + String cql = String.format("SELECT * FROM %s WHERE token(k) = token(%s)", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void nonDeterministicFunctionInSelectTokenSliceRestriction() throws Throwable + { + String functionName = createSimpleFunction(false); + String cql = String.format("SELECT * FROM %s WHERE token(k) > token(%s)", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInSelectTokenSliceRestriction() throws Throwable + { + String functionName = createSimpleFunction(true); + String cql = String.format("SELECT * FROM %s WHERE token(k) < token(%s)", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + @Test + public void nonDeterministicFunctionInPKForInsert() throws Throwable + { + String functionName = createSimpleFunction(false); + String cql = String.format("INSERT INTO %s (k, v1 ,v2) VALUES (%s, 0, 0)", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInPKForInsert() throws Throwable + { + String functionName = createSimpleFunction(true); + String cql = String.format("INSERT INTO %s (k, v1, v2) VALUES (%s, 0, 0)", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void nonDeterministicFunctionInClusteringValuesForInsert() throws Throwable + { + String functionName = createSimpleFunction(false); + String cql = String.format("INSERT INTO %s (k, v1, v2) VALUES (0, %s, 0)", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInClusteringValuesForInsert() throws Throwable + { + String functionName = createSimpleFunction(true); + String cql = String.format("INSERT INTO %s (k, v1, v2) VALUES (0, %s, 0)", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void nonDeterministicFunctionInPKForDelete() throws Throwable + { + String functionName = createSimpleFunction(false); + String cql = String.format("DELETE FROM %s WHERE k = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInPKForDelete() throws Throwable + { + String functionName = createSimpleFunction(true); + String cql = String.format("DELETE FROM %s WHERE k = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void nonDeterministicFunctionInClusteringValuesForDelete() throws Throwable + { + String functionName = createSimpleFunction(false); + String cql = String.format("DELETE FROM %s WHERE k = 0 AND v1 = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + + public void deterministicFunctionInClusteringValuesForDelete() throws Throwable + { + String functionName = createSimpleFunction(true); + String cql = String.format("DELETE FROM %s WHERE k = 0 AND v1 = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void testBatchStatement() throws Throwable + { + List<ModificationStatement> statements = new ArrayList<>(); + List<String> functions = new ArrayList<>(); + for (int i = 0; i < 3; i++) + { + String functionName = createSimpleFunction(false); + ModificationStatement stmt = + (ModificationStatement) getStatement(String.format("INSERT INTO %s (k, v1, v2) " + + "VALUES (%s, %s, %s)", + KEYSPACE + "." + currentTable(), + i, i, functionCall(functionName))); + functions.add(functionName); + statements.add(stmt); + } + BatchStatement batch = new BatchStatement(-1, BatchStatement.Type.LOGGED, statements, Attributes.none()); + assertUnauthorized(batch, functions); + + grantExecuteOnFunction(functions.get(0)); + assertUnauthorized(batch, functions.subList(1, functions.size())); + + grantExecuteOnFunction(functions.get(1)); + assertUnauthorized(batch, functions.subList(2, functions.size())); + + grantExecuteOnFunction(functions.get(2)); + batch.checkAccess(clientState); + } + + @Test + public void testNestedNonDeterministicFunctions() throws Throwable + { + String innerFunctionName = createSimpleFunction(false); + String outerFunctionName = createFunction("int", + "CREATE NON DETERMINISTIC FUNCTION %s(input int) " + + " RETURNS int" + + " LANGUAGE java" + + " AS 'return Integer.valueOf(0);'"); + assertPermissionsOnNestedFunctions(innerFunctionName, outerFunctionName); + } + + @Test + public void testNestedDeterministicFunctions() throws Throwable + { + String innerFunctionName = createSimpleFunction(true); + String outerFunctionName = createFunction("int", + "CREATE FUNCTION %s(input int) " + + " RETURNS int" + + " LANGUAGE java" + + " AS 'return Integer.valueOf(0);'"); + assertPermissionsOnNestedFunctions(innerFunctionName, outerFunctionName); + } + + @Test + public void testNestedMixedFunctions() throws Throwable + { + String innerFunctionName = createSimpleFunction(true); + String outerFunctionName = createFunction("int", + "CREATE NON DETERMINISTIC FUNCTION %s(input int) " + + " RETURNS int" + + " LANGUAGE java" + + " AS 'return Integer.valueOf(0);'"); + assertPermissionsOnNestedFunctions(innerFunctionName, outerFunctionName); + + innerFunctionName = createSimpleFunction(false); + outerFunctionName = createFunction("int", + "CREATE FUNCTION %s(input int) " + + " RETURNS int" + + " LANGUAGE java" + + " AS 'return Integer.valueOf(0);'"); + assertPermissionsOnNestedFunctions(innerFunctionName, outerFunctionName); + } + + @Test + public void nonDeterministicFunctionInStaticColumnRestrictionInSelect() throws Throwable + { + setupTable("CREATE TABLE %s (k int, s int STATIC, v1 int, v2 int, PRIMARY KEY(k, v1))"); + String functionName = createSimpleFunction(false); + String cql = String.format("SELECT k FROM %s WHERE k = 0 AND s = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInStaticColumnRestrictionInSelect() throws Throwable + { + setupTable("CREATE TABLE %s (k int, s int STATIC, v1 int, v2 int, PRIMARY KEY(k, v1))"); + String functionName = createSimpleFunction(true); + String cql = String.format("SELECT k FROM %s WHERE k = 0 AND s = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void nonDeterministicFunctionInRegularCondition() throws Throwable + { + String functionName = createSimpleFunction(false); + String cql = String.format("UPDATE %s SET v2 = 0 WHERE k = 0 AND v1 = 0 IF v2 = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInRegularCondition() throws Throwable + { + String functionName = createSimpleFunction(true); + String cql = String.format("UPDATE %s SET v2 = 0 WHERE k = 0 AND v1 = 0 IF v2 = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void nonDeterministicFunctionInStaticColumnCondition() throws Throwable + { + setupTable("CREATE TABLE %s (k int, s int STATIC, v1 int, v2 int, PRIMARY KEY(k, v1))"); + String functionName = createSimpleFunction(false); + String cql = String.format("UPDATE %s SET v2 = 0 WHERE k = 0 AND v1 = 0 IF s = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInStaticColumnCondition() throws Throwable + { + setupTable("CREATE TABLE %s (k int, s int STATIC, v1 int, v2 int, PRIMARY KEY(k, v1))"); + String functionName = createSimpleFunction(true); + String cql = String.format("UPDATE %s SET v2 = 0 WHERE k = 0 AND v1 = 0 IF s = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void nonDeterministicFunctionInCollectionLiteralCondition() throws Throwable + { + setupTable("CREATE TABLE %s (k int, v1 int, m_val map<int, int>, PRIMARY KEY(k))"); + String functionName = createSimpleFunction(false); + String cql = String.format("UPDATE %s SET v1 = 0 WHERE k = 0 IF m_val = {%s : %s}", + KEYSPACE + "." + currentTable(), + functionCall(functionName), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInCollectionLiteralCondition() throws Throwable + { + setupTable("CREATE TABLE %s (k int, v1 int, m_val map<int, int>, PRIMARY KEY(k))"); + String functionName = createSimpleFunction(true); + String cql = String.format("UPDATE %s SET v1 = 0 WHERE k = 0 IF m_val = {%s : %s}", + KEYSPACE + "." + currentTable(), + functionCall(functionName), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void nonDeterministicFunctionInCollectionElementCondition() throws Throwable + { + setupTable("CREATE TABLE %s (k int, v1 int, m_val map<int, int>, PRIMARY KEY(k))"); + String functionName = createSimpleFunction(false); + String cql = String.format("UPDATE %s SET v1 = 0 WHERE k = 0 IF m_val[%s] = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void deterministicFunctionInCollectionElementCondition() throws Throwable + { + setupTable("CREATE TABLE %s (k int, v1 int, m_val map<int, int>, PRIMARY KEY(k))"); + String functionName = createSimpleFunction(true); + String cql = String.format("UPDATE %s SET v1 = 0 WHERE k = 0 IF m_val[%s] = %s", + KEYSPACE + "." + currentTable(), + functionCall(functionName), + functionCall(functionName)); + assertPermissionsOnFunction(cql, functionName); + } + + @Test + public void systemFunctionsRequireNoExplicitPrivileges() throws Throwable + { + // with terminal arguments, so evaluated at prepare time + String cql = String.format("UPDATE %s SET v2 = 0 WHERE k = blobasint(intasblob(0))", + KEYSPACE + "." + currentTable()); + getStatement(cql).checkAccess(clientState); + + // with non-terminal arguments, so evaluated at execution + String functionName = createSimpleFunction(false); + grantExecuteOnFunction(functionName); + cql = String.format("UPDATE %s SET v2 = 0 WHERE k = blobasint(intasblob(%s))", + KEYSPACE + "." + currentTable(), + functionCall(functionName)); + getStatement(cql).checkAccess(clientState); + } + + @Test + public void requireExecutePermissionOnComponentFunctionsWhenDefiningAggregate() throws Throwable + { + String sFunc = createSimpleStateFunction(); + String fFunc = createSimpleFinalFunction(); + // aside from the component functions, we need CREATE on the keyspace's functions + DatabaseDescriptor.getAuthorizer().grant(AuthenticatedUser.SYSTEM_USER, + ImmutableSet.of(Permission.CREATE), + FunctionResource.keyspace(KEYSPACE), + role); + String aggDef = String.format(aggregateCql(sFunc, fFunc), + KEYSPACE + ".aggregate_for_permissions_test"); + + assertUnauthorized(aggDef, sFunc, "int, int"); + grantExecuteOnFunction(sFunc); + + assertUnauthorized(aggDef, fFunc, "int"); + grantExecuteOnFunction(fFunc); + + getStatement(aggDef).checkAccess(clientState); + } + + @Test + public void revokeExecutePermissionsOnAggregateComponents() throws Throwable + { + String sFunc = createSimpleStateFunction(); + String fFunc = createSimpleFinalFunction(); + String aggDef = aggregateCql(sFunc, fFunc); + grantExecuteOnFunction(sFunc); + grantExecuteOnFunction(fFunc); + + String aggregate = createAggregate(KEYSPACE, "int", aggDef); + grantExecuteOnFunction(aggregate); + + String cql = String.format("SELECT %s(v1) FROM %s", + aggregate, + KEYSPACE + "." + currentTable()); + getStatement(cql).checkAccess(clientState); + + // check that revoking EXECUTE permission on any one of the + // component functions means we lose the ability to execute it + revokeExecuteOnFunction(aggregate); + assertUnauthorized(cql, aggregate, "int"); + grantExecuteOnFunction(aggregate); + getStatement(cql).checkAccess(clientState); + + revokeExecuteOnFunction(sFunc); + assertUnauthorized(cql, sFunc, "int, int"); + grantExecuteOnFunction(sFunc); + getStatement(cql).checkAccess(clientState); + + revokeExecuteOnFunction(fFunc); + assertUnauthorized(cql, fFunc, "int"); + grantExecuteOnFunction(fFunc); + getStatement(cql).checkAccess(clientState); + } + + @Test + public void nonDeterministicFunctionWrappingAggregate() throws Throwable + { + String outerFunc = createFunction("int", + "CREATE NON DETERMINISTIC FUNCTION %s(input int) " + + "RETURNS int " + + "LANGUAGE java " + + "AS 'return input;'"); + + String sFunc = createSimpleStateFunction(); + String fFunc = createSimpleFinalFunction(); + String aggDef = aggregateCql(sFunc, fFunc); + grantExecuteOnFunction(sFunc); + grantExecuteOnFunction(fFunc); + + String aggregate = createAggregate(KEYSPACE, "int", aggDef); + + String cql = String.format("SELECT %s(%s(v1)) FROM %s", + outerFunc, + aggregate, + KEYSPACE + "." + currentTable()); + + assertUnauthorized(cql, outerFunc, "int"); + grantExecuteOnFunction(outerFunc); + + assertUnauthorized(cql, aggregate, "int"); + grantExecuteOnFunction(aggregate); + + getStatement(cql).checkAccess(clientState); + } + + @Test + public void aggregateWrappingNonDeterministicFunction() throws Throwable + { + String innerFunc = createFunction("int", + "CREATE NON DETERMINISTIC FUNCTION %s(input int) " + + "RETURNS int " + + "LANGUAGE java " + + "AS 'return input;'"); + + String sFunc = createSimpleStateFunction(); + String fFunc = createSimpleFinalFunction(); + String aggDef = aggregateCql(sFunc, fFunc); + grantExecuteOnFunction(sFunc); + grantExecuteOnFunction(fFunc); + + String aggregate = createAggregate(KEYSPACE, "int", aggDef); + + String cql = String.format("SELECT %s(%s(v1)) FROM %s", + aggregate, + innerFunc, + KEYSPACE + "." + currentTable()); + + assertUnauthorized(cql, aggregate, "int"); + grantExecuteOnFunction(aggregate); + + assertUnauthorized(cql, innerFunc, "int"); + grantExecuteOnFunction(innerFunc); + + getStatement(cql).checkAccess(clientState); + } + + private void assertPermissionsOnNestedFunctions(String innerFunction, String outerFunction) throws Throwable + { + String cql = String.format("SELECT k, %s FROM %s WHERE k=0", + functionCall(outerFunction, functionCall(innerFunction)), + KEYSPACE + "." + currentTable()); + // fail fast with an UAE on the first function + assertUnauthorized(cql, outerFunction, "int"); + grantExecuteOnFunction(outerFunction); + + // after granting execute on the first function, still fail due to the inner function + assertUnauthorized(cql, innerFunction, ""); + grantExecuteOnFunction(innerFunction); + + // now execution of both is permitted + getStatement(cql).checkAccess(clientState); + } + + private void assertPermissionsOnFunction(String cql, String functionName) throws Throwable + { + assertPermissionsOnFunction(cql, functionName, ""); + } + + private void assertPermissionsOnFunction(String cql, String functionName, String argTypes) throws Throwable + { + assertUnauthorized(cql, functionName, argTypes); + grantExecuteOnFunction(functionName); + getStatement(cql).checkAccess(clientState); + } + + private void assertUnauthorized(BatchStatement batch, Iterable<String> functionNames) throws Throwable + { + try + { + batch.checkAccess(clientState); + fail("Expected an UnauthorizedException, but none was thrown"); + } + catch (UnauthorizedException e) + { + String functions = String.format("(%s)", Joiner.on("|").join(functionNames)); + assertTrue(e.getLocalizedMessage() + .matches(String.format("User %s has no EXECUTE permission on <function %s\\(\\)> or any of its parents", + roleName, + functions))); + } + } + + private void assertUnauthorized(String cql, String functionName, String argTypes) throws Throwable + { + try + { + getStatement(cql).checkAccess(clientState); + fail("Expected an UnauthorizedException, but none was thrown"); + } + catch (UnauthorizedException e) + { + assertEquals(String.format("User %s has no EXECUTE permission on <function %s(%s)> or any of its parents", + roleName, + functionName, + argTypes), + e.getLocalizedMessage()); + } + } + + private void grantExecuteOnFunction(String functionName) + { + DatabaseDescriptor.getAuthorizer().grant(AuthenticatedUser.SYSTEM_USER, + ImmutableSet.of(Permission.EXECUTE), + functionResource(functionName), + role); + } + + private void revokeExecuteOnFunction(String functionName) + { + DatabaseDescriptor.getAuthorizer().revoke(AuthenticatedUser.SYSTEM_USER, + ImmutableSet.of(Permission.EXECUTE), + functionResource(functionName), + role); + } + + void setupClientState() + { + + try + { + role = RoleResource.role(roleName); + // use reflection to set the logged in user so that we don't need to + // bother setting up an IRoleManager + user = new AuthenticatedUser(roleName); + clientState = ClientState.forInternalCalls(); + Field userField = ClientState.class.getDeclaredField("user"); + userField.setAccessible(true); + userField.set(clientState, user); + } + catch (IllegalAccessException | NoSuchFieldException e) + { + throw new RuntimeException(e); + } + } + + private void setupTable(String tableDef) throws Throwable + { + createTable(tableDef); + // test user needs SELECT & MODIFY on the table regardless of permissions on any function + DatabaseDescriptor.getAuthorizer().grant(AuthenticatedUser.SYSTEM_USER, + ImmutableSet.of(Permission.SELECT, Permission.MODIFY), + DataResource.table(KEYSPACE, currentTable()), + RoleResource.role(user.getName())); + } + + private String aggregateCql(String sFunc, String fFunc) + { + return "CREATE AGGREGATE %s(int) " + + "SFUNC " + shortFunctionName(sFunc) + " " + + "STYPE int " + + "FINALFUNC " + shortFunctionName(fFunc) + " " + + "INITCOND 0"; + } + + private String createSimpleStateFunction() throws Throwable + { + return createFunction("int, int", + "CREATE FUNCTION %s(a int, b int) " + + "RETURNS int " + + "LANGUAGE java " + + "AS 'return Integer.valueOf( (a != null ? a.intValue() : 0 ) + b.intValue());'"); + } + + private String createSimpleFinalFunction() throws Throwable + { + return createFunction("int", + "CREATE FUNCTION %s(a int) " + + "RETURNS int " + + "LANGUAGE java " + + "AS 'return a;'"); + } + + private String createSimpleFunction(boolean deterministic) throws Throwable + { + return createFunction("", + "CREATE " + (deterministic ? "" : " NON ") + " DETERMINISTIC FUNCTION %s() " + + " RETURNS int " + + " LANGUAGE java " + + " AS 'return Integer.valueOf(0);'"); + } + + private String createFunction(String argTypes, String functionDef) throws Throwable + { + return createFunction(KEYSPACE, argTypes, functionDef); + } + + private CQLStatement getStatement(String cql) + { + return QueryProcessor.getStatement(cql, clientState).statement; + } + + private FunctionResource functionResource(String functionName) + { + // Note that this is somewhat brittle as it assumes that function names are + // truly unique. As such, it will break in the face of overloading. + // It is here to avoid having to duplicate the functionality of CqlParser + // for transforming cql types into AbstractTypes + FunctionName fn = parseFunctionName(functionName); + List<Function> functions = Functions.find(fn); + assertEquals(String.format("Expected a single function definition for %s, but found %s", + functionName, + functions.size()), + 1, functions.size()); + return FunctionResource.function(fn.keyspace, fn.name, functions.get(0).argTypes()); + } + + private String functionCall(String functionName, String...args) + { + return String.format("%s(%s)", functionName, Joiner.on(",").join(args)); + } + + static class StubAuthorizer implements IAuthorizer + { + Map<Pair<String, IResource>, Set<Permission>> userPermissions = new HashMap<>(); + + private void clear() + { + userPermissions.clear(); + } + + public Set<Permission> authorize(AuthenticatedUser user, IResource resource) + { + Pair<String, IResource> key = Pair.create(user.getName(), resource); + Set<Permission> perms = userPermissions.get(key); + return perms != null ? perms : Collections.<Permission>emptySet(); + } + + public void grant(AuthenticatedUser performer, + Set<Permission> permissions, + IResource resource, + RoleResource grantee) throws RequestValidationException, RequestExecutionException + { + Pair<String, IResource> key = Pair.create(grantee.getRoleName(), resource); + Set<Permission> perms = userPermissions.get(key); + if (null == perms) + { + perms = new HashSet<>(); + userPermissions.put(key, perms); + } + perms.addAll(permissions); + } + + public void revoke(AuthenticatedUser performer, + Set<Permission> permissions, + IResource resource, + RoleResource revokee) throws RequestValidationException, RequestExecutionException + { + Pair<String, IResource> key = Pair.create(revokee.getRoleName(), resource); + Set<Permission> perms = userPermissions.get(key); + if (null != perms) + perms.removeAll(permissions); + if (perms.isEmpty()) + userPermissions.remove(key); + } + + public Set<PermissionDetails> list(AuthenticatedUser performer, + Set<Permission> permissions, + IResource resource, + RoleResource grantee) throws RequestValidationException, RequestExecutionException + { + Pair<String, IResource> key = Pair.create(grantee.getRoleName(), resource); + Set<Permission> perms = userPermissions.get(key); + if (perms == null) + return Collections.emptySet(); + + + Set<PermissionDetails> details = new HashSet<>(); + for (Permission permission : perms) + { + if (permissions.contains(permission)) + details.add(new PermissionDetails(grantee.getRoleName(), resource, permission)); + } + return details; + } + + public void revokeAllFrom(RoleResource revokee) + { + for (Pair<String, IResource> key : userPermissions.keySet()) + if (key.left.equals(revokee.getRoleName())) + userPermissions.remove(key); + } + + public void revokeAllOn(IResource droppedResource) + { + for (Pair<String, IResource> key : userPermissions.keySet()) + if (key.right.equals(droppedResource)) + userPermissions.remove(key); + + } + + public Set<? extends IResource> protectedResources() + { + return Collections.emptySet(); + } + + public void validateConfiguration() throws ConfigurationException + { + + } + + public void setup() + { + + } + } +}
