result = SystemChecker.benchmarkBcrypt(BCRYPT_MAX_MILLISECONDS);
+
+ final BcryptFunction prototype = result.getPrototype();
+ int rounds = prototype.getLogarithmicRounds();
+ long realElapsed = result.getElapsed();
+
+
+ log.info("Using bcrypt with {} logarithmic rounds. Elapsed time={}", rounds, realElapsed);
LocalAuthenticationRealm realm = new LocalAuthenticationRealm(
manager.getValidator(),
@@ -82,7 +97,9 @@ public ConqueryAuthenticationRealm createRealm(ManagerNode manager) {
storeName,
directory,
passwordStoreConfig,
- jwtDuration);
+ jwtDuration,
+ prototype
+ );
UserAuthenticationManagementProcessor processor = new UserAuthenticationManagementProcessor(realm, manager.getStorage());
// Register resources for users to exchange username and password for an access token
diff --git a/backend/src/main/java/com/bakdata/conquery/resources/unprotected/TokenResource.java b/backend/src/main/java/com/bakdata/conquery/resources/unprotected/TokenResource.java
index 23cd8c62d6..da4be49818 100644
--- a/backend/src/main/java/com/bakdata/conquery/resources/unprotected/TokenResource.java
+++ b/backend/src/main/java/com/bakdata/conquery/resources/unprotected/TokenResource.java
@@ -1,6 +1,7 @@
package com.bakdata.conquery.resources.unprotected;
import javax.ws.rs.Consumes;
+import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@@ -10,9 +11,12 @@
import com.bakdata.conquery.apiv1.auth.UsernamePasswordToken;
import com.bakdata.conquery.models.auth.basic.AccessTokenCreator;
import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.shiro.authc.AuthenticationException;
@Path("/")
@AllArgsConstructor
+@Slf4j
public class TokenResource {
private final AccessTokenCreator realm;
@@ -21,6 +25,12 @@ public class TokenResource {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public JwtWrapper getToken(UsernamePasswordToken token) {
- return new JwtWrapper(realm.createAccessToken(token.getUser(), token.getPassword()));
+ try {
+ return new JwtWrapper(realm.createAccessToken(token.getUser(), token.getPassword()));
+ }
+ catch (AuthenticationException e) {
+ log.warn("Failed to authorize request", e);
+ throw new NotAuthorizedException("Failed to authenticate request. The cause has been logged.");
+ }
}
}
diff --git a/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java b/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java
index 3c8ed27c4f..18e949e1b3 100644
--- a/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java
+++ b/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java
@@ -117,7 +117,7 @@ public void dataset() throws IOException, JSONException {
@Test
public void passwordCredential() throws IOException, JSONException {
- PasswordCredential credential = new PasswordCredential("testPassword".toCharArray());
+ PasswordCredential credential = new PasswordCredential("testPassword");
SerializationTestUtil
.forType(PasswordCredential.class)
diff --git a/backend/src/test/java/com/bakdata/conquery/models/auth/IdpDelegatingAccessTokenCreatorTest.java b/backend/src/test/java/com/bakdata/conquery/models/auth/IdpDelegatingAccessTokenCreatorTest.java
index d6a3dfb1fc..0d30bd9e8a 100644
--- a/backend/src/test/java/com/bakdata/conquery/models/auth/IdpDelegatingAccessTokenCreatorTest.java
+++ b/backend/src/test/java/com/bakdata/conquery/models/auth/IdpDelegatingAccessTokenCreatorTest.java
@@ -98,7 +98,7 @@ private static void initOIDCServer() {
@Test
public void vaildUsernamePassword() {
- String jwt = idpDelegatingAccessTokenCreator.createAccessToken(USER_1_NAME, USER_1_PASSWORD.toCharArray());
+ String jwt = idpDelegatingAccessTokenCreator.createAccessToken(USER_1_NAME, USER_1_PASSWORD);
assertThat(jwt).isEqualTo(USER_1_TOKEN);
}
@@ -107,7 +107,7 @@ public void vaildUsernamePassword() {
public void invaildUsernamePassword() {
log.info("This test will print an Error below.");
assertThatThrownBy(
- () -> idpDelegatingAccessTokenCreator.createAccessToken(USER_1_NAME, "bad_password".toCharArray()))
+ () -> idpDelegatingAccessTokenCreator.createAccessToken(USER_1_NAME, "bad_password"))
.isInstanceOf(IllegalStateException.class);
}
diff --git a/backend/src/test/java/com/bakdata/conquery/models/auth/LocalAuthRealmTest.java b/backend/src/test/java/com/bakdata/conquery/models/auth/LocalAuthRealmTest.java
index 3c2dd2d9fa..fa560e3c01 100644
--- a/backend/src/test/java/com/bakdata/conquery/models/auth/LocalAuthRealmTest.java
+++ b/backend/src/test/java/com/bakdata/conquery/models/auth/LocalAuthRealmTest.java
@@ -4,7 +4,6 @@
import java.io.File;
import java.nio.file.Files;
-import java.util.List;
import com.auth0.jwt.JWT;
import com.bakdata.conquery.apiv1.auth.PasswordCredential;
@@ -14,13 +13,13 @@
import com.bakdata.conquery.models.auth.conquerytoken.ConqueryTokenRealm;
import com.bakdata.conquery.models.auth.entities.User;
import com.bakdata.conquery.models.config.XodusConfig;
-import com.bakdata.conquery.models.identifiable.ids.specific.UserId;
import com.bakdata.conquery.util.NonPersistentStoreFactory;
+import com.password4j.BcryptFunction;
import io.dropwizard.jersey.validation.Validators;
import io.dropwizard.util.Duration;
import org.apache.commons.io.FileUtils;
-import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.BearerToken;
+import org.apache.shiro.authc.CredentialsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.util.LifecycleUtils;
import org.junit.jupiter.api.AfterAll;
@@ -53,7 +52,16 @@ public void setupAll() throws Exception {
conqueryTokenRealm = new ConqueryTokenRealm(storage);
- realm = new LocalAuthenticationRealm(Validators.newValidator(), Jackson.BINARY_MAPPER, conqueryTokenRealm, "localtestRealm", tmpDir, new XodusConfig(), Duration.hours(1));
+ realm =
+ new LocalAuthenticationRealm(
+ Validators.newValidator(),
+ Jackson.BINARY_MAPPER, conqueryTokenRealm,
+ "localtestRealm",
+ tmpDir,
+ new XodusConfig(),
+ Duration.hours(4),
+ BcryptFunction.getInstance(4)
+ ); // 4 is minimum
LifecycleUtils.init(realm);
}
@@ -61,9 +69,9 @@ public void setupAll() throws Exception {
public void setupEach() {
// Create User in Realm
user1 = new User("TestUser", "Test User", storage);
- PasswordCredential user1Password = new PasswordCredential("testPassword".toCharArray());
+ PasswordCredential user1Password = new PasswordCredential("testPassword");
storage.addUser(user1);
- realm.addUser(user1, List.of(user1Password));
+ realm.addUser(user1, user1Password);
}
@AfterEach
@@ -81,32 +89,32 @@ public void cleanUpAll() {
@Test
public void testEmptyUsername() {
- assertThatThrownBy(() -> realm.createAccessToken("", "testPassword".toCharArray()))
+ assertThatThrownBy(() -> realm.createAccessToken("", "testPassword"))
.isInstanceOf(IncorrectCredentialsException.class).hasMessageContaining("Username was empty");
}
@Test
public void testEmptyPassword() {
- assertThatThrownBy(() -> realm.createAccessToken("TestUser", "".toCharArray()))
+ assertThatThrownBy(() -> realm.createAccessToken("TestUser", ""))
.isInstanceOf(IncorrectCredentialsException.class).hasMessageContaining("Password was empty");
}
@Test
public void testWrongPassword() {
- assertThatThrownBy(() -> realm.createAccessToken("TestUser", "wrongPassword".toCharArray()))
- .isInstanceOf(AuthenticationException.class).hasMessageContaining("Provided username or password was not valid.");
+ assertThatThrownBy(() -> realm.createAccessToken("TestUser", "wrongPassword"))
+ .isInstanceOf(IncorrectCredentialsException.class).hasMessageContaining("Password was was invalid for user");
}
@Test
public void testWrongUsername() {
- assertThatThrownBy(() -> realm.createAccessToken("NoTestUser", "testPassword".toCharArray()))
- .isInstanceOf(AuthenticationException.class).hasMessageContaining("Provided username or password was not valid.");
+ assertThatThrownBy(() -> realm.createAccessToken("NoTestUser", "testPassword"))
+ .isInstanceOf(CredentialsException.class).hasMessageContaining("No password hash was found for user");
}
@Test
public void testValidUsernamePassword() {
// Right username and password should yield a JWT
- String jwt = realm.createAccessToken("TestUser", "testPassword".toCharArray());
+ String jwt = realm.createAccessToken("TestUser", "testPassword");
assertThatCode(() -> JWT.decode(jwt)).doesNotThrowAnyException();
assertThat(conqueryTokenRealm.doGetAuthenticationInfo(new BearerToken(jwt)).getPrincipals().getPrimaryPrincipal())
@@ -116,13 +124,13 @@ public void testValidUsernamePassword() {
@Test
public void testUserUpdate() {
- realm.updateUser(user1, List.of(new PasswordCredential("newTestPassword".toCharArray())));
+ realm.updateUser(user1, new PasswordCredential("newTestPassword"));
// Wrong (old) password
- assertThatThrownBy(() -> realm.createAccessToken("TestUser", "testPassword".toCharArray()))
- .isInstanceOf(AuthenticationException.class).hasMessageContaining("Provided username or password was not valid.");
+ assertThatThrownBy(() -> realm.createAccessToken("TestUser", "testPassword"))
+ .isInstanceOf(IncorrectCredentialsException.class).hasMessageContaining("Password was was invalid for user");
// Right (new) password
- String jwt = realm.createAccessToken("TestUser", "newTestPassword".toCharArray());
+ String jwt = realm.createAccessToken("TestUser", "newTestPassword");
assertThatCode(() -> JWT.decode(jwt)).doesNotThrowAnyException();
}
@@ -130,8 +138,8 @@ public void testUserUpdate() {
public void testRemoveUser() {
realm.removeUser(user1);
// Wrong password
- assertThatThrownBy(() -> realm.createAccessToken("TestUser", "testPassword".toCharArray()))
- .isInstanceOf(AuthenticationException.class).hasMessageContaining("Provided username or password was not valid.");
+ assertThatThrownBy(() -> realm.createAccessToken("TestUser", "testPassword"))
+ .isInstanceOf(CredentialsException.class).hasMessageContaining("No password hash was found for user");
}
}
diff --git a/backend/src/test/java/com/bakdata/conquery/util/PasswordHelperTest.java b/backend/src/test/java/com/bakdata/conquery/util/PasswordHelperTest.java
new file mode 100644
index 0000000000..b6ae79e8c9
--- /dev/null
+++ b/backend/src/test/java/com/bakdata/conquery/util/PasswordHelperTest.java
@@ -0,0 +1,54 @@
+package com.bakdata.conquery.util;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.stream.Stream;
+
+import com.bakdata.conquery.models.auth.basic.PasswordHelper;
+import com.password4j.Argon2Function;
+import com.password4j.BcryptFunction;
+import com.password4j.CompressedPBKDF2Function;
+import com.password4j.HashingFunction;
+import com.password4j.ScryptFunction;
+import com.password4j.types.Argon2;
+import com.password4j.types.Bcrypt;
+import com.password4j.types.Hmac;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class PasswordHelperTest {
+
+
+ /**
+ * Arguments where generated with:
+ *
+ *
+ * import com.password4j.Password;
+ *
+ * class Scratch {
+ * public static void main(String[] args) {
+ * System.out.println(Password.hash("test").withArgon2().getResult());
+ * System.out.println(Password.hash("test").withScrypt().getResult());
+ * System.out.println(Password.hash("test").withBcrypt().getResult());
+ * System.out.println(Password.hash("test").withCompressedPBKDF2().getResult());
+ * }
+ * }
+ *
+ */
+ static Stream arguments() {
+ return Stream.of(
+ Arguments.arguments("$argon2id$v=19$m=15360,t=2,p=1$r35m/UGz8lq4ICjcNkb2GUcfYub07450QRTTapYwiJCQDOI9Maa0dlym/iL0AceTNNXgaxLUyGB5EfJoqr+Wng$WVnHZU8uwvufgWPlVh5T+MnTtX5Ry0hhCD0ej90L0Kk", Argon2Function.getInstance(15360, 2, 1, 32, Argon2.ID)),
+ Arguments.arguments("$100801$SBpPHCtLT+2FbJ2BS49J4sgRXfvduVm17U9yd0Ygky/3MgUgK1r4LMixKSQX4LQjSEuE6tV8ibABXXAr9tCZKA==$aPTssj2maVw34QgrhRIsUHu6irB1NrjiFpdpUXFHHA+XhjPG03PKrbj5CBXJx3cCUosU/IARQliSW2LWRLFtiw==", ScryptFunction.getInstance(65536, 8, 1, 64)),
+ Arguments.arguments("$2b$10$YMPj.MoAs81tO8HzrCYxnOujaPwbu5SGsSrdNyxdIJ9BlBIv9i0t.", BcryptFunction.getInstance(Bcrypt.B, 10)),
+ Arguments.arguments("$3$1331439861760256$+Rqke26gKhtP60UkVR2a3SfszrOkVrMiJ6LZUWvl2vI5OpW815zKiod8Sdz3aOcuajo6c1iKEXcWjk61emmgTw==$CiH0mwqibUZD5R5HqFNpaYCkWjiYcTQe0sjG+4ZYw/A=", CompressedPBKDF2Function.getInstance(Hmac.SHA256, 310000, 256))
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("arguments")
+ void test(String hash, HashingFunction hashProvider) {
+ assertThat(PasswordHelper.getHashingFunction(hash)).isEqualTo(hashProvider);
+
+ }
+}