From 5a3eb1a6c3e61bd217e23e5d1e7c27f03fab4f08 Mon Sep 17 00:00:00 2001 From: Aleksey Yeschenko Date: Tue, 27 Nov 2012 20:04:22 +0300 Subject: [PATCH] Replace IAuthority with new IAuthorizer; patch by Aleksey Yeschenko, reviewed by Jonathan Ellis for CASSANDRA-4874 --- CHANGES.txt | 1 + NEWS.txt | 14 + conf/cassandra.yaml | 4 +- examples/client_only/conf/cassandra.yaml | 4 +- examples/simple_authentication/README.txt | 4 +- .../conf/access.properties | 2 +- ...leAuthority.java => SimpleAuthorizer.java} | 10 +- pylib/cqlshlib/cql3handling.py | 61 ++--- .../cassandra/auth/AllowAllAuthenticator.java | 10 +- .../cassandra/auth/AllowAllAuthorizer.java | 70 +++++ .../cassandra/auth/AuthenticatedUser.java | 22 +- .../apache/cassandra/auth/DataResource.java | 248 ++++++++++++++++++ .../org/apache/cassandra/auth/IAuthority.java | 63 ----- .../apache/cassandra/auth/IAuthority2.java | 70 ----- .../cassandra/auth/IAuthorityContainer.java | 83 ------ .../apache/cassandra/auth/IAuthorizer.java | 122 +++++++++ .../org/apache/cassandra/auth/IResource.java | 53 ++++ .../cassandra/auth/LegacyAuthorizer.java | 111 ++++++++ ...lAuthority.java => MigrationListener.java} | 35 ++- .../org/apache/cassandra/auth/Permission.java | 53 ++-- .../cassandra/auth/PermissionDenied.java | 34 --- .../cassandra/auth/PermissionDetails.java | 70 +++++ .../org/apache/cassandra/auth/Resources.java | 28 +- .../org/apache/cassandra/config/Config.java | 3 +- .../cassandra/config/DatabaseDescriptor.java | 39 ++- .../apache/cassandra/cql/DeleteStatement.java | 2 +- .../cassandra/cql/DropIndexStatement.java | 65 +++-- .../apache/cassandra/cql/QueryProcessor.java | 77 ++---- .../apache/cassandra/cql/UpdateStatement.java | 10 +- src/java/org/apache/cassandra/cql3/Cql.g | 116 ++++---- .../apache/cassandra/cql3/QueryProcessor.java | 2 +- .../statements/AuthorizationStatement.java | 66 +++++ .../cql3/statements/BatchStatement.java | 2 +- .../CreateColumnFamilyStatement.java | 2 +- .../statements/CreateKeyspaceStatement.java | 2 +- .../cql3/statements/DropIndexStatement.java | 79 +++--- .../cql3/statements/GrantStatement.java | 17 +- .../cql3/statements/ListGrantsStatement.java | 42 --- .../statements/ListPermissionsStatement.java | 106 ++++++++ .../statements/ModificationStatement.java | 5 +- .../PermissionAlteringStatement.java | 56 ++-- .../cql3/statements/RevokeStatement.java | 15 +- .../cql3/statements/TruncateStatement.java | 2 +- .../cassandra/service/CassandraDaemon.java | 6 + .../apache/cassandra/service/ClientState.java | 185 ++++++------- .../cassandra/thrift/CassandraServer.java | 119 ++++----- test/conf/access.properties | 2 +- 47 files changed, 1357 insertions(+), 835 deletions(-) rename examples/simple_authentication/src/org/apache/cassandra/auth/{SimpleAuthority.java => SimpleAuthorizer.java} (94%) create mode 100644 src/java/org/apache/cassandra/auth/AllowAllAuthorizer.java create mode 100644 src/java/org/apache/cassandra/auth/DataResource.java delete mode 100644 src/java/org/apache/cassandra/auth/IAuthority.java delete mode 100644 src/java/org/apache/cassandra/auth/IAuthority2.java delete mode 100644 src/java/org/apache/cassandra/auth/IAuthorityContainer.java create mode 100644 src/java/org/apache/cassandra/auth/IAuthorizer.java create mode 100644 src/java/org/apache/cassandra/auth/IResource.java create mode 100644 src/java/org/apache/cassandra/auth/LegacyAuthorizer.java rename src/java/org/apache/cassandra/auth/{AllowAllAuthority.java => MigrationListener.java} (51%) delete mode 100644 src/java/org/apache/cassandra/auth/PermissionDenied.java create mode 100644 src/java/org/apache/cassandra/auth/PermissionDetails.java create mode 100644 src/java/org/apache/cassandra/cql3/statements/AuthorizationStatement.java delete mode 100644 src/java/org/apache/cassandra/cql3/statements/ListGrantsStatement.java create mode 100644 src/java/org/apache/cassandra/cql3/statements/ListPermissionsStatement.java diff --git a/CHANGES.txt b/CHANGES.txt index 9c80e878bd..8bfcc0686c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -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) diff --git a/NEWS.txt b/NEWS.txt index 639e3c915b..bc361c5d18 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -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 diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index 8aaeb3873c..2861c432ad 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -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 diff --git a/examples/client_only/conf/cassandra.yaml b/examples/client_only/conf/cassandra.yaml index 701d1911df..0edc4731cc 100644 --- a/examples/client_only/conf/cassandra.yaml +++ b/examples/client_only/conf/cassandra.yaml @@ -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 diff --git a/examples/simple_authentication/README.txt b/examples/simple_authentication/README.txt index dcf95538d9..f81f5e782d 100644 --- a/examples/simple_authentication/README.txt +++ b/examples/simple_authentication/README.txt @@ -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. diff --git a/examples/simple_authentication/conf/access.properties b/examples/simple_authentication/conf/access.properties index 9cce0dc6b5..f403c2fd09 100644 --- a/examples/simple_authentication/conf/access.properties +++ b/examples/simple_authentication/conf/access.properties @@ -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. diff --git a/examples/simple_authentication/src/org/apache/cassandra/auth/SimpleAuthority.java b/examples/simple_authentication/src/org/apache/cassandra/auth/SimpleAuthorizer.java similarity index 94% rename from examples/simple_authentication/src/org/apache/cassandra/auth/SimpleAuthority.java rename to examples/simple_authentication/src/org/apache/cassandra/auth/SimpleAuthorizer.java index 88814ec5c1..2d7b6442de 100644 --- a/examples/simple_authentication/src/org/apache/cassandra/auth/SimpleAuthority.java +++ b/examples/simple_authentication/src/org/apache/cassandra/auth/SimpleAuthorizer.java @@ -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 authorize(AuthenticatedUser user, List 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 authorized = Permission.NONE; + EnumSet 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); diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py index a775245b3b..1ef9dcb473 100644 --- a/pylib/cqlshlib/cql3handling.py +++ b/pylib/cqlshlib/cql3handling.py @@ -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]|$)|[/][*].*?[*][/])/ ; | ; - ::= cl=( - | - | - | - | - | - | - | ) - ; - # timestamp is included here, since it's also a keyword ::= typename=( | | ) ; @@ -307,15 +295,13 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ; ::= nocomplete= ( - | | # | -- to get count(*) completion, treat count as reserved | | | | - | - | ) + | ) ; # 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''' ::= "ALTER" ( "KEYSPACE" | "SCHEMA" ) ks= "WITH" ( "AND" )* ; +''' - ::= "GRANT" "ON" cf= - "TO" - ( "WITH" "GRANT" "OPTION" )? +syntax_rules += r''' + ::= "GRANT" "ON" "TO" ; - ::= "REVOKE" "ON" cf= - "FROM" + ::= "REVOKE" "ON" "FROM" ; - ::= "LIST" "GRANTS" "FOR" ; + ::= "LIST" + ( "ON" )? ( "OF" )? "NORECURSIVE"? + ; - ::= "DESCRIBE" - | "USE" + ::= "AUTHORIZE" | "CREATE" | "ALTER" | "DROP" | "SELECT" - | "INSERT" - | "UPDATE" - | "DELETE" - | "FULL_ACCESS" - | "NO_ACCESS" + | "MODIFY" ; + ::= ( "PERMISSION"? ) + | ( "ALL" "PERMISSIONS"? ) + ; + + ::= + ; + + ::= ( "ALL" "KEYSPACES" ) + | ( "KEYSPACE" ) + | ( "TABLE"? ) + ; + ::= user=( | ) ; ''' @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('')] # END SYNTAX/COMPLETION RULE DEFINITIONS diff --git a/src/java/org/apache/cassandra/auth/AllowAllAuthenticator.java b/src/java/org/apache/cassandra/auth/AllowAllAuthenticator.java index af88372ba6..cd93dd0dd3 100644 --- a/src/java/org/apache/cassandra/auth/AllowAllAuthenticator.java +++ b/src/java/org/apache/cassandra/auth/AllowAllAuthenticator.java @@ -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 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 diff --git a/src/java/org/apache/cassandra/auth/AllowAllAuthorizer.java b/src/java/org/apache/cassandra/auth/AllowAllAuthorizer.java new file mode 100644 index 0000000000..8b839ea552 --- /dev/null +++ b/src/java/org/apache/cassandra/auth/AllowAllAuthorizer.java @@ -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 authorize(AuthenticatedUser user, IResource resource) + { + return Permission.ALL; + } + + public void validateConfiguration() + { + } + + public void setup() + { + } + + public void grant(AuthenticatedUser performer, Set permissions, IResource resource, String to) + throws InvalidRequestException + { + throw new InvalidRequestException("GRANT operation is not supported by AllowAllAuthorizer"); + } + + public void revoke(AuthenticatedUser performer, Set 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 listPermissions(AuthenticatedUser performer, Set permissions, IResource resource, String of) + throws InvalidRequestException + { + throw new InvalidRequestException("LIST PERMISSIONS operation is not supported by AllowAllAuthorizer"); + } + + public Set protectedResources() + { + return Collections.emptySet(); + } +} diff --git a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java index 091bdc5785..e65446defc 100644 --- a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java +++ b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java @@ -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 groups; + private final boolean isSuperUser; public AuthenticatedUser(String username) { - this.username = username; - this.groups = Collections.emptySet(); + this(username, false); } - public AuthenticatedUser(String username, Set 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("#", username, groups); + return String.format("#", username, isSuperUser); } } diff --git a/src/java/org/apache/cassandra/auth/DataResource.java b/src/java/org/apache/cassandra/auth/DataResource.java new file mode 100644 index 0000000000..3ee40a7905 --- /dev/null +++ b/src/java/org/apache/cassandra/auth/DataResource.java @@ -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 ""; + case KEYSPACE: + return String.format("", keyspace); + case COLUMN_FAMILY: + return String.format("", 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); + } +} diff --git a/src/java/org/apache/cassandra/auth/IAuthority.java b/src/java/org/apache/cassandra/auth/IAuthority.java deleted file mode 100644 index 8b37470c14..0000000000 --- a/src/java/org/apache/cassandra/auth/IAuthority.java +++ /dev/null @@ -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 authorize(AuthenticatedUser user, List resource); - - public void validateConfiguration() throws ConfigurationException; -} diff --git a/src/java/org/apache/cassandra/auth/IAuthority2.java b/src/java/org/apache/cassandra/auth/IAuthority2.java deleted file mode 100644 index 2933f323c4..0000000000 --- a/src/java/org/apache/cassandra/auth/IAuthority2.java +++ /dev/null @@ -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 ON TO [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 ON FROM ; - * - * @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 ; - * 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; -} diff --git a/src/java/org/apache/cassandra/auth/IAuthorityContainer.java b/src/java/org/apache/cassandra/auth/IAuthorityContainer.java deleted file mode 100644 index 4aeb315d47..0000000000 --- a/src/java/org/apache/cassandra/auth/IAuthorityContainer.java +++ /dev/null @@ -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); - } -} diff --git a/src/java/org/apache/cassandra/auth/IAuthorizer.java b/src/java/org/apache/cassandra/auth/IAuthorizer.java new file mode 100644 index 0000000000..916878b120 --- /dev/null +++ b/src/java/org/apache/cassandra/auth/IAuthorizer.java @@ -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 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 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 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 listPermissions(AuthenticatedUser performer, Set 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 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(); +} diff --git a/src/java/org/apache/cassandra/auth/IResource.java b/src/java/org/apache/cassandra/auth/IResource.java new file mode 100644 index 0000000000..635b0a65a9 --- /dev/null +++ b/src/java/org/apache/cassandra/auth/IResource.java @@ -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(); +} diff --git a/src/java/org/apache/cassandra/auth/LegacyAuthorizer.java b/src/java/org/apache/cassandra/auth/LegacyAuthorizer.java new file mode 100644 index 0000000000..99e2ba8f17 --- /dev/null +++ b/src/java/org/apache/cassandra/auth/LegacyAuthorizer.java @@ -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 authorize(AuthenticatedUser user, List resource); + + @Override + public void setup() + { + } + + /** + * Translates new-style authorize() method call to the old-style (including permissions and the hierarchy). + */ + @Override + public Set 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 legacyResource = new ArrayList(); + legacyResource.add(Resources.ROOT); + legacyResource.add(Resources.KEYSPACES); + if (!dr.isRootLevel()) + legacyResource.add(dr.getKeyspace()); + if (dr.isColumnFamilyLevel()) + legacyResource.add(dr.getColumnFamily()); + + Set 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 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 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 listPermissions(AuthenticatedUser performer, Set permissions, IResource resource, String of) + throws InvalidRequestException, UnauthorizedException + { + throw new InvalidRequestException("LIST PERMISSIONS operation is not supported by LegacyAuthorizer"); + } + + @Override + public Set protectedResources() + { + return Collections.emptySet(); + } +} diff --git a/src/java/org/apache/cassandra/auth/AllowAllAuthority.java b/src/java/org/apache/cassandra/auth/MigrationListener.java similarity index 51% rename from src/java/org/apache/cassandra/auth/AllowAllAuthority.java rename to src/java/org/apache/cassandra/auth/MigrationListener.java index 5a6f86dca9..66ac4b032b 100644 --- a/src/java/org/apache/cassandra/auth/AllowAllAuthority.java +++ b/src/java/org/apache/cassandra/auth/MigrationListener.java @@ -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 authorize(AuthenticatedUser user, List 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 } } diff --git a/src/java/org/apache/cassandra/auth/Permission.java b/src/java/org/apache/cassandra/auth/Permission.java index 69e2d90e0c..a982c859cc 100644 --- a/src/java/org/apache/cassandra/auth/Permission.java +++ b/src/java/org/apache/cassandra/auth/Permission.java @@ -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 ALL = EnumSet.allOf(Permission.class); - public static final EnumSet NONE = EnumSet.noneOf(Permission.class); - public static final EnumSet GRANULAR_PERMISSIONS = EnumSet.range(FULL_ACCESS, SELECT); - public static final EnumSet 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> oldToNew = new HashMap>(2) - {{ - put(READ, EnumSet.of(DESCRIBE, SELECT)); - put(WRITE, EnumSet.range(DESCRIBE, DELETE)); - }}; + + public static final Set ALL_DATA = + ImmutableSet.copyOf(EnumSet.range(Permission.CREATE, Permission.AUTHORIZE)); + + public static final Set ALL = + ImmutableSet.copyOf(EnumSet.range(Permission.CREATE, Permission.AUTHORIZE)); + public static final Set NONE = ImmutableSet.of(); } diff --git a/src/java/org/apache/cassandra/auth/PermissionDenied.java b/src/java/org/apache/cassandra/auth/PermissionDenied.java deleted file mode 100644 index 22bf35abd3..0000000000 --- a/src/java/org/apache/cassandra/auth/PermissionDenied.java +++ /dev/null @@ -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; - } -} diff --git a/src/java/org/apache/cassandra/auth/PermissionDetails.java b/src/java/org/apache/cassandra/auth/PermissionDetails.java new file mode 100644 index 0000000000..52a8712c31 --- /dev/null +++ b/src/java/org/apache/cassandra/auth/PermissionDetails.java @@ -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 +{ + 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("", + 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); + } +} diff --git a/src/java/org/apache/cassandra/auth/Resources.java b/src/java/org/apache/cassandra/auth/Resources.java index 26d2abf62f..86900f9bfa 100644 --- a/src/java/org/apache/cassandra/auth/Resources.java +++ b/src/java/org/apache/cassandra/auth/Resources.java @@ -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 chain(IResource resource) + { + List chain = new ArrayList(); + 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 resource) { StringBuilder buff = new StringBuilder(); diff --git a/src/java/org/apache/cassandra/config/Config.java b/src/java/org/apache/cassandra/config/Config.java index 639569060a..24edf088f6 100644 --- a/src/java/org/apache/cassandra/config/Config.java +++ b/src/java/org/apache/cassandra/config/Config.java @@ -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; diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java index bb3e7c16d3..41eb437e00 100644 --- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java +++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java @@ -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.construct(conf.authenticator, "authenticator"); - if (conf.authority != null) - authority = FBUtilities.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.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() diff --git a/src/java/org/apache/cassandra/cql/DeleteStatement.java b/src/java/org/apache/cassandra/cql/DeleteStatement.java index 4190c80d83..87f48aa562 100644 --- a/src/java/org/apache/cassandra/cql/DeleteStatement.java +++ b/src/java/org/apache/cassandra/cql/DeleteStatement.java @@ -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 rowMutations = new ArrayList(keys.size()); diff --git a/src/java/org/apache/cassandra/cql/DropIndexStatement.java b/src/java/org/apache/cassandra/cql/DropIndexStatement.java index 7df1f512e2..5114a41603 100644 --- a/src/java/org/apache/cassandra/cql/DropIndexStatement.java +++ b/src/java/org/apache/cassandra/cql/DropIndexStatement.java @@ -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; } } diff --git a/src/java/org/apache/cassandra/cql/QueryProcessor.java b/src/java/org/apache/cassandra/cql/QueryProcessor.java index 628b94dec6..ec07761a23 100644 --- a/src/java/org/apache/cassandra/cql/QueryProcessor.java +++ b/src/java/org/apache/cassandra/cql/QueryProcessor.java @@ -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 updateStatements, ConsistencyLevel consistency, List variables ) - throws RequestValidationException, RequestExecutionException - { - String globalKeyspace = clientState.getKeyspace(); - List rowMutations = new ArrayList(updateStatements.size()); - List cfamsSeen = new ArrayList(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 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 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 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 { diff --git a/src/java/org/apache/cassandra/cql/UpdateStatement.java b/src/java/org/apache/cassandra/cql/UpdateStatement.java index 69daea640c..3470bca1f7 100644 --- a/src/java/org/apache/cassandra/cql/UpdateStatement.java +++ b/src/java/org/apache/cassandra/cql/UpdateStatement.java @@ -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 prepareRowMutations(String keyspace, ThriftClientState clientState, Long timestamp, List variables) throws InvalidRequestException, UnauthorizedException { - List cfamsSeen = new ArrayList(); - boolean hasCommutativeOperation = false; for (Map.Entry 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 rowMutations = new LinkedList(); diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g index d81443450d..493411b37c 100644 --- a/src/java/org/apache/cassandra/cql3/Cql.g +++ b/src/java/org/apache/cassandra/cql3/Cql.g @@ -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 * FROM - * USING CONSISTENCY * WHERE KEY = "key1" AND COL > 1 AND COL < 100 * LIMIT ; */ @@ -277,7 +280,7 @@ usingClauseObjective[Attributes attrs] /** * UPDATE - * USING CONSISTENCY AND TIMESTAMP + * USING TIMESTAMP * SET name1 = value1, name2 = value2 * WHERE key = value; */ @@ -298,7 +301,7 @@ updateStatement returns [UpdateStatement expr] /** * DELETE name1, name2 * FROM - * USING CONSISTENCY AND TIMESTAMP + * USING TIMESTAMP * WHERE KEY = keyname; */ deleteStatement returns [DeleteStatement expr] @@ -325,7 +328,7 @@ deleteSelector returns [Selector s] ; /** - * BEGIN BATCH [USING CONSISTENCY ] + * BEGIN BATCH * UPDATE SET name1 = value1 WHERE KEY = keyname1; * UPDATE SET name2 = value2 WHERE KEY = keyname2; * UPDATE SET name3 = value3 WHERE KEY = keyname3; @@ -334,7 +337,7 @@ deleteSelector returns [Selector s] * * OR * - * BEGIN BATCH [USING CONSISTENCY ] + * BEGIN BATCH * INSERT INTO (KEY, ) VALUES ('', ''); * INSERT INTO (KEY, ) VALUES ('', ''); * ... @@ -342,7 +345,7 @@ deleteSelector returns [Selector s] * * OR * - * BEGIN BATCH [USING CONSISTENCY ] + * BEGIN BATCH * DELETE name1, name2 FROM WHERE key = * DELETE name3, name4 FROM WHERE key = * ... @@ -493,23 +496,16 @@ truncateStatement returns [TruncateStatement stmt] ; /** - * GRANT ON TO [WITH GRANT OPTION] + * GRANT ON TO */ 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 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; diff --git a/src/java/org/apache/cassandra/cql3/QueryProcessor.java b/src/java/org/apache/cassandra/cql3/QueryProcessor.java index 93990f62fc..1d4ddfa2c9 100644 --- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java +++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java @@ -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; } diff --git a/src/java/org/apache/cassandra/cql3/statements/AuthorizationStatement.java b/src/java/org/apache/cassandra/cql3/statements/AuthorizationStatement.java new file mode 100644 index 0000000000..fefcd95393 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/statements/AuthorizationStatement.java @@ -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 variables) throws UnauthorizedException, InvalidRequestException + { + return execute(state.getClientState(), variables); + } + + public abstract ResultMessage execute(ClientState state, List 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; + } +} diff --git a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java index ae94f27664..3909159516 100644 --- a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java @@ -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()); } } diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateColumnFamilyStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateColumnFamilyStatement.java index 3e40cd6a81..c45c3b115c 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateColumnFamilyStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateColumnFamilyStatement.java @@ -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 diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java index 2238a20421..0a12f974e2 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java @@ -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); } /** diff --git a/src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java index d7f966ce3f..827ba3a3a2 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java @@ -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; + } } diff --git a/src/java/org/apache/cassandra/cql3/statements/GrantStatement.java b/src/java/org/apache/cassandra/cql3/statements/GrantStatement.java index 45d7d2acfe..1264def5f2 100644 --- a/src/java/org/apache/cassandra/cql3/statements/GrantStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/GrantStatement.java @@ -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 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 variables) throws UnauthorizedException, InvalidRequestException { - state.grantPermission(permission, username, resource, grantOption); + state.grantPermission(permissions, resource, username); return null; } } diff --git a/src/java/org/apache/cassandra/cql3/statements/ListGrantsStatement.java b/src/java/org/apache/cassandra/cql3/statements/ListGrantsStatement.java deleted file mode 100644 index ecc312f309..0000000000 --- a/src/java/org/apache/cassandra/cql3/statements/ListGrantsStatement.java +++ /dev/null @@ -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 variables) throws UnauthorizedException, InvalidRequestException - { - return state.listPermissions(username); - } -} diff --git a/src/java/org/apache/cassandra/cql3/statements/ListPermissionsStatement.java b/src/java/org/apache/cassandra/cql3/statements/ListPermissionsStatement.java new file mode 100644 index 0000000000..7b5d34ae00 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/statements/ListPermissionsStatement.java @@ -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 metadata; + + static + { + List columns = new ArrayList(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 permissions; + private DataResource resource; + private final String username; + private final boolean recursive; + + public ListPermissionsStatement(Set 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 variables) throws UnauthorizedException, InvalidRequestException + { + List details = new ArrayList(); + + 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 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); + } +} diff --git a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java index a9ddb14807..77bf83d890 100644 --- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java @@ -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 diff --git a/src/java/org/apache/cassandra/cql3/statements/PermissionAlteringStatement.java b/src/java/org/apache/cassandra/cql3/statements/PermissionAlteringStatement.java index dd6e190540..6e7af77a50 100644 --- a/src/java/org/apache/cassandra/cql3/statements/PermissionAlteringStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/PermissionAlteringStatement.java @@ -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 permissions; + protected DataResource resource; + protected final String username; + + protected PermissionAlteringStatement(Set 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 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 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
, we need to correct the resource. + resource = maybeCorrectResource(resource, state); + if (!resource.exists()) + throw new InvalidRequestException(String.format("%s doesn't exist", resource)); } } diff --git a/src/java/org/apache/cassandra/cql3/statements/RevokeStatement.java b/src/java/org/apache/cassandra/cql3/statements/RevokeStatement.java index 68c0514966..23d7d55fdd 100644 --- a/src/java/org/apache/cassandra/cql3/statements/RevokeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/RevokeStatement.java @@ -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 permissions, IResource resource, String username) { - this.permission = permission; - this.from = from; - this.resource = resource; + super(permissions, resource, username); } public ResultMessage execute(ClientState state, List variables) throws UnauthorizedException, InvalidRequestException { - state.revokePermission(permission, from, resource); + state.revokePermission(permissions, resource, username); return null; } } diff --git a/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java b/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java index 4cd0e9b09a..16445f57d2 100644 --- a/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java @@ -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 diff --git a/src/java/org/apache/cassandra/service/CassandraDaemon.java b/src/java/org/apache/cassandra/service/CassandraDaemon.java index 687ddaa1cb..1d66d9a4b0 100644 --- a/src/java/org/apache/cassandra/service/CassandraDaemon.java +++ b/src/java/org/apache/cassandra/service/CassandraDaemon.java @@ -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()) { diff --git a/src/java/org/apache/cassandra/service/ClientState.java b/src/java/org/apache/cassandra/service/ClientState.java index c326d9caaa..97d25dc1dc 100644 --- a/src/java/org/apache/cassandra/service/ClientState.java +++ b/src/java/org/apache/cassandra/service/ClientState.java @@ -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 READABLE_SYSTEM_RESOURCES = new HashSet(5); + private static final Set PROTECTED_AUTH_RESOURCES = new HashSet(); + + 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 resource = new ArrayList(); 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 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 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 perms, Permission perm, List 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 authorize(IResource resource) { - DatabaseDescriptor.getAuthorityContainer().grant(user, permission, to, on, grantOption); + return DatabaseDescriptor.getAuthorizer().authorize(user, resource); + + } + public void grantPermission(Set 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 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 listPermissions(Set permissions, IResource resource, String of) + throws UnauthorizedException, InvalidRequestException { - return DatabaseDescriptor.getAuthorityContainer().listPermissions(username); + return DatabaseDescriptor.getAuthorizer().listPermissions(user, permissions, resource, of); } } diff --git a/src/java/org/apache/cassandra/thrift/CassandraServer.java b/src/java/org/apache/cassandra/thrift/CassandraServer.java index 9cebacb323..5f05057f7f 100644 --- a/src/java/org/apache/cassandra/thrift/CassandraServer.java +++ b/src/java/org/apache/cassandra/thrift/CassandraServer.java @@ -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 counts = new HashMap(); Map> 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 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 describe_keyspaces() throws TException, InvalidRequestException { - try + Set keyspaces = Schema.instance.getTables(); + List ksset = new ArrayList(keyspaces.size()); + for (String ks : keyspaces) { - Set keyspaces = Schema.instance.getTables(); - List ksset = new ArrayList(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 tr = new Range(tf.fromString(start_token), tf.fromString(end_token)); List, 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); diff --git a/test/conf/access.properties b/test/conf/access.properties index ef6c8c43fe..f7c5e07a96 100644 --- a/test/conf/access.properties +++ b/test/conf/access.properties @@ -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