Merge pull request #73 from Asimq/develop
Moved password encryption from MD5 to a salted and secure hash - 1448
This commit is contained in:
commit
111c0a8ee5
30 changed files with 487 additions and 50 deletions
|
@ -53,6 +53,11 @@
|
|||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>de.mkammerer</groupId>
|
||||
<artifactId>argon2-jvm</artifactId>
|
||||
<version>2.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.vivoweb</groupId>
|
||||
<artifactId>vitro-dependencies</artifactId>
|
||||
|
|
|
@ -102,7 +102,7 @@ public class PolicyHelper {
|
|||
String uri = user.getUri();
|
||||
log.debug("userAccount is '" + uri + "'");
|
||||
|
||||
if (!auth.isCurrentPassword(user, password)) {
|
||||
if (!auth.isCurrentPasswordArgon2(user, password)) {
|
||||
log.debug(String.format("UNAUTHORIZED, password not accepted "
|
||||
+ "for %s, account URI: %s", email, uri));
|
||||
return false;
|
||||
|
|
|
@ -67,6 +67,7 @@ public class RootUserPolicy implements PolicyIface {
|
|||
private ServletContext ctx;
|
||||
private StartupStatus ss;
|
||||
private UserAccountsDao uaDao;
|
||||
private ConfigurationProperties cp;
|
||||
private String configuredRootUser;
|
||||
private boolean configuredRootUserExists;
|
||||
private TreeSet<String> otherRootUsers;
|
||||
|
@ -75,6 +76,7 @@ public class RootUserPolicy implements PolicyIface {
|
|||
public void contextInitialized(ServletContextEvent sce) {
|
||||
ctx = sce.getServletContext();
|
||||
ss = StartupStatus.getBean(ctx);
|
||||
cp = ConfigurationProperties.getBean(ctx);
|
||||
|
||||
try {
|
||||
uaDao = ModelAccess.on(ctx).getWebappDaoFactory()
|
||||
|
@ -148,8 +150,9 @@ public class RootUserPolicy implements PolicyIface {
|
|||
ua.setEmailAddress(configuredRootUser);
|
||||
ua.setFirstName("root");
|
||||
ua.setLastName("user");
|
||||
ua.setMd5Password(Authenticator
|
||||
.applyMd5Encoding(ROOT_USER_INITIAL_PASSWORD));
|
||||
ua.setArgon2Password(Authenticator.applyArgon2iEncoding(
|
||||
ROOT_USER_INITIAL_PASSWORD));
|
||||
ua.setMd5Password("");
|
||||
ua.setPasswordChangeRequired(true);
|
||||
ua.setStatus(Status.ACTIVE);
|
||||
ua.setRootUser(true);
|
||||
|
|
|
@ -48,6 +48,7 @@ public class UserAccount {
|
|||
private String firstName = ""; // Never null.
|
||||
private String lastName = ""; // Never null.
|
||||
|
||||
private String argon2Password = ""; //Never null.
|
||||
private String md5Password = ""; // Never null.
|
||||
private String oldPassword = ""; // Never null.
|
||||
private long passwordLinkExpires = 0L; // Never negative.
|
||||
|
@ -104,6 +105,14 @@ public class UserAccount {
|
|||
this.lastName = nonNull(lastName, "");
|
||||
}
|
||||
|
||||
public String getArgon2Password() {
|
||||
return argon2Password;
|
||||
}
|
||||
|
||||
public void setArgon2Password(String argo2Password) {
|
||||
this.argon2Password = nonNull(argo2Password, "");
|
||||
}
|
||||
|
||||
public String getMd5Password() {
|
||||
return md5Password;
|
||||
}
|
||||
|
@ -125,7 +134,7 @@ public class UserAccount {
|
|||
}
|
||||
|
||||
public String getPasswordLinkExpiresHash() {
|
||||
return limitStringLength(8, Authenticator.applyMd5Encoding(String
|
||||
return limitStringLength(8, Authenticator.applyArgon2iEncoding(String
|
||||
.valueOf(passwordLinkExpires)));
|
||||
}
|
||||
|
||||
|
@ -236,6 +245,7 @@ public class UserAccount {
|
|||
+ (", firstName=" + firstName) + (", lastName=" + lastName)
|
||||
+ (", md5password=" + md5Password)
|
||||
+ (", oldPassword=" + oldPassword)
|
||||
+ (", argon2password=" + argon2Password)
|
||||
+ (", passwordLinkExpires=" + passwordLinkExpires)
|
||||
+ (", passwordChangeRequired=" + passwordChangeRequired)
|
||||
+ (", externalAuthOnly=" + externalAuthOnly)
|
||||
|
|
|
@ -29,6 +29,9 @@ public class ConfigurationPropertiesSmokeTests implements
|
|||
private static final String PROPERTY_LANGUAGE_SELECTABLE = "languages.selectableLocales";
|
||||
private static final String PROPERTY_LANGUAGE_FORCE = "languages.forceLocale";
|
||||
private static final String PROPERTY_LANGUAGE_FILTER = "RDFService.languageFilter";
|
||||
private static final String PROPERTY_ARGON2_TIME = "argon2.time";
|
||||
private static final String PROPERTY_ARGON2_MEMORY = "argon2.memory";
|
||||
private static final String PROPERTY_ARGON2_PARALLELISM = "argon2.parallelism";
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
|
@ -39,6 +42,7 @@ public class ConfigurationPropertiesSmokeTests implements
|
|||
checkDefaultNamespace(ctx, props, ss);
|
||||
checkMultipleRPFs(ctx, props, ss);
|
||||
checkLanguages(props, ss);
|
||||
checkEncryptionParameters(props, ss);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -149,6 +153,26 @@ public class ConfigurationPropertiesSmokeTests implements
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fail if there are no config properties for the Argon2 encryption.
|
||||
*/
|
||||
private void checkEncryptionParameters(ConfigurationProperties props,
|
||||
StartupStatus ss) {
|
||||
failIfNotPresent(props, ss, PROPERTY_ARGON2_TIME);
|
||||
failIfNotPresent(props, ss, PROPERTY_ARGON2_MEMORY);
|
||||
failIfNotPresent(props, ss, PROPERTY_ARGON2_PARALLELISM);
|
||||
}
|
||||
|
||||
private void failIfNotPresent(ConfigurationProperties props,
|
||||
StartupStatus ss, String name) {
|
||||
String value = props.getProperty(name);
|
||||
if (value == null || value.isEmpty()) {
|
||||
ss.fatal(this, "runtime.properties does not contain a value for '"
|
||||
+ name + "'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
// nothing to do at shutdown
|
||||
|
|
|
@ -37,7 +37,7 @@ public class UserAccountsSelector {
|
|||
+ "PREFIX auth: <http://vitro.mannlib.cornell.edu/ns/vitro/authorization#> \n";
|
||||
|
||||
private static final String ALL_VARIABLES = "?uri ?email ?firstName "
|
||||
+ "?lastName ?pwd ?expire ?count ?lastLogin ?status ?isRoot";
|
||||
+ "?lastName ?md5pwd ?a2pwd ?expire ?count ?lastLogin ?status ?isRoot";
|
||||
|
||||
private static final String COUNT_VARIABLE = "?uri";
|
||||
|
||||
|
@ -158,7 +158,8 @@ public class UserAccountsSelector {
|
|||
private String optionalClauses() {
|
||||
return "OPTIONAL { ?uri auth:firstName ?firstName } \n"
|
||||
+ " OPTIONAL { ?uri auth:lastName ?lastName } \n"
|
||||
+ " OPTIONAL { ?uri auth:md5password ?pwd } \n"
|
||||
+ " OPTIONAL { ?uri auth:md5password ?md5pwd } \n"
|
||||
+ " OPTIONAL { ?uri auth:argon2password ?a2pwd } \n"
|
||||
+ " OPTIONAL { ?uri auth:passwordChangeExpires ?expire } \n"
|
||||
+ " OPTIONAL { ?uri auth:loginCount ?count } \n"
|
||||
+ " OPTIONAL { ?uri auth:lastLoginTime ?lastLogin } \n"
|
||||
|
@ -245,7 +246,8 @@ public class UserAccountsSelector {
|
|||
user.setEmailAddress(solution.getLiteral("email").getString());
|
||||
user.setFirstName(ifLiteralPresent(solution, "firstName", ""));
|
||||
user.setLastName(ifLiteralPresent(solution, "lastName", ""));
|
||||
user.setMd5Password(ifLiteralPresent(solution, "pwd", ""));
|
||||
user.setMd5Password(ifLiteralPresent(solution, "md5pwd", ""));
|
||||
user.setArgon2Password(ifLiteralPresent(solution, "a2pwd", ""));
|
||||
user.setPasswordLinkExpires(ifLongPresent(solution, "expire", 0L));
|
||||
user.setLoginCount(ifIntPresent(solution, "count", 0));
|
||||
user.setLastLoginTime(ifLongPresent(solution, "lastLogin", 0));
|
||||
|
|
|
@ -198,8 +198,8 @@ public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
|
|||
@Override
|
||||
protected void setAdditionalProperties(UserAccount u) {
|
||||
if (!page.isExternalAuthOnly()) {
|
||||
u.setMd5Password(Authenticator
|
||||
.applyMd5Encoding(initialPassword));
|
||||
u.setArgon2Password(Authenticator.applyArgon2iEncoding(initialPassword));
|
||||
u.setMd5Password("");
|
||||
u.setPasswordChangeRequired(true);
|
||||
}
|
||||
u.setStatus(Status.ACTIVE);
|
||||
|
|
|
@ -194,7 +194,8 @@ public abstract class UserAccountsEditPageStrategy extends UserAccountsPage {
|
|||
@Override
|
||||
protected void setAdditionalProperties(UserAccount u) {
|
||||
if (!page.isExternalAuthOnly() && !newPassword.isEmpty()) {
|
||||
u.setMd5Password(Authenticator.applyMd5Encoding(newPassword));
|
||||
u.setArgon2Password(Authenticator.applyArgon2iEncoding(newPassword));
|
||||
u.setMd5Password("");
|
||||
u.setPasswordChangeRequired(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,14 +33,14 @@ public class UserAccountsCreatePasswordPage extends
|
|||
}
|
||||
|
||||
public void createPassword() {
|
||||
userAccount.setMd5Password(Authenticator.applyMd5Encoding(newPassword));
|
||||
userAccount.setArgon2Password(Authenticator.applyArgon2iEncoding(newPassword));
|
||||
userAccount.setMd5Password("");
|
||||
userAccount.setPasswordLinkExpires(0L);
|
||||
userAccount.setPasswordChangeRequired(false);
|
||||
userAccount.setStatus(Status.ACTIVE);
|
||||
userAccountsDao.updateUserAccount(userAccount);
|
||||
log.debug("Set password on '" + userAccount.getEmailAddress()
|
||||
+ "' to '" + newPassword + "'");
|
||||
|
||||
notifyUser();
|
||||
}
|
||||
|
||||
|
|
|
@ -155,8 +155,8 @@ public abstract class UserAccountsMyAccountPageStrategy extends
|
|||
@Override
|
||||
public void setAdditionalProperties(UserAccount userAccount) {
|
||||
if (!newPassword.isEmpty() && !page.isExternalAuthOnly()) {
|
||||
userAccount.setMd5Password(Authenticator
|
||||
.applyMd5Encoding(newPassword));
|
||||
userAccount.setArgon2Password(Authenticator.applyArgon2iEncoding(newPassword));
|
||||
userAccount.setMd5Password("");
|
||||
userAccount.setPasswordChangeRequired(false);
|
||||
userAccount.setPasswordLinkExpires(0L);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,8 @@ public class UserAccountsResetPasswordPage extends UserAccountsPasswordBasePage
|
|||
}
|
||||
|
||||
public void resetPassword() {
|
||||
userAccount.setMd5Password(Authenticator.applyMd5Encoding(newPassword));
|
||||
userAccount.setArgon2Password(Authenticator.applyArgon2iEncoding(newPassword));
|
||||
userAccount.setMd5Password("");
|
||||
userAccount.setPasswordLinkExpires(0L);
|
||||
userAccount.setPasswordChangeRequired(false);
|
||||
userAccount.setStatus(Status.ACTIVE);
|
||||
|
|
|
@ -54,7 +54,7 @@ public class VitroApiServlet extends HttpServlet {
|
|||
+ "last names and a valid email address.");
|
||||
}
|
||||
|
||||
if (!auth.isCurrentPassword(account, password)) {
|
||||
if (!auth.isCurrentPasswordArgon2(account, password)) {
|
||||
log.debug("Invalid: '" + email + "'/'" + password + "'");
|
||||
throw new AuthException("email/password combination is not valid");
|
||||
}
|
||||
|
|
|
@ -141,8 +141,12 @@ public class AdminLoginController extends FreemarkerHttpServlet {
|
|||
}
|
||||
|
||||
private boolean newPasswordRequired() {
|
||||
return auth.isCurrentPassword(userAccount, password)
|
||||
&& (userAccount.isPasswordChangeRequired());
|
||||
if(auth.md5HashIsNull(userAccount)) {
|
||||
return auth.isCurrentPasswordArgon2(userAccount, password)
|
||||
&& userAccount.isPasswordChangeRequired();
|
||||
}
|
||||
else
|
||||
return auth.isCurrentPassword(userAccount, password); // MD5 password should be changed anyway
|
||||
}
|
||||
|
||||
private boolean isPasswordValidLength(String pw) {
|
||||
|
@ -151,8 +155,18 @@ public class AdminLoginController extends FreemarkerHttpServlet {
|
|||
}
|
||||
|
||||
private boolean tryToLogin() {
|
||||
if (!auth.isCurrentPassword(userAccount, password)) {
|
||||
return false;
|
||||
if(auth.md5HashIsNull(userAccount)) {
|
||||
if (!auth.isCurrentPasswordArgon2(userAccount, password))
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
if (!auth.isCurrentPassword(userAccount, password))
|
||||
return false;
|
||||
else {
|
||||
userAccount.setPasswordChangeRequired(true);
|
||||
userAccount.setMd5Password("");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -2,21 +2,23 @@
|
|||
|
||||
package edu.cornell.mannlib.vitro.webapp.controller.authenticate;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.mail.internet.AddressException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
|
||||
import de.mkammerer.argon2.Argon2;
|
||||
import de.mkammerer.argon2.Argon2Factory;
|
||||
import edu.cornell.mannlib.vedit.beans.LoginStatusBean.AuthenticationSource;
|
||||
import edu.cornell.mannlib.vitro.webapp.auth.identifier.ActiveIdentifierBundleFactories;
|
||||
import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
|
||||
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
|
||||
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils;
|
||||
import javax.mail.internet.AddressException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* The tool that a login process will use to interface with the user records in
|
||||
|
@ -55,6 +57,7 @@ public abstract class Authenticator {
|
|||
*
|
||||
* If there is no factory, configure a Basic one.
|
||||
*/
|
||||
|
||||
public static Authenticator getInstance(HttpServletRequest request) {
|
||||
ServletContext ctx = request.getSession().getServletContext();
|
||||
Object attribute = ctx.getAttribute(FACTORY_ATTRIBUTE_NAME);
|
||||
|
@ -112,6 +115,22 @@ public abstract class Authenticator {
|
|||
public abstract boolean isCurrentPassword(UserAccount userAccount,
|
||||
String clearTextPassword);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Does this UserAccount have this Argon2 password? False if the
|
||||
* userAccount is null.
|
||||
*/
|
||||
public abstract boolean isCurrentPasswordArgon2(UserAccount userAccount,
|
||||
String clearTextPassword);
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Checks if the user still has got an MD5 Password
|
||||
*/
|
||||
public abstract boolean md5HashIsNull(UserAccount userAccount);
|
||||
|
||||
/**
|
||||
* Internal: record a new password for the user. Takes no action if the
|
||||
* userAccount is null.
|
||||
|
@ -180,6 +199,46 @@ public abstract class Authenticator {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies Argon2i hashing on a string. Obtains the argon2i parameters
|
||||
* from the configuration properties specified in the runtime.properties
|
||||
* through this class "Authenticator".
|
||||
**/
|
||||
|
||||
|
||||
public static String applyArgon2iEncoding(String raw) {
|
||||
ServletContext ctx = ApplicationUtils.instance().getServletContext();
|
||||
ConfigurationProperties configProp = ConfigurationProperties.getBean(ctx);
|
||||
|
||||
Argon2 argon2 = Argon2Factory.create();
|
||||
if (configProp.getProperty("argon2.time") != null
|
||||
&& configProp.getProperty("argon2.memory") != null
|
||||
&& configProp.getProperty("argon2.parallelism") != null) {
|
||||
return argon2.hash(
|
||||
Integer.parseInt(configProp.getProperty("argon2.time")),
|
||||
Integer.parseInt(configProp.getProperty("argon2.memory")),
|
||||
Integer.parseInt(configProp.getProperty("argon2.parallelism")), raw);
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Parameters \"argon2.time\", \"argon2.memory\" and "
|
||||
+ "\"argon2.parallelism\" are either missing in the "
|
||||
+ "\"runtime.properties\" file or are not defined correctly");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Verifies the string against the Argon2i hash stored for a user account
|
||||
*/
|
||||
|
||||
public static boolean verifyArgon2iHash(String hash, String raw)
|
||||
{
|
||||
Argon2 argon2 = Argon2Factory.create();
|
||||
return argon2.verify(hash, raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the form of the emailAddress is syntactically correct. Does
|
||||
* not allow multiple addresses. Does not allow local addresses (without a
|
||||
|
|
|
@ -98,6 +98,30 @@ public class BasicAuthenticator extends Authenticator {
|
|||
return encodedPassword.equals(userAccount.getMd5Password());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean md5HashIsNull(UserAccount userAccount){
|
||||
if(userAccount.getMd5Password().compareTo("")==0 ||
|
||||
userAccount.getMd5Password()==null)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isCurrentPasswordArgon2(UserAccount userAccount,
|
||||
String clearTextPassword) {
|
||||
if (userAccount == null) {
|
||||
return false;
|
||||
}
|
||||
if (clearTextPassword == null) {
|
||||
return false;
|
||||
}
|
||||
return verifyArgon2iHash(userAccount.getArgon2Password(),
|
||||
clearTextPassword);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void recordNewPassword(UserAccount userAccount,
|
||||
String newClearTextPassword) {
|
||||
|
@ -105,7 +129,9 @@ public class BasicAuthenticator extends Authenticator {
|
|||
log.error("Trying to change password on null user.");
|
||||
return;
|
||||
}
|
||||
userAccount.setMd5Password(applyMd5Encoding(newClearTextPassword));
|
||||
userAccount.setArgon2Password((applyArgon2iEncoding(
|
||||
newClearTextPassword)));
|
||||
userAccount.setMd5Password("");
|
||||
userAccount.setPasswordChangeRequired(false);
|
||||
userAccount.setPasswordLinkExpires(0L);
|
||||
getUserAccountsDao().updateUserAccount(userAccount);
|
||||
|
|
|
@ -5,6 +5,7 @@ package edu.cornell.mannlib.vitro.webapp.controller.authenticate;
|
|||
import static edu.cornell.mannlib.vedit.beans.LoginStatusBean.AuthenticationSource.INTERNAL;
|
||||
import static edu.cornell.mannlib.vitro.webapp.beans.UserAccount.MAX_PASSWORD_LENGTH;
|
||||
import static edu.cornell.mannlib.vitro.webapp.beans.UserAccount.MIN_PASSWORD_LENGTH;
|
||||
import static edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean.MLevel.ERROR;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
@ -158,7 +159,19 @@ public class ProgramLogin extends HttpServlet {
|
|||
}
|
||||
|
||||
private boolean usernameAndPasswordAreValid() {
|
||||
return auth.isCurrentPassword(userAccount, password);
|
||||
|
||||
if(auth.md5HashIsNull(userAccount)) {
|
||||
if (!auth.isCurrentPasswordArgon2(userAccount, password))
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
if (!auth.isCurrentPassword(userAccount, password))
|
||||
return false;
|
||||
else {
|
||||
userAccount.setPasswordChangeRequired(true);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean loginDisabled() {
|
||||
|
|
|
@ -76,6 +76,30 @@ public class RestrictedAuthenticator extends Authenticator {
|
|||
return auth.getAccountForInternalAuth(emailAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean md5HashIsNull(UserAccount userAccount){
|
||||
if(userAccount.getMd5Password().compareTo("")==0 ||
|
||||
userAccount.getMd5Password()==null)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isCurrentPasswordArgon2(UserAccount userAccount,
|
||||
String clearTextPassword) {
|
||||
if (userAccount == null) {
|
||||
return false;
|
||||
}
|
||||
if (clearTextPassword == null) {
|
||||
return false;
|
||||
}
|
||||
return verifyArgon2iHash(userAccount.getArgon2Password(),
|
||||
clearTextPassword);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isCurrentPassword(UserAccount userAccount,
|
||||
String clearTextPassword) {
|
||||
|
|
|
@ -331,15 +331,37 @@ public class Authenticate extends VitroHttpServlet {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!getAuthenticator(request).isUserPermittedToLogin(user)) {
|
||||
bean.setMessage(request, ERROR, "logins_disabled_for_maintenance");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!getAuthenticator(request).isCurrentPassword(user, password)) {
|
||||
bean.setMessage(request, ERROR, "error_incorrect_credentials");
|
||||
return;
|
||||
|
||||
if(getAuthenticator(request).md5HashIsNull(user)) {
|
||||
if (!getAuthenticator(request)
|
||||
.isCurrentPasswordArgon2(user, password)) {
|
||||
bean.setMessage(request, ERROR,
|
||||
"error_incorrect_credentials");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!getAuthenticator(request)
|
||||
.isCurrentPassword(user, password)) {
|
||||
bean.setMessage(request, ERROR,
|
||||
"error_incorrect_credentials");
|
||||
return;
|
||||
}
|
||||
else {
|
||||
user.setPasswordChangeRequired(true);
|
||||
user.setMd5Password("");
|
||||
bean.setMessage(request, ERROR,
|
||||
"password_system_has_changed");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Username and password are correct. What next?
|
||||
if (user.isPasswordChangeRequired()) {
|
||||
|
@ -401,7 +423,7 @@ public class Authenticate extends VitroHttpServlet {
|
|||
|
||||
UserAccount user = getAuthenticator(request).getAccountForInternalAuth(
|
||||
username);
|
||||
if (getAuthenticator(request).isCurrentPassword(user, newPassword)) {
|
||||
if (getAuthenticator(request).isCurrentPasswordArgon2(user, newPassword)) {
|
||||
bean.setMessage(request, ERROR, "error_previous_password");
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -149,6 +149,7 @@ public class VitroVocabulary {
|
|||
public static final String USERACCOUNT_EMAIL_ADDRESS = VITRO_AUTH + "emailAddress";
|
||||
public static final String USERACCOUNT_FIRST_NAME = VITRO_AUTH + "firstName";
|
||||
public static final String USERACCOUNT_LAST_NAME = VITRO_AUTH + "lastName";
|
||||
public static final String USERACCOUNT_ARGON2_PASSWORD = VITRO_AUTH + "argon2password";
|
||||
public static final String USERACCOUNT_MD5_PASSWORD = VITRO_AUTH + "md5password";
|
||||
public static final String USERACCOUNT_OLD_PASSWORD = VITRO_AUTH + "oldpassword";
|
||||
public static final String USERACCOUNT_LOGIN_COUNT = VITRO_AUTH + "loginCount";
|
||||
|
|
|
@ -112,6 +112,7 @@ public class JenaBaseDaoCon {
|
|||
protected DatatypeProperty USERACCOUNT_EMAIL_ADDRESS = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_EMAIL_ADDRESS);
|
||||
protected DatatypeProperty USERACCOUNT_FIRST_NAME = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_FIRST_NAME);
|
||||
protected DatatypeProperty USERACCOUNT_LAST_NAME = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_LAST_NAME);
|
||||
protected DatatypeProperty USERACCOUNT_ARGON2_PASSWORD = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_ARGON2_PASSWORD);
|
||||
protected DatatypeProperty USERACCOUNT_MD5_PASSWORD = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_MD5_PASSWORD);
|
||||
protected DatatypeProperty USERACCOUNT_OLD_PASSWORD = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_OLD_PASSWORD);
|
||||
protected DatatypeProperty USERACCOUNT_LOGIN_COUNT = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_LOGIN_COUNT);
|
||||
|
|
|
@ -93,6 +93,7 @@ public class UserAccountsDaoJena extends JenaBaseDao implements UserAccountsDao
|
|||
USERACCOUNT_EMAIL_ADDRESS));
|
||||
u.setFirstName(getPropertyStringValue(r, USERACCOUNT_FIRST_NAME));
|
||||
u.setLastName(getPropertyStringValue(r, USERACCOUNT_LAST_NAME));
|
||||
u.setArgon2Password(getPropertyStringValue(r, USERACCOUNT_ARGON2_PASSWORD));
|
||||
u.setMd5Password(getPropertyStringValue(r, USERACCOUNT_MD5_PASSWORD));
|
||||
u.setOldPassword(getPropertyStringValue(r, USERACCOUNT_OLD_PASSWORD));
|
||||
u.setPasswordLinkExpires(getPropertyLongValue(r,
|
||||
|
@ -225,6 +226,8 @@ public class UserAccountsDaoJena extends JenaBaseDao implements UserAccountsDao
|
|||
userAccount.getLastName(), model);
|
||||
addPropertyStringValue(res, USERACCOUNT_MD5_PASSWORD,
|
||||
userAccount.getMd5Password(), model);
|
||||
addPropertyStringValue(res, USERACCOUNT_ARGON2_PASSWORD,
|
||||
userAccount.getArgon2Password(), model);
|
||||
addPropertyStringValue(res, USERACCOUNT_OLD_PASSWORD,
|
||||
userAccount.getOldPassword(), model);
|
||||
addPropertyLongValue(res, USERACCOUNT_PASSWORD_LINK_EXPIRES,
|
||||
|
@ -288,6 +291,8 @@ public class UserAccountsDaoJena extends JenaBaseDao implements UserAccountsDao
|
|||
userAccount.getLastName(), model);
|
||||
updatePropertyStringValue(res, USERACCOUNT_MD5_PASSWORD,
|
||||
userAccount.getMd5Password(), model);
|
||||
updatePropertyStringValue(res, USERACCOUNT_ARGON2_PASSWORD,
|
||||
userAccount.getArgon2Password(), model);
|
||||
updatePropertyStringValue(res, USERACCOUNT_OLD_PASSWORD,
|
||||
userAccount.getOldPassword(), model);
|
||||
updatePropertyLongValue(res, USERACCOUNT_PASSWORD_LINK_EXPIRES,
|
||||
|
|
|
@ -81,7 +81,8 @@ public class UserAccountsSelectorTest extends AbstractTestClass {
|
|||
assertEquals("email", "email@jones.edu", acct.getEmailAddress());
|
||||
assertEquals("firstName", "Bob", acct.getFirstName());
|
||||
assertEquals("lastName", "Caruso", acct.getLastName());
|
||||
assertEquals("password", "garbage", acct.getMd5Password());
|
||||
assertEquals("md5password", "garbage", acct.getMd5Password());
|
||||
assertEquals("argon2password", "betterGarbage", acct.getArgon2Password());
|
||||
assertEquals("expires", 1100234965897L, acct.getPasswordLinkExpires());
|
||||
assertEquals("loginCount", 50, acct.getLoginCount());
|
||||
assertEquals("lastLogin", 1020304050607080L, acct.getLastLoginTime());
|
||||
|
@ -105,7 +106,8 @@ public class UserAccountsSelectorTest extends AbstractTestClass {
|
|||
assertEquals("email", "email@henry.edu", acct.getEmailAddress());
|
||||
assertEquals("firstName", "Mary", acct.getFirstName());
|
||||
assertEquals("lastName", "McInerney", acct.getLastName());
|
||||
assertEquals("password", "garbage", acct.getMd5Password());
|
||||
assertEquals("md5password", "garbage", acct.getMd5Password());
|
||||
assertEquals("argon2password", "betterGarbage", acct.getArgon2Password());
|
||||
assertEquals("expires", 0L, acct.getPasswordLinkExpires());
|
||||
assertEquals("loginCount", 7, acct.getLoginCount());
|
||||
assertEquals("lastLogin", 1122334455667788L, acct.getLastLoginTime());
|
||||
|
|
|
@ -9,6 +9,8 @@ import java.util.Map;
|
|||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import de.mkammerer.argon2.Argon2;
|
||||
import de.mkammerer.argon2.Argon2Factory;
|
||||
import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
|
||||
import edu.cornell.mannlib.vedit.beans.LoginStatusBean.AuthenticationSource;
|
||||
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
|
||||
|
@ -102,6 +104,49 @@ public class AuthenticatorStub extends Authenticator {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean md5HashIsNull(UserAccount userAccount){
|
||||
if(userAccount!=null) {
|
||||
if (userAccount.getMd5Password().compareTo("") == 0 ||
|
||||
userAccount.getMd5Password() == null)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies Argon2i hashing on a string.
|
||||
* Used by tests only with pre-specified values because the configuration
|
||||
* properties (runtime.properties) is not set at compile time.
|
||||
**/
|
||||
|
||||
public static String applyArgon2iEncodingStub(String raw) {
|
||||
Argon2 argon2 = Argon2Factory.create();
|
||||
try {
|
||||
return argon2.hash(200, 500, 1, raw);
|
||||
} catch (Exception e) {
|
||||
// This can't happen with a normal Java runtime.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isCurrentPasswordArgon2(UserAccount userAccount,
|
||||
String clearTextPassword) {
|
||||
if (userAccount == null) {
|
||||
return false;
|
||||
}
|
||||
if (clearTextPassword == null) {
|
||||
return false;
|
||||
}
|
||||
return verifyArgon2iHash(userAccount.getArgon2Password(),
|
||||
clearTextPassword);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isCurrentPassword(UserAccount userAccount,
|
||||
String clearTextPassword) {
|
||||
|
|
|
@ -98,7 +98,8 @@ public class ProgramLoginTest extends AbstractTestClass {
|
|||
user.setUri(uri);
|
||||
user.setPermissionSetUris(Collections
|
||||
.singleton(PermissionSets.URI_DBA));
|
||||
user.setMd5Password(Authenticator.applyMd5Encoding(password));
|
||||
user.setArgon2Password(AuthenticatorStub.applyArgon2iEncodingStub(password));
|
||||
user.setMd5Password("");
|
||||
user.setLoginCount(loginCount);
|
||||
user.setPasswordChangeRequired(loginCount == 0);
|
||||
return user;
|
||||
|
|
|
@ -191,7 +191,8 @@ public class AuthenticateTest extends AbstractTestClass {
|
|||
user.setEmailAddress(userInfo.username);
|
||||
user.setUri(userInfo.uri);
|
||||
user.setPermissionSetUris(userInfo.permissionSetUris);
|
||||
user.setMd5Password(Authenticator.applyMd5Encoding(userInfo.password));
|
||||
user.setArgon2Password(AuthenticatorStub.applyArgon2iEncodingStub(userInfo.password));
|
||||
user.setMd5Password("");
|
||||
user.setLoginCount(userInfo.loginCount);
|
||||
user.setPasswordChangeRequired(userInfo.loginCount == 0);
|
||||
return user;
|
||||
|
|
|
@ -91,20 +91,20 @@ public class UserAccountsDaoJenaTest extends AbstractTestClass {
|
|||
@Before
|
||||
public void createUserAccountValues() {
|
||||
user1 = userAccount(URI_USER1, "email@able.edu", "Zack", "Roberts",
|
||||
"garbage", "", 0L, false, 5, 12345678L, Status.ACTIVE, "user1",
|
||||
"garbage", "" ,"", 0L, false, 5, 12345678L, Status.ACTIVE, "user1",
|
||||
false, collection(URI_ROLE1), false, EMPTY);
|
||||
userNew = userAccount("", "email@here", "Joe", "Blow", "XXXX", "YYYY",
|
||||
userNew = userAccount("", "email@here", "Joe", "Blow", "XXXX","", "YYYY",
|
||||
0L, false, 1, 0L, Status.ACTIVE, "jblow", false, EMPTY, false,
|
||||
EMPTY);
|
||||
|
||||
userA = userAccount("", "aahern@here", "Alf", "Ahern", "XXXX", "YYYY",
|
||||
userA = userAccount("", "aahern@here", "Alf", "Ahern", "XXXX", "", "YYYY",
|
||||
0L, false, 1, 0L, Status.ACTIVE, "aahern", false, EMPTY, false,
|
||||
collection(URI_PROFILE1));
|
||||
userB = userAccount("", "email@here", "Betty", "Boop", "XXXX", "YYYY",
|
||||
userB = userAccount("", "email@here", "Betty", "Boop", "XXXX", "", "YYYY",
|
||||
0L, false, 1, 0L, Status.ACTIVE, "bboop", false, EMPTY, false,
|
||||
collection(URI_PROFILE1, URI_PROFILE2));
|
||||
userC = userAccount("", "ccallas@here", "Charlie", "Callas", "XXXX",
|
||||
"YYYY", 0L, false, 1, 0L, Status.ACTIVE, "ccallas", false,
|
||||
userC = userAccount("", "ccallas@here", "Charlie", "Callas", "XXXX", "",
|
||||
"YYYY", 0L, false, 1, 0L, Status.ACTIVE, "ccallas", false,
|
||||
EMPTY, false, collection(URI_PROFILE2));
|
||||
}
|
||||
|
||||
|
@ -179,7 +179,7 @@ public class UserAccountsDaoJenaTest extends AbstractTestClass {
|
|||
@Test
|
||||
public void updateUserAccountSuccess() {
|
||||
UserAccount orig = userAccount(URI_USER1, "updatedEmail@able.edu",
|
||||
"Ezekiel", "Roberts", "differentHash", "oldHash", 1L, false,
|
||||
"Ezekiel", "Roberts", "differentHash", "", "oldHash", 1L, false,
|
||||
43, 1020304050607080L, Status.ACTIVE, "updatedUser1", false,
|
||||
collection(URI_ROLE1, URI_ROLE3), false, EMPTY);
|
||||
|
||||
|
@ -379,7 +379,7 @@ public class UserAccountsDaoJenaTest extends AbstractTestClass {
|
|||
}
|
||||
|
||||
private UserAccount userAccount(String uri, String emailAddress,
|
||||
String firstName, String lastName, String md5Password,
|
||||
String firstName, String lastName, String argon2Password, String md5Password,
|
||||
String oldPassword, long passwordLinkExpires,
|
||||
boolean passwordChangeRequired, int loginCount, long lastLoginTime,
|
||||
Status status, String externalAuthId, boolean externalAuthOnly,
|
||||
|
@ -390,6 +390,7 @@ public class UserAccountsDaoJenaTest extends AbstractTestClass {
|
|||
ua.setEmailAddress(emailAddress);
|
||||
ua.setFirstName(firstName);
|
||||
ua.setLastName(lastName);
|
||||
ua.setArgon2Password(argon2Password);
|
||||
ua.setMd5Password(md5Password);
|
||||
ua.setOldPassword(oldPassword);
|
||||
ua.setPasswordLinkExpires(passwordLinkExpires);
|
||||
|
@ -411,6 +412,7 @@ public class UserAccountsDaoJenaTest extends AbstractTestClass {
|
|||
out.setEmailAddress(in.getEmailAddress());
|
||||
out.setFirstName(in.getFirstName());
|
||||
out.setLastName(in.getLastName());
|
||||
out.setArgon2Password(in.getArgon2Password());
|
||||
out.setMd5Password(in.getMd5Password());
|
||||
out.setOldPassword(in.getOldPassword());
|
||||
out.setPasswordLinkExpires(in.getPasswordLinkExpires());
|
||||
|
@ -433,7 +435,7 @@ public class UserAccountsDaoJenaTest extends AbstractTestClass {
|
|||
assertEquals("email", e.getEmailAddress(), a.getEmailAddress());
|
||||
assertEquals("first name", e.getFirstName(), a.getFirstName());
|
||||
assertEquals("last name", e.getLastName(), a.getLastName());
|
||||
assertEquals("password", e.getMd5Password(), a.getMd5Password());
|
||||
assertEquals("password", e.getArgon2Password(), e.getArgon2Password());
|
||||
assertEquals("old password", e.getOldPassword(), a.getOldPassword());
|
||||
assertEquals("link expires", e.getPasswordLinkExpires(),
|
||||
a.getPasswordLinkExpires());
|
||||
|
|
|
@ -113,6 +113,7 @@ mydomain:user08
|
|||
auth:firstName "Mary" ;
|
||||
auth:lastName "McInerney" ;
|
||||
auth:md5password "garbage" ;
|
||||
auth:argon2password "betterGarbage" ;
|
||||
auth:passwordChangeExpires 0 ;
|
||||
auth:loginCount 7 ;
|
||||
auth:lastLoginTime 1122334455667788 ;
|
||||
|
@ -138,6 +139,7 @@ mydomain:user10
|
|||
auth:firstName "Bob" ;
|
||||
auth:lastName "Caruso" ;
|
||||
auth:md5password "garbage" ;
|
||||
auth:argon2password "betterGarbage" ;
|
||||
auth:passwordChangeExpires 1100234965897 ;
|
||||
auth:loginCount 50 ;
|
||||
auth:lastLoginTime 1020304050607080 ;
|
||||
|
|
155
home/src/main/resources/config/example.runtime.properties
Normal file
155
home/src/main/resources/config/example.runtime.properties
Normal file
|
@ -0,0 +1,155 @@
|
|||
# -----------------------------------------------------------------------------
|
||||
#
|
||||
# Vitro runtime properties
|
||||
#
|
||||
# This file is provided as example.runtime.properties.
|
||||
#
|
||||
# Save a copy of this file as runtime.properties in your Vitro home directory,
|
||||
# and edit the properties as needed for your installation.
|
||||
#
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
#
|
||||
# This namespace will be used when generating URIs for objects created in the
|
||||
# editor. In order to serve linked data, the default namespace must be composed
|
||||
# as follows (optional elements in parentheses):
|
||||
#
|
||||
# scheme + server_name (+ port) (+ servlet_context) + "/individual/"
|
||||
#
|
||||
# For example, Cornell's default namespace is:
|
||||
#
|
||||
# http://vivo.cornell.edu/individual/
|
||||
#
|
||||
Vitro.defaultNamespace = http://vivo.mydomain.edu/individual/
|
||||
|
||||
#
|
||||
# URL of Solr context used in local Vitro search. This will usually consist of:
|
||||
# scheme + server_name + port + vitro_webapp_name + "solr"
|
||||
# In the standard installation, the Solr context will be on the same server as Vitro,
|
||||
# and in the same Tomcat instance. The path will be the Vitro webapp.name (specified
|
||||
# above) + "solr"
|
||||
# Example:
|
||||
# vitro.local.solr.url = http://localhost:8080/vitrosolr
|
||||
vitro.local.solr.url = http://localhost:8080/vitrosolr
|
||||
|
||||
#
|
||||
# Email parameters which VIVO can use to send mail. If these are left empty,
|
||||
# the "Contact Us" form will be disabled and users will not be notified of
|
||||
# changes to their accounts.
|
||||
#
|
||||
email.smtpHost = smtp.my.domain.edu
|
||||
email.replyTo = vivoAdmin@my.domain.edu
|
||||
|
||||
#
|
||||
# The basic parameters for a MySQL database connection. Change the end of the
|
||||
# URL to reflect your database name (if it is not "vitro"). Change the username
|
||||
# and password to match the authorized user you created in MySQL.
|
||||
#
|
||||
VitroConnection.DataSource.url = jdbc:mysql://localhost/vitro
|
||||
VitroConnection.DataSource.username = vitroweb
|
||||
VitroConnection.DataSource.password = vitrovitro
|
||||
|
||||
#
|
||||
# The maximum number of active connections in the database connection pool.
|
||||
# Increase this value to support a greater number of concurrent page requests.
|
||||
#
|
||||
VitroConnection.DataSource.pool.maxActive = 40
|
||||
|
||||
#
|
||||
# The maximum number of database connections that will be allowed
|
||||
# to remain idle in the connection pool. Default is 25%
|
||||
# of the maximum number of active connections.
|
||||
#
|
||||
VitroConnection.DataSource.pool.maxIdle = 10
|
||||
|
||||
#
|
||||
# Parameters to change in order to use VIVO with a database other than
|
||||
# MySQL.
|
||||
#
|
||||
VitroConnection.DataSource.dbtype = MySQL
|
||||
VitroConnection.DataSource.driver = com.mysql.jdbc.Driver
|
||||
VitroConnection.DataSource.validationQuery = SELECT 1
|
||||
|
||||
#
|
||||
# The email address of the root user for the VIVO application. The password
|
||||
# for this user is initially set to "rootPassword", but you will be asked to
|
||||
# change the password the first time you log in.
|
||||
#
|
||||
rootUser.emailAddress = root@myDomain.com
|
||||
|
||||
#
|
||||
# Argon2 password hashing parameters for time, memory and parallelism required to
|
||||
# compute a hash.
|
||||
#
|
||||
# A time cost defines the amount of computation realized and therefore the execution
|
||||
# time, given in a number of iterations.
|
||||
# A memory cost defines the memory usage, given in kibibytes
|
||||
# A parallelism degree defines the number of parallel threads
|
||||
# For determining the optimal values of the parameters for your setup please refer to
|
||||
# the white paper section 9
|
||||
# https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
|
||||
#
|
||||
argon2.parallelism =1
|
||||
argon2.memory = 1024
|
||||
argon2.time = 1000
|
||||
|
||||
#
|
||||
# How is a logged-in user associated with a particular Individual? One way is
|
||||
# for the Individual to have a property whose value is the username of the user.
|
||||
# This is the name of that property.
|
||||
#
|
||||
selfEditing.idMatchingProperty = http://vitro.mydomain.edu/ns#networkId
|
||||
|
||||
#
|
||||
# If an external authentication system like Shibboleth or CUWebAuth is to be
|
||||
# used, these properties say how the login button should be labeled, and which
|
||||
# HTTP header will contain the user ID from the authentication system. If such
|
||||
# as system is not to be used, leave these commented out. Consult the
|
||||
# installation instructions for more details.
|
||||
#
|
||||
#externalAuth.buttonText = Log in using BearCat Shibboleth
|
||||
#externalAuth.netIdHeaderName = remote_userID
|
||||
|
||||
#
|
||||
# Types of individual for which we can create proxy editors.
|
||||
# If this is omitted, defaults to http://www.w3.org/2002/07/owl#Thing
|
||||
proxy.eligibleTypeList = http://www.w3.org/2002/07/owl#Thing
|
||||
|
||||
#
|
||||
# Show only the most appropriate data values based on the Accept-Language
|
||||
# header supplied by the browser. Default is false if not set.
|
||||
#
|
||||
# RDFService.languageFilter = true
|
||||
|
||||
#
|
||||
# Tell VIVO to generate HTTP headers on its responses to facilitate caching the
|
||||
# profile pages that it creates.
|
||||
#
|
||||
# For more information, see
|
||||
# https://wiki.duraspace.org/display/VIVO/Use+HTTP+caching+to+improve+performance
|
||||
#
|
||||
# Developers will likely want to leave caching disabled, since a change to a
|
||||
# Freemarker template or to a Java class would not cause the page to be
|
||||
# considered stale.
|
||||
#
|
||||
# http.createCacheHeaders = true
|
||||
|
||||
#
|
||||
# Force VIVO to use a specific language or Locale instead of those
|
||||
# specified by the browser. This affects RDF data retrieved from the model,
|
||||
# if RDFService.languageFilter is true. This also affects the text of pages
|
||||
# that have been modified to support multiple languages.
|
||||
#
|
||||
# languages.forceLocale = en_US
|
||||
|
||||
#
|
||||
# A list of supported languages or Locales that the user may choose to
|
||||
# use instead of the one specified by the browser. Selection images must
|
||||
# be available in the i18n/images directory of the theme. This affects
|
||||
# RDF data retrieved from the model, if RDFService.languageFilter is true.
|
||||
# This also affects the text of pages that have been modified to support
|
||||
# multiple languages.
|
||||
#
|
||||
# This should not be used with languages.forceLocale, which will override it.
|
||||
#
|
||||
# languages.selectableLocales = en, es, fr
|
|
@ -77,6 +77,22 @@ VitroConnection.DataSource.validationQuery = SELECT 1
|
|||
#
|
||||
rootUser.emailAddress = root@myDomain.com
|
||||
|
||||
#
|
||||
# Argon2 password hashing parameters for time, memory and parallelism required to
|
||||
# compute a hash.
|
||||
#
|
||||
# A time cost defines the amount of computation realized and therefore the execution
|
||||
# time, given in a number of iterations.
|
||||
# A memory cost defines the memory usage, given in kibibytes
|
||||
# A parallelism degree defines the number of parallel threads
|
||||
# For determining the optimal values of the parameters for your setup please refer to
|
||||
# the white paper section 9
|
||||
# https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
|
||||
#
|
||||
argon2.parallelism =1
|
||||
argon2.memory = 1024
|
||||
argon2.time = 1000
|
||||
|
||||
#
|
||||
# How is a logged-in user associated with a particular Individual? One way is
|
||||
# for the Individual to have a property whose value is the username of the user.
|
||||
|
|
|
@ -123,6 +123,8 @@ error_no_new_password = Please enter your new password.
|
|||
error_passwords_dont_match = The passwords entered do not match.
|
||||
error_password_length = Please enter a password between {0} and {1} characters in length.
|
||||
error_previous_password = Your new password cannot match the current one.
|
||||
password_system_has_changed = Our password system has been upgraded to make it more secure. \
|
||||
Please enter a new password.
|
||||
|
||||
search_accounts_button = Search accounts
|
||||
accounts_search_results = Search results for
|
||||
|
|
Loading…
Add table
Reference in a new issue