new IAuthenticator interface. patch by stuhood, reviewed by gdusbabek. CASSANDRA-1237

git-svn-id: https://svn.apache.org/repos/asf/cassandra/trunk@980222 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Gary Dusbabek 2010-07-28 21:01:57 +00:00
parent 7b9f69ad18
commit 3f9f50725b
6 changed files with 129 additions and 121 deletions

View File

@ -1,22 +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.
#
# This is a sample access file for SimpleAuthenticator. 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
# mode and ownership.
Keyspace1=jsmith,Elvis Presley,dilbert

View File

@ -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 AccessLevel login(String keyspace, AuthenticationRequest authRequest) throws AuthenticationException, AuthorizationException
public AuthenticatedUser defaultUser()
{
// do nothing, allow anything
return AccessLevel.FULL;
return USER;
}
@Override
public AuthenticatedUser login(Map<String,String> credentials) throws AuthenticationException, AuthorizationException
{
return USER;
}
@Override
@Override
public void validateConfiguration() throws ConfigurationException
{
{
// do nothing, no configuration to validate
}
}

View File

@ -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<String> 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<String> 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<String,AccessLevel> usersAccess, Map<String,AccessLevel> 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 "#<User %s groups=%s>".format(username, groups);
}
}

View File

@ -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<String,String> credentials) throws AuthenticationException, AuthorizationException;
public void validateConfiguration() throws ConfigurationException;
}

View File

@ -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 implements IAuthenticator
};
@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<String,String> credentials) throws AuthenticationException, AuthorizationException
{
String pmode_plain = System.getProperty(PMODE_PROPERTY);
PasswordMode mode = PasswordMode.PLAIN;
@ -71,10 +76,10 @@ public class SimpleAuthenticator implements IAuthenticator
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 implements IAuthenticator
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 implements IAuthenticator
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);

View File

@ -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 Cassandra.Iface
private final static List<Column> EMPTY_SUBCOLUMNS = Collections.emptyList();
// will be set only by login()
private ThreadLocal<AccessLevel> loginDone = new ThreadLocal<AccessLevel>()
{
private ThreadLocal<AuthenticatedUser> loginDone = new ThreadLocal<AuthenticatedUser>() {
@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 Cassandra.Iface
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 Cassandra.Iface
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 Cassandra.Iface
// 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 Cassandra.Iface
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);
}