From 80f8815b9a019d2687277ba5dcb3119be709622c Mon Sep 17 00:00:00 2001 From: Machiel Keizer-Groeneveld Date: Fri, 9 Jun 2017 09:32:33 +0200 Subject: [PATCH] KEYCLOAK-5026 Store credentials Credentials are stored with user creation if they are present in the UserRepresentation. --- .../idm/CredentialRepresentation.java | 97 +++++++++++++++++++ .../resources/admin/UsersResource.java | 4 +- .../keycloak/testsuite/admin/UserTest.java | 78 +++++++++++++++ 3 files changed, 178 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java index d597cf32aa..95c7ca3d1a 100755 --- a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java @@ -155,4 +155,101 @@ public class CredentialRepresentation { public void setConfig(MultivaluedHashMap config) { this.config = config; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((algorithm == null) ? 0 : algorithm.hashCode()); + result = prime * result + ((config == null) ? 0 : config.hashCode()); + result = prime * result + ((counter == null) ? 0 : counter.hashCode()); + result = prime * result + ((createdDate == null) ? 0 : createdDate.hashCode()); + result = prime * result + ((device == null) ? 0 : device.hashCode()); + result = prime * result + ((digits == null) ? 0 : digits.hashCode()); + result = prime * result + ((hashIterations == null) ? 0 : hashIterations.hashCode()); + result = prime * result + ((hashedSaltedValue == null) ? 0 : hashedSaltedValue.hashCode()); + result = prime * result + ((period == null) ? 0 : period.hashCode()); + result = prime * result + ((salt == null) ? 0 : salt.hashCode()); + result = prime * result + ((temporary == null) ? 0 : temporary.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CredentialRepresentation other = (CredentialRepresentation) obj; + if (algorithm == null) { + if (other.algorithm != null) + return false; + } else if (!algorithm.equals(other.algorithm)) + return false; + if (config == null) { + if (other.config != null) + return false; + } else if (!config.equals(other.config)) + return false; + if (counter == null) { + if (other.counter != null) + return false; + } else if (!counter.equals(other.counter)) + return false; + if (createdDate == null) { + if (other.createdDate != null) + return false; + } else if (!createdDate.equals(other.createdDate)) + return false; + if (device == null) { + if (other.device != null) + return false; + } else if (!device.equals(other.device)) + return false; + if (digits == null) { + if (other.digits != null) + return false; + } else if (!digits.equals(other.digits)) + return false; + if (hashIterations == null) { + if (other.hashIterations != null) + return false; + } else if (!hashIterations.equals(other.hashIterations)) + return false; + if (hashedSaltedValue == null) { + if (other.hashedSaltedValue != null) + return false; + } else if (!hashedSaltedValue.equals(other.hashedSaltedValue)) + return false; + if (period == null) { + if (other.period != null) + return false; + } else if (!period.equals(other.period)) + return false; + if (salt == null) { + if (other.salt != null) + return false; + } else if (!salt.equals(other.salt)) + return false; + if (temporary == null) { + if (other.temporary != null) + return false; + } else if (!temporary.equals(other.temporary)) + return false; + if (type == null) { + if (other.type != null) + return false; + } else if (!type.equals(other.type)) + return false; + if (value == null) { + if (other.value != null) + return false; + } else if (!value.equals(other.value)) + return false; + return true; + } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index 815ee8981b..d99940d686 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -37,6 +37,7 @@ import org.keycloak.events.admin.ResourceType; import org.keycloak.models.*; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.provider.ProviderFactory; @@ -196,7 +197,8 @@ public class UsersResource { UserModel user = session.users().addUser(realm, rep.getUsername()); Set emptySet = Collections.emptySet(); updateUserFromRep(user, rep, emptySet, realm, session, false); - + RepresentationToModel.createCredentials(rep, session, realm, user); + adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, user.getId()).representation(rep).success(); if (session.getTransactionManager().isActive()) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java index 567b2846fa..edcb938b33 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java @@ -17,6 +17,7 @@ package org.keycloak.testsuite.admin; +import org.apache.commons.collections.map.SingletonMap; import org.hamcrest.Matchers; import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.arquillian.graphene.page.Page; @@ -29,9 +30,14 @@ import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RoleMappingResource; import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UsersResource; +import org.keycloak.common.util.Base64; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.credential.CredentialModel; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.Constants; +import org.keycloak.models.PasswordPolicy; +import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; @@ -58,6 +64,8 @@ import org.keycloak.testsuite.util.RoleBuilder; import org.keycloak.testsuite.util.UserBuilder; import org.openqa.selenium.WebDriver; +import com.google.common.collect.ImmutableMap; + import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import javax.ws.rs.ClientErrorException; @@ -65,12 +73,15 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -168,6 +179,73 @@ public class UserTest extends AbstractAdminTest { response.close(); } + @Test + public void createUserWithHashedCredentials() { + UserRepresentation user = new UserRepresentation(); + user.setUsername("user_creds"); + user.setEmail("email@localhost"); + + CredentialRepresentation hashedPassword = new CredentialRepresentation(); + hashedPassword.setAlgorithm("my-algorithm"); + hashedPassword.setCounter(11); + hashedPassword.setCreatedDate(1001l); + hashedPassword.setDevice("deviceX"); + hashedPassword.setDigits(6); + hashedPassword.setHashIterations(22); + hashedPassword.setHashedSaltedValue("ABC"); + hashedPassword.setPeriod(99); + hashedPassword.setSalt(Base64.encodeBytes("theSalt".getBytes())); + hashedPassword.setType(CredentialRepresentation.PASSWORD); + + user.setCredentials(Arrays.asList(hashedPassword)); + + createUser(user); + + CredentialModel credentialHashed = fetchCredentials("user_creds"); + assertNotNull("Expecting credential", credentialHashed); + assertEquals("my-algorithm", credentialHashed.getAlgorithm()); + assertEquals(11, credentialHashed.getCounter()); + assertEquals(Long.valueOf(1001), credentialHashed.getCreatedDate()); + assertEquals("deviceX", credentialHashed.getDevice()); + assertEquals(6, credentialHashed.getDigits()); + assertEquals(22, credentialHashed.getHashIterations()); + assertEquals("ABC", credentialHashed.getValue()); + assertEquals(99, credentialHashed.getPeriod()); + assertEquals("theSalt", new String(credentialHashed.getSalt())); + assertEquals(CredentialRepresentation.PASSWORD, credentialHashed.getType()); + } + + @Test + public void createUserWithRawCredentials() { + UserRepresentation user = new UserRepresentation(); + user.setUsername("user_rawpw"); + user.setEmail("email.raw@localhost"); + + CredentialRepresentation rawPassword = new CredentialRepresentation(); + rawPassword.setValue("ABCD"); + rawPassword.setType(CredentialRepresentation.PASSWORD); + user.setCredentials(Arrays.asList(rawPassword)); + + createUser(user); + + CredentialModel credential = fetchCredentials("user_rawpw"); + assertNotNull("Expecting credential", credential); + assertEquals(PasswordPolicy.HASH_ALGORITHM_DEFAULT, credential.getAlgorithm()); + assertEquals(PasswordPolicy.HASH_ITERATIONS_DEFAULT, credential.getHashIterations()); + assertNotEquals("ABCD", credential.getValue()); + assertEquals(CredentialRepresentation.PASSWORD, credential.getType()); + } + + private CredentialModel fetchCredentials(String username) { + return getTestingClient().server(REALM_NAME).fetch(session -> { + RealmModel realm = session.getContext().getRealm(); + UserModel user = session.users().getUserByUsername(username, realm); + List storedCredentialsByType = session.userCredentialManager().getStoredCredentialsByType(realm, user, CredentialRepresentation.PASSWORD); + System.out.println(storedCredentialsByType.size()); + return storedCredentialsByType.get(0); + }, CredentialModel.class); + } + @Test public void createDuplicatedUser3() { createUser();