mirror of https://github.com/apache/cassandra
Introduce role based access control
patch by Sam Tunnicliffe; reviewed by Aleksey Yeschenko for CASSANDRA-7653
This commit is contained in:
parent
c65a9f5c63
commit
879b694d34
|
|
@ -1,4 +1,5 @@
|
|||
3.0
|
||||
* Add role based access control (CASSANDRA-7653)
|
||||
* Group sstables for anticompaction correctly (CASSANDRA-8578)
|
||||
* Add ReadFailureException to native protocol, respond
|
||||
immediately when replicas encounter errors while handling
|
||||
|
|
|
|||
21
NEWS.txt
21
NEWS.txt
|
|
@ -18,6 +18,14 @@ using the provided 'sstableupgrade' tool.
|
|||
|
||||
New features
|
||||
------------
|
||||
- Authentication & Authorization APIs have been updated to introduce
|
||||
roles. Roles and Permissions granted to them are inherited, supporting
|
||||
role based access control. The role concept supercedes that of users
|
||||
and CQL constructs such as CREATE USER are deprecated but retained for
|
||||
compatibility. The requirement to explicitly create Roles in Cassandra
|
||||
even when auth is handled by an external system has been removed, so
|
||||
authentication & authorization can be delegated to such systems in their
|
||||
entirety.
|
||||
- SSTable file name is changed. Now you don't have Keyspace/CF name
|
||||
in file name. Also, secondary index has its own directory under parent's
|
||||
directory.
|
||||
|
|
@ -25,6 +33,18 @@ New features
|
|||
|
||||
Upgrading
|
||||
---------
|
||||
- IAuthenticator been updated to remove responsibility for user/role
|
||||
maintenance and is now solely responsible for validating credentials,
|
||||
This is primarily done via SASL, though an optional method exists for
|
||||
systems which need support for the Thrift login() method.
|
||||
- IRoleManager interface has been added which takes over the maintenance
|
||||
functions from IAuthenticator. IAuthorizer is mainly unchanged. Auth data
|
||||
in systems using the stock internal implementations PasswordAuthenticator
|
||||
& CassandraAuthorizer will be automatically converted during upgrade,
|
||||
with minimal operator intervention required. Custom implementations will
|
||||
require modification, though these can be used in conjunction with the
|
||||
stock CassandraRoleManager so providing an IRoleManager implementation
|
||||
should not usually be necessary.
|
||||
- Fat client support has been removed since we have push notifications to clients
|
||||
- cassandra-cli has been removed. Please use cqlsh instead.
|
||||
- YamlFileNetworkTopologySnitch has been removed; switch to
|
||||
|
|
@ -37,6 +57,7 @@ Upgrading
|
|||
in the normal order and not anymore in the order in which the column values were
|
||||
specified in the IN restriction.
|
||||
|
||||
|
||||
2.1.2
|
||||
=====
|
||||
|
||||
|
|
|
|||
34
bin/cqlsh
34
bin/cqlsh
|
|
@ -108,7 +108,7 @@ except ImportError, e:
|
|||
from cassandra.cluster import Cluster, PagedResult
|
||||
from cassandra.query import SimpleStatement, ordered_dict_factory
|
||||
from cassandra.policies import WhiteListRoundRobinPolicy
|
||||
from cassandra.metadata import protect_name, protect_names, protect_value
|
||||
from cassandra.metadata import protect_name, protect_names, protect_value, KeyspaceMetadata, TableMetadata, ColumnMetadata
|
||||
from cassandra.auth import PlainTextAuthProvider
|
||||
|
||||
# cqlsh should run correctly when run out of a Cassandra source tree,
|
||||
|
|
@ -751,9 +751,31 @@ class Shell(cmd.Cmd):
|
|||
ksmeta = self.get_keyspace_meta(ksname)
|
||||
|
||||
if tablename not in ksmeta.tables:
|
||||
raise ColumnFamilyNotFound("Column family %r not found" % tablename)
|
||||
if ksname == 'system_auth' and tablename in ['roles','role_permissions']:
|
||||
self.get_fake_auth_table_meta(ksname, tablename)
|
||||
else:
|
||||
raise ColumnFamilyNotFound("Column family %r not found" % tablename)
|
||||
else:
|
||||
return ksmeta.tables[tablename]
|
||||
|
||||
return ksmeta.tables[tablename]
|
||||
def get_fake_auth_table_meta(self, ksname, tablename):
|
||||
# may be using external auth implementation so internal tables
|
||||
# aren't actually defined in schema. In this case, we'll fake
|
||||
# them up
|
||||
if tablename == 'roles':
|
||||
ks_meta = KeyspaceMetadata(ksname, True, None, None)
|
||||
table_meta = TableMetadata(ks_meta, 'roles')
|
||||
table_meta.columns['role'] = ColumnMetadata(table_meta, 'role', cassandra.cqltypes.UTF8Type)
|
||||
table_meta.columns['is_superuser'] = ColumnMetadata(table_meta, 'is_superuser', cassandra.cqltypes.BooleanType)
|
||||
table_meta.columns['can_login'] = ColumnMetadata(table_meta, 'can_login', cassandra.cqltypes.BooleanType)
|
||||
elif tablename == 'role_permissions':
|
||||
ks_meta = KeyspaceMetadata(ksname, True, None, None)
|
||||
table_meta = TableMetadata(ks_meta, 'role_permissions')
|
||||
table_meta.columns['role'] = ColumnMetadata(table_meta, 'role', cassandra.cqltypes.UTF8Type)
|
||||
table_meta.columns['resource'] = ColumnMetadata(table_meta, 'resource', cassandra.cqltypes.UTF8Type)
|
||||
table_meta.columns['permission'] = ColumnMetadata(table_meta, 'permission', cassandra.cqltypes.UTF8Type)
|
||||
else:
|
||||
raise ColumnFamilyNotFoundException("Column family %r not found" % tablename)
|
||||
|
||||
def get_usertypes_meta(self):
|
||||
data = self.session.execute("select * from system.schema_usertypes")
|
||||
|
|
@ -1006,10 +1028,10 @@ class Shell(cmd.Cmd):
|
|||
|
||||
if statement.query_string[:6].lower() == 'select':
|
||||
self.print_result(rows, self.parse_for_table_meta(statement.query_string))
|
||||
elif statement.query_string.lower().startswith("list users"):
|
||||
self.print_result(rows, self.get_table_meta('system_auth','users'))
|
||||
elif statement.query_string.lower().startswith("list users") or statement.query_string.lower().startswith("list roles"):
|
||||
self.print_result(rows, self.get_table_meta('system_auth','roles'))
|
||||
elif statement.query_string.lower().startswith("list"):
|
||||
self.print_result(rows, self.get_table_meta('system_auth','permissions'))
|
||||
self.print_result(rows, self.get_table_meta('system_auth','role_permissions'))
|
||||
elif rows:
|
||||
# CAS INSERT/UPDATE
|
||||
self.writeresult("")
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ batchlog_replay_throttle_in_kb: 1024
|
|||
# - PasswordAuthenticator relies on username/password pairs to authenticate
|
||||
# users. It keeps usernames and hashed passwords in system_auth.credentials table.
|
||||
# Please increase system_auth keyspace replication factor if you use this authenticator.
|
||||
# If using PasswordAuthenticator, CassandraRoleManager must also be used (see below)
|
||||
authenticator: AllowAllAuthenticator
|
||||
|
||||
# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions
|
||||
|
|
@ -73,6 +74,25 @@ authenticator: AllowAllAuthenticator
|
|||
# increase system_auth keyspace replication factor if you use this authorizer.
|
||||
authorizer: AllowAllAuthorizer
|
||||
|
||||
# Part of the Authentication & Authorization backend, implementing IRoleManager; used
|
||||
# to maintain grants and memberships between roles.
|
||||
# Out of the box, Cassandra provides org.apache.cassandra.auth.CassandraRoleManager,
|
||||
# which stores role information in the system_auth keyspace. Most functions of the
|
||||
# IRoleManager require an authenticated login, so unless the configured IAuthenticator
|
||||
# actually implements authentication, most of this functionality will be unavailable.
|
||||
#
|
||||
# - CassandraRoleManager stores role data in the system_auth keyspace. Please
|
||||
# increase system_auth keyspace replication factor if you use this role manager.
|
||||
role_manager: CassandraRoleManager
|
||||
|
||||
# Validity period for roles cache (fetching permissions can be an
|
||||
# expensive operation depending on the authorizer). Granted roles are cached for
|
||||
# authenticated sessions in AuthenticatedUser and after the period specified
|
||||
# here, become eligible for (async) reload.
|
||||
# Defaults to 2000, set to 0 to disable.
|
||||
# Will be disabled automatically for AllowAllAuthenticator.
|
||||
roles_validity_in_ms: 2000
|
||||
|
||||
# Validity period for permissions cache (fetching permissions can be an
|
||||
# expensive operation depending on the authorizer, CassandraAuthorizer is
|
||||
# one example). Defaults to 2000, set to 0 to disable.
|
||||
|
|
|
|||
|
|
@ -254,10 +254,16 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ;
|
|||
| <alterUserStatement>
|
||||
| <dropUserStatement>
|
||||
| <listUsersStatement>
|
||||
| <createRoleStatement>
|
||||
| <alterRoleStatement>
|
||||
| <dropRoleStatement>
|
||||
| <listRolesStatement>
|
||||
;
|
||||
|
||||
<authorizationStatement> ::= <grantStatement>
|
||||
| <grantRoleStatement>
|
||||
| <revokeStatement>
|
||||
| <revokeRoleStatement>
|
||||
| <listPermissionsStatement>
|
||||
;
|
||||
|
||||
|
|
@ -1169,14 +1175,49 @@ syntax_rules += r'''
|
|||
'''
|
||||
|
||||
syntax_rules += r'''
|
||||
<grantStatement> ::= "GRANT" <permissionExpr> "ON" <resource> "TO" <username>
|
||||
<rolename> ::= <identifier>
|
||||
| <quotedName>
|
||||
| <unreservedKeyword>
|
||||
;
|
||||
|
||||
<createRoleStatement> ::= "CREATE" "ROLE" <rolename>
|
||||
( "WITH" <roleProperty> ("AND" <roleProperty>)*)?
|
||||
( "SUPERUSER" | "NOSUPERUSER" )?
|
||||
( "LOGIN" | "NOLOGIN" )?
|
||||
;
|
||||
|
||||
<alterRoleStatement> ::= "ALTER" "ROLE" <rolename>
|
||||
( "WITH" <roleProperty> ("AND" <roleProperty>)*)?
|
||||
( "SUPERUSER" | "NOSUPERUSER" )?
|
||||
( "LOGIN" | "NOLOGIN" )?
|
||||
;
|
||||
<roleProperty> ::= "PASSWORD" <stringLiteral>
|
||||
| "OPTIONS" <mapLiteral>
|
||||
;
|
||||
|
||||
<dropRoleStatement> ::= "DROP" "ROLE" <rolename>
|
||||
;
|
||||
|
||||
<grantRoleStatement> ::= "GRANT" <rolename> "TO" <rolename>
|
||||
;
|
||||
|
||||
<revokeRoleStatement> ::= "REVOKE" <rolename> "FROM" <rolename>
|
||||
;
|
||||
|
||||
<listRolesStatement> ::= "LIST" "ROLES"
|
||||
( "OF" <rolename> )? "NORECURSIVE"?
|
||||
;
|
||||
'''
|
||||
|
||||
syntax_rules += r'''
|
||||
<grantStatement> ::= "GRANT" <permissionExpr> "ON" <resource> "TO" <rolename>
|
||||
;
|
||||
|
||||
<revokeStatement> ::= "REVOKE" <permissionExpr> "ON" <resource> "FROM" <username>
|
||||
<revokeStatement> ::= "REVOKE" <permissionExpr> "ON" <resource> "FROM" <rolename>
|
||||
;
|
||||
|
||||
<listPermissionsStatement> ::= "LIST" <permissionExpr>
|
||||
( "ON" <resource> )? ( "OF" <username> )? "NORECURSIVE"?
|
||||
( "ON" <resource> )? ( "OF" <rolename> )? "NORECURSIVE"?
|
||||
;
|
||||
|
||||
<permission> ::= "AUTHORIZE"
|
||||
|
|
@ -1214,11 +1255,24 @@ def username_name_completer(ctxt, cass):
|
|||
session = cass.session
|
||||
return [maybe_quote(row.values()[0].replace("'", "''")) for row in session.execute("LIST USERS")]
|
||||
|
||||
@completer_for('rolename', 'role')
|
||||
def rolename_completer(ctxt, cass):
|
||||
def maybe_quote(name):
|
||||
if CqlRuleSet.is_valid_cql3_name(name):
|
||||
return name
|
||||
return "'%s'" % name
|
||||
|
||||
# disable completion for CREATE ROLE.
|
||||
if ctxt.matched[0][0] == 'K_CREATE':
|
||||
return [Hint('<rolename>')]
|
||||
|
||||
session = cass.session
|
||||
return [maybe_quote(row[0].replace("'", "''")) for row in session.execute("LIST ROLES")]
|
||||
|
||||
syntax_rules += r'''
|
||||
<createTriggerStatement> ::= "CREATE" "TRIGGER" ( "IF" "NOT" "EXISTS" )? <cident>
|
||||
"ON" cf=<columnFamilyName> "USING" class=<stringLiteral>
|
||||
;
|
||||
|
||||
<dropTriggerStatement> ::= "DROP" "TRIGGER" ( "IF" "EXISTS" )? triggername=<cident>
|
||||
"ON" cf=<columnFamilyName>
|
||||
;
|
||||
|
|
|
|||
|
|
@ -666,7 +666,9 @@ class CQL3HelpTopics(CQLHelpTopics):
|
|||
|
||||
def help_create(self):
|
||||
super(CQL3HelpTopics, self).help_create()
|
||||
print " HELP CREATE_USER;\n"
|
||||
print """ HELP CREATE_USER;
|
||||
HELP CREATE_ROLE;
|
||||
"""
|
||||
|
||||
def help_alter(self):
|
||||
print """
|
||||
|
|
@ -702,8 +704,10 @@ class CQL3HelpTopics(CQLHelpTopics):
|
|||
"""
|
||||
|
||||
def help_drop(self):
|
||||
super(CQL3HelpTopics, self).help_drop()
|
||||
print " HELP DROP_USER;\n"
|
||||
super(CQL3HelpTopics, self).help_create()
|
||||
print """ HELP DROP_USER;
|
||||
HELP DROP_ROLE;
|
||||
"""
|
||||
|
||||
def help_list(self):
|
||||
print """
|
||||
|
|
@ -758,10 +762,10 @@ class CQL3HelpTopics(CQLHelpTopics):
|
|||
ON ALL KEYSPACES
|
||||
| KEYSPACE <keyspace>
|
||||
| [TABLE] [<keyspace>.]<table>
|
||||
TO <username>
|
||||
TO [ROLE <rolename> | USER <username>]
|
||||
|
||||
Grant the specified permission (or all permissions) on a resource
|
||||
to a user.
|
||||
to a role or user.
|
||||
|
||||
To be able to grant a permission on some resource you have to
|
||||
have that permission yourself and also AUTHORIZE permission on it,
|
||||
|
|
@ -776,10 +780,10 @@ class CQL3HelpTopics(CQLHelpTopics):
|
|||
ON ALL KEYSPACES
|
||||
| KEYSPACE <keyspace>
|
||||
| [TABLE] [<keyspace>.]<table>
|
||||
FROM <username>
|
||||
FROM [ROLE <rolename> | USER <username>]
|
||||
|
||||
Revokes the specified permission (or all permissions) on a resource
|
||||
from a user.
|
||||
from a role or user.
|
||||
|
||||
To be able to revoke a permission on some resource you have to
|
||||
have that permission yourself and also AUTHORIZE permission on it,
|
||||
|
|
@ -794,12 +798,13 @@ class CQL3HelpTopics(CQLHelpTopics):
|
|||
[ON ALL KEYSPACES
|
||||
| KEYSPACE <keyspace>
|
||||
| [TABLE] [<keyspace>.]<table>]
|
||||
[OF <username>]
|
||||
[OF [ROLE <rolename> | USER <username>]
|
||||
[NORECURSIVE]
|
||||
|
||||
Omitting ON <resource> part will list permissions on ALL KEYSPACES,
|
||||
every keyspace and table.
|
||||
Omitting OF <username> part will list permissions of all users.
|
||||
Omitting OF [ROLE <rolename> | USER <username>] part will list permissions
|
||||
of all roles and users.
|
||||
Omitting NORECURSIVE specifier will list permissions of the resource
|
||||
and all its parents (table, table's keyspace and ALL KEYSPACES).
|
||||
|
||||
|
|
@ -818,3 +823,46 @@ class CQL3HelpTopics(CQLHelpTopics):
|
|||
MODIFY: required for INSERT, DELETE, UPDATE, TRUNCATE
|
||||
SELECT: required for SELECT
|
||||
"""
|
||||
|
||||
def help_create_role(self):
|
||||
print """
|
||||
CREATE ROLE <rolename>;
|
||||
|
||||
CREATE ROLE creates a new Cassandra role.
|
||||
Only superusers can issue CREATE ROLE requests.
|
||||
To create a superuser account use SUPERUSER option (NOSUPERUSER is the default).
|
||||
"""
|
||||
|
||||
def help_drop_role(self):
|
||||
print """
|
||||
DROP ROLE <rolename>;
|
||||
|
||||
DROP ROLE removes an existing role. You have to be logged in as a superuser
|
||||
to issue a DROP ROLE statement.
|
||||
"""
|
||||
|
||||
def help_list_roles(self):
|
||||
print """
|
||||
LIST ROLES [OF [ROLE <rolename> | USER <username>] [NORECURSIVE]];
|
||||
|
||||
Only superusers can use the OF clause to list the roles granted to a role or user.
|
||||
If a superuser omits the OF clause then all the created roles will be listed.
|
||||
If a non-superuser calls LIST ROLES then the roles granted to that user are listed.
|
||||
If NORECURSIVE is provided then only directly granted roles are listed.
|
||||
"""
|
||||
|
||||
def help_grant_role(self):
|
||||
print """
|
||||
GRANT ROLE <rolename> TO [ROLE <rolename> | USER <username>]
|
||||
|
||||
Grant the specified role to another role or user. You have to be logged
|
||||
in as superuser to issue a GRANT ROLE statement.
|
||||
"""
|
||||
|
||||
def help_revoke_role(self):
|
||||
print """
|
||||
REVOKE ROLE <rolename> FROM [ROLE <rolename> | USER <username>]
|
||||
|
||||
Revoke the specified role from another role or user. You have to be logged
|
||||
in as superuser to issue a REVOKE ROLE statement.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -23,45 +23,16 @@ import java.util.Set;
|
|||
|
||||
import org.apache.cassandra.exceptions.AuthenticationException;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
|
||||
public class AllowAllAuthenticator implements IAuthenticator
|
||||
{
|
||||
private static final SaslNegotiator AUTHENTICATOR_INSTANCE = new Negotiator();
|
||||
|
||||
public boolean requireAuthentication()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public Set<Option> supportedOptions()
|
||||
{
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public Set<Option> alterableOptions()
|
||||
{
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public AuthenticatedUser authenticate(Map<String, String> credentials) throws AuthenticationException
|
||||
{
|
||||
return AuthenticatedUser.ANONYMOUS_USER;
|
||||
}
|
||||
|
||||
public void create(String username, Map<Option, Object> options) throws InvalidRequestException
|
||||
{
|
||||
throw new InvalidRequestException("CREATE USER operation is not supported by AllowAllAuthenticator");
|
||||
}
|
||||
|
||||
public void alter(String username, Map<Option, Object> options) throws InvalidRequestException
|
||||
{
|
||||
throw new InvalidRequestException("ALTER USER operation is not supported by AllowAllAuthenticator");
|
||||
}
|
||||
|
||||
public void drop(String username) throws InvalidRequestException
|
||||
{
|
||||
throw new InvalidRequestException("DROP USER operation is not supported by AllowAllAuthenticator");
|
||||
}
|
||||
|
||||
public Set<IResource> protectedResources()
|
||||
{
|
||||
return Collections.emptySet();
|
||||
|
|
@ -74,4 +45,33 @@ public class AllowAllAuthenticator implements IAuthenticator
|
|||
public void setup()
|
||||
{
|
||||
}
|
||||
|
||||
public SaslNegotiator newSaslNegotiator()
|
||||
{
|
||||
return AUTHENTICATOR_INSTANCE;
|
||||
}
|
||||
|
||||
public AuthenticatedUser legacyAuthenticate(Map<String, String> credentialsData)
|
||||
{
|
||||
return AuthenticatedUser.ANONYMOUS_USER;
|
||||
}
|
||||
|
||||
private static class Negotiator implements SaslNegotiator
|
||||
{
|
||||
|
||||
public byte[] evaluateResponse(byte[] clientResponse) throws AuthenticationException
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isComplete()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public AuthenticatedUser getAuthenticatedUser() throws AuthenticationException
|
||||
{
|
||||
return AuthenticatedUser.ANONYMOUS_USER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,298 +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.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
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.config.CFMetaData;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.config.KSMetaData;
|
||||
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.CFStatement;
|
||||
import org.apache.cassandra.cql3.statements.CreateTableStatement;
|
||||
import org.apache.cassandra.cql3.statements.SelectStatement;
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
import org.apache.cassandra.exceptions.RequestExecutionException;
|
||||
import org.apache.cassandra.exceptions.RequestValidationException;
|
||||
import org.apache.cassandra.locator.SimpleStrategy;
|
||||
import org.apache.cassandra.service.*;
|
||||
import org.apache.cassandra.transport.messages.ResultMessage;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
public class Auth
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Auth.class);
|
||||
|
||||
public static final String DEFAULT_SUPERUSER_NAME = "cassandra";
|
||||
|
||||
public static final long SUPERUSER_SETUP_DELAY = Long.getLong("cassandra.superuser_setup_delay_ms", 10000);
|
||||
|
||||
public static final String AUTH_KS = "system_auth";
|
||||
public static final String USERS_CF = "users";
|
||||
|
||||
// User-level permissions cache.
|
||||
private static final PermissionsCache permissionsCache = new PermissionsCache(DatabaseDescriptor.getPermissionsValidity(),
|
||||
DatabaseDescriptor.getPermissionsUpdateInterval(),
|
||||
DatabaseDescriptor.getPermissionsCacheMaxEntries(),
|
||||
DatabaseDescriptor.getAuthorizer());
|
||||
|
||||
private static final String USERS_CF_SCHEMA = String.format("CREATE TABLE %s.%s ("
|
||||
+ "name text,"
|
||||
+ "super boolean,"
|
||||
+ "PRIMARY KEY(name)"
|
||||
+ ") WITH gc_grace_seconds=%d",
|
||||
AUTH_KS,
|
||||
USERS_CF,
|
||||
90 * 24 * 60 * 60); // 3 months.
|
||||
|
||||
private static SelectStatement selectUserStatement;
|
||||
|
||||
public static Set<Permission> getPermissions(AuthenticatedUser user, IResource resource)
|
||||
{
|
||||
return permissionsCache.getPermissions(user, resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the username is stored in AUTH_KS.USERS_CF.
|
||||
*
|
||||
* @param username Username to query.
|
||||
* @return whether or not Cassandra knows about the user.
|
||||
*/
|
||||
public static boolean isExistingUser(String username)
|
||||
{
|
||||
return !selectUser(username).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is a known superuser.
|
||||
*
|
||||
* @param username Username to query.
|
||||
* @return true is the user is a superuser, false if they aren't or don't exist at all.
|
||||
*/
|
||||
public static boolean isSuperuser(String username)
|
||||
{
|
||||
UntypedResultSet result = selectUser(username);
|
||||
return !result.isEmpty() && result.one().getBoolean("super");
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the user into AUTH_KS.USERS_CF (or overwrites their superuser status as a result of an ALTER USER query).
|
||||
*
|
||||
* @param username Username to insert.
|
||||
* @param isSuper User's new status.
|
||||
* @throws RequestExecutionException
|
||||
*/
|
||||
public static void insertUser(String username, boolean isSuper) throws RequestExecutionException
|
||||
{
|
||||
QueryProcessor.process(String.format("INSERT INTO %s.%s (name, super) VALUES ('%s', %s)",
|
||||
AUTH_KS,
|
||||
USERS_CF,
|
||||
escape(username),
|
||||
isSuper),
|
||||
consistencyForUser(username));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the user from AUTH_KS.USERS_CF.
|
||||
*
|
||||
* @param username Username to delete.
|
||||
* @throws RequestExecutionException
|
||||
*/
|
||||
public static void deleteUser(String username) throws RequestExecutionException
|
||||
{
|
||||
QueryProcessor.process(String.format("DELETE FROM %s.%s WHERE name = '%s'",
|
||||
AUTH_KS,
|
||||
USERS_CF,
|
||||
escape(username)),
|
||||
consistencyForUser(username));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up Authenticator and Authorizer.
|
||||
*/
|
||||
public static void setup()
|
||||
{
|
||||
if (DatabaseDescriptor.getAuthenticator() instanceof AllowAllAuthenticator)
|
||||
return;
|
||||
|
||||
setupAuthKeyspace();
|
||||
setupTable(USERS_CF, USERS_CF_SCHEMA);
|
||||
|
||||
DatabaseDescriptor.getAuthenticator().setup();
|
||||
DatabaseDescriptor.getAuthorizer().setup();
|
||||
|
||||
// register a custom MigrationListener for permissions cleanup after dropped keyspaces/cfs.
|
||||
MigrationManager.instance.register(new AuthMigrationListener());
|
||||
|
||||
// the delay is here to give the node some time to see its peers - to reduce
|
||||
// "Skipped default superuser setup: some nodes were not ready" log spam.
|
||||
// It's the only reason for the delay.
|
||||
ScheduledExecutors.nonPeriodicTasks.schedule(new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
setupDefaultSuperuser();
|
||||
}
|
||||
}, SUPERUSER_SETUP_DELAY, TimeUnit.MILLISECONDS);
|
||||
|
||||
try
|
||||
{
|
||||
String query = String.format("SELECT * FROM %s.%s WHERE name = ?", AUTH_KS, USERS_CF);
|
||||
selectUserStatement = (SelectStatement) QueryProcessor.parseStatement(query).prepare().statement;
|
||||
}
|
||||
catch (RequestValidationException e)
|
||||
{
|
||||
throw new AssertionError(e); // not supposed to happen
|
||||
}
|
||||
}
|
||||
|
||||
// Only use QUORUM cl for the default superuser.
|
||||
private static ConsistencyLevel consistencyForUser(String username)
|
||||
{
|
||||
if (username.equals(DEFAULT_SUPERUSER_NAME))
|
||||
return ConsistencyLevel.QUORUM;
|
||||
else
|
||||
return ConsistencyLevel.LOCAL_ONE;
|
||||
}
|
||||
|
||||
private static void setupAuthKeyspace()
|
||||
{
|
||||
if (Schema.instance.getKSMetaData(AUTH_KS) == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
KSMetaData ksm = KSMetaData.newKeyspace(AUTH_KS, SimpleStrategy.class.getName(), ImmutableMap.of("replication_factor", "1"), true);
|
||||
MigrationManager.announceNewKeyspace(ksm, 0, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new AssertionError(e); // shouldn't ever happen.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up table from given CREATE TABLE statement under system_auth keyspace, if not already done so.
|
||||
*
|
||||
* @param name name of the table
|
||||
* @param cql CREATE TABLE statement
|
||||
*/
|
||||
public static void setupTable(String name, String cql)
|
||||
{
|
||||
if (Schema.instance.getCFMetaData(AUTH_KS, name) == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
CFStatement parsed = (CFStatement)QueryProcessor.parseStatement(cql);
|
||||
parsed.prepareKeyspace(AUTH_KS);
|
||||
CreateTableStatement statement = (CreateTableStatement) parsed.prepare().statement;
|
||||
CFMetaData cfm = statement.getCFMetaData().copy(CFMetaData.generateLegacyCfId(AUTH_KS, name));
|
||||
assert cfm.cfName.equals(name);
|
||||
MigrationManager.announceNewColumnFamily(cfm);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void setupDefaultSuperuser()
|
||||
{
|
||||
try
|
||||
{
|
||||
// insert a default superuser if AUTH_KS.USERS_CF is empty.
|
||||
if (!hasExistingUsers())
|
||||
{
|
||||
QueryProcessor.process(String.format("INSERT INTO %s.%s (name, super) VALUES ('%s', %s) USING TIMESTAMP 0",
|
||||
AUTH_KS,
|
||||
USERS_CF,
|
||||
DEFAULT_SUPERUSER_NAME,
|
||||
true),
|
||||
ConsistencyLevel.ONE);
|
||||
logger.info("Created default superuser '{}'", DEFAULT_SUPERUSER_NAME);
|
||||
}
|
||||
}
|
||||
catch (RequestExecutionException e)
|
||||
{
|
||||
logger.warn("Skipped default superuser setup: some nodes were not ready");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasExistingUsers() throws RequestExecutionException
|
||||
{
|
||||
// Try looking up the 'cassandra' default super user first, to avoid the range query if possible.
|
||||
String defaultSUQuery = String.format("SELECT * FROM %s.%s WHERE name = '%s'", AUTH_KS, USERS_CF, DEFAULT_SUPERUSER_NAME);
|
||||
String allUsersQuery = String.format("SELECT * FROM %s.%s LIMIT 1", AUTH_KS, USERS_CF);
|
||||
return !QueryProcessor.process(defaultSUQuery, ConsistencyLevel.ONE).isEmpty()
|
||||
|| !QueryProcessor.process(defaultSUQuery, ConsistencyLevel.QUORUM).isEmpty()
|
||||
|| !QueryProcessor.process(allUsersQuery, ConsistencyLevel.QUORUM).isEmpty();
|
||||
}
|
||||
|
||||
// we only worry about one character ('). Make sure it's properly escaped.
|
||||
private static String escape(String name)
|
||||
{
|
||||
return StringUtils.replace(name, "'", "''");
|
||||
}
|
||||
|
||||
private static UntypedResultSet selectUser(String username)
|
||||
{
|
||||
try
|
||||
{
|
||||
ResultMessage.Rows rows = selectUserStatement.execute(QueryState.forInternalCalls(),
|
||||
QueryOptions.forInternalCalls(consistencyForUser(username),
|
||||
Lists.newArrayList(ByteBufferUtil.bytes(username))));
|
||||
return UntypedResultSet.create(rows.result);
|
||||
}
|
||||
catch (RequestValidationException e)
|
||||
{
|
||||
throw new AssertionError(e); // not supposed to happen
|
||||
}
|
||||
catch (RequestExecutionException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MigrationListener implementation that cleans up permissions on dropped resources.
|
||||
*/
|
||||
public static class AuthMigrationListener extends MigrationListener
|
||||
{
|
||||
public void onDropKeyspace(String ksName)
|
||||
{
|
||||
DatabaseDescriptor.getAuthorizer().revokeAll(DataResource.keyspace(ksName));
|
||||
}
|
||||
|
||||
public void onDropColumnFamily(String ksName, String cfName)
|
||||
{
|
||||
DatabaseDescriptor.getAuthorizer().revokeAll(DataResource.columnFamily(ksName, cfName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.auth;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.KSMetaData;
|
||||
import org.apache.cassandra.locator.SimpleStrategy;
|
||||
|
||||
public class AuthKeyspace
|
||||
{
|
||||
public static final String NAME = "system_auth";
|
||||
|
||||
public static final String ROLES = "roles";
|
||||
public static final String ROLE_MEMBERS = "role_members";
|
||||
public static final String ROLE_PERMISSIONS = "role_permissions";
|
||||
public static final String RESOURCE_ROLE_INDEX = "resource_role_permissons_index";
|
||||
|
||||
public static final long SUPERUSER_SETUP_DELAY = Long.getLong("cassandra.superuser_setup_delay_ms", 10000);
|
||||
|
||||
private static final CFMetaData Roles =
|
||||
compile(ROLES,
|
||||
"role definitions",
|
||||
"CREATE TABLE %s ("
|
||||
+ "role text,"
|
||||
+ "is_superuser boolean,"
|
||||
+ "can_login boolean,"
|
||||
+ "salted_hash text,"
|
||||
+ "member_of set<text>,"
|
||||
+ "PRIMARY KEY(role))");
|
||||
|
||||
private static final CFMetaData RoleMembers =
|
||||
compile(ROLE_MEMBERS,
|
||||
"role memberships lookup table",
|
||||
"CREATE TABLE %s ("
|
||||
+ "role text,"
|
||||
+ "member text,"
|
||||
+ "PRIMARY KEY(role, member))");
|
||||
|
||||
private static final CFMetaData RolePermissions =
|
||||
compile(ROLE_PERMISSIONS,
|
||||
"permissions granted to db roles",
|
||||
"CREATE TABLE %s ("
|
||||
+ "role text,"
|
||||
+ "resource text,"
|
||||
+ "permissions set<text>,"
|
||||
+ "PRIMARY KEY(role, resource))");
|
||||
|
||||
private static final CFMetaData ResourceRoleIndex =
|
||||
compile(RESOURCE_ROLE_INDEX,
|
||||
"index of db roles with permissions granted on a resource",
|
||||
"CREATE TABLE %s ("
|
||||
+ "resource text,"
|
||||
+ "role text,"
|
||||
+ "PRIMARY KEY(resource, role))");
|
||||
|
||||
|
||||
private static CFMetaData compile(String name, String description, String schema)
|
||||
{
|
||||
return CFMetaData.compile(String.format(schema, name), NAME)
|
||||
.comment(description)
|
||||
.gcGraceSeconds((int) TimeUnit.DAYS.toSeconds(90));
|
||||
}
|
||||
|
||||
public static KSMetaData definition()
|
||||
{
|
||||
List<CFMetaData> tables = Arrays.asList(Roles, RoleMembers, RolePermissions, ResourceRoleIndex);
|
||||
return new KSMetaData(NAME, SimpleStrategy.class, ImmutableMap.of("replication_factor", "1"), true, tables);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,25 +17,21 @@
|
|||
*/
|
||||
package org.apache.cassandra.auth;
|
||||
|
||||
import org.apache.cassandra.exceptions.AuthenticationException;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.service.MigrationListener;
|
||||
|
||||
public interface ISaslAwareAuthenticator extends IAuthenticator
|
||||
/**
|
||||
* MigrationListener implementation that cleans up permissions on dropped resources.
|
||||
*/
|
||||
public class AuthMigrationListener extends MigrationListener
|
||||
{
|
||||
/**
|
||||
* 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 void onDropKeyspace(String ksName)
|
||||
{
|
||||
public byte[] evaluateResponse(byte[] clientResponse) throws AuthenticationException;
|
||||
public boolean isComplete();
|
||||
public AuthenticatedUser getAuthenticatedUser() throws AuthenticationException;
|
||||
DatabaseDescriptor.getAuthorizer().revokeAll(DataResource.keyspace(ksName));
|
||||
}
|
||||
|
||||
public void onDropColumnFamily(String ksName, String cfName)
|
||||
{
|
||||
DatabaseDescriptor.getAuthorizer().revokeAll(DataResource.table(ksName, cfName));
|
||||
}
|
||||
}
|
||||
|
|
@ -17,16 +17,46 @@
|
|||
*/
|
||||
package org.apache.cassandra.auth;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.ListenableFutureTask;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.concurrent.ScheduledExecutors;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.exceptions.RequestExecutionException;
|
||||
import org.apache.cassandra.exceptions.RequestValidationException;
|
||||
|
||||
/**
|
||||
* Returned from IAuthenticator#authenticate(), represents an authenticated user everywhere internally.
|
||||
*
|
||||
* Holds the name of the user and the roles that have been granted to the user. The roles will be cached
|
||||
* for roles_validity_in_ms.
|
||||
*/
|
||||
public class AuthenticatedUser
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(AuthenticatedUser.class);
|
||||
|
||||
public static final String ANONYMOUS_USERNAME = "anonymous";
|
||||
public static final AuthenticatedUser ANONYMOUS_USER = new AuthenticatedUser(ANONYMOUS_USERNAME);
|
||||
|
||||
// User-level roles cache
|
||||
private static final LoadingCache<String, Set<String>> rolesCache = initRolesCache();
|
||||
|
||||
// User-level permissions cache.
|
||||
private static final PermissionsCache permissionsCache = new PermissionsCache(DatabaseDescriptor.getPermissionsValidity(),
|
||||
DatabaseDescriptor.getPermissionsUpdateInterval(),
|
||||
DatabaseDescriptor.getPermissionsCacheMaxEntries(),
|
||||
DatabaseDescriptor.getAuthorizer());
|
||||
|
||||
private final String name;
|
||||
|
||||
public AuthenticatedUser(String name)
|
||||
|
|
@ -47,7 +77,16 @@ public class AuthenticatedUser
|
|||
*/
|
||||
public boolean isSuper()
|
||||
{
|
||||
return !isAnonymous() && Auth.isSuperuser(name);
|
||||
return !isAnonymous() && hasSuperuserRole();
|
||||
}
|
||||
|
||||
private boolean hasSuperuserRole()
|
||||
{
|
||||
IRoleManager roleManager = DatabaseDescriptor.getRoleManager();
|
||||
for (String role : getRoles())
|
||||
if (roleManager.isSuper(role))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -58,6 +97,80 @@ public class AuthenticatedUser
|
|||
return this == ANONYMOUS_USER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the roles that have been granted to the user via the IRoleManager
|
||||
*
|
||||
* @return a list of roles that have been granted to the user
|
||||
*/
|
||||
public Set<String> getRoles()
|
||||
{
|
||||
if (rolesCache == null)
|
||||
return loadRoles(name);
|
||||
|
||||
try
|
||||
{
|
||||
return rolesCache.get(name);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<Permission> getPermissions(AuthenticatedUser user, IResource resource)
|
||||
{
|
||||
return permissionsCache.getPermissions(user, resource);
|
||||
}
|
||||
|
||||
private static Set<String> loadRoles(String name)
|
||||
{
|
||||
try
|
||||
{
|
||||
return DatabaseDescriptor.getRoleManager().getRoles(name, true);
|
||||
}
|
||||
catch (RequestValidationException e)
|
||||
{
|
||||
throw new AssertionError(e); // not supposed to happen
|
||||
}
|
||||
catch (RequestExecutionException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static LoadingCache<String, Set<String>> initRolesCache()
|
||||
{
|
||||
if (DatabaseDescriptor.getAuthenticator() instanceof AllowAllAuthenticator)
|
||||
return null;
|
||||
|
||||
int validityPeriod = DatabaseDescriptor.getRolesValidity();
|
||||
if (validityPeriod <= 0)
|
||||
return null;
|
||||
|
||||
return CacheBuilder.newBuilder()
|
||||
.refreshAfterWrite(validityPeriod, TimeUnit.MILLISECONDS)
|
||||
.build(new CacheLoader<String, Set<String>>()
|
||||
{
|
||||
public Set<String> load(String name)
|
||||
{
|
||||
return loadRoles(name);
|
||||
}
|
||||
|
||||
public ListenableFuture<Set<String>> reload(final String name, Set<String> oldValue)
|
||||
{
|
||||
ListenableFutureTask<Set<String>> task = ListenableFutureTask.create(new Callable<Set<String>>()
|
||||
{
|
||||
public Set<String> call()
|
||||
{
|
||||
return loadRoles(name);
|
||||
}
|
||||
});
|
||||
ScheduledExecutors.optionalTasks.execute(task);
|
||||
return task;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,63 +18,65 @@
|
|||
package org.apache.cassandra.auth;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.cql3.UntypedResultSet;
|
||||
import org.apache.cassandra.cql3.QueryProcessor;
|
||||
import org.apache.cassandra.cql3.QueryOptions;
|
||||
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.BatchStatement;
|
||||
import org.apache.cassandra.cql3.statements.ModificationStatement;
|
||||
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.ClientState;
|
||||
import org.apache.cassandra.service.QueryState;
|
||||
import org.apache.cassandra.transport.messages.ResultMessage;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
/**
|
||||
* CassandraAuthorizer is an IAuthorizer implementation that keeps
|
||||
* permissions internally in C* - in system_auth.permissions CQL3 table.
|
||||
* user permissions internally in C* using the system_auth.role_permissions
|
||||
* table.
|
||||
*/
|
||||
public class CassandraAuthorizer implements IAuthorizer
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(CassandraAuthorizer.class);
|
||||
|
||||
private static final String USERNAME = "username";
|
||||
private static final String ROLE = "role";
|
||||
private static final String RESOURCE = "resource";
|
||||
private static final String PERMISSIONS = "permissions";
|
||||
|
||||
private static final String PERMISSIONS_CF = "permissions";
|
||||
private static final String PERMISSIONS_CF_SCHEMA = String.format("CREATE TABLE %s.%s ("
|
||||
+ "username text,"
|
||||
+ "resource text,"
|
||||
+ "permissions set<text>,"
|
||||
+ "PRIMARY KEY(username, resource)"
|
||||
+ ") WITH gc_grace_seconds=%d",
|
||||
Auth.AUTH_KS,
|
||||
PERMISSIONS_CF,
|
||||
90 * 24 * 60 * 60); // 3 months.
|
||||
// used during upgrades to perform authz on mixed clusters
|
||||
public static final String USERNAME = "username";
|
||||
public static final String USER_PERMISSIONS = "permissions";
|
||||
|
||||
private SelectStatement authorizeStatement;
|
||||
private SelectStatement authorizeRoleStatement;
|
||||
private SelectStatement legacyAuthorizeRoleStatement;
|
||||
|
||||
// Returns every permission on the resource granted to the user.
|
||||
public CassandraAuthorizer()
|
||||
{
|
||||
}
|
||||
// Returns every permission on the resource granted to the user either directly
|
||||
// or indirectly via roles granted to the user.
|
||||
public Set<Permission> authorize(AuthenticatedUser user, IResource resource)
|
||||
{
|
||||
if (user.isSuper())
|
||||
return Permission.ALL;
|
||||
|
||||
UntypedResultSet result;
|
||||
Set<Permission> permissions = EnumSet.noneOf(Permission.class);
|
||||
try
|
||||
{
|
||||
ResultMessage.Rows rows = authorizeStatement.execute(QueryState.forInternalCalls(),
|
||||
QueryOptions.forInternalCalls(ConsistencyLevel.LOCAL_ONE,
|
||||
Lists.newArrayList(ByteBufferUtil.bytes(user.getName()),
|
||||
ByteBufferUtil.bytes(resource.getName()))));
|
||||
result = UntypedResultSet.create(rows.result);
|
||||
for (String role: user.getRoles())
|
||||
addPermissionsForRole(permissions, resource, role);
|
||||
}
|
||||
catch (RequestValidationException e)
|
||||
{
|
||||
|
|
@ -86,53 +88,209 @@ public class CassandraAuthorizer implements IAuthorizer
|
|||
return Permission.NONE;
|
||||
}
|
||||
|
||||
if (result.isEmpty() || !result.one().has(PERMISSIONS))
|
||||
return Permission.NONE;
|
||||
|
||||
Set<Permission> permissions = EnumSet.noneOf(Permission.class);
|
||||
for (String perm : result.one().getSet(PERMISSIONS, UTF8Type.instance))
|
||||
permissions.add(Permission.valueOf(perm));
|
||||
return permissions;
|
||||
}
|
||||
|
||||
public void grant(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String to)
|
||||
throws RequestExecutionException
|
||||
public void grant(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String grantee)
|
||||
throws RequestValidationException, RequestExecutionException
|
||||
{
|
||||
modify(permissions, resource, to, "+");
|
||||
modifyRolePermissions(permissions, resource, grantee, "+");
|
||||
addLookupEntry(resource, grantee);
|
||||
}
|
||||
|
||||
public void revoke(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String from)
|
||||
throws RequestExecutionException
|
||||
public void revoke(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String revokee)
|
||||
throws RequestValidationException, RequestExecutionException
|
||||
{
|
||||
modify(permissions, resource, from, "-");
|
||||
modifyRolePermissions(permissions, resource, revokee, "-");
|
||||
removeLookupEntry(resource, revokee);
|
||||
}
|
||||
|
||||
// Adds or removes permissions from user's 'permissions' set (adds if op is "+", removes if op is "-")
|
||||
private void modify(Set<Permission> permissions, IResource resource, String user, String op) throws RequestExecutionException
|
||||
// Called prior to deleting the user with DROP USER query.
|
||||
// Internal hook, so no permission checks are needed here.
|
||||
// Executes a logged batch removing the granted premissions
|
||||
// for the role as well as the entries from the reverse index
|
||||
// table
|
||||
public void revokeAll(String revokee)
|
||||
{
|
||||
process(String.format("UPDATE %s.%s SET permissions = permissions %s {%s} WHERE username = '%s' AND resource = '%s'",
|
||||
Auth.AUTH_KS,
|
||||
PERMISSIONS_CF,
|
||||
try
|
||||
{
|
||||
UntypedResultSet rows = process(String.format("SELECT resource FROM %s.%s WHERE role = '%s'",
|
||||
AuthKeyspace.NAME,
|
||||
AuthKeyspace.ROLE_PERMISSIONS,
|
||||
escape(revokee)));
|
||||
|
||||
List<CQLStatement> statements = new ArrayList<>();
|
||||
for (UntypedResultSet.Row row : rows)
|
||||
{
|
||||
statements.add(
|
||||
QueryProcessor.getStatement(String.format("DELETE FROM %s.%s WHERE resource = '%s' AND role = '%s'",
|
||||
AuthKeyspace.NAME,
|
||||
AuthKeyspace.RESOURCE_ROLE_INDEX,
|
||||
escape(row.getString("resource")),
|
||||
escape(revokee)),
|
||||
ClientState.forInternalCalls()).statement);
|
||||
|
||||
}
|
||||
|
||||
statements.add(QueryProcessor.getStatement(String.format("DELETE FROM %s.%s WHERE role = '%s'",
|
||||
AuthKeyspace.NAME,
|
||||
AuthKeyspace.ROLE_PERMISSIONS,
|
||||
escape(revokee)),
|
||||
ClientState.forInternalCalls()).statement);
|
||||
|
||||
executeLoggedBatch(statements);
|
||||
}
|
||||
catch (RequestExecutionException | RequestValidationException e)
|
||||
{
|
||||
logger.warn("CassandraAuthorizer failed to revoke all permissions of {}: {}", revokee, e);
|
||||
}
|
||||
}
|
||||
|
||||
// Called after a resource is removed (DROP KEYSPACE, DROP TABLE, etc.).
|
||||
// Execute a logged batch removing all the permissions for the resource
|
||||
// as well as the index table entry
|
||||
public void revokeAll(IResource droppedResource)
|
||||
{
|
||||
try
|
||||
{
|
||||
UntypedResultSet rows = process(String.format("SELECT role FROM %s.%s WHERE resource = '%s'",
|
||||
AuthKeyspace.NAME,
|
||||
AuthKeyspace.RESOURCE_ROLE_INDEX,
|
||||
escape(droppedResource.getName())));
|
||||
|
||||
List<CQLStatement> statements = new ArrayList<>();
|
||||
for (UntypedResultSet.Row row : rows)
|
||||
{
|
||||
statements.add(QueryProcessor.getStatement(String.format("DELETE FROM %s.%s WHERE role = '%s' AND resource = '%s'",
|
||||
AuthKeyspace.NAME,
|
||||
AuthKeyspace.ROLE_PERMISSIONS,
|
||||
escape(row.getString("role")),
|
||||
escape(droppedResource.getName())),
|
||||
ClientState.forInternalCalls()).statement);
|
||||
}
|
||||
|
||||
statements.add(QueryProcessor.getStatement(String.format("DELETE FROM %s.%s WHERE resource = '%s'",
|
||||
AuthKeyspace.NAME,
|
||||
AuthKeyspace.RESOURCE_ROLE_INDEX,
|
||||
escape(droppedResource.getName())),
|
||||
ClientState.forInternalCalls()).statement);
|
||||
|
||||
executeLoggedBatch(statements);
|
||||
}
|
||||
catch (RequestExecutionException | RequestValidationException e)
|
||||
{
|
||||
logger.warn("CassandraAuthorizer failed to revoke all permissions on {}: {}", droppedResource, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void executeLoggedBatch(List<CQLStatement> statements)
|
||||
throws RequestExecutionException, RequestValidationException
|
||||
{
|
||||
BatchStatement batch = new BatchStatement(0,
|
||||
BatchStatement.Type.LOGGED,
|
||||
Lists.newArrayList(Iterables.filter(statements, ModificationStatement.class)),
|
||||
Attributes.none());
|
||||
QueryProcessor.instance.processBatch(batch,
|
||||
QueryState.forInternalCalls(),
|
||||
BatchQueryOptions.withoutPerStatementVariables(QueryOptions.DEFAULT));
|
||||
|
||||
}
|
||||
|
||||
// Add every permission on the resource granted to the role
|
||||
private void addPermissionsForRole(Set<Permission> permissions, IResource resource, String rolename)
|
||||
throws RequestExecutionException, RequestValidationException
|
||||
{
|
||||
QueryOptions options = QueryOptions.forInternalCalls(ConsistencyLevel.LOCAL_ONE,
|
||||
Lists.newArrayList(ByteBufferUtil.bytes(rolename),
|
||||
ByteBufferUtil.bytes(resource.getName())));
|
||||
|
||||
// If it exists, read from the legacy user permissions table to handle the case where the cluster
|
||||
// is being upgraded and so is running with mixed versions of the authz schema
|
||||
SelectStatement statement = Schema.instance.getCFMetaData(AuthKeyspace.NAME, USER_PERMISSIONS) == null
|
||||
? authorizeRoleStatement
|
||||
: legacyAuthorizeRoleStatement;
|
||||
ResultMessage.Rows rows = statement.execute(QueryState.forInternalCalls(), options) ;
|
||||
UntypedResultSet result = UntypedResultSet.create(rows.result);
|
||||
|
||||
if (!result.isEmpty() && result.one().has(PERMISSIONS))
|
||||
{
|
||||
for (String perm : result.one().getSet(PERMISSIONS, UTF8Type.instance))
|
||||
{
|
||||
permissions.add(Permission.valueOf(perm));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adds or removes permissions from a role_permissions table (adds if op is "+", removes if op is "-")
|
||||
private void modifyRolePermissions(Set<Permission> permissions, IResource resource, String rolename, String op)
|
||||
throws RequestExecutionException
|
||||
{
|
||||
process(String.format("UPDATE %s.%s SET permissions = permissions %s {%s} WHERE role = '%s' AND resource = '%s'",
|
||||
AuthKeyspace.NAME,
|
||||
AuthKeyspace.ROLE_PERMISSIONS,
|
||||
op,
|
||||
"'" + StringUtils.join(permissions, "','") + "'",
|
||||
escape(user),
|
||||
escape(rolename),
|
||||
escape(resource.getName())));
|
||||
}
|
||||
|
||||
// Removes an entry from the inverted index table (from resource -> role with defined permissions)
|
||||
private void removeLookupEntry(IResource resource, String rolename) throws RequestExecutionException
|
||||
{
|
||||
process(String.format("DELETE FROM %s.%s WHERE resource = '%s' and role = '%s'",
|
||||
AuthKeyspace.NAME,
|
||||
AuthKeyspace.RESOURCE_ROLE_INDEX,
|
||||
escape(resource.getName()),
|
||||
escape(rolename)));
|
||||
}
|
||||
|
||||
// Adds an entry to the inverted index table (from resource -> role with defined permissions)
|
||||
private void addLookupEntry(IResource resource, String rolename) throws RequestExecutionException
|
||||
{
|
||||
process(String.format("INSERT INTO %s.%s (resource, role) VALUES ('%s','%s')",
|
||||
AuthKeyspace.NAME,
|
||||
AuthKeyspace.RESOURCE_ROLE_INDEX,
|
||||
escape(resource.getName()),
|
||||
escape(rolename)));
|
||||
}
|
||||
|
||||
// 'of' can be null - in that case everyone's permissions have been requested. Otherwise only single user's.
|
||||
// If the user requesting 'LIST PERMISSIONS' is not a superuser OR his username doesn't match 'of', we
|
||||
// If the user requesting 'LIST PERMISSIONS' is not a superuser OR their username doesn't match 'of', we
|
||||
// throw UnauthorizedException. So only a superuser can view everybody's permissions. Regular users are only
|
||||
// allowed to see their own permissions.
|
||||
public Set<PermissionDetails> list(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String of)
|
||||
public Set<PermissionDetails> list(AuthenticatedUser performer,
|
||||
Set<Permission> permissions,
|
||||
IResource resource,
|
||||
String grantee)
|
||||
throws RequestValidationException, RequestExecutionException
|
||||
{
|
||||
if (!performer.isSuper() && !performer.getName().equals(of))
|
||||
if (!performer.isSuper() && ! performer.getRoles().contains(grantee))
|
||||
throw new UnauthorizedException(String.format("You are not authorized to view %s's permissions",
|
||||
of == null ? "everyone" : of));
|
||||
grantee == null ? "everyone" : grantee));
|
||||
|
||||
Set<PermissionDetails> details = new HashSet<PermissionDetails>();
|
||||
if (null == grantee)
|
||||
return listPermissionsForRole(permissions, resource, grantee);
|
||||
|
||||
for (UntypedResultSet.Row row : process(buildListQuery(resource, of)))
|
||||
Set<String> roles = DatabaseDescriptor.getRoleManager().getRoles(grantee, true);
|
||||
Set<PermissionDetails> details = new HashSet<>();
|
||||
for (String role : roles)
|
||||
details.addAll(listPermissionsForRole(permissions, resource, role));
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
private Set<PermissionDetails> listPermissionsForRole(Set<Permission> permissions,
|
||||
IResource resource,
|
||||
String rolename)
|
||||
throws RequestExecutionException
|
||||
{
|
||||
Set<PermissionDetails> details = new HashSet<>();
|
||||
// If it exists, try the legacy user permissions table first. This is to handle the case
|
||||
// where the cluster is being upgraded and so is running with mixed versions of the perms table
|
||||
boolean useLegacyTable = Schema.instance.getCFMetaData(AuthKeyspace.NAME, USER_PERMISSIONS) != null;
|
||||
String entityColumnName = useLegacyTable ? USERNAME : ROLE;
|
||||
for (UntypedResultSet.Row row : process(buildListQuery(resource, rolename, useLegacyTable)))
|
||||
{
|
||||
if (row.has(PERMISSIONS))
|
||||
{
|
||||
|
|
@ -140,20 +298,21 @@ public class CassandraAuthorizer implements IAuthorizer
|
|||
{
|
||||
Permission permission = Permission.valueOf(p);
|
||||
if (permissions.contains(permission))
|
||||
details.add(new PermissionDetails(row.getString(USERNAME),
|
||||
details.add(new PermissionDetails(row.getString(entityColumnName),
|
||||
DataResource.fromName(row.getString(RESOURCE)),
|
||||
permission));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
private static String buildListQuery(IResource resource, String of)
|
||||
private String buildListQuery(IResource resource, String grantee, boolean useLegacyTable)
|
||||
{
|
||||
List<String> vars = Lists.newArrayList(Auth.AUTH_KS, PERMISSIONS_CF);
|
||||
List<String> conditions = new ArrayList<String>();
|
||||
String tableName = useLegacyTable ? USER_PERMISSIONS : AuthKeyspace.ROLE_PERMISSIONS;
|
||||
String entityName = useLegacyTable ? USERNAME : ROLE;
|
||||
List<String> vars = Lists.newArrayList(AuthKeyspace.NAME, tableName);
|
||||
List<String> conditions = new ArrayList<>();
|
||||
|
||||
if (resource != null)
|
||||
{
|
||||
|
|
@ -161,75 +320,27 @@ public class CassandraAuthorizer implements IAuthorizer
|
|||
vars.add(escape(resource.getName()));
|
||||
}
|
||||
|
||||
if (of != null)
|
||||
if (grantee != null)
|
||||
{
|
||||
conditions.add("username = '%s'");
|
||||
vars.add(escape(of));
|
||||
conditions.add(entityName + " = '%s'");
|
||||
vars.add(escape(grantee));
|
||||
}
|
||||
|
||||
String query = "SELECT username, resource, permissions FROM %s.%s";
|
||||
String query = "SELECT " + entityName + ", resource, permissions FROM %s.%s";
|
||||
|
||||
if (!conditions.isEmpty())
|
||||
query += " WHERE " + StringUtils.join(conditions, " AND ");
|
||||
|
||||
if (resource != null && of == null)
|
||||
if (resource != null && grantee == null)
|
||||
query += " ALLOW FILTERING";
|
||||
|
||||
return String.format(query, vars.toArray());
|
||||
}
|
||||
|
||||
// Called prior to deleting the user with DROP USER query. Internal hook, so no permission checks are needed here.
|
||||
public void revokeAll(String droppedUser)
|
||||
{
|
||||
try
|
||||
{
|
||||
process(String.format("DELETE FROM %s.%s WHERE username = '%s'", Auth.AUTH_KS, PERMISSIONS_CF, escape(droppedUser)));
|
||||
}
|
||||
catch (RequestExecutionException e)
|
||||
{
|
||||
logger.warn("CassandraAuthorizer failed to revoke all permissions of {}: {}", droppedUser, e);
|
||||
}
|
||||
}
|
||||
|
||||
// Called after a resource is removed (DROP KEYSPACE, DROP TABLE, etc.).
|
||||
public void revokeAll(IResource droppedResource)
|
||||
{
|
||||
|
||||
UntypedResultSet rows;
|
||||
try
|
||||
{
|
||||
// TODO: switch to secondary index on 'resource' once https://issues.apache.org/jira/browse/CASSANDRA-5125 is resolved.
|
||||
rows = process(String.format("SELECT username FROM %s.%s WHERE resource = '%s' ALLOW FILTERING",
|
||||
Auth.AUTH_KS,
|
||||
PERMISSIONS_CF,
|
||||
escape(droppedResource.getName())));
|
||||
}
|
||||
catch (RequestExecutionException e)
|
||||
{
|
||||
logger.warn("CassandraAuthorizer failed to revoke all permissions on {}: {}", droppedResource, e);
|
||||
return;
|
||||
}
|
||||
|
||||
for (UntypedResultSet.Row row : rows)
|
||||
{
|
||||
try
|
||||
{
|
||||
process(String.format("DELETE FROM %s.%s WHERE username = '%s' AND resource = '%s'",
|
||||
Auth.AUTH_KS,
|
||||
PERMISSIONS_CF,
|
||||
escape(row.getString(USERNAME)),
|
||||
escape(droppedResource.getName())));
|
||||
}
|
||||
catch (RequestExecutionException e)
|
||||
{
|
||||
logger.warn("CassandraAuthorizer failed to revoke all permissions on {}: {}", droppedResource, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Set<DataResource> protectedResources()
|
||||
{
|
||||
return ImmutableSet.of(DataResource.columnFamily(Auth.AUTH_KS, PERMISSIONS_CF));
|
||||
return ImmutableSet.of(DataResource.table(AuthKeyspace.NAME, AuthKeyspace.ROLE_PERMISSIONS));
|
||||
}
|
||||
|
||||
public void validateConfiguration() throws ConfigurationException
|
||||
|
|
@ -238,27 +349,99 @@ public class CassandraAuthorizer implements IAuthorizer
|
|||
|
||||
public void setup()
|
||||
{
|
||||
Auth.setupTable(PERMISSIONS_CF, PERMISSIONS_CF_SCHEMA);
|
||||
authorizeRoleStatement = prepare(ROLE, AuthKeyspace.ROLE_PERMISSIONS);
|
||||
|
||||
// If old user permissions table exists, migrate the legacy authz data to the new table
|
||||
// The delay is to give the node a chance to see its peers before attempting the conversion
|
||||
if (Schema.instance.getCFMetaData(AuthKeyspace.NAME, "permissions") != null)
|
||||
{
|
||||
legacyAuthorizeRoleStatement = prepare(USERNAME, USER_PERMISSIONS);
|
||||
|
||||
ScheduledExecutors.optionalTasks.schedule(new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
convertLegacyData();
|
||||
}
|
||||
}, AuthKeyspace.SUPERUSER_SETUP_DELAY, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private SelectStatement prepare(String entityname, String permissionsTable)
|
||||
{
|
||||
try
|
||||
{
|
||||
String query = String.format("SELECT permissions FROM %s.%s WHERE username = ? AND resource = ?", Auth.AUTH_KS, PERMISSIONS_CF);
|
||||
authorizeStatement = (SelectStatement) QueryProcessor.parseStatement(query).prepare().statement;
|
||||
String query = String.format("SELECT permissions FROM %s.%s WHERE %s = ? AND resource = ?",
|
||||
AuthKeyspace.NAME,
|
||||
permissionsTable,
|
||||
entityname);
|
||||
return (SelectStatement) QueryProcessor.getStatement(query, ClientState.forInternalCalls()).statement;
|
||||
}
|
||||
catch (RequestValidationException e)
|
||||
{
|
||||
throw new AssertionError(e); // not supposed to happen
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy legacy authz data from the system_auth.permissions table to the new system_auth.role_permissions table and
|
||||
* also insert entries into the reverse lookup table.
|
||||
* In theory, we could simply rename the existing table as the schema is structurally the same, but this would
|
||||
* break mixed clusters during a rolling upgrade.
|
||||
* This setup is not performed if AllowAllAuthenticator is configured (see Auth#setup).
|
||||
*/
|
||||
private void convertLegacyData()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Schema.instance.getCFMetaData("system_auth", "permissions") != null)
|
||||
{
|
||||
logger.info("Converting legacy permissions data");
|
||||
CQLStatement insertStatement =
|
||||
QueryProcessor.getStatement(String.format("INSERT INTO %s.%s (role, resource, permissions) " +
|
||||
"VALUES (?, ?, ?)",
|
||||
AuthKeyspace.NAME,
|
||||
AuthKeyspace.ROLE_PERMISSIONS),
|
||||
ClientState.forInternalCalls()).statement;
|
||||
CQLStatement indexStatement =
|
||||
QueryProcessor.getStatement(String.format("INSERT INTO %s.%s (resource, role) VALUES (?,?)",
|
||||
AuthKeyspace.NAME,
|
||||
AuthKeyspace.RESOURCE_ROLE_INDEX),
|
||||
ClientState.forInternalCalls()).statement;
|
||||
|
||||
UntypedResultSet permissions = process("SELECT * FROM system_auth.permissions");
|
||||
for (UntypedResultSet.Row row : permissions)
|
||||
{
|
||||
insertStatement.execute(QueryState.forInternalCalls(),
|
||||
QueryOptions.forInternalCalls(ConsistencyLevel.ONE,
|
||||
Lists.newArrayList(row.getBytes("username"),
|
||||
row.getBytes("resource"),
|
||||
row.getBytes("permissions"))));
|
||||
indexStatement.execute(QueryState.forInternalCalls(),
|
||||
QueryOptions.forInternalCalls(ConsistencyLevel.ONE,
|
||||
Lists.newArrayList(row.getBytes("resource"),
|
||||
row.getBytes("username"))));
|
||||
|
||||
}
|
||||
logger.info("Completed conversion of legacy permissions");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.info("Unable to complete conversion of legacy permissions data (perhaps not enough nodes are upgraded yet). " +
|
||||
"Conversion should not be considered complete");
|
||||
logger.debug("Conversion error", e);
|
||||
}
|
||||
}
|
||||
|
||||
// We only worry about one character ('). Make sure it's properly escaped.
|
||||
private static String escape(String name)
|
||||
private String escape(String name)
|
||||
{
|
||||
return StringUtils.replace(name, "'", "''");
|
||||
}
|
||||
|
||||
private static UntypedResultSet process(String query) throws RequestExecutionException
|
||||
private UntypedResultSet process(String query) throws RequestExecutionException
|
||||
{
|
||||
return QueryProcessor.process(query, ConsistencyLevel.ONE);
|
||||
return QueryProcessor.process(query, ConsistencyLevel.LOCAL_ONE);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,85 +22,15 @@ 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<Option> alterableOptions();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @throws AuthenticationException if credentials don't match any known user.
|
||||
*/
|
||||
AuthenticatedUser authenticate(Map<String, String> credentials) throws AuthenticationException;
|
||||
|
||||
/**
|
||||
* 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().
|
||||
*
|
||||
* @param username Username of the user to create.
|
||||
* @param options Options the user will be created with.
|
||||
* @throws RequestValidationException
|
||||
* @throws RequestExecutionException
|
||||
*/
|
||||
void create(String username, Map<Option, Object> options) throws RequestValidationException, RequestExecutionException;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
void alter(String username, Map<Option, Object> options) throws RequestValidationException, RequestExecutionException;
|
||||
|
||||
|
||||
/**
|
||||
* Called during execution of DROP USER query.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
|
|
@ -121,4 +51,78 @@ public interface IAuthenticator
|
|||
* For example, use this method to create any required keyspaces/column families.
|
||||
*/
|
||||
void setup();
|
||||
|
||||
/**
|
||||
* 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})
|
||||
*/
|
||||
SaslNegotiator newSaslNegotiator();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Should never return null - always throw AuthenticationException instead.
|
||||
* Returning AuthenticatedUser.ANONYMOUS_USER is an option as well if authentication is not required.
|
||||
*
|
||||
* @param credentials implementation specific key/value pairs
|
||||
* @return non-null representation of the authenticated subject
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
AuthenticatedUser legacyAuthenticate(Map<String, String> credentials) throws AuthenticationException;
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -19,78 +19,107 @@ 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()
|
||||
private AuthenticatedUser authenticate(String username, String password) throws AuthenticationException
|
||||
{
|
||||
return ImmutableSet.of(Option.PASSWORD);
|
||||
try
|
||||
{
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
// Let users alter their own password.
|
||||
public Set<Option> alterableOptions()
|
||||
public Set<DataResource> protectedResources()
|
||||
{
|
||||
return ImmutableSet.of(Option.PASSWORD);
|
||||
// Also protected by CassandraRoleManager, but the duplication doesn't hurt and is more explicit
|
||||
return ImmutableSet.of(DataResource.table(AuthKeyspace.NAME, AuthKeyspace.ROLES));
|
||||
}
|
||||
|
||||
public AuthenticatedUser authenticate(Map<String, String> credentials) throws AuthenticationException
|
||||
public void validateConfiguration() throws ConfigurationException
|
||||
{
|
||||
}
|
||||
|
||||
public void setup()
|
||||
{
|
||||
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 AuthenticatedUser legacyAuthenticate(Map<String, String> credentials) throws AuthenticationException
|
||||
{
|
||||
String username = credentials.get(USERNAME_KEY);
|
||||
if (username == null)
|
||||
|
|
@ -100,183 +129,71 @@ public class PasswordAuthenticator implements ISaslAwareAuthenticator
|
|||
if (password == null)
|
||||
throw new AuthenticationException(String.format("Required key '%s' is missing", PASSWORD_KEY));
|
||||
|
||||
return authenticate(username, password);
|
||||
}
|
||||
|
||||
public SaslNegotiator newSaslNegotiator()
|
||||
{
|
||||
return new PlainTextSaslAuthenticator();
|
||||
}
|
||||
|
||||
private AuthenticatedUser doAuthenticate(String username, String password, SelectStatement authenticationStatement)
|
||||
throws RequestExecutionException, AuthenticationException
|
||||
{
|
||||
UntypedResultSet result;
|
||||
try
|
||||
{
|
||||
ResultMessage.Rows rows = authenticateStatement.execute(QueryState.forInternalCalls(),
|
||||
QueryOptions.forInternalCalls(consistencyForUser(username),
|
||||
Lists.newArrayList(ByteBufferUtil.bytes(username))));
|
||||
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
|
||||
}
|
||||
catch (RequestExecutionException e)
|
||||
{
|
||||
throw new AuthenticationException(e.toString());
|
||||
}
|
||||
|
||||
if (result.isEmpty() || !BCrypt.checkpw(password, result.one().getString(SALTED_HASH)))
|
||||
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);
|
||||
}
|
||||
|
||||
public void create(String username, Map<Option, Object> options) throws InvalidRequestException, RequestExecutionException
|
||||
private SelectStatement prepare(String query)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
public void alter(String username, Map<Option, Object> options) throws RequestExecutionException
|
||||
{
|
||||
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
|
||||
{
|
||||
process(String.format("DELETE FROM %s.%s WHERE username = '%s'", Auth.AUTH_KS, CREDENTIALS_CF, escape(username)),
|
||||
consistencyForUser(username));
|
||||
}
|
||||
|
||||
public Set<DataResource> protectedResources()
|
||||
{
|
||||
return ImmutableSet.of(DataResource.columnFamily(Auth.AUTH_KS, CREDENTIALS_CF));
|
||||
}
|
||||
|
||||
public void validateConfiguration() throws ConfigurationException
|
||||
{
|
||||
}
|
||||
|
||||
public void setup()
|
||||
{
|
||||
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);
|
||||
|
||||
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;
|
||||
return (SelectStatement) QueryProcessor.getStatement(query, ClientState.forInternalCalls()).statement;
|
||||
}
|
||||
catch (RequestValidationException e)
|
||||
{
|
||||
throw new AssertionError(e); // not supposed to happen
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public SaslAuthenticator newAuthenticator()
|
||||
private class PlainTextSaslAuthenticator implements SaslNegotiator
|
||||
{
|
||||
return new PlainTextSaslAuthenticator();
|
||||
}
|
||||
|
||||
// if there are no users yet - add default superuser.
|
||||
private void setupDefaultUser()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
catch (RequestExecutionException e)
|
||||
{
|
||||
logger.warn("PasswordAuthenticator skipped default user setup: some nodes were not ready");
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,20 +19,19 @@ package org.apache.cassandra.config;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.sql.Time;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import org.supercsv.io.CsvListReader;
|
||||
import org.supercsv.prefs.CsvPreference;
|
||||
|
||||
import org.apache.cassandra.config.EncryptionOptions.ClientEncryptionOptions;
|
||||
import org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.io.util.NativeAllocator;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.supercsv.io.CsvListReader;
|
||||
import org.supercsv.prefs.CsvPreference;
|
||||
|
||||
/**
|
||||
* A class that contains configuration properties for the cassandra node it runs within.
|
||||
|
|
@ -44,9 +43,11 @@ public class Config
|
|||
public String cluster_name = "Test Cluster";
|
||||
public String authenticator;
|
||||
public String authorizer;
|
||||
public String role_manager;
|
||||
public int permissions_validity_in_ms = 2000;
|
||||
public int permissions_cache_max_entries = 1000;
|
||||
public int permissions_update_interval_in_ms = -1;
|
||||
public int roles_validity_in_ms = 2000;
|
||||
|
||||
/* Hashing strategy Random or OPHF */
|
||||
public String partitioner;
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ public class DatabaseDescriptor
|
|||
|
||||
private static IAuthenticator authenticator = new AllowAllAuthenticator();
|
||||
private static IAuthorizer authorizer = new AllowAllAuthorizer();
|
||||
private static IRoleManager roleManager = new CassandraRoleManager();
|
||||
|
||||
private static IRequestScheduler requestScheduler;
|
||||
private static RequestSchedulerId requestSchedulerId;
|
||||
|
|
@ -184,7 +185,7 @@ public class DatabaseDescriptor
|
|||
}
|
||||
}
|
||||
|
||||
/* Authentication and authorization backend, implementing IAuthenticator and IAuthorizer */
|
||||
/* Authentication, authorization and role management backend, implementing IAuthenticator, IAuthorizer & IRoleMapper*/
|
||||
if (conf.authenticator != null)
|
||||
authenticator = FBUtilities.newAuthenticator(conf.authenticator);
|
||||
|
||||
|
|
@ -194,6 +195,12 @@ public class DatabaseDescriptor
|
|||
if (authenticator instanceof AllowAllAuthenticator && !(authorizer instanceof AllowAllAuthorizer))
|
||||
throw new ConfigurationException("AllowAllAuthenticator can't be used with " + conf.authorizer);
|
||||
|
||||
if (conf.role_manager != null)
|
||||
roleManager = FBUtilities.newRoleManager(conf.role_manager);
|
||||
|
||||
if (authenticator instanceof PasswordAuthenticator && !(roleManager instanceof CassandraRoleManager))
|
||||
throw new ConfigurationException("CassandraRoleManager must be used with PasswordAuthenticator");
|
||||
|
||||
if (conf.internode_authenticator != null)
|
||||
internodeAuthenticator = FBUtilities.construct(conf.internode_authenticator, "internode_authenticator");
|
||||
else
|
||||
|
|
@ -201,6 +208,7 @@ public class DatabaseDescriptor
|
|||
|
||||
authenticator.validateConfiguration();
|
||||
authorizer.validateConfiguration();
|
||||
roleManager.validateConfiguration();
|
||||
internodeAuthenticator.validateConfiguration();
|
||||
|
||||
/* Hashing strategy */
|
||||
|
|
@ -604,6 +612,11 @@ public class DatabaseDescriptor
|
|||
return authorizer;
|
||||
}
|
||||
|
||||
public static IRoleManager getRoleManager()
|
||||
{
|
||||
return roleManager;
|
||||
}
|
||||
|
||||
public static int getPermissionsValidity()
|
||||
{
|
||||
return conf.permissions_validity_in_ms;
|
||||
|
|
@ -621,6 +634,11 @@ public class DatabaseDescriptor
|
|||
: conf.permissions_update_interval_in_ms;
|
||||
}
|
||||
|
||||
public static int getRolesValidity()
|
||||
{
|
||||
return conf.roles_validity_in_ms;
|
||||
}
|
||||
|
||||
public static int getThriftFramedTransportSize()
|
||||
{
|
||||
return conf.thrift_framed_transport_size_in_mb * 1024 * 1024;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ options {
|
|||
import org.apache.cassandra.auth.Permission;
|
||||
import org.apache.cassandra.auth.DataResource;
|
||||
import org.apache.cassandra.auth.IResource;
|
||||
import org.apache.cassandra.auth.IRoleManager;
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.cql3.statements.*;
|
||||
import org.apache.cassandra.cql3.selection.*;
|
||||
|
|
@ -247,6 +248,12 @@ cqlStatement returns [ParsedStatement stmt]
|
|||
| st29=dropFunctionStatement { $stmt = st29; }
|
||||
| st30=createAggregateStatement { $stmt = st30; }
|
||||
| st31=dropAggregateStatement { $stmt = st31; }
|
||||
| st32=createRoleStatement { $stmt = st32; }
|
||||
| st33=alterRoleStatement { $stmt = st33; }
|
||||
| st34=dropRoleStatement { $stmt = st34; }
|
||||
| st35=listRolesStatement { $stmt = st35; }
|
||||
| st36=grantRoleStatement { $stmt = st36; }
|
||||
| st37=revokeRoleStatement { $stmt = st37; }
|
||||
;
|
||||
|
||||
/*
|
||||
|
|
@ -802,7 +809,7 @@ truncateStatement returns [TruncateStatement stmt]
|
|||
;
|
||||
|
||||
/**
|
||||
* GRANT <permission> ON <resource> TO <username>
|
||||
* GRANT <permission> ON <resource> TO <rolename>
|
||||
*/
|
||||
grantStatement returns [GrantStatement stmt]
|
||||
: K_GRANT
|
||||
|
|
@ -810,12 +817,12 @@ grantStatement returns [GrantStatement stmt]
|
|||
K_ON
|
||||
resource
|
||||
K_TO
|
||||
username
|
||||
{ $stmt = new GrantStatement($permissionOrAll.perms, (DataResource) $resource.res, $username.text); }
|
||||
grantee=userOrRoleName
|
||||
{ $stmt = new GrantStatement($permissionOrAll.perms, (DataResource) $resource.res, grantee); }
|
||||
;
|
||||
|
||||
/**
|
||||
* REVOKE <permission> ON <resource> FROM <username>
|
||||
* REVOKE <permission> ON <resource> FROM <rolename>
|
||||
*/
|
||||
revokeStatement returns [RevokeStatement stmt]
|
||||
: K_REVOKE
|
||||
|
|
@ -823,22 +830,44 @@ revokeStatement returns [RevokeStatement stmt]
|
|||
K_ON
|
||||
resource
|
||||
K_FROM
|
||||
username
|
||||
{ $stmt = new RevokeStatement($permissionOrAll.perms, (DataResource) $resource.res, $username.text); }
|
||||
revokee=userOrRoleName
|
||||
{ $stmt = new RevokeStatement($permissionOrAll.perms, (DataResource) $resource.res, revokee); }
|
||||
;
|
||||
|
||||
/**
|
||||
* GRANT ROLE <rolename> TO <grantee>
|
||||
*/
|
||||
grantRoleStatement returns [GrantRoleStatement stmt]
|
||||
: K_GRANT
|
||||
role=userOrRoleName
|
||||
K_TO
|
||||
grantee=userOrRoleName
|
||||
{ $stmt = new GrantRoleStatement(role, grantee); }
|
||||
;
|
||||
|
||||
/**
|
||||
* REVOKE ROLE <rolename> FROM <revokee>
|
||||
*/
|
||||
revokeRoleStatement returns [RevokeRoleStatement stmt]
|
||||
: K_REVOKE
|
||||
role=userOrRoleName
|
||||
K_FROM
|
||||
revokee=userOrRoleName
|
||||
{ $stmt = new RevokeRoleStatement(role, revokee); }
|
||||
;
|
||||
|
||||
listPermissionsStatement returns [ListPermissionsStatement stmt]
|
||||
@init {
|
||||
IResource resource = null;
|
||||
String username = null;
|
||||
boolean recursive = true;
|
||||
RoleName grantee = new RoleName();
|
||||
}
|
||||
: K_LIST
|
||||
permissionOrAll
|
||||
( K_ON resource { resource = $resource.res; } )?
|
||||
( K_OF username { username = $username.text; } )?
|
||||
( K_OF roleName[grantee] )?
|
||||
( K_NORECURSIVE { recursive = false; } )?
|
||||
{ $stmt = new ListPermissionsStatement($permissionOrAll.perms, (DataResource) resource, username, recursive); }
|
||||
{ $stmt = new ListPermissionsStatement($permissionOrAll.perms, (DataResource) resource, grantee, recursive); }
|
||||
;
|
||||
|
||||
permission returns [Permission perm]
|
||||
|
|
@ -859,59 +888,127 @@ dataResource returns [DataResource res]
|
|||
: K_ALL K_KEYSPACES { $res = DataResource.root(); }
|
||||
| K_KEYSPACE ks = keyspaceName { $res = DataResource.keyspace($ks.id); }
|
||||
| ( K_COLUMNFAMILY )? cf = columnFamilyName
|
||||
{ $res = DataResource.columnFamily($cf.name.getKeyspace(), $cf.name.getColumnFamily()); }
|
||||
{ $res = DataResource.table($cf.name.getKeyspace(), $cf.name.getColumnFamily()); }
|
||||
;
|
||||
|
||||
/**
|
||||
* CREATE USER [IF NOT EXISTS] <username> [WITH PASSWORD <password>] [SUPERUSER|NOSUPERUSER]
|
||||
*/
|
||||
createUserStatement returns [CreateUserStatement stmt]
|
||||
createUserStatement returns [CreateRoleStatement stmt]
|
||||
@init {
|
||||
UserOptions opts = new UserOptions();
|
||||
RoleOptions opts = new RoleOptions();
|
||||
opts.put(IRoleManager.Option.LOGIN.name(), true);
|
||||
boolean superuser = false;
|
||||
boolean ifNotExists = false;
|
||||
RoleName name = new RoleName();
|
||||
}
|
||||
: K_CREATE K_USER (K_IF K_NOT K_EXISTS { ifNotExists = true; })? username
|
||||
( K_WITH userOptions[opts] )?
|
||||
: K_CREATE K_USER (K_IF K_NOT K_EXISTS { ifNotExists = true; })? u=username { name.setName($u.text, false); }
|
||||
( K_WITH roleOptions[opts] )?
|
||||
( K_SUPERUSER { superuser = true; } | K_NOSUPERUSER { superuser = false; } )?
|
||||
{ $stmt = new CreateUserStatement($username.text, opts, superuser, ifNotExists); }
|
||||
{ opts.put(IRoleManager.Option.SUPERUSER.name(), superuser);
|
||||
$stmt = new CreateRoleStatement(name, opts, ifNotExists); }
|
||||
;
|
||||
|
||||
/**
|
||||
* ALTER USER <username> [WITH PASSWORD <password>] [SUPERUSER|NOSUPERUSER]
|
||||
*/
|
||||
alterUserStatement returns [AlterUserStatement stmt]
|
||||
alterUserStatement returns [AlterRoleStatement stmt]
|
||||
@init {
|
||||
UserOptions opts = new UserOptions();
|
||||
Boolean superuser = null;
|
||||
RoleOptions opts = new RoleOptions();
|
||||
RoleName name = new RoleName();
|
||||
}
|
||||
: K_ALTER K_USER username
|
||||
( K_WITH userOptions[opts] )?
|
||||
( K_SUPERUSER { superuser = true; } | K_NOSUPERUSER { superuser = false; } )?
|
||||
{ $stmt = new AlterUserStatement($username.text, opts, superuser); }
|
||||
: K_ALTER K_USER u=username { name.setName($u.text, false); }
|
||||
( K_WITH roleOptions[opts] )?
|
||||
( K_SUPERUSER { opts.put(IRoleManager.Option.SUPERUSER.name(), true); }
|
||||
| K_NOSUPERUSER { opts.put(IRoleManager.Option.SUPERUSER.name(), false); } ) ?
|
||||
{ $stmt = new AlterRoleStatement(name, opts); }
|
||||
;
|
||||
|
||||
/**
|
||||
* DROP USER [IF EXISTS] <username>
|
||||
*/
|
||||
dropUserStatement returns [DropUserStatement stmt]
|
||||
@init { boolean ifExists = false; }
|
||||
: K_DROP K_USER (K_IF K_EXISTS { ifExists = true; })? username { $stmt = new DropUserStatement($username.text, ifExists); }
|
||||
dropUserStatement returns [DropRoleStatement stmt]
|
||||
@init {
|
||||
boolean ifExists = false;
|
||||
RoleName name = new RoleName();
|
||||
}
|
||||
: K_DROP K_USER (K_IF K_EXISTS { ifExists = true; })? u=username { name.setName($u.text, false); $stmt = new DropRoleStatement(name, ifExists); }
|
||||
;
|
||||
|
||||
/**
|
||||
* LIST USERS
|
||||
*/
|
||||
listUsersStatement returns [ListUsersStatement stmt]
|
||||
listUsersStatement returns [ListRolesStatement stmt]
|
||||
: K_LIST K_USERS { $stmt = new ListUsersStatement(); }
|
||||
;
|
||||
|
||||
userOptions[UserOptions opts]
|
||||
: userOption[opts]
|
||||
/**
|
||||
* CREATE ROLE [IF NOT EXISTS] <rolename> [WITH PASSWORD <password>] [SUPERUSER|NOSUPERUSER] [LOGIN|NOLOGIN]
|
||||
*/
|
||||
createRoleStatement returns [CreateRoleStatement stmt]
|
||||
@init {
|
||||
RoleOptions opts = new RoleOptions();
|
||||
boolean superuser = false;
|
||||
boolean login = false;
|
||||
boolean ifNotExists = false;
|
||||
}
|
||||
: K_CREATE K_ROLE (K_IF K_NOT K_EXISTS { ifNotExists = true; })? name=userOrRoleName
|
||||
( K_WITH roleOptions[opts] )?
|
||||
( K_SUPERUSER { superuser = true; } | K_NOSUPERUSER { superuser = false; } )?
|
||||
( K_LOGIN { login = true; } | K_NOLOGIN { login = false; } )?
|
||||
{ opts.put(IRoleManager.Option.SUPERUSER.name(), superuser);
|
||||
opts.put(IRoleManager.Option.LOGIN.name(), login);
|
||||
$stmt = new CreateRoleStatement(name, opts, ifNotExists); }
|
||||
;
|
||||
|
||||
userOption[UserOptions opts]
|
||||
: k=K_PASSWORD v=STRING_LITERAL { opts.put($k.text, $v.text); }
|
||||
/**
|
||||
* ALTER ROLE <rolename> [WITH PASSWORD <password>] [SUPERUSER|NOSUPERUSER]
|
||||
*/
|
||||
alterRoleStatement returns [AlterRoleStatement stmt]
|
||||
@init {
|
||||
RoleOptions opts = new RoleOptions();
|
||||
}
|
||||
: K_ALTER K_ROLE name=userOrRoleName
|
||||
( K_WITH roleOptions[opts] )?
|
||||
( K_SUPERUSER { opts.put(IRoleManager.Option.SUPERUSER.name(), true); }
|
||||
| K_NOSUPERUSER { opts.put(IRoleManager.Option.SUPERUSER.name(), false); } ) ?
|
||||
( K_LOGIN { opts.put(IRoleManager.Option.LOGIN.name(), true); }
|
||||
| K_NOLOGIN { opts.put(IRoleManager.Option.LOGIN.name(), false); } )?
|
||||
{ $stmt = new AlterRoleStatement(name, opts); }
|
||||
;
|
||||
|
||||
/**
|
||||
* DROP ROLE [IF EXISTS] <rolename>
|
||||
*/
|
||||
dropRoleStatement returns [DropRoleStatement stmt]
|
||||
@init {
|
||||
boolean ifExists = false;
|
||||
}
|
||||
: K_DROP K_ROLE (K_IF K_EXISTS { ifExists = true; })? name=userOrRoleName
|
||||
{ $stmt = new DropRoleStatement(name, ifExists); }
|
||||
;
|
||||
|
||||
/**
|
||||
* LIST ROLES [OF <rolename>] [NORECURSIVE]
|
||||
*/
|
||||
listRolesStatement returns [ListRolesStatement stmt]
|
||||
@init {
|
||||
boolean recursive = true;
|
||||
RoleName grantee = new RoleName();
|
||||
}
|
||||
: K_LIST K_ROLES
|
||||
( K_OF roleName[grantee])?
|
||||
( K_NORECURSIVE { recursive = false; } )?
|
||||
{ $stmt = new ListRolesStatement(grantee, recursive); }
|
||||
;
|
||||
|
||||
roleOptions[RoleOptions opts]
|
||||
: roleOption[opts] (K_AND roleOption[opts])*
|
||||
;
|
||||
|
||||
roleOption[RoleOptions opts]
|
||||
: k=K_PASSWORD v=STRING_LITERAL { opts.put($k.text, $v.text); }
|
||||
| k=K_OPTIONS m=mapLiteral { opts.put(IRoleManager.Option.OPTIONS.name(), convertPropertyMap(m)); }
|
||||
;
|
||||
|
||||
/** DEFINITIONS **/
|
||||
|
|
@ -952,6 +1049,11 @@ userTypeName returns [UTName name]
|
|||
: (ks=ident '.')? ut=non_type_ident { return new UTName(ks, ut); }
|
||||
;
|
||||
|
||||
userOrRoleName returns [RoleName name]
|
||||
@init { $name = new RoleName(); }
|
||||
: roleName[name] {return $name;}
|
||||
;
|
||||
|
||||
ksName[KeyspaceElementName name]
|
||||
: t=IDENT { $name.setKeyspace($t.text, false);}
|
||||
| t=QUOTED_NAME { $name.setKeyspace($t.text, true);}
|
||||
|
|
@ -973,6 +1075,13 @@ idxName[IndexName name]
|
|||
| QMARK {addRecognitionError("Bind variables cannot be used for index names");}
|
||||
;
|
||||
|
||||
roleName[RoleName name]
|
||||
: t=IDENT { $name.setName($t.text, false); }
|
||||
| t=QUOTED_NAME { $name.setName($t.text, true); }
|
||||
| k=unreserved_keyword { $name.setName(k, false); }
|
||||
| QMARK {addRecognitionError("Bind variables cannot be used for role names");}
|
||||
;
|
||||
|
||||
constant returns [Constants.Literal constant]
|
||||
: t=STRING_LITERAL { $constant = Constants.Literal.string($t.text); }
|
||||
| t=INTEGER { $constant = Constants.Literal.integer($t.text); }
|
||||
|
|
@ -1326,8 +1435,13 @@ basic_unreserved_keyword returns [String str]
|
|||
| K_ALL
|
||||
| K_USER
|
||||
| K_USERS
|
||||
| K_ROLE
|
||||
| K_ROLES
|
||||
| K_SUPERUSER
|
||||
| K_NOSUPERUSER
|
||||
| K_LOGIN
|
||||
| K_NOLOGIN
|
||||
| K_OPTIONS
|
||||
| K_PASSWORD
|
||||
| K_EXISTS
|
||||
| K_CUSTOM
|
||||
|
|
@ -1417,9 +1531,14 @@ K_NORECURSIVE: N O R E C U R S I V E;
|
|||
|
||||
K_USER: U S E R;
|
||||
K_USERS: U S E R S;
|
||||
K_ROLE: R O L E;
|
||||
K_ROLES: R O L E S;
|
||||
K_SUPERUSER: S U P E R U S E R;
|
||||
K_NOSUPERUSER: N O S U P E R U S E R;
|
||||
K_PASSWORD: P A S S W O R D;
|
||||
K_LOGIN: L O G I N;
|
||||
K_NOLOGIN: N O L O G I N;
|
||||
K_OPTIONS: O P T I O N S;
|
||||
|
||||
K_CLUSTERING: C L U S T E R I N G;
|
||||
K_ASCII: A S C I I;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class RoleName
|
||||
{
|
||||
private String name;
|
||||
|
||||
public void setName(String name, boolean keepCase)
|
||||
{
|
||||
this.name = keepCase ? name : name.toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
|
@ -20,18 +20,18 @@ package org.apache.cassandra.cql3;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cassandra.auth.IAuthenticator;
|
||||
import org.apache.cassandra.auth.IRoleManager;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
public class UserOptions
|
||||
public class RoleOptions
|
||||
{
|
||||
private final Map<IAuthenticator.Option, Object> options = new HashMap<IAuthenticator.Option, Object>();
|
||||
private final Map<IRoleManager.Option, Object> options = new HashMap<>();
|
||||
|
||||
public void put(String name, Object value)
|
||||
{
|
||||
options.put(IAuthenticator.Option.valueOf(name.toUpperCase()), value);
|
||||
options.put(IRoleManager.Option.valueOf(name.toUpperCase()), value);
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
|
|
@ -39,18 +39,18 @@ public class UserOptions
|
|||
return options.isEmpty();
|
||||
}
|
||||
|
||||
public Map<IAuthenticator.Option, Object> getOptions()
|
||||
public Map<IRoleManager.Option, Object> getOptions()
|
||||
{
|
||||
return options;
|
||||
}
|
||||
|
||||
public void validate() throws InvalidRequestException
|
||||
{
|
||||
for (IAuthenticator.Option option : options.keySet())
|
||||
for (IRoleManager.Option option : options.keySet())
|
||||
{
|
||||
if (!DatabaseDescriptor.getAuthenticator().supportedOptions().contains(option))
|
||||
throw new InvalidRequestException(String.format("%s doesn't support %s option",
|
||||
DatabaseDescriptor.getAuthenticator().getClass().getName(),
|
||||
if (!DatabaseDescriptor.getRoleManager().supportedOptions().contains(option))
|
||||
throw new InvalidRequestException(String.format("%s doesn't support %s",
|
||||
DatabaseDescriptor.getRoleManager().getClass().getName(),
|
||||
option));
|
||||
}
|
||||
}
|
||||
|
|
@ -17,66 +17,60 @@
|
|||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
import org.apache.cassandra.auth.Auth;
|
||||
import org.apache.cassandra.auth.AuthenticatedUser;
|
||||
import org.apache.cassandra.auth.IAuthenticator;
|
||||
import org.apache.cassandra.auth.IRoleManager.Option;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.UserOptions;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.exceptions.RequestExecutionException;
|
||||
import org.apache.cassandra.exceptions.RequestValidationException;
|
||||
import org.apache.cassandra.exceptions.UnauthorizedException;
|
||||
import org.apache.cassandra.cql3.RoleName;
|
||||
import org.apache.cassandra.cql3.RoleOptions;
|
||||
import org.apache.cassandra.exceptions.*;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.transport.messages.ResultMessage;
|
||||
|
||||
public class AlterUserStatement extends AuthenticationStatement
|
||||
public class AlterRoleStatement extends AuthenticationStatement
|
||||
{
|
||||
private final String username;
|
||||
private final UserOptions opts;
|
||||
private final Boolean superuser;
|
||||
private final String role;
|
||||
private final RoleOptions opts;
|
||||
|
||||
public AlterUserStatement(String username, UserOptions opts, Boolean superuser)
|
||||
public AlterRoleStatement(RoleName name, RoleOptions opts)
|
||||
{
|
||||
this.username = username;
|
||||
this.role = name.getName();
|
||||
this.opts = opts;
|
||||
this.superuser = superuser;
|
||||
}
|
||||
|
||||
public void validate(ClientState state) throws RequestValidationException
|
||||
{
|
||||
opts.validate();
|
||||
|
||||
if (superuser == null && opts.isEmpty())
|
||||
throw new InvalidRequestException("ALTER USER can't be empty");
|
||||
if (opts.isEmpty())
|
||||
throw new InvalidRequestException("ALTER [ROLE|USER] can't be empty");
|
||||
|
||||
// validate login here before checkAccess to avoid leaking user existence to anonymous users.
|
||||
state.ensureNotAnonymous();
|
||||
|
||||
if (!Auth.isExistingUser(username))
|
||||
throw new InvalidRequestException(String.format("User %s doesn't exist", username));
|
||||
if (!DatabaseDescriptor.getRoleManager().isExistingRole(role))
|
||||
throw new InvalidRequestException(String.format("%s doesn't exist", role));
|
||||
}
|
||||
|
||||
public void checkAccess(ClientState state) throws UnauthorizedException
|
||||
{
|
||||
AuthenticatedUser user = state.getUser();
|
||||
|
||||
boolean isSuper = user.isSuper();
|
||||
|
||||
if (superuser != null && user.getName().equals(username))
|
||||
throw new UnauthorizedException("You aren't allowed to alter your own superuser status");
|
||||
if (opts.getOptions().containsKey(Option.SUPERUSER) && user.getRoles().contains(role))
|
||||
throw new UnauthorizedException("You aren't allowed to alter your own superuser " +
|
||||
"status or that of a role granted to you");
|
||||
|
||||
if (superuser != null && !isSuper)
|
||||
if (opts.getOptions().containsKey(Option.SUPERUSER) && !isSuper)
|
||||
throw new UnauthorizedException("Only superusers are allowed to alter superuser status");
|
||||
|
||||
if (!user.isSuper() && !user.getName().equals(username))
|
||||
throw new UnauthorizedException("You aren't allowed to alter this user");
|
||||
if (!user.isSuper() && !user.getName().equals(role))
|
||||
throw new UnauthorizedException("You aren't allowed to alter this role");
|
||||
|
||||
if (!isSuper)
|
||||
{
|
||||
for (IAuthenticator.Option option : opts.getOptions().keySet())
|
||||
for (Option option : opts.getOptions().keySet())
|
||||
{
|
||||
if (!DatabaseDescriptor.getAuthenticator().alterableOptions().contains(option))
|
||||
throw new UnauthorizedException(String.format("You aren't allowed to alter %s option", option));
|
||||
if (!DatabaseDescriptor.getRoleManager().alterableOptions().contains(option))
|
||||
throw new UnauthorizedException(String.format("You aren't allowed to alter %s", option));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -84,9 +78,7 @@ public class AlterUserStatement extends AuthenticationStatement
|
|||
public ResultMessage execute(ClientState state) throws RequestValidationException, RequestExecutionException
|
||||
{
|
||||
if (!opts.isEmpty())
|
||||
DatabaseDescriptor.getAuthenticator().alter(username, opts.getOptions());
|
||||
if (superuser != null)
|
||||
Auth.insertUser(username, superuser.booleanValue());
|
||||
DatabaseDescriptor.getRoleManager().alterRole(state.getUser(), role, opts.getOptions());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -55,8 +55,8 @@ public abstract class AuthorizationStatement extends ParsedStatement implements
|
|||
|
||||
public static DataResource maybeCorrectResource(DataResource resource, ClientState state) throws InvalidRequestException
|
||||
{
|
||||
if (resource.isColumnFamilyLevel() && resource.getKeyspace() == null)
|
||||
return DataResource.columnFamily(state.getKeyspace(), resource.getColumnFamily());
|
||||
if (resource.isTableLevel() && resource.getKeyspace() == null)
|
||||
return DataResource.table(state.getKeyspace(), resource.getTable());
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,59 +17,60 @@
|
|||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
import org.apache.cassandra.auth.Auth;
|
||||
import org.apache.cassandra.auth.IRoleManager.Option;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.UserOptions;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.exceptions.RequestExecutionException;
|
||||
import org.apache.cassandra.exceptions.RequestValidationException;
|
||||
import org.apache.cassandra.exceptions.UnauthorizedException;
|
||||
import org.apache.cassandra.cql3.RoleName;
|
||||
import org.apache.cassandra.cql3.RoleOptions;
|
||||
import org.apache.cassandra.exceptions.*;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.transport.messages.ResultMessage;
|
||||
|
||||
public class CreateUserStatement extends AuthenticationStatement
|
||||
public class CreateRoleStatement extends AuthorizationStatement
|
||||
{
|
||||
private final String username;
|
||||
private final UserOptions opts;
|
||||
private final boolean superuser;
|
||||
private final String role;
|
||||
private final RoleOptions opts;
|
||||
private final boolean ifNotExists;
|
||||
|
||||
public CreateUserStatement(String username, UserOptions opts, boolean superuser, boolean ifNotExists)
|
||||
public CreateRoleStatement(RoleName name, RoleOptions options, boolean ifNotExists)
|
||||
{
|
||||
this.username = username;
|
||||
this.opts = opts;
|
||||
this.superuser = superuser;
|
||||
this.role = name.getName();
|
||||
this.opts = options;
|
||||
this.ifNotExists = ifNotExists;
|
||||
}
|
||||
|
||||
public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException
|
||||
{
|
||||
if (!state.getUser().isSuper())
|
||||
throw new UnauthorizedException("Only superusers are allowed to perform CREATE [ROLE|USER] queries");
|
||||
}
|
||||
|
||||
public void validate(ClientState state) throws RequestValidationException
|
||||
{
|
||||
if (username.isEmpty())
|
||||
throw new InvalidRequestException("Username can't be an empty string");
|
||||
|
||||
opts.validate();
|
||||
|
||||
// validate login here before checkAccess to avoid leaking user existence to anonymous users.
|
||||
if (role.isEmpty())
|
||||
throw new InvalidRequestException("Role name can't be an empty string");
|
||||
|
||||
// validate login here before checkAccess to avoid leaking role existence to anonymous users.
|
||||
state.ensureNotAnonymous();
|
||||
|
||||
if (!ifNotExists && Auth.isExistingUser(username))
|
||||
throw new InvalidRequestException(String.format("User %s already exists", username));
|
||||
if (!ifNotExists && DatabaseDescriptor.getRoleManager().isExistingRole(role))
|
||||
throw new InvalidRequestException(String.format("%s already exists", role));
|
||||
|
||||
for (Option option : opts.getOptions().keySet())
|
||||
{
|
||||
if (!DatabaseDescriptor.getRoleManager().supportedOptions().contains(option))
|
||||
throw new UnauthorizedException(String.format("You aren't allowed to alter %s", option));
|
||||
}
|
||||
}
|
||||
|
||||
public void checkAccess(ClientState state) throws UnauthorizedException
|
||||
{
|
||||
if (!state.getUser().isSuper())
|
||||
throw new UnauthorizedException("Only superusers are allowed to perform CREATE USER queries");
|
||||
}
|
||||
|
||||
public ResultMessage execute(ClientState state) throws RequestValidationException, RequestExecutionException
|
||||
public ResultMessage execute(ClientState state) throws RequestExecutionException, RequestValidationException
|
||||
{
|
||||
// not rejected in validate()
|
||||
if (ifNotExists && Auth.isExistingUser(username))
|
||||
if (ifNotExists && DatabaseDescriptor.getRoleManager().isExistingRole(role))
|
||||
return null;
|
||||
|
||||
DatabaseDescriptor.getAuthenticator().create(username, opts.getOptions());
|
||||
Auth.insertUser(username, superuser);
|
||||
DatabaseDescriptor.getRoleManager().createRole(state.getUser(), role, opts.getOptions());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,24 +17,21 @@
|
|||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
import org.apache.cassandra.auth.Auth;
|
||||
import org.apache.cassandra.auth.AuthenticatedUser;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.exceptions.RequestExecutionException;
|
||||
import org.apache.cassandra.exceptions.RequestValidationException;
|
||||
import org.apache.cassandra.exceptions.UnauthorizedException;
|
||||
import org.apache.cassandra.cql3.RoleName;
|
||||
import org.apache.cassandra.exceptions.*;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.transport.messages.ResultMessage;
|
||||
|
||||
public class DropUserStatement extends AuthenticationStatement
|
||||
public class DropRoleStatement extends AuthenticationStatement
|
||||
{
|
||||
private final String username;
|
||||
private final String role;
|
||||
private final boolean ifExists;
|
||||
|
||||
public DropUserStatement(String username, boolean ifExists)
|
||||
public DropRoleStatement(RoleName name, boolean ifExists)
|
||||
{
|
||||
this.username = username;
|
||||
this.role = name.getName();
|
||||
this.ifExists = ifExists;
|
||||
}
|
||||
|
||||
|
|
@ -43,30 +40,29 @@ public class DropUserStatement extends AuthenticationStatement
|
|||
// validate login here before checkAccess to avoid leaking user existence to anonymous users.
|
||||
state.ensureNotAnonymous();
|
||||
|
||||
if (!ifExists && !Auth.isExistingUser(username))
|
||||
throw new InvalidRequestException(String.format("User %s doesn't exist", username));
|
||||
if (!ifExists && !DatabaseDescriptor.getRoleManager().isExistingRole(role))
|
||||
throw new InvalidRequestException(String.format("%s doesn't exist", role));
|
||||
|
||||
AuthenticatedUser user = state.getUser();
|
||||
if (user != null && user.getName().equals(username))
|
||||
throw new InvalidRequestException("Users aren't allowed to DROP themselves");
|
||||
if (user != null && user.getName().equals(role))
|
||||
throw new InvalidRequestException("Cannot DROP primary role for current login");
|
||||
}
|
||||
|
||||
public void checkAccess(ClientState state) throws UnauthorizedException
|
||||
{
|
||||
if (!state.getUser().isSuper())
|
||||
throw new UnauthorizedException("Only superusers are allowed to perform DROP USER queries");
|
||||
throw new UnauthorizedException("Only superusers are allowed to perform DROP [ROLE|USER] queries" );
|
||||
}
|
||||
|
||||
public ResultMessage execute(ClientState state) throws RequestValidationException, RequestExecutionException
|
||||
{
|
||||
// not rejected in validate()
|
||||
if (ifExists && !Auth.isExistingUser(username))
|
||||
if (ifExists && !DatabaseDescriptor.getRoleManager().isExistingRole(role))
|
||||
return null;
|
||||
|
||||
// clean up permissions after the dropped user.
|
||||
DatabaseDescriptor.getAuthorizer().revokeAll(username);
|
||||
Auth.deleteUser(username);
|
||||
DatabaseDescriptor.getAuthenticator().drop(username);
|
||||
// clean up grants and permissions of the dropped role.
|
||||
DatabaseDescriptor.getRoleManager().dropRole(state.getUser(), role);
|
||||
DatabaseDescriptor.getAuthorizer().revokeAll(role);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.RoleName;
|
||||
import org.apache.cassandra.exceptions.RequestExecutionException;
|
||||
import org.apache.cassandra.exceptions.RequestValidationException;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.transport.messages.ResultMessage;
|
||||
|
||||
public class GrantRoleStatement extends RoleManagementStatement
|
||||
{
|
||||
public GrantRoleStatement(RoleName name, RoleName grantee)
|
||||
{
|
||||
super(name, grantee);
|
||||
}
|
||||
|
||||
public ResultMessage execute(ClientState state) throws RequestValidationException, RequestExecutionException
|
||||
{
|
||||
DatabaseDescriptor.getRoleManager().grantRole(state.getUser(), role, grantee);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/*
|
||||
* 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
|
||||
|
|
@ -7,14 +7,13 @@
|
|||
* "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
|
||||
* 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.
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
|
|
@ -23,6 +22,7 @@ import java.util.Set;
|
|||
import org.apache.cassandra.auth.DataResource;
|
||||
import org.apache.cassandra.auth.Permission;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.RoleName;
|
||||
import org.apache.cassandra.exceptions.RequestExecutionException;
|
||||
import org.apache.cassandra.exceptions.RequestValidationException;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
|
|
@ -30,14 +30,14 @@ import org.apache.cassandra.transport.messages.ResultMessage;
|
|||
|
||||
public class GrantStatement extends PermissionAlteringStatement
|
||||
{
|
||||
public GrantStatement(Set<Permission> permissions, DataResource resource, String username)
|
||||
public GrantStatement(Set<Permission> permissions, DataResource resource, RoleName grantee)
|
||||
{
|
||||
super(permissions, resource, username);
|
||||
super(permissions, resource, grantee);
|
||||
}
|
||||
|
||||
public ResultMessage execute(ClientState state) throws RequestValidationException, RequestExecutionException
|
||||
{
|
||||
DatabaseDescriptor.getAuthorizer().grant(state.getUser(), permissions, resource, username);
|
||||
DatabaseDescriptor.getAuthorizer().grant(state.getUser(), permissions, resource, grantee);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,9 +21,7 @@ import java.util.*;
|
|||
|
||||
import org.apache.cassandra.auth.*;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.ColumnSpecification;
|
||||
import org.apache.cassandra.cql3.ResultSet;
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.db.marshal.UTF8Type;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.exceptions.RequestExecutionException;
|
||||
|
|
@ -33,7 +31,7 @@ import org.apache.cassandra.transport.messages.ResultMessage;
|
|||
|
||||
public class ListPermissionsStatement extends AuthorizationStatement
|
||||
{
|
||||
private static final String KS = Auth.AUTH_KS;
|
||||
private static final String KS = AuthKeyspace.NAME;
|
||||
private static final String CF = "permissions"; // virtual cf to use for now.
|
||||
|
||||
private static final List<ColumnSpecification> metadata;
|
||||
|
|
@ -41,23 +39,24 @@ public class ListPermissionsStatement extends AuthorizationStatement
|
|||
static
|
||||
{
|
||||
List<ColumnSpecification> columns = new ArrayList<ColumnSpecification>(4);
|
||||
columns.add(new ColumnSpecification(KS, CF, new ColumnIdentifier("role", true), UTF8Type.instance));
|
||||
columns.add(new ColumnSpecification(KS, CF, new ColumnIdentifier("username", true), UTF8Type.instance));
|
||||
columns.add(new ColumnSpecification(KS, CF, new ColumnIdentifier("resource", true), UTF8Type.instance));
|
||||
columns.add(new ColumnSpecification(KS, CF, new ColumnIdentifier("permission", true), UTF8Type.instance));
|
||||
metadata = Collections.unmodifiableList(columns);
|
||||
}
|
||||
|
||||
private final Set<Permission> permissions;
|
||||
private DataResource resource;
|
||||
private final String username;
|
||||
private final boolean recursive;
|
||||
protected final Set<Permission> permissions;
|
||||
protected DataResource resource;
|
||||
protected final boolean recursive;
|
||||
private final String grantee;
|
||||
|
||||
public ListPermissionsStatement(Set<Permission> permissions, DataResource resource, String username, boolean recursive)
|
||||
public ListPermissionsStatement(Set<Permission> permissions, DataResource resource, RoleName grantee, boolean recursive)
|
||||
{
|
||||
this.permissions = permissions;
|
||||
this.resource = resource;
|
||||
this.username = username;
|
||||
this.recursive = recursive;
|
||||
this.grantee = grantee.getName();
|
||||
}
|
||||
|
||||
public void validate(ClientState state) throws RequestValidationException
|
||||
|
|
@ -65,16 +64,16 @@ public class ListPermissionsStatement extends AuthorizationStatement
|
|||
// a check to ensure the existence of the user isn't being leaked by user existence check.
|
||||
state.ensureNotAnonymous();
|
||||
|
||||
if (username != null && !Auth.isExistingUser(username))
|
||||
throw new InvalidRequestException(String.format("User %s doesn't exist", username));
|
||||
|
||||
if (resource != null)
|
||||
{
|
||||
resource = maybeCorrectResource(resource, state);
|
||||
if (!resource.exists())
|
||||
throw new InvalidRequestException(String.format("%s doesn't exist", resource));
|
||||
}
|
||||
}
|
||||
|
||||
if ((grantee != null) && !DatabaseDescriptor.getRoleManager().isExistingRole(grantee))
|
||||
throw new InvalidRequestException(String.format("%s doesn't exist", grantee));
|
||||
}
|
||||
|
||||
public void checkAccess(ClientState state)
|
||||
{
|
||||
|
|
@ -100,6 +99,12 @@ public class ListPermissionsStatement extends AuthorizationStatement
|
|||
return resultMessage(details);
|
||||
}
|
||||
|
||||
private Set<PermissionDetails> list(ClientState state, IResource resource)
|
||||
throws RequestValidationException, RequestExecutionException
|
||||
{
|
||||
return DatabaseDescriptor.getAuthorizer().list(state.getUser(), permissions, resource, grantee);
|
||||
}
|
||||
|
||||
private ResultMessage resultMessage(List<PermissionDetails> details)
|
||||
{
|
||||
if (details.isEmpty())
|
||||
|
|
@ -108,16 +113,11 @@ public class ListPermissionsStatement extends AuthorizationStatement
|
|||
ResultSet result = new ResultSet(metadata);
|
||||
for (PermissionDetails pd : details)
|
||||
{
|
||||
result.addColumnValue(UTF8Type.instance.decompose(pd.username));
|
||||
result.addColumnValue(UTF8Type.instance.decompose(pd.grantee));
|
||||
result.addColumnValue(UTF8Type.instance.decompose(pd.grantee));
|
||||
result.addColumnValue(UTF8Type.instance.decompose(pd.resource.toString()));
|
||||
result.addColumnValue(UTF8Type.instance.decompose(pd.permission.toString()));
|
||||
}
|
||||
return new ResultMessage.Rows(result);
|
||||
}
|
||||
|
||||
private Set<PermissionDetails> list(ClientState state, IResource resource)
|
||||
throws RequestValidationException, RequestExecutionException
|
||||
{
|
||||
return DatabaseDescriptor.getAuthorizer().list(state.getUser(), permissions, resource, username);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import org.apache.cassandra.auth.AuthKeyspace;
|
||||
import org.apache.cassandra.auth.IRoleManager;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.db.marshal.BooleanType;
|
||||
import org.apache.cassandra.db.marshal.UTF8Type;
|
||||
import org.apache.cassandra.exceptions.*;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.transport.messages.ResultMessage;
|
||||
|
||||
public class ListRolesStatement extends AuthorizationStatement
|
||||
{
|
||||
// pseudo-virtual cf as the actual datasource is dependent on the IRoleManager impl
|
||||
private static final String KS = AuthKeyspace.NAME;
|
||||
private static final String CF = AuthKeyspace.ROLES;
|
||||
|
||||
private static final List<ColumnSpecification> metadata =
|
||||
ImmutableList.of(new ColumnSpecification(KS, CF, new ColumnIdentifier("role", true), UTF8Type.instance),
|
||||
new ColumnSpecification(KS, CF, new ColumnIdentifier("super", true), BooleanType.instance),
|
||||
new ColumnSpecification(KS, CF, new ColumnIdentifier("login", true), BooleanType.instance));
|
||||
|
||||
private final String grantee;
|
||||
private final boolean recursive;
|
||||
|
||||
public ListRolesStatement()
|
||||
{
|
||||
this(new RoleName(), false);
|
||||
}
|
||||
|
||||
public ListRolesStatement(RoleName grantee, boolean recursive)
|
||||
{
|
||||
this.grantee = grantee.getName();
|
||||
this.recursive = recursive;
|
||||
}
|
||||
|
||||
public void validate(ClientState state) throws UnauthorizedException, InvalidRequestException
|
||||
{
|
||||
state.ensureNotAnonymous();
|
||||
|
||||
if ((grantee != null) && !DatabaseDescriptor.getRoleManager().isExistingRole(grantee))
|
||||
throw new InvalidRequestException(String.format("%s doesn't exist", grantee));
|
||||
}
|
||||
|
||||
public void checkAccess(ClientState state) throws InvalidRequestException
|
||||
{
|
||||
}
|
||||
|
||||
public ResultMessage execute(ClientState state) throws RequestValidationException, RequestExecutionException
|
||||
{
|
||||
if (state.getUser().isSuper())
|
||||
{
|
||||
if (grantee == null)
|
||||
return resultMessage(DatabaseDescriptor.getRoleManager().getAllRoles());
|
||||
else
|
||||
return resultMessage(DatabaseDescriptor.getRoleManager().getRoles(grantee, recursive));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (grantee == null)
|
||||
return resultMessage(DatabaseDescriptor.getRoleManager().getRoles(state.getUser().getName(), recursive));
|
||||
if (DatabaseDescriptor.getRoleManager().getRoles(state.getUser().getName(), true).contains(grantee))
|
||||
return resultMessage(DatabaseDescriptor.getRoleManager().getRoles(grantee, recursive));
|
||||
else
|
||||
throw new UnauthorizedException(String.format("You are not authorized to view roles granted to %s ", grantee));
|
||||
}
|
||||
}
|
||||
|
||||
private ResultMessage resultMessage(Set<String> roles)
|
||||
{
|
||||
if (roles.isEmpty())
|
||||
return new ResultMessage.Void();
|
||||
|
||||
List<String> sorted = Lists.newArrayList(roles);
|
||||
Collections.sort(sorted);
|
||||
return formatResults(sorted);
|
||||
}
|
||||
|
||||
// overridden in ListUsersStatement to include legacy metadata
|
||||
protected ResultMessage formatResults(List<String> sortedRoles)
|
||||
{
|
||||
ResultSet result = new ResultSet(metadata);
|
||||
|
||||
IRoleManager roleManager = DatabaseDescriptor.getRoleManager();
|
||||
for (String role : sortedRoles)
|
||||
{
|
||||
result.addColumnValue(UTF8Type.instance.decompose(role));
|
||||
result.addColumnValue(BooleanType.instance.decompose(roleManager.isSuper(role)));
|
||||
result.addColumnValue(BooleanType.instance.decompose(roleManager.canLogin(role)));
|
||||
}
|
||||
return new ResultMessage.Rows(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,31 +17,43 @@
|
|||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
import org.apache.cassandra.auth.Auth;
|
||||
import org.apache.cassandra.cql3.QueryProcessor;
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
import org.apache.cassandra.exceptions.RequestExecutionException;
|
||||
import org.apache.cassandra.exceptions.RequestValidationException;
|
||||
import org.apache.cassandra.exceptions.UnauthorizedException;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.service.QueryState;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import org.apache.cassandra.auth.AuthKeyspace;
|
||||
import org.apache.cassandra.auth.IRoleManager;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.ColumnSpecification;
|
||||
import org.apache.cassandra.cql3.ResultSet;
|
||||
import org.apache.cassandra.db.marshal.BooleanType;
|
||||
import org.apache.cassandra.db.marshal.UTF8Type;
|
||||
import org.apache.cassandra.transport.messages.ResultMessage;
|
||||
|
||||
public class ListUsersStatement extends AuthenticationStatement
|
||||
public class ListUsersStatement extends ListRolesStatement
|
||||
{
|
||||
public void validate(ClientState state)
|
||||
{
|
||||
}
|
||||
// pseudo-virtual cf as the actual datasource is dependent on the IRoleManager impl
|
||||
private static final String KS = AuthKeyspace.NAME;
|
||||
private static final String CF = "users";
|
||||
|
||||
public void checkAccess(ClientState state) throws UnauthorizedException
|
||||
{
|
||||
state.ensureNotAnonymous();
|
||||
}
|
||||
private static final List<ColumnSpecification> metadata =
|
||||
ImmutableList.of(new ColumnSpecification(KS, CF, new ColumnIdentifier("name", true), UTF8Type.instance),
|
||||
new ColumnSpecification(KS, CF, new ColumnIdentifier("super", true), BooleanType.instance));
|
||||
|
||||
public ResultMessage execute(ClientState state) throws RequestValidationException, RequestExecutionException
|
||||
@Override
|
||||
protected ResultMessage formatResults(List<String> sortedRoles)
|
||||
{
|
||||
return QueryProcessor.process(String.format("SELECT * FROM %s.%s", Auth.AUTH_KS, Auth.USERS_CF),
|
||||
ConsistencyLevel.QUORUM,
|
||||
QueryState.forInternalCalls());
|
||||
ResultSet result = new ResultSet(metadata);
|
||||
|
||||
IRoleManager roleManager = DatabaseDescriptor.getRoleManager();
|
||||
for (String role : sortedRoles)
|
||||
{
|
||||
if (!roleManager.canLogin(role))
|
||||
continue;
|
||||
result.addColumnValue(UTF8Type.instance.decompose(role));
|
||||
result.addColumnValue(BooleanType.instance.decompose(roleManager.isSuper(role)));
|
||||
}
|
||||
return new ResultMessage.Rows(result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ package org.apache.cassandra.cql3.statements;
|
|||
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.auth.Auth;
|
||||
import org.apache.cassandra.auth.DataResource;
|
||||
import org.apache.cassandra.auth.IResource;
|
||||
import org.apache.cassandra.auth.Permission;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.RoleName;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.exceptions.RequestValidationException;
|
||||
import org.apache.cassandra.exceptions.UnauthorizedException;
|
||||
|
|
@ -32,13 +32,13 @@ public abstract class PermissionAlteringStatement extends AuthorizationStatement
|
|||
{
|
||||
protected final Set<Permission> permissions;
|
||||
protected DataResource resource;
|
||||
protected final String username;
|
||||
protected final String grantee;
|
||||
|
||||
protected PermissionAlteringStatement(Set<Permission> permissions, DataResource resource, String username)
|
||||
protected PermissionAlteringStatement(Set<Permission> permissions, DataResource resource, RoleName grantee)
|
||||
{
|
||||
this.permissions = permissions;
|
||||
this.resource = resource;
|
||||
this.username = username;
|
||||
this.grantee = grantee.getName();
|
||||
}
|
||||
|
||||
public void validate(ClientState state) throws RequestValidationException
|
||||
|
|
@ -46,13 +46,13 @@ public abstract class PermissionAlteringStatement extends AuthorizationStatement
|
|||
// validate login here before checkAccess to avoid leaking user existence to anonymous users.
|
||||
state.ensureNotAnonymous();
|
||||
|
||||
if (!Auth.isExistingUser(username))
|
||||
throw new InvalidRequestException(String.format("User %s doesn't exist", username));
|
||||
if (!DatabaseDescriptor.getRoleManager().isExistingRole(grantee))
|
||||
throw new InvalidRequestException(String.format("Role %s doesn't exist", grantee));
|
||||
|
||||
// if a keyspace is omitted when GRANT/REVOKE ON TABLE <table>, we need to correct the resource.
|
||||
resource = maybeCorrectResource(resource, state);
|
||||
if (!resource.exists())
|
||||
throw new InvalidRequestException(String.format("%s doesn't exist", resource));
|
||||
throw new InvalidRequestException(String.format("Resource %s doesn't exist", resource));
|
||||
}
|
||||
|
||||
public void checkAccess(ClientState state) throws UnauthorizedException
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.RoleName;
|
||||
import org.apache.cassandra.exceptions.RequestExecutionException;
|
||||
import org.apache.cassandra.exceptions.RequestValidationException;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.transport.messages.ResultMessage;
|
||||
|
||||
public class RevokeRoleStatement extends RoleManagementStatement
|
||||
{
|
||||
public RevokeRoleStatement(RoleName name, RoleName grantee)
|
||||
{
|
||||
super(name, grantee);
|
||||
}
|
||||
|
||||
public ResultMessage execute(ClientState state) throws RequestValidationException, RequestExecutionException
|
||||
{
|
||||
DatabaseDescriptor.getRoleManager().revokeRole(state.getUser(), role, grantee);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/*
|
||||
* 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
|
||||
|
|
@ -7,14 +7,13 @@
|
|||
* "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
|
||||
* 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.
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
|
|
@ -23,6 +22,7 @@ import java.util.Set;
|
|||
import org.apache.cassandra.auth.DataResource;
|
||||
import org.apache.cassandra.auth.Permission;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.RoleName;
|
||||
import org.apache.cassandra.exceptions.RequestExecutionException;
|
||||
import org.apache.cassandra.exceptions.RequestValidationException;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
|
|
@ -30,14 +30,14 @@ import org.apache.cassandra.transport.messages.ResultMessage;
|
|||
|
||||
public class RevokeStatement extends PermissionAlteringStatement
|
||||
{
|
||||
public RevokeStatement(Set<Permission> permissions, DataResource resource, String username)
|
||||
public RevokeStatement(Set<Permission> permissions, DataResource resource, RoleName grantee)
|
||||
{
|
||||
super(permissions, resource, username);
|
||||
super(permissions, resource, grantee);
|
||||
}
|
||||
|
||||
public ResultMessage execute(ClientState state) throws RequestValidationException, RequestExecutionException
|
||||
{
|
||||
DatabaseDescriptor.getAuthorizer().revoke(state.getUser(), permissions, resource, username);
|
||||
DatabaseDescriptor.getAuthorizer().revoke(state.getUser(), permissions, resource, grantee);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.RoleName;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.exceptions.RequestValidationException;
|
||||
import org.apache.cassandra.exceptions.UnauthorizedException;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
|
||||
public abstract class RoleManagementStatement extends AuthorizationStatement
|
||||
{
|
||||
protected final String role;
|
||||
protected final String grantee;
|
||||
|
||||
public RoleManagementStatement(RoleName name, RoleName grantee)
|
||||
{
|
||||
this.role = name.getName();
|
||||
this.grantee = grantee.getName();
|
||||
}
|
||||
|
||||
public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException
|
||||
{
|
||||
if (!state.getUser().isSuper())
|
||||
throw new UnauthorizedException("Only superusers are allowed to perform role management queries");
|
||||
}
|
||||
|
||||
public void validate(ClientState state) throws RequestValidationException
|
||||
{
|
||||
state.ensureNotAnonymous();
|
||||
|
||||
if (!DatabaseDescriptor.getRoleManager().isExistingRole(role))
|
||||
throw new InvalidRequestException(String.format("%s doesn't exist", role));
|
||||
|
||||
if (!DatabaseDescriptor.getRoleManager().isExistingRole(grantee))
|
||||
throw new InvalidRequestException(String.format("%s doesn't exist", grantee));
|
||||
}
|
||||
}
|
||||
|
|
@ -21,18 +21,13 @@ import java.io.Closeable;
|
|||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import org.apache.cassandra.auth.IAuthenticator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.auth.PasswordAuthenticator;
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.Config;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
|
|
@ -46,8 +41,6 @@ import org.apache.hadoop.conf.Configuration;
|
|||
import org.apache.hadoop.mapreduce.RecordWriter;
|
||||
import org.apache.hadoop.mapreduce.TaskAttemptContext;
|
||||
import org.apache.hadoop.util.Progressable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class AbstractBulkRecordWriter<K, V> extends RecordWriter<K, V>
|
||||
implements org.apache.hadoop.mapred.RecordWriter<K, V>
|
||||
|
|
@ -191,8 +184,8 @@ implements org.apache.hadoop.mapred.RecordWriter<K, V>
|
|||
if (username != null)
|
||||
{
|
||||
Map<String, String> creds = new HashMap<String, String>();
|
||||
creds.put(IAuthenticator.USERNAME_KEY, username);
|
||||
creds.put(IAuthenticator.PASSWORD_KEY, password);
|
||||
creds.put(PasswordAuthenticator.USERNAME_KEY, username);
|
||||
creds.put(PasswordAuthenticator.PASSWORD_KEY, password);
|
||||
AuthenticationRequest authRequest = new AuthenticationRequest(creds);
|
||||
client.login(authRequest);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,36 +20,22 @@ package org.apache.cassandra.hadoop;
|
|||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.auth.IAuthenticator;
|
||||
import org.apache.cassandra.auth.PasswordAuthenticator;
|
||||
import org.apache.cassandra.dht.IPartitioner;
|
||||
import org.apache.cassandra.dht.Range;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.thrift.AuthenticationRequest;
|
||||
import org.apache.cassandra.thrift.Cassandra;
|
||||
import org.apache.cassandra.thrift.CfSplit;
|
||||
import org.apache.cassandra.thrift.InvalidRequestException;
|
||||
import org.apache.cassandra.thrift.KeyRange;
|
||||
import org.apache.cassandra.thrift.TokenRange;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.cassandra.thrift.*;
|
||||
import org.apache.hadoop.conf.Configuration;
|
||||
import org.apache.hadoop.mapred.JobConf;
|
||||
import org.apache.hadoop.mapreduce.InputFormat;
|
||||
import org.apache.hadoop.mapreduce.InputSplit;
|
||||
import org.apache.hadoop.mapreduce.JobContext;
|
||||
import org.apache.hadoop.mapreduce.TaskAttemptContext;
|
||||
import org.apache.hadoop.mapreduce.TaskAttemptID;
|
||||
import org.apache.hadoop.mapreduce.*;
|
||||
import org.apache.thrift.TApplicationException;
|
||||
import org.apache.thrift.TException;
|
||||
import org.apache.thrift.protocol.TBinaryProtocol;
|
||||
|
|
@ -106,8 +92,8 @@ public abstract class AbstractColumnFamilyInputFormat<K, Y> extends InputFormat<
|
|||
if ((ConfigHelper.getInputKeyspaceUserName(conf) != null) && (ConfigHelper.getInputKeyspacePassword(conf) != null))
|
||||
{
|
||||
Map<String, String> creds = new HashMap<String, String>();
|
||||
creds.put(IAuthenticator.USERNAME_KEY, ConfigHelper.getInputKeyspaceUserName(conf));
|
||||
creds.put(IAuthenticator.PASSWORD_KEY, ConfigHelper.getInputKeyspacePassword(conf));
|
||||
creds.put(PasswordAuthenticator.USERNAME_KEY, ConfigHelper.getInputKeyspaceUserName(conf));
|
||||
creds.put(PasswordAuthenticator.PASSWORD_KEY, ConfigHelper.getInputKeyspacePassword(conf));
|
||||
AuthenticationRequest authRequest = new AuthenticationRequest(creds);
|
||||
client.login(authRequest);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,9 @@ import java.util.Map;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.auth.IAuthenticator;
|
||||
import org.apache.cassandra.thrift.*;
|
||||
import org.apache.cassandra.auth.PasswordAuthenticator;
|
||||
import org.apache.cassandra.thrift.AuthenticationRequest;
|
||||
import org.apache.cassandra.thrift.Cassandra;
|
||||
import org.apache.hadoop.conf.Configuration;
|
||||
import org.apache.hadoop.mapreduce.*;
|
||||
import org.apache.thrift.protocol.TBinaryProtocol;
|
||||
|
|
@ -134,8 +135,8 @@ public abstract class AbstractColumnFamilyOutputFormat<K, Y> extends OutputForma
|
|||
public static void login(String user, String password, Cassandra.Client client) throws Exception
|
||||
{
|
||||
Map<String, String> creds = new HashMap<String, String>();
|
||||
creds.put(IAuthenticator.USERNAME_KEY, user);
|
||||
creds.put(IAuthenticator.PASSWORD_KEY, password);
|
||||
creds.put(PasswordAuthenticator.USERNAME_KEY, user);
|
||||
creds.put(PasswordAuthenticator.PASSWORD_KEY, password);
|
||||
AuthenticationRequest authRequest = new AuthenticationRequest(creds);
|
||||
client.login(authRequest);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,27 +25,28 @@ import java.nio.ByteBuffer;
|
|||
import java.nio.charset.CharacterCodingException;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.cassandra.db.Cell;
|
||||
import org.apache.cassandra.schema.LegacySchemaTables;
|
||||
import org.apache.cassandra.db.SystemKeyspace;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.exceptions.SyntaxException;
|
||||
import org.apache.cassandra.auth.IAuthenticator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.auth.PasswordAuthenticator;
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.Cell;
|
||||
import org.apache.cassandra.db.SystemKeyspace;
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.db.marshal.AbstractCompositeType.CompositeComponent;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.exceptions.SyntaxException;
|
||||
import org.apache.cassandra.hadoop.ConfigHelper;
|
||||
import org.apache.cassandra.schema.LegacySchemaTables;
|
||||
import org.apache.cassandra.serializers.CollectionSerializer;
|
||||
import org.apache.cassandra.hadoop.*;
|
||||
import org.apache.cassandra.thrift.*;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.Hex;
|
||||
import org.apache.cassandra.utils.UUIDGen;
|
||||
|
||||
import org.apache.cassandra.utils.*;
|
||||
import org.apache.hadoop.conf.Configuration;
|
||||
import org.apache.hadoop.fs.Path;
|
||||
import org.apache.hadoop.mapreduce.*;
|
||||
import org.apache.hadoop.mapreduce.InputFormat;
|
||||
import org.apache.hadoop.mapreduce.Job;
|
||||
import org.apache.hadoop.mapreduce.OutputFormat;
|
||||
import org.apache.pig.*;
|
||||
import org.apache.pig.backend.executionengine.ExecException;
|
||||
import org.apache.pig.data.*;
|
||||
|
|
@ -54,8 +55,6 @@ import org.apache.thrift.TDeserializer;
|
|||
import org.apache.thrift.TException;
|
||||
import org.apache.thrift.TSerializer;
|
||||
import org.apache.thrift.protocol.TBinaryProtocol;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A LoadStoreFunc for retrieving data from and storing data to Cassandra
|
||||
|
|
@ -505,8 +504,8 @@ public abstract class AbstractCassandraStorage extends LoadFunc implements Store
|
|||
if (username != null && password != null)
|
||||
{
|
||||
Map<String, String> credentials = new HashMap<String, String>(2);
|
||||
credentials.put(IAuthenticator.USERNAME_KEY, username);
|
||||
credentials.put(IAuthenticator.PASSWORD_KEY, password);
|
||||
credentials.put(PasswordAuthenticator.USERNAME_KEY, username);
|
||||
credentials.put(PasswordAuthenticator.PASSWORD_KEY, password);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,11 +18,12 @@
|
|||
package org.apache.cassandra.service;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -31,13 +32,13 @@ import org.apache.cassandra.config.DatabaseDescriptor;
|
|||
import org.apache.cassandra.config.Schema;
|
||||
import org.apache.cassandra.cql3.QueryHandler;
|
||||
import org.apache.cassandra.cql3.QueryProcessor;
|
||||
import org.apache.cassandra.schema.LegacySchemaTables;
|
||||
import org.apache.cassandra.db.SystemKeyspace;
|
||||
import org.apache.cassandra.exceptions.AuthenticationException;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.exceptions.UnauthorizedException;
|
||||
import org.apache.cassandra.tracing.TraceKeyspace;
|
||||
import org.apache.cassandra.schema.LegacySchemaTables;
|
||||
import org.apache.cassandra.thrift.ThriftValidation;
|
||||
import org.apache.cassandra.tracing.TraceKeyspace;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.JVMStabilityInspector;
|
||||
import org.apache.cassandra.utils.SemanticVersion;
|
||||
|
|
@ -52,16 +53,27 @@ public class ClientState
|
|||
|
||||
private static final Set<IResource> READABLE_SYSTEM_RESOURCES = new HashSet<>();
|
||||
private static final Set<IResource> PROTECTED_AUTH_RESOURCES = new HashSet<>();
|
||||
|
||||
private static final Set<String> ALTERABLE_SYSTEM_KEYSPACES = new HashSet<>();
|
||||
private static final Set<IResource> DROPPABLE_SYSTEM_TABLES = new HashSet<>();
|
||||
static
|
||||
{
|
||||
// We want these system cfs to be always readable to authenticated users since many tools rely on them
|
||||
// (nodetool, cqlsh, bulkloader, etc.)
|
||||
for (String cf : Iterables.concat(Arrays.asList(SystemKeyspace.LOCAL, SystemKeyspace.PEERS), LegacySchemaTables.ALL))
|
||||
READABLE_SYSTEM_RESOURCES.add(DataResource.columnFamily(SystemKeyspace.NAME, cf));
|
||||
READABLE_SYSTEM_RESOURCES.add(DataResource.table(SystemKeyspace.NAME, cf));
|
||||
|
||||
PROTECTED_AUTH_RESOURCES.addAll(DatabaseDescriptor.getAuthenticator().protectedResources());
|
||||
PROTECTED_AUTH_RESOURCES.addAll(DatabaseDescriptor.getAuthorizer().protectedResources());
|
||||
PROTECTED_AUTH_RESOURCES.addAll(DatabaseDescriptor.getRoleManager().protectedResources());
|
||||
|
||||
// allow users with sufficient privileges to alter KS level options on AUTH_KS and
|
||||
// TRACING_KS, and also to drop legacy tables (users, credentials, permissions) from
|
||||
// AUTH_KS
|
||||
ALTERABLE_SYSTEM_KEYSPACES.add(AuthKeyspace.NAME);
|
||||
ALTERABLE_SYSTEM_KEYSPACES.add(TraceKeyspace.NAME);
|
||||
DROPPABLE_SYSTEM_TABLES.add(DataResource.table(AuthKeyspace.NAME, PasswordAuthenticator.LEGACY_CREDENTIALS_TABLE));
|
||||
DROPPABLE_SYSTEM_TABLES.add(DataResource.table(AuthKeyspace.NAME, CassandraRoleManager.LEGACY_USERS_TABLE));
|
||||
DROPPABLE_SYSTEM_TABLES.add(DataResource.table(AuthKeyspace.NAME, CassandraAuthorizer.USER_PERMISSIONS));
|
||||
}
|
||||
|
||||
// Current user for the session
|
||||
|
|
@ -200,10 +212,13 @@ public class ClientState
|
|||
*/
|
||||
public void login(AuthenticatedUser user) throws AuthenticationException
|
||||
{
|
||||
if (!user.isAnonymous() && !Auth.isExistingUser(user.getName()))
|
||||
throw new AuthenticationException(String.format("User %s doesn't exist - create it with CREATE USER query first",
|
||||
user.getName()));
|
||||
this.user = user;
|
||||
// Login privilege is not inherited via granted roles, so just
|
||||
// verify that the role with the credentials that were actually
|
||||
// supplied has it
|
||||
if (user.isAnonymous() || DatabaseDescriptor.getRoleManager().canLogin(user.getName()))
|
||||
this.user = user;
|
||||
else
|
||||
throw new AuthenticationException(String.format("%s is not permitted to log in", user.getName()));
|
||||
}
|
||||
|
||||
public void hasAllKeyspacesAccess(Permission perm) throws UnauthorizedException
|
||||
|
|
@ -223,7 +238,7 @@ public class ClientState
|
|||
throws UnauthorizedException, InvalidRequestException
|
||||
{
|
||||
ThriftValidation.validateColumnFamily(keyspace, columnFamily);
|
||||
hasAccess(keyspace, perm, DataResource.columnFamily(keyspace, columnFamily));
|
||||
hasAccess(keyspace, perm, DataResource.table(keyspace, columnFamily));
|
||||
}
|
||||
|
||||
private void hasAccess(String keyspace, Permission perm, DataResource resource)
|
||||
|
|
@ -264,10 +279,15 @@ public class ClientState
|
|||
if (SystemKeyspace.NAME.equalsIgnoreCase(keyspace))
|
||||
throw new UnauthorizedException(keyspace + " keyspace is not user-modifiable.");
|
||||
|
||||
// we want to allow altering AUTH_KS and TRACING_KS.
|
||||
Set<String> allowAlter = Sets.newHashSet(Auth.AUTH_KS, TraceKeyspace.NAME);
|
||||
if (allowAlter.contains(keyspace.toLowerCase()) && !(resource.isKeyspaceLevel() && (perm == Permission.ALTER)))
|
||||
// allow users with sufficient privileges to alter KS level options on AUTH_KS and
|
||||
// TRACING_KS, and also to drop legacy tables (users, credentials, permissions) from
|
||||
// AUTH_KS
|
||||
if (ALTERABLE_SYSTEM_KEYSPACES.contains(resource.getKeyspace().toLowerCase())
|
||||
&& ((perm == Permission.ALTER && !resource.isKeyspaceLevel())
|
||||
|| (perm == Permission.DROP && !DROPPABLE_SYSTEM_TABLES.contains(resource))))
|
||||
{
|
||||
throw new UnauthorizedException(String.format("Cannot %s %s", perm, resource));
|
||||
}
|
||||
}
|
||||
|
||||
public void validateLogin() throws UnauthorizedException
|
||||
|
|
@ -307,6 +327,6 @@ public class ClientState
|
|||
|
||||
private Set<Permission> authorize(IResource resource)
|
||||
{
|
||||
return Auth.getPermissions(user, resource);
|
||||
return AuthenticatedUser.getPermissions(user, resource);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,7 @@
|
|||
*/
|
||||
package org.apache.cassandra.service;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.*;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
|
@ -29,20 +26,10 @@ import java.util.*;
|
|||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import javax.management.JMX;
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.Notification;
|
||||
import javax.management.NotificationBroadcasterSupport;
|
||||
import javax.management.ObjectName;
|
||||
import javax.management.*;
|
||||
import javax.management.openmbean.TabularData;
|
||||
import javax.management.openmbean.TabularDataSupport;
|
||||
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.jmx.JMXConfiguratorMBean;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.Appender;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.*;
|
||||
|
|
@ -52,12 +39,17 @@ import org.apache.commons.lang3.time.DurationFormatUtils;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.auth.Auth;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.jmx.JMXConfiguratorMBean;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.Appender;
|
||||
import org.apache.cassandra.auth.AuthKeyspace;
|
||||
import org.apache.cassandra.auth.AuthMigrationListener;
|
||||
import org.apache.cassandra.concurrent.*;
|
||||
import org.apache.cassandra.config.*;
|
||||
import org.apache.cassandra.cql3.UntypedResultSet;
|
||||
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.*;
|
||||
import org.apache.cassandra.db.commitlog.CommitLog;
|
||||
|
|
@ -74,15 +66,9 @@ import org.apache.cassandra.io.sstable.SSTableLoader;
|
|||
import org.apache.cassandra.io.util.FileUtils;
|
||||
import org.apache.cassandra.locator.*;
|
||||
import org.apache.cassandra.metrics.StorageMetrics;
|
||||
import org.apache.cassandra.net.AsyncOneResponse;
|
||||
import org.apache.cassandra.net.MessageOut;
|
||||
import org.apache.cassandra.net.MessagingService;
|
||||
import org.apache.cassandra.net.ResponseVerbHandler;
|
||||
import org.apache.cassandra.repair.RepairMessageVerbHandler;
|
||||
import org.apache.cassandra.repair.RepairSessionResult;
|
||||
import org.apache.cassandra.net.*;
|
||||
import org.apache.cassandra.repair.*;
|
||||
import org.apache.cassandra.repair.messages.RepairOption;
|
||||
import org.apache.cassandra.repair.RepairSession;
|
||||
import org.apache.cassandra.repair.RepairParallelism;
|
||||
import org.apache.cassandra.service.paxos.CommitVerbHandler;
|
||||
import org.apache.cassandra.service.paxos.PrepareVerbHandler;
|
||||
import org.apache.cassandra.service.paxos.ProposeVerbHandler;
|
||||
|
|
@ -843,7 +829,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
|
|||
Gossiper.instance.replacedEndpoint(existing);
|
||||
assert tokenMetadata.sortedTokens().size() > 0;
|
||||
|
||||
Auth.setup();
|
||||
doAuthSetup();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -882,10 +868,41 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
|
|||
logger.info("Leaving write survey mode and joining ring at operator request");
|
||||
assert tokenMetadata.sortedTokens().size() > 0;
|
||||
|
||||
Auth.setup();
|
||||
doAuthSetup();
|
||||
}
|
||||
}
|
||||
|
||||
private void doAuthSetup()
|
||||
{
|
||||
try
|
||||
{
|
||||
// if we don't have system_auth keyspace at this point, then create it manually
|
||||
// otherwise, create any necessary tables as we may be upgrading in which case
|
||||
// the ks exists with the only the legacy tables defined
|
||||
if (Schema.instance.getKSMetaData(AuthKeyspace.NAME) == null)
|
||||
{
|
||||
MigrationManager.announceNewKeyspace(AuthKeyspace.definition(), 0, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (Map.Entry<String, CFMetaData> table : AuthKeyspace.definition().cfMetaData().entrySet())
|
||||
{
|
||||
if (Schema.instance.getCFMetaData(AuthKeyspace.NAME, table.getKey()) == null)
|
||||
MigrationManager.announceNewColumnFamily(table.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new AssertionError(e); // shouldn't ever happen.
|
||||
}
|
||||
|
||||
DatabaseDescriptor.getRoleManager().setup();
|
||||
DatabaseDescriptor.getAuthenticator().setup();
|
||||
DatabaseDescriptor.getAuthorizer().setup();
|
||||
MigrationManager.instance.register(new AuthMigrationListener());
|
||||
}
|
||||
|
||||
public boolean isJoined()
|
||||
{
|
||||
return joined;
|
||||
|
|
|
|||
|
|
@ -29,43 +29,30 @@ import java.util.zip.Inflater;
|
|||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.*;
|
||||
import com.google.common.primitives.Longs;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.auth.AuthenticatedUser;
|
||||
import org.apache.cassandra.auth.Permission;
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.config.KSMetaData;
|
||||
import org.apache.cassandra.config.Schema;
|
||||
import org.apache.cassandra.config.*;
|
||||
import org.apache.cassandra.cql3.QueryOptions;
|
||||
import org.apache.cassandra.cql3.statements.ParsedStatement;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.composites.*;
|
||||
import org.apache.cassandra.db.context.CounterContext;
|
||||
import org.apache.cassandra.db.filter.ColumnSlice;
|
||||
import org.apache.cassandra.db.filter.IDiskAtomFilter;
|
||||
import org.apache.cassandra.db.filter.NamesQueryFilter;
|
||||
import org.apache.cassandra.db.filter.SliceQueryFilter;
|
||||
import org.apache.cassandra.db.filter.*;
|
||||
import org.apache.cassandra.db.marshal.TimeUUIDType;
|
||||
import org.apache.cassandra.dht.*;
|
||||
import org.apache.cassandra.dht.Range;
|
||||
import org.apache.cassandra.exceptions.*;
|
||||
import org.apache.cassandra.io.util.DataOutputBuffer;
|
||||
import org.apache.cassandra.locator.DynamicEndpointSnitch;
|
||||
import org.apache.cassandra.metrics.ClientMetrics;
|
||||
import org.apache.cassandra.scheduler.IRequestScheduler;
|
||||
import org.apache.cassandra.serializers.MarshalException;
|
||||
import org.apache.cassandra.service.CASRequest;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.service.MigrationManager;
|
||||
import org.apache.cassandra.service.StorageProxy;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
import org.apache.cassandra.service.*;
|
||||
import org.apache.cassandra.service.pager.QueryPagers;
|
||||
import org.apache.cassandra.tracing.Tracing;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
|
@ -1472,12 +1459,11 @@ public class CassandraServer implements Cassandra.Iface
|
|||
}
|
||||
}
|
||||
|
||||
public void login(AuthenticationRequest auth_request) throws AuthenticationException, AuthorizationException, TException
|
||||
public void login(AuthenticationRequest auth_request) throws TException
|
||||
{
|
||||
try
|
||||
{
|
||||
AuthenticatedUser user = DatabaseDescriptor.getAuthenticator().authenticate(auth_request.getCredentials());
|
||||
state().login(user);
|
||||
state().login(DatabaseDescriptor.getAuthenticator().legacyAuthenticate(auth_request.getCredentials()));
|
||||
}
|
||||
catch (org.apache.cassandra.exceptions.AuthenticationException e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,33 +18,33 @@
|
|||
package org.apache.cassandra.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.*;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import org.apache.commons.cli.*;
|
||||
|
||||
import org.apache.thrift.protocol.TBinaryProtocol;
|
||||
import org.apache.thrift.protocol.TProtocol;
|
||||
import org.apache.thrift.transport.TTransport;
|
||||
|
||||
import org.apache.cassandra.auth.IAuthenticator;
|
||||
import org.apache.cassandra.auth.PasswordAuthenticator;
|
||||
import org.apache.cassandra.config.*;
|
||||
import org.apache.cassandra.schema.LegacySchemaTables;
|
||||
import org.apache.cassandra.db.SystemKeyspace;
|
||||
import org.apache.cassandra.db.marshal.UTF8Type;
|
||||
import org.apache.cassandra.dht.Range;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.io.sstable.SSTableLoader;
|
||||
import org.apache.cassandra.schema.LegacySchemaTables;
|
||||
import org.apache.cassandra.streaming.*;
|
||||
import org.apache.cassandra.thrift.*;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.JVMStabilityInspector;
|
||||
import org.apache.cassandra.utils.OutputHandler;
|
||||
import org.apache.thrift.protocol.TBinaryProtocol;
|
||||
import org.apache.thrift.protocol.TProtocol;
|
||||
import org.apache.thrift.transport.TTransport;
|
||||
|
||||
public class BulkLoader
|
||||
{
|
||||
|
|
@ -359,8 +359,8 @@ public class BulkLoader
|
|||
if (user != null && passwd != null)
|
||||
{
|
||||
Map<String, String> credentials = new HashMap<>();
|
||||
credentials.put(IAuthenticator.USERNAME_KEY, user);
|
||||
credentials.put(IAuthenticator.PASSWORD_KEY, passwd);
|
||||
credentials.put(PasswordAuthenticator.USERNAME_KEY, user);
|
||||
credentials.put(PasswordAuthenticator.PASSWORD_KEY, passwd);
|
||||
AuthenticationRequest authenticationRequest = new AuthenticationRequest(credentials);
|
||||
client.login(authenticationRequest);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,16 +22,11 @@ import java.io.IOException;
|
|||
import java.io.InputStreamReader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
|
||||
import org.apache.cassandra.auth.IAuthenticator;
|
||||
import org.apache.cassandra.auth.PasswordAuthenticator;
|
||||
import org.apache.cassandra.cql3.QueryOptions;
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
import org.apache.cassandra.db.marshal.Int32Type;
|
||||
|
|
@ -179,7 +174,7 @@ public class Client extends SimpleClient
|
|||
else if (msgType.equals("AUTHENTICATE"))
|
||||
{
|
||||
Map<String, String> credentials = readCredentials(iter);
|
||||
if(!credentials.containsKey(IAuthenticator.USERNAME_KEY) || !credentials.containsKey(IAuthenticator.PASSWORD_KEY))
|
||||
if(!credentials.containsKey(PasswordAuthenticator.USERNAME_KEY) || !credentials.containsKey(PasswordAuthenticator.PASSWORD_KEY))
|
||||
{
|
||||
System.err.println("[ERROR] Authentication requires both 'username' and 'password'");
|
||||
return null;
|
||||
|
|
@ -221,8 +216,8 @@ public class Client extends SimpleClient
|
|||
|
||||
private byte[] encodeCredentialsForSasl(Map<String, String> credentials)
|
||||
{
|
||||
byte[] username = credentials.get(IAuthenticator.USERNAME_KEY).getBytes(StandardCharsets.UTF_8);
|
||||
byte[] password = credentials.get(IAuthenticator.PASSWORD_KEY).getBytes(StandardCharsets.UTF_8);
|
||||
byte[] username = credentials.get(PasswordAuthenticator.USERNAME_KEY).getBytes(StandardCharsets.UTF_8);
|
||||
byte[] password = credentials.get(PasswordAuthenticator.PASSWORD_KEY).getBytes(StandardCharsets.UTF_8);
|
||||
byte[] initialResponse = new byte[username.length + password.length + 2];
|
||||
initialResponse[0] = 0;
|
||||
System.arraycopy(username, 0, initialResponse, 1, username.length);
|
||||
|
|
|
|||
|
|
@ -28,21 +28,24 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.util.Version;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.group.ChannelGroup;
|
||||
import io.netty.channel.group.DefaultChannelGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.util.Version;
|
||||
import io.netty.util.concurrent.EventExecutor;
|
||||
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
import io.netty.util.internal.logging.Slf4JLoggerFactory;
|
||||
import org.apache.cassandra.auth.IAuthenticator;
|
||||
import org.apache.cassandra.auth.ISaslAwareAuthenticator;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.config.EncryptionOptions;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
|
|
@ -50,11 +53,6 @@ import org.apache.cassandra.metrics.ClientMetrics;
|
|||
import org.apache.cassandra.security.SSLFactory;
|
||||
import org.apache.cassandra.service.*;
|
||||
import org.apache.cassandra.transport.messages.EventMessage;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.group.ChannelGroup;
|
||||
import io.netty.channel.group.DefaultChannelGroup;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
|
||||
public class Server implements CassandraDaemon.Server
|
||||
{
|
||||
|
|
@ -132,16 +130,6 @@ public class Server implements CassandraDaemon.Server
|
|||
|
||||
private void run()
|
||||
{
|
||||
// Check that a SaslAuthenticator can be provided by the configured
|
||||
// IAuthenticator. If not, don't start the server.
|
||||
IAuthenticator authenticator = DatabaseDescriptor.getAuthenticator();
|
||||
if (authenticator.requireAuthentication() && !(authenticator instanceof ISaslAwareAuthenticator))
|
||||
{
|
||||
logger.error("Not starting native transport as the configured IAuthenticator is not capable of SASL authentication");
|
||||
isRunning.compareAndSet(true, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure the server.
|
||||
eventExecutorGroup = new RequestThreadPoolExecutor();
|
||||
|
||||
|
|
|
|||
|
|
@ -20,21 +20,17 @@ package org.apache.cassandra.transport;
|
|||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import org.apache.cassandra.auth.IAuthenticator;
|
||||
import org.apache.cassandra.auth.ISaslAwareAuthenticator;
|
||||
import org.apache.cassandra.auth.ISaslAwareAuthenticator.SaslAuthenticator;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.service.QueryState;
|
||||
|
||||
import org.cliffc.high_scale_lib.NonBlockingHashMap;
|
||||
|
||||
public class ServerConnection extends Connection
|
||||
{
|
||||
private enum State { UNINITIALIZED, AUTHENTICATION, READY }
|
||||
|
||||
private volatile SaslAuthenticator saslAuthenticator;
|
||||
private volatile IAuthenticator.SaslNegotiator saslNegotiator;
|
||||
private final ClientState clientState;
|
||||
private volatile State state;
|
||||
|
||||
|
|
@ -104,7 +100,7 @@ public class ServerConnection extends Connection
|
|||
{
|
||||
state = State.READY;
|
||||
// we won't use the authenticator again, null it so that it can be GC'd
|
||||
saslAuthenticator = null;
|
||||
saslNegotiator = null;
|
||||
}
|
||||
break;
|
||||
case READY:
|
||||
|
|
@ -114,14 +110,10 @@ public class ServerConnection extends Connection
|
|||
}
|
||||
}
|
||||
|
||||
public SaslAuthenticator getAuthenticator()
|
||||
public IAuthenticator.SaslNegotiator getSaslNegotiator()
|
||||
{
|
||||
if (saslAuthenticator == null)
|
||||
{
|
||||
IAuthenticator authenticator = DatabaseDescriptor.getAuthenticator();
|
||||
assert authenticator instanceof ISaslAwareAuthenticator : "Configured IAuthenticator does not support SASL authentication";
|
||||
saslAuthenticator = ((ISaslAwareAuthenticator)authenticator).newAuthenticator();
|
||||
}
|
||||
return saslAuthenticator;
|
||||
if (saslNegotiator == null)
|
||||
saslNegotiator = DatabaseDescriptor.getAuthenticator().newSaslNegotiator();
|
||||
return saslNegotiator;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,18 +17,14 @@
|
|||
*/
|
||||
package org.apache.cassandra.transport.messages;
|
||||
|
||||
import org.apache.cassandra.auth.AuthenticatedUser;
|
||||
import org.apache.cassandra.auth.ISaslAwareAuthenticator.SaslAuthenticator;
|
||||
import org.apache.cassandra.exceptions.AuthenticationException;
|
||||
import org.apache.cassandra.service.QueryState;
|
||||
import org.apache.cassandra.transport.CBUtil;
|
||||
import org.apache.cassandra.transport.Message;
|
||||
import org.apache.cassandra.transport.ProtocolException;
|
||||
import org.apache.cassandra.transport.ServerConnection;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.apache.cassandra.auth.AuthenticatedUser;
|
||||
import org.apache.cassandra.auth.IAuthenticator;
|
||||
import org.apache.cassandra.exceptions.AuthenticationException;
|
||||
import org.apache.cassandra.service.QueryState;
|
||||
import org.apache.cassandra.transport.*;
|
||||
|
||||
/**
|
||||
* A SASL token message sent from client to server. Some SASL
|
||||
|
|
@ -61,11 +57,12 @@ public class AuthResponse extends Message.Request
|
|||
}
|
||||
};
|
||||
|
||||
private byte[] token;
|
||||
private final byte[] token;
|
||||
|
||||
public AuthResponse(byte[] token)
|
||||
{
|
||||
super(Message.Type.AUTH_RESPONSE);
|
||||
assert token != null;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
|
|
@ -74,11 +71,11 @@ public class AuthResponse extends Message.Request
|
|||
{
|
||||
try
|
||||
{
|
||||
SaslAuthenticator authenticator = ((ServerConnection) connection).getAuthenticator();
|
||||
byte[] challenge = authenticator.evaluateResponse(token == null ? new byte[0] : token);
|
||||
if (authenticator.isComplete())
|
||||
IAuthenticator.SaslNegotiator negotiator = ((ServerConnection) connection).getSaslNegotiator();
|
||||
byte[] challenge = negotiator.evaluateResponse(token);
|
||||
if (negotiator.isComplete())
|
||||
{
|
||||
AuthenticatedUser user = authenticator.getAuthenticatedUser();
|
||||
AuthenticatedUser user = negotiator.getAuthenticatedUser();
|
||||
queryState.getClientState().login(user);
|
||||
// authentication is complete, send a ready message to the client
|
||||
return new AuthSuccess(challenge);
|
||||
|
|
|
|||
|
|
@ -20,15 +20,14 @@ package org.apache.cassandra.transport.messages;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.apache.cassandra.auth.AuthenticatedUser;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.transport.ProtocolException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import org.apache.cassandra.exceptions.AuthenticationException;
|
||||
import org.apache.cassandra.service.QueryState;
|
||||
import org.apache.cassandra.transport.CBUtil;
|
||||
import org.apache.cassandra.transport.Message;
|
||||
import org.apache.cassandra.transport.ProtocolException;
|
||||
|
||||
/**
|
||||
* Message to indicate that the server is ready to receive requests.
|
||||
|
|
@ -75,14 +74,15 @@ public class CredentialsMessage extends Message.Request
|
|||
{
|
||||
try
|
||||
{
|
||||
AuthenticatedUser user = DatabaseDescriptor.getAuthenticator().authenticate(credentials);
|
||||
AuthenticatedUser user = DatabaseDescriptor.getAuthenticator().legacyAuthenticate(credentials);
|
||||
state.getClientState().login(user);
|
||||
return new ReadyMessage();
|
||||
}
|
||||
catch (AuthenticationException e)
|
||||
{
|
||||
return ErrorMessage.fromException(e);
|
||||
}
|
||||
|
||||
return new ReadyMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -20,19 +20,12 @@ package org.apache.cassandra.utils;
|
|||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.net.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.zip.Checksum;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
|
|
@ -43,6 +36,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import org.apache.cassandra.auth.IAuthenticator;
|
||||
import org.apache.cassandra.auth.IAuthorizer;
|
||||
import org.apache.cassandra.auth.IRoleManager;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.db.DecoratedKey;
|
||||
import org.apache.cassandra.dht.IPartitioner;
|
||||
|
|
@ -54,10 +48,7 @@ import org.apache.cassandra.io.util.DataOutputBuffer;
|
|||
import org.apache.cassandra.io.util.FileUtils;
|
||||
import org.apache.cassandra.io.util.IAllocator;
|
||||
import org.apache.cassandra.net.AsyncOneResponse;
|
||||
import org.apache.thrift.TBase;
|
||||
import org.apache.thrift.TDeserializer;
|
||||
import org.apache.thrift.TException;
|
||||
import org.apache.thrift.TSerializer;
|
||||
import org.apache.thrift.*;
|
||||
import org.codehaus.jackson.JsonFactory;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
|
||||
|
|
@ -439,6 +430,13 @@ public class FBUtilities
|
|||
return FBUtilities.construct(className, "authenticator");
|
||||
}
|
||||
|
||||
public static IRoleManager newRoleManager(String className) throws ConfigurationException
|
||||
{
|
||||
if (!className.contains("."))
|
||||
className = "org.apache.cassandra.auth." + className;
|
||||
return FBUtilities.construct(className, "role manager");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The Class for the given name.
|
||||
* @param classname Fully qualified classname.
|
||||
|
|
|
|||
Loading…
Reference in New Issue