Ticket #269: Continuing work, more work on UserMapping and UserDirectory

This commit is contained in:
James Muehlner
2013-02-11 23:59:53 -08:00
parent 9cea60d8e6
commit 6213cf490b
9 changed files with 385 additions and 20 deletions

View File

@@ -94,6 +94,17 @@
<version>3.2</version> <version>3.2</version>
</dependency> </dependency>
<dependency>
<groupId>com.google.collections</groupId>
<artifactId>google-collections</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>net.sourceforge.guacamole</groupId>
<artifactId>guacamole-auth-mysql</artifactId>
<version>0.8.0</version>
<type>jar</type>
</dependency>
</dependencies> </dependencies>
<repositories> <repositories>

View File

@@ -52,6 +52,10 @@ import net.sourceforge.guacamole.net.auth.mysql.dao.guacamole.SystemPermissionMa
import net.sourceforge.guacamole.net.auth.mysql.dao.guacamole.UserMapper; import net.sourceforge.guacamole.net.auth.mysql.dao.guacamole.UserMapper;
import net.sourceforge.guacamole.net.auth.mysql.dao.guacamole.UserPermissionMapper; import net.sourceforge.guacamole.net.auth.mysql.dao.guacamole.UserPermissionMapper;
import net.sourceforge.guacamole.net.auth.mysql.properties.MySQLGuacamoleProperties; import net.sourceforge.guacamole.net.auth.mysql.properties.MySQLGuacamoleProperties;
import net.sourceforge.guacamole.net.auth.mysql.utility.PasswordEncryptionUtility;
import net.sourceforge.guacamole.net.auth.mysql.utility.SaltUtility;
import net.sourceforge.guacamole.net.auth.mysql.utility.SecureRandomSaltUtility;
import net.sourceforge.guacamole.net.auth.mysql.utility.Sha256PasswordEncryptionUtility;
import net.sourceforge.guacamole.properties.GuacamoleProperties; import net.sourceforge.guacamole.properties.GuacamoleProperties;
import org.mybatis.guice.MyBatisModule; import org.mybatis.guice.MyBatisModule;
import org.mybatis.guice.datasource.builtin.PooledDataSourceProvider; import org.mybatis.guice.datasource.builtin.PooledDataSourceProvider;
@@ -105,6 +109,9 @@ public class MySQLAuthenticationProvider implements AuthenticationProvider {
addMapperClass(UserMapper.class); addMapperClass(UserMapper.class);
addMapperClass(UserPermissionMapper.class); addMapperClass(UserPermissionMapper.class);
bind(MySQLUserContext.class); bind(MySQLUserContext.class);
bind(MySQLUser.class);
bind(SaltUtility.class).to(SecureRandomSaltUtility.class);
bind(PasswordEncryptionUtility.class).to(Sha256PasswordEncryptionUtility.class);
} }
} }
); );

View File

@@ -35,65 +35,116 @@
* ***** END LICENSE BLOCK ***** */ * ***** END LICENSE BLOCK ***** */
package net.sourceforge.guacamole.net.auth.mysql; package net.sourceforge.guacamole.net.auth.mysql;
import com.google.inject.Inject;
import java.io.UnsupportedEncodingException;
import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import net.sourceforge.guacamole.GuacamoleException; import net.sourceforge.guacamole.GuacamoleException;
import net.sourceforge.guacamole.net.auth.Credentials; import net.sourceforge.guacamole.net.auth.Credentials;
import net.sourceforge.guacamole.net.auth.User; import net.sourceforge.guacamole.net.auth.User;
import net.sourceforge.guacamole.net.auth.mysql.dao.guacamole.UserMapper;
import net.sourceforge.guacamole.net.auth.mysql.model.guacamole.UserExample;
import net.sourceforge.guacamole.net.auth.mysql.utility.PasswordEncryptionUtility;
import net.sourceforge.guacamole.net.auth.mysql.utility.SaltUtility;
import net.sourceforge.guacamole.net.auth.permission.Permission; import net.sourceforge.guacamole.net.auth.permission.Permission;
/** /**
* * A MySQL based implementation of the User object.
* @author James Muehlner * @author James Muehlner
*/ */
public class MySQLUser implements User { public class MySQLUser implements User {
private String username; private net.sourceforge.guacamole.net.auth.mysql.model.guacamole.User user;
private String userID;
private String salt;
MySQLUser(Credentials credentials) { @Inject
//TODO: load the user from the DB if the credentials are correct, UserMapper userDao;
// otherwise, throw some kind of exception
@Inject
PasswordEncryptionUtility passwordUtility;
@Inject
SaltUtility saltUtility;
Set<Permission> permissions;
MySQLUser() {
user = new net.sourceforge.guacamole.net.auth.mysql.model.guacamole.User();
permissions = new HashSet<Permission>();
}
/**
* Create the user, throwing an exception if the credentials do not match what's in the database.
* @param credentials
* @throws GuacamoleException
*/
void init (Credentials credentials) throws GuacamoleException {
UserExample userExample = new UserExample();
userExample.createCriteria().andUsernameEqualTo(credentials.getUsername());
List<net.sourceforge.guacamole.net.auth.mysql.model.guacamole.User> users = userDao.selectByExample(userExample);
if(users.size() > 1) // the unique constraint on the table should prevent this
throw new GuacamoleException("Multiple users found with the same username: " + credentials.getUsername());
if(users.isEmpty())
throw new GuacamoleException("No user found with the supplied credentials");
user = users.get(0);
// check password
if(!passwordUtility.checkCredentials(credentials, user.getPassword_hash(), user.getUsername(), user.getPassword_salt()))
throw new GuacamoleException("No user found with the supplied credentials");
}
void init (User user) {
this.setPassword(user.getPassword());
this.setUsername(user.getUsername());
}
public net.sourceforge.guacamole.net.auth.mysql.model.guacamole.User getUser() {
return user;
} }
@Override @Override
public String getUsername() { public String getUsername() {
return username; return user.getUsername();
} }
@Override @Override
public void setUsername(String username) { public void setUsername(String username) {
throw new UnsupportedOperationException("Not supported yet."); user.setUsername(username);
} }
@Override @Override
public String getPassword() { public String getPassword() {
throw new UnsupportedOperationException("Not supported yet."); try {
return new String(user.getPassword_hash(), "UTF-8");
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException(ex); // should not happen
}
} }
@Override @Override
public void setPassword(String password) { public void setPassword(String password) {
throw new UnsupportedOperationException("Not supported yet."); String salt = saltUtility.generateSalt();
user.setPassword_salt(salt);
byte[] hash = passwordUtility.createPasswordHash(password, salt);
user.setPassword_hash(hash);
} }
@Override @Override
public Set<Permission> getPermissions() throws GuacamoleException { public Set<Permission> getPermissions() throws GuacamoleException {
throw new UnsupportedOperationException("Not supported yet."); return permissions;
} }
@Override @Override
public boolean hasPermission(Permission permission) throws GuacamoleException { public boolean hasPermission(Permission permission) throws GuacamoleException {
throw new UnsupportedOperationException("Not supported yet."); return permissions.contains(permission);
} }
@Override @Override
public void addPermission(Permission permission) throws GuacamoleException { public void addPermission(Permission permission) throws GuacamoleException {
throw new UnsupportedOperationException("Not supported yet."); permissions.add(permission);
} }
@Override @Override
public void removePermission(Permission permission) throws GuacamoleException { public void removePermission(Permission permission) throws GuacamoleException {
throw new UnsupportedOperationException("Not supported yet."); permissions.remove(permission);
} }
} }

View File

@@ -53,13 +53,16 @@ public class MySQLUserContext implements UserContext {
private Logger logger = LoggerFactory.getLogger(MySQLUserContext.class); private Logger logger = LoggerFactory.getLogger(MySQLUserContext.class);
void init(Credentials credentials) { @Inject
// load the required data with the provided credentials private MySQLUser user;
void init(Credentials credentials) throws GuacamoleException {
user.init(credentials);
} }
@Override @Override
public User self() { public User self() {
throw new UnsupportedOperationException("Not supported yet."); return user;
} }
@Override @Override

View File

@@ -0,0 +1,72 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.sourceforge.guacamole.net.auth.mysql;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import net.sourceforge.guacamole.GuacamoleException;
import net.sourceforge.guacamole.net.auth.Directory;
import net.sourceforge.guacamole.net.auth.User;
import net.sourceforge.guacamole.net.auth.mysql.dao.guacamole.UserMapper;
/**
* A MySQL based implementation of the User Directory.
* @author James Muehlner
*/
public class UserDirectory implements Directory<String, User> {
private Map<String, User> userMap = new HashMap<String, User>();
@Inject
UserMapper userDAO;
@Inject
Provider<MySQLUser> mySQLUserProvider;
private MySQLUser getMySQLUser(User user) {
MySQLUser mySQLUser = mySQLUserProvider.get();
mySQLUser.init(user);
return mySQLUser;
}
@Override
public User get(String identifier) throws GuacamoleException {
return userMap.get(identifier);
}
@Override
public Set<String> getIdentifiers() throws GuacamoleException {
return userMap.keySet();
}
@Override
public void add(User object) throws GuacamoleException {
MySQLUser mySQLUser = getMySQLUser(object);
userDAO.insert(mySQLUser.getUser());
userMap.put(mySQLUser.getUsername(), mySQLUser);
}
@Override
public void update(User object) throws GuacamoleException {
if(!userMap.containsKey(object.getUsername()))
throw new GuacamoleException("User not found in Directory.");
MySQLUser mySQLUser = getMySQLUser(object);
userDAO.updateByPrimaryKey(mySQLUser.getUser());
userMap.put(object.getUsername(), mySQLUser);
}
@Override
public void remove(String identifier) throws GuacamoleException {
User user = userMap.get(identifier);
if(user == null)
throw new GuacamoleException("User not found in Directory.");
MySQLUser mySQLUser = getMySQLUser(user);
userDAO.deleteByPrimaryKey(mySQLUser.getUser().getUser_id());
userMap.remove(user.getUsername());
}
}

View File

@@ -0,0 +1,64 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is guacamole-auth-mysql.
*
* The Initial Developer of the Original Code is
* James Muehlner.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package net.sourceforge.guacamole.net.auth.mysql.utility;
import net.sourceforge.guacamole.net.auth.Credentials;
/**
*
* @author James Muehlner
*/
public interface PasswordEncryptionUtility {
/**
* Checks if the provided Credentials are correct, compared with what the values from the database.
* @param credentials
* @param dbPasswordHash
* @param dbUsername
* @param dbSalt
* @return true if the provided credentials match what's in the database for that user.
*/
public boolean checkCredentials(Credentials credentials, byte[] dbPasswordHash, String dbUsername, String dbSalt);
/**
* Creates a password hash based on the provided username, password, and salt.
* @param username
* @param password
* @param salt
* @return the generated password hash.
*/
public byte[] createPasswordHash(String password, String salt);
}

View File

@@ -0,0 +1,50 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is guacamole-auth-mysql.
*
* The Initial Developer of the Original Code is
* James Muehlner.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package net.sourceforge.guacamole.net.auth.mysql.utility;
import net.sourceforge.guacamole.GuacamoleException;
/**
*
* @author James Muehlner
*/
public interface SaltUtility {
/**
* Generates a new String that can be used as a password salt.
* @return a new salt for password encryption.
*/
public String generateSalt();
}

View File

@@ -0,0 +1,28 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.sourceforge.guacamole.net.auth.mysql.utility;
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
/**
* Generates password salts via the SecureRandom utility.
* @author dagger10k
*/
public class SecureRandomSaltUtility implements SaltUtility {
SecureRandom secureRandom = new SecureRandom();
@Override
public String generateSalt() {
byte[] salt = new byte[32];
secureRandom.nextBytes(salt);
try {
return new String(salt, "UTF-8");
} catch (UnsupportedEncodingException ex) { // should not happen
throw new RuntimeException(ex);
}
}
}

View File

@@ -0,0 +1,79 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is guacamole-auth-mysql.
*
* The Initial Developer of the Original Code is
* James Muehlner.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package net.sourceforge.guacamole.net.auth.mysql.utility;
import com.google.common.base.Preconditions;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import net.sourceforge.guacamole.net.auth.Credentials;
/**
* Provides a SHA-256 based implementation of the password encryption functionality.
* @author James Muehlner
*/
public class Sha256PasswordEncryptionUtility implements PasswordEncryptionUtility {
@Override
public boolean checkCredentials(Credentials credentials, byte[] dbPasswordHash, String dbUsername, String dbSalt) {
Preconditions.checkNotNull(credentials);
Preconditions.checkNotNull(dbPasswordHash);
Preconditions.checkNotNull(dbUsername);
Preconditions.checkNotNull(dbSalt);
byte[] passwordBytes = createPasswordHash(credentials.getPassword(), dbSalt);
return Arrays.equals(passwordBytes, dbPasswordHash);
}
@Override
public byte[] createPasswordHash(String password, String salt) {
Preconditions.checkNotNull(password);
Preconditions.checkNotNull(salt);
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
StringBuilder builder = new StringBuilder();
builder.append(password);
builder.append(salt);
md.update(builder.toString().getBytes("UTF-8"));
return md.digest();
} catch (UnsupportedEncodingException ex) { // should not happen
throw new RuntimeException(ex);
} catch (NoSuchAlgorithmException ex) { // should not happen
throw new RuntimeException(ex);
}
}
}