KEYCLOAK-12281 Fix export/import for users that have custom credential algorithms with no salt

This commit is contained in:
mposolda 2019-12-19 16:32:05 +01:00 committed by Stian Thorgersen
parent b90a0307ea
commit f0d95da52d
3 changed files with 51 additions and 2 deletions

View file

@ -1,18 +1,40 @@
package org.keycloak.models.credential.dto; package org.keycloak.models.credential.dto;
import java.io.IOException;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Base64;
public class PasswordSecretData { public class PasswordSecretData {
public static final Logger logger = Logger.getLogger(PasswordSecretData.class);
private final String value; private final String value;
private final byte[] salt; private final byte[] salt;
@JsonCreator @JsonCreator
public PasswordSecretData(@JsonProperty("value") String value, @JsonProperty("salt") byte[] salt) { public PasswordSecretData(@JsonProperty("value") String value, @JsonProperty("salt") String salt) {
this(value, decodeSalt(salt));
}
public PasswordSecretData(String value, byte[] salt) {
this.value = value; this.value = value;
this.salt = salt; this.salt = salt;
} }
private static byte[] decodeSalt(String salt) {
try {
return Base64.decode(salt);
} catch (IOException ioe) {
// Could happen under some corner cases that value is still placeholder value "__SALT__" . For example when importing JSON from
// previous version and using custom hash provider without salt support.
logger.tracef("Can't base64 decode the salt %s . Fallback to null salt", salt);
return null;
}
}
public String getValue() { public String getValue() {
return value; return value;
} }

View file

@ -28,6 +28,8 @@ import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.constants.KerberosConstants; import org.keycloak.common.constants.KerberosConstants;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.LDAPConstants; import org.keycloak.models.LDAPConstants;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.models.credential.dto.PasswordCredentialData;
import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory; import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
@ -39,6 +41,7 @@ import org.keycloak.representations.idm.ClientMappingsRepresentation;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientScopeRepresentation; import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation;
@ -58,6 +61,7 @@ import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.client.KeycloakTestingClient; import org.keycloak.testsuite.client.KeycloakTestingClient;
import org.keycloak.testsuite.util.RealmRepUtil; import org.keycloak.testsuite.util.RealmRepUtil;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -72,6 +76,8 @@ import java.util.stream.Collectors;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.keycloak.util.JsonSerialization;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
/** /**
@ -82,7 +88,7 @@ public class ExportImportUtil {
// In the old testsuite, this method exists as a public method of ImportTest from the model package. // In the old testsuite, this method exists as a public method of ImportTest from the model package.
// However, model package is not ready to be migrated yet. // However, model package is not ready to be migrated yet.
public static void assertDataImportedInRealm(Keycloak adminClient, KeycloakTestingClient testingClient, RealmRepresentation realm) { public static void assertDataImportedInRealm(Keycloak adminClient, KeycloakTestingClient testingClient, RealmRepresentation realm) throws IOException {
Assert.assertTrue(realm.isVerifyEmail()); Assert.assertTrue(realm.isVerifyEmail());
Assert.assertEquals((Integer)3600000, realm.getOfflineSessionIdleTimeout()); Assert.assertEquals((Integer)3600000, realm.getOfflineSessionIdleTimeout());
Assert.assertEquals((Integer)1500, realm.getAccessTokenLifespanForImplicitFlow()); Assert.assertEquals((Integer)1500, realm.getAccessTokenLifespanForImplicitFlow());
@ -179,6 +185,13 @@ public class ExportImportUtil {
// user with creation timestamp as string in import // user with creation timestamp as string in import
Assert.assertEquals(new Long(123655), loginclient.getCreatedTimestamp()); Assert.assertEquals(new Long(123655), loginclient.getCreatedTimestamp());
UserRepresentation hashedPasswordUser = findByUsername(realmRsc, "hashedpassworduser");
CredentialRepresentation password = realmRsc.users().get(hashedPasswordUser.getId()).credentials().stream()
.filter(credential -> PasswordCredentialModel.TYPE.equals(credential.getType()))
.findFirst().get();
PasswordCredentialData credentialData = JsonSerialization.readValue(password.getCredentialData(), PasswordCredentialData.class);
Assert.assertEquals(1234, credentialData.getHashIterations());
List<RoleRepresentation> realmRoles = realmRolesForUser(realmRsc, admin); List<RoleRepresentation> realmRoles = realmRolesForUser(realmRsc, admin);
Assert.assertEquals(1, realmRoles.size()); Assert.assertEquals(1, realmRoles.size());
Assert.assertEquals("admin", realmRoles.iterator().next().getName()); Assert.assertEquals("admin", realmRoles.iterator().next().getName());

View file

@ -150,6 +150,20 @@
} }
] ]
}, },
{
"username": "hashedpassworduser",
"createdTimestamp" : "123655",
"enabled": true,
"credentials": [
{
"id": "b0c22f34-f6fb-4165-8fd3-54d3cb4599d1",
"type": "password",
"createdDate": 1576751136227,
"secretData": "{\"value\":\"pass\",\"salt\":\"__SALT__\"}",
"credentialData": "{\"hashIterations\":1234,\"algorithm\":\"pbkdf2-sha256\"}"
}
]
},
{ {
"username": "admin", "username": "admin",
"enabled": true, "enabled": true,