KEYCLOAK-12281 Fix export/import for users that have custom credential algorithms with no salt
This commit is contained in:
parent
b90a0307ea
commit
f0d95da52d
3 changed files with 51 additions and 2 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue