diff --git a/extensions/guacamole-auth-mysql/pom.xml b/extensions/guacamole-auth-mysql/pom.xml index 952db2420..c193336ad 100644 --- a/extensions/guacamole-auth-mysql/pom.xml +++ b/extensions/guacamole-auth-mysql/pom.xml @@ -93,7 +93,18 @@ mybatis-guice 3.2 - + + + com.google.collections + google-collections + 1.0 + + + net.sourceforge.guacamole + guacamole-auth-mysql + 0.8.0 + jar + diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLAuthenticationProvider.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLAuthenticationProvider.java index 6505dbea8..db95428a7 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLAuthenticationProvider.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLAuthenticationProvider.java @@ -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.UserPermissionMapper; 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 org.mybatis.guice.MyBatisModule; import org.mybatis.guice.datasource.builtin.PooledDataSourceProvider; @@ -105,6 +109,9 @@ public class MySQLAuthenticationProvider implements AuthenticationProvider { addMapperClass(UserMapper.class); addMapperClass(UserPermissionMapper.class); bind(MySQLUserContext.class); + bind(MySQLUser.class); + bind(SaltUtility.class).to(SecureRandomSaltUtility.class); + bind(PasswordEncryptionUtility.class).to(Sha256PasswordEncryptionUtility.class); } } ); diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLUser.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLUser.java index c0a351576..f29ec948a 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLUser.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLUser.java @@ -35,65 +35,116 @@ * ***** END LICENSE BLOCK ***** */ 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 net.sourceforge.guacamole.GuacamoleException; import net.sourceforge.guacamole.net.auth.Credentials; 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; /** - * + * A MySQL based implementation of the User object. * @author James Muehlner */ public class MySQLUser implements User { - private String username; - private String userID; - private String salt; + private net.sourceforge.guacamole.net.auth.mysql.model.guacamole.User user; - MySQLUser(Credentials credentials) { - //TODO: load the user from the DB if the credentials are correct, - // otherwise, throw some kind of exception + @Inject + UserMapper userDao; + + @Inject + PasswordEncryptionUtility passwordUtility; + + @Inject + SaltUtility saltUtility; + + Set permissions; + + MySQLUser() { + user = new net.sourceforge.guacamole.net.auth.mysql.model.guacamole.User(); + permissions = new HashSet(); + } + + /** + * 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 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 public String getUsername() { - return username; + return user.getUsername(); } @Override public void setUsername(String username) { - throw new UnsupportedOperationException("Not supported yet."); + user.setUsername(username); } @Override 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 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 public Set getPermissions() throws GuacamoleException { - throw new UnsupportedOperationException("Not supported yet."); + return permissions; } @Override public boolean hasPermission(Permission permission) throws GuacamoleException { - throw new UnsupportedOperationException("Not supported yet."); + return permissions.contains(permission); } @Override public void addPermission(Permission permission) throws GuacamoleException { - throw new UnsupportedOperationException("Not supported yet."); + permissions.add(permission); } @Override public void removePermission(Permission permission) throws GuacamoleException { - throw new UnsupportedOperationException("Not supported yet."); + permissions.remove(permission); } - } diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLUserContext.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLUserContext.java index 6de80c19e..026533aea 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLUserContext.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLUserContext.java @@ -53,13 +53,16 @@ public class MySQLUserContext implements UserContext { private Logger logger = LoggerFactory.getLogger(MySQLUserContext.class); - void init(Credentials credentials) { - // load the required data with the provided credentials + @Inject + private MySQLUser user; + + void init(Credentials credentials) throws GuacamoleException { + user.init(credentials); } @Override public User self() { - throw new UnsupportedOperationException("Not supported yet."); + return user; } @Override diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/UserDirectory.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/UserDirectory.java new file mode 100644 index 000000000..09419b969 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/UserDirectory.java @@ -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 { + + private Map userMap = new HashMap(); + + @Inject + UserMapper userDAO; + + @Inject + Provider 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 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()); + } +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/utility/PasswordEncryptionUtility.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/utility/PasswordEncryptionUtility.java new file mode 100644 index 000000000..f9d7a4963 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/utility/PasswordEncryptionUtility.java @@ -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); +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/utility/SaltUtility.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/utility/SaltUtility.java new file mode 100644 index 000000000..fd4661769 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/utility/SaltUtility.java @@ -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(); +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/utility/SecureRandomSaltUtility.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/utility/SecureRandomSaltUtility.java new file mode 100644 index 000000000..63d37d3a5 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/utility/SecureRandomSaltUtility.java @@ -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); + } + } +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/utility/Sha256PasswordEncryptionUtility.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/utility/Sha256PasswordEncryptionUtility.java new file mode 100644 index 000000000..ff233d7f6 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/utility/Sha256PasswordEncryptionUtility.java @@ -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); + } + } +}