This is an automated email from the ASF dual-hosted git repository.

samt pushed a commit to branch cassandra-4.1
in repository https://gitbox.apache.org/repos/asf/cassandra.git

commit d14d16926e52078eb86fb06bbc13715670c7c650
Merge: 18c777f641 1848efaf53
Author: Sam Tunnicliffe <[email protected]>
AuthorDate: Fri Jan 17 09:07:33 2025 +0000

    Merge branch 'cassandra-4.0' into cassandra-4.1

 CHANGES.txt                                        |   3 +
 src/java/org/apache/cassandra/auth/Permission.java |   7 ++
 src/java/org/apache/cassandra/auth/Resources.java  |  28 ++++-
 .../cql3/statements/GrantPermissionsStatement.java |  21 ++++
 .../apache/cassandra/schema/SchemaConstants.java   |  10 ++
 .../org/apache/cassandra/service/ClientState.java  |  60 ++++++++--
 .../apache/cassandra/auth/GrantAndRevokeTest.java  | 127 ++++++++++++++++++++-
 test/unit/org/apache/cassandra/cql3/CQLTester.java |   8 ++
 .../apache/cassandra/service/ClientStateTest.java  |   4 +-
 9 files changed, 248 insertions(+), 20 deletions(-)

diff --cc CHANGES.txt
index 48ba5a2bae,1ef6a902e4..17f31338ae
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@@ -19,10 -16,9 +19,13 @@@ Merged from 4.0
   * Fix text containing "/*" being interpreted as multiline comment in cqlsh 
(CASSANDRA-17667)
   * Fix indexing of a frozen collection that is the clustering key and 
reversed (CASSANDRA-19889)
   * Emit error when altering a table with non-frozen UDTs with nested 
non-frozen collections the same way as done upon table creation 
(CASSANDRA-19925)
++Merged from 3.0
++  * Tighten up permissions on system keyspaces (CASSANDRA-20090)
++  * Fix incorrect column identifier bytes problem when renaming a column 
(CASSANDRA-18956)
  
  
 -4.0.14
 +4.1.7
 +Merged from 4.0:
   * Safer handling of out-of-range tokens (CASSANDRA-13704)
   * Fix memory leak in BTree.FastBuilder (CASSANDRA-19785)
   * Fix millisecond and microsecond precision for commit log replay 
(CASSANDRA-19448)
diff --cc src/java/org/apache/cassandra/auth/Permission.java
index d552280e64,d552280e64..11c7aeb05b
--- a/src/java/org/apache/cassandra/auth/Permission.java
+++ b/src/java/org/apache/cassandra/auth/Permission.java
@@@ -66,4 -66,4 +66,11 @@@ public enum Permissio
      public static final Set<Permission> ALL =
              Sets.immutableEnumSet(EnumSet.range(Permission.CREATE, 
Permission.EXECUTE));
      public static final Set<Permission> NONE = ImmutableSet.of();
++
++    /**
++     * Set of Permissions which may never be granted on any system keyspace, 
or table in a system keyspace, to any role.
++     * (Only SELECT, DESCRIBE and ALTER may ever be granted).
++     */
++    public static final Set<Permission> INVALID_FOR_SYSTEM_KEYSPACES =
++            
Sets.immutableEnumSet(EnumSet.complementOf(EnumSet.of(Permission.SELECT, 
Permission.DESCRIBE, Permission.ALTER)));
  }
diff --cc src/java/org/apache/cassandra/auth/Resources.java
index 653cd46e32,653cd46e32..2863710632
--- a/src/java/org/apache/cassandra/auth/Resources.java
+++ b/src/java/org/apache/cassandra/auth/Resources.java
@@@ -19,6 -19,6 +19,7 @@@ package org.apache.cassandra.auth
  
  import java.util.ArrayList;
  import java.util.List;
++import java.util.function.Predicate;
  
  import org.apache.cassandra.utils.Hex;
  
@@@ -27,18 -27,18 +28,33 @@@ public final class Resource
      /**
       * Construct a chain of resource parents starting with the resource and 
ending with the root.
       *
--     * @param resource The staring point.
++     * @param resource The starting point.
       * @return list of resource in the chain form start to the root.
       */
      public static List<? extends IResource> chain(IResource resource)
      {
--        List<IResource> chain = new ArrayList<IResource>();
++        return chain(resource, (r) -> true);
++    }
++
++    /**
++     * Construct a chain of resource parents starting with the resource and 
ending with the root. Only resources which
++     * satisfy the supplied predicate will be included.
++     *
++     * @param resource The starting point.
++     * @param filter can be used to omit specific resources from the chain
++     * @return list of resource in the chain form start to the root.
++     */
++    public static List<? extends IResource> chain(IResource resource, 
Predicate<IResource> filter)
++    {
++
++        List<IResource> chain = new ArrayList<>(4);
          while (true)
          {
--           chain.add(resource);
--           if (!resource.hasParent())
--               break;
--           resource = resource.getParent();
++            if (filter.test(resource))
++                chain.add(resource);
++            if (!resource.hasParent())
++                break;
++            resource = resource.getParent();
          }
          return chain;
      }
diff --cc 
src/java/org/apache/cassandra/cql3/statements/GrantPermissionsStatement.java
index 824c4856d5,3db20e3841..5e165f5841
--- 
a/src/java/org/apache/cassandra/cql3/statements/GrantPermissionsStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/GrantPermissionsStatement.java
@@@ -17,20 -17,17 +17,24 @@@
   */
  package org.apache.cassandra.cql3.statements;
  
++import java.util.Collections;
  import java.util.Set;
 +import java.util.stream.Collectors;
  
  import org.apache.cassandra.audit.AuditLogContext;
  import org.apache.cassandra.audit.AuditLogEntryType;
++import org.apache.cassandra.auth.DataResource;
 +import org.apache.cassandra.auth.IAuthorizer;
  import org.apache.cassandra.auth.IResource;
  import org.apache.cassandra.auth.Permission;
  import org.apache.cassandra.config.DatabaseDescriptor;
  import org.apache.cassandra.cql3.RoleName;
  import org.apache.cassandra.exceptions.RequestExecutionException;
  import org.apache.cassandra.exceptions.RequestValidationException;
++import org.apache.cassandra.exceptions.UnauthorizedException;
++import org.apache.cassandra.schema.SchemaConstants;
  import org.apache.cassandra.service.ClientState;
 +import org.apache.cassandra.service.ClientWarn;
  import org.apache.cassandra.transport.messages.ResultMessage;
  
  public class GrantPermissionsStatement extends PermissionsManagementStatement
@@@ -40,27 -37,9 +44,44 @@@
          super(permissions, resource, grantee);
      }
  
++    public void validate(ClientState state) throws RequestValidationException
++    {
++        super.validate(state);
++        if (resource instanceof DataResource)
++        {
++            DataResource data = (DataResource) resource;
++            // Only a subset of permissions can be granted on non-virtual 
system keyspaces
++            if (!data.isRootLevel()
++                && 
SchemaConstants.isNonVirtualSystemKeyspace(data.getKeyspace())
++                && !Collections.disjoint(permissions, 
Permission.INVALID_FOR_SYSTEM_KEYSPACES))
++            {
++                throw new UnauthorizedException("Granting permissions on 
system keyspaces is strictly limited, " +
++                                                "this operation is not 
permitted");
++            }
++        }
++    }
++
      public ResultMessage execute(ClientState state) throws 
RequestValidationException, RequestExecutionException
      {
 -        DatabaseDescriptor.getAuthorizer().grant(state.getUser(), 
permissions, resource, grantee);
 +        IAuthorizer authorizer = DatabaseDescriptor.getAuthorizer();
 +        Set<Permission> granted = authorizer.grant(state.getUser(), 
permissions, resource, grantee);
 +
 +        // We want to warn the client if all the specified permissions have 
not been granted and the client did
 +        // not specify ALL in the query.
 +        if (!granted.equals(permissions) && 
!permissions.equals(Permission.ALL))
 +        {
 +            String permissionsStr = permissions.stream()
 +                                               .filter(permission -> 
!granted.contains(permission))
 +                                               .sorted(Permission::compareTo) 
// guarantee the order for testing
 +                                               .map(Permission::name)
 +                                               .collect(Collectors.joining(", 
"));
 +
 +            ClientWarn.instance.warn(String.format("Role '%s' was already 
granted %s on %s",
 +                                                   grantee.getRoleName(),
 +                                                   permissionsStr,
 +                                                   resource));
 +        }
 +
          return null;
      }
  
diff --cc src/java/org/apache/cassandra/schema/SchemaConstants.java
index 888ea4a31f,7b6b7de490..d4cf888a9f
--- a/src/java/org/apache/cassandra/schema/SchemaConstants.java
+++ b/src/java/org/apache/cassandra/schema/SchemaConstants.java
@@@ -112,16 -104,8 +112,26 @@@ public final class SchemaConstant
       */
      public static boolean isSystemKeyspace(String keyspaceName)
      {
 -        return isLocalSystemKeyspace(keyspaceName)
 -                || isReplicatedSystemKeyspace(keyspaceName)
 -                || isVirtualSystemKeyspace(keyspaceName);
 +        return isLocalSystemKeyspace(keyspaceName) // this includes vtables
 +                || isReplicatedSystemKeyspace(keyspaceName);
 +    }
 +
++    /**
++     * @return whether or not the keyspace is a non-virtual, system keyspace
++     */
++    public static boolean isNonVirtualSystemKeyspace(String keyspaceName)
++    {
++        final String lowercaseKeyspaceName = keyspaceName.toLowerCase();
++        return LOCAL_SYSTEM_KEYSPACE_NAMES.contains(lowercaseKeyspaceName)
++               || 
REPLICATED_SYSTEM_KEYSPACE_NAMES.contains(lowercaseKeyspaceName);
++    }
++
 +    /**
 +     * Returns the set of all system keyspaces
 +     * @return all system keyspaces
 +     */
 +    public static Set<String> getSystemKeyspaces()
 +    {
 +        return Sets.union(Sets.union(LOCAL_SYSTEM_KEYSPACE_NAMES, 
REPLICATED_SYSTEM_KEYSPACE_NAMES), VIRTUAL_SYSTEM_KEYSPACE_NAMES);
      }
  }
diff --cc src/java/org/apache/cassandra/service/ClientState.java
index 9e35c7f4b0,f76e7e3a4f..21243cd68c
--- a/src/java/org/apache/cassandra/service/ClientState.java
+++ b/src/java/org/apache/cassandra/service/ClientState.java
@@@ -21,18 -21,11 +21,18 @@@ import java.net.InetAddress
  import java.net.InetSocketAddress;
  import java.net.SocketAddress;
  import java.util.Arrays;
--import java.util.HashSet;
 +import java.util.List;
 +import java.util.Map;
  import java.util.Optional;
  import java.util.Set;
 +import java.util.concurrent.TimeUnit;
  import java.util.concurrent.atomic.AtomicLong;
  
 +import com.google.common.annotations.VisibleForTesting;
 +import com.google.common.collect.ImmutableMap;
++import com.google.common.collect.ImmutableSet;
 +import com.google.common.collect.Lists;
 +
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
@@@ -54,6 -47,6 +54,7 @@@ import org.apache.cassandra.dht.Datacen
  import org.apache.cassandra.exceptions.AuthenticationException;
  import org.apache.cassandra.exceptions.InvalidRequestException;
  import org.apache.cassandra.exceptions.UnauthorizedException;
++import org.apache.cassandra.tracing.TraceKeyspace;
  import org.apache.cassandra.utils.FBUtilities;
  import org.apache.cassandra.utils.JVMStabilityInspector;
  import org.apache.cassandra.utils.MD5Digest;
@@@ -67,29 -58,29 +68,39 @@@ public class ClientStat
  {
      private static final Logger logger = 
LoggerFactory.getLogger(ClientState.class);
  
--    private static final Set<IResource> READABLE_SYSTEM_RESOURCES = new 
HashSet<>();
--    private static final Set<IResource> PROTECTED_AUTH_RESOURCES = new 
HashSet<>();
++    public static final ImmutableSet<IResource> READABLE_SYSTEM_RESOURCES;
++    public static final ImmutableSet<IResource> PROTECTED_AUTH_RESOURCES;
  
      static
      {
          // We want these system cfs to be always readable to authenticated 
users since many tools rely on them
          // (nodetool, cqlsh, bulkloader, etc.)
--        for (String cf : Arrays.asList(SystemKeyspace.LOCAL, 
SystemKeyspace.LEGACY_PEERS, SystemKeyspace.PEERS_V2))
--            
READABLE_SYSTEM_RESOURCES.add(DataResource.table(SchemaConstants.SYSTEM_KEYSPACE_NAME,
 cf));
++        ImmutableSet.Builder<IResource> readableBuilder = 
ImmutableSet.builder();
++        for (String cf : Arrays.asList(SystemKeyspace.LOCAL, 
SystemKeyspace.LEGACY_PEERS, SystemKeyspace.PEERS_V2,
++                                       SystemKeyspace.LEGACY_SIZE_ESTIMATES, 
SystemKeyspace.TABLE_ESTIMATES))
++            
readableBuilder.add(DataResource.table(SchemaConstants.SYSTEM_KEYSPACE_NAME, 
cf));
  
          // make all schema tables readable by default (required by the 
drivers)
--        SchemaKeyspaceTables.ALL.forEach(table -> 
READABLE_SYSTEM_RESOURCES.add(DataResource.table(SchemaConstants.SCHEMA_KEYSPACE_NAME,
 table)));
++        SchemaKeyspaceTables.ALL.forEach(table -> 
readableBuilder.add(DataResource.table(SchemaConstants.SCHEMA_KEYSPACE_NAME, 
table)));
++
++        // make system_traces readable by all or else tracing will require 
explicit grants
++        
readableBuilder.add(DataResource.table(SchemaConstants.TRACE_KEYSPACE_NAME, 
TraceKeyspace.EVENTS));
++        
readableBuilder.add(DataResource.table(SchemaConstants.TRACE_KEYSPACE_NAME, 
TraceKeyspace.SESSIONS));
  
          // make all virtual schema tables readable by default as well
--        VirtualSchemaKeyspace.instance.tables().forEach(t -> 
READABLE_SYSTEM_RESOURCES.add(t.metadata().resource));
++        VirtualSchemaKeyspace.instance.tables().forEach(t -> 
readableBuilder.add(t.metadata().resource));
++        READABLE_SYSTEM_RESOURCES = readableBuilder.build();
  
++        ImmutableSet.Builder<IResource> protectedBuilder = 
ImmutableSet.builder();
          // neither clients nor tools need authentication/authorization
          if (DatabaseDescriptor.isDaemonInitialized())
          {
--            
PROTECTED_AUTH_RESOURCES.addAll(DatabaseDescriptor.getAuthenticator().protectedResources());
--            
PROTECTED_AUTH_RESOURCES.addAll(DatabaseDescriptor.getAuthorizer().protectedResources());
--            
PROTECTED_AUTH_RESOURCES.addAll(DatabaseDescriptor.getRoleManager().protectedResources());
++            
protectedBuilder.addAll(DatabaseDescriptor.getAuthenticator().protectedResources());
++            
protectedBuilder.addAll(DatabaseDescriptor.getAuthorizer().protectedResources());
++            
protectedBuilder.addAll(DatabaseDescriptor.getRoleManager().protectedResources());
          }
++
++        PROTECTED_AUTH_RESOURCES = protectedBuilder.build();
      }
  
      // Current user for the session
@@@ -425,12 -384,12 +436,16 @@@
  
          preventSystemKSSchemaModification(keyspace, resource, perm);
  
++        // Some system data is always readable
          if ((perm == Permission.SELECT) && 
READABLE_SYSTEM_RESOURCES.contains(resource))
              return;
  
++        // Modifications to any resource upon which the authenticator, 
authorizer or role manager depend should not be
++        // be performed by users
          if (PROTECTED_AUTH_RESOURCES.contains(resource))
              if ((perm == Permission.CREATE) || (perm == Permission.ALTER) || 
(perm == Permission.DROP))
                  throw new UnauthorizedException(String.format("%s schema is 
protected", resource));
++
          ensurePermission(perm, resource);
      }
  
@@@ -444,6 -403,6 +459,24 @@@
              if 
(((FunctionResource)resource).getKeyspace().equals(SchemaConstants.SYSTEM_KEYSPACE_NAME))
                  return;
  
++        if (resource instanceof DataResource && isOrdinaryUser())
++        {
++            DataResource dataResource = (DataResource)resource;
++            if (!dataResource.isRootLevel())
++            {
++                String keyspace = dataResource.getKeyspace();
++                // A user may have permissions granted on ALL KEYSPACES, but 
this should exclude system keyspaces. Any
++                // permission on those keyspaces or their tables must be 
granted to the user either explicitly or
++                // transitively. The set of grantable permissions for 
non-virtual system keyspaces is further limited,
++                // see the Permission enum for details.
++                if (SchemaConstants.isSystemKeyspace(keyspace))
++                {
++                    ensurePermissionOnResourceChain(perm, 
Resources.chain(dataResource, IResource::hasParent));
++                    return;
++                }
++            }
++        }
++
          ensurePermissionOnResourceChain(perm, resource);
      }
  
@@@ -466,11 -425,7 +499,16 @@@
  
      private void ensurePermissionOnResourceChain(Permission perm, IResource 
resource)
      {
-         List<? extends IResource> resources = Resources.chain(resource);
 -        for (IResource r : Resources.chain(resource))
++        ensurePermissionOnResourceChain(perm, Resources.chain(resource));
++    }
++
++    private void ensurePermissionOnResourceChain(Permission perm, List<? 
extends IResource> resources)
++    {
++        IResource resource = resources.get(0);
 +        if (DatabaseDescriptor.getAuthFromRoot())
 +            resources = Lists.reverse(resources);
 +
 +        for (IResource r : resources)
              if (authorize(r).contains(perm))
                  return;
  
diff --cc test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java
index 5c1c2a0298,0000000000..73923ea9d4
mode 100644,000000..100644
--- a/test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java
+++ b/test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java
@@@ -1,388 -1,0 +1,511 @@@
 +/*
 + * 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.auth;
 +
++import java.util.Arrays;
++import java.util.HashSet;
++import java.util.Set;
++
++import com.google.common.collect.Iterables;
 +import org.junit.After;
 +import org.junit.BeforeClass;
 +import org.junit.Test;
 +
 +import com.datastax.driver.core.ResultSet;
 +import org.apache.cassandra.Util;
 +import org.apache.cassandra.config.DatabaseDescriptor;
 +import org.apache.cassandra.cql3.CQLTester;
- 
++import org.apache.cassandra.db.ConsistencyLevel;
++import org.apache.cassandra.db.SystemKeyspace;
++import org.apache.cassandra.schema.Schema;
++import org.apache.cassandra.schema.SchemaConstants;
++import org.apache.cassandra.schema.TableMetadata;
++import org.apache.cassandra.transport.ProtocolVersion;
++
++import static java.lang.String.format;
++import static 
org.apache.cassandra.schema.SchemaConstants.LOCAL_SYSTEM_KEYSPACE_NAMES;
++import static 
org.apache.cassandra.schema.SchemaConstants.REPLICATED_SYSTEM_KEYSPACE_NAMES;
++import static 
org.apache.cassandra.schema.SchemaConstants.SCHEMA_KEYSPACE_NAME;
++import static 
org.apache.cassandra.schema.SchemaConstants.SYSTEM_KEYSPACE_NAME;
++import static org.apache.cassandra.schema.SchemaConstants.TRACE_KEYSPACE_NAME;
 +import static org.junit.Assert.assertTrue;
 +
 +public class GrantAndRevokeTest extends CQLTester
 +{
 +    private static final String user = "user";
 +    private static final String pass = "12345";
 +
 +    @BeforeClass
 +    public static void setUpClass()
 +    {
 +        DatabaseDescriptor.setPermissionsValidity(0);
 +        CQLTester.setUpClass();
 +        requireAuthentication();
 +        requireNetwork();
 +    }
 +
 +    @After
 +    public void tearDown() throws Throwable
 +    {
 +        useSuperUser();
 +        executeNet("DROP ROLE " + user);
 +    }
 +
 +    @Test
 +    public void testGrantedKeyspace() throws Throwable
 +    {
 +        useSuperUser();
 +
 +        executeNet(String.format("CREATE ROLE %s WITH LOGIN = TRUE AND 
password='%s'", user, pass));
 +        executeNet("GRANT CREATE ON KEYSPACE " + KEYSPACE_PER_TEST + " TO " + 
user);
 +        String table = KEYSPACE_PER_TEST + '.' + 
createTable(KEYSPACE_PER_TEST, "CREATE TABLE %s (pk int, ck int, val int, val_2 
text, PRIMARY KEY (pk, ck))");
 +        String index = KEYSPACE_PER_TEST + '.' + 
createIndex(KEYSPACE_PER_TEST, "CREATE INDEX ON %s (val_2)");
 +        String type = KEYSPACE_PER_TEST + '.' + createType(KEYSPACE_PER_TEST, 
"CREATE TYPE %s (a int, b text)");
 +        String mv = KEYSPACE_PER_TEST + ".ks_mv_01";
 +        executeNet("CREATE MATERIALIZED VIEW " + mv + " AS SELECT * FROM " + 
table + " WHERE val IS NOT NULL AND pk IS NOT NULL AND ck IS NOT NULL PRIMARY 
KEY (val, pk, ck)");
 +
 +        useUser(user, pass);
 +
 +        // ALTER and DROP tables created by somebody else
 +        // Spin assert for effective auth changes.
 +        final String spinAssertTable = table;
 +        Util.spinAssertEquals(false, () -> {
 +            try
 +            {
 +                assertUnauthorizedQuery("User user has no MODIFY permission 
on <table " + spinAssertTable + "> or any of its parents",
 +                                        formatQuery(KEYSPACE_PER_TEST, 
"INSERT INTO %s (pk, ck, val, val_2) VALUES (1, 1, 1, '1')"));
 +            }
 +            catch(Throwable e)
 +            {
 +                return true;
 +            }
 +            return false;
 +        }, 10);
 +        assertUnauthorizedQuery("User user has no MODIFY permission on <table 
" + table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "UPDATE %s SET 
val = 1 WHERE pk = 1 AND ck = 1"));
 +        assertUnauthorizedQuery("User user has no MODIFY permission on <table 
" + table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "DELETE FROM 
%s WHERE pk = 1 AND ck = 2"));
 +        assertUnauthorizedQuery("User user has no SELECT permission on <table 
" + table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "SELECT * FROM 
%s WHERE pk = 1 AND ck = 1"));
 +        assertUnauthorizedQuery("User user has no SELECT permission on <table 
" + table + "> or any of its parents",
 +                                "SELECT * FROM " + mv + " WHERE val = 1 AND 
pk = 1 AND ck = 1");
 +        assertUnauthorizedQuery("User user has no MODIFY permission on <table 
" + table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "TRUNCATE 
TABLE %s"));
 +        assertUnauthorizedQuery("User user has no ALTER permission on <table 
" + table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE 
%s ADD val_3 int"));
 +        assertUnauthorizedQuery("User user has no DROP permission on <table " 
+ table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "DROP TABLE 
%s"));
 +        assertUnauthorizedQuery("User user has no ALTER permission on <all 
tables in " + KEYSPACE_PER_TEST + "> or any of its parents",
 +                                "ALTER TYPE " + type + " ADD c bigint");
 +        assertUnauthorizedQuery("User user has no DROP permission on <all 
tables in " + KEYSPACE_PER_TEST + "> or any of its parents",
 +                                "DROP TYPE " + type);
 +        assertUnauthorizedQuery("User user has no ALTER permission on <table 
" + table + "> or any of its parents",
 +                                "DROP MATERIALIZED VIEW " + mv);
 +        assertUnauthorizedQuery("User user has no ALTER permission on <table 
" + table + "> or any of its parents",
 +                                "DROP INDEX " + index);
 +
 +
 +        useSuperUser();
 +
 +        executeNet("GRANT ALTER, DROP, SELECT, MODIFY ON KEYSPACE " + 
KEYSPACE_PER_TEST + " TO " + user);
 +
 +        useUser(user, pass);
 +
 +        // Spin assert for effective auth changes.
 +        Util.spinAssertEquals(false, () -> {
 +            try
 +            {
 +                executeNet("ALTER KEYSPACE " + KEYSPACE_PER_TEST + " WITH 
replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}");
 +            }
 +            catch(Throwable e)
 +            {
 +                return true;
 +            }
 +            return false;
 +        }, 10);
 +
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "INSERT INTO %s (pk, ck, 
val, val_2) VALUES (1, 1, 1, '1')"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "UPDATE %s SET val = 1 
WHERE pk = 1 AND ck = 1"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "DELETE FROM %s WHERE pk = 
1 AND ck = 2"));
 +        assertRowsNet(executeNet(formatQuery(KEYSPACE_PER_TEST, "SELECT * 
FROM %s WHERE pk = 1 AND ck = 1")), row(1, 1, 1, "1"));
 +        assertRowsNet(executeNet("SELECT * FROM " + mv + " WHERE val = 1 AND 
pk = 1"), row(1, 1, 1, "1"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "TRUNCATE TABLE %s"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE %s ADD val_3 
int"));
 +        executeNet("DROP MATERIALIZED VIEW " + mv);
 +        executeNet("DROP INDEX " + index);
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "DROP TABLE %s"));
 +        executeNet("ALTER TYPE " + type + " ADD c bigint");
 +        executeNet("DROP TYPE " + type);
 +
 +        // calling creatTableName to create a new table name that will be 
used by the formatQuery
 +        table = createTableName();
 +        type = KEYSPACE_PER_TEST + "." + createTypeName();
 +        mv = KEYSPACE_PER_TEST + ".ks_mv_02";
 +        executeNet("CREATE TYPE " + type + " (a int, b text)");
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "CREATE TABLE %s (pk int, 
ck int, val int, val_2 text, PRIMARY KEY (pk, ck))"));
 +        executeNet("CREATE MATERIALIZED VIEW " + mv + " AS SELECT * FROM " + 
table + " WHERE val IS NOT NULL AND pk IS NOT NULL AND ck IS NOT NULL PRIMARY 
KEY (val, pk, ck)");
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "INSERT INTO %s (pk, ck, 
val, val_2) VALUES (1, 1, 1, '1')"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "UPDATE %s SET val = 1 
WHERE pk = 1 AND ck = 1"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "DELETE FROM %s WHERE pk = 
1 AND ck = 2"));
 +        assertRowsNet(executeNet(formatQuery(KEYSPACE_PER_TEST, "SELECT * 
FROM %s WHERE pk = 1 AND ck = 1")), row(1, 1, 1, "1"));
 +        assertRowsNet(executeNet("SELECT * FROM " + mv + " WHERE val = 1 AND 
pk = 1"), row(1, 1, 1, "1"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "TRUNCATE TABLE %s"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE %s ADD val_3 
int"));
 +        executeNet("DROP MATERIALIZED VIEW " + mv);
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "DROP TABLE %s"));
 +        executeNet("ALTER TYPE " + type + " ADD c bigint");
 +        executeNet("DROP TYPE " + type);
 +
 +        useSuperUser();
 +
 +        executeNet("REVOKE ALTER, DROP, MODIFY, SELECT ON KEYSPACE " + 
KEYSPACE_PER_TEST + " FROM " + user);
 +
 +        table = KEYSPACE_PER_TEST + "." + createTable(KEYSPACE_PER_TEST, 
"CREATE TABLE %s (pk int, ck int, val int, val_2 text, PRIMARY KEY (pk, ck))");
 +        type = KEYSPACE_PER_TEST + "." + createType(KEYSPACE_PER_TEST, 
"CREATE TYPE %s (a int, b text)");
 +        index = KEYSPACE_PER_TEST + '.' + createIndex(KEYSPACE_PER_TEST, 
"CREATE INDEX ON %s (val_2)");
 +        mv = KEYSPACE_PER_TEST + ".ks_mv_03";
 +        executeNet("CREATE MATERIALIZED VIEW " + mv + " AS SELECT * FROM " + 
table + " WHERE val IS NOT NULL AND pk IS NOT NULL AND ck IS NOT NULL PRIMARY 
KEY (val, pk, ck)");
 +
 +        useUser(user, pass);
 +
 +        // Spin assert for effective auth changes.
 +        final String spinAssertTable2 = table;
 +        Util.spinAssertEquals(false, () -> {
 +            try
 +            {
 +                assertUnauthorizedQuery("User user has no MODIFY permission 
on <table " + spinAssertTable2 + "> or any of its parents",
 +                                        "INSERT INTO " + spinAssertTable2 + " 
(pk, ck, val, val_2) VALUES (1, 1, 1, '1')");
 +            }
 +            catch(Throwable e)
 +            {
 +                return true;
 +            }
 +            return false;
 +        }, 10);
 +        assertUnauthorizedQuery("User user has no MODIFY permission on <table 
" + table + "> or any of its parents",
 +                                "UPDATE " + table + " SET val = 1 WHERE pk = 
1 AND ck = 1");
 +        assertUnauthorizedQuery("User user has no MODIFY permission on <table 
" + table + "> or any of its parents",
 +                                "DELETE FROM " + table + " WHERE pk = 1 AND 
ck = 2");
 +        assertUnauthorizedQuery("User user has no SELECT permission on <table 
" + table + "> or any of its parents",
 +                                "SELECT * FROM " + table + " WHERE pk = 1 AND 
ck = 1");
 +        assertUnauthorizedQuery("User user has no SELECT permission on <table 
" + table + "> or any of its parents",
 +                                "SELECT * FROM " + mv + " WHERE val = 1 AND 
pk = 1 AND ck = 1");
 +        assertUnauthorizedQuery("User user has no MODIFY permission on <table 
" + table + "> or any of its parents",
 +                                "TRUNCATE TABLE " + table);
 +        assertUnauthorizedQuery("User user has no ALTER permission on <table 
" + table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE " 
+ table + " ADD val_3 int"));
 +        assertUnauthorizedQuery("User user has no DROP permission on <table " 
+ table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "DROP TABLE " 
+ table));
 +        assertUnauthorizedQuery("User user has no ALTER permission on <all 
tables in " + KEYSPACE_PER_TEST + "> or any of its parents",
 +                                "ALTER TYPE " + type + " ADD c bigint");
 +        assertUnauthorizedQuery("User user has no DROP permission on <all 
tables in " + KEYSPACE_PER_TEST + "> or any of its parents",
 +                                "DROP TYPE " + type);
 +        assertUnauthorizedQuery("User user has no ALTER permission on <table 
" + table + "> or any of its parents",
 +                                "DROP MATERIALIZED VIEW " + mv);
 +        assertUnauthorizedQuery("User user has no ALTER permission on <table 
" + table + "> or any of its parents",
 +                                "DROP INDEX " + index);
- 
 +    }
 +
 +    @Test
 +    public void testGrantedAllTables() throws Throwable
 +    {
 +        useSuperUser();
 +
 +        executeNet(String.format("CREATE ROLE %s WITH LOGIN = TRUE AND 
password='%s'", user, pass));
 +        executeNet("GRANT CREATE ON ALL TABLES IN KEYSPACE " + 
KEYSPACE_PER_TEST + " TO " + user);
 +        String table = KEYSPACE_PER_TEST + "." + 
createTable(KEYSPACE_PER_TEST, "CREATE TABLE %s (pk int, ck int, val int, val_2 
text, PRIMARY KEY (pk, ck))");
 +        String index = KEYSPACE_PER_TEST + '.' + 
createIndex(KEYSPACE_PER_TEST, "CREATE INDEX ON %s (val_2)");
 +        String type = KEYSPACE_PER_TEST + "." + createType(KEYSPACE_PER_TEST, 
"CREATE TYPE %s (a int, b text)");
 +        String mv = KEYSPACE_PER_TEST + ".alltables_mv_01";
 +        executeNet("CREATE MATERIALIZED VIEW " + mv + " AS SELECT * FROM " + 
table + " WHERE val IS NOT NULL AND pk IS NOT NULL AND ck IS NOT NULL PRIMARY 
KEY (val, pk, ck)");
 +
 +        useUser(user, pass);
 +
 +        // ALTER and DROP tables created by somebody else
 +        // Spin assert for effective auth changes.
 +        final String spinAssertTable = table;
 +        Util.spinAssertEquals(false, () -> {
 +            try
 +            {
 +                assertUnauthorizedQuery("User user has no MODIFY permission 
on <table " + spinAssertTable + "> or any of its parents",
 +                                        formatQuery(KEYSPACE_PER_TEST, 
"INSERT INTO %s (pk, ck, val, val_2) VALUES (1, 1, 1, '1')"));
 +            }
 +            catch(Throwable e)
 +            {
 +                return true;
 +            }
 +            return false;
 +        }, 10);
 +        assertUnauthorizedQuery("User user has no MODIFY permission on <table 
" + table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "UPDATE %s SET 
val = 1 WHERE pk = 1 AND ck = 1"));
 +        assertUnauthorizedQuery("User user has no MODIFY permission on <table 
" + table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "DELETE FROM 
%s WHERE pk = 1 AND ck = 2"));
 +        assertUnauthorizedQuery("User user has no SELECT permission on <table 
" + table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "SELECT * FROM 
%s WHERE pk = 1 AND ck = 1"));
 +        assertUnauthorizedQuery("User user has no SELECT permission on <table 
" + table + "> or any of its parents",
 +                                "SELECT * FROM " + mv + " WHERE val = 1 AND 
pk = 1 AND ck = 1");
 +        assertUnauthorizedQuery("User user has no MODIFY permission on <table 
" + table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "TRUNCATE 
TABLE %s"));
 +        assertUnauthorizedQuery("User user has no ALTER permission on <table 
" + table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE 
%s ADD val_3 int"));
 +        assertUnauthorizedQuery("User user has no DROP permission on <table " 
+ table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "DROP TABLE 
%s"));
 +        assertUnauthorizedQuery("User user has no ALTER permission on <all 
tables in " + KEYSPACE_PER_TEST + "> or any of its parents",
 +                                "ALTER TYPE " + type + " ADD c bigint");
 +        assertUnauthorizedQuery("User user has no DROP permission on <all 
tables in " + KEYSPACE_PER_TEST + "> or any of its parents",
 +                                "DROP TYPE " + type);
 +        assertUnauthorizedQuery("User user has no ALTER permission on <table 
" + table + "> or any of its parents",
 +                                "DROP MATERIALIZED VIEW " + mv);
 +        assertUnauthorizedQuery("User user has no ALTER permission on <table 
" + table + "> or any of its parents",
 +                                "DROP INDEX " + index);
 +
 +        useSuperUser();
 +
 +        executeNet("GRANT ALTER, DROP, SELECT, MODIFY ON ALL TABLES IN 
KEYSPACE " + KEYSPACE_PER_TEST + " TO " + user);
 +
 +        useUser(user, pass);
 +
 +        // Spin assert for effective auth changes.
 +        Util.spinAssertEquals(false, () -> {
 +            try
 +            {
 +                assertUnauthorizedQuery("User user has no ALTER permission on 
<keyspace " + KEYSPACE_PER_TEST + "> or any of its parents",
 +                                        "ALTER KEYSPACE " + KEYSPACE_PER_TEST 
+ " WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}");
 +            }
 +            catch(Throwable e)
 +            {
 +                return true;
 +            }
 +            return false;
 +        }, 10);
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "INSERT INTO %s (pk, ck, 
val, val_2) VALUES (1, 1, 1, '1')"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "UPDATE %s SET val = 1 
WHERE pk = 1 AND ck = 1"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "DELETE FROM %s WHERE pk = 
1 AND ck = 2"));
 +        assertRowsNet(executeNet(formatQuery(KEYSPACE_PER_TEST, "SELECT * 
FROM %s WHERE pk = 1 AND ck = 1")), row(1, 1, 1, "1"));
 +        assertRowsNet(executeNet("SELECT * FROM " + mv + " WHERE val = 1 AND 
pk = 1"), row(1, 1, 1, "1"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "TRUNCATE TABLE %s"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE %s ADD val_3 
int"));
 +        executeNet("DROP MATERIALIZED VIEW " + mv);
 +        executeNet("DROP INDEX " + index);
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "DROP TABLE %s"));
 +        executeNet("ALTER TYPE " + type + " ADD c bigint");
 +        executeNet("DROP TYPE " + type);
 +
 +        // calling creatTableName to create a new table name that will be 
used by the formatQuery
 +        table = createTableName();
 +        type = KEYSPACE_PER_TEST + "." + createTypeName();
 +        mv = KEYSPACE_PER_TEST + ".alltables_mv_02";
 +        executeNet("CREATE TYPE " + type + " (a int, b text)");
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "CREATE TABLE %s (pk int, 
ck int, val int, val_2 text, PRIMARY KEY (pk, ck))"));
 +        index = KEYSPACE_PER_TEST + '.' + createIndex(KEYSPACE_PER_TEST, 
"CREATE INDEX ON %s (val_2)");
 +        executeNet("CREATE MATERIALIZED VIEW " + mv + " AS SELECT * FROM " + 
table + " WHERE val IS NOT NULL AND pk IS NOT NULL AND ck IS NOT NULL PRIMARY 
KEY (val, pk, ck)");
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "INSERT INTO %s (pk, ck, 
val, val_2) VALUES (1, 1, 1, '1')"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "UPDATE %s SET val = 1 
WHERE pk = 1 AND ck = 1"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "DELETE FROM %s WHERE pk = 
1 AND ck = 2"));
 +        assertRowsNet(executeNet(formatQuery(KEYSPACE_PER_TEST, "SELECT * 
FROM %s WHERE pk = 1 AND ck = 1")), row(1, 1, 1, "1"));
 +        assertRowsNet(executeNet("SELECT * FROM " + mv + " WHERE val = 1 AND 
pk = 1"), row(1, 1, 1, "1"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "TRUNCATE TABLE %s"));
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE %s ADD val_3 
int"));
 +        executeNet("DROP MATERIALIZED VIEW " + mv);
 +        executeNet("DROP INDEX " + index);
 +        executeNet(formatQuery(KEYSPACE_PER_TEST, "DROP TABLE %s"));
 +        executeNet("ALTER TYPE " + type + " ADD c bigint");
 +        executeNet("DROP TYPE " + type);
 +
 +        useSuperUser();
 +
 +        executeNet("REVOKE ALTER, DROP, SELECT, MODIFY ON ALL TABLES IN 
KEYSPACE " + KEYSPACE_PER_TEST + " FROM " + user);
 +
 +        table = KEYSPACE_PER_TEST + "." + createTable(KEYSPACE_PER_TEST, 
"CREATE TABLE %s (pk int, ck int, val int, val_2 text, PRIMARY KEY (pk, ck))");
 +        index = KEYSPACE_PER_TEST + '.' + createIndex(KEYSPACE_PER_TEST, 
"CREATE INDEX ON %s (val_2)");
 +        type = KEYSPACE_PER_TEST + "." + createType(KEYSPACE_PER_TEST, 
"CREATE TYPE %s (a int, b text)");
 +        mv = KEYSPACE_PER_TEST + ".alltables_mv_03";
 +        executeNet("CREATE MATERIALIZED VIEW " + mv + " AS SELECT * FROM " + 
table + " WHERE val IS NOT NULL AND pk IS NOT NULL AND ck IS NOT NULL PRIMARY 
KEY (val, pk, ck)");
 +
 +        useUser(user, pass);
 +
 +        // Spin assert for effective auth changes.
 +        final String spinAssertTable2 = table;
 +        Util.spinAssertEquals(false, () -> {
 +            try
 +            {
 +                assertUnauthorizedQuery("User user has no MODIFY permission 
on <table " + spinAssertTable2 + "> or any of its parents",
 +                                        "INSERT INTO " + spinAssertTable2 + " 
(pk, ck, val, val_2) VALUES (1, 1, 1, '1')");
 +            }
 +            catch(Throwable e)
 +            {
 +                return true;
 +            }
 +            return false;
 +        }, 10);
 +        assertUnauthorizedQuery("User user has no MODIFY permission on <table 
" + table + "> or any of its parents",
 +                                "UPDATE " + table + " SET val = 1 WHERE pk = 
1 AND ck = 1");
 +        assertUnauthorizedQuery("User user has no MODIFY permission on <table 
" + table + "> or any of its parents",
 +                                "DELETE FROM " + table + " WHERE pk = 1 AND 
ck = 2");
 +        assertUnauthorizedQuery("User user has no SELECT permission on <table 
" + table + "> or any of its parents",
 +                                "SELECT * FROM " + table + " WHERE pk = 1 AND 
ck = 1");
 +        assertUnauthorizedQuery("User user has no SELECT permission on <table 
" + table + "> or any of its parents",
 +                                "SELECT * FROM " + mv + " WHERE val = 1 AND 
pk = 1 AND ck = 1");
 +        assertUnauthorizedQuery("User user has no MODIFY permission on <table 
" + table + "> or any of its parents",
 +                                "TRUNCATE TABLE " + table);
 +        assertUnauthorizedQuery("User user has no ALTER permission on <table 
" + table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "ALTER TABLE " 
+ table + " ADD val_3 int"));
 +        assertUnauthorizedQuery("User user has no DROP permission on <table " 
+ table + "> or any of its parents",
 +                                formatQuery(KEYSPACE_PER_TEST, "DROP TABLE " 
+ table));
 +        assertUnauthorizedQuery("User user has no ALTER permission on <all 
tables in " + KEYSPACE_PER_TEST + "> or any of its parents",
 +                                "ALTER TYPE " + type + " ADD c bigint");
 +        assertUnauthorizedQuery("User user has no DROP permission on <all 
tables in " + KEYSPACE_PER_TEST + "> or any of its parents",
 +                                "DROP TYPE " + type);
 +        assertUnauthorizedQuery("User user has no ALTER permission on <table 
" + table + "> or any of its parents",
 +                                "DROP MATERIALIZED VIEW " + mv);
 +        assertUnauthorizedQuery("User user has no ALTER permission on <table 
" + table + "> or any of its parents",
 +                                "DROP INDEX " + index);
 +    }
 +
 +    @Test
 +    public void testWarnings() throws Throwable
 +    {
 +        useSuperUser();
 +
 +        executeNet("CREATE KEYSPACE revoke_yeah WITH replication = {'class': 
'SimpleStrategy', 'replication_factor': '1'}");
 +        executeNet("CREATE TABLE revoke_yeah.t1 (id int PRIMARY KEY, val 
text)");
 +        executeNet("CREATE USER '" + user + "' WITH PASSWORD '" + pass + "'");
 +
 +        ResultSet res = executeNet("REVOKE CREATE ON KEYSPACE revoke_yeah 
FROM " + user);
 +        assertWarningsContain(res.getExecutionInfo().getWarnings(), "Role '" 
+ user + "' was not granted CREATE on <keyspace revoke_yeah>");
 +
 +        res = executeNet("GRANT SELECT ON KEYSPACE revoke_yeah TO " + user);
 +        assertTrue(res.getExecutionInfo().getWarnings().isEmpty());
 +
 +        res = executeNet("GRANT SELECT ON KEYSPACE revoke_yeah TO " + user);
 +        assertWarningsContain(res.getExecutionInfo().getWarnings(), "Role '" 
+ user + "' was already granted SELECT on <keyspace revoke_yeah>");
 +
 +        res = executeNet("REVOKE SELECT ON TABLE revoke_yeah.t1 FROM " + 
user);
 +        assertWarningsContain(res.getExecutionInfo().getWarnings(), "Role '" 
+ user + "' was not granted SELECT on <table revoke_yeah.t1>");
 +
 +        res = executeNet("REVOKE SELECT, MODIFY ON KEYSPACE revoke_yeah FROM 
" + user);
 +        assertWarningsContain(res.getExecutionInfo().getWarnings(), "Role '" 
+ user + "' was not granted MODIFY on <keyspace revoke_yeah>");
 +    }
++
++    @Test
++    public void testSpecificGrantsOnSystemKeyspaces() throws Throwable
++    {
++        // Granting specific permissions on system keyspaces should not be 
allowed if those permissions include any from
++        // the denylist Permission.INVALID_FOR_SYSTEM_KEYSPACES. By this 
definition, GRANT ALL on any system keyspace,
++        // or a table within one, should be rejected.
++        useSuperUser();
++        executeNet("CREATE ROLE '" + user + "'");
++        String responseMsg = "Granting permissions on system keyspaces is 
strictly limited, this operation is not permitted";
++        for (String keyspace : Iterables.concat(LOCAL_SYSTEM_KEYSPACE_NAMES, 
REPLICATED_SYSTEM_KEYSPACE_NAMES))
++        {
++            assertUnauthorizedQuery(responseMsg, format("GRANT ALL 
PERMISSIONS ON KEYSPACE %s TO %s", keyspace, user));
++            DataResource keyspaceResource = DataResource.keyspace(keyspace);
++            for (Permission p : keyspaceResource.applicablePermissions())
++                maybeRejectGrant(p, responseMsg, format("GRANT %s ON KEYSPACE 
%s TO %s", p.name(), keyspace, user));
++
++            assertUnauthorizedQuery(responseMsg, format("GRANT ALL 
PERMISSIONS ON ALL TABLES IN KEYSPACE %s TO %s", keyspace, user));
++            for (TableMetadata table : 
Schema.instance.getKeyspaceMetadata(keyspace).tables)
++            {
++                DataResource tableResource = DataResource.table(keyspace, 
table.name);
++                assertUnauthorizedQuery(responseMsg, format("GRANT ALL 
PERMISSIONS ON %s TO %s", table, user));
++                for (Permission p : tableResource.applicablePermissions())
++                    maybeRejectGrant(p, responseMsg, format("GRANT %s ON %s 
TO %s", p.name(), table, user));
++            }
++        }
++    }
++
++    @Test
++    public void testGrantOnAllKeyspaces() throws Throwable
++    {
++        // Granting either specific or ALL permissions on ALL KEYSPACES is 
allowed, however these permissions are
++        // effective for non-system keyspaces only. If for any reason it is 
necessary to modify permissions on
++        // on a system keyspace, it must be done using keyspace specific 
grant statements.
++        useSuperUser();
++        executeNet(String.format("CREATE ROLE %s WITH LOGIN = TRUE AND 
password='%s'", user, pass));
++        executeNet(String.format("ALTER KEYSPACE %s WITH replication = 
{'class': 'SimpleStrategy', 'replication_factor': '1'}", 
SchemaConstants.TRACE_KEYSPACE_NAME));
++        executeNet("CREATE KEYSPACE user_keyspace WITH replication = 
{'class': 'SimpleStrategy', 'replication_factor': '1'}");
++        executeNet("CREATE TABLE user_keyspace.t1 (k int PRIMARY KEY)");
++        useUser(user, pass);
++
++        assertUnauthorizedQuery("User user has no MODIFY permission on <table 
user_keyspace.t1> or any of its parents",
++                                "INSERT INTO user_keyspace.t1 (k) VALUES 
(0)");
++        assertUnauthorizedQuery("User user has no MODIFY permission on <table 
system.local> or any of its parents",
++                                "INSERT INTO system.local(key) VALUES 
('invalid')");
++
++        useSuperUser();
++        executeNet(ProtocolVersion.CURRENT, format("GRANT MODIFY ON ALL 
KEYSPACES TO %s", user));
++
++        useUser(user, pass);
++        // User now has write permission on non-system keyspaces only
++        executeNet(ProtocolVersion.CURRENT, "INSERT INTO user_keyspace.t1 (k) 
VALUES (0)");
++        assertUnauthorizedQuery("User user has no MODIFY permission on <table 
system.local> or any of its parents",
++                                "INSERT INTO system.local(key) VALUES 
('invalid')");
++
++        // A non-superuser only has read access to a pre-defined set of 
system tables and all system_schema/traces
++        // tables and granting ALL permissions on ALL keyspaces also does not 
affect this.
++        maybeReadSystemTables(false);
++        useSuperUser();
++        executeNet(ProtocolVersion.CURRENT, format("GRANT ALL PERMISSIONS ON 
ALL KEYSPACES TO %s", user));
++        maybeReadSystemTables(false);
++
++        // A superuser can still read system tables
++        useSuperUser();
++        maybeReadSystemTables(true);
++        // and also write to them, though this is still strongly discouraged
++        executeNet(ProtocolVersion.CURRENT, "INSERT INTO 
system.peers_v2(peer, peer_port, data_center) VALUES ('127.0.100.100', 7012, 
'invalid_dc')");
++    }
++
++    private void maybeReadSystemTables(boolean superuser) throws Throwable
++    {
++        if (superuser)
++            useSuperUser();
++        else
++            useUser(user, pass);
++
++        Set<String> readableKeyspaces = new 
HashSet<>(Arrays.asList(SCHEMA_KEYSPACE_NAME, TRACE_KEYSPACE_NAME));
++        Set<String> readableSystemTables = new 
HashSet<>(Arrays.asList(SystemKeyspace.LOCAL,
++                                                                       
SystemKeyspace.PEERS_V2,
++                                                                       
SystemKeyspace.LEGACY_PEERS,
++                                                                       
SystemKeyspace.LEGACY_SIZE_ESTIMATES,
++                                                                       
SystemKeyspace.TABLE_ESTIMATES));
++
++        for (String keyspace : Iterables.concat(LOCAL_SYSTEM_KEYSPACE_NAMES, 
REPLICATED_SYSTEM_KEYSPACE_NAMES))
++        {
++            for (TableMetadata table : 
Schema.instance.getKeyspaceMetadata(keyspace).tables)
++            {
++                if (superuser || (readableKeyspaces.contains(keyspace) || 
(keyspace.equals(SYSTEM_KEYSPACE_NAME) && 
readableSystemTables.contains(table.name))))
++                {
++                    executeNet(ProtocolVersion.CURRENT, ConsistencyLevel.ONE, 
format("SELECT * FROM %s LIMIT 1", table));
++                }
++                else
++                {
++                    assertUnauthorizedQuery(format("User %s has no SELECT 
permission on %s or any of its parents", user, table.resource),
++                                            format("SELECT * FROM %s LIMIT 
1", table));
++                }
++            }
++        }
++    }
++
++    private void maybeRejectGrant(Permission p, String errorResponse, String 
grant) throws Throwable
++    {
++        if (Permission.INVALID_FOR_SYSTEM_KEYSPACES.contains(p))
++            assertUnauthorizedQuery(errorResponse, grant);
++        else
++            executeNet(ProtocolVersion.CURRENT, grant);
++    }
 +}
diff --cc test/unit/org/apache/cassandra/cql3/CQLTester.java
index 61bb4839b8,48fddec68a..c20a0cea2a
--- a/test/unit/org/apache/cassandra/cql3/CQLTester.java
+++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java
@@@ -61,24 -59,13 +61,25 @@@ import com.datastax.driver.core.excepti
  import com.datastax.shaded.netty.channel.EventLoopGroup;
  import org.apache.cassandra.SchemaLoader;
  import org.apache.cassandra.ServerTestUtils;
 +import org.apache.cassandra.Util;
 +import org.apache.cassandra.auth.AuthCacheService;
 +import org.apache.cassandra.auth.AuthKeyspace;
 +import org.apache.cassandra.auth.AuthSchemaChangeListener;
 +import org.apache.cassandra.auth.AuthTestUtils;
 +import org.apache.cassandra.auth.IRoleManager;
  import org.apache.cassandra.concurrent.ScheduledExecutors;
 +import org.apache.cassandra.concurrent.Stage;
 +import org.apache.cassandra.config.DataStorageSpec;
 +import org.apache.cassandra.config.EncryptionOptions;
++import org.apache.cassandra.db.ConsistencyLevel;
  import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry;
  import org.apache.cassandra.db.virtual.VirtualSchemaKeyspace;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
  import org.apache.cassandra.index.SecondaryIndexManager;
 -import org.apache.cassandra.config.EncryptionOptions;
 +import org.apache.cassandra.io.util.File;
  import org.apache.cassandra.locator.InetAddressAndPort;
  import org.apache.cassandra.locator.TokenMetadata;
 +import org.apache.cassandra.metrics.CassandraMetricsRegistry;
  import org.apache.cassandra.metrics.ClientMetrics;
  import org.apache.cassandra.schema.*;
  import org.apache.cassandra.config.DatabaseDescriptor;
@@@ -1258,6 -1014,6 +1259,13 @@@ public abstract class CQLTeste
          return Schema.instance.getTableMetadata(KEYSPACE, currentTable());
      }
  
++    protected com.datastax.driver.core.ResultSet executeNet(ProtocolVersion 
protocolVersion, ConsistencyLevel consistency, String query) throws Throwable
++    {
++        Statement statement = new SimpleStatement(formatQuery(query));
++        statement = 
statement.setConsistencyLevel(com.datastax.driver.core.ConsistencyLevel.valueOf(consistency.name()));
++        return sessionNet(protocolVersion).execute(statement);
++    }
++
      protected com.datastax.driver.core.ResultSet executeNet(ProtocolVersion 
protocolVersion, String query, Object... values) throws Throwable
      {
          return sessionNet(protocolVersion).execute(formatQuery(query), 
values);
diff --cc test/unit/org/apache/cassandra/service/ClientStateTest.java
index 56d0893356,0000000000..05d1f78036
mode 100644,000000..100644
--- a/test/unit/org/apache/cassandra/service/ClientStateTest.java
+++ b/test/unit/org/apache/cassandra/service/ClientStateTest.java
@@@ -1,112 -1,0 +1,114 @@@
 +/*
 + * 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.service;
 +
 +import java.util.Set;
 +import java.util.concurrent.atomic.AtomicInteger;
 +
 +import com.google.common.collect.Iterables;
 +import org.junit.AfterClass;
 +import org.junit.BeforeClass;
 +import org.junit.Test;
 +
 +import org.apache.cassandra.SchemaLoader;
 +import org.apache.cassandra.auth.AuthCacheService;
 +import org.apache.cassandra.auth.AuthKeyspace;
 +import org.apache.cassandra.auth.AuthTestUtils;
 +import org.apache.cassandra.auth.AuthenticatedUser;
 +import org.apache.cassandra.auth.DataResource;
 +import org.apache.cassandra.auth.IResource;
 +import org.apache.cassandra.auth.Permission;
 +import org.apache.cassandra.auth.Roles;
 +import org.apache.cassandra.config.DatabaseDescriptor;
 +import org.apache.cassandra.schema.KeyspaceParams;
 +import org.apache.cassandra.schema.SchemaConstants;
 +import org.apache.cassandra.schema.TableMetadata;
 +
 +import static org.junit.Assert.assertEquals;
 +import static org.junit.Assert.fail;
 +
 +public class ClientStateTest
 +{
 +    @BeforeClass
 +    public static void beforeClass()
 +    {
 +        System.setProperty("org.apache.cassandra.disable_mbean_registration", 
"true");
 +        SchemaLoader.prepareServer();
 +        DatabaseDescriptor.setAuthFromRoot(true);
 +        // create the system_auth keyspace so the IRoleManager can function 
as normal
 +        SchemaLoader.createKeyspace(SchemaConstants.AUTH_KEYSPACE_NAME,
 +                                    KeyspaceParams.simple(1),
 +                                    
Iterables.toArray(AuthKeyspace.metadata().tables, TableMetadata.class));
- 
++        DatabaseDescriptor.setRoleManager(new 
AuthTestUtils.LocalCassandraRoleManager());
++        DatabaseDescriptor.getRoleManager().setup();
++        Roles.init();
 +        AuthCacheService.initializeAndRegisterCaches();
 +    }
 +
 +    @AfterClass
 +    public static void afterClass()
 +    {
 +        
System.clearProperty("org.apache.cassandra.disable_mbean_registration");
 +    }
 +
 +    @Test
 +    public void permissionsCheckStartsAtHeadOfResourceChain()
 +    {
 +        // verify that when performing a permissions check, we start from the
 +        // root IResource in the applicable hierarchy and proceed to the more
 +        // granular resources until we find the required permission (or until
 +        // we reach the end of the resource chain). This is because our 
typical
 +        // usage is to grant blanket permissions on the root resources to 
users
 +        // and so we save lookups, cache misses and cache space by traversing 
in
 +        // this order. e.g. for DataResources, we typically grant perms on the
 +        // 'data' resource, so when looking up a users perms on a specific 
table
 +        // it makes sense to follow: data -> keyspace -> table
 +
 +        final AtomicInteger getPermissionsRequestCount = new AtomicInteger(0);
 +        final IResource rootResource = DataResource.root();
 +        final IResource tableResource = DataResource.table("test_ks", 
"test_table");
 +        final AuthenticatedUser testUser = new AuthenticatedUser("test_user")
 +        {
 +            public Set<Permission> getPermissions(IResource resource)
 +            {
 +                getPermissionsRequestCount.incrementAndGet();
 +                if (resource.equals(rootResource))
 +                    return Permission.ALL;
 +
 +                fail(String.format("Permissions requested for unexpected 
resource %s", resource));
 +                // need a return to make the compiler happy
 +                return null;
 +            }
 +
 +            public boolean canLogin() { return true; }
 +        };
 +
 +        Roles.cache.invalidate();
 +
 +        // finally, need to configure CassandraAuthorizer so we don't 
shortcircuit out of the authz process
 +        DatabaseDescriptor.setAuthorizer(new 
AuthTestUtils.LocalCassandraAuthorizer());
 +
 +        // check permissions on the table, which should check for the root 
resource first
 +        // & return successfully without needing to proceed further
 +        ClientState state = ClientState.forInternalCalls();
 +        state.login(testUser);
 +        state.ensurePermission(Permission.SELECT, tableResource);
 +        assertEquals(1, getPermissionsRequestCount.get());
 +    }
 +}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to