cassandra-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From alek...@apache.org
Subject [3/4] cassandra git commit: Introduce role based access control
Date Wed, 14 Jan 2015 19:51:11 GMT
http://git-wip-us.apache.org/repos/asf/cassandra/blob/879b694d/src/java/org/apache/cassandra/auth/CassandraRoleManager.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java
new file mode 100644
index 0000000..34feb22
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java
@@ -0,0 +1,586 @@
+/*
+ * 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.*;
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.base.*;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.cassandra.concurrent.ScheduledExecutors;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.config.Schema;
+import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.cql3.statements.SelectStatement;
+import org.apache.cassandra.db.ConsistencyLevel;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.exceptions.*;
+import org.apache.cassandra.service.QueryState;
+import org.apache.cassandra.transport.messages.ResultMessage;
+import org.apache.cassandra.utils.ByteBufferUtil;
+import org.mindrot.jbcrypt.BCrypt;
+
+/**
+ * Responsible for the creation, maintainance and delation of roles
+ * for the purposes of authentication and authorization.
+ * Role data is stored internally, using the roles and role_members tables
+ * in the system_auth keyspace.
+ *
+ * Additionally, if org.apache.cassandra.auth.PasswordAuthenticator is used,
+ * encrypted passwords are also stored in the system_auth.roles table. This
+ * coupling between the IAuthenticator and IRoleManager implementations exists
+ * because setting a role's password via CQL is done with a CREATE ROLE or
+ * ALTER ROLE statement, the processing of which is handled by IRoleManager.
+ * As IAuthenticator is concerned only with credentials checking and has no
+ * means to modify passwords, PasswordAuthenticator depends on
+ * CassandraRoleManager for those functions.
+ *
+ * Alternative IAuthenticator implementations may be used in conjunction with
+ * CassandraRoleManager, but WITH PASSWORD = 'password' will not be supported
+ * in CREATE/ALTER ROLE statements.
+ *
+ * Such a configuration could be implemented using a custom IRoleManager that
+ * extends CassandraRoleManager and which includes Option.PASSWORD in the Set<Option>
+ * returned from supportedOptions/alterableOptions. Any additional processing
+ * of the password itself (such as storing it in an alternative location) would
+ * be added in overriden createRole and alterRole implementations.
+ */
+public class CassandraRoleManager implements IRoleManager
+{
+    private static final Logger logger = LoggerFactory.getLogger(CassandraRoleManager.class);
+
+    static final String DEFAULT_SUPERUSER_NAME = "cassandra";
+    static final String DEFAULT_SUPERUSER_PASSWORD = "cassandra";
+
+    // Transform a row in the AuthKeyspace.ROLES to a Role instance
+    private static final Function<UntypedResultSet.Row, Role> ROW_TO_ROLE = new Function<UntypedResultSet.Row, Role>()
+    {
+        public Role apply(UntypedResultSet.Row row)
+        {
+            return new Role(row.getString("role"),
+                            row.getBoolean("is_superuser"),
+                            row.getBoolean("can_login"),
+                            row.has("member_of") ? row.getSet("member_of", UTF8Type.instance)
+                                                 : Collections.<String>emptySet());
+        }
+    };
+
+    public static final String LEGACY_USERS_TABLE = "users";
+    // Transform a row in the legacy system_auth.users table to a Role instance,
+    // used to fallback to previous schema on a mixed cluster during an upgrade
+    private static final Function<UntypedResultSet.Row, Role> LEGACY_ROW_TO_ROLE = new Function<UntypedResultSet.Row, Role>()
+    {
+        public Role apply(UntypedResultSet.Row row)
+        {
+            return new Role(row.getString("name"),
+                            row.getBoolean("super"),
+                            true,
+                            Collections.<String>emptySet());
+        }
+    };
+
+    // 2 ** GENSALT_LOG2_ROUNS rounds of hashing will be performed.
+    private static final int GENSALT_LOG2_ROUNDS = 10;
+
+    // NullObject returned when a supplied role name not found in AuthKeyspace.ROLES
+    private static final Role NULL_ROLE = new Role(null, false, false, Collections.<String>emptySet());
+
+    private SelectStatement loadRoleStatement;
+    private SelectStatement legacySelectUserStatement;
+
+    private final Set<Option> supportedOptions;
+    private final Set<Option> alterableOptions;
+
+    public CassandraRoleManager()
+    {
+        supportedOptions = DatabaseDescriptor.getAuthenticator().getClass() == PasswordAuthenticator.class
+                           ? ImmutableSet.of(Option.LOGIN, Option.SUPERUSER, Option.PASSWORD)
+                           : ImmutableSet.of(Option.LOGIN, Option.SUPERUSER);
+        alterableOptions = DatabaseDescriptor.getAuthenticator().getClass().equals(PasswordAuthenticator.class)
+                           ? ImmutableSet.of(Option.PASSWORD)
+                           : ImmutableSet.<Option>of();
+    }
+
+    public void setup()
+    {
+        loadRoleStatement = (SelectStatement) prepare("SELECT * from %s.%s WHERE role = ?",
+                                                      AuthKeyspace.NAME,
+                                                      AuthKeyspace.ROLES);
+        // If the old users table exists, we may need to migrate the legacy authn
+        // data to the new table. We also need to prepare a statement to read from
+        // it, so we can continue to use the old tables while the cluster is upgraded.
+        // Otherwise, we may need to create a default superuser role to enable others
+        // to be added.
+        if (Schema.instance.getCFMetaData(AuthKeyspace.NAME, "users") != null)
+        {
+             legacySelectUserStatement = (SelectStatement) prepare("SELECT * FROM %s.%s WHERE name = ?",
+                                                             AuthKeyspace.NAME,
+                                                             LEGACY_USERS_TABLE);
+            scheduleSetupTask(new Runnable()
+            {
+                public void run()
+                {
+                    convertLegacyData();
+                }
+            });
+        }
+        else
+        {
+            scheduleSetupTask(new Runnable()
+            {
+                public void run()
+                {
+                    setupDefaultRole();
+                }
+            });
+        }
+    }
+
+    public Set<Option> supportedOptions()
+    {
+        return supportedOptions;
+    }
+
+    public Set<Option> alterableOptions()
+    {
+        return alterableOptions;
+    }
+
+    public void createRole(AuthenticatedUser performer, String role, Map<Option, Object> options)
+    throws RequestValidationException, RequestExecutionException
+    {
+        String insertCql = options.containsKey(Option.PASSWORD)
+                            ? String.format("INSERT INTO %s.%s (role, is_superuser, can_login, salted_hash) VALUES ('%s', %s, %s, '%s')",
+                                            AuthKeyspace.NAME,
+                                            AuthKeyspace.ROLES,
+                                            escape(role),
+                                            options.get(Option.SUPERUSER),
+                                            options.get(Option.LOGIN),
+                                            escape(hashpw(options.get(Option.PASSWORD).toString())))
+                            : String.format("INSERT INTO %s.%s (role, is_superuser, can_login) VALUES ('%s', %s, %s)",
+                                            AuthKeyspace.NAME,
+                                            AuthKeyspace.ROLES,
+                                            escape(role),
+                                            options.get(Option.SUPERUSER),
+                                            options.get(Option.LOGIN));
+        process(insertCql, consistencyForRole(role));
+    }
+
+    public void dropRole(AuthenticatedUser performer, String role) throws RequestValidationException, RequestExecutionException
+    {
+        process(String.format("DELETE FROM %s.%s WHERE role = '%s'",
+                              AuthKeyspace.NAME,
+                              AuthKeyspace.ROLES,
+                              escape(role)),
+                consistencyForRole(role));
+        removeAllMembers(role);
+    }
+
+    public void alterRole(AuthenticatedUser performer, String role, Map<Option, Object> options)
+    throws RequestValidationException, RequestExecutionException
+    {
+        // Unlike most of the other data access methods here, this does not use a
+        // prepared statement in order to allow the set of assignments to be variable.
+        String assignments = Joiner.on(',')
+                                   .join(Iterables.filter(optionsToAssignments(options),
+                                                          Predicates.notNull()));
+        if (!Strings.isNullOrEmpty(assignments))
+        {
+            QueryProcessor.process(String.format("UPDATE %s.%s SET %s WHERE role = '%s'",
+                                                 AuthKeyspace.NAME,
+                                                 AuthKeyspace.ROLES,
+                                                 assignments,
+                                                 escape(role)),
+                                   consistencyForRole(role));
+        }
+    }
+
+    public void grantRole(AuthenticatedUser performer, String role, String grantee)
+    throws RequestValidationException, RequestExecutionException
+    {
+        if (getRoles(grantee, true).contains(role))
+            throw new InvalidRequestException(String.format("%s is a member of %s", grantee, role));
+        if (getRoles(role, true).contains(grantee))
+            throw new InvalidRequestException(String.format("%s is a member of %s", role, grantee));
+
+        modifyRoleMembership(grantee, role, "+");
+        process(String.format("INSERT INTO %s.%s (role, member) values ('%s', '%s')",
+                              AuthKeyspace.NAME,
+                              AuthKeyspace.ROLE_MEMBERS,
+                              escape(role),
+                              escape(grantee)),
+                consistencyForRole(role));
+    }
+
+    public void revokeRole(AuthenticatedUser performer, String role, String revokee)
+    throws RequestValidationException, RequestExecutionException
+    {
+        if (!getRoles(revokee, false).contains(role))
+            throw new InvalidRequestException(String.format("%s is not a member of %s", revokee, role));
+
+        modifyRoleMembership(revokee, role, "-");
+        process(String.format("DELETE FROM %s.%s WHERE role = '%s' and member = '%s'",
+                              AuthKeyspace.NAME,
+                              AuthKeyspace.ROLE_MEMBERS,
+                              escape(role),
+                              escape(revokee)),
+                consistencyForRole(role));
+    }
+
+    public Set<String> getRoles(String grantee, boolean includeInherited) throws RequestValidationException, RequestExecutionException
+    {
+        Set<String> roles = new HashSet<>();
+        Role role = getRole(grantee);
+        if (!role.equals(NULL_ROLE))
+        {
+            roles.add(role.name);
+            collectRoles(role, roles, includeInherited);
+        }
+        return roles;
+    }
+
+    public Set<String> getAllRoles() throws RequestValidationException, RequestExecutionException
+    {
+        UntypedResultSet rows = QueryProcessor.process(String.format("SELECT role from %s.%s",
+                                                                     AuthKeyspace.NAME,
+                                                                     AuthKeyspace.ROLES),
+                                                       ConsistencyLevel.QUORUM);
+        Iterable<String> roles = Iterables.transform(rows, new Function<UntypedResultSet.Row, String>()
+        {
+            public String apply(UntypedResultSet.Row row)
+            {
+                return row.getString("role");
+            }
+        });
+        return ImmutableSet.<String>builder().addAll(roles).build();
+    }
+
+    public boolean isSuper(String role)
+    {
+        return getRole(role).isSuper;
+    }
+
+    public boolean canLogin(String role)
+    {
+        return getRole(role).canLogin;
+    }
+
+    public boolean isExistingRole(String role)
+    {
+        return getRole(role) != NULL_ROLE;
+    }
+
+    public Set<? extends IResource> protectedResources()
+    {
+        return ImmutableSet.of(DataResource.table(AuthKeyspace.NAME, AuthKeyspace.ROLES),
+                               DataResource.table(AuthKeyspace.NAME, AuthKeyspace.ROLE_MEMBERS));
+    }
+
+    public void validateConfiguration() throws ConfigurationException
+    {
+    }
+
+    /*
+     * Create the default superuser role to bootstrap role creation on a clean system. Preemptively
+     * gives the role the default password so PasswordAuthenticator can be used to log in (if
+     * configured)
+     */
+    private static void setupDefaultRole()
+    {
+        try
+        {
+            if (!hasExistingRoles())
+            {
+                QueryProcessor.process(String.format("INSERT INTO %s.%s (role, is_superuser, can_login, salted_hash) " +
+                                                     "VALUES ('%s', true, true, '%s')",
+                                                     AuthKeyspace.NAME,
+                                                     AuthKeyspace.ROLES,
+                                                     DEFAULT_SUPERUSER_NAME,
+                                                     escape(hashpw(DEFAULT_SUPERUSER_PASSWORD))),
+                                       consistencyForRole(DEFAULT_SUPERUSER_NAME));
+                logger.info("Created default superuser role '{}'", DEFAULT_SUPERUSER_NAME);
+            }
+        }
+        catch (RequestExecutionException e)
+        {
+            logger.warn("CassandraRoleManager skipped default role setup: some nodes were not ready");
+        }
+    }
+
+    private static boolean hasExistingRoles() throws RequestExecutionException
+    {
+        // Try looking up the 'cassandra' default role first, to avoid the range query if possible.
+        String defaultSUQuery = String.format("SELECT * FROM %s.%s WHERE role = '%s'", AuthKeyspace.NAME, AuthKeyspace.ROLES, DEFAULT_SUPERUSER_NAME);
+        String allUsersQuery = String.format("SELECT * FROM %s.%s LIMIT 1", AuthKeyspace.NAME, AuthKeyspace.ROLES);
+        return !process(defaultSUQuery, ConsistencyLevel.ONE).isEmpty()
+               || !process(defaultSUQuery, ConsistencyLevel.QUORUM).isEmpty()
+               || !process(allUsersQuery, ConsistencyLevel.QUORUM).isEmpty();
+    }
+
+    private void scheduleSetupTask(Runnable runnable)
+    {
+        // The delay is to give the node a chance to see its peers before attempting the operation
+        ScheduledExecutors.optionalTasks.schedule(runnable, AuthKeyspace.SUPERUSER_SETUP_DELAY, TimeUnit.MILLISECONDS);
+    }
+
+    /*
+     * Copy legacy auth data from the system_auth.users & system_auth.credentials tables to
+     * the new system_auth.roles table. This setup is not performed if AllowAllAuthenticator
+     * is configured (see Auth#setup).
+     */
+    private void convertLegacyData()
+    {
+        try
+        {
+            // read old data at QUORUM as it may contain the data for the default superuser
+            if (Schema.instance.getCFMetaData("system_auth", "users") != null)
+            {
+                logger.info("Converting legacy users");
+                UntypedResultSet users = QueryProcessor.process("SELECT * FROM system_auth.users",
+                                                                ConsistencyLevel.QUORUM);
+                for (UntypedResultSet.Row row : users)
+                {
+                    Map<Option, Object> options = new HashMap<>();
+                    options.put(Option.SUPERUSER, row.getBoolean("super"));
+                    options.put(Option.LOGIN, true);
+                    createRole(null, row.getString("name"), options);
+                }
+                logger.info("Completed conversion of legacy users");
+            }
+
+            if (Schema.instance.getCFMetaData("system_auth", "credentials") != null)
+            {
+                logger.info("Migrating legacy credentials data to new system table");
+                UntypedResultSet credentials = QueryProcessor.process("SELECT * FROM system_auth.credentials",
+                                                                      ConsistencyLevel.QUORUM);
+                for (UntypedResultSet.Row row : credentials)
+                {
+                    // Write the password directly into the table to avoid doubly encrypting it
+                    QueryProcessor.process(String.format("UPDATE %s.%s SET salted_hash = '%s' WHERE role = '%s'",
+                                                         AuthKeyspace.NAME,
+                                                         AuthKeyspace.ROLES,
+                                                         row.getString("salted_hash"),
+                                                         row.getString("username")),
+                                           consistencyForRole(row.getString("username")));
+                }
+                logger.info("Completed conversion of legacy credentials");
+            }
+        }
+        catch (Exception e)
+        {
+            logger.info("Unable to complete conversion of legacy auth data (perhaps not enough nodes are upgraded yet). " +
+                        "Conversion should not be considered complete");
+            logger.debug("Conversion error", e);
+        }
+    }
+
+    private CQLStatement prepare(String template, String keyspace, String table)
+    {
+        try
+        {
+            return QueryProcessor.parseStatement(String.format(template, keyspace, table)).prepare().statement;
+        }
+        catch (RequestValidationException e)
+        {
+            throw new AssertionError(e); // not supposed to happen
+        }
+    }
+
+    /*
+     * Retrieve all roles granted to the given role. includeInherited specifies
+     * whether to include only those roles granted directly or all inherited roles.
+     */
+    private void collectRoles(Role role, Set<String> collected, boolean includeInherited) throws RequestValidationException, RequestExecutionException
+    {
+        for (String memberOf : role.memberOf)
+        {
+            Role granted = getRole(memberOf);
+            if (role.equals(NULL_ROLE))
+                continue;
+            collected.add(granted.name);
+            if (includeInherited)
+                collectRoles(granted, collected, true);
+        }
+    }
+
+    /*
+     * Get a single Role instance given the role name. This never returns null, instead it
+     * uses the null object NULL_ROLE when a role with the given name cannot be found. So
+     * it's always safe to call methods on the returned object without risk of NPE.
+     */
+    private Role getRole(String name)
+    {
+        try
+        {
+            // If it exists, try the legacy users table in case the cluster
+            // is in the process of being upgraded and so is running with mixed
+            // versions of the authn schema.
+            return (Schema.instance.getCFMetaData(AuthKeyspace.NAME, "users") != null)
+                    ? getRoleFromTable(name, legacySelectUserStatement, LEGACY_ROW_TO_ROLE)
+                    : getRoleFromTable(name, loadRoleStatement, ROW_TO_ROLE);
+        }
+        catch (RequestExecutionException | RequestValidationException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private Role getRoleFromTable(String name, SelectStatement statement, Function<UntypedResultSet.Row, Role> function)
+    throws RequestExecutionException, RequestValidationException
+    {
+        ResultMessage.Rows rows =
+            statement.execute(QueryState.forInternalCalls(),
+                              QueryOptions.forInternalCalls(consistencyForRole(name),
+                                                            Collections.singletonList(ByteBufferUtil.bytes(name))));
+        if (rows.result.isEmpty())
+            return NULL_ROLE;
+
+        return function.apply(UntypedResultSet.create(rows.result).one());
+    }
+
+    /*
+     * Adds or removes a role name from the membership list of an entry in the roles table table
+     * (adds if op is "+", removes if op is "-")
+     */
+    private void modifyRoleMembership(String grantee, String role, String op)
+    throws RequestExecutionException
+    {
+        process(String.format("UPDATE %s.%s SET member_of = member_of %s {'%s'} WHERE role = '%s'",
+                              AuthKeyspace.NAME,
+                              AuthKeyspace.ROLES,
+                              op,
+                              escape(role),
+                              escape(grantee)),
+                consistencyForRole(grantee));
+    }
+
+    /*
+     * Clear the membership list of the given role
+     */
+    private void removeAllMembers(String role) throws RequestValidationException, RequestExecutionException
+    {
+        // Get the membership list of the the given role
+        UntypedResultSet rows = process(String.format("SELECT member FROM %s.%s WHERE role = '%s'",
+                                                      AuthKeyspace.NAME,
+                                                      AuthKeyspace.ROLE_MEMBERS,
+                                                      escape(role)),
+                                        consistencyForRole(role));
+        if (rows.isEmpty())
+            return;
+
+        // Update each member in the list, removing this role from its own list of granted roles
+        for (UntypedResultSet.Row row : rows)
+            modifyRoleMembership(row.getString("member"), role, "-");
+
+        // Finally, remove the membership list for the dropped role
+        process(String.format("DELETE FROM %s.%s WHERE role = '%s'",
+                              AuthKeyspace.NAME,
+                              AuthKeyspace.ROLE_MEMBERS,
+                              escape(role)),
+                consistencyForRole(role));
+    }
+
+    /*
+     * Convert a map of Options from a CREATE/ALTER statement into
+     * assignment clauses used to construct a CQL UPDATE statement
+     */
+    private Iterable<String> optionsToAssignments(Map<Option, Object> options)
+    {
+        return Iterables.transform(
+                                  options.entrySet(),
+                                  new Function<Map.Entry<Option, Object>, String>()
+                                  {
+                                      public String apply(Map.Entry<Option, Object> entry)
+                                      {
+                                          switch (entry.getKey())
+                                          {
+                                              case LOGIN:
+                                                  return String.format("can_login = %s", entry.getValue());
+                                              case SUPERUSER:
+                                                  return String.format("is_superuser = %s", entry.getValue());
+                                              case PASSWORD:
+                                                  return String.format("salted_hash = '%s'", escape(hashpw((String) entry.getValue())));
+                                              default:
+                                                  return null;
+                                          }
+                                      }
+                                  });
+    }
+
+    protected static ConsistencyLevel consistencyForRole(String role)
+    {
+        if (role.equals(DEFAULT_SUPERUSER_NAME))
+            return ConsistencyLevel.QUORUM;
+        else
+            return ConsistencyLevel.LOCAL_ONE;
+    }
+
+    private static String hashpw(String password)
+    {
+        return BCrypt.hashpw(password, BCrypt.gensalt(GENSALT_LOG2_ROUNDS));
+    }
+
+    private static String escape(String name)
+    {
+        return StringUtils.replace(name, "'", "''");
+    }
+
+    private static UntypedResultSet process(String query, ConsistencyLevel consistencyLevel) throws RequestExecutionException
+    {
+        return QueryProcessor.process(query, consistencyLevel);
+    }
+
+    private static final class Role
+    {
+        private String name;
+        private final boolean isSuper;
+        private final boolean canLogin;
+        private Set<String> memberOf;
+
+        private Role(String name, boolean isSuper, boolean canLogin, Set<String> memberOf)
+        {
+            this.name = name;
+            this.isSuper = isSuper;
+            this.canLogin = canLogin;
+            this.memberOf = memberOf;
+        }
+
+        public boolean equals(Object o)
+        {
+            if (this == o)
+                return true;
+
+            if (!(o instanceof Role))
+                return false;
+
+            Role r = (Role) o;
+            return Objects.equal(name, r.name);
+        }
+
+        public int hashCode()
+        {
+            return Objects.hashCode(name);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/879b694d/src/java/org/apache/cassandra/auth/DataResource.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/auth/DataResource.java b/src/java/org/apache/cassandra/auth/DataResource.java
index 75a3fdf..dcd2665 100644
--- a/src/java/org/apache/cassandra/auth/DataResource.java
+++ b/src/java/org/apache/cassandra/auth/DataResource.java
@@ -25,16 +25,16 @@ import org.apache.cassandra.config.Schema;
 /**
  * The primary type of resource in Cassandra.
  *
- * Used to represent a column family or a keyspace or the root level "data" resource.
+ * Used to represent a table or a keyspace or the root level "data" resource.
  * "data"                                 - the root level data resource.
  * "data/keyspace_name"                   - keyspace-level data resource.
- * "data/keyspace_name/column_family_name" - cf-level data resource.
+ * "data/keyspace_name/table_name"        - table-level data resource.
  */
 public class DataResource implements IResource
 {
     enum Level
     {
-        ROOT, KEYSPACE, COLUMN_FAMILY
+        ROOT, KEYSPACE, TABLE
     }
 
     private static final String ROOT_NAME = "data";
@@ -42,27 +42,27 @@ public class DataResource implements IResource
 
     private final Level level;
     private final String keyspace;
-    private final String columnFamily;
+    private final String table;
 
     private DataResource()
     {
         level = Level.ROOT;
         keyspace = null;
-        columnFamily = null;
+        table = null;
     }
 
     private DataResource(String keyspace)
     {
         level = Level.KEYSPACE;
         this.keyspace = keyspace;
-        columnFamily = null;
+        table = null;
     }
 
-    private DataResource(String keyspace, String columnFamily)
+    private DataResource(String keyspace, String table)
     {
-        level = Level.COLUMN_FAMILY;
+        level = Level.TABLE;
         this.keyspace = keyspace;
-        this.columnFamily = columnFamily;
+        this.table = table;
     }
 
     /**
@@ -85,15 +85,15 @@ public class DataResource implements IResource
     }
 
     /**
-     * Creates a DataResource instance representing a column family.
+     * Creates a DataResource instance representing a table.
      *
      * @param keyspace Name of the keyspace.
-     * @param columnFamily Name of the column family.
+     * @param table Name of the table.
      * @return DataResource instance representing the column family.
      */
-    public static DataResource columnFamily(String keyspace, String columnFamily)
+    public static DataResource table(String keyspace, String table)
     {
-        return new DataResource(keyspace, columnFamily);
+        return new DataResource(keyspace, table);
     }
 
     /**
@@ -115,7 +115,7 @@ public class DataResource implements IResource
         if (parts.length == 2)
             return keyspace(parts[1]);
 
-        return columnFamily(parts[1], parts[2]);
+        return table(parts[1], parts[2]);
     }
 
     /**
@@ -129,8 +129,8 @@ public class DataResource implements IResource
                 return ROOT_NAME;
             case KEYSPACE:
                 return String.format("%s/%s", ROOT_NAME, keyspace);
-            case COLUMN_FAMILY:
-                return String.format("%s/%s/%s", ROOT_NAME, keyspace, columnFamily);
+            case TABLE:
+                return String.format("%s/%s/%s", ROOT_NAME, keyspace, table);
         }
         throw new AssertionError();
     }
@@ -144,7 +144,7 @@ public class DataResource implements IResource
         {
             case KEYSPACE:
                 return root();
-            case COLUMN_FAMILY:
+            case TABLE:
                 return keyspace(keyspace);
         }
         throw new IllegalStateException("Root-level resource can't have a parent");
@@ -160,9 +160,9 @@ public class DataResource implements IResource
         return level == Level.KEYSPACE;
     }
 
-    public boolean isColumnFamilyLevel()
+    public boolean isTableLevel()
     {
-        return level == Level.COLUMN_FAMILY;
+        return level == Level.TABLE;
     }
     /**
      * @return keyspace of the resource. Throws IllegalStateException if it's the root-level resource.
@@ -175,13 +175,13 @@ public class DataResource implements IResource
     }
 
     /**
-     * @return column family of the resource. Throws IllegalStateException if it's not a cf-level resource.
+     * @return column family of the resource. Throws IllegalStateException if it's not a table-level resource.
      */
-    public String getColumnFamily()
+    public String getTable()
     {
-        if (!isColumnFamilyLevel())
+        if (!isTableLevel())
             throw new IllegalStateException(String.format("%s data resource has no table", level));
-        return columnFamily;
+        return table;
     }
 
     /**
@@ -205,8 +205,8 @@ public class DataResource implements IResource
                 return true;
             case KEYSPACE:
                 return Schema.instance.getKeyspaces().contains(keyspace);
-            case COLUMN_FAMILY:
-                return Schema.instance.getCFMetaData(keyspace, columnFamily) != null;
+            case TABLE:
+                return Schema.instance.getCFMetaData(keyspace, table) != null;
         }
         throw new AssertionError();
     }
@@ -220,8 +220,8 @@ public class DataResource implements IResource
                 return "<all keyspaces>";
             case KEYSPACE:
                 return String.format("<keyspace %s>", keyspace);
-            case COLUMN_FAMILY:
-                return String.format("<table %s.%s>", keyspace, columnFamily);
+            case TABLE:
+                return String.format("<table %s.%s>", keyspace, table);
         }
         throw new AssertionError();
     }
@@ -239,12 +239,12 @@ public class DataResource implements IResource
 
         return Objects.equal(level, ds.level)
             && Objects.equal(keyspace, ds.keyspace)
-            && Objects.equal(columnFamily, ds.columnFamily);
+            && Objects.equal(table, ds.table);
     }
 
     @Override
     public int hashCode()
     {
-        return Objects.hashCode(level, keyspace, columnFamily);
+        return Objects.hashCode(level, keyspace, table);
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/879b694d/src/java/org/apache/cassandra/auth/IAuthenticator.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/auth/IAuthenticator.java b/src/java/org/apache/cassandra/auth/IAuthenticator.java
index 6086490..24792f6 100644
--- a/src/java/org/apache/cassandra/auth/IAuthenticator.java
+++ b/src/java/org/apache/cassandra/auth/IAuthenticator.java
@@ -22,103 +22,107 @@ import java.util.Set;
 
 import org.apache.cassandra.exceptions.AuthenticationException;
 import org.apache.cassandra.exceptions.ConfigurationException;
-import org.apache.cassandra.exceptions.RequestExecutionException;
-import org.apache.cassandra.exceptions.RequestValidationException;
 
 public interface IAuthenticator
 {
-    static final String USERNAME_KEY = "username";
-    static final String PASSWORD_KEY = "password";
-
-    /**
-     * Supported CREATE USER/ALTER USER options.
-     * Currently only PASSWORD is available.
-     */
-    enum Option
-    {
-        PASSWORD
-    }
-
     /**
      * Whether or not the authenticator requires explicit login.
      * If false will instantiate user with AuthenticatedUser.ANONYMOUS_USER.
      */
     boolean requireAuthentication();
 
-    /**
-     * Set of options supported by CREATE USER and ALTER USER queries.
-     * Should never return null - always return an empty set instead.
-     */
-    Set<Option> supportedOptions();
-
-    /**
-     * Subset of supportedOptions that users are allowed to alter when performing ALTER USER [themselves].
-     * Should never return null - always return an empty set instead.
+     /**
+     * Set of resources that should be made inaccessible to users and only accessible internally.
+     *
+     * @return Keyspaces, column families that will be unmodifiable by users; other resources.
      */
-    Set<Option> alterableOptions();
+    Set<? extends IResource> protectedResources();
 
     /**
-     * Authenticates a user given a Map<String, String> of credentials.
-     * Should never return null - always throw AuthenticationException instead.
-     * Returning AuthenticatedUser.ANONYMOUS_USER is an option as well if authentication is not required.
+     * Validates configuration of IAuthenticator implementation (if configurable).
      *
-     * @throws AuthenticationException if credentials don't match any known user.
+     * @throws ConfigurationException when there is a configuration error.
      */
-    AuthenticatedUser authenticate(Map<String, String> credentials) throws AuthenticationException;
+    void validateConfiguration() throws ConfigurationException;
 
     /**
-     * Called during execution of CREATE USER query (also may be called on startup, see seedSuperuserOptions method).
-     * If authenticator is static then the body of the method should be left blank, but don't throw an exception.
-     * options are guaranteed to be a subset of supportedOptions().
+     * Setup is called once upon system startup to initialize the IAuthenticator.
      *
-     * @param username Username of the user to create.
-     * @param options Options the user will be created with.
-     * @throws RequestValidationException
-     * @throws RequestExecutionException
+     * For example, use this method to create any required keyspaces/column families.
      */
-    void create(String username, Map<Option, Object> options) throws RequestValidationException, RequestExecutionException;
+    void setup();
 
     /**
-     * Called during execution of ALTER USER query.
-     * options are always guaranteed to be a subset of supportedOptions(). Furthermore, if the user performing the query
-     * is not a superuser and is altering himself, then options are guaranteed to be a subset of alterableOptions().
-     * Keep the body of the method blank if your implementation doesn't support any options.
-     *
-     * @param username Username of the user that will be altered.
-     * @param options Options to alter.
-     * @throws RequestValidationException
-     * @throws RequestExecutionException
+     * Provide a SASL handler to perform authentication for an single connection. SASL
+     * is a stateful protocol, so a new instance must be used for each authentication
+     * attempt.
+     * @return org.apache.cassandra.auth.IAuthenticator.SaslNegotiator implementation
+     * (see {@link org.apache.cassandra.auth.PasswordAuthenticator.PlainTextSaslAuthenticator})
      */
-    void alter(String username, Map<Option, Object> options) throws RequestValidationException, RequestExecutionException;
-
+    SaslNegotiator newSaslNegotiator();
 
     /**
-     * Called during execution of DROP USER query.
+     * For implementations which support the Thrift login method that accepts arbitrary
+     * key/value pairs containing credentials data.
+     * Also used by CQL native protocol v1, in which username and password are sent from
+     * client to server in a {@link org.apache.cassandra.transport.messages.CredentialsMessage}
+     * Implementations where support for Thrift and CQL protocol v1 is not required should make
+     * this an unsupported operation.
      *
-     * @param username Username of the user that will be dropped.
-     * @throws RequestValidationException
-     * @throws RequestExecutionException
-     */
-    void drop(String username) throws RequestValidationException, RequestExecutionException;
-
-     /**
-     * Set of resources that should be made inaccessible to users and only accessible internally.
+     * Should never return null - always throw AuthenticationException instead.
+     * Returning AuthenticatedUser.ANONYMOUS_USER is an option as well if authentication is not required.
      *
-     * @return Keyspaces, column families that will be unmodifiable by users; other resources.
+     * @param credentials implementation specific key/value pairs
+     * @return non-null representation of the authenticated subject
+     * @throws AuthenticationException
      */
-    Set<? extends IResource> protectedResources();
+    AuthenticatedUser legacyAuthenticate(Map<String, String> credentials) throws AuthenticationException;
 
     /**
-     * Validates configuration of IAuthenticator implementation (if configurable).
-     *
-     * @throws ConfigurationException when there is a configuration error.
+     * Performs the actual SASL negotiation for a single authentication attempt.
+     * SASL is stateful, so a new instance should be used for each attempt.
+     * Non-trivial implementations may delegate to an instance of {@link javax.security.sasl.SaslServer}
      */
-    void validateConfiguration() throws ConfigurationException;
+    public interface SaslNegotiator
+    {
+        /**
+         * Evaluates the client response data and generates a byte[] reply which may be a further challenge or purely
+         * informational in the case that the negotiation is completed on this round.
+         *
+         * This method is called each time a {@link org.apache.cassandra.transport.messages.AuthResponse} is received
+         * from a client. After it is called, {@link isComplete()} is checked to determine whether the negotiation has
+         * finished. If so, an AuthenticatedUser is obtained by calling {@link getAuthenticatedUser()} and that user
+         * associated with the active connection and the byte[] sent back to the client via an
+         * {@link org.apache.cassandra.transport.messages.AuthSuccess} message. If the negotiation is not yet complete,
+         * the byte[] is returned to the client as a further challenge in an
+         * {@link org.apache.cassandra.transport.messages.AuthChallenge} message. This continues until the negotiation
+         * does complete or an error is encountered.
+         *
+         * @param clientResponse The non-null (but possibly empty) response sent by the client
+         * @return The possibly null response to send to the client.
+         * @throws AuthenticationException
+         * see {@link javax.security.sasl.SaslServer#evaluateResponse(byte[])}
+         */
+        public byte[] evaluateResponse(byte[] clientResponse) throws AuthenticationException;
 
-    /**
-     * Setup is called once upon system startup to initialize the IAuthenticator.
-     *
-     * For example, use this method to create any required keyspaces/column families.
-     */
-    void setup();
+        /**
+         * Called after each invocation of {@link evaluateResponse(byte[])} to determine whether the  authentication has
+         * completed successfully or should be continued.
+         *
+         * @return true if the authentication exchange has completed; false otherwise.
+         * see {@link javax.security.sasl.SaslServer#isComplete()}
+         */
+        public boolean isComplete();
+
+        /**
+         * Following a sucessful negotiation, get the AuthenticatedUser representing the logged in subject.
+         * This method should only be called if {@link isComplete()} returns true.
+         * Should never return null - always throw AuthenticationException instead.
+         * Returning AuthenticatedUser.ANONYMOUS_USER is an option if authentication is not required.
+         *
+         * @return non-null representation of the authenticated subject
+         * @throws AuthenticationException
+         */
+        public AuthenticatedUser getAuthenticatedUser() throws AuthenticationException;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/879b694d/src/java/org/apache/cassandra/auth/IAuthorizer.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/auth/IAuthorizer.java b/src/java/org/apache/cassandra/auth/IAuthorizer.java
index 8ad204f..7e3240a 100644
--- a/src/java/org/apache/cassandra/auth/IAuthorizer.java
+++ b/src/java/org/apache/cassandra/auth/IAuthorizer.java
@@ -29,7 +29,12 @@ import org.apache.cassandra.exceptions.RequestValidationException;
 public interface IAuthorizer
 {
     /**
-     * The primary IAuthorizer method. Returns a set of permissions of a user on a resource.
+     * Returns a set of permissions of a user on a resource.
+     * Since Roles were introduced in version 3.0, Cassandra does not distinguish in any
+     * meaningful way between users and roles. A role may or may not have login privileges
+     * and roles may be granted to other roles. In fact, Cassandra does not really have the
+     * concept of a user, except to link a client session to role. AuthenticatedUser can be
+     * thought of as a manifestation of a role, linked to a specific client connection.
      *
      * @param user Authenticated user requesting authorization.
      * @param resource Resource for which the authorization is being requested. @see DataResource.
@@ -38,18 +43,18 @@ public interface IAuthorizer
     Set<Permission> authorize(AuthenticatedUser user, IResource resource);
 
     /**
-     * Grants a set of permissions on a resource to a user.
+     * Grants a set of permissions on a resource to a role.
      * The opposite of revoke().
      *
      * @param performer User who grants the permissions.
      * @param permissions Set of permissions to grant.
-     * @param to Grantee of the permissions.
+     * @param to Name of the role to which the permissions are to be granted.
      * @param resource Resource on which to grant the permissions.
      *
      * @throws RequestValidationException
      * @throws RequestExecutionException
      */
-    void grant(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String to)
+    void grant(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String grantee)
     throws RequestValidationException, RequestExecutionException;
 
     /**
@@ -58,39 +63,41 @@ public interface IAuthorizer
      *
      * @param performer User who revokes the permissions.
      * @param permissions Set of permissions to revoke.
-     * @param from Revokee of the permissions.
+     * @param revokee Name of the role from which to the permissions are to be revoked.
      * @param resource Resource on which to revoke the permissions.
      *
      * @throws RequestValidationException
      * @throws RequestExecutionException
      */
-    void revoke(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String from)
+    void revoke(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String revokee)
     throws RequestValidationException, RequestExecutionException;
 
     /**
-     * Returns a list of permissions on a resource of a user.
+     * Returns a list of permissions on a resource granted to a role.
      *
      * @param performer User who wants to see the permissions.
-     * @param permissions Set of Permission values the user is interested in. The result should only include the matching ones.
-     * @param resource The resource on which permissions are requested. Can be null, in which case permissions on all resources
-     *                 should be returned.
-     * @param of The user whose permissions are requested. Can be null, in which case permissions of every user should be returned.
+     * @param permissions Set of Permission values the user is interested in. The result should only include the
+     *                    matching ones.
+     * @param resource The resource on which permissions are requested. Can be null, in which case permissions on all
+     *                 resources should be returned.
+     * @param of The name of the role whose permissions are requested. Can be null, in which case permissions of every
+     *           role should be returned.
      *
      * @return All of the matching permission that the requesting user is authorized to know about.
      *
      * @throws RequestValidationException
      * @throws RequestExecutionException
      */
-    Set<PermissionDetails> list(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String of)
+    Set<PermissionDetails> list(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String grantee)
     throws RequestValidationException, RequestExecutionException;
 
     /**
-     * This method is called before deleting a user with DROP USER query so that a new user with the same
-     * name wouldn't inherit permissions of the deleted user in the future.
+     * Called before deleting a role with DROP ROLE statement (or the alias provided for compatibility,
+     * DROP USER) so that a new role with the same name wouldn't inherit permissions of the deleted one in the future.
      *
-     * @param droppedUser The user to revoke all permissions from.
+     * @param revokee The role to revoke all permissions from.
      */
-    void revokeAll(String droppedUser);
+    void revokeAll(String revokee);
 
     /**
      * This method is called after a resource is removed (i.e. keyspace or a table is dropped).

http://git-wip-us.apache.org/repos/asf/cassandra/blob/879b694d/src/java/org/apache/cassandra/auth/IRoleManager.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/auth/IRoleManager.java b/src/java/org/apache/cassandra/auth/IRoleManager.java
new file mode 100644
index 0000000..4307c5d
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/IRoleManager.java
@@ -0,0 +1,200 @@
+/*
+ * 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.Map;
+import java.util.Set;
+
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.exceptions.RequestExecutionException;
+import org.apache.cassandra.exceptions.RequestValidationException;
+
+/**
+ * Responsible for managing roles (which also includes what
+ * used to be known as users), including creation, deletion,
+ * alteration and the granting & revoking of roles to other
+ * roles.
+ */
+public interface IRoleManager
+{
+
+    /**
+     * Supported options for CREATE ROLE/ALTER ROLE (and
+     * CREATE USER/ALTER USER, which are aliases provided
+     * for backwards compatibility).
+     */
+    public enum Option
+    {
+        SUPERUSER, PASSWORD, LOGIN, OPTIONS
+    }
+
+    /**
+     * Set of options supported by CREATE ROLE and ALTER ROLE queries.
+     * Should never return null - always return an empty set instead.
+     */
+    Set<Option> supportedOptions();
+
+    /**
+     * Subset of supportedOptions that users are allowed to alter when performing ALTER ROLE [themselves].
+     * Should never return null - always return an empty set instead.
+     */
+    Set<Option> alterableOptions();
+
+    /**
+     * Called during execution of a CREATE ROLE statement.
+     * options are guaranteed to be a subset of supportedOptions().
+     *
+     * @param performer User issuing the create role statement.
+     * @param role Name of the role being created
+     * @param options Options the role will be created with
+     * @throws RequestValidationException
+     * @throws RequestExecutionException
+     */
+    void createRole(AuthenticatedUser performer, String role, Map<Option, Object> options)
+    throws RequestValidationException, RequestExecutionException;
+
+    /**
+     * Called during execution of DROP ROLE statement, as well we removing any main record of the role from the system
+     * this implies that we want to revoke this role from all other roles that it has been granted to.
+     *
+     * @param performer User issuing the drop role statement.
+     * @param role The name of the role to be dropped.
+     * @throws RequestValidationException
+     * @throws RequestExecutionException
+     */
+    void dropRole(AuthenticatedUser performer, String role) throws RequestValidationException, RequestExecutionException;
+
+    /**
+     * Called during execution of ALTER ROLE statement.
+     * options are always guaranteed to be a subset of supportedOptions(). Furthermore, if the actor performing the query
+     * is not a superuser and is altering themself, then options are guaranteed to be a subset of alterableOptions().
+     * Keep the body of the method blank if your implementation doesn't support modification of any options.
+     *
+     * @param performer User issuing the alter role statement.
+     * @param role Name of the role that will be altered.
+     * @param options Options to alter.
+     * @throws RequestValidationException
+     * @throws RequestExecutionException
+     */
+    void alterRole(AuthenticatedUser performer, String role, Map<Option, Object> options)
+    throws RequestValidationException, RequestExecutionException;
+
+    /**
+     * Called during execution of GRANT ROLE query.
+     * Grant an role to another existing role. A grantee that has a role granted to it will inherit any
+     * permissions of the granted role.
+     *
+     * @param performer User issuing the grant statement.
+     * @param role The name of the role to be granted to the grantee.
+     * @param grantee The name of the role acting as the grantee.
+     * @throws RequestValidationException
+     * @throws RequestExecutionException
+     */
+    void grantRole(AuthenticatedUser performer, String role, String grantee)
+    throws RequestValidationException, RequestExecutionException;
+
+    /**
+     * Called during the execution of a REVOKE ROLE query.
+     * Revoke an granted role from an existing role. The revokee will lose any permissions inherited from the role being
+     * revoked.
+     *
+     * @param performer User issuing the revoke statement.
+     * @param role The name of the role to be revoked.
+     * @param revokee The name of the role from which the granted role is to be revoked.
+     * @throws RequestValidationException
+     * @throws RequestExecutionException
+     */
+    void revokeRole(AuthenticatedUser performer, String role, String revokee)
+    throws RequestValidationException, RequestExecutionException;
+
+    /**
+     * Called during execution of a LIST ROLES query.
+     * Returns a set of roles that have been granted to the grantee using GRANT ROLE.
+     *
+     * @param grantee Name of the role whose granted roles will be listed.
+     * @param includeInherited if True will list inherited roles as well as those directly granted to the grantee.
+     * @return A list containing the granted roles for the user.
+     * @throws RequestValidationException
+     * @throws RequestExecutionException
+     */
+    Set<String> getRoles(String grantee, boolean includeInherited) throws RequestValidationException, RequestExecutionException;
+
+    /**
+     * Called during the execution of an unqualified LIST ROLES query.
+     * Returns the total set of distinct roles in the system.
+     *
+     * @return the set of all roles in the system.
+     * @throws RequestValidationException
+     * @throws RequestExecutionException
+     */
+    Set<String> getAllRoles() throws RequestValidationException, RequestExecutionException;
+
+    /**
+     * Return true if there exists a Role with the given name that also has
+     * superuser status. Superuser status may be inherited from another
+     * granted role, so this method should return true if either the named
+     * Role, or any other Role it is transitively granted has superuser
+     * status.
+     *
+     * @param role name of the role
+     * @return true if the role exists and has superuser status, either
+     * directly or transitively, otherwise false.
+     */
+    boolean isSuper(String role);
+
+    /**
+     * Return true if there exists a Role with the given name which has login
+     * privileges. Such privileges is not inherited from other granted Roles
+     * and so must be directly granted to the named Role with the LOGIN option
+     * of CREATE ROLE or ALTER ROLE
+     *
+     * @param role name of the Role
+     * @return true if the role exists and is permitted to login, otherwise false
+     */
+    boolean canLogin(String role);
+
+    /**
+     * Return true is a Role with the given name exists in the system.
+     *
+     * @param role name of the Role.
+     * @return true if the name identifies an extant Role in the system,
+     * otherwise false
+     */
+    boolean isExistingRole(String role);
+
+    /**
+     * Set of resources that should be made inaccessible to users and only accessible internally.
+     *
+     * @return Keyspaces and column families that will be unmodifiable by users; other resources.
+     */
+    Set<? extends IResource> protectedResources();
+
+    /**
+     * Hook to perform validation of an implementation's configuration (if supported).
+     *
+     * @throws ConfigurationException
+     */
+    void validateConfiguration() throws ConfigurationException;
+
+    /**
+     * Hook to perform implementation specific initialization, called once upon system startup.
+     *
+     * For example, use this method to create any required keyspaces/column families.
+     */
+    void setup();
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/879b694d/src/java/org/apache/cassandra/auth/ISaslAwareAuthenticator.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/auth/ISaslAwareAuthenticator.java b/src/java/org/apache/cassandra/auth/ISaslAwareAuthenticator.java
deleted file mode 100644
index 959506f..0000000
--- a/src/java/org/apache/cassandra/auth/ISaslAwareAuthenticator.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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 org.apache.cassandra.exceptions.AuthenticationException;
-
-public interface ISaslAwareAuthenticator extends IAuthenticator
-{
-    /**
-     * Provide a SaslAuthenticator to be used by the CQL binary protocol server. If
-     * the configured IAuthenticator requires authentication but does not implement this
-     * interface we refuse to start the binary protocol server as it will have no way
-     * of authenticating clients.
-     * @return SaslAuthenticator implementation
-     * (see {@link PasswordAuthenticator.PlainTextSaslAuthenticator})
-     */
-    SaslAuthenticator newAuthenticator();
-
-
-    public interface SaslAuthenticator
-    {
-        public byte[] evaluateResponse(byte[] clientResponse) throws AuthenticationException;
-        public boolean isComplete();
-        public AuthenticatedUser getAuthenticatedUser() throws AuthenticationException;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/879b694d/src/java/org/apache/cassandra/auth/LegacyAuthenticator.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/auth/LegacyAuthenticator.java b/src/java/org/apache/cassandra/auth/LegacyAuthenticator.java
deleted file mode 100644
index c5fd8da..0000000
--- a/src/java/org/apache/cassandra/auth/LegacyAuthenticator.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.Collections;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.cassandra.exceptions.AuthenticationException;
-import org.apache.cassandra.exceptions.ConfigurationException;
-import org.apache.cassandra.exceptions.RequestExecutionException;
-import org.apache.cassandra.exceptions.RequestValidationException;
-
-/**
- * Provides a transitional IAuthenticator implementation for old-style (pre-1.2) authenticators.
- *
- * Comes with default implementation for the all of the new methods.
- * Subclass LegacyAuthenticator instead of implementing the old IAuthenticator and your old IAuthenticator
- * implementation should continue to work.
- */
-public abstract class LegacyAuthenticator implements IAuthenticator
-{
-    /**
-     * @return The user that a connection is initialized with, or 'null' if a user must call login().
-     */
-    public abstract AuthenticatedUser defaultUser();
-
-    /**
-     * @param credentials An implementation specific collection of identifying information.
-     * @return A successfully authenticated user: should throw AuthenticationException rather than ever returning null.
-     */
-    public abstract AuthenticatedUser authenticate(Map<String, String> credentials) throws AuthenticationException;
-
-    public abstract void validateConfiguration() throws ConfigurationException;
-
-    @Override
-    public boolean requireAuthentication()
-    {
-        return defaultUser() == null;
-    }
-
-    @Override
-    public Set<Option> supportedOptions()
-    {
-        return Collections.emptySet();
-    }
-
-    @Override
-    public Set<Option> alterableOptions()
-    {
-        return Collections.emptySet();
-    }
-
-    @Override
-    public void create(String username, Map<Option, Object> options) throws RequestValidationException, RequestExecutionException
-    {
-    }
-
-    @Override
-    public void alter(String username, Map<Option, Object> options) throws RequestValidationException, RequestExecutionException
-    {
-    }
-
-    @Override
-    public void drop(String username) throws RequestValidationException, RequestExecutionException
-    {
-    }
-
-    @Override
-    public Set<IResource> protectedResources()
-    {
-        return Collections.emptySet();
-    }
-
-    @Override
-    public void setup()
-    {
-    }
-}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/879b694d/src/java/org/apache/cassandra/auth/LegacyAuthorizer.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/auth/LegacyAuthorizer.java b/src/java/org/apache/cassandra/auth/LegacyAuthorizer.java
deleted file mode 100644
index f834793..0000000
--- a/src/java/org/apache/cassandra/auth/LegacyAuthorizer.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.*;
-
-import org.apache.cassandra.exceptions.ConfigurationException;
-import org.apache.cassandra.exceptions.InvalidRequestException;
-import org.apache.cassandra.exceptions.UnauthorizedException;
-
-/**
- * Provides a transitional IAuthorizer implementation for old-style (pre-1.2) authorizers.
- *
- * Translates old-style authorize() calls to the new-style, expands Permission.READ and Permission.WRITE
- * into the new Permission values, translates the new resource hierarchy into the old hierarchy.
- * Stubs the rest of the new methods.
- * Subclass LegacyAuthorizer instead of implementing the old IAuthority and your old IAuthority implementation should
- * continue to work.
- */
-public abstract class LegacyAuthorizer implements IAuthorizer
-{
-    /**
-     * @param user Authenticated user requesting authorization.
-     * @param resource List of Objects containing Strings and byte[]s: represents a resource in the old hierarchy.
-     * @return Set of permissions of the user on the resource. Should never return null. Use Permission.NONE instead.
-     */
-    public abstract EnumSet<Permission> authorize(AuthenticatedUser user, List<Object> resource);
-
-    public abstract void validateConfiguration() throws ConfigurationException;
-
-    /**
-     * Translates new-style authorize() method call to the old-style (including permissions and the hierarchy).
-     */
-    @Override
-    public Set<Permission> authorize(AuthenticatedUser user, IResource resource)
-    {
-        if (!(resource instanceof DataResource))
-            throw new IllegalArgumentException(String.format("%s resource is not supported by LegacyAuthorizer", resource.getName()));
-        DataResource dr = (DataResource) resource;
-
-        List<Object> legacyResource = new ArrayList<Object>();
-        legacyResource.add(Resources.ROOT);
-        legacyResource.add(Resources.KEYSPACES);
-        if (!dr.isRootLevel())
-            legacyResource.add(dr.getKeyspace());
-        if (dr.isColumnFamilyLevel())
-            legacyResource.add(dr.getColumnFamily());
-
-        Set<Permission> permissions = authorize(user, legacyResource);
-        if (permissions.contains(Permission.READ))
-            permissions.add(Permission.SELECT);
-        if (permissions.contains(Permission.WRITE))
-            permissions.addAll(EnumSet.of(Permission.CREATE, Permission.ALTER, Permission.DROP, Permission.MODIFY));
-
-        return permissions;
-    }
-
-    @Override
-    public void grant(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String to)
-    throws InvalidRequestException
-    {
-        throw new InvalidRequestException("GRANT operation is not supported by LegacyAuthorizer");
-    }
-
-    @Override
-    public void revoke(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String from)
-    throws InvalidRequestException
-    {
-        throw new InvalidRequestException("REVOKE operation is not supported by LegacyAuthorizer");
-    }
-
-    @Override
-    public void revokeAll(String droppedUser)
-    {
-    }
-
-    @Override
-    public void revokeAll(IResource droppedResource)
-    {
-    }
-
-    @Override
-    public Set<PermissionDetails> list(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String of)
-    throws InvalidRequestException, UnauthorizedException
-    {
-        throw new InvalidRequestException("LIST PERMISSIONS operation is not supported by LegacyAuthorizer");
-    }
-
-    @Override
-    public Set<IResource> protectedResources()
-    {
-        return Collections.emptySet();
-    }
-
-    @Override
-    public void setup()
-    {
-    }
-}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/879b694d/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java b/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java
index 9570770..14a6ecf 100644
--- a/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java
+++ b/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java
@@ -19,264 +19,181 @@ package org.apache.cassandra.auth;
 
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.TimeUnit;
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
-import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import org.apache.cassandra.concurrent.ScheduledExecutors;
-import org.apache.cassandra.cql3.UntypedResultSet;
-import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.config.Schema;
 import org.apache.cassandra.cql3.QueryOptions;
+import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.cql3.UntypedResultSet;
 import org.apache.cassandra.cql3.statements.SelectStatement;
-import org.apache.cassandra.db.ConsistencyLevel;
 import org.apache.cassandra.exceptions.*;
+import org.apache.cassandra.service.ClientState;
 import org.apache.cassandra.service.QueryState;
 import org.apache.cassandra.transport.messages.ResultMessage;
 import org.apache.cassandra.utils.ByteBufferUtil;
 import org.mindrot.jbcrypt.BCrypt;
 
+import static org.apache.cassandra.auth.CassandraRoleManager.consistencyForRole;
+
 /**
  * PasswordAuthenticator is an IAuthenticator implementation
- * that keeps credentials (usernames and bcrypt-hashed passwords)
- * internally in C* - in system_auth.credentials CQL3 table.
+ * that keeps credentials (rolenames and bcrypt-hashed passwords)
+ * internally in C* - in system_auth.roles CQL3 table.
+ * Since 3.0, the management of roles (creation, modification,
+ * querying etc is the responsibility of IRoleManager. Use of
+ * PasswordAuthenticator requires the use of CassandraRoleManager
+ * for storage & retrieval of encryted passwords.
  */
-public class PasswordAuthenticator implements ISaslAwareAuthenticator
+public class PasswordAuthenticator implements IAuthenticator
 {
     private static final Logger logger = LoggerFactory.getLogger(PasswordAuthenticator.class);
 
-    // 2 ** GENSALT_LOG2_ROUNS rounds of hashing will be performed.
-    private static final int GENSALT_LOG2_ROUNDS = 10;
-
     // name of the hash column.
     private static final String SALTED_HASH = "salted_hash";
 
-    private static final String DEFAULT_USER_NAME = Auth.DEFAULT_SUPERUSER_NAME;
-    private static final String DEFAULT_USER_PASSWORD = Auth.DEFAULT_SUPERUSER_NAME;
-
-    private static final String CREDENTIALS_CF = "credentials";
-    private static final String CREDENTIALS_CF_SCHEMA = String.format("CREATE TABLE %s.%s ("
-                                                                      + "username text,"
-                                                                      + "salted_hash text," // salt + hash + number of rounds
-                                                                      + "options map<text,text>," // for future extensions
-                                                                      + "PRIMARY KEY(username)"
-                                                                      + ") WITH gc_grace_seconds=%d",
-                                                                      Auth.AUTH_KS,
-                                                                      CREDENTIALS_CF,
-                                                                      90 * 24 * 60 * 60); // 3 months.
+    // really this is a rolename now, but as it only matters for Thrift, we leave it for backwards compatibility
+    public static final String USERNAME_KEY = "username";
+    public static final String PASSWORD_KEY = "password";
 
+    private static final byte NUL = 0;
     private SelectStatement authenticateStatement;
 
+    public static final String LEGACY_CREDENTIALS_TABLE = "credentials";
+    private SelectStatement legacyAuthenticateStatement;
+
     // No anonymous access.
     public boolean requireAuthentication()
     {
         return true;
     }
 
-    public Set<Option> supportedOptions()
-    {
-        return ImmutableSet.of(Option.PASSWORD);
-    }
-
-    // Let users alter their own password.
-    public Set<Option> alterableOptions()
+    private AuthenticatedUser authenticate(String username, String password) throws AuthenticationException
     {
-        return ImmutableSet.of(Option.PASSWORD);
-    }
-
-    public AuthenticatedUser authenticate(Map<String, String> credentials) throws AuthenticationException
-    {
-        String username = credentials.get(USERNAME_KEY);
-        if (username == null)
-            throw new AuthenticationException(String.format("Required key '%s' is missing", USERNAME_KEY));
-
-        String password = credentials.get(PASSWORD_KEY);
-        if (password == null)
-            throw new AuthenticationException(String.format("Required key '%s' is missing", PASSWORD_KEY));
-
-        UntypedResultSet result;
         try
         {
-            ResultMessage.Rows rows = authenticateStatement.execute(QueryState.forInternalCalls(),
-                                                                    QueryOptions.forInternalCalls(consistencyForUser(username),
-                                                                                                  Lists.newArrayList(ByteBufferUtil.bytes(username))));
-            result = UntypedResultSet.create(rows.result);
-        }
-        catch (RequestValidationException e)
-        {
-            throw new AssertionError(e); // not supposed to happen
+            // If the legacy users table exists try to verify credentials there. This is to handle the case
+            // where the cluster is being upgraded and so is running with mixed versions of the authn tables
+            SelectStatement authenticationStatement = Schema.instance.getCFMetaData(AuthKeyspace.NAME, LEGACY_CREDENTIALS_TABLE) == null
+                                                    ? authenticateStatement
+                                                    : legacyAuthenticateStatement;
+            return doAuthenticate(username, password, authenticationStatement);
         }
         catch (RequestExecutionException e)
         {
+            logger.debug("Error performing internal authentication", e);
             throw new AuthenticationException(e.toString());
         }
-
-        if (result.isEmpty() || !BCrypt.checkpw(password, result.one().getString(SALTED_HASH)))
-            throw new AuthenticationException("Username and/or password are incorrect");
-
-        return new AuthenticatedUser(username);
     }
 
-    public void create(String username, Map<Option, Object> options) throws InvalidRequestException, RequestExecutionException
+    public Set<DataResource> protectedResources()
     {
-        String password = (String) options.get(Option.PASSWORD);
-        if (password == null)
-            throw new InvalidRequestException("PasswordAuthenticator requires PASSWORD option");
-
-        process(String.format("INSERT INTO %s.%s (username, salted_hash) VALUES ('%s', '%s')",
-                              Auth.AUTH_KS,
-                              CREDENTIALS_CF,
-                              escape(username),
-                              escape(hashpw(password))),
-                consistencyForUser(username));
+        // Also protected by CassandraRoleManager, but the duplication doesn't hurt and is more explicit
+        return ImmutableSet.of(DataResource.table(AuthKeyspace.NAME, AuthKeyspace.ROLES));
     }
 
-    public void alter(String username, Map<Option, Object> options) throws RequestExecutionException
+    public void validateConfiguration() throws ConfigurationException
     {
-        process(String.format("UPDATE %s.%s SET salted_hash = '%s' WHERE username = '%s'",
-                              Auth.AUTH_KS,
-                              CREDENTIALS_CF,
-                              escape(hashpw((String) options.get(Option.PASSWORD))),
-                              escape(username)),
-                consistencyForUser(username));
     }
 
-    public void drop(String username) throws RequestExecutionException
+    public void setup()
     {
-        process(String.format("DELETE FROM %s.%s WHERE username = '%s'", Auth.AUTH_KS, CREDENTIALS_CF, escape(username)),
-                consistencyForUser(username));
+        String query = String.format("SELECT %s FROM %s.%s WHERE role = ?",
+                                     SALTED_HASH,
+                                     AuthKeyspace.NAME,
+                                     AuthKeyspace.ROLES);
+        authenticateStatement = prepare(query);
+
+        if (Schema.instance.getCFMetaData(AuthKeyspace.NAME, LEGACY_CREDENTIALS_TABLE) != null)
+        {
+            query = String.format("SELECT %s from %s.%s WHERE username = ?",
+                                  SALTED_HASH,
+                                  AuthKeyspace.NAME,
+                                  LEGACY_CREDENTIALS_TABLE);
+            legacyAuthenticateStatement = prepare(query);
+        }
     }
 
-    public Set<DataResource> protectedResources()
+    public AuthenticatedUser legacyAuthenticate(Map<String, String> credentials) throws AuthenticationException
     {
-        return ImmutableSet.of(DataResource.columnFamily(Auth.AUTH_KS, CREDENTIALS_CF));
+        String username = credentials.get(USERNAME_KEY);
+        if (username == null)
+            throw new AuthenticationException(String.format("Required key '%s' is missing", USERNAME_KEY));
+
+        String password = credentials.get(PASSWORD_KEY);
+        if (password == null)
+            throw new AuthenticationException(String.format("Required key '%s' is missing", PASSWORD_KEY));
+
+        return authenticate(username, password);
     }
 
-    public void validateConfiguration() throws ConfigurationException
+    public SaslNegotiator newSaslNegotiator()
     {
+        return new PlainTextSaslAuthenticator();
     }
 
-    public void setup()
+    private AuthenticatedUser doAuthenticate(String username, String password, SelectStatement authenticationStatement)
+    throws RequestExecutionException, AuthenticationException
     {
-        Auth.setupTable(CREDENTIALS_CF, CREDENTIALS_CF_SCHEMA);
-
-        // the delay is here to give the node some time to see its peers - to reduce
-        // "skipped default user setup: some nodes are were not ready" log spam.
-        // It's the only reason for the delay.
-        ScheduledExecutors.nonPeriodicTasks.schedule(new Runnable()
-        {
-            public void run()
-            {
-              setupDefaultUser();
-            }
-        }, Auth.SUPERUSER_SETUP_DELAY, TimeUnit.MILLISECONDS);
-
+        UntypedResultSet result;
         try
         {
-            String query = String.format("SELECT %s FROM %s.%s WHERE username = ?",
-                                         SALTED_HASH,
-                                         Auth.AUTH_KS,
-                                         CREDENTIALS_CF);
-            authenticateStatement = (SelectStatement) QueryProcessor.parseStatement(query).prepare().statement;
+            ResultMessage.Rows rows = authenticationStatement.execute(QueryState.forInternalCalls(),
+                                                                      QueryOptions.forInternalCalls(consistencyForRole(username),
+                                                                                                    Lists.newArrayList(ByteBufferUtil.bytes(username))));
+            result = UntypedResultSet.create(rows.result);
         }
         catch (RequestValidationException e)
         {
             throw new AssertionError(e); // not supposed to happen
         }
-    }
 
-    public SaslAuthenticator newAuthenticator()
-    {
-        return new PlainTextSaslAuthenticator();
+        if ((result.isEmpty() || !result.one().has(SALTED_HASH)) || !BCrypt.checkpw(password, result.one().getString(SALTED_HASH)))
+            throw new AuthenticationException("Username and/or password are incorrect");
+
+        return new AuthenticatedUser(username);
     }
 
-    // if there are no users yet - add default superuser.
-    private void setupDefaultUser()
+    private SelectStatement prepare(String query)
     {
         try
         {
-            // insert the default superuser if AUTH_KS.CREDENTIALS_CF is empty.
-            if (!hasExistingUsers())
-            {
-                process(String.format("INSERT INTO %s.%s (username, salted_hash) VALUES ('%s', '%s') USING TIMESTAMP 0",
-                                      Auth.AUTH_KS,
-                                      CREDENTIALS_CF,
-                                      DEFAULT_USER_NAME,
-                                      escape(hashpw(DEFAULT_USER_PASSWORD))),
-                        ConsistencyLevel.ONE);
-                logger.info("PasswordAuthenticator created default user '{}'", DEFAULT_USER_NAME);
-            }
+            return (SelectStatement) QueryProcessor.getStatement(query, ClientState.forInternalCalls()).statement;
         }
-        catch (RequestExecutionException e)
+        catch (RequestValidationException e)
         {
-            logger.warn("PasswordAuthenticator skipped default user setup: some nodes were not ready");
+            throw new AssertionError(e);
         }
     }
 
-    private static boolean hasExistingUsers() throws RequestExecutionException
-    {
-        // Try looking up the 'cassandra' default user first, to avoid the range query if possible.
-        String defaultSUQuery = String.format("SELECT * FROM %s.%s WHERE username = '%s'", Auth.AUTH_KS, CREDENTIALS_CF, DEFAULT_USER_NAME);
-        String allUsersQuery = String.format("SELECT * FROM %s.%s LIMIT 1", Auth.AUTH_KS, CREDENTIALS_CF);
-        return !process(defaultSUQuery, ConsistencyLevel.ONE).isEmpty()
-            || !process(defaultSUQuery, ConsistencyLevel.QUORUM).isEmpty()
-            || !process(allUsersQuery, ConsistencyLevel.QUORUM).isEmpty();
-    }
-
-    private static String hashpw(String password)
-    {
-        return BCrypt.hashpw(password, BCrypt.gensalt(GENSALT_LOG2_ROUNDS));
-    }
-
-    private static String escape(String name)
+    private class PlainTextSaslAuthenticator implements SaslNegotiator
     {
-        return StringUtils.replace(name, "'", "''");
-    }
-
-    private static UntypedResultSet process(String query, ConsistencyLevel cl) throws RequestExecutionException
-    {
-        return QueryProcessor.process(query, cl);
-    }
-
-    private static ConsistencyLevel consistencyForUser(String username)
-    {
-        if (username.equals(DEFAULT_USER_NAME))
-            return ConsistencyLevel.QUORUM;
-        else
-            return ConsistencyLevel.LOCAL_ONE;
-    }
-
-    private class PlainTextSaslAuthenticator implements ISaslAwareAuthenticator.SaslAuthenticator
-    {
-        private static final byte NUL = 0;
-
         private boolean complete = false;
-        private Map<String, String> credentials;
+        private String username;
+        private String password;
 
-        @Override
         public byte[] evaluateResponse(byte[] clientResponse) throws AuthenticationException
         {
-            credentials = decodeCredentials(clientResponse);
+            decodeCredentials(clientResponse);
             complete = true;
             return null;
         }
 
-        @Override
         public boolean isComplete()
         {
             return complete;
         }
 
-        @Override
         public AuthenticatedUser getAuthenticatedUser() throws AuthenticationException
         {
-            return authenticate(credentials);
+            if (!complete)
+                throw new AuthenticationException("SASL negotiation not complete");
+            return authenticate(username, password);
         }
 
         /**
@@ -285,14 +202,14 @@ public class PasswordAuthenticator implements ISaslAwareAuthenticator
          * The form is : {code}authzId<NUL>authnId<NUL>password<NUL>{code}
          * authzId is optional, and in fact we don't care about it here as we'll
          * set the authzId to match the authnId (that is, there is no concept of
-         * a user being authorized to act on behalf of another).
+         * a user being authorized to act on behalf of another with this IAuthenticator).
          *
          * @param bytes encoded credentials string sent by the client
          * @return map containing the username/password pairs in the form an IAuthenticator
          * would expect
          * @throws javax.security.sasl.SaslException
          */
-        private Map<String, String> decodeCredentials(byte[] bytes) throws AuthenticationException
+        private void decodeCredentials(byte[] bytes) throws AuthenticationException
         {
             logger.debug("Decoding credentials from client token");
             byte[] user = null;
@@ -315,10 +232,8 @@ public class PasswordAuthenticator implements ISaslAwareAuthenticator
             if (pass == null)
                 throw new AuthenticationException("Password must not be null");
 
-            Map<String, String> credentials = new HashMap<String, String>();
-            credentials.put(IAuthenticator.USERNAME_KEY, new String(user, StandardCharsets.UTF_8));
-            credentials.put(IAuthenticator.PASSWORD_KEY, new String(pass, StandardCharsets.UTF_8));
-            return credentials;
+            username = new String(user, StandardCharsets.UTF_8);
+            password = new String(pass, StandardCharsets.UTF_8);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/879b694d/src/java/org/apache/cassandra/auth/PermissionDetails.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/auth/PermissionDetails.java b/src/java/org/apache/cassandra/auth/PermissionDetails.java
index b2b4512..7e8c625 100644
--- a/src/java/org/apache/cassandra/auth/PermissionDetails.java
+++ b/src/java/org/apache/cassandra/auth/PermissionDetails.java
@@ -26,13 +26,13 @@ import com.google.common.collect.ComparisonChain;
  */
 public class PermissionDetails implements Comparable<PermissionDetails>
 {
-    public final String username;
+    public final String grantee;
     public final IResource resource;
     public final Permission permission;
 
-    public PermissionDetails(String username, IResource resource, Permission permission)
+    public PermissionDetails(String grantee, IResource resource, Permission permission)
     {
-        this.username = username;
+        this.grantee = grantee;
         this.resource = resource;
         this.permission = permission;
     }
@@ -41,7 +41,7 @@ public class PermissionDetails implements Comparable<PermissionDetails>
     public int compareTo(PermissionDetails other)
     {
         return ComparisonChain.start()
-                              .compare(username, other.username)
+                              .compare(grantee, other.grantee)
                               .compare(resource.getName(), other.resource.getName())
                               .compare(permission, other.permission)
                               .result();
@@ -50,8 +50,8 @@ public class PermissionDetails implements Comparable<PermissionDetails>
     @Override
     public String toString()
     {
-        return String.format("<PermissionDetails username:%s resource:%s permission:%s>",
-                             username,
+        return String.format("<PermissionDetails grantee:%s resource:%s permission:%s>",
+                             grantee,
                              resource.getName(),
                              permission);
     }
@@ -66,7 +66,7 @@ public class PermissionDetails implements Comparable<PermissionDetails>
             return false;
 
         PermissionDetails pd = (PermissionDetails) o;
-        return Objects.equal(username, pd.username)
+        return Objects.equal(grantee, pd.grantee)
             && Objects.equal(resource, pd.resource)
             && Objects.equal(permission, pd.permission);
     }
@@ -74,6 +74,6 @@ public class PermissionDetails implements Comparable<PermissionDetails>
     @Override
     public int hashCode()
     {
-        return Objects.hashCode(username, resource, permission);
+        return Objects.hashCode(grantee, resource, permission);
     }
 }


Mime
View raw message