diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java index 87c64873e01..5f8094be863 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java @@ -21,10 +21,13 @@ import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; +import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.component.ComponentModel; import org.keycloak.connections.mongo.api.MongoStore; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.credential.CredentialInput; +import org.keycloak.credential.CredentialModel; +import org.keycloak.credential.UserCredentialStore; import org.keycloak.models.ClientModel; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.FederatedIdentityModel; @@ -41,15 +44,19 @@ import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; +import org.keycloak.models.cache.CachedUserModel; +import org.keycloak.models.entities.CredentialEntity; import org.keycloak.models.entities.FederatedIdentityEntity; import org.keycloak.models.entities.UserConsentEntity; import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity; import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity; import org.keycloak.models.utils.CredentialValidation; +import org.keycloak.models.utils.KeycloakModelUtils; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -59,7 +66,7 @@ import java.util.regex.Pattern; /** * @author Marek Posolda */ -public class MongoUserProvider implements UserProvider { +public class MongoUserProvider implements UserProvider, UserCredentialStore { private final MongoStoreInvocationContext invocationContext; private final KeycloakSession session; @@ -627,4 +634,185 @@ public class MongoUserProvider implements UserProvider { } + @Override + public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) { + MongoUserEntity mongoUser = getMongoUserEntity(user); + CredentialEntity credentialEntity = getCredentialEntity(cred, mongoUser); + if (credentialEntity == null) return; + // old store may not have id set + if (credentialEntity.getId() == null) credentialEntity.setId(KeycloakModelUtils.generateId()); + setValues(cred, credentialEntity); + getMongoStore().updateEntity(mongoUser, invocationContext); + + } + + public CredentialEntity getCredentialEntity(CredentialModel cred, MongoUserEntity mongoUser) { + CredentialEntity credentialEntity = null; + // old store may not have id set + for (CredentialEntity entity : mongoUser.getCredentials()) { + if (cred.getId() != null && cred.getId().equals(entity.getId())) { + credentialEntity = entity; + break; + } else if (cred.getType().equals(entity.getType())) { + credentialEntity = entity; + break; + } + } + return credentialEntity; + } + + public MongoUserEntity getMongoUserEntity(UserModel user) { + UserAdapter adapter = null; + if (user instanceof CachedUserModel) { + adapter = (UserAdapter)((CachedUserModel)user).getDelegateForUpdate(); + } else if (user instanceof UserAdapter ){ + adapter = (UserAdapter)user; + } else { + return getMongoStore().loadEntity(MongoUserEntity.class, user.getId(), invocationContext); + + } + return adapter.getMongoEntity(); + } + + @Override + public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) { + MongoUserEntity mongoUser = getMongoUserEntity(user); + CredentialEntity credentialEntity = new CredentialEntity(); + credentialEntity.setId(KeycloakModelUtils.generateId()); + setValues(cred, credentialEntity); + cred.setId(credentialEntity.getId()); + mongoUser.getCredentials().add(credentialEntity); + getMongoStore().updateEntity(mongoUser, invocationContext); + cred.setId(credentialEntity.getId()); + return cred; + } + + public void setValues(CredentialModel cred, CredentialEntity credentialEntity) { + credentialEntity.setType(cred.getType()); + credentialEntity.setDevice(cred.getDevice()); + credentialEntity.setValue(cred.getValue()); + credentialEntity.setSalt(cred.getSalt()); + credentialEntity.setDevice(cred.getDevice()); + credentialEntity.setHashIterations(cred.getHashIterations()); + credentialEntity.setCounter(cred.getCounter()); + credentialEntity.setAlgorithm(cred.getAlgorithm()); + credentialEntity.setDigits(cred.getDigits()); + credentialEntity.setPeriod(cred.getPeriod()); + if (cred.getConfig() == null) { + credentialEntity.setConfig(null); + } + else { + if (credentialEntity.getConfig() == null) credentialEntity.setConfig(new MultivaluedHashMap<>()); + credentialEntity.getConfig().clear(); + credentialEntity.getConfig().putAll(cred.getConfig()); + } + } + + @Override + public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) { + MongoUserEntity mongoUser = getMongoUserEntity(user); + Iterator it = mongoUser.getCredentials().iterator(); + while (it.hasNext()) { + CredentialEntity entity = it.next(); + if (id.equals(entity.getId())) { + it.remove(); + getMongoStore().updateEntity(mongoUser, invocationContext); + return true; + } + } + return false; + } + + @Override + public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) { + MongoUserEntity mongoUser = getMongoUserEntity(user); + for (CredentialEntity credEntity : mongoUser.getCredentials()) { + if(id.equals(credEntity.getId())) { + if (credEntity.getId() == null) { + credEntity.setId(KeycloakModelUtils.generateId()); + getMongoStore().updateEntity(mongoUser, invocationContext); + } + return toModel(credEntity); + } + + } + return null; + } + + public CredentialModel toModel(CredentialEntity credEntity) { + CredentialModel credModel = new CredentialModel(); + credModel.setId(credEntity.getId()); + credModel.setType(credEntity.getType()); + credModel.setDevice(credEntity.getDevice()); + credModel.setCreatedDate(credEntity.getCreatedDate()); + credModel.setValue(credEntity.getValue()); + credModel.setSalt(credEntity.getSalt()); + credModel.setHashIterations(credEntity.getHashIterations()); + credModel.setAlgorithm(credEntity.getAlgorithm()); + credModel.setCounter(credEntity.getCounter()); + credModel.setPeriod(credEntity.getPeriod()); + credModel.setDigits(credEntity.getDigits()); + if (credEntity.getConfig() != null) { + credModel.setConfig(new MultivaluedHashMap<>()); + credModel.getConfig().putAll(credEntity.getConfig()); + } + return credModel; + } + + @Override + public List getStoredCredentials(RealmModel realm, UserModel user) { + List list = new LinkedList<>(); + MongoUserEntity mongoUser = getMongoUserEntity(user); + boolean update = false; + for (CredentialEntity credEntity : mongoUser.getCredentials()) { + if (credEntity.getId() == null) { + credEntity.setId(KeycloakModelUtils.generateId()); + update = true; + } + CredentialModel credModel = toModel(credEntity); + list.add(credModel); + + } + if (update) getMongoStore().updateEntity(mongoUser, invocationContext); + return list; + + } + + @Override + public List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { + List list = new LinkedList<>(); + MongoUserEntity mongoUser = getMongoUserEntity(user); + boolean update = false; + for (CredentialEntity credEntity : mongoUser.getCredentials()) { + if (credEntity.getId() == null) { + credEntity.setId(KeycloakModelUtils.generateId()); + update = true; + } + if (credEntity.getType().equals(type)) { + CredentialModel credModel = toModel(credEntity); + list.add(credModel); + } + } + if (update) getMongoStore().updateEntity(mongoUser, invocationContext); + return list; + } + + @Override + public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) { + MongoUserEntity mongoUser = getMongoUserEntity(user); + boolean update = false; + CredentialModel credModel = null; + for (CredentialEntity credEntity : mongoUser.getCredentials()) { + if (credEntity.getId() == null) { + credEntity.setId(KeycloakModelUtils.generateId()); + update = true; + } + if (credEntity.getType().equals(type) && name.equals(credEntity.getDevice())) { + credModel = toModel(credEntity); + break; + } + } + if (update) getMongoStore().updateEntity(mongoUser, invocationContext); + return credModel; + } } diff --git a/server-spi/src/main/java/org/keycloak/models/entities/CredentialEntity.java b/server-spi/src/main/java/org/keycloak/models/entities/CredentialEntity.java index ef1932e2e04..aa4752c2e00 100755 --- a/server-spi/src/main/java/org/keycloak/models/entities/CredentialEntity.java +++ b/server-spi/src/main/java/org/keycloak/models/entities/CredentialEntity.java @@ -17,6 +17,11 @@ package org.keycloak.models.entities; +import org.keycloak.common.util.MultivaluedHashMap; + +import java.util.List; +import java.util.Map; + /** * @author Marek Posolda */ @@ -33,6 +38,7 @@ public class CredentialEntity extends AbstractIdentifiableEntity { protected String algorithm; protected int digits; protected int period; + protected Map> config = new MultivaluedHashMap<>(); public String getType() { @@ -122,4 +128,12 @@ public class CredentialEntity extends AbstractIdentifiableEntity { public void setPeriod(int period) { this.period = period; } + + public Map> getConfig() { + return config; + } + + public void setConfig(Map> config) { + this.config = config; + } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPMultipleAttributesTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPMultipleAttributesTest.java index fb23b5dd505..ec95eafa285 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPMultipleAttributesTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPMultipleAttributesTest.java @@ -20,6 +20,7 @@ package org.keycloak.testsuite.federation.ldap.base; import java.net.URL; import java.util.Arrays; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -149,7 +150,9 @@ public class LDAPMultipleAttributesTest { // Actually there are 2 postalCodes List postalCodes = user.getAttribute("postal_code"); assertPostalCodes(postalCodes, "88441", "77332"); - + List tmp = new LinkedList<>(); + tmp.addAll(postalCodes); + postalCodes = tmp; postalCodes.remove("77332"); user.setAttribute("postal_code", postalCodes); @@ -163,7 +166,9 @@ public class LDAPMultipleAttributesTest { UserModel user = session.users().getUserByUsername("bwilson", appRealm); List postalCodes = user.getAttribute("postal_code"); assertPostalCodes(postalCodes, "88441"); - + List tmp = new LinkedList<>(); + tmp.addAll(postalCodes); + postalCodes = tmp; postalCodes.add("77332"); user.setAttribute("postal_code", postalCodes); } finally {