Return-Path: Delivered-To: apmail-cassandra-commits-archive@www.apache.org Received: (qmail 94447 invoked from network); 28 Jul 2010 21:03:15 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 28 Jul 2010 21:03:15 -0000 Received: (qmail 38650 invoked by uid 500); 28 Jul 2010 21:03:15 -0000 Delivered-To: apmail-cassandra-commits-archive@cassandra.apache.org Received: (qmail 38624 invoked by uid 500); 28 Jul 2010 21:03:15 -0000 Mailing-List: contact commits-help@cassandra.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cassandra.apache.org Delivered-To: mailing list commits@cassandra.apache.org Received: (qmail 38616 invoked by uid 99); 28 Jul 2010 21:03:15 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 28 Jul 2010 21:03:15 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 28 Jul 2010 21:03:13 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id A6EDA2388ABB; Wed, 28 Jul 2010 21:01:57 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r980222 - in /cassandra/trunk: conf/ src/java/org/apache/cassandra/auth/ src/java/org/apache/cassandra/thrift/ Date: Wed, 28 Jul 2010 21:01:57 -0000 To: commits@cassandra.apache.org From: gdusbabek@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20100728210157.A6EDA2388ABB@eris.apache.org> Author: gdusbabek Date: Wed Jul 28 21:01:57 2010 New Revision: 980222 URL: http://svn.apache.org/viewvc?rev=980222&view=rev Log: new IAuthenticator interface. patch by stuhood, reviewed by gdusbabek. CASSANDRA-1237 Added: cassandra/trunk/src/java/org/apache/cassandra/auth/AuthenticatedUser.java Removed: cassandra/trunk/conf/access.properties Modified: cassandra/trunk/src/java/org/apache/cassandra/auth/AllowAllAuthenticator.java cassandra/trunk/src/java/org/apache/cassandra/auth/IAuthenticator.java cassandra/trunk/src/java/org/apache/cassandra/auth/SimpleAuthenticator.java cassandra/trunk/src/java/org/apache/cassandra/thrift/CassandraServer.java Modified: cassandra/trunk/src/java/org/apache/cassandra/auth/AllowAllAuthenticator.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/auth/AllowAllAuthenticator.java?rev=980222&r1=980221&r2=980222&view=diff ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/auth/AllowAllAuthenticator.java (original) +++ cassandra/trunk/src/java/org/apache/cassandra/auth/AllowAllAuthenticator.java Wed Jul 28 21:01:57 2010 @@ -20,25 +20,31 @@ package org.apache.cassandra.auth; * */ +import java.util.Map; import org.apache.cassandra.config.ConfigurationException; -import org.apache.cassandra.thrift.AccessLevel; import org.apache.cassandra.thrift.AuthenticationException; -import org.apache.cassandra.thrift.AuthenticationRequest; import org.apache.cassandra.thrift.AuthorizationException; public class AllowAllAuthenticator implements IAuthenticator { + private final static AuthenticatedUser USER = new AuthenticatedUser("allow_all", true); + + @Override + public AuthenticatedUser defaultUser() + { + return USER; + } + @Override - public AccessLevel login(String keyspace, AuthenticationRequest authRequest) throws AuthenticationException, AuthorizationException + public AuthenticatedUser login(Map credentials) throws AuthenticationException, AuthorizationException { - // do nothing, allow anything - return AccessLevel.FULL; + return USER; } - @Override + @Override public void validateConfiguration() throws ConfigurationException - { + { // do nothing, no configuration to validate } } Added: cassandra/trunk/src/java/org/apache/cassandra/auth/AuthenticatedUser.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/auth/AuthenticatedUser.java?rev=980222&view=auto ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/auth/AuthenticatedUser.java (added) +++ cassandra/trunk/src/java/org/apache/cassandra/auth/AuthenticatedUser.java Wed Jul 28 21:01:57 2010 @@ -0,0 +1,76 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.cassandra.auth; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import org.apache.cassandra.avro.AccessLevel; + +/** + * An authenticated user and her groups. + */ +public class AuthenticatedUser +{ + public final String username; + public final Set groups; + public final boolean isSuper; + + public AuthenticatedUser(String username, boolean isSuper) + { + this.username = username; + this.groups = Collections.emptySet(); + this.isSuper = isSuper; + } + + public AuthenticatedUser(String username, Set groups, boolean isSuper) + { + this.username = username; + this.groups = Collections.unmodifiableSet(groups); + this.isSuper = isSuper; + } + + /** + * @return The access level granted to the user by the given access maps. + */ + public AccessLevel levelFor(Map usersAccess, Map groupsAccess) + { + // determine the maximum access level for this user and groups + AccessLevel level = usersAccess.get(username); + if (level == null) + level = AccessLevel.NONE; + for (String group : groups) + { + AccessLevel forGroup = groupsAccess.get(group); + if (forGroup != null && forGroup.ordinal() > level.ordinal()) + level = forGroup; + } + return level; + } + + @Override + public String toString() + { + return "#".format(username, groups); + } +} Modified: cassandra/trunk/src/java/org/apache/cassandra/auth/IAuthenticator.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/auth/IAuthenticator.java?rev=980222&r1=980221&r2=980222&view=diff ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/auth/IAuthenticator.java (original) +++ cassandra/trunk/src/java/org/apache/cassandra/auth/IAuthenticator.java Wed Jul 28 21:01:57 2010 @@ -20,16 +20,20 @@ package org.apache.cassandra.auth; * */ +import java.util.Map; import org.apache.cassandra.config.ConfigurationException; -import org.apache.cassandra.thrift.AccessLevel; import org.apache.cassandra.thrift.AuthenticationException; -import org.apache.cassandra.thrift.AuthenticationRequest; import org.apache.cassandra.thrift.AuthorizationException; public interface IAuthenticator { - public AccessLevel login(String keyspace, AuthenticationRequest auth_request) throws AuthenticationException, AuthorizationException; + /** + * @return The user that a connection is initialized with, or 'null' if a user must call login(). + */ + public AuthenticatedUser defaultUser(); + + public AuthenticatedUser login(Map credentials) throws AuthenticationException, AuthorizationException; public void validateConfiguration() throws ConfigurationException; } Modified: cassandra/trunk/src/java/org/apache/cassandra/auth/SimpleAuthenticator.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/auth/SimpleAuthenticator.java?rev=980222&r1=980221&r2=980222&view=diff ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/auth/SimpleAuthenticator.java (original) +++ cassandra/trunk/src/java/org/apache/cassandra/auth/SimpleAuthenticator.java Wed Jul 28 21:01:57 2010 @@ -25,17 +25,15 @@ import java.io.*; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Properties; +import java.util.Map; import org.apache.cassandra.config.ConfigurationException; -import org.apache.cassandra.thrift.AccessLevel; import org.apache.cassandra.thrift.AuthenticationException; -import org.apache.cassandra.thrift.AuthenticationRequest; import org.apache.cassandra.thrift.AuthorizationException; public class SimpleAuthenticator implements IAuthenticator { public final static String PASSWD_FILENAME_PROPERTY = "passwd.properties"; - public final static String ACCESS_FILENAME_PROPERTY = "access.properties"; public final static String PMODE_PROPERTY = "passwd.mode"; public static final String USERNAME_KEY = "username"; public static final String PASSWORD_KEY = "password"; @@ -46,7 +44,14 @@ public class SimpleAuthenticator impleme }; @Override - public AccessLevel login(String keyspace, AuthenticationRequest authRequest) throws AuthenticationException, AuthorizationException + public AuthenticatedUser defaultUser() + { + // users must log in + return null; + } + + @Override + public AuthenticatedUser login(Map credentials) throws AuthenticationException, AuthorizationException { String pmode_plain = System.getProperty(PMODE_PROPERTY); PasswordMode mode = PasswordMode.PLAIN; @@ -71,10 +76,10 @@ public class SimpleAuthenticator impleme String pfilename = System.getProperty(PASSWD_FILENAME_PROPERTY); - String username = authRequest.getCredentials().get(USERNAME_KEY); + String username = credentials.get(USERNAME_KEY); if (null == username) throw new AuthenticationException("Authentication request was missing the required key '" + USERNAME_KEY + "'"); - String password = authRequest.getCredentials().get(PASSWORD_KEY); + String password = credentials.get(PASSWORD_KEY); if (null == password) throw new AuthenticationException("Authentication request was missing the required key '" + PASSWORD_KEY + "'"); boolean authenticated = false; @@ -96,15 +101,13 @@ public class SimpleAuthenticator impleme case MD5: authenticated = MessageDigest.isEqual(password.getBytes(), MessageDigest.getInstance("MD5").digest(props.getProperty(username).getBytes())); break; + default: + throw new RuntimeException("Unknown PasswordMode " + mode); } } catch (NoSuchAlgorithmException e) { - throw new AuthenticationException("You requested MD5 checking but the MD5 digest algorithm is not available: " + e.getMessage()); - } - catch (FileNotFoundException e) - { - throw new RuntimeException("Authentication table file given by property " + PASSWD_FILENAME_PROPERTY + " could not be found: " + e.getMessage()); + throw new RuntimeException("You requested MD5 checking but the MD5 digest algorithm is not available: " + e.getMessage()); } catch (IOException e) { @@ -117,63 +120,21 @@ public class SimpleAuthenticator impleme if (!authenticated) throw new AuthenticationException(authenticationErrorMessage(mode, username)); - // if we're here, the authentication succeeded. Now let's see if the user is authorized for this keyspace. - - String afilename = System.getProperty(ACCESS_FILENAME_PROPERTY); - AccessLevel authorized = AccessLevel.NONE; - try - { - FileInputStream in = new FileInputStream(afilename); - Properties props = new Properties(); - props.load(in); - in.close(); - - // structure: - // given keyspace X, users A B and C can be authorized like this (separate their names with spaces): - // X = A B C - - // note we keep the message here and for other authorization problems exactly the same to prevent attackers - // from guessing what keyspaces are valid - if (null == props.getProperty(keyspace)) - throw new AuthorizationException(authorizationErrorMessage(keyspace, username)); - - for (String allow : props.getProperty(keyspace).split(",")) - { - if (allow.equals(username)) authorized = AccessLevel.FULL; - } - } - catch (FileNotFoundException e) - { - throw new RuntimeException("Authorization table file given by property " + ACCESS_FILENAME_PROPERTY + " could not be found: " + e.getMessage()); - } - catch (IOException e) - { - throw new RuntimeException("Authorization table file given by property " + ACCESS_FILENAME_PROPERTY + " could not be opened: " + e.getMessage()); - } - - if (authorized == AccessLevel.NONE) throw new AuthorizationException(authorizationErrorMessage(keyspace, username)); - - return authorized; + // TODO: Should allow/require a user to configure a 'super' username. + return new AuthenticatedUser(username, false); } - @Override + @Override public void validateConfiguration() throws ConfigurationException { - String aFileName = System.getProperty(SimpleAuthenticator.ACCESS_FILENAME_PROPERTY); String pfilename = System.getProperty(SimpleAuthenticator.PASSWD_FILENAME_PROPERTY); - if (aFileName == null || pfilename == null) + if (pfilename == null) { throw new ConfigurationException("When using " + this.getClass().getCanonicalName() + " " + - SimpleAuthenticator.ACCESS_FILENAME_PROPERTY + " and " + SimpleAuthenticator.PASSWD_FILENAME_PROPERTY + " properties must be defined."); } } - static String authorizationErrorMessage(String keyspace, String username) - { - return String.format("User %s could not be authorized to use keyspace %s", username, keyspace); - } - static String authenticationErrorMessage(PasswordMode mode, String username) { return String.format("Given password in password mode %s could not be validated for user %s", mode, username); Modified: cassandra/trunk/src/java/org/apache/cassandra/thrift/CassandraServer.java URL: http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/thrift/CassandraServer.java?rev=980222&r1=980221&r2=980222&view=diff ============================================================================== --- cassandra/trunk/src/java/org/apache/cassandra/thrift/CassandraServer.java (original) +++ cassandra/trunk/src/java/org/apache/cassandra/thrift/CassandraServer.java Wed Jul 28 21:01:57 2010 @@ -30,6 +30,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.auth.AllowAllAuthenticator; +import org.apache.cassandra.auth.AuthenticatedUser; import org.apache.cassandra.concurrent.StageManager; import org.apache.cassandra.config.ConfigurationException; import org.apache.cassandra.config.KSMetaData; @@ -66,14 +67,14 @@ public class CassandraServer implements private final static List EMPTY_SUBCOLUMNS = Collections.emptyList(); // will be set only by login() - private ThreadLocal loginDone = new ThreadLocal() - { + private ThreadLocal loginDone = new ThreadLocal() { @Override - protected AccessLevel initialValue() + public AuthenticatedUser initialValue() { - return AccessLevel.NONE; + return DatabaseDescriptor.getAuthenticator().defaultUser(); } }; + /* * Keyspace associated with session */ @@ -706,22 +707,14 @@ public class CassandraServer implements return splits; } - public AccessLevel login(AuthenticationRequest auth_request) throws AuthenticationException, AuthorizationException, TException + public void login(AuthenticationRequest auth_request) throws AuthenticationException, AuthorizationException, TException { - AccessLevel level; - - if (keySpace.get() == null) - { - throw new AuthenticationException("You have not set a specific keyspace; please call set_keyspace first"); - } - - level = DatabaseDescriptor.getAuthenticator().login(keySpace.get(), auth_request); + AuthenticatedUser user = DatabaseDescriptor.getAuthenticator().login(auth_request.getCredentials()); if (logger.isDebugEnabled()) - logger.debug("login confirmed; new access level is " + level); + logger.debug("login confirmed; user is " + user); - loginDone.set(level); - return level; + loginDone.set(user); } public void logout() @@ -735,18 +728,11 @@ public class CassandraServer implements protected void checkKeyspaceAndLoginAuthorized(AccessLevel level) throws InvalidRequestException { - if (keySpace.get() == null) - { - throw new InvalidRequestException("You have not assigned a keyspace; please use set_keyspace (and login if necessary)"); - } - - if (!(DatabaseDescriptor.getAuthenticator() instanceof AllowAllAuthenticator)) - { - if (loginDone.get() == null) - throw new InvalidRequestException("You have not logged into keyspace " + keySpace.get()); - if (loginDone.get().getValue() < level.getValue()) - throw new InvalidRequestException("Your credentials are not sufficient to perform " + level + " operations"); - } + if (loginDone.get() == null) + throw new InvalidRequestException("You have not logged in"); + + // FIXME: if no keyspace set, check against global authlist. otherwise, check + // against keyspace authlist } /** @@ -877,6 +863,7 @@ public class CassandraServer implements // IAuthenticator was devised prior to, and without thought for, dynamic keyspace creation. As // a result, we must choose between letting anyone/everyone create keyspaces (which they likely // won't even be able to use), or be honest and disallow it entirely if configured for auth. + // See CASSANDRA-1271 for a proposed solution. if (!(DatabaseDescriptor.getAuthenticator() instanceof AllowAllAuthenticator)) throw new InvalidRequestException("Unable to create new keyspace while authentication is enabled."); @@ -1034,10 +1021,6 @@ public class CassandraServer implements throw new InvalidRequestException("Keyspace does not exist"); } - // If switching, invalidate previous access level; force a new login. - if (keySpace.get() != null && !keySpace.get().equals(keyspace)) - loginDone.set(AccessLevel.NONE); - keySpace.set(keyspace); requestSchedulerId.set(keyspace); }