Implement caching of authorization results

Patch by Aleksey Yeschenko; reviewed by Jonathan Ellis for
CASSANDRA-4295


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

Branch: refs/heads/trunk
Commit: d0f7e9e14a80ce29510672dd21cd04156b073514
Parents: 858dfef
Author: Aleksey Yeschenko <alek...@apache.org>
Authored: Tue Feb 12 21:14:43 2013 +0300
Committer: Aleksey Yeschenko <alek...@apache.org>
Committed: Tue Feb 12 21:14:43 2013 +0300

----------------------------------------------------------------------
 CHANGES.txt                                        |    1 +
 conf/cassandra.yaml                                |    5 ++
 .../apache/cassandra/auth/AuthenticatedUser.java   |   22 ++++++++
 src/java/org/apache/cassandra/config/Config.java   |    1 +
 .../cassandra/config/DatabaseDescriptor.java       |    5 ++
 .../cassandra/cql3/statements/BatchStatement.java  |   17 +------
 .../org/apache/cassandra/service/ClientState.java  |   42 ++++++++++++++-
 .../apache/cassandra/thrift/CassandraServer.java   |    8 +---
 8 files changed, 77 insertions(+), 24 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/d0f7e9e1/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index f176742..466dca6 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -15,6 +15,7 @@
  * add UseCondCardMark XX jvm settings on jdk 1.7 (CASSANDRA-4366)
  * CQL3 refactor to allow conversion function (CASSANDRA-5226)
  * Fix drop of sstables in some circumstance (CASSANDRA-5232)
+ * Implement caching of authorization results (CASSANDRA-4295)
 
 
 1.2.1

http://git-wip-us.apache.org/repos/asf/cassandra/blob/d0f7e9e1/conf/cassandra.yaml
----------------------------------------------------------------------
diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml
index 14f4e96..f027c15 100644
--- a/conf/cassandra.yaml
+++ b/conf/cassandra.yaml
@@ -60,6 +60,11 @@ authenticator: 
org.apache.cassandra.auth.AllowAllAuthenticator
 # authorization backend, implementing IAuthorizer; used to limit 
access/provide permissions
 authorizer: org.apache.cassandra.auth.AllowAllAuthorizer
 
+# Validity period for permissions cache (fetching permissions can be an
+# expensive operation depending on the authorizer). Defaults to 2000,
+# set to 0 to disable. Will be disabled automatically for AllowAllAuthorizer.
+permissions_validity_in_ms: 2000
+
 # The partitioner is responsible for distributing rows (by key) across
 # nodes in the cluster.  Any IPartitioner may be used, including your
 # own as long as it is on the classpath.  Out of the box, Cassandra

http://git-wip-us.apache.org/repos/asf/cassandra/blob/d0f7e9e1/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 cf208b8..f834878 100644
--- a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java
+++ b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java
@@ -17,6 +17,8 @@
  */
 package org.apache.cassandra.auth;
 
+import com.google.common.base.Objects;
+
 /**
  * Returned from IAuthenticator#authenticate(), represents an authenticated 
user everywhere internally.
  */
@@ -61,4 +63,24 @@ public class AuthenticatedUser
     {
         return String.format("#<User %s>", name);
     }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o)
+            return true;
+
+        if (!(o instanceof AuthenticatedUser))
+            return false;
+
+        AuthenticatedUser u = (AuthenticatedUser) o;
+
+        return Objects.equal(this.name, u.name);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return Objects.hashCode(name);
+    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/d0f7e9e1/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 d8a8afd..02324ee 100644
--- a/src/java/org/apache/cassandra/config/Config.java
+++ b/src/java/org/apache/cassandra/config/Config.java
@@ -33,6 +33,7 @@ public class Config
     public String authenticator;
     public String authority; // for backwards compatibility - will log a 
warning.
     public String authorizer;
+    public int permissions_validity_in_ms = 2000;
 
     /* Hashing strategy Random or OPHF */
     public String partitioner;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/d0f7e9e1/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 42bff03..e0fb9b5 100644
--- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
+++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
@@ -604,6 +604,11 @@ public class DatabaseDescriptor
         return authorizer;
     }
 
+    public static int getPermissionsValidity()
+    {
+        return conf.permissions_validity_in_ms;
+    }
+
     public static int getThriftMaxMessageLength()
     {
         return conf.thrift_max_message_length_in_mb * 1024 * 1024;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/d0f7e9e1/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
index 69db6a5..6200237 100644
--- a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
@@ -64,23 +64,8 @@ public class BatchStatement extends ModificationStatement
     @Override
     public void checkAccess(ClientState state) throws InvalidRequestException, 
UnauthorizedException
     {
-        Map<String, Set<String>> cfamsSeen = new HashMap<String, 
Set<String>>();
         for (ModificationStatement statement : statements)
-        {
-            String ks = statement.keyspace();
-            String cf = statement.columnFamily();
-
-            if (!cfamsSeen.containsKey(ks))
-                cfamsSeen.put(ks, new HashSet<String>());
-
-            // Avoid unnecessary authorization.
-            Set<String> cfs = cfamsSeen.get(ks);
-            if (!(cfs.contains(cf)))
-            {
-                state.hasColumnFamilyAccess(ks, cf, Permission.MODIFY);
-                cfs.add(cf);
-            }
-        }
+            state.hasColumnFamilyAccess(statement.keyspace(), 
statement.columnFamily(), Permission.MODIFY);
     }
 
     public void validate(ClientState state) throws InvalidRequestException

http://git-wip-us.apache.org/repos/asf/cassandra/blob/d0f7e9e1/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 d7b394a..110e134 100644
--- a/src/java/org/apache/cassandra/service/ClientState.java
+++ b/src/java/org/apache/cassandra/service/ClientState.java
@@ -18,7 +18,12 @@
 package org.apache.cassandra.service;
 
 import java.util.*;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
 
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
 import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -31,6 +36,7 @@ import org.apache.cassandra.db.Table;
 import org.apache.cassandra.exceptions.AuthenticationException;
 import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.exceptions.UnauthorizedException;
+import org.apache.cassandra.utils.Pair;
 import org.apache.cassandra.utils.SemanticVersion;
 
 /**
@@ -44,6 +50,9 @@ public class ClientState
     private static final Set<IResource> READABLE_SYSTEM_RESOURCES = new 
HashSet<IResource>(5);
     private static final Set<IResource> PROTECTED_AUTH_RESOURCES = new 
HashSet<IResource>();
 
+    // User-level permissions cache.
+    private static final LoadingCache<Pair<AuthenticatedUser, IResource>, 
Set<Permission>> permissionsCache = initPermissionsCache();
+
     static
     {
         // We want these system cfs to be always readable since many tools 
rely on them (nodetool, cqlsh, bulkloader, etc.)
@@ -239,8 +248,39 @@ public class ClientState
         return new SemanticVersion[]{ cql, cql3 };
     }
 
+    private static LoadingCache<Pair<AuthenticatedUser, IResource>, 
Set<Permission>> initPermissionsCache()
+    {
+        if (DatabaseDescriptor.getAuthorizer() instanceof AllowAllAuthorizer)
+            return null;
+
+        int validityPeriod = DatabaseDescriptor.getPermissionsValidity();
+        if (validityPeriod <= 0)
+            return null;
+
+        return CacheBuilder.newBuilder().expireAfterWrite(validityPeriod, 
TimeUnit.MILLISECONDS)
+                                        .build(new 
CacheLoader<Pair<AuthenticatedUser, IResource>, Set<Permission>>()
+                                        {
+                                            public Set<Permission> 
load(Pair<AuthenticatedUser, IResource> userResource)
+                                            {
+                                                return 
DatabaseDescriptor.getAuthorizer().authorize(userResource.left,
+                                                                               
                     userResource.right);
+                                            }
+                                        });
+    }
+
     private Set<Permission> authorize(IResource resource)
     {
-        return DatabaseDescriptor.getAuthorizer().authorize(user, resource);
+        // AllowAllAuthorizer or manually disabled caching.
+        if (permissionsCache == null)
+            return DatabaseDescriptor.getAuthorizer().authorize(user, 
resource);
+
+        try
+        {
+            return permissionsCache.get(Pair.create(user, resource));
+        }
+        catch (ExecutionException e)
+        {
+            throw new RuntimeException(e);
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/d0f7e9e1/src/java/org/apache/cassandra/thrift/CassandraServer.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/thrift/CassandraServer.java 
b/src/java/org/apache/cassandra/thrift/CassandraServer.java
index fbdf184..7edaf2b 100644
--- a/src/java/org/apache/cassandra/thrift/CassandraServer.java
+++ b/src/java/org/apache/cassandra/thrift/CassandraServer.java
@@ -659,7 +659,6 @@ public class CassandraServer implements Cassandra.Iface
                                                boolean allowCounterMutations)
     throws RequestValidationException
     {
-        List<String> cfamsSeen = new ArrayList<String>();
         List<IMutation> rowMutations = new ArrayList<IMutation>();
         ThriftClientState cState = state();
         String keyspace = cState.getKeyspace();
@@ -678,12 +677,7 @@ public class CassandraServer implements Cassandra.Iface
             {
                 String cfName = columnFamilyMutations.getKey();
 
-                // Avoid unneeded authorizations
-                if (!(cfamsSeen.contains(cfName)))
-                {
-                    cState.hasColumnFamilyAccess(keyspace, cfName, 
Permission.MODIFY);
-                    cfamsSeen.add(cfName);
-                }
+                cState.hasColumnFamilyAccess(keyspace, cfName, 
Permission.MODIFY);
 
                 CFMetaData metadata = 
ThriftValidation.validateColumnFamily(keyspace, cfName);
                 ThriftValidation.validateKey(metadata, key);

Reply via email to