Replace IAuthority with new IAuthorizer; patch by Aleksey Yeschenko,

reviewed by Jonathan Ellis for CASSANDRA-4874
This commit is contained in:
Aleksey Yeschenko 2012-11-27 20:04:22 +03:00
parent 7b4639ab26
commit 5a3eb1a6c3
47 changed files with 1357 additions and 835 deletions

View File

@ -25,6 +25,7 @@
* Fix adding column when the table has collections (CASSANDRA-4982)
* Fix allowing collections with compact storage (CASSANDRA-4990)
* Refuse ttl/writetime function on collections (CASSANDRA-4992)
* Replace IAuthority with new IAuthorizer (CASSANDRA-4874)
Merged from 1.1:
* cqlsh: improve COPY FROM performance (CASSANDRA-4921)
* Fall back to old describe_splits if d_s_ex is not available (CASSANDRA-4803)

View File

@ -14,6 +14,15 @@ by version X, but the inverse is not necessarily the case.)
Upgrading
---------
- IAuthority interface has been deprecated in favor of IAuthorizer.
AllowAllAuthority and SimpleAuthority have been renamed to
AllowAllAuthorizer and SimpleAuthorizer, respectively. In order to
simplify the upgrade to the new interface, a new abstract
LegacyAuthorizer has been added - you should subclass it in your
old IAuthority implementation and everything should just work
(this only affects users who implemented custom authorities).
'authority' setting in cassandra.yaml has been renamed to 'authorizer',
'authority' is no longer recognized. This affects all upgrading users.
- 1.2 is NOT network-compatible with versions older than 1.0. That
means if you want to do a rolling, zero-downtime upgrade, you'll need
to upgrade first to 1.0.x or 1.1.x, and then to 1.2. 1.2 retains
@ -93,6 +102,11 @@ Features
guarantee that (at the price of pre-writing the batch to another node
first), all mutations in the batch will be applied, even if the
coordinator fails mid-batch.
- new IAuthorizer interface has replaced the old IAuthority. IAuthorizer
allows dynamic permission management via new CQL3 statements:
GRANT, REVOKE, LIST PERMISSIONS. A native implementation storing
the permissions in Cassandra is being worked on and we expect to
include it in 1.2.1 or 1.2.2.
1.1.5

View File

@ -56,8 +56,8 @@ max_hints_delivery_threads: 2
# authentication backend, implementing IAuthenticator; used to identify users
authenticator: org.apache.cassandra.auth.AllowAllAuthenticator
# authorization backend, implementing IAuthority; used to limit access/provide permissions
authority: org.apache.cassandra.auth.AllowAllAuthority
# authorization backend, implementing IAuthorizer; used to limit access/provide permissions
authorizer: org.apache.cassandra.auth.AllowAllAuthorizer
# The partitioner is responsible for distributing rows (by key) across
# nodes in the cluster. Any IPartitioner may be used, including your

View File

@ -40,8 +40,8 @@ hinted_handoff_throttle_delay_in_ms: 50
# authentication backend, implementing IAuthenticator; used to identify users
authenticator: org.apache.cassandra.auth.AllowAllAuthenticator
# authorization backend, implementing IAuthority; used to limit access/provide permissions
authority: org.apache.cassandra.auth.AllowAllAuthority
# authorization backend, implementing IAuthorizer; used to limit access/provide permissions
authorizer: org.apache.cassandra.auth.AllowAllAuthorizer
# The partitioner is responsible for distributing rows (by key) across
# nodes in the cluster. Any IPartitioner may be used, including your

View File

@ -1,12 +1,12 @@
The files in this directory provide a (simplistic) example of how to add
authentication and resource permissions to Cassandra by implementing the
org.apache.cassandra.auth.{IAuthenticator, IAuthority} interfaces.
org.apache.cassandra.auth.{IAuthenticator, IAuthorizer} interfaces.
To try those examples, copy the two JAVA sources (in src/) into the main
cassandra sources directory and the two configuration files (in conf/) in the
main cassandra configuration directory.
You can then set the authenticator and authority properties in cassandra.yaml
You can then set the authenticator and authorizer properties in cassandra.yaml
to use those classes. See the two configuration files access.properties and
passwd.properties to configure the authorized users and permissions.

View File

@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# This is a sample access file for SimpleAuthority. The format of this file
# This is a sample access file for SimpleAuthorizer. The format of this file
# is KEYSPACE[.COLUMNFAMILY].PERMISSION=USERS, where:
#
# * KEYSPACE is the keyspace name.

View File

@ -32,7 +32,7 @@ import java.util.Properties;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.io.util.FileUtils;
public class SimpleAuthority implements IAuthority
public class SimpleAuthorizer extends LegacyAuthorizer
{
public final static String ACCESS_FILENAME_PROPERTY = "access.properties";
// magical property for WRITE permissions to the keyspaces list
@ -41,10 +41,10 @@ public class SimpleAuthority implements IAuthority
public EnumSet<Permission> authorize(AuthenticatedUser user, List<Object> resource)
{
if (resource.size() < 2 || !Resources.ROOT.equals(resource.get(0)) || !Resources.KEYSPACES.equals(resource.get(1)))
return Permission.NONE;
return EnumSet.copyOf(Permission.NONE);
String keyspace, columnFamily = null;
EnumSet<Permission> authorized = Permission.NONE;
EnumSet<Permission> authorized = EnumSet.copyOf(Permission.NONE);
// /cassandra/keyspaces
if (resource.size() == 2)
@ -83,7 +83,7 @@ public class SimpleAuthority implements IAuthority
String kspAdmins = accessProperties.getProperty(KEYSPACES_WRITE_PROPERTY);
for (String admin : kspAdmins.split(","))
if (admin.equals(user.username))
return Permission.ALL;
return EnumSet.copyOf(Permission.ALL);
}
boolean canRead = false, canWrite = false;
@ -125,7 +125,7 @@ public class SimpleAuthority implements IAuthority
}
if (canWrite)
authorized = Permission.ALL;
authorized = EnumSet.copyOf(Permission.ALL);
else if (canRead)
authorized = EnumSet.of(Permission.READ);

View File

@ -43,14 +43,12 @@ SYSTEM_KEYSPACES = ('system', 'system_traces')
class Cql3ParsingRuleSet(CqlParsingRuleSet):
keywords = set((
'select', 'from', 'where', 'and', 'key', 'insert', 'update', 'with',
'limit', 'using', 'consistency', 'one', 'quorum', 'all', 'any',
'local_quorum', 'each_quorum', 'two', 'three', 'use', 'count', 'set',
'limit', 'using', 'use', 'count', 'set',
'begin', 'apply', 'batch', 'truncate', 'delete', 'in', 'create',
'keyspace', 'schema', 'columnfamily', 'table', 'index', 'on', 'drop',
'primary', 'into', 'values', 'timestamp', 'ttl', 'alter', 'add', 'type',
'compact', 'storage', 'order', 'by', 'asc', 'desc', 'clustering',
'token', 'writetime', 'map', 'list', 'to', 'grant', 'grants', 'revoke',
'option', 'describe', 'for', 'full_access', 'no_access'
'token', 'writetime', 'map', 'list', 'to'
))
columnfamily_options = (
@ -275,16 +273,6 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ;
| <alterKeyspaceStatement>
;
<consistencylevel> ::= cl=( <K_ONE>
| <K_QUORUM>
| <K_ALL>
| <K_ANY>
| <K_LOCAL_QUORUM>
| <K_EACH_QUORUM>
| <K_TWO>
| <K_THREE> )
;
# timestamp is included here, since it's also a keyword
<simpleStorageType> ::= typename=( <identifier> | <stringLiteral> | <K_TIMESTAMP> ) ;
@ -307,15 +295,13 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ;
<unreservedKeyword> ::= nocomplete=
( <K_KEY>
| <K_CONSISTENCY>
| <K_CLUSTERING>
# | <K_COUNT> -- to get count(*) completion, treat count as reserved
| <K_TTL>
| <K_COMPACT>
| <K_STORAGE>
| <K_TYPE>
| <K_VALUES>
| <consistencylevel> )
| <K_VALUES> )
;
# <property> will be defined once cqlsh determines whether we're using
@ -666,10 +652,6 @@ def cf_old_prop_suboption_completer(ctxt, cass):
return subopts
return ()
@completer_for('consistencylevel', 'cl')
def consistencylevel_cl_completer(ctxt, cass):
return CqlRuleSet.consistency_levels
@completer_for('tokenDefinition', 'token')
def token_word_completer(ctxt, cass):
return ['TOKEN(']
@ -1246,39 +1228,48 @@ syntax_rules += r'''
<alterKeyspaceStatement> ::= "ALTER" ( "KEYSPACE" | "SCHEMA" ) ks=<nonSystemKeyspaceName>
"WITH" <newPropSpec> ( "AND" <newPropSpec> )*
;
'''
<grantStatement> ::= "GRANT" <permission> "ON" cf=<columnFamilyName>
"TO" <username>
( "WITH" "GRANT" "OPTION" )?
syntax_rules += r'''
<grantStatement> ::= "GRANT" <permissionExpr> "ON" <resource> "TO" <username>
;
<revokeStatement> ::= "REVOKE" <permission> "ON" cf=<columnFamilyName>
"FROM" <username>
<revokeStatement> ::= "REVOKE" <permissionExpr> "ON" <resource> "FROM" <username>
;
<listGrantsStatement> ::= "LIST" "GRANTS" "FOR" <username>;
<listGrantsStatement> ::= "LIST" <permissionExpr>
( "ON" <resource> )? ( "OF" <username> )? "NORECURSIVE"?
;
<permission> ::= "DESCRIBE"
| "USE"
<permission> ::= "AUTHORIZE"
| "CREATE"
| "ALTER"
| "DROP"
| "SELECT"
| "INSERT"
| "UPDATE"
| "DELETE"
| "FULL_ACCESS"
| "NO_ACCESS"
| "MODIFY"
;
<permissionExpr> ::= ( <permission> "PERMISSION"? )
| ( "ALL" "PERMISSIONS"? )
;
<resource> ::= <dataResource>
;
<dataResource> ::= ( "ALL" "KEYSPACES" )
| ( "KEYSPACE" <nonSystemKeyspaceName> )
| ( "TABLE"? <columnFamilyName> )
;
<username> ::= user=( <identifier> | <stringLiteral> )
;
'''
@completer_for('username', 'user')
def username_user_completer(ctxt, cass):
# TODO: implement user autocompletion
# with I could see a way to do this usefully, but I don't. I don't know
# how any Authorities other than AllowAllAuthority work :/
# how any Authorities other than AllowAllAuthorizer work :/
return [Hint('<username>')]
# END SYNTAX/COMPLETION RULE DEFINITIONS

View File

@ -24,16 +24,20 @@ import org.apache.cassandra.thrift.AuthenticationException;
public class AllowAllAuthenticator implements IAuthenticator
{
private final static AuthenticatedUser USER = new AuthenticatedUser("allow_all");
private final static AuthenticatedUser DEFAULT_USER = new AuthenticatedUser("nobody");
public AuthenticatedUser defaultUser()
{
return USER;
return DEFAULT_USER;
}
public AuthenticatedUser authenticate(Map<? extends CharSequence,? extends CharSequence> credentials) throws AuthenticationException
{
return USER;
CharSequence username = credentials.get(IAuthenticator.USERNAME_KEY);
if (username == null)
return DEFAULT_USER;
return new AuthenticatedUser((String)username);
}
public void validateConfiguration() throws ConfigurationException

View File

@ -0,0 +1,70 @@
/*
* 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.Set;
import org.apache.cassandra.exceptions.InvalidRequestException;
public class AllowAllAuthorizer implements IAuthorizer
{
public Set<Permission> authorize(AuthenticatedUser user, IResource resource)
{
return Permission.ALL;
}
public void validateConfiguration()
{
}
public void setup()
{
}
public void grant(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String to)
throws InvalidRequestException
{
throw new InvalidRequestException("GRANT operation is not supported by AllowAllAuthorizer");
}
public void revoke(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String from)
throws InvalidRequestException
{
throw new InvalidRequestException("REVOKE operation is not supported by AllowAllAuthorizer");
}
public void revokeAll(String droppedUser)
{
}
public void revokeAll(IResource droppedResource)
{
}
public Set<PermissionDetails> listPermissions(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String of)
throws InvalidRequestException
{
throw new InvalidRequestException("LIST PERMISSIONS operation is not supported by AllowAllAuthorizer");
}
public Set<IResource> protectedResources()
{
return Collections.emptySet();
}
}

View File

@ -17,32 +17,30 @@
*/
package org.apache.cassandra.auth;
import java.util.Collections;
import java.util.Set;
/**
* An authenticated user and her groups.
*/
public class AuthenticatedUser
{
public final String username;
public final Set<String> groups;
private final boolean isSuperUser;
public AuthenticatedUser(String username)
{
this.username = username;
this.groups = Collections.emptySet();
this(username, false);
}
public AuthenticatedUser(String username, Set<String> groups)
public AuthenticatedUser(String username, boolean isSuperUser)
{
this.username = username;
this.groups = Collections.unmodifiableSet(groups);
this.isSuperUser = isSuperUser;
}
public boolean isSuperUser()
{
return isSuperUser;
}
@Override
public String toString()
{
return String.format("#<User %s groups=%s>", username, groups);
return String.format("#<User %s super=%s>", username, isSuperUser);
}
}

View File

@ -0,0 +1,248 @@
/*
* 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 com.google.common.base.Objects;
import org.apache.commons.lang.StringUtils;
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.
* "data" - the root level data resource.
* "data/keyspace_name" - keyspace-level data resource.
* "data/keyspace_name/column_family_name" - cf-level data resource.
*/
public class DataResource implements IResource
{
enum Level
{
ROOT, KEYSPACE, COLUMN_FAMILY
}
private static final String ROOT_NAME = "data";
private static final DataResource ROOT_RESOURCE = new DataResource();
private final Level level;
private final String keyspace;
private final String columnFamily;
private DataResource()
{
level = Level.ROOT;
keyspace = null;
columnFamily = null;
}
private DataResource(String keyspace)
{
level = Level.KEYSPACE;
this.keyspace = keyspace;
columnFamily = null;
}
private DataResource(String keyspace, String columnFamily)
{
level = Level.COLUMN_FAMILY;
this.keyspace = keyspace;
this.columnFamily = columnFamily;
}
/**
* @return the root-level resource.
*/
public static DataResource root()
{
return ROOT_RESOURCE;
}
/**
* Creates a DataResource representing a keyspace.
*
* @param keyspace Name of the keyspace.
* @return DataResource instance representing the keyspace.
*/
public static DataResource keyspace(String keyspace)
{
return new DataResource(keyspace);
}
/**
* Creates a DataResource instance representing a column family.
*
* @param keyspace Name of the keyspace.
* @param columnFamily Name of the column family.
* @return DataResource instance representing the column family.
*/
public static DataResource columnFamily(String keyspace, String columnFamily)
{
return new DataResource(keyspace, columnFamily);
}
/**
* Parses a data resource name into a DataResource instance.
*
* @param name Name of the data resource.
* @return DataResource instance matching the name.
*/
public static DataResource fromName(String name)
{
String[] parts = StringUtils.split(name, '/');
if (!parts[0].equals(ROOT_NAME) || parts.length > 3)
throw new IllegalArgumentException(String.format("%s is not a valid data resource name", name));
if (parts.length == 1)
return root();
if (parts.length == 2)
return keyspace(parts[1]);
return columnFamily(parts[1], parts[2]);
}
/**
* @return Printable name of the resource.
*/
public String getName()
{
switch (level)
{
case ROOT:
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);
}
throw new AssertionError();
}
/**
* @return Parent of the resource, if any. Throws IllegalStateException if it's the root-level resource.
*/
public IResource getParent()
{
switch (level)
{
case KEYSPACE:
return root();
case COLUMN_FAMILY:
return keyspace(keyspace);
}
throw new IllegalStateException("Root-level resource can't have a parent");
}
public boolean isRootLevel()
{
return level.equals(Level.ROOT);
}
public boolean isKeyspaceLevel()
{
return level.equals(Level.KEYSPACE);
}
public boolean isColumnFamilyLevel()
{
return level.equals(Level.COLUMN_FAMILY);
}
/**
* @return keyspace of the resource. Throws IllegalStateException if it's the root-level resource.
*/
public String getKeyspace()
{
if (isRootLevel())
throw new IllegalStateException("ROOT data resource has no keyspace");
return keyspace;
}
/**
* @return column family of the resource. Throws IllegalStateException if it's not a cf-level resource.
*/
public String getColumnFamily()
{
if (!isColumnFamilyLevel())
throw new IllegalStateException(String.format("%s data resource has no column family", level));
return columnFamily;
}
/**
* @return Whether or not the resource has a parent in the hierarchy.
*/
public boolean hasParent()
{
return !level.equals(Level.ROOT);
}
/**
* @return Whether or not the resource exists in Cassandra.
*/
public boolean exists()
{
switch (level)
{
case ROOT:
return true;
case KEYSPACE:
return Schema.instance.getTables().contains(keyspace);
case COLUMN_FAMILY:
return Schema.instance.getCFMetaData(keyspace, columnFamily) != null;
}
throw new AssertionError();
}
@Override
public String toString()
{
switch (level)
{
case ROOT:
return "<all keyspaces>";
case KEYSPACE:
return String.format("<keyspace %s>", keyspace);
case COLUMN_FAMILY:
return String.format("<table %s.%s>", keyspace, columnFamily);
}
throw new AssertionError();
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof DataResource))
return false;
DataResource ds = (DataResource) o;
return Objects.equal(this.level, ds.level)
&& Objects.equal(this.keyspace, ds.keyspace)
&& Objects.equal(this.columnFamily, ds.columnFamily);
}
@Override
public int hashCode()
{
return Objects.hashCode(level, keyspace, columnFamily);
}
}

View File

@ -1,63 +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.EnumSet;
import java.util.List;
import org.apache.cassandra.exceptions.ConfigurationException;
/**
*
* Cassandra's resource hierarchy looks something like:
* {{/cassandra/keyspaces/$ks_name/...}}
*
* In table form:
* /cassandra/
* - no checked permissions
* - String
* * Separates Cassandra-internal resources from resources that might be provided by plugins.
* keyspaces/
* - READ, WRITE
* - String
* * The list of keyspaces: READ/WRITE for this resource mean the ability to view/modify the list of keyspaces.
* $ks_name/
* - READ, WRITE
* - String
* * An individual keyspace: READ/WRITE permissions apply to the entire namespace and control the ability to both
* view and manipulate column families, and to read and write the data contained within.
*
* Over time Cassandra _may_ add additional authorize calls for resources higher or lower in the hierarchy and
* IAuthority implementations should be able to handle these calls (although many will choose to ignore them
* completely).
*
* NB: {{/cassandra/}} will not be checked for permissions via a call to IAuthority.authorize, so IAuthority
* implementations can only deny access when a user attempts to access an ancestor resource.
*/
public interface IAuthority
{
/**
* @param user An authenticated user from a previous call to IAuthenticator.authenticate.
* @param resource A List of Objects containing Strings and byte[]s: represents a resource in the hierarchy
* described in the Javadocs.
* @return An AccessLevel representing the permissions for the user and resource: should never return null.
*/
public EnumSet<Permission> authorize(AuthenticatedUser user, List<Object> resource);
public void validateConfiguration() throws ConfigurationException;
}

View File

@ -1,70 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.cassandra.auth;
import org.apache.cassandra.cql3.CFName;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.UnauthorizedException;
import org.apache.cassandra.transport.messages.ResultMessage;
public interface IAuthority2 extends IAuthority
{
/**
* Setup is called each time upon system startup
*/
public void setup();
/**
* GRANT <permission> ON <resource> TO <user> [WITH GRANT OPTION];
*
* @param granter The user who grants the permission
* @param permission The specific permission
* @param to Grantee of the permission
* @param resource The resource which is affect by permission change
* @param grantOption Does grantee has a permission to grant the same kind of permission on this particular resource?
*
* @throws InvalidRequestException upon parameter misconfiguration or internal error.
*/
public void grant(AuthenticatedUser granter, Permission permission, String to, CFName resource, boolean grantOption) throws UnauthorizedException, InvalidRequestException;
/**
* REVOKE <permission> ON <resource> FROM <user_name>;
*
* @param revoker The user know requests permission revoke
* @param permission The permission to revoke
* @param from The user to revoke permission from.
* @param resource The resource affected by permission change.
*
* @throws InvalidRequestException upon parameter misconfiguration or internal error.
*/
public void revoke(AuthenticatedUser revoker, Permission permission, String from, CFName resource) throws UnauthorizedException, InvalidRequestException;
/**
* LIST GRANTS FOR <user>;
* Not 'SHOW' because it's reserved for CQLsh for commands like 'show cluster'
*
* @param username The username to look for permissions.
*
* @return All of the permission of this particular user.
*
* @throws InvalidRequestException upon parameter misconfiguration or internal error.
*/
public ResultMessage listPermissions(String username) throws UnauthorizedException, InvalidRequestException;
}

View File

@ -1,83 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.cassandra.auth;
import org.apache.cassandra.cql3.CFName;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.UnauthorizedException;
import org.apache.cassandra.transport.messages.ResultMessage;
/**
* 1.1.x : Temporary measure to unable dynamic operations without changing IAuthority interface.
*/
public class IAuthorityContainer
{
private final IAuthority authority;
private final IAuthority2 dynamicAuthority;
public IAuthorityContainer(IAuthority authority)
{
this.authority = authority;
dynamicAuthority = (authority instanceof IAuthority2) ? ((IAuthority2) authority) : null;
}
public void setup()
{
if (dynamicAuthority != null)
dynamicAuthority.setup();
}
public boolean isDynamic()
{
return dynamicAuthority != null;
}
public IAuthority getAuthority()
{
return authority;
}
public void grant(AuthenticatedUser granter, Permission permission, String to, CFName resource, boolean grantOption) throws UnauthorizedException, InvalidRequestException
{
if (dynamicAuthority == null)
throw new InvalidRequestException("GRANT operation is not supported by your authority: " + authority);
if (permission.equals(Permission.READ) || permission.equals(Permission.WRITE))
throw new InvalidRequestException(String.format("Error setting permission to: %s, available permissions are %s", permission, Permission.GRANULAR_PERMISSIONS));
dynamicAuthority.grant(granter, permission, to, resource, grantOption);
}
public void revoke(AuthenticatedUser revoker, Permission permission, String from, CFName resource) throws UnauthorizedException, InvalidRequestException
{
if (dynamicAuthority == null)
throw new InvalidRequestException("REVOKE operation is not supported by your authority: " + authority);
dynamicAuthority.revoke(revoker, permission, from, resource);
}
public ResultMessage listPermissions(String username) throws UnauthorizedException, InvalidRequestException
{
if (dynamicAuthority == null)
throw new InvalidRequestException("LIST GRANTS operation is not supported by your authority: " + authority);
return dynamicAuthority.listPermissions(username);
}
}

View File

@ -0,0 +1,122 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.auth;
import java.util.Set;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.UnauthorizedException;
/**
* Primary Cassandra authorization interface.
*/
public interface IAuthorizer
{
/**
* The primary IAuthorizer method. Returns a set of permissions of a user on a resource.
*
* @param user Authenticated user requesting authorization.
* @param resource Resource for which the authorization is being requested. @see DataResource.
* @return Set of permissions of the user on the resource. Should never return null. Use Permission.NONE instead.
*/
public Set<Permission> authorize(AuthenticatedUser user, IResource resource);
/**
* Grants a set of permissions on a resource to a user.
* 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 resource Resource on which to grant the permissions.
*
* @throws UnauthorizedException if the granting user isn't allowed to grant (and revoke) the permissions on the resource.
* @throws InvalidRequestException upon parameter misconfiguration or internal error.
*/
public void grant(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String to)
throws UnauthorizedException, InvalidRequestException;
/**
* Revokes a set of permissions on a resource from a user.
* The opposite of grant().
*
* @param performer User who revokes the permissions.
* @param permissions Set of permissions to revoke.
* @param from Revokee of the permissions.
* @param resource Resource on which to revoke the permissions.
*
* @throws UnauthorizedException if the revoking user isn't allowed to revoke the permissions on the resource.
* @throws InvalidRequestException upon parameter misconfiguration or internal error.
*/
public void revoke(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String from)
throws UnauthorizedException, InvalidRequestException;
/**
* Returns a list of permissions on a resource of a user.
*
* @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.
*
* @return All of the matching permission that the requesting user is authorized to know about.
*
* @throws UnauthorizedException if the user isn't allowed to view the requested permissions.
* @throws InvalidRequestException upon parameter misconfiguration or internal error.
*/
public Set<PermissionDetails> listPermissions(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, String of)
throws UnauthorizedException, InvalidRequestException;
/**
* 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.
*
* @param droppedUser The user to revoke all permissions from.
*/
public void revokeAll(String droppedUser);
/**
* This method is called after a resource is removed (i.e. keyspace or a table is dropped).
*
* @param droppedResource The resource to revoke all permissions on.
*/
public void revokeAll(IResource droppedResource);
/**
* Set of resources that should be made inaccessible to users and only accessible internally.
*
* @return Keyspaces, column families that will be unreadable and unmodifiable by users; other resources.
*/
public Set<? extends IResource> protectedResources();
/**
* Validates configuration of IAuthorizer implementation (if configurable).
*
* @throws ConfigurationException when there is a configuration error.
*/
public void validateConfiguration() throws ConfigurationException;
/**
* Setup is called once upon system startup to initialize the IAuthorizer.
*
* For example, use this method to create any required keyspaces/column families.
*/
public void setup();
}

View File

@ -0,0 +1,53 @@
/*
* 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;
/**
* The interface at the core of Cassandra authorization.
*
* Represents a resource in the hierarchy.
* Currently just one resource type is supported by Cassandra
* @see DataResource
*/
public interface IResource
{
/**
* @return printable name of the resource.
*/
public String getName();
/**
* Gets next resource in the hierarchy. Call hasParent first to make sure there is one.
*
* @return Resource parent (or IllegalStateException if there is none). Never a null.
*/
public IResource getParent();
/**
* Indicates whether or not this resource has a parent in the hierarchy.
*
* Please perform this check before calling getParent() method.
* @return Whether or not the resource has a parent.
*/
public boolean hasParent();
/**
* @return Whether or not this resource exists in Cassandra.
*/
public boolean exists();
}

View File

@ -0,0 +1,111 @@
/*
* 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.InvalidRequestException;
import org.apache.cassandra.exceptions.UnauthorizedException;
/**
* Provides a transitional IAuthorizer implementation for old-style (pre-1.2) authorizers.
*
* Translates old-style authorze() 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);
@Override
public void setup()
{
}
/**
* 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, UnauthorizedException
{
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, UnauthorizedException
{
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> listPermissions(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();
}
}

View File

@ -17,20 +17,37 @@
*/
package org.apache.cassandra.auth;
import java.util.EnumSet;
import java.util.List;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.service.IMigrationListener;
import org.apache.cassandra.exceptions.ConfigurationException;
public class AllowAllAuthority implements IAuthority
/**
* IMigrationListener implementation that cleans up permissions on dropped resources.
*/
public class MigrationListener implements IMigrationListener
{
public EnumSet<Permission> authorize(AuthenticatedUser user, List<Object> resource)
public void onDropKeyspace(String ksName)
{
return Permission.ALL;
DatabaseDescriptor.getAuthorizer().revokeAll(DataResource.keyspace(ksName));
}
public void validateConfiguration() throws ConfigurationException
public void onDropColumnFamly(String ksName, String cfName)
{
DatabaseDescriptor.getAuthorizer().revokeAll(DataResource.columnFamily(ksName, cfName));
}
public void onCreateKeyspace(String ksName)
{
}
public void onCreateColumnFamly(String ksName, String cfName)
{
}
public void onUpdateKeyspace(String ksName)
{
}
public void onUpdateColumnFamly(String ksName, String cfName)
{
// pass
}
}

View File

@ -18,45 +18,40 @@
package org.apache.cassandra.auth;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ImmutableSet;
/**
* An enum encapsulating the set of possible permissions that an authenticated user can have for a resource.
* An enum encapsulating the set of possible permissions that an authenticated user can have on a resource.
*
* IAuthority implementations may encode permissions using ordinals, so the Enum order must never change.
* IAuthorizer implementations may encode permissions using ordinals, so the Enum order must never change order.
* Adding new values is ok.
*/
public enum Permission
{
READ, // for backward compatibility
WRITE, // for backward compatibility
FULL_ACCESS,
NO_ACCESS,
@Deprecated
READ,
@Deprecated
WRITE,
// schema management
DESCRIBE,
CREATE,
ALTER,
DROP,
CREATE, // required for CREATE KEYSPACE and CREATE TABLE.
ALTER, // required for ALTER KEYSPACE, ALTER TABLE, CREATE INDEX, DROP INDEX.
DROP, // required for DROP KEYSPACE and DROP TABLE.
// data access
UPDATE,
DELETE,
SELECT;
SELECT, // required for SELECT.
MODIFY, // required for INSERT, UPDATE, DELETE, TRUNCATE.
public static final EnumSet<Permission> ALL = EnumSet.allOf(Permission.class);
public static final EnumSet<Permission> NONE = EnumSet.noneOf(Permission.class);
public static final EnumSet<Permission> GRANULAR_PERMISSIONS = EnumSet.range(FULL_ACCESS, SELECT);
public static final EnumSet<Permission> ALLOWED_SYSTEM_ACTIONS = EnumSet.of(DESCRIBE, UPDATE, DELETE, SELECT);
// permission management
AUTHORIZE; // required for GRANT and REVOKE.
/**
* Maps old permissions to the new ones as we want to support old client IAuthority implementations
* and new style of granular permission checking at the same time.
*/
public static final Map<Permission, EnumSet<Permission>> oldToNew = new HashMap<Permission, EnumSet<Permission>>(2)
{{
put(READ, EnumSet.of(DESCRIBE, SELECT));
put(WRITE, EnumSet.range(DESCRIBE, DELETE));
}};
public static final Set<Permission> ALL_DATA =
ImmutableSet.copyOf(EnumSet.range(Permission.CREATE, Permission.AUTHORIZE));
public static final Set<Permission> ALL =
ImmutableSet.copyOf(EnumSet.range(Permission.CREATE, Permission.AUTHORIZE));
public static final Set<Permission> NONE = ImmutableSet.of();
}

View File

@ -1,34 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cassandra.auth;
import org.apache.cassandra.thrift.InvalidRequestException;
public class PermissionDenied extends InvalidRequestException
{
public PermissionDenied(String reason)
{
super(reason);
}
public String getMessage()
{
return why;
}
}

View File

@ -0,0 +1,70 @@
/*
* 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 com.google.common.base.Objects;
import com.google.common.collect.ComparisonChain;
/**
* Sets of instances of this class are returned by IAuthorizer.listPermissions() method for LIST PERMISSIONS query.
* None of the fields are nullable.
*/
public class PermissionDetails implements Comparable<PermissionDetails>
{
public final String username;
public final IResource resource;
public final Permission permission;
public PermissionDetails(String username, IResource resource, Permission permission)
{
this.username = username;
this.resource = resource;
this.permission = permission;
}
@Override
public int compareTo(PermissionDetails other)
{
return ComparisonChain.start()
.compare(username, other.username)
.compare(resource.getName(), other.resource.getName())
.compare(permission, other.permission)
.result();
}
@Override
public String toString()
{
return String.format("<PermissionDetails username:%s resource:%s permission:%s>",
username,
resource.getName(),
permission);
}
@Override
public boolean equals(Object o)
{
return Objects.equal(this, o);
}
@Override
public int hashCode()
{
return Objects.hashCode(username, resource, permission);
}
}

View File

@ -17,20 +17,38 @@
*/
package org.apache.cassandra.auth;
import java.util.ArrayList;
import java.util.List;
import org.apache.cassandra.utils.Hex;
/**
* Constants related to Cassandra's resource hierarchy.
*
* A resource in Cassandra is a List containing both Strings and byte[]s.
*/
public final class Resources
{
/**
* Construct a chain of resource parents starting with the resource and ending with the root.
*
* @param resource The staring point.
* @return list of resource in the chain form start to the root.
*/
public static List<? extends IResource> chain(IResource resource)
{
List<IResource> chain = new ArrayList<IResource>();
while (true)
{
chain.add(resource);
if (!resource.hasParent())
break;
resource = resource.getParent();
}
return chain;
}
@Deprecated
public final static String ROOT = "cassandra";
@Deprecated
public final static String KEYSPACES = "keyspaces";
@Deprecated
public static String toString(List<Object> resource)
{
StringBuilder buff = new StringBuilder();

View File

@ -30,7 +30,8 @@ public class Config
{
public String cluster_name = "Test Cluster";
public String authenticator;
public String authority;
public String authority; // for backwards compatibility - will log a warning.
public String authorizer;
/* Hashing strategy Random or OPHF */
public String partitioner;

View File

@ -51,7 +51,6 @@ import org.apache.cassandra.scheduler.IRequestScheduler;
import org.apache.cassandra.scheduler.NoScheduler;
import org.apache.cassandra.service.CacheService;
import org.apache.cassandra.service.MigrationManager;
import org.apache.cassandra.thrift.ThriftServer;
import org.apache.cassandra.utils.FBUtilities;
import org.yaml.snakeyaml.Loader;
import org.yaml.snakeyaml.TypeDescription;
@ -77,8 +76,7 @@ public class DatabaseDescriptor
private static Config conf;
private static IAuthenticator authenticator = new AllowAllAuthenticator();
private static IAuthority authority = new AllowAllAuthority();
private static IAuthorityContainer authorityContainer;
private static IAuthorizer authorizer = new AllowAllAuthorizer();
private final static String DEFAULT_CONFIGURATION = "cassandra.yaml";
@ -206,15 +204,23 @@ public class DatabaseDescriptor
logger.debug("page_cache_hinting is " + conf.populate_io_cache_on_flush);
/* Authentication and authorization backend, implementing IAuthenticator and IAuthority */
/* Authentication and authorization backend, implementing IAuthenticator and IAuthorizer */
if (conf.authenticator != null)
authenticator = FBUtilities.<IAuthenticator>construct(conf.authenticator, "authenticator");
if (conf.authority != null)
authority = FBUtilities.<IAuthority>construct(conf.authority, "authority");
authenticator.validateConfiguration();
authority.validateConfiguration();
authorityContainer = new IAuthorityContainer(authority);
if (conf.authority != null)
{
logger.warn("Please rename 'authority' to 'authorizer' in cassandra.yaml");
if (!conf.authority.equals("org.apache.cassandra.auth.AllowAllAuthority"))
throw new ConfigurationException("IAuthority interface has been deprecated,"
+ " please implement IAuthorizer instead.");
}
if (conf.authorizer != null)
authorizer = FBUtilities.<IAuthorizer>construct(conf.authorizer, "authorizer");
authenticator.validateConfiguration();
authorizer.validateConfiguration();
/* Hashing strategy */
if (conf.partitioner == null)
@ -462,12 +468,6 @@ public class DatabaseDescriptor
Schema.instance.setTableDefinition(ksmd);
}
// setup schema required for authorization
authorityContainer.setup();
// setup schema required for authorization
authorityContainer.setup();
/* Load the seeds for node contact points */
if (conf.seed_provider == null)
{
@ -585,14 +585,9 @@ public class DatabaseDescriptor
return authenticator;
}
public static IAuthority getAuthority()
public static IAuthorizer getAuthorizer()
{
return authority;
}
public static IAuthorityContainer getAuthorityContainer()
{
return authorityContainer;
return authorizer;
}
public static int getThriftMaxMessageLength()

View File

@ -73,7 +73,7 @@ public class DeleteStatement extends AbstractModification
{
CFMetaData metadata = validateColumnFamily(keyspace, columnFamily);
clientState.hasColumnFamilyAccess(columnFamily, Permission.DELETE);
clientState.hasColumnFamilyAccess(keyspace, columnFamily, Permission.MODIFY);
AbstractType<?> keyType = Schema.instance.getCFMetaData(keyspace, columnFamily).getKeyValidator();
List<IMutation> rowMutations = new ArrayList<IMutation>(keys.size());

View File

@ -17,55 +17,64 @@
*/
package org.apache.cassandra.cql;
import java.io.IOException;
import org.apache.cassandra.config.*;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.thrift.CfDef;
import org.apache.cassandra.thrift.ColumnDef;
public class DropIndexStatement
{
public final String index;
public final String indexName;
private String keyspace;
public DropIndexStatement(String indexName)
{
index = indexName;
this.indexName = indexName;
}
public CFMetaData generateCFMetadataUpdate(String keyspace)
throws InvalidRequestException, ConfigurationException, IOException
public void setKeyspace(String keyspace)
{
CfDef cfDef = null;
this.keyspace = keyspace;
}
public String getColumnFamily() throws InvalidRequestException
{
return findIndexedCF().cfName;
}
public CFMetaData generateCFMetadataUpdate() throws InvalidRequestException
{
return updateCFMetadata(findIndexedCF());
}
private CFMetaData updateCFMetadata(CFMetaData cfm) throws InvalidRequestException
{
ColumnDefinition column = findIndexedColumn(cfm);
assert column != null;
CFMetaData cloned = cfm.clone();
ColumnDefinition toChange = cloned.getColumn_metadata().get(column.name);
assert toChange.getIndexName() != null && toChange.getIndexName().equals(indexName);
toChange.setIndexName(null);
toChange.setIndexType(null, null);
return cloned;
}
private CFMetaData findIndexedCF() throws InvalidRequestException
{
KSMetaData ksm = Schema.instance.getTableDefinition(keyspace);
for (CFMetaData cfm : ksm.cfMetaData().values())
{
cfDef = getUpdatedCFDef(cfm.toThrift());
if (cfDef != null)
break;
if (findIndexedColumn(cfm) != null)
return cfm;
}
if (cfDef == null)
throw new InvalidRequestException("Index '" + index + "' could not be found in any of the ColumnFamilies of keyspace '" + keyspace + "'");
return CFMetaData.fromThrift(cfDef);
throw new InvalidRequestException("Index '" + indexName + "' could not be found in any of the column families of keyspace '" + keyspace + "'");
}
private CfDef getUpdatedCFDef(CfDef cfDef) throws InvalidRequestException
private ColumnDefinition findIndexedColumn(CFMetaData cfm)
{
for (ColumnDef column : cfDef.column_metadata)
for (ColumnDefinition column : cfm.getColumn_metadata().values())
{
if (column.isSetIndex_type() && column.isSetIndex_name() && column.index_name.equals(index))
{
column.unsetIndex_name();
column.unsetIndex_type();
return cfDef;
}
if (column.getIndexType() != null && column.getIndexName() != null && column.getIndexName().equals(indexName))
return column;
}
return null;
}
}

View File

@ -23,6 +23,11 @@ import java.nio.charset.CharacterCodingException;
import java.util.*;
import java.util.concurrent.TimeoutException;
import com.google.common.base.Predicates;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.config.*;
import org.apache.cassandra.cli.CliUtils;
@ -41,7 +46,6 @@ import org.apache.cassandra.service.StorageProxy;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.MigrationManager;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.thrift.CqlMetadata;
import org.apache.cassandra.thrift.CqlResult;
import org.apache.cassandra.thrift.CqlResultType;
@ -56,13 +60,8 @@ import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.SemanticVersion;
import com.google.common.base.Predicates;
import com.google.common.collect.Maps;
import org.antlr.runtime.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.cassandra.thrift.ThriftValidation.validateColumnFamily;
@ -222,35 +221,6 @@ public class QueryProcessor
return rows.subList(0, select.getNumRecords() < rows.size() ? select.getNumRecords() : rows.size());
}
private static void batchUpdate(ThriftClientState clientState, List<UpdateStatement> updateStatements, ConsistencyLevel consistency, List<ByteBuffer> variables )
throws RequestValidationException, RequestExecutionException
{
String globalKeyspace = clientState.getKeyspace();
List<IMutation> rowMutations = new ArrayList<IMutation>(updateStatements.size());
List<String> cfamsSeen = new ArrayList<String>(updateStatements.size());
for (UpdateStatement update : updateStatements)
{
String keyspace = update.keyspace == null ? globalKeyspace : update.keyspace;
// Avoid unnecessary authorizations.
if (!(cfamsSeen.contains(update.getColumnFamily())))
{
clientState.hasColumnFamilyAccess(keyspace, update.getColumnFamily(), Permission.UPDATE);
cfamsSeen.add(update.getColumnFamily());
}
rowMutations.addAll(update.prepareRowMutations(keyspace, clientState, variables));
}
for (IMutation mutation : rowMutations)
{
validateKey(mutation.key());
}
StorageProxy.mutate(rowMutations, consistency);
}
private static IDiskAtomFilter filterFromSelect(SelectStatement select, CFMetaData metadata, List<ByteBuffer> variables)
throws InvalidRequestException
{
@ -556,8 +526,18 @@ public class QueryProcessor
case UPDATE:
UpdateStatement update = (UpdateStatement)statement.statement;
update.getConsistencyLevel().validateForWrite(keyspace);
clientState.hasColumnFamilyAccess(keyspace, update.getColumnFamily(), Permission.UPDATE);
batchUpdate(clientState, Collections.singletonList(update), update.getConsistencyLevel(), variables);
keyspace = update.keyspace == null ? clientState.getKeyspace() : update.keyspace;
// permission is checked in prepareRowMutations()
List<IMutation> rowMutations = update.prepareRowMutations(keyspace, clientState, variables);
for (IMutation mutation : rowMutations)
{
validateKey(mutation.key());
}
StorageProxy.mutate(rowMutations, update.getConsistencyLevel());
result.type = CqlResultType.VOID;
return result;
@ -601,7 +581,7 @@ public class QueryProcessor
keyspace = columnFamily.left == null ? clientState.getKeyspace() : columnFamily.left;
validateColumnFamily(keyspace, columnFamily.right);
clientState.hasColumnFamilyAccess(keyspace, columnFamily.right, Permission.DELETE);
clientState.hasColumnFamilyAccess(keyspace, columnFamily.right, Permission.MODIFY);
try
{
@ -623,7 +603,7 @@ public class QueryProcessor
DeleteStatement delete = (DeleteStatement)statement.statement;
keyspace = delete.keyspace == null ? clientState.getKeyspace() : delete.keyspace;
clientState.hasColumnFamilyAccess(keyspace, delete.columnFamily, Permission.DELETE);
// permission is checked in prepareRowMutations()
List<IMutation> deletions = delete.prepareRowMutations(keyspace, clientState, variables);
for (IMutation deletion : deletions)
{
@ -639,7 +619,7 @@ public class QueryProcessor
CreateKeyspaceStatement create = (CreateKeyspaceStatement)statement.statement;
create.validate();
ThriftValidation.validateKeyspaceNotSystem(create.getName());
clientState.hasKeyspaceAccess(create.getName(), Permission.CREATE);
clientState.hasAllKeyspacesAccess(Permission.CREATE);
try
{
@ -662,7 +642,7 @@ public class QueryProcessor
case CREATE_COLUMNFAMILY:
CreateColumnFamilyStatement createCf = (CreateColumnFamilyStatement)statement.statement;
clientState.hasColumnFamilySchemaAccess(keyspace, Permission.CREATE);
clientState.hasKeyspaceAccess(keyspace, Permission.CREATE);
try
{
@ -723,12 +703,13 @@ public class QueryProcessor
case DROP_INDEX:
DropIndexStatement dropIdx = (DropIndexStatement)statement.statement;
keyspace = clientState.getKeyspace();
dropIdx.setKeyspace(keyspace);
clientState.hasColumnFamilyAccess(keyspace, dropIdx.getColumnFamily(), Permission.ALTER);
try
{
CFMetaData updatedCF = dropIdx.generateCFMetadataUpdate(clientState.getKeyspace());
clientState.hasColumnFamilyAccess(updatedCF.ksName, updatedCF.cfName, Permission.DESCRIBE);
CFMetaData updatedCF = dropIdx.generateCFMetadataUpdate();
MigrationManager.announceColumnFamilyUpdate(updatedCF);
}
catch (ConfigurationException e)
@ -737,12 +718,6 @@ public class QueryProcessor
ex.initCause(e);
throw ex;
}
catch (IOException e)
{
InvalidRequestException ex = new InvalidRequestException(e.toString());
ex.initCause(e);
throw ex;
}
result.type = CqlResultType.VOID;
return result;
@ -788,7 +763,7 @@ public class QueryProcessor
AlterTableStatement alterTable = (AlterTableStatement) statement.statement;
validateColumnFamily(keyspace, alterTable.columnFamily);
clientState.hasColumnFamilyAccess(alterTable.columnFamily, Permission.ALTER);
clientState.hasColumnFamilyAccess(keyspace, alterTable.columnFamily, Permission.ALTER);
try
{

View File

@ -35,7 +35,6 @@ import org.apache.cassandra.thrift.ThriftClientState;
import static org.apache.cassandra.cql.QueryProcessor.validateColumn;
import static org.apache.cassandra.cql.QueryProcessor.validateKey;
import static org.apache.cassandra.thrift.ThriftValidation.validateColumnFamily;
/**
@ -132,8 +131,6 @@ public class UpdateStatement extends AbstractModification
public List<IMutation> prepareRowMutations(String keyspace, ThriftClientState clientState, Long timestamp, List<ByteBuffer> variables)
throws InvalidRequestException, UnauthorizedException
{
List<String> cfamsSeen = new ArrayList<String>();
boolean hasCommutativeOperation = false;
for (Map.Entry<Term, Operation> column : getColumns().entrySet())
@ -151,12 +148,7 @@ public class UpdateStatement extends AbstractModification
QueryProcessor.validateKeyAlias(metadata, keyName);
// Avoid unnecessary authorizations.
if (!(cfamsSeen.contains(columnFamily)))
{
clientState.hasColumnFamilyAccess(columnFamily, Permission.UPDATE);
cfamsSeen.add(columnFamily);
}
clientState.hasColumnFamilyAccess(keyspace, columnFamily, Permission.MODIFY);
List<IMutation> rowMutations = new LinkedList<IMutation>();

View File

@ -29,13 +29,17 @@ options {
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.cassandra.cql3.operations.*;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.auth.DataResource;
import org.apache.cassandra.auth.IResource;
import org.apache.cassandra.cql3.operations.*;
import org.apache.cassandra.cql3.statements.*;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.exceptions.ConfigurationException;
@ -167,7 +171,7 @@ cqlStatement returns [ParsedStatement stmt]
| st14=alterTableStatement { $stmt = st14; }
| st15=grantStatement { $stmt = st15; }
| st16=revokeStatement { $stmt = st16; }
| st17=listGrantsStatement { $stmt = st17; }
| st17=listPermissionsStatement { $stmt = st17; }
| st18=alterKeyspaceStatement { $stmt = st18; }
;
@ -181,7 +185,6 @@ useStatement returns [UseStatement stmt]
/**
* SELECT <expression>
* FROM <CF>
* USING CONSISTENCY <LEVEL>
* WHERE KEY = "key1" AND COL > 1 AND COL < 100
* LIMIT <NUMBER>;
*/
@ -277,7 +280,7 @@ usingClauseObjective[Attributes attrs]
/**
* UPDATE <CF>
* USING CONSISTENCY <level> AND TIMESTAMP <long>
* USING TIMESTAMP <long>
* SET name1 = value1, name2 = value2
* WHERE key = value;
*/
@ -298,7 +301,7 @@ updateStatement returns [UpdateStatement expr]
/**
* DELETE name1, name2
* FROM <CF>
* USING CONSISTENCY <level> AND TIMESTAMP <long>
* USING TIMESTAMP <long>
* WHERE KEY = keyname;
*/
deleteStatement returns [DeleteStatement expr]
@ -325,7 +328,7 @@ deleteSelector returns [Selector s]
;
/**
* BEGIN BATCH [USING CONSISTENCY <LVL>]
* BEGIN BATCH
* UPDATE <CF> SET name1 = value1 WHERE KEY = keyname1;
* UPDATE <CF> SET name2 = value2 WHERE KEY = keyname2;
* UPDATE <CF> SET name3 = value3 WHERE KEY = keyname3;
@ -334,7 +337,7 @@ deleteSelector returns [Selector s]
*
* OR
*
* BEGIN BATCH [USING CONSISTENCY <LVL>]
* BEGIN BATCH
* INSERT INTO <CF> (KEY, <name>) VALUES ('<key>', '<value>');
* INSERT INTO <CF> (KEY, <name>) VALUES ('<key>', '<value>');
* ...
@ -342,7 +345,7 @@ deleteSelector returns [Selector s]
*
* OR
*
* BEGIN BATCH [USING CONSISTENCY <LVL>]
* BEGIN BATCH
* DELETE name1, name2 FROM <CF> WHERE key = <key>
* DELETE name3, name4 FROM <CF> WHERE key = <key>
* ...
@ -493,23 +496,16 @@ truncateStatement returns [TruncateStatement stmt]
;
/**
* GRANT <permission> ON <resource> TO <username> [WITH GRANT OPTION]
* GRANT <permission> ON <resource> TO <username>
*/
grantStatement returns [GrantStatement stmt]
@init { boolean withGrant = false; }
: K_GRANT
permission
permissionOrAll
K_ON
resource=columnFamilyName
resource
K_TO
user=(IDENT | STRING_LITERAL)
(K_WITH K_GRANT K_OPTION { withGrant = true; })?
{
$stmt = new GrantStatement($permission.perm,
resource,
$user.text,
withGrant);
}
username
{ $stmt = new GrantStatement($permissionOrAll.perms, $resource.res, $username.text); }
;
/**
@ -517,26 +513,54 @@ grantStatement returns [GrantStatement stmt]
*/
revokeStatement returns [RevokeStatement stmt]
: K_REVOKE
permission
permissionOrAll
K_ON
resource=columnFamilyName
resource
K_FROM
user=(IDENT | STRING_LITERAL)
{
$stmt = new RevokeStatement($permission.perm,
$user.text,
resource);
}
username
{ $stmt = new RevokeStatement($permissionOrAll.perms, $resource.res, $username.text); }
;
listGrantsStatement returns [ListGrantsStatement stmt]
: K_LIST K_GRANTS K_FOR username=(IDENT | STRING_LITERAL) { $stmt = new ListGrantsStatement($username.text); }
listPermissionsStatement returns [ListPermissionsStatement stmt]
@init {
IResource resource = null;
String username = null;
boolean recursive = true;
}
: K_LIST
permissionOrAll
( K_ON resource { resource = $resource.res; } )?
( K_OF username { username = $username.text; } )?
( K_NORECURSIVE { recursive = false; } )?
{ $stmt = new ListPermissionsStatement($permissionOrAll.perms, resource, username, recursive); }
;
permission returns [Permission perm]
: p=(K_DESCRIBE | K_USE | K_CREATE | K_ALTER | K_DROP | K_SELECT | K_INSERT | K_UPDATE | K_DELETE | K_FULL_ACCESS | K_NO_ACCESS)
: p=(K_CREATE | K_ALTER | K_DROP | K_SELECT | K_MODIFY | K_AUTHORIZE)
{ $perm = Permission.valueOf($p.text.toUpperCase()); }
;
permissionOrAll returns [Set<Permission> perms]
: K_ALL ( K_PERMISSIONS )? { $perms = Permission.ALL_DATA; }
| p=permission ( K_PERMISSION )? { $perms = EnumSet.of($p.perm); }
;
username
: IDENT
| STRING_LITERAL
;
resource returns [IResource res]
: r=dataResource { $res = $r.res; }
;
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()); }
;
/** DEFINITIONS **/
// Column Identifiers
@ -747,7 +771,6 @@ collection_type returns [ParsedType pt]
unreserved_keyword returns [String str]
: k=( K_KEY
| K_CLUSTERING
| K_LEVEL
| K_COUNT
| K_TTL
| K_COMPACT
@ -758,6 +781,10 @@ unreserved_keyword returns [String str]
| K_MAP
| K_LIST
| K_FILTERING
| K_PERMISSION
| K_PERMISSIONS
| K_KEYSPACES
| K_ALL
) { $str = $k.text; }
| t=native_type { $str = t.toString(); }
;
@ -774,16 +801,6 @@ K_UPDATE: U P D A T E;
K_WITH: W I T H;
K_LIMIT: L I M I T;
K_USING: U S I N G;
K_LEVEL: ( O N E
| Q U O R U M
| A L L
| A N Y
| L O C A L '_' Q U O R U M
| E A C H '_' Q U O R U M
| T W O
| T H R E E
)
;
K_USE: U S E;
K_COUNT: C O U N T;
K_SET: S E T;
@ -797,6 +814,7 @@ K_IN: I N;
K_CREATE: C R E A T E;
K_KEYSPACE: ( K E Y S P A C E
| S C H E M A );
K_KEYSPACES: K E Y S P A C E S;
K_COLUMNFAMILY:( C O L U M N F A M I L Y
| T A B L E );
K_INDEX: I N D E X;
@ -819,16 +837,16 @@ K_BY: B Y;
K_ASC: A S C;
K_DESC: D E S C;
K_GRANT: G R A N T;
K_GRANTS: G R A N T S;
K_ALL: A L L;
K_PERMISSION: P E R M I S S I O N;
K_PERMISSIONS: P E R M I S S I O N S;
K_OF: O F;
K_REVOKE: R E V O K E;
K_OPTION: O P T I O N;
K_DESCRIBE: D E S C R I B E;
K_FOR: F O R;
K_FULL_ACCESS: F U L L '_' A C C E S S;
K_NO_ACCESS: N O '_' A C C E S S;
K_ALLOW: A L L O W;
K_FILTERING: F I L T E R I N G;
K_MODIFY: M O D I F Y;
K_AUTHORIZE: A U T H O R I Z E;
K_NORECURSIVE: N O R E C U R S I V E;
K_CLUSTERING: C L U S T E R I N G;
K_ASCII: A S C I I;

View File

@ -127,8 +127,8 @@ public class QueryProcessor
throws RequestExecutionException, RequestValidationException
{
ClientState clientState = queryState.getClientState();
statement.checkAccess(clientState);
statement.validate(clientState);
statement.checkAccess(clientState);
ResultMessage result = statement.execute(cl, queryState, variables);
return result == null ? new ResultMessage.Void() : result;
}

View File

@ -0,0 +1,66 @@
/*
* 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.nio.ByteBuffer;
import java.util.List;
import org.apache.cassandra.auth.DataResource;
import org.apache.cassandra.cql3.CQLStatement;
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;
public abstract class AuthorizationStatement extends ParsedStatement implements CQLStatement
{
@Override
public Prepared prepare()
{
return new Prepared(this);
}
public int getBoundsTerms()
{
return 0;
}
public void checkAccess(ClientState state) throws InvalidRequestException, UnauthorizedException
{}
public ResultMessage execute(ConsistencyLevel cl, QueryState state, List<ByteBuffer> variables) throws UnauthorizedException, InvalidRequestException
{
return execute(state.getClientState(), variables);
}
public abstract ResultMessage execute(ClientState state, List<ByteBuffer> variables) throws UnauthorizedException, InvalidRequestException;
public ResultMessage executeInternal(QueryState state)
{
// executeInternal is for local query only, thus altering permission doesn't make sense and is not supported
throw new UnsupportedOperationException();
}
public static DataResource maybeCorrectResource(DataResource resource, ClientState state) throws InvalidRequestException
{
if (resource.isColumnFamilyLevel() && resource.getKeyspace() == null)
return DataResource.columnFamily(state.getKeyspace(), resource.getColumnFamily());
return resource;
}
}

View File

@ -70,7 +70,7 @@ public class BatchStatement extends ModificationStatement
// Avoid unnecessary authorizations.
if (!(cfamsSeen.contains(statement.columnFamily())))
{
state.hasColumnFamilyAccess(statement.keyspace(), statement.columnFamily(), Permission.UPDATE);
state.hasColumnFamilyAccess(statement.keyspace(), statement.columnFamily(), Permission.MODIFY);
cfamsSeen.add(statement.columnFamily());
}
}

View File

@ -68,7 +68,7 @@ public class CreateColumnFamilyStatement extends SchemaAlteringStatement
public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException
{
state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.CREATE);
state.hasKeyspaceAccess(keyspace(), Permission.CREATE);
}
// Column definitions

View File

@ -60,7 +60,7 @@ public class CreateKeyspaceStatement extends SchemaAlteringStatement
public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException
{
state.hasKeyspaceAccess(name, Permission.CREATE);
state.hasAllKeyspacesAccess(Permission.CREATE);
}
/**

View File

@ -17,7 +17,6 @@
*/
package org.apache.cassandra.cql3.statements;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
@ -41,44 +40,7 @@ public class DropIndexStatement extends SchemaAlteringStatement
public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException
{
state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.ALTER);
}
public void announceMigration() throws InvalidRequestException, ConfigurationException
{
CFMetaData updatedCfm = null;
KSMetaData ksm = Schema.instance.getTableDefinition(keyspace());
for (CFMetaData cfm : ksm.cfMetaData().values())
{
updatedCfm = getUpdatedCFMetadata(cfm);
if (updatedCfm != null)
break;
}
if (updatedCfm == null)
throw new InvalidRequestException("Index '" + indexName + "' could not be found in any of the column families of keyspace '" + keyspace() + "'");
MigrationManager.announceColumnFamilyUpdate(updatedCfm);
}
private CFMetaData getUpdatedCFMetadata(CFMetaData cfm) throws InvalidRequestException
{
for (ColumnDefinition column : cfm.getColumn_metadata().values())
{
if (column.getIndexType() != null && column.getIndexName() != null && column.getIndexName().equals(indexName))
{
CFMetaData cloned = cfm.clone();
ColumnDefinition toChange = cloned.getColumn_metadata().get(column.name);
assert toChange.getIndexName() != null && toChange.getIndexName().equals(indexName);
toChange.setIndexName(null);
toChange.setIndexType(null, null);
return cloned;
}
}
return null;
state.hasColumnFamilyAccess(keyspace(), findIndexedCF().cfName, Permission.ALTER);
}
public ResultMessage.SchemaChange.Change changeType()
@ -86,4 +48,43 @@ public class DropIndexStatement extends SchemaAlteringStatement
// Dropping an index is akin to updating the CF
return ResultMessage.SchemaChange.Change.UPDATED;
}
public void announceMigration() throws InvalidRequestException, ConfigurationException
{
CFMetaData updatedCfm = updateCFMetadata(findIndexedCF());
MigrationManager.announceColumnFamilyUpdate(updatedCfm);
}
private CFMetaData updateCFMetadata(CFMetaData cfm) throws InvalidRequestException
{
ColumnDefinition column = findIndexedColumn(cfm);
assert column != null;
CFMetaData cloned = cfm.clone();
ColumnDefinition toChange = cloned.getColumn_metadata().get(column.name);
assert toChange.getIndexName() != null && toChange.getIndexName().equals(indexName);
toChange.setIndexName(null);
toChange.setIndexType(null, null);
return cloned;
}
private CFMetaData findIndexedCF() throws InvalidRequestException
{
KSMetaData ksm = Schema.instance.getTableDefinition(keyspace());
for (CFMetaData cfm : ksm.cfMetaData().values())
{
if (findIndexedColumn(cfm) != null)
return cfm;
}
throw new InvalidRequestException("Index '" + indexName + "' could not be found in any of the column families of keyspace '" + keyspace() + "'");
}
private ColumnDefinition findIndexedColumn(CFMetaData cfm)
{
for (ColumnDefinition column : cfm.getColumn_metadata().values())
{
if (column.getIndexType() != null && column.getIndexName() != null && column.getIndexName().equals(indexName))
return column;
}
return null;
}
}

View File

@ -20,9 +20,10 @@ package org.apache.cassandra.cql3.statements;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Set;
import org.apache.cassandra.auth.IResource;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.cql3.CFName;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.UnauthorizedException;
import org.apache.cassandra.service.ClientState;
@ -30,22 +31,14 @@ import org.apache.cassandra.transport.messages.ResultMessage;
public class GrantStatement extends PermissionAlteringStatement
{
private final Permission permission;
private final CFName resource;
private final String username;
private final boolean grantOption;
public GrantStatement(Permission permission, CFName resource, String username, boolean grantOption)
public GrantStatement(Set<Permission> permissions, IResource resource, String username)
{
this.permission = permission;
this.resource = resource;
this.username = username;
this.grantOption = grantOption;
super(permissions, resource, username);
}
public ResultMessage execute(ClientState state, List<ByteBuffer> variables) throws UnauthorizedException, InvalidRequestException
{
state.grantPermission(permission, username, resource, grantOption);
state.grantPermission(permissions, resource, username);
return null;
}
}

View File

@ -1,42 +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.cql3.statements;
import java.nio.ByteBuffer;
import java.util.List;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.UnauthorizedException;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.transport.messages.ResultMessage;
public class ListGrantsStatement extends PermissionAlteringStatement
{
private final String username;
public ListGrantsStatement(String username)
{
this.username = username;
}
public ResultMessage execute(ClientState state, List<ByteBuffer> variables) throws UnauthorizedException, InvalidRequestException
{
return state.listPermissions(username);
}
}

View File

@ -0,0 +1,106 @@
/*
* 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.nio.ByteBuffer;
import java.util.*;
import org.apache.cassandra.auth.*;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.cql3.ResultSet;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.UnauthorizedException;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.transport.messages.ResultMessage;
public class ListPermissionsStatement extends AuthorizationStatement
{
private static final String KS = "auth"; // virtual keyspace to use for now.
private static final String CF = "permissions"; // virtual cf to use for now.
private static final List<ColumnSpecification> metadata;
static
{
List<ColumnSpecification> columns = new ArrayList<ColumnSpecification>(4);
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;
public ListPermissionsStatement(Set<Permission> permissions, IResource resource, String username, boolean recursive)
{
this.permissions = permissions;
this.resource = (DataResource) resource;
this.username = username;
this.recursive = recursive;
}
// TODO: user existence check (when IAuthenticator rewrite is done)
public void validate(ClientState state) throws InvalidRequestException
{
if (resource != null)
{
resource = maybeCorrectResource(resource, state);
if (!resource.exists())
throw new InvalidRequestException(String.format("%s doesn't exist", resource));
}
}
// TODO: Create a new ResultMessage type (?). Rows will do for now.
public ResultMessage execute(ClientState state, List<ByteBuffer> variables) throws UnauthorizedException, InvalidRequestException
{
List<PermissionDetails> details = new ArrayList<PermissionDetails>();
if (resource != null && recursive)
{
for (IResource r : Resources.chain(resource))
details.addAll(state.listPermissions(permissions, r, username));
}
else
{
details.addAll(state.listPermissions(permissions, resource, username));
}
Collections.sort(details);
return resultMessage(details);
}
private ResultMessage resultMessage(List<PermissionDetails> details)
{
if (details.isEmpty())
return new ResultMessage.Void();
ResultSet result = new ResultSet(metadata);
for (PermissionDetails pd : details)
{
result.addColumnValue(UTF8Type.instance.decompose(pd.username));
result.addColumnValue(UTF8Type.instance.decompose(pd.resource.toString()));
result.addColumnValue(UTF8Type.instance.decompose(pd.permission.toString()));
}
return new ResultMessage.Rows(result);
}
}

View File

@ -24,8 +24,6 @@ import java.util.*;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.ColumnSlice;
import org.apache.cassandra.db.filter.QueryPath;
@ -37,6 +35,7 @@ import org.apache.cassandra.db.ExpiringColumn;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.service.StorageProxy;
import org.apache.cassandra.transport.messages.ResultMessage;
/**
* Abstract class for statements that apply on a given column family.
@ -69,7 +68,7 @@ public abstract class ModificationStatement extends CFStatement implements CQLSt
public void checkAccess(ClientState state) throws InvalidRequestException, UnauthorizedException
{
state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.UPDATE);
state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.MODIFY);
}
public void validate(ClientState state) throws InvalidRequestException

View File

@ -17,45 +17,43 @@
*/
package org.apache.cassandra.cql3.statements;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Set;
import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.auth.DataResource;
import org.apache.cassandra.auth.IResource;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.UnauthorizedException;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.transport.messages.ResultMessage;
public abstract class PermissionAlteringStatement extends ParsedStatement implements CQLStatement
public abstract class PermissionAlteringStatement extends AuthorizationStatement
{
@Override
public Prepared prepare()
protected final Set<Permission> permissions;
protected DataResource resource;
protected final String username;
protected PermissionAlteringStatement(Set<Permission> permissions, IResource resource, String username)
{
return new Prepared(this);
this.permissions = permissions;
this.resource = (DataResource) resource;
this.username = username;
}
public int getBoundsTerms()
public void checkAccess(ClientState state) throws InvalidRequestException, UnauthorizedException
{
return 0;
// check that the user has AUTHORIZE permission on the resource or its parents, otherwise reject GRANT/REVOKE.
state.ensureHasPermission(Permission.AUTHORIZE, resource);
// check that the user has [a single permission or all in case of ALL] on the resource or its parents.
for (Permission p : permissions)
state.ensureHasPermission(p, resource);
}
public void checkAccess(ClientState state)
{}
public void validate(ClientState state)
{}
public ResultMessage execute(ConsistencyLevel cl, QueryState state, List<ByteBuffer> variables) throws UnauthorizedException, InvalidRequestException
// TODO: user existence check (when IAuthenticator rewrite is done)
public void validate(ClientState state) throws InvalidRequestException
{
return execute(state.getClientState(), variables);
}
public abstract ResultMessage execute(ClientState state, List<ByteBuffer> variables) throws UnauthorizedException, InvalidRequestException;
public ResultMessage executeInternal(QueryState state)
{
// executeInternal is for local query only, thus altering permission doesn't make sense and is not supported
throw new UnsupportedOperationException();
// 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));
}
}

View File

@ -20,9 +20,10 @@ package org.apache.cassandra.cql3.statements;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Set;
import org.apache.cassandra.auth.IResource;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.cql3.CFName;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.UnauthorizedException;
import org.apache.cassandra.service.ClientState;
@ -30,20 +31,14 @@ import org.apache.cassandra.transport.messages.ResultMessage;
public class RevokeStatement extends PermissionAlteringStatement
{
private final Permission permission;
private final String from;
private final CFName resource;
public RevokeStatement(Permission permission, String from, CFName resource)
public RevokeStatement(Set<Permission> permissions, IResource resource, String username)
{
this.permission = permission;
this.from = from;
this.resource = resource;
super(permissions, resource, username);
}
public ResultMessage execute(ClientState state, List<ByteBuffer> variables) throws UnauthorizedException, InvalidRequestException
{
state.revokePermission(permission, from, resource);
state.revokePermission(permissions, resource, username);
return null;
}
}

View File

@ -46,7 +46,7 @@ public class TruncateStatement extends CFStatement implements CQLStatement
public void checkAccess(ClientState state) throws InvalidRequestException, UnauthorizedException
{
state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.DELETE);
state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.MODIFY);
}
public void validate(ClientState state) throws InvalidRequestException

View File

@ -219,6 +219,12 @@ public class CassandraDaemon
System.exit(100);
}
// TODO: setup authenticator
// setup Authorizer.
DatabaseDescriptor.getAuthorizer().setup();
// register a custom MigrationListener for permissions cleanup after dropped keyspaces/cfs.
MigrationManager.instance.register(new org.apache.cassandra.auth.MigrationListener());
// clean up debris in the rest of the tables
for (String table : Schema.instance.getTables())
{

View File

@ -19,15 +19,14 @@ package org.apache.cassandra.service;
import java.util.*;
import org.apache.cassandra.auth.*;
import org.apache.cassandra.cql3.CFName;
import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.auth.*;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.Schema;
import org.apache.cassandra.db.SystemTable;
import org.apache.cassandra.db.Table;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.UnauthorizedException;
@ -44,12 +43,28 @@ public class ClientState
private static final Logger logger = LoggerFactory.getLogger(ClientState.class);
public static final SemanticVersion DEFAULT_CQL_VERSION = org.apache.cassandra.cql3.QueryProcessor.CQL_VERSION;
private static final Set<IResource> READABLE_SYSTEM_RESOURCES = new HashSet<IResource>(5);
private static final Set<IResource> PROTECTED_AUTH_RESOURCES = new HashSet<IResource>();
static
{
// We want these system cfs to be always readable since many tools rely on them (nodetool, cqlsh, bulkloader, etc.)
String[] cfs = new String[] { SystemTable.LOCAL_CF,
SystemTable.PEERS_CF,
SystemTable.SCHEMA_KEYSPACES_CF,
SystemTable.SCHEMA_COLUMNFAMILIES_CF,
SystemTable.SCHEMA_COLUMNS_CF };
for (String cf : cfs)
READABLE_SYSTEM_RESOURCES.add(DataResource.columnFamily(Table.SYSTEM_KS, cf));
PROTECTED_AUTH_RESOURCES.addAll(DatabaseDescriptor.getAuthorizer().protectedResources());
// TODO: the same with IAuthenticator once it's done.
}
// Current user for the session
private AuthenticatedUser user;
private String keyspace;
// Reusable array for authorization
private final List<Object> resource = new ArrayList<Object>();
private SemanticVersion cqlVersion = DEFAULT_CQL_VERSION;
// internalCall is used to mark ClientState as used by some internal component
@ -97,71 +112,57 @@ public class ClientState
this.user = DatabaseDescriptor.getAuthenticator().authenticate(credentials);
}
private void resourceClear()
public void hasAllKeyspacesAccess(Permission perm) throws UnauthorizedException, InvalidRequestException
{
resource.clear();
resource.add(Resources.ROOT);
resource.add(Resources.KEYSPACES);
if (internalCall)
return;
validateLogin();
ensureHasPermission(perm, DataResource.root());
}
public void hasKeyspaceAccess(String keyspace, Permission perm) throws UnauthorizedException, InvalidRequestException
{
hasColumnFamilySchemaAccess(keyspace, perm);
hasAccess(keyspace, perm, DataResource.keyspace(keyspace));
}
/**
* Confirms that the client thread has the given Permission for the ColumnFamily list of
* the provided keyspace.
*/
public void hasColumnFamilySchemaAccess(String keyspace, Permission perm) throws UnauthorizedException, InvalidRequestException
public void hasColumnFamilyAccess(String keyspace, String columnFamily, Permission perm)
throws UnauthorizedException, InvalidRequestException
{
hasAccess(keyspace, perm, DataResource.columnFamily(keyspace, columnFamily));
}
private void hasAccess(String keyspace, Permission perm, DataResource resource)
throws UnauthorizedException, InvalidRequestException
{
validateLogin();
validateKeyspace(keyspace);
preventSystemKSSchemaModification(keyspace, perm);
resourceClear();
resource.add(keyspace);
Set<Permission> perms = DatabaseDescriptor.getAuthority().authorize(user, resource);
hasAccess(user, perms, perm, resource);
}
private void preventSystemKSSchemaModification(String keyspace, Permission perm) throws InvalidRequestException
{
if (keyspace.equalsIgnoreCase(Table.SYSTEM_KS) && !Permission.ALLOWED_SYSTEM_ACTIONS.contains(perm))
throw new InvalidRequestException("system keyspace is not user-modifiable.");
}
/**
* Confirms that the client thread has the given Permission in the context of the given
* ColumnFamily and the current keyspace.
*/
public void hasColumnFamilyAccess(String columnFamily, Permission perm) throws UnauthorizedException, InvalidRequestException
{
hasColumnFamilyAccess(keyspace, columnFamily, perm);
}
public void hasColumnFamilyAccess(String keyspace, String columnFamily, Permission perm) throws UnauthorizedException, InvalidRequestException
{
validateLogin();
validateKeyspace(keyspace);
resourceClear();
resource.add(keyspace);
if (!internalCall)
preventSystemKSSchemaModification(keyspace, perm);
// check if keyspace access is set to Permission.FULL_ACCESS
// (which means that user has all access on keyspace and it's underlying elements)
if (DatabaseDescriptor.getAuthority().authorize(user, resource).contains(Permission.FULL_ACCESS))
if (internalCall)
return;
validateLogin();
preventSystemKSSModification(keyspace, perm);
if (perm.equals(Permission.SELECT) && READABLE_SYSTEM_RESOURCES.contains(resource))
return;
if (PROTECTED_AUTH_RESOURCES.contains(resource))
throw new UnauthorizedException(String.format("Resource %s is inaccessible", resource));
ensureHasPermission(perm, resource);
}
resource.add(columnFamily);
Set<Permission> perms = DatabaseDescriptor.getAuthority().authorize(user, resource);
public void ensureHasPermission(Permission perm, IResource resource) throws UnauthorizedException
{
for (IResource r : Resources.chain(resource))
{
if (authorize(r).contains(perm))
return;
}
throw new UnauthorizedException(String.format("User %s has no %s permission on %s or any of its parents",
user.username,
perm,
resource));
}
hasAccess(user, perms, perm, resource);
private void preventSystemKSSModification(String keyspace, Permission perm) throws UnauthorizedException
{
if (Schema.systemKeyspaceNames.contains(keyspace.toLowerCase()) && !perm.equals(Permission.SELECT))
throw new UnauthorizedException(keyspace + " keyspace is not user-modifiable.");
}
public boolean isLogged()
@ -178,55 +179,7 @@ public class ClientState
private static void validateKeyspace(String keyspace) throws InvalidRequestException
{
if (keyspace == null)
{
throw new InvalidRequestException("You have not set a keyspace for this session");
}
}
private static void hasAccess(AuthenticatedUser user, Set<Permission> perms, Permission perm, List<Object> resource) throws UnauthorizedException
{
if (perms.contains(Permission.FULL_ACCESS))
return; // full access
if (perms.contains(Permission.NO_ACCESS))
throw new UnauthorizedException(String.format("%s does not have permission %s for %s",
user,
perm,
Resources.toString(resource)));
boolean granular = false;
for (Permission p : perms)
{
// mixing of old and granular permissions is denied by IAuthorityContainer
// and CQL grammar so it's name to assume that once a granular permission is found
// all other permissions are going to be a subset of Permission.GRANULAR_PERMISSIONS
if (Permission.GRANULAR_PERMISSIONS.contains(p))
{
granular = true;
break;
}
}
if (granular)
{
if (perms.contains(perm))
return; // user has a given permission, perm is always one of Permission.GRANULAR_PERMISSIONS
}
else
{
for (Permission p : perms)
{
if (Permission.oldToNew.get(p).contains(perm))
return;
}
}
throw new UnauthorizedException(String.format("%s does not have permission %s for %s",
user,
perm,
Resources.toString(resource)));
}
public void setCQLVersion(String str) throws InvalidRequestException
@ -274,18 +227,26 @@ public class ClientState
return new SemanticVersion[]{ cql, cql3 };
}
public void grantPermission(Permission permission, String to, CFName on, boolean grantOption) throws UnauthorizedException, InvalidRequestException
public Set<Permission> authorize(IResource resource)
{
DatabaseDescriptor.getAuthorityContainer().grant(user, permission, to, on, grantOption);
return DatabaseDescriptor.getAuthorizer().authorize(user, resource);
}
public void grantPermission(Set<Permission> permissions, IResource resource, String to)
throws UnauthorizedException, InvalidRequestException
{
DatabaseDescriptor.getAuthorizer().grant(user, permissions, resource, to);
}
public void revokePermission(Permission permission, String from, CFName resource) throws UnauthorizedException, InvalidRequestException
public void revokePermission(Set<Permission> permissions, IResource resource, String from)
throws UnauthorizedException, InvalidRequestException
{
DatabaseDescriptor.getAuthorityContainer().revoke(user, permission, from, resource);
DatabaseDescriptor.getAuthorizer().revoke(user, permissions, resource, from);
}
public ResultMessage listPermissions(String username) throws UnauthorizedException, InvalidRequestException
public Set<PermissionDetails> listPermissions(Set<Permission> permissions, IResource resource, String of)
throws UnauthorizedException, InvalidRequestException
{
return DatabaseDescriptor.getAuthorityContainer().listPermissions(username);
return DatabaseDescriptor.getAuthorizer().listPermissions(user, permissions, resource, of);
}
}

View File

@ -30,9 +30,6 @@ import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.cassandra.db.filter.IDiskAtomFilter;
import org.apache.cassandra.utils.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -45,6 +42,7 @@ import org.apache.cassandra.cql.CQLStatement;
import org.apache.cassandra.cql.QueryProcessor;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.context.CounterContext;
import org.apache.cassandra.db.filter.IDiskAtomFilter;
import org.apache.cassandra.db.filter.QueryPath;
import org.apache.cassandra.db.marshal.MarshalException;
import org.apache.cassandra.db.marshal.TimeUUIDType;
@ -52,7 +50,6 @@ import org.apache.cassandra.dht.*;
import org.apache.cassandra.exceptions.ReadTimeoutException;
import org.apache.cassandra.exceptions.RequestExecutionException;
import org.apache.cassandra.exceptions.RequestValidationException;
import org.apache.cassandra.exceptions.UnauthorizedException;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.locator.DynamicEndpointSnitch;
import org.apache.cassandra.scheduler.IRequestScheduler;
@ -60,6 +57,7 @@ import org.apache.cassandra.service.*;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.UUIDGen;
import org.apache.thrift.TException;
@ -309,8 +307,10 @@ public class CassandraServer implements Cassandra.Iface
try
{
state().hasColumnFamilyAccess(column_parent.column_family, Permission.SELECT);
return multigetSliceInternal(state().getKeyspace(), Collections.singletonList(key), column_parent, predicate, consistency_level).get(key);
ClientState cState = state();
String keyspace = cState.getKeyspace();
state().hasColumnFamilyAccess(keyspace, column_parent.column_family, Permission.SELECT);
return multigetSliceInternal(keyspace, Collections.singletonList(key), column_parent, predicate, consistency_level).get(key);
}
catch (RequestValidationException e)
{
@ -343,8 +343,10 @@ public class CassandraServer implements Cassandra.Iface
try
{
state().hasColumnFamilyAccess(column_parent.column_family, Permission.SELECT);
return multigetSliceInternal(state().getKeyspace(), keys, column_parent, predicate, consistency_level);
ClientState cState = state();
String keyspace = cState.getKeyspace();
cState.hasColumnFamilyAccess(keyspace, column_parent.column_family, Permission.SELECT);
return multigetSliceInternal(keyspace, keys, column_parent, predicate, consistency_level);
}
catch (RequestValidationException e)
{
@ -392,8 +394,8 @@ public class CassandraServer implements Cassandra.Iface
throws RequestValidationException, NotFoundException, UnavailableException, TimedOutException
{
ThriftClientState cState = state();
cState.hasColumnFamilyAccess(column_path.column_family, Permission.SELECT);
String keyspace = cState.getKeyspace();
cState.hasColumnFamilyAccess(keyspace, column_path.column_family, Permission.SELECT);
CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace, column_path.column_family);
ThriftValidation.validateColumnPath(metadata, column_path);
@ -466,8 +468,9 @@ public class CassandraServer implements Cassandra.Iface
try
{
ThriftClientState cState = state();
cState.hasColumnFamilyAccess(column_parent.column_family, Permission.SELECT);
Table table = Table.open(cState.getKeyspace());
String keyspace = cState.getKeyspace();
cState.hasColumnFamilyAccess(keyspace, column_parent.column_family, Permission.SELECT);
Table table = Table.open(keyspace);
ColumnFamilyStore cfs = table.getColumnFamilyStore(column_parent.column_family);
if (predicate.column_names != null)
@ -569,8 +572,8 @@ public class CassandraServer implements Cassandra.Iface
try
{
ThriftClientState cState = state();
cState.hasColumnFamilyAccess(column_parent.column_family, Permission.SELECT);
String keyspace = cState.getKeyspace();
cState.hasColumnFamilyAccess(keyspace, column_parent.column_family, Permission.SELECT);
Map<ByteBuffer, Integer> counts = new HashMap<ByteBuffer, Integer>();
Map<ByteBuffer, List<ColumnOrSuperColumn>> columnFamiliesMap = multigetSliceInternal(keyspace, keys, column_parent, predicate, consistency_level);
@ -593,9 +596,10 @@ public class CassandraServer implements Cassandra.Iface
throws RequestValidationException, UnavailableException, TimedOutException
{
ThriftClientState cState = state();
cState.hasColumnFamilyAccess(column_parent.column_family, Permission.UPDATE);
String keyspace = cState.getKeyspace();
cState.hasColumnFamilyAccess(keyspace, column_parent.column_family, Permission.MODIFY);
CFMetaData metadata = ThriftValidation.validateColumnFamily(cState.getKeyspace(), column_parent.column_family, false);
CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace, column_parent.column_family, false);
ThriftValidation.validateKey(metadata, key);
ThriftValidation.validateColumnParent(metadata, column_parent);
// SuperColumn field is usually optional, but not when we're inserting
@ -675,7 +679,7 @@ public class CassandraServer implements Cassandra.Iface
// Avoid unneeded authorizations
if (!(cfamsSeen.contains(cfName)))
{
cState.hasColumnFamilyAccess(cfName, Permission.UPDATE);
cState.hasColumnFamilyAccess(keyspace, cfName, Permission.MODIFY);
cfamsSeen.add(cfName);
}
@ -794,15 +798,16 @@ public class CassandraServer implements Cassandra.Iface
throws RequestValidationException, UnavailableException, TimedOutException
{
ThriftClientState cState = state();
cState.hasColumnFamilyAccess(column_path.column_family, Permission.DELETE);
String keyspace = cState.getKeyspace();
cState.hasColumnFamilyAccess(keyspace, column_path.column_family, Permission.MODIFY);
CFMetaData metadata = ThriftValidation.validateColumnFamily(cState.getKeyspace(), column_path.column_family, isCommutativeOp);
CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace, column_path.column_family, isCommutativeOp);
ThriftValidation.validateKey(metadata, key);
ThriftValidation.validateColumnPathOrParent(metadata, column_path);
if (isCommutativeOp)
ThriftConversion.fromThrift(consistency_level).validateCounterForWrite(metadata);
RowMutation rm = new RowMutation(cState.getKeyspace(), key);
RowMutation rm = new RowMutation(keyspace, key);
rm.delete(new QueryPath(column_path), timestamp);
if (isCommutativeOp)
@ -875,20 +880,11 @@ public class CassandraServer implements Cassandra.Iface
public KsDef describe_keyspace(String table) throws NotFoundException, InvalidRequestException
{
try
{
state().hasKeyspaceAccess(table, Permission.DESCRIBE);
KSMetaData ksm = Schema.instance.getTableDefinition(table);
if (ksm == null)
throw new NotFoundException();
KSMetaData ksm = Schema.instance.getTableDefinition(table);
if (ksm == null)
throw new NotFoundException();
return ksm.toThrift();
}
catch (RequestValidationException e)
{
throw ThriftConversion.toThrift(e);
}
return ksm.toThrift();
}
public List<KeySlice> get_range_slices(ColumnParent column_parent, SlicePredicate predicate, KeyRange range, ConsistencyLevel consistency_level)
@ -915,7 +911,7 @@ public class CassandraServer implements Cassandra.Iface
ThriftClientState cState = state();
keyspace = cState.getKeyspace();
cState.hasColumnFamilyAccess(column_parent.column_family, Permission.SELECT);
cState.hasColumnFamilyAccess(keyspace, column_parent.column_family, Permission.SELECT);
metadata = ThriftValidation.validateColumnFamily(keyspace, column_parent.column_family);
ThriftValidation.validateColumnParent(metadata, column_parent);
@ -1002,7 +998,7 @@ public class CassandraServer implements Cassandra.Iface
ThriftClientState cState = state();
String keyspace = cState.getKeyspace();
cState.hasColumnFamilyAccess(column_family, Permission.SELECT);
cState.hasColumnFamilyAccess(keyspace, column_family, Permission.SELECT);
CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace, column_family);
ThriftValidation.validateKeyRange(metadata, null, range);
@ -1101,8 +1097,8 @@ public class CassandraServer implements Cassandra.Iface
try
{
ThriftClientState cState = state();
cState.hasColumnFamilyAccess(column_parent.column_family, Permission.SELECT);
String keyspace = cState.getKeyspace();
cState.hasColumnFamilyAccess(keyspace, column_parent.column_family, Permission.SELECT);
CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace, column_parent.column_family, false);
ThriftValidation.validateColumnParent(metadata, column_parent);
ThriftValidation.validatePredicate(metadata, column_parent, column_predicate);
@ -1151,33 +1147,20 @@ public class CassandraServer implements Cassandra.Iface
public List<KsDef> describe_keyspaces() throws TException, InvalidRequestException
{
try
Set<String> keyspaces = Schema.instance.getTables();
List<KsDef> ksset = new ArrayList<KsDef>(keyspaces.size());
for (String ks : keyspaces)
{
Set<String> keyspaces = Schema.instance.getTables();
List<KsDef> ksset = new ArrayList<KsDef>(keyspaces.size());
for (String ks : keyspaces)
try
{
try
{
state().hasKeyspaceAccess(ks, Permission.DESCRIBE);
ksset.add(describe_keyspace(ks));
}
catch (UnauthorizedException e)
{
if (logger.isDebugEnabled())
logger.debug("PermissionDenied: " + e.getMessage());
}
catch (NotFoundException nfe)
{
logger.info("Failed to find metadata for keyspace '" + ks + "'. Continuing... ");
}
ksset.add(describe_keyspace(ks));
}
catch (NotFoundException nfe)
{
logger.info("Failed to find metadata for keyspace '" + ks + "'. Continuing... ");
}
return ksset;
}
catch (RequestValidationException e)
{
throw ThriftConversion.toThrift(e);
}
return ksset;
}
public String describe_cluster_name() throws TException
@ -1238,7 +1221,6 @@ public class CassandraServer implements Cassandra.Iface
{
try
{
// TODO: add keyspace authorization call post CASSANDRA-1425
Token.TokenFactory tf = StorageService.getPartitioner().getTokenFactory();
Range<Token> tr = new Range<Token>(tf.fromString(start_token), tf.fromString(end_token));
List<Pair<Range<Token>, Long>> splits =
@ -1289,7 +1271,9 @@ public class CassandraServer implements Cassandra.Iface
try
{
state().hasColumnFamilyAccess(cf_def.name, Permission.CREATE);
ClientState cState = state();
String keyspace = cState.getKeyspace();
cState.hasKeyspaceAccess(keyspace, Permission.CREATE);
cf_def.unsetId(); // explicitly ignore any id set by client (Hector likes to set zero)
CFMetaData cfm = CFMetaData.fromThrift(cf_def);
cfm.addDefaultIndexNames();
@ -1311,8 +1295,9 @@ public class CassandraServer implements Cassandra.Iface
try
{
cState.hasColumnFamilyAccess(column_family, Permission.DROP);
MigrationManager.announceColumnFamilyDrop(cState.getKeyspace(), column_family);
String keyspace = cState.getKeyspace();
cState.hasColumnFamilyAccess(keyspace, column_family, Permission.DROP);
MigrationManager.announceColumnFamilyDrop(keyspace, column_family);
return Schema.instance.getVersion().toString();
}
catch (RequestValidationException e)
@ -1329,7 +1314,7 @@ public class CassandraServer implements Cassandra.Iface
try
{
ThriftValidation.validateKeyspaceNotSystem(ks_def.name);
state().hasKeyspaceAccess(ks_def.name, Permission.CREATE);
state().hasAllKeyspacesAccess(Permission.CREATE);
ThriftValidation.validateKeyspaceNotYetExisting(ks_def.name);
// generate a meaningful error if the user setup keyspace and/or column definition incorrectly
@ -1415,7 +1400,7 @@ public class CassandraServer implements Cassandra.Iface
if (oldCfm == null)
throw new InvalidRequestException("Could not find column family definition to modify.");
state().hasColumnFamilyAccess(cf_def.name, Permission.ALTER);
state().hasColumnFamilyAccess(cf_def.keyspace, cf_def.name, Permission.ALTER);
CFMetaData.applyImplicitDefaults(cf_def);
CFMetaData cfm = CFMetaData.fromThrift(cf_def);
@ -1435,11 +1420,12 @@ public class CassandraServer implements Cassandra.Iface
try
{
cState.hasColumnFamilyAccess(cfname, Permission.DELETE);
String keyspace = cState.getKeyspace();
cState.hasColumnFamilyAccess(keyspace, cfname, Permission.MODIFY);
if (startSessionIfRequested())
{
Tracing.instance().begin("truncate", ImmutableMap.of("cf", cfname, "ks", cState.getKeyspace()));
Tracing.instance().begin("truncate", ImmutableMap.of("cf", cfname, "ks", keyspace));
}
else
{
@ -1518,9 +1504,10 @@ public class CassandraServer implements Cassandra.Iface
try
{
ClientState cState = state();
cState.hasColumnFamilyAccess(column_parent.column_family, Permission.UPDATE);
String keyspace = cState.getKeyspace();
cState.hasColumnFamilyAccess(keyspace, column_parent.column_family, Permission.MODIFY);
CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace, column_parent.column_family, true);
ThriftValidation.validateKey(metadata, key);
ThriftConversion.fromThrift(consistency_level).validateCounterForWrite(metadata);

View File

@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# This is a sample access file for SimpleAuthority. The format of
# This is a sample access file for SimpleAuthorizer. The format of
# this file is keyspace=users, where users is a comma delimited list of
# authenticatable users from passwd.properties. This file contains
# potentially sensitive information, keep this in mind when setting its