diff --git a/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java b/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java index 2975085a91..0945bf67d3 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java @@ -143,7 +143,7 @@ public class ConnectDialog extends MageDialog { lblUserName = new javax.swing.JLabel(); txtUserName = new javax.swing.JTextField(); lblPassword = new javax.swing.JLabel(); - txtPassword = new javax.swing.JTextField(); + txtPassword = new javax.swing.JPasswordField(); lblFlag = new javax.swing.JLabel(); cbFlag = new mage.client.util.gui.countryBox.CountryComboBox(); chkAutoConnect = new javax.swing.JCheckBox(); @@ -180,9 +180,21 @@ public class ConnectDialog extends MageDialog { lblUserName.setLabelFor(txtUserName); lblUserName.setText("User name:"); + txtUserName.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtUserNameActionPerformed(evt); + } + }); + lblPassword.setLabelFor(txtPassword); lblPassword.setText("Password:"); + txtPassword.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtPasswordActionPerformed(evt); + } + }); + lblFlag.setLabelFor(txtUserName); lblFlag.setText("User flag:"); @@ -564,6 +576,10 @@ public class ConnectDialog extends MageDialog { // TODO add your handling code here: }//GEN-LAST:event_txtUserNameActionPerformed + private void txtPasswordActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtPasswordActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtPasswordActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton btnCancel; private javax.swing.JButton btnConnect; @@ -578,7 +594,7 @@ public class ConnectDialog extends MageDialog { private javax.swing.JLabel lblServer; private javax.swing.JLabel lblStatus; private javax.swing.JLabel lblUserName; - private javax.swing.JTextField txtPassword; + private javax.swing.JPasswordField txtPassword; private javax.swing.JTextField txtPort; private javax.swing.JTextField txtServer; private javax.swing.JTextField txtUserName; diff --git a/Mage.Server/pom.xml b/Mage.Server/pom.xml index dabb43df49..80916da83e 100644 --- a/Mage.Server/pom.xml +++ b/Mage.Server/pom.xml @@ -148,6 +148,12 @@ ${project.version} runtime + + org.apache.shiro + shiro-core + 1.2.4 + jar + diff --git a/Mage.Server/src/main/java/mage/server/AuthorizedUser.java b/Mage.Server/src/main/java/mage/server/AuthorizedUser.java new file mode 100644 index 0000000000..d3a678ccd1 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/AuthorizedUser.java @@ -0,0 +1,52 @@ +package mage.server; + +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.table.DatabaseTable; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.authc.credential.HashedCredentialsMatcher; +import org.apache.shiro.util.ByteSource; +import org.apache.shiro.codec.Base64; +import org.apache.shiro.crypto.hash.Hash; + +@DatabaseTable(tableName = "authorized_user") +public class AuthorizedUser { + + @DatabaseField(indexName = "name_index") + protected String name; + + @DatabaseField + protected String password; + + @DatabaseField + protected String salt; + + @DatabaseField + protected String hashAlgorithm; + + @DatabaseField + protected int hashIterations; + + public AuthorizedUser() { + } + + public AuthorizedUser(String name, Hash hash) { + this.name = name; + this.password = hash.toBase64(); + this.salt = hash.getSalt().toBase64(); + this.hashAlgorithm = hash.getAlgorithmName(); + this.hashIterations = hash.getIterations(); + } + + public boolean doCredentialsMatch(String name, String password) { + HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(this.hashAlgorithm); + matcher.setHashIterations(this.hashIterations); + AuthenticationToken token = new UsernamePasswordToken(name, password); + AuthenticationInfo info = new SimpleAuthenticationInfo(this.name, + ByteSource.Util.bytes(Base64.decode(this.password)), + ByteSource.Util.bytes(Base64.decode(this.salt)), ""); + return matcher.doCredentialsMatch(token, info); + } +} diff --git a/Mage.Server/src/main/java/mage/server/AuthorizedUserRepository.java b/Mage.Server/src/main/java/mage/server/AuthorizedUserRepository.java new file mode 100644 index 0000000000..551c8cb6fc --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/AuthorizedUserRepository.java @@ -0,0 +1,97 @@ +package mage.server; + +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.DaoManager; +import com.j256.ormlite.jdbc.JdbcConnectionSource; +import com.j256.ormlite.stmt.QueryBuilder; +import com.j256.ormlite.stmt.SelectArg; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.table.TableUtils; +import java.io.File; +import java.sql.SQLException; +import java.util.List; +import java.util.concurrent.Callable; +import mage.cards.repository.RepositoryUtil; +import org.apache.log4j.Logger; +import org.apache.shiro.crypto.RandomNumberGenerator; +import org.apache.shiro.crypto.SecureRandomNumberGenerator; +import org.apache.shiro.crypto.hash.Hash; +import org.apache.shiro.crypto.hash.Sha256Hash; +import org.apache.shiro.crypto.hash.SimpleHash; + +public enum AuthorizedUserRepository { + + instance; + + private static final String JDBC_URL = "jdbc:h2:file:./db/authorized_user.h2;AUTO_SERVER=TRUE"; + private static final String VERSION_ENTITY_NAME = "authorized_user"; + // raise this if db structure was changed + private static final long DB_VERSION = 0; + private static final RandomNumberGenerator rng = new SecureRandomNumberGenerator(); + + private Dao dao; + + private AuthorizedUserRepository() { + File file = new File("db"); + if (!file.exists()) { + file.mkdirs(); + } + try { + ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL); + boolean obsolete = RepositoryUtil.isDatabaseObsolete(connectionSource, VERSION_ENTITY_NAME, DB_VERSION); + + if (obsolete) { + TableUtils.dropTable(connectionSource, AuthorizedUser.class, true); + } + + TableUtils.createTableIfNotExists(connectionSource, AuthorizedUser.class); + dao = DaoManager.createDao(connectionSource, AuthorizedUser.class); + } catch (SQLException ex) { + Logger.getLogger(AuthorizedUserRepository.class).error("Error creating authorized_user repository - ", ex); + } + } + + public void add(final String userName, final String password) { + try { + dao.callBatchTasks(new Callable() { + @Override + public Object call() throws Exception { + try { + Hash hash = new SimpleHash(Sha256Hash.ALGORITHM_NAME, password, rng.nextBytes(), 1024); + AuthorizedUser user = new AuthorizedUser(userName, hash); + dao.create(user); + } catch (SQLException ex) { + Logger.getLogger(AuthorizedUserRepository.class).error("Error adding a user to DB - ", ex); + } + return null; + } + }); + } catch (Exception ex) { + } + } + + public AuthorizedUser get(String userName) { + try { + QueryBuilder qb = dao.queryBuilder(); + qb.where().eq("name", new SelectArg(userName)); + List results = dao.query(qb.prepare()); + if (results.size() == 1) { + return results.get(0); + } + return null; + } catch (SQLException ex) { + } + return null; + } + + public void closeDB() { + try { + if (dao != null && dao.getConnectionSource() != null) { + DatabaseConnection conn = dao.getConnectionSource().getReadWriteConnection(); + conn.executeStatement("shutdown compact", 0); + } + } catch (SQLException ex) { + } + } +} diff --git a/Mage.Server/src/main/java/mage/server/Session.java b/Mage.Server/src/main/java/mage/server/Session.java index 9e49fcd24b..2daabc14a8 100644 --- a/Mage.Server/src/main/java/mage/server/Session.java +++ b/Mage.Server/src/main/java/mage/server/Session.java @@ -102,6 +102,17 @@ public class Session { if (m.find()) { return "User name '" + userName + "' includes not allowed characters: use a-z, A-Z and 0-9"; } + + AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.get(userName); + if (authorizedUser == null) { + // Do this in an explicit sign-up flow. + AuthorizedUserRepository.instance.add(userName, password); + } else { + if (!authorizedUser.doCredentialsMatch(userName, password)) { + return "Wrong username or password"; + } + } + // TODO: Do an authentication with userName and password. User user = UserManager.getInstance().createUser(userName, host); boolean reconnect = false;