From 9143bc748f681eb35c3329fdb967c957d21f0106 Mon Sep 17 00:00:00 2001 From: Elisabeth Schulz Date: Tue, 4 Aug 2020 10:13:08 +0200 Subject: [PATCH] KEYCLOAK-13983 Include algorithm parameters Include suggestions made by @mposolda to enable more generic usage --- .../credential/PasswordCredentialModel.java | 6 ++- .../dto/PasswordCredentialData.java | 18 ++++--- .../credential/dto/PasswordSecretData.java | 16 +++--- .../credential/CredentialModelTest.java | 49 ++++++++++++++----- .../node_modules/rcue/dist/img/git-logo.svg | 21 -------- 5 files changed, 60 insertions(+), 50 deletions(-) delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/node_modules/rcue/dist/img/git-logo.svg diff --git a/server-spi/src/main/java/org/keycloak/models/credential/PasswordCredentialModel.java b/server-spi/src/main/java/org/keycloak/models/credential/PasswordCredentialModel.java index 50a3c25d4f..044e70481b 100644 --- a/server-spi/src/main/java/org/keycloak/models/credential/PasswordCredentialModel.java +++ b/server-spi/src/main/java/org/keycloak/models/credential/PasswordCredentialModel.java @@ -15,11 +15,15 @@ public class PasswordCredentialModel extends CredentialModel { private final PasswordCredentialData credentialData; private final PasswordSecretData secretData; - public PasswordCredentialModel(PasswordCredentialData credentialData, PasswordSecretData secretData) { + private PasswordCredentialModel(PasswordCredentialData credentialData, PasswordSecretData secretData) { this.credentialData = credentialData; this.secretData = secretData; } + public static PasswordCredentialModel createFromValues(PasswordCredentialData credentialData, PasswordSecretData secretData) { + return new PasswordCredentialModel(credentialData, secretData); + } + public static PasswordCredentialModel createFromValues(String algorithm, byte[] salt, int hashIterations, String encodedPassword){ PasswordCredentialData credentialData = new PasswordCredentialData(hashIterations, algorithm); PasswordSecretData secretData = new PasswordSecretData(encodedPassword, salt); diff --git a/server-spi/src/main/java/org/keycloak/models/credential/dto/PasswordCredentialData.java b/server-spi/src/main/java/org/keycloak/models/credential/dto/PasswordCredentialData.java index 3d1b98aa57..1a46857a60 100644 --- a/server-spi/src/main/java/org/keycloak/models/credential/dto/PasswordCredentialData.java +++ b/server-spi/src/main/java/org/keycloak/models/credential/dto/PasswordCredentialData.java @@ -2,14 +2,16 @@ package org.keycloak.models.credential.dto; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import org.keycloak.common.util.MultivaluedHashMap; import java.util.Collections; +import java.util.List; import java.util.Map; public class PasswordCredentialData { private final int hashIterations; private final String algorithm; - private final Map algorithmData; + private final MultivaluedHashMap additionalParameters; /** * Creator for standard algorithms (no algorithm tuning beyond hash iterations) @@ -24,13 +26,13 @@ public class PasswordCredentialData { * Creator for custom algorithms (algorithm with tuning parameters beyond simple has iterations) * @param hashIterations iterations * @param algorithm algorithm id - * @param algorithmData additional tuning parameters + * @param additionalParameters additional tuning parameters */ @JsonCreator - public PasswordCredentialData(@JsonProperty("hashIterations") int hashIterations, @JsonProperty("algorithm") String algorithm, @JsonProperty("algorithmData") Map algorithmData) { + public PasswordCredentialData(@JsonProperty("hashIterations") int hashIterations, @JsonProperty("algorithm") String algorithm, @JsonProperty("algorithmData") Map> additionalParameters) { this.hashIterations = hashIterations; this.algorithm = algorithm; - this.algorithmData = algorithmData == null ? Collections.emptyMap() : Collections.unmodifiableMap(algorithmData); + this.additionalParameters = new MultivaluedHashMap<>(additionalParameters == null ? Collections.emptyMap() : additionalParameters); } @@ -44,11 +46,11 @@ public class PasswordCredentialData { } /** - * Returns an immutable map of algorithm-specific settings. These settings may include additional - * parameters such as Bcrypt memory-tuning parameters + * Returns a map of algorithm-specific settings. These settings may include additional + * parameters such as Bcrypt memory-tuning parameters. It should be used immutably. * @return algorithm data */ - public Map getAlgorithmData() { - return algorithmData; + public MultivaluedHashMap getAdditionalParameters() { + return additionalParameters; } } diff --git a/server-spi/src/main/java/org/keycloak/models/credential/dto/PasswordSecretData.java b/server-spi/src/main/java/org/keycloak/models/credential/dto/PasswordSecretData.java index c14433cd1c..0dd9ac4be5 100644 --- a/server-spi/src/main/java/org/keycloak/models/credential/dto/PasswordSecretData.java +++ b/server-spi/src/main/java/org/keycloak/models/credential/dto/PasswordSecretData.java @@ -2,12 +2,14 @@ package org.keycloak.models.credential.dto; import java.io.IOException; import java.util.Collections; +import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.jboss.logging.Logger; import org.keycloak.common.util.Base64; +import org.keycloak.common.util.MultivaluedHashMap; public class PasswordSecretData { @@ -15,18 +17,18 @@ public class PasswordSecretData { private final String value; private final byte[] salt; - private final Map algorithmData; + private final MultivaluedHashMap additionalParameters; /** * Creator with the option to provide customized secret data (multiple salt values, chiefly) * @param value hash value * @param salt salt value - * @param algorithmData additional data required by the algorithm + * @param additionalParameters additional data required by the algorithm * @throws IOException invalid base64 in salt value */ @JsonCreator - public PasswordSecretData(@JsonProperty("value") String value, @JsonProperty("salt") String salt, @JsonProperty("algorithmData") Map algorithmData) throws IOException { - this.algorithmData = algorithmData == null ? Collections.emptyMap() : Collections.unmodifiableMap(algorithmData); + public PasswordSecretData(@JsonProperty("value") String value, @JsonProperty("salt") String salt, @JsonProperty("algorithmData") Map> additionalParameters) throws IOException { + this.additionalParameters = new MultivaluedHashMap<>(additionalParameters == null ? Collections.emptyMap() : additionalParameters); if (salt == null || "__SALT__".equals(salt)) { this.value = value; @@ -46,7 +48,7 @@ public class PasswordSecretData { public PasswordSecretData(String value, byte[] salt) { this.value = value; this.salt = salt; - this.algorithmData = Collections.emptyMap(); + this.additionalParameters = new MultivaluedHashMap<>(); } public String getValue() { @@ -57,7 +59,7 @@ public class PasswordSecretData { return salt; } - public Map getAlgorithmData() { - return algorithmData; + public MultivaluedHashMap getAdditionalParameters() { + return additionalParameters; } } diff --git a/server-spi/src/test/java/org/keycloak/models/credential/CredentialModelTest.java b/server-spi/src/test/java/org/keycloak/models/credential/CredentialModelTest.java index 4808f68fd3..20e5998af9 100644 --- a/server-spi/src/test/java/org/keycloak/models/credential/CredentialModelTest.java +++ b/server-spi/src/test/java/org/keycloak/models/credential/CredentialModelTest.java @@ -2,12 +2,13 @@ package org.keycloak.models.credential; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; +import org.keycloak.credential.CredentialModel; import org.keycloak.models.credential.dto.PasswordCredentialData; import org.keycloak.models.credential.dto.PasswordSecretData; import java.io.IOException; -import java.lang.reflect.Array; import java.util.Arrays; +import java.util.Base64; import java.util.Collections; import static org.hamcrest.CoreMatchers.*; @@ -17,6 +18,28 @@ public class CredentialModelTest { private ObjectMapper mapper = new ObjectMapper(); + @Test + public void canDeserializeMinimalJson() { + CredentialModel model = new CredentialModel(); + model.setCredentialData("{\"hashIterations\": 10000, \"algorithm\": \"custom\"}"); + model.setSecretData("{\"value\": \"the value\", \"salt\": \"saltValu\"}"); + + PasswordCredentialModel decoded = PasswordCredentialModel.createFromCredentialModel(model); + assertThat(decoded, notNullValue()); + assertThat(decoded.getPasswordCredentialData(), notNullValue()); + assertThat(decoded.getPasswordCredentialData().getAlgorithm(), equalTo("custom")); + assertThat(decoded.getPasswordCredentialData().getHashIterations(), equalTo(10000)); + assertThat(decoded.getPasswordCredentialData().getAdditionalParameters(), equalTo(Collections.emptyMap())); + + assertThat(decoded.getPasswordSecretData(), notNullValue()); + assertThat(decoded.getPasswordSecretData().getValue(), equalTo("the value")); + assertThat(decoded.getPasswordSecretData().getSalt(), notNullValue()); + String base64Salt = Base64.getEncoder().encodeToString(decoded.getPasswordSecretData().getSalt()); + assertThat(base64Salt, equalTo("saltValu")); + assertThat(decoded.getPasswordSecretData().getAdditionalParameters(), equalTo(Collections.emptyMap())); + + } + @Test public void canCreateDefaultCredentialModel() { PasswordCredentialModel model = PasswordCredentialModel.createFromValues("pbkdf2", new byte[32], 1000, "secretValue"); @@ -24,27 +47,27 @@ public class CredentialModelTest { assertThat(model.getPasswordCredentialData(), notNullValue()); assertThat(model.getPasswordCredentialData().getAlgorithm(), equalTo("pbkdf2")); assertThat(model.getPasswordCredentialData().getHashIterations(), equalTo(1000)); - assertThat(model.getPasswordCredentialData().getAlgorithmData(), equalTo(Collections.emptyMap())); + assertThat(model.getPasswordCredentialData().getAdditionalParameters(), equalTo(Collections.emptyMap())); assertThat(model.getPasswordSecretData(), notNullValue()); - assertThat(model.getPasswordSecretData().getAlgorithmData(), equalTo(Collections.emptyMap())); + assertThat(model.getPasswordSecretData().getAdditionalParameters(), equalTo(Collections.emptyMap())); assertThat(model.getPasswordSecretData().getValue(), equalTo("secretValue")); assertThat(Arrays.equals(model.getPasswordSecretData().getSalt(), new byte[32]), is(true)); } @Test public void canCreatedExtendedCredentialModel() throws IOException { - PasswordCredentialData credentialData = new PasswordCredentialData(1000, "bcrypt", Collections.singletonMap("cost", "18")); - PasswordSecretData secretData = new PasswordSecretData("secretValue", "AAAAAAAAAAAAAAAA", Collections.singletonMap("salt2", "BBBBBBBBBBBBBBBB")); - PasswordCredentialModel model = new PasswordCredentialModel(credentialData, secretData); + PasswordCredentialData credentialData = new PasswordCredentialData(1000, "bcrypt", Collections.singletonMap("cost", Collections.singletonList("18"))); + PasswordSecretData secretData = new PasswordSecretData("secretValue", "AAAAAAAAAAAAAAAA", Collections.singletonMap("salt2", Collections.singletonList("BBBBBBBBBBBBBBBB"))); + PasswordCredentialModel model = PasswordCredentialModel.createFromValues(credentialData, secretData); assertThat(model.getPasswordCredentialData(), notNullValue()); assertThat(model.getPasswordCredentialData().getAlgorithm(), equalTo("bcrypt")); assertThat(model.getPasswordCredentialData().getHashIterations(), equalTo(1000)); - assertThat(model.getPasswordCredentialData().getAlgorithmData(), equalTo(Collections.singletonMap("cost", "18"))); + assertThat(model.getPasswordCredentialData().getAdditionalParameters(), equalTo(Collections.singletonMap("cost", Collections.singletonList("18")))); assertThat(model.getPasswordSecretData(), notNullValue()); - assertThat(model.getPasswordSecretData().getAlgorithmData(), equalTo(Collections.singletonMap("salt2", "BBBBBBBBBBBBBBBB"))); + assertThat(model.getPasswordSecretData().getAdditionalParameters(), equalTo(Collections.singletonMap("salt2", Collections.singletonList("BBBBBBBBBBBBBBBB")))); assertThat(model.getPasswordSecretData().getValue(), equalTo("secretValue")); assertThat(Arrays.equals(model.getPasswordSecretData().getSalt(), new byte[12]), is(true)); } @@ -60,7 +83,7 @@ public class CredentialModelTest { PasswordCredentialData pcdOriginal = model.getPasswordCredentialData(); PasswordCredentialData pcdRoundtrip = mapper.readValue(mapper.writeValueAsString(pcdOriginal), PasswordCredentialData.class); - assertThat(pcdRoundtrip.getAlgorithmData(), equalTo(pcdOriginal.getAlgorithmData())); + assertThat(pcdRoundtrip.getAdditionalParameters(), equalTo(pcdOriginal.getAdditionalParameters())); assertThat(pcdRoundtrip.getAlgorithm(), equalTo(pcdOriginal.getAlgorithm())); assertThat(pcdRoundtrip.getHashIterations(), equalTo(pcdOriginal.getHashIterations())); @@ -69,14 +92,14 @@ public class CredentialModelTest { assertThat(psdRoundtrip.getValue(), equalTo(psdOriginal.getValue())); assertThat(psdRoundtrip.getSalt(), equalTo(psdOriginal.getSalt())); - assertThat(psdRoundtrip.getAlgorithmData(), equalTo(psdRoundtrip.getAlgorithmData())); + assertThat(psdRoundtrip.getAdditionalParameters(), equalTo(psdRoundtrip.getAdditionalParameters())); } @Test public void roudtripToJsonExtendedCredentialModel() throws IOException { - PasswordCredentialData credentialData = new PasswordCredentialData(1000, "bcrypt", Collections.singletonMap("cost", "18")); - PasswordSecretData secretData = new PasswordSecretData("secretValue", "AAAAAAAAAAAAAAAA", Collections.singletonMap("salt2", "BBBBBBBBBBBBBBBB")); - PasswordCredentialModel model = new PasswordCredentialModel(credentialData, secretData); + PasswordCredentialData credentialData = new PasswordCredentialData(1000, "bcrypt", Collections.singletonMap("cost", Collections.singletonList("18"))); + PasswordSecretData secretData = new PasswordSecretData("secretValue", "AAAAAAAAAAAAAAAA", Collections.singletonMap("salt2", Collections.singletonList("BBBBBBBBBBBBBBBB"))); + PasswordCredentialModel model = PasswordCredentialModel.createFromValues(credentialData, secretData); roundTripAndVerify(model); } diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/rcue/dist/img/git-logo.svg b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/rcue/dist/img/git-logo.svg deleted file mode 100644 index 825d8ecc6b..0000000000 --- a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/rcue/dist/img/git-logo.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - -