Repository: cassandra
Updated Branches:
  refs/heads/trunk 56348ea7b -> b32ce687e


Make LIST USERS display inherited superuser status

patch by Sam Tunnicliffe; reviewed by Aleksey Yeschenko for
CASSANDRA-8849


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/b32ce687
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/b32ce687
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/b32ce687

Branch: refs/heads/trunk
Commit: b32ce687e713de8d8535b0607c9edd9b55a7b6aa
Parents: 56348ea
Author: Sam Tunnicliffe <s...@beobal.com>
Authored: Tue Mar 3 15:58:48 2015 -0800
Committer: Aleksey Yeschenko <alek...@apache.org>
Committed: Tue Mar 3 15:58:48 2015 -0800

----------------------------------------------------------------------
 CHANGES.txt                                     |   2 +-
 conf/cassandra.yaml                             |   8 ++
 .../cassandra/auth/AuthenticatedUser.java       |  89 +--------------
 src/java/org/apache/cassandra/auth/Roles.java   |  59 ++++++++++
 .../org/apache/cassandra/auth/RolesCache.java   | 109 +++++++++++++++++++
 .../org/apache/cassandra/config/Config.java     |   2 +
 .../cassandra/config/DatabaseDescriptor.java    |  12 ++
 .../statements/AuthenticationStatement.java     |  20 ----
 .../cql3/statements/DropRoleStatement.java      |   6 +-
 .../cql3/statements/ListUsersStatement.java     |   6 +-
 .../apache/cassandra/service/ClientState.java   |   2 +-
 11 files changed, 200 insertions(+), 115 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index cc3658d..b877cbe 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,5 @@
 3.0
- * Add role based access control (CASSANDRA-7653, 8650, 7216, 8760)
+ * Add role based access control (CASSANDRA-7653, 8650, 7216, 8760, 8849)
  * Avoid accessing partitioner through StorageProxy (CASSANDRA-8244, 8268)
  * Upgrade Metrics library and remove depricated metrics (CASSANDRA-5657)
  * Serializing Row cache alternative, fully off heap (CASSANDRA-7438)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/conf/cassandra.yaml
----------------------------------------------------------------------
diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml
index 3643986..240f37a 100644
--- a/conf/cassandra.yaml
+++ b/conf/cassandra.yaml
@@ -93,6 +93,14 @@ role_manager: CassandraRoleManager
 # Will be disabled automatically for AllowAllAuthenticator.
 roles_validity_in_ms: 2000
 
+# Refresh interval for roles cache (if enabled).
+# After this interval, cache entries become eligible for refresh. Upon next
+# access, an async reload is scheduled and the old value returned until it
+# completes. If roles_validity_in_ms is non-zero, then this must be
+# also.
+# Defaults to the same value as roles_validity_in_ms.
+# roles_update_interval_in_ms: 1000
+
 # Validity period for permissions cache (fetching permissions can be an
 # expensive operation depending on the authorizer, CassandraAuthorizer is
 # one example). Defaults to 2000, set to 0 to disable.

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/auth/AuthenticatedUser.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java 
b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java
index e4a065d..ee62503 100644
--- a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java
+++ b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java
@@ -18,20 +18,10 @@
 package org.apache.cassandra.auth;
 
 import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
 
 import com.google.common.base.Objects;
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.CacheLoader;
-import com.google.common.cache.LoadingCache;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListenableFutureTask;
 
-import org.apache.cassandra.concurrent.ScheduledExecutors;
 import org.apache.cassandra.config.DatabaseDescriptor;
-import org.apache.cassandra.exceptions.RequestExecutionException;
-import org.apache.cassandra.exceptions.RequestValidationException;
 
 /**
  * Returned from IAuthenticator#authenticate(), represents an authenticated 
user everywhere internally.
@@ -47,9 +37,6 @@ public class AuthenticatedUser
     public static final String ANONYMOUS_USERNAME = "anonymous";
     public static final AuthenticatedUser ANONYMOUS_USER = new 
AuthenticatedUser(ANONYMOUS_USERNAME);
 
-    // User-level roles cache
-    private static final LoadingCache<RoleResource, Set<RoleResource>> 
rolesCache = initRolesCache();
-
     // User-level permissions cache.
     private static final PermissionsCache permissionsCache = new 
PermissionsCache(DatabaseDescriptor.getPermissionsValidity(),
                                                                                
   DatabaseDescriptor.getPermissionsUpdateInterval(),
@@ -84,16 +71,7 @@ public class AuthenticatedUser
      */
     public boolean isSuper()
     {
-        return !isAnonymous() && hasSuperuserRole();
-    }
-
-    private boolean hasSuperuserRole()
-    {
-        IRoleManager roleManager = DatabaseDescriptor.getRoleManager();
-        for (RoleResource role : getRoles())
-            if (roleManager.isSuper(role))
-                return true;
-        return false;
+        return !isAnonymous() && Roles.hasSuperuserStatus(role);
     }
 
     /**
@@ -121,71 +99,12 @@ public class AuthenticatedUser
      */
     public Set<RoleResource> getRoles()
     {
-        if (rolesCache == null)
-            return loadRoles(role);
-
-        try
-        {
-            return rolesCache.get(role);
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e);
-        }
+        return Roles.getRoles(role);
     }
 
-    public static Set<Permission> getPermissions(AuthenticatedUser user, 
IResource resource)
-    {
-        return permissionsCache.getPermissions(user, resource);
-    }
-
-    private static Set<RoleResource> loadRoles(RoleResource primary)
-    {
-        try
-        {
-            return DatabaseDescriptor.getRoleManager().getRoles(primary, true);
-        }
-        catch (RequestValidationException e)
-        {
-            throw new AssertionError(e); // not supposed to happen
-        }
-        catch (RequestExecutionException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-    
-    private static LoadingCache<RoleResource, Set<RoleResource>> 
initRolesCache()
+    public Set<Permission> getPermissions(IResource resource)
     {
-        if (DatabaseDescriptor.getAuthenticator() instanceof 
AllowAllAuthenticator)
-            return null;
-
-        int validityPeriod = DatabaseDescriptor.getRolesValidity();
-        if (validityPeriod <= 0)
-            return null;
-
-        return CacheBuilder.newBuilder()
-                           .refreshAfterWrite(validityPeriod, 
TimeUnit.MILLISECONDS)
-                           .build(new CacheLoader<RoleResource, 
Set<RoleResource>>()
-                           {
-                               public Set<RoleResource> load(RoleResource 
primary)
-                               {
-                                   return loadRoles(primary);
-                               }
-
-                               public ListenableFuture<Set<RoleResource>> 
reload(final RoleResource primary, Set<RoleResource> oldValue)
-                               {
-                                   ListenableFutureTask<Set<RoleResource>> 
task = ListenableFutureTask.create(new Callable<Set<RoleResource>>()
-                                   {
-                                       public Set<RoleResource> call()
-                                       {
-                                           return loadRoles(primary);
-                                       }
-                                   });
-                                   
ScheduledExecutors.optionalTasks.execute(task);
-                                   return task;
-                               }
-                           });
+        return permissionsCache.getPermissions(this, resource);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/auth/Roles.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/auth/Roles.java 
b/src/java/org/apache/cassandra/auth/Roles.java
new file mode 100644
index 0000000..c73f882
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/Roles.java
@@ -0,0 +1,59 @@
+/*
+ * 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.Set;
+
+import org.apache.cassandra.config.DatabaseDescriptor;
+
+public class Roles
+{
+    private static final RolesCache cache = new 
RolesCache(DatabaseDescriptor.getRolesValidity(),
+                                                           
DatabaseDescriptor.getRolesUpdateInterval(),
+                                                           
DatabaseDescriptor.getRolesCacheMaxEntries(),
+                                                           
DatabaseDescriptor.getRoleManager());
+
+    /**
+     * Get all roles granted to the supplied Role, including both directly 
granted
+     * and inherited roles.
+     * The returned roles may be cached if roles_validity_in_ms > 0
+     *
+     * @param primaryRole the Role
+     * @return set of all granted Roles for the primary Role
+     */
+    public static Set<RoleResource> getRoles(RoleResource primaryRole)
+    {
+        return cache.getRoles(primaryRole);
+    }
+
+    /**
+     * Returns true if the supplied role or any other role granted to it
+     * (directly or indirectly) has superuser status.
+     *
+     * @param role the primary role
+     * @return true if the role has superuser status, false otherwise
+     */
+    public static boolean hasSuperuserStatus(RoleResource role)
+    {
+        IRoleManager roleManager = DatabaseDescriptor.getRoleManager();
+        for (RoleResource r : cache.getRoles(role))
+            if (roleManager.isSuper(r))
+                return true;
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/auth/RolesCache.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/auth/RolesCache.java 
b/src/java/org/apache/cassandra/auth/RolesCache.java
new file mode 100644
index 0000000..0e9e134
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/RolesCache.java
@@ -0,0 +1,109 @@
+/*
+ * 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.Set;
+import java.util.concurrent.*;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListenableFutureTask;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor;
+import org.apache.cassandra.config.DatabaseDescriptor;
+
+public class RolesCache
+{
+    private static final Logger logger = 
LoggerFactory.getLogger(RolesCache.class);
+
+    private final ThreadPoolExecutor cacheRefreshExecutor = new 
DebuggableThreadPoolExecutor("RolesCacheRefresh",
+                                                                               
              Thread.NORM_PRIORITY);
+    private final IRoleManager roleManager;
+    private final LoadingCache<RoleResource, Set<RoleResource>> cache;
+
+    public RolesCache(int validityPeriod, int updateInterval, int maxEntries, 
IRoleManager roleManager)
+    {
+        this.roleManager = roleManager;
+        this.cache = initCache(validityPeriod, updateInterval, maxEntries);
+    }
+
+    public Set<RoleResource> getRoles(RoleResource role)
+    {
+        if (cache == null)
+            return roleManager.getRoles(role, true);
+
+        try
+        {
+            return cache.get(role);
+        }
+        catch (ExecutionException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private LoadingCache<RoleResource, Set<RoleResource>> initCache(int 
validityPeriod,
+                                                                    int 
updateInterval,
+                                                                    int 
maxEntries)
+    {
+        if (DatabaseDescriptor.getAuthenticator() instanceof 
AllowAllAuthenticator)
+            return null;
+
+        if (validityPeriod <= 0)
+            return null;
+
+        return CacheBuilder.newBuilder()
+                           .refreshAfterWrite(updateInterval, 
TimeUnit.MILLISECONDS)
+                           .expireAfterWrite(validityPeriod, 
TimeUnit.MILLISECONDS)
+                           .maximumSize(maxEntries)
+                           .build(new CacheLoader<RoleResource, 
Set<RoleResource>>()
+                           {
+                               public Set<RoleResource> load(RoleResource 
primaryRole)
+                               {
+                                   return roleManager.getRoles(primaryRole, 
true);
+                               }
+
+                               public ListenableFuture<Set<RoleResource>> 
reload(final RoleResource primaryRole,
+                                                                               
  final Set<RoleResource> oldValue)
+                               {
+                                   ListenableFutureTask<Set<RoleResource>> 
task;
+                                   task = ListenableFutureTask.create(new 
Callable<Set<RoleResource>>()
+                                   {
+                                       public Set<RoleResource> call() throws 
Exception
+                                       {
+                                           try
+                                           {
+                                               return 
roleManager.getRoles(primaryRole, true);
+                                           }
+                                           catch (Exception e)
+                                           {
+                                               logger.debug("Error performing 
async refresh of user roles", e);
+                                               throw e;
+                                           }
+                                       }
+                                   });
+                                   cacheRefreshExecutor.execute(task);
+                                   return task;
+                               }
+                           });
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/config/Config.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/Config.java 
b/src/java/org/apache/cassandra/config/Config.java
index 806c7b4..eec6826 100644
--- a/src/java/org/apache/cassandra/config/Config.java
+++ b/src/java/org/apache/cassandra/config/Config.java
@@ -48,6 +48,8 @@ public class Config
     public int permissions_cache_max_entries = 1000;
     public int permissions_update_interval_in_ms = -1;
     public int roles_validity_in_ms = 2000;
+    public int roles_cache_max_entries = 1000;
+    public int roles_update_interval_in_ms = -1;
 
     /* Hashing strategy Random or OPHF */
     public String partitioner;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java 
b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
index 2891e9a..92d5d5b 100644
--- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
+++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
@@ -640,6 +640,18 @@ public class DatabaseDescriptor
         return conf.roles_validity_in_ms;
     }
 
+    public static int getRolesCacheMaxEntries()
+    {
+        return conf.roles_cache_max_entries;
+    }
+
+    public static int getRolesUpdateInterval()
+    {
+        return conf.roles_update_interval_in_ms == -1
+             ? conf.roles_validity_in_ms
+             : conf.roles_update_interval_in_ms;
+    }
+
     public static int getThriftFramedTransportSize()
     {
         return conf.thrift_framed_transport_size_in_mb * 1024 * 1024;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java
index b0ea246..151e4f0 100644
--- a/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java
@@ -17,11 +17,8 @@
  */
 package org.apache.cassandra.cql3.statements;
 
-
-import org.apache.cassandra.auth.IRoleManager;
 import org.apache.cassandra.auth.Permission;
 import org.apache.cassandra.auth.RoleResource;
-import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.cql3.CQLStatement;
 import org.apache.cassandra.cql3.QueryOptions;
 import org.apache.cassandra.exceptions.RequestExecutionException;
@@ -72,22 +69,5 @@ public abstract class AuthenticationStatement extends 
ParsedStatement implements
                                                           
state.getUser().getName()));
         }
     }
-
-    protected boolean hasSuperuserStatus(RoleResource role)
-    {
-        IRoleManager roleManager = DatabaseDescriptor.getRoleManager();
-        try
-        {
-            for (RoleResource r : roleManager.getRoles(role, true))
-                if (roleManager.isSuper(r))
-                    return true;
-            return false;
-        }
-        catch(RequestValidationException | RequestExecutionException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
 }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java
index 44c749b..ec4bde7 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java
@@ -17,9 +17,7 @@
  */
 package org.apache.cassandra.cql3.statements;
 
-import org.apache.cassandra.auth.AuthenticatedUser;
-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.exceptions.*;
@@ -40,7 +38,7 @@ public class DropRoleStatement extends AuthenticationStatement
     public void checkAccess(ClientState state) throws UnauthorizedException
     {
         super.checkPermission(state, Permission.DROP, role);
-        if (hasSuperuserStatus(role) && !state.getUser().isSuper())
+        if (Roles.hasSuperuserStatus(role) && !state.getUser().isSuper())
             throw new UnauthorizedException("Only superusers can drop a role 
with superuser status");
     }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/cql3/statements/ListUsersStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/ListUsersStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/ListUsersStatement.java
index 3edf3a4..7251980 100644
--- a/src/java/org/apache/cassandra/cql3/statements/ListUsersStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ListUsersStatement.java
@@ -21,9 +21,7 @@ import java.util.List;
 
 import com.google.common.collect.ImmutableList;
 
-import org.apache.cassandra.auth.AuthKeyspace;
-import org.apache.cassandra.auth.IRoleManager;
-import org.apache.cassandra.auth.RoleResource;
+import org.apache.cassandra.auth.*;
 import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.cql3.ColumnIdentifier;
 import org.apache.cassandra.cql3.ColumnSpecification;
@@ -53,7 +51,7 @@ public class ListUsersStatement extends ListRolesStatement
             if (!roleManager.canLogin(role))
                 continue;
             
result.addColumnValue(UTF8Type.instance.decompose(role.getRoleName()));
-            
result.addColumnValue(BooleanType.instance.decompose(roleManager.isSuper(role)));
+            
result.addColumnValue(BooleanType.instance.decompose(Roles.hasSuperuserStatus(role)));
         }
         return new ResultMessage.Rows(result);
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/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 57a57e4..1bc6e9d 100644
--- a/src/java/org/apache/cassandra/service/ClientState.java
+++ b/src/java/org/apache/cassandra/service/ClientState.java
@@ -327,6 +327,6 @@ public class ClientState
 
     private Set<Permission> authorize(IResource resource)
     {
-        return AuthenticatedUser.getPermissions(user, resource);
+        return user.getPermissions(resource);
     }
 }

Reply via email to