keycloak-memoizeit
Changes
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java 7(+7 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java 11(+0 -11)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUserConsents.java 53(+53 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java 120(+117 -3)
model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java 5(+4 -1)
model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserAttributeEntity.java 133(+133 -0)
model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java 148(+148 -0)
model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentProtocolMapperEntity.java 135(+135 -0)
model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentRoleEntity.java 133(+133 -0)
model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java 222(+222 -0)
model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java 162(+162 -0)
model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java 160(+160 -0)
model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java 161(+161 -0)
model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProviderFactory.java 58(+58 -0)
model/jpa/src/main/resources/META-INF/services/org.keycloak.storage.federated.UserFederatedStorageProviderFactory 1(+1 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java 130(+128 -2)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java 185(+177 -8)
server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java 35(+35 -0)
server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java 36(+36 -0)
server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java 35(+35 -0)
server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java 58(+58 -0)
server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java 26(+26 -0)
server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java 51(+51 -0)
server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java 36(+36 -0)
server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java 32(+32 -0)
server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java 36(+36 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java 12(+8 -4)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java 10(+10 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java 38(+19 -19)
Details
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
index bf2b1c2..f077927 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
@@ -16,6 +16,40 @@ import java.util.Set;
import java.util.function.Predicate;
/**
+ *
+ * Some notes on how this works:
+
+ * This implementation manages optimistic locking and version checks itself. The reason is Infinispan just does behave
+ * the way we need it to. Not saying Infinispan is bad, just that we have specific caching requirements!
+ *
+ * This is an invalidation cache implementation and requires to caches:
+ * Cache 1 is an Invalidation Cache
+ * Cache 2 is a local-only revision number cache.
+ *
+ *
+ * Each node in the cluster maintains its own revision number cache for each entry in the main invalidation cache. This revision
+ * cache holds the version counter for each cached entity.
+ *
+ * Cache listeners do not receive a @CacheEntryInvalidated event if that node does not have an entry for that item. So, consider the following.
+
+ 1. Node 1 gets current counter for user. There currently isn't one as this user isn't cached.
+ 2. Node 1 reads user from DB
+ 3. Node 2 updates user
+ 4. Node 2 calls cache.remove(user). This does not result in an invalidation listener event to node 1!
+ 5. node 1 checks version counter, checks pass. Stale entry is cached.
+
+ The issue is that Node 1 doesn't have an entry for the user, so it never receives an invalidation listener event from Node 2 thus it can't bump the version. So, when node 1 goes to cache the user it is stale as the version number was never bumped.
+
+ So how is this issue fixed? here is pseudo code:
+
+ 1. Node 1 calls cacheManager.getCurrentRevision() to get the current local version counter of that User
+ 2. Node 1 getCurrentRevision() pulls current counter for that user
+ 3. Node 1 getCurrentRevision() adds a "invalidation.key.userid" to invalidation cache. Its just a marker. nothing else
+ 4. Node 2 update user
+ 5. Node 2 does a cache.remove(user) cache.remove(invalidation.key.userid)
+ 6. Node 1 receives invalidation event for invalidation.key.userid. Bumps the version counter for that user
+ 7. node 1 version check fails, it doesn't cache the user
+ *
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
index 6a4ff4f..6dcc0d9 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
@@ -37,6 +37,7 @@ import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.cache.infinispan.RealmCache;
import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.storage.StorageProviderModel;
import java.io.Serializable;
import java.security.PrivateKey;
@@ -110,6 +111,7 @@ public class CachedRealm extends AbstractRevisioned {
protected List<RequiredCredentialModel> requiredCredentials;
protected List<UserFederationProviderModel> userFederationProviders;
+ protected List<StorageProviderModel> storageProviders;
protected MultivaluedHashMap<String, UserFederationMapperModel> userFederationMappers = new MultivaluedHashMap<String, UserFederationMapperModel>();
protected Set<UserFederationMapperModel> userFederationMapperSet;
protected List<IdentityProviderModel> identityProviders;
@@ -206,6 +208,7 @@ public class CachedRealm extends AbstractRevisioned {
requiredCredentials = model.getRequiredCredentials();
userFederationProviders = model.getUserFederationProviders();
+ storageProviders = model.getStorageProviders();
userFederationMapperSet = model.getUserFederationMappers();
for (UserFederationMapperModel mapper : userFederationMapperSet) {
this.userFederationMappers.add(mapper.getFederationProviderId(), mapper);
@@ -598,4 +601,8 @@ public class CachedRealm extends AbstractRevisioned {
public List<RequiredActionProviderModel> getRequiredActionProviderList() {
return requiredActionProviderList;
}
+
+ public List<StorageProviderModel> getStorageProviders() {
+ return storageProviders;
+ }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
index f37ecf1..89049ca 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
@@ -53,7 +53,6 @@ public class CachedUser extends AbstractRevisioned implements InRealm {
private Set<String> requiredActions = new HashSet<>();
private Set<String> roleMappings = new HashSet<>();
private Set<String> groups = new HashSet<>();
- private Map<String, CachedUserConsent> consents = new HashMap<>(); // Key is client DB Id
@@ -82,13 +81,6 @@ public class CachedUser extends AbstractRevisioned implements InRealm {
groups.add(group.getId());
}
}
-
- List<UserConsentModel> consents = user.getConsents();
- if (consents != null) {
- for (UserConsentModel consent : consents) {
- this.consents.put(consent.getClient().getId(), new CachedUserConsent(consent));
- }
- }
}
public String getRealm() {
@@ -155,7 +147,4 @@ public class CachedUser extends AbstractRevisioned implements InRealm {
return groups;
}
- public Map<String, CachedUserConsent> getConsents() {
- return consents;
- }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUserConsents.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUserConsents.java
new file mode 100644
index 0000000..eaca957
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUserConsents.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models.cache.infinispan.entities;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserConsentModel;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CachedUserConsents extends AbstractRevisioned implements InRealm {
+ private HashMap<String, CachedUserConsent> consents = new HashMap<>();
+ private final String realmId;
+
+ public CachedUserConsents(Long revision, String id, RealmModel realm,
+ List<UserConsentModel> consents) {
+ super(revision, id);
+ this.realmId = realm.getId();
+ if (consents != null) {
+ for (UserConsentModel consent : consents) {
+ this.consents.put(consent.getClient().getId(), new CachedUserConsent(consent));
+ }
+ }
+ }
+
+ @Override
+ public String getRealm() {
+ return realmId;
+ }
+
+
+ public HashMap<String, CachedUserConsent> getConsents() {
+ return consents;
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 4ea71c7..69f0510 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -23,6 +23,7 @@ import org.keycloak.models.*;
import org.keycloak.models.cache.CacheRealmProvider;
import org.keycloak.models.cache.infinispan.entities.CachedRealm;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.StorageProviderModel;
import java.security.Key;
import java.security.PrivateKey;
@@ -746,6 +747,48 @@ public class RealmAdapter implements RealmModel {
}
@Override
+ public StorageProviderModel addStorageProvider(StorageProviderModel provider) {
+ getDelegateForUpdate();
+ return updated.addStorageProvider(provider);
+ }
+
+ @Override
+ public void updateStorageProvider(StorageProviderModel provider) {
+ getDelegateForUpdate();
+ updated.updateStorageProvider(provider);
+
+ }
+
+ @Override
+ public void removeStorageProvider(StorageProviderModel provider) {
+ getDelegateForUpdate();
+ updated.removeStorageProvider(provider);
+
+ }
+
+ @Override
+ public void setStorageProviders(List<StorageProviderModel> providers) {
+ getDelegateForUpdate();
+ updated.setStorageProviders(providers);
+
+ }
+
+ @Override
+ public List<StorageProviderModel> getStorageProviders() {
+ if (isUpdated()) return updated.getStorageProviders();
+ return cached.getStorageProviders();
+ }
+
+ @Override
+ public StorageProviderModel getStorageProvider(String id) {
+ if (isUpdated()) return updated.getStorageProvider(id);
+ for (StorageProviderModel model : cached.getStorageProviders()) {
+ if (model.getId().equals(id)) return model;
+ }
+ return null;
+ }
+
+ @Override
public String getLoginTheme() {
if (isUpdated()) return updated.getLoginTheme();
return cached.getLoginTheme();
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
index 2b02cee..d697ad2 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
@@ -389,70 +389,6 @@ public class UserAdapter implements UserModel {
}
@Override
- public void addConsent(UserConsentModel consent) {
- getDelegateForUpdate();
- updated.addConsent(consent);
- }
-
- @Override
- public UserConsentModel getConsentByClient(String clientId) {
- if (updated != null) return updated.getConsentByClient(clientId);
- CachedUserConsent cachedConsent = cached.getConsents().get(clientId);
- if (cachedConsent == null) {
- return null;
- }
-
- return toConsentModel(cachedConsent);
- }
-
- @Override
- public List<UserConsentModel> getConsents() {
- if (updated != null) return updated.getConsents();
- Collection<CachedUserConsent> cachedConsents = cached.getConsents().values();
-
- List<UserConsentModel> result = new LinkedList<>();
- for (CachedUserConsent cachedConsent : cachedConsents) {
- UserConsentModel consent = toConsentModel(cachedConsent);
- if (consent != null) {
- result.add(consent);
- }
- }
- return result;
- }
-
- @Override
- public void updateConsent(UserConsentModel consent) {
- getDelegateForUpdate();
- updated.updateConsent(consent);
- }
-
- @Override
- public boolean revokeConsentForClient(String clientId) {
- getDelegateForUpdate();
- return updated.revokeConsentForClient(clientId);
- }
-
- private UserConsentModel toConsentModel(CachedUserConsent cachedConsent) {
- ClientModel client = keycloakSession.realms().getClientById(cachedConsent.getClientDbId(), realm);
- if (client == null) {
- return null;
- }
-
- UserConsentModel consentModel = new UserConsentModel(client);
-
- for (String roleId : cachedConsent.getRoleIds()) {
- RoleModel role = keycloakSession.realms().getRoleById(roleId, realm);
- if (role != null) {
- consentModel.addGrantedRole(role);
- }
- }
- for (ProtocolMapperModel protocolMapper : cachedConsent.getProtocolMappers()) {
- consentModel.addGrantedProtocolMapper(protocolMapper);
- }
- return consentModel;
- }
-
- @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof UserModel)) return false;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
index 1a489ad..c5e4c30 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
@@ -28,6 +28,7 @@ import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
@@ -35,7 +36,10 @@ import org.keycloak.models.UserProvider;
import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.models.cache.infinispan.entities.CachedFederatedIdentityLinks;
import org.keycloak.models.cache.infinispan.entities.CachedUser;
+import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
+import org.keycloak.models.cache.infinispan.entities.CachedUserConsents;
import org.keycloak.models.cache.infinispan.entities.UserListQuery;
+import org.keycloak.storage.StorageProviderModel;
import java.util.*;
@@ -73,7 +77,7 @@ public class UserCacheSession implements CacheUserProvider {
public UserProvider getDelegate() {
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
if (delegate != null) return delegate;
- delegate = session.getProvider(UserProvider.class);
+ delegate = session.userStorageManager();
return delegate;
}
@@ -342,7 +346,7 @@ public class UserCacheSession implements CacheUserProvider {
}
@Override
- public UserModel getUserByServiceAccountClient(ClientModel client) {
+ public UserModel getServiceAccount(ClientModel client) {
// Just an attempt to find the user from cache by default serviceAccount username
String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + client.getClientId();
UserModel user = getUserByUsername(username, client.getRealm());
@@ -350,7 +354,7 @@ public class UserCacheSession implements CacheUserProvider {
return user;
}
- return getDelegate().getUserByServiceAccountClient(client);
+ return getDelegate().getServiceAccount(client);
}
@Override
@@ -369,6 +373,16 @@ public class UserCacheSession implements CacheUserProvider {
}
@Override
+ public List<UserModel> getUsers(RealmModel realm) {
+ return getUsers(realm, false);
+ }
+
+ @Override
+ public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
+ return getUsers(realm, firstResult, maxResults, false);
+ }
+
+ @Override
public List<UserModel> searchForUser(String search, RealmModel realm) {
return getDelegate().searchForUser(search, realm);
}
@@ -434,6 +448,101 @@ public class UserCacheSession implements CacheUserProvider {
}
@Override
+ public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+ invalidations.add(getConsentCacheKey(user.getId()));
+ getDelegate().updateConsent(realm, user, consent);
+ }
+
+ @Override
+ public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId) {
+ invalidations.add(getConsentCacheKey(user.getId()));
+ return getDelegate().revokeConsentForClient(realm, user, clientInternalId);
+ }
+
+ public String getConsentCacheKey(String userId) {
+ return userId + ".consents";
+ }
+
+
+ @Override
+ public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+ invalidations.add(getConsentCacheKey(user.getId()));
+ getDelegate().addConsent(realm, user, consent);
+ }
+
+ @Override
+ public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientId) {
+ logger.tracev("getConsentByClient: {0}", user.getUsername());
+
+ String cacheKey = getConsentCacheKey(user.getId());
+ if (realmInvalidations.contains(realm.getId()) || invalidations.contains(user.getId()) || invalidations.contains(cacheKey)) {
+ return getDelegate().getConsentByClient(realm, user, clientId);
+ }
+
+ CachedUserConsents cached = cache.get(cacheKey, CachedUserConsents.class);
+
+ if (cached == null) {
+ Long loaded = cache.getCurrentRevision(cacheKey);
+ List<UserConsentModel> consents = getDelegate().getConsents(realm, user);
+ cached = new CachedUserConsents(loaded, cacheKey, realm, consents);
+ cache.addRevisioned(cached, startupRevision);
+ }
+ CachedUserConsent cachedConsent = cached.getConsents().get(clientId);
+ if (cachedConsent == null) return null;
+ return toConsentModel(realm, cachedConsent);
+ }
+
+ @Override
+ public List<UserConsentModel> getConsents(RealmModel realm, UserModel user) {
+ logger.tracev("getConsents: {0}", user.getUsername());
+
+ String cacheKey = getConsentCacheKey(user.getId());
+ if (realmInvalidations.contains(realm.getId()) || invalidations.contains(user.getId()) || invalidations.contains(cacheKey)) {
+ return getDelegate().getConsents(realm, user);
+ }
+
+ CachedUserConsents cached = cache.get(cacheKey, CachedUserConsents.class);
+
+ if (cached == null) {
+ Long loaded = cache.getCurrentRevision(cacheKey);
+ List<UserConsentModel> consents = getDelegate().getConsents(realm, user);
+ cached = new CachedUserConsents(loaded, cacheKey, realm, consents);
+ cache.addRevisioned(cached, startupRevision);
+ return consents;
+ } else {
+ List<UserConsentModel> result = new LinkedList<>();
+ for (CachedUserConsent cachedConsent : cached.getConsents().values()) {
+ UserConsentModel consent = toConsentModel(realm, cachedConsent);
+ if (consent != null) {
+ result.add(consent);
+ }
+ }
+ return result;
+ }
+ }
+
+ private UserConsentModel toConsentModel(RealmModel realm, CachedUserConsent cachedConsent) {
+ ClientModel client = session.realms().getClientById(cachedConsent.getClientDbId(), realm);
+ if (client == null) {
+ return null;
+ }
+
+ UserConsentModel consentModel = new UserConsentModel(client);
+
+ for (String roleId : cachedConsent.getRoleIds()) {
+ RoleModel role = session.realms().getRoleById(roleId, realm);
+ if (role != null) {
+ consentModel.addGrantedRole(role);
+ }
+ }
+ for (ProtocolMapperModel protocolMapper : cachedConsent.getProtocolMappers()) {
+ consentModel.addGrantedProtocolMapper(protocolMapper);
+ }
+ return consentModel;
+ }
+
+
+ @Override
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
UserModel user = getDelegate().addUser(realm, id, username, addDefaultRoles, addDefaultRoles);
// just in case the transaction is rolled back you need to invalidate the user and all cache queries for that user
@@ -554,4 +663,9 @@ public class UserCacheSession implements CacheUserProvider {
public void preRemove(ProtocolMapperModel protocolMapper) {
getDelegate().preRemove(protocolMapper);
}
+
+ @Override
+ public void preRemove(RealmModel realm, StorageProviderModel provider) {
+ getDelegate().preRemove(realm, provider);
+ }
}
diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
index 3dcc913..de6e587 100755
--- a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
+++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
@@ -29,6 +29,8 @@ import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
@Ignore
public class ClusteredCacheBehaviorTest {
public EmbeddedCacheManager createManager() {
+ System.setProperty("java.net.preferIPv4Stack", "true");
+ System.setProperty("jgroups.tcp.port", "53715");
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
boolean clustered = true;
@@ -36,7 +38,8 @@ public class ClusteredCacheBehaviorTest {
boolean allowDuplicateJMXDomains = true;
if (clustered) {
- gcb.transport().defaultTransport();
+ gcb = gcb.clusteredDefault();
+ gcb.transport().clusterName("test-clustering");
}
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 5ad023a..3c9ae5f 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -136,13 +136,16 @@ public class RealmEntity {
protected String emailTheme;
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
- Collection<RealmAttributeEntity> attributes = new ArrayList<RealmAttributeEntity>();
+ Collection<RealmAttributeEntity> attributes = new ArrayList<>();
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
- Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
+ Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<>();
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
- List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
+ List<UserFederationProviderEntity> userFederationProviders = new ArrayList<>();
+
+ @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
+ List<StorageProviderEntity> storageProviders = new ArrayList<>();
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
@@ -551,6 +554,14 @@ public class RealmEntity {
this.userFederationProviders = userFederationProviders;
}
+ public List<StorageProviderEntity> getStorageProviders() {
+ return storageProviders;
+ }
+
+ public void setStorageProviders(List<StorageProviderEntity> storageProviders) {
+ this.storageProviders = storageProviders;
+ }
+
public Collection<UserFederationMapperEntity> getUserFederationMappers() {
return userFederationMappers;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/StorageProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/StorageProviderEntity.java
new file mode 100755
index 0000000..f663795
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/StorageProviderEntity.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.jpa.entities;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.MapKeyColumn;
+import javax.persistence.Table;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
+ */
+@Entity
+@Table(name="STORAGE_PROVIDER")
+public class StorageProviderEntity {
+
+ @Id
+ @Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
+ protected String id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "REALM_ID")
+ protected RealmEntity realm;
+
+ @Column(name="PROVIDER_NAME")
+ private String providerName;
+ @Column(name="PRIORITY")
+ private int priority;
+
+ @ElementCollection
+ @MapKeyColumn(name="name")
+ @Column(name="VALUE")
+ @CollectionTable(name="STORAGE_PROVIDER_CONFIG", joinColumns={ @JoinColumn(name="STORAGE_PROVIDER_ID") })
+ private Map<String, String> config;
+
+ @Column(name="DISPLAY_NAME")
+ private String displayName;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public RealmEntity getRealm() {
+ return realm;
+ }
+
+ public void setRealm(RealmEntity realm) {
+ this.realm = realm;
+ }
+
+ public String getProviderName() {
+ return providerName;
+ }
+
+ public void setProviderName(String providerName) {
+ this.providerName = providerName;
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+
+ public void setPriority(int priority) {
+ this.priority = priority;
+ }
+
+ public Map<String, String> getConfig() {
+ return config;
+ }
+
+ public void setConfig(Map<String, String> config) {
+ this.config = config;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (!(o instanceof StorageProviderEntity)) return false;
+
+ StorageProviderEntity that = (StorageProviderEntity) o;
+
+ if (!id.equals(that.getId())) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index 593bae5..7802453 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -22,24 +22,32 @@ import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
import org.keycloak.models.jpa.entities.UserAttributeEntity;
+import org.keycloak.models.jpa.entities.UserConsentEntity;
+import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
+import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.utils.CredentialValidation;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.StorageProviderModel;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -187,6 +195,164 @@ public class JpaUserProvider implements UserProvider {
}
@Override
+ public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+ String clientId = consent.getClient().getId();
+
+ UserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
+ if (consentEntity != null) {
+ throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]");
+ }
+
+ consentEntity = new UserConsentEntity();
+ consentEntity.setId(KeycloakModelUtils.generateId());
+ consentEntity.setUser(em.getReference(UserEntity.class, user.getId()));
+ consentEntity.setClientId(clientId);
+ em.persist(consentEntity);
+ em.flush();
+
+ updateGrantedConsentEntity(consentEntity, consent);
+ }
+
+ @Override
+ public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientId) {
+ UserConsentEntity entity = getGrantedConsentEntity(user, clientId);
+ return toConsentModel(realm, entity);
+ }
+
+ @Override
+ public List<UserConsentModel> getConsents(RealmModel realm, UserModel user) {
+ TypedQuery<UserConsentEntity> query = em.createNamedQuery("userConsentsByUser", UserConsentEntity.class);
+ query.setParameter("userId", user.getId());
+ List<UserConsentEntity> results = query.getResultList();
+
+ List<UserConsentModel> consents = new ArrayList<UserConsentModel>();
+ for (UserConsentEntity entity : results) {
+ UserConsentModel model = toConsentModel(realm, entity);
+ consents.add(model);
+ }
+ return consents;
+ }
+
+ @Override
+ public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+ String clientId = consent.getClient().getId();
+
+ UserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
+ if (consentEntity == null) {
+ throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]");
+ }
+
+ updateGrantedConsentEntity(consentEntity, consent);
+ }
+
+ public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientId) {
+ UserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
+ if (consentEntity == null) return false;
+
+ em.remove(consentEntity);
+ em.flush();
+ return true;
+ }
+
+
+ private UserConsentEntity getGrantedConsentEntity(UserModel user, String clientId) {
+ TypedQuery<UserConsentEntity> query = em.createNamedQuery("userConsentByUserAndClient", UserConsentEntity.class);
+ query.setParameter("userId", user.getId());
+ query.setParameter("clientId", clientId);
+ List<UserConsentEntity> results = query.getResultList();
+ if (results.size() > 1) {
+ throw new ModelException("More results found for user [" + user.getUsername() + "] and client [" + clientId + "]");
+ } else if (results.size() == 1) {
+ return results.get(0);
+ } else {
+ return null;
+ }
+ }
+
+ private UserConsentModel toConsentModel(RealmModel realm, UserConsentEntity entity) {
+ if (entity == null) {
+ return null;
+ }
+
+ ClientModel client = realm.getClientById(entity.getClientId());
+ if (client == null) {
+ throw new ModelException("Client with id " + entity.getClientId() + " is not available");
+ }
+ UserConsentModel model = new UserConsentModel(client);
+
+ Collection<UserConsentRoleEntity> grantedRoleEntities = entity.getGrantedRoles();
+ if (grantedRoleEntities != null) {
+ for (UserConsentRoleEntity grantedRole : grantedRoleEntities) {
+ RoleModel grantedRoleModel = realm.getRoleById(grantedRole.getRoleId());
+ if (grantedRoleModel != null) {
+ model.addGrantedRole(grantedRoleModel);
+ }
+ }
+ }
+
+ Collection<UserConsentProtocolMapperEntity> grantedProtocolMapperEntities = entity.getGrantedProtocolMappers();
+ if (grantedProtocolMapperEntities != null) {
+ for (UserConsentProtocolMapperEntity grantedProtMapper : grantedProtocolMapperEntities) {
+ ProtocolMapperModel protocolMapper = client.getProtocolMapperById(grantedProtMapper.getProtocolMapperId());
+ model.addGrantedProtocolMapper(protocolMapper );
+ }
+ }
+
+ return model;
+ }
+
+ // Update roles and protocolMappers to given consentEntity from the consentModel
+ private void updateGrantedConsentEntity(UserConsentEntity consentEntity, UserConsentModel consentModel) {
+ Collection<UserConsentProtocolMapperEntity> grantedProtocolMapperEntities = consentEntity.getGrantedProtocolMappers();
+ Collection<UserConsentProtocolMapperEntity> mappersToRemove = new HashSet<UserConsentProtocolMapperEntity>(grantedProtocolMapperEntities);
+
+ for (ProtocolMapperModel protocolMapper : consentModel.getGrantedProtocolMappers()) {
+ UserConsentProtocolMapperEntity grantedProtocolMapperEntity = new UserConsentProtocolMapperEntity();
+ grantedProtocolMapperEntity.setUserConsent(consentEntity);
+ grantedProtocolMapperEntity.setProtocolMapperId(protocolMapper.getId());
+
+ // Check if it's already there
+ if (!grantedProtocolMapperEntities.contains(grantedProtocolMapperEntity)) {
+ em.persist(grantedProtocolMapperEntity);
+ em.flush();
+ grantedProtocolMapperEntities.add(grantedProtocolMapperEntity);
+ } else {
+ mappersToRemove.remove(grantedProtocolMapperEntity);
+ }
+ }
+ // Those mappers were no longer on consentModel and will be removed
+ for (UserConsentProtocolMapperEntity toRemove : mappersToRemove) {
+ grantedProtocolMapperEntities.remove(toRemove);
+ em.remove(toRemove);
+ }
+
+ Collection<UserConsentRoleEntity> grantedRoleEntities = consentEntity.getGrantedRoles();
+ Set<UserConsentRoleEntity> rolesToRemove = new HashSet<UserConsentRoleEntity>(grantedRoleEntities);
+ for (RoleModel role : consentModel.getGrantedRoles()) {
+ UserConsentRoleEntity consentRoleEntity = new UserConsentRoleEntity();
+ consentRoleEntity.setUserConsent(consentEntity);
+ consentRoleEntity.setRoleId(role.getId());
+
+ // Check if it's already there
+ if (!grantedRoleEntities.contains(consentRoleEntity)) {
+ em.persist(consentRoleEntity);
+ em.flush();
+ grantedRoleEntities.add(consentRoleEntity);
+ } else {
+ rolesToRemove.remove(consentRoleEntity);
+ }
+ }
+ // Those roles were no longer on consentModel and will be removed
+ for (UserConsentRoleEntity toRemove : rolesToRemove) {
+ grantedRoleEntities.remove(toRemove);
+ em.remove(toRemove);
+ }
+
+ em.flush();
+ }
+
+
+ @Override
public void grantToAllUsers(RealmModel realm, RoleModel role) {
int num = em.createNamedQuery("grantRoleToAllUsers")
.setParameter("realmId", realm.getId())
@@ -337,7 +503,7 @@ public class JpaUserProvider implements UserProvider {
}
@Override
- public UserModel getUserByServiceAccountClient(ClientModel client) {
+ public UserModel getServiceAccount(ClientModel client) {
TypedQuery<UserEntity> query = em.createNamedQuery("getRealmUserByServiceAccount", UserEntity.class);
query.setParameter("realmId", client.getRealm().getId());
query.setParameter("clientInternalId", client.getId());
@@ -367,6 +533,16 @@ public class JpaUserProvider implements UserProvider {
}
@Override
+ public List<UserModel> getUsers(RealmModel realm) {
+ return getUsers(realm, false);
+ }
+
+ @Override
+ public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
+ return getUsers(realm, firstResult, maxResults, false);
+ }
+
+ @Override
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) {
String queryName = includeServiceAccounts ? "getAllUsersByRealm" : "getAllUsersByRealmExcludeServiceAccount" ;
@@ -542,4 +718,9 @@ public class JpaUserProvider implements UserProvider {
// Not supported yet
return null;
}
+
+ @Override
+ public void preRemove(RealmModel realm, StorageProviderModel link) {
+
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 8de1395..40112cd 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -43,6 +43,8 @@ import org.keycloak.models.UserFederationProviderCreationEventImpl;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.jpa.entities.*;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.StorageProvider;
+import org.keycloak.storage.StorageProviderModel;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
@@ -812,6 +814,15 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
em.flush();
}
+
+ private void removeFederationMappersForProvider(String federationProviderId) {
+ Set<UserFederationMapperEntity> mappers = getUserFederationMapperEntitiesByFederationProvider(federationProviderId);
+ for (UserFederationMapperEntity mapper : mappers) {
+ realm.getUserFederationMappers().remove(mapper);
+ em.remove(mapper);
+ }
+ }
+
@Override
public List<UserFederationProviderModel> getUserFederationProviders() {
List<UserFederationProviderEntity> entities = realm.getUserFederationProviders();
@@ -882,15 +893,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
}
}
}
-
- private void removeFederationMappersForProvider(String federationProviderId) {
- Set<UserFederationMapperEntity> mappers = getUserFederationMapperEntitiesByFederationProvider(federationProviderId);
- for (UserFederationMapperEntity mapper : mappers) {
- realm.getUserFederationMappers().remove(mapper);
- em.remove(mapper);
- }
- }
-
@Override
public void updateUserFederationProvider(UserFederationProviderModel model) {
KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getUserFederationProviders());
@@ -1002,6 +1004,186 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
}
@Override
+ public StorageProviderModel getStorageProvider(String id) {
+ StorageProviderEntity entity = em.find(StorageProviderEntity.class, id);
+ if (entity == null) return null;
+ return toModel(entity);
+ }
+
+ @Override
+ public List<StorageProviderModel> getStorageProviders() {
+ List<StorageProviderEntity> entities = realm.getStorageProviders();
+ if (entities.isEmpty()) return Collections.EMPTY_LIST;
+ List<StorageProviderEntity> copy = new LinkedList<>();
+ for (StorageProviderEntity entity : entities) {
+ copy.add(entity);
+
+ }
+ Collections.sort(copy, new Comparator<StorageProviderEntity>() {
+
+ @Override
+ public int compare(StorageProviderEntity o1, StorageProviderEntity o2) {
+ return o1.getPriority() - o2.getPriority();
+ }
+
+ });
+ List<StorageProviderModel> result = new LinkedList<>();
+ for (StorageProviderEntity entity : copy) {
+ result.add(toModel(entity));
+ }
+
+ return Collections.unmodifiableList(result);
+ }
+
+ protected StorageProviderModel toModel(StorageProviderEntity entity) {
+ StorageProviderModel model = new StorageProviderModel();
+ model.setId(entity.getId());
+ model.setProviderName(entity.getProviderName());
+ model.getConfig().putAll(entity.getConfig());
+ model.setPriority(entity.getPriority());
+ model.setDisplayName(entity.getDisplayName());
+ return model;
+ }
+
+ @Override
+ public StorageProviderModel addStorageProvider(StorageProviderModel model) {
+ KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), null, getStorageProviders());
+
+ String id = KeycloakModelUtils.generateId();
+ StorageProviderEntity entity = new StorageProviderEntity();
+ entity.setId(id);
+ entity.setRealm(realm);
+ entity.setProviderName(model.getProviderName());
+ entity.setConfig(model.getConfig());
+ entity.setPriority(model.getPriority());
+ String displayName = model.getDisplayName();
+ if (model.getDisplayName() == null) {
+ displayName = id;
+ }
+ entity.setDisplayName(displayName);
+ em.persist(entity);
+ realm.getStorageProviders().add(entity);
+ em.flush();
+ StorageProviderModel providerModel = toModel(entity);
+
+ return providerModel;
+ }
+
+ @Override
+ public void removeStorageProvider(StorageProviderModel provider) {
+ Iterator<StorageProviderEntity> it = realm.getStorageProviders().iterator();
+ while (it.hasNext()) {
+ StorageProviderEntity entity = it.next();
+ if (entity.getId().equals(provider.getId())) {
+
+ session.users().preRemove(this, provider);
+
+ it.remove();
+ em.remove(entity);
+ return;
+ }
+ }
+ }
+ @Override
+ public void updateStorageProvider(StorageProviderModel model) {
+ KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getStorageProviders());
+
+ Iterator<StorageProviderEntity> it = realm.getStorageProviders().iterator();
+ while (it.hasNext()) {
+ StorageProviderEntity entity = it.next();
+ if (entity.getId().equals(model.getId())) {
+ String displayName = model.getDisplayName();
+ if (displayName != null) {
+ entity.setDisplayName(model.getDisplayName());
+ }
+ entity.setConfig(model.getConfig());
+ entity.setPriority(model.getPriority());
+ entity.setProviderName(model.getProviderName());
+ entity.setPriority(model.getPriority());
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void setStorageProviders(List<StorageProviderModel> providers) {
+ for (StorageProviderModel currentProvider : providers) {
+ KeycloakModelUtils.ensureUniqueDisplayName(currentProvider.getDisplayName(), currentProvider, providers);
+ }
+
+ Iterator<StorageProviderEntity> it = realm.getStorageProviders().iterator();
+ while (it.hasNext()) {
+ StorageProviderEntity entity = it.next();
+ boolean found = false;
+ for (StorageProviderModel model : providers) {
+ if (entity.getId().equals(model.getId())) {
+ entity.setConfig(model.getConfig());
+ entity.setPriority(model.getPriority());
+ entity.setProviderName(model.getProviderName());
+ String displayName = model.getDisplayName();
+ if (displayName != null) {
+ entity.setDisplayName(displayName);
+ }
+ found = true;
+ break;
+ }
+
+ }
+ if (found) continue;
+ session.users().preRemove(this, toModel(entity));
+ removeFederationMappersForProvider(entity.getId());
+
+ it.remove();
+ em.remove(entity);
+ }
+
+ List<StorageProviderModel> add = new LinkedList<>();
+ for (StorageProviderModel model : providers) {
+ boolean found = false;
+ for (StorageProviderEntity entity : realm.getStorageProviders()) {
+ if (entity.getId().equals(model.getId())) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) add.add(model);
+ }
+
+ for (StorageProviderModel model : add) {
+ StorageProviderEntity entity = new StorageProviderEntity();
+ if (model.getId() != null) {
+ entity.setId(model.getId());
+ } else {
+ String id = KeycloakModelUtils.generateId();
+ entity.setId(id);
+ model.setId(id);
+ }
+ entity.setConfig(model.getConfig());
+ entity.setPriority(model.getPriority());
+ entity.setProviderName(model.getProviderName());
+ entity.setPriority(model.getPriority());
+ String displayName = model.getDisplayName();
+ if (displayName == null) {
+ displayName = entity.getId();
+ }
+ entity.setDisplayName(displayName);
+ entity.setRealm(realm);
+ em.persist(entity);
+ realm.getStorageProviders().add(entity);
+
+ }
+ }
+
+ protected StorageProviderEntity getStorageProviderEntityById(String id) {
+ for (StorageProviderEntity entity : realm.getStorageProviders()) {
+ if (entity.getId().equals(id)) {
+ return entity;
+ }
+ }
+ return null;
+ }
+
+ @Override
public RoleModel getRole(String name) {
return session.realms().getRealmRole(this, name);
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index 9cce404..5d51fc9 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -668,163 +668,6 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
user.setServiceAccountClientLink(clientInternalId);
}
- @Override
- public void addConsent(UserConsentModel consent) {
- String clientId = consent.getClient().getId();
-
- UserConsentEntity consentEntity = getGrantedConsentEntity(clientId);
- if (consentEntity != null) {
- throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]");
- }
-
- consentEntity = new UserConsentEntity();
- consentEntity.setId(KeycloakModelUtils.generateId());
- consentEntity.setUser(user);
- consentEntity.setClientId(clientId);
- em.persist(consentEntity);
- em.flush();
-
- updateGrantedConsentEntity(consentEntity, consent);
- }
-
- @Override
- public UserConsentModel getConsentByClient(String clientId) {
- UserConsentEntity entity = getGrantedConsentEntity(clientId);
- return toConsentModel(entity);
- }
-
- @Override
- public List<UserConsentModel> getConsents() {
- TypedQuery<UserConsentEntity> query = em.createNamedQuery("userConsentsByUser", UserConsentEntity.class);
- query.setParameter("userId", getId());
- List<UserConsentEntity> results = query.getResultList();
-
- List<UserConsentModel> consents = new ArrayList<UserConsentModel>();
- for (UserConsentEntity entity : results) {
- UserConsentModel model = toConsentModel(entity);
- consents.add(model);
- }
- return consents;
- }
-
- @Override
- public void updateConsent(UserConsentModel consent) {
- String clientId = consent.getClient().getId();
-
- UserConsentEntity consentEntity = getGrantedConsentEntity(clientId);
- if (consentEntity == null) {
- throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]");
- }
-
- updateGrantedConsentEntity(consentEntity, consent);
- }
-
- @Override
- public boolean revokeConsentForClient(String clientId) {
- UserConsentEntity consentEntity = getGrantedConsentEntity(clientId);
- if (consentEntity == null) return false;
-
- em.remove(consentEntity);
- em.flush();
- return true;
- }
-
-
- private UserConsentEntity getGrantedConsentEntity(String clientId) {
- TypedQuery<UserConsentEntity> query = em.createNamedQuery("userConsentByUserAndClient", UserConsentEntity.class);
- query.setParameter("userId", getId());
- query.setParameter("clientId", clientId);
- List<UserConsentEntity> results = query.getResultList();
- if (results.size() > 1) {
- throw new ModelException("More results found for user [" + getUsername() + "] and client [" + clientId + "]");
- } else if (results.size() == 1) {
- return results.get(0);
- } else {
- return null;
- }
- }
-
- private UserConsentModel toConsentModel(UserConsentEntity entity) {
- if (entity == null) {
- return null;
- }
-
- ClientModel client = realm.getClientById(entity.getClientId());
- if (client == null) {
- throw new ModelException("Client with id " + entity.getClientId() + " is not available");
- }
- UserConsentModel model = new UserConsentModel(client);
-
- Collection<UserConsentRoleEntity> grantedRoleEntities = entity.getGrantedRoles();
- if (grantedRoleEntities != null) {
- for (UserConsentRoleEntity grantedRole : grantedRoleEntities) {
- RoleModel grantedRoleModel = realm.getRoleById(grantedRole.getRoleId());
- if (grantedRoleModel != null) {
- model.addGrantedRole(grantedRoleModel);
- }
- }
- }
-
- Collection<UserConsentProtocolMapperEntity> grantedProtocolMapperEntities = entity.getGrantedProtocolMappers();
- if (grantedProtocolMapperEntities != null) {
- for (UserConsentProtocolMapperEntity grantedProtMapper : grantedProtocolMapperEntities) {
- ProtocolMapperModel protocolMapper = client.getProtocolMapperById(grantedProtMapper.getProtocolMapperId());
- model.addGrantedProtocolMapper(protocolMapper );
- }
- }
-
- return model;
- }
-
- // Update roles and protocolMappers to given consentEntity from the consentModel
- private void updateGrantedConsentEntity(UserConsentEntity consentEntity, UserConsentModel consentModel) {
- Collection<UserConsentProtocolMapperEntity> grantedProtocolMapperEntities = consentEntity.getGrantedProtocolMappers();
- Collection<UserConsentProtocolMapperEntity> mappersToRemove = new HashSet<UserConsentProtocolMapperEntity>(grantedProtocolMapperEntities);
-
- for (ProtocolMapperModel protocolMapper : consentModel.getGrantedProtocolMappers()) {
- UserConsentProtocolMapperEntity grantedProtocolMapperEntity = new UserConsentProtocolMapperEntity();
- grantedProtocolMapperEntity.setUserConsent(consentEntity);
- grantedProtocolMapperEntity.setProtocolMapperId(protocolMapper.getId());
-
- // Check if it's already there
- if (!grantedProtocolMapperEntities.contains(grantedProtocolMapperEntity)) {
- em.persist(grantedProtocolMapperEntity);
- em.flush();
- grantedProtocolMapperEntities.add(grantedProtocolMapperEntity);
- } else {
- mappersToRemove.remove(grantedProtocolMapperEntity);
- }
- }
- // Those mappers were no longer on consentModel and will be removed
- for (UserConsentProtocolMapperEntity toRemove : mappersToRemove) {
- grantedProtocolMapperEntities.remove(toRemove);
- em.remove(toRemove);
- }
-
- Collection<UserConsentRoleEntity> grantedRoleEntities = consentEntity.getGrantedRoles();
- Set<UserConsentRoleEntity> rolesToRemove = new HashSet<UserConsentRoleEntity>(grantedRoleEntities);
- for (RoleModel role : consentModel.getGrantedRoles()) {
- UserConsentRoleEntity consentRoleEntity = new UserConsentRoleEntity();
- consentRoleEntity.setUserConsent(consentEntity);
- consentRoleEntity.setRoleId(role.getId());
-
- // Check if it's already there
- if (!grantedRoleEntities.contains(consentRoleEntity)) {
- em.persist(consentRoleEntity);
- em.flush();
- grantedRoleEntities.add(consentRoleEntity);
- } else {
- rolesToRemove.remove(consentRoleEntity);
- }
- }
- // Those roles were no longer on consentModel and will be removed
- for (UserConsentRoleEntity toRemove : rolesToRemove) {
- grantedRoleEntities.remove(toRemove);
- em.remove(toRemove);
- }
-
- em.flush();
- }
@Override
public boolean equals(Object o) {
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java
new file mode 100755
index 0000000..a6b05cd
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+ @NamedQuery(name= "findBrokerLinkByUser", query="select link from BrokerLinkEntity link where link.userId = :userId"),
+ @NamedQuery(name= "findBrokerLinkByUserAndProvider", query="select link from BrokerLinkEntity link where link.userId = :userId and link.identityProvider = :identityProvider and link.realmId = :realmId"),
+ @NamedQuery(name= "findUserByBrokerLinkAndRealm", query="select link.userId from BrokerLinkEntity link where link.realmId = :realmId and link.identityProvider = :identityProvider and link.brokerUserId = :brokerUserId"),
+ @NamedQuery(name= "deleteBrokerLinkByStorageProvider", query="delete from BrokerLinkEntity social where social.storageProviderId = :storageProviderId"),
+ @NamedQuery(name= "deleteBrokerLinkByRealm", query="delete from BrokerLinkEntity social where social.realmId = :realmId"),
+ @NamedQuery(name= "deleteBrokerLinkByRealmAndLink", query="delete from BrokerLinkEntity social where social.userId IN (select u.id from UserEntity u where realmId=:realmId and u.federationLink=:link)"),
+ @NamedQuery(name= "deleteBrokerLinkByUser", query="delete from BrokerLinkEntity social where social.userId = :userId and social.realmId = :realmId")
+})
+@Table(name="BROKER_LINK")
+@Entity
+@IdClass(BrokerLinkEntity.Key.class)
+public class BrokerLinkEntity {
+
+ @Id
+ @Column(name = "USER_ID")
+ private String userId;
+
+ @Id
+ @Column(name = "IDENTITY_PROVIDER")
+ protected String identityProvider;
+
+ @Column(name = "REALM_ID")
+ protected String realmId;
+
+ @Column(name = "STORAGE_PROVIDER_ID")
+ protected String storageProviderId;
+
+ @Column(name = "BROKER_USER_ID")
+ protected String brokerUserId;
+ @Column(name = "BROKER_USERNAME")
+ protected String brokerUserName;
+
+ @Column(name = "TOKEN")
+ protected String token;
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getIdentityProvider() {
+ return identityProvider;
+ }
+
+ public void setIdentityProvider(String identityProvider) {
+ this.identityProvider = identityProvider;
+ }
+
+ public String getBrokerUserId() {
+ return brokerUserId;
+ }
+
+ public void setBrokerUserId(String brokerUserId) {
+ this.brokerUserId = brokerUserId;
+ }
+
+ public String getBrokerUserName() {
+ return brokerUserName;
+ }
+
+ public void setBrokerUserName(String brokerUserName) {
+ this.brokerUserName = brokerUserName;
+ }
+
+ public String getStorageProviderId() {
+ return storageProviderId;
+ }
+
+ public void setStorageProviderId(String storageProviderId) {
+ this.storageProviderId = storageProviderId;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public static class Key implements Serializable {
+
+ protected String userId;
+
+ protected String identityProvider;
+
+ public Key() {
+ }
+
+ public Key(String userId, String identityProvider) {
+ this.userId = userId;
+ this.identityProvider = identityProvider;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public String getIdentityProvider() {
+ return identityProvider;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Key key = (Key) o;
+
+ if (identityProvider != null ? !identityProvider.equals(key.identityProvider) : key.identityProvider != null)
+ return false;
+ if (userId != null ? !userId.equals(key.userId != null ? key.userId : null) : key.userId != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = userId != null ? userId.hashCode() : 0;
+ result = 31 * result + (identityProvider != null ? identityProvider.hashCode() : 0);
+ return result;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (!(o instanceof BrokerLinkEntity)) return false;
+
+ BrokerLinkEntity key = (BrokerLinkEntity) o;
+
+ if (identityProvider != null ? !identityProvider.equals(key.identityProvider) : key.identityProvider != null)
+ return false;
+ if (userId != null ? !userId.equals(key.userId != null ? key.userId : null) : key.userId != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = userId != null ? userId.hashCode() : 0;
+ result = 31 * result + (identityProvider != null ? identityProvider.hashCode() : 0);
+ return result;
+ }
+
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserAttributeEntity.java
new file mode 100755
index 0000000..071eacd
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserAttributeEntity.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+ @NamedQuery(name="getFederatedAttributesByNameAndValue", query="select attr from FederatedUserAttributeEntity attr where attr.name = :name and attr.value = :value and attr.realmId=:realmId"),
+ @NamedQuery(name="getFederatedAttributesByUser", query="select attr from FederatedUserAttributeEntity attr where attr.userId = :userId and attr.realmId=:realmId"),
+ @NamedQuery(name="deleteUserFederatedAttributesByUser", query="delete from FederatedUserAttributeEntity attr where attr.userId = :userId and attr.realmId=:realmId"),
+ @NamedQuery(name="deleteUserFederatedAttributesByUserAndName", query="delete from FederatedUserAttributeEntity attr where attr.userId = :userId and attr.name=:name and attr.realmId=:realmId"),
+ @NamedQuery(name="deleteUserFederatedAttributesByRealm", query="delete from FederatedUserAttributeEntity attr where attr.realmId=:realmId"),
+ @NamedQuery(name="deleteFederatedAttributesByStorageProvider", query="delete from FederatedUserAttributeEntity e where e.storageProviderId=:storageProviderId"),
+ @NamedQuery(name="deleteUserFederatedAttributesByRealmAndLink", query="delete from FederatedUserAttributeEntity attr where attr.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
+})
+@Table(name="FED_USER_ATTRIBUTE")
+@Entity
+public class FederatedUserAttributeEntity {
+
+ @Id
+ @Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
+ protected String id;
+
+ @Column(name = "USER_ID")
+ protected String userId;
+
+ @Column(name = "REALM_ID")
+ protected String realmId;
+
+ @Column(name = "STORAGE_PROVIDER_ID")
+ protected String storageProviderId;
+
+ @Column(name = "NAME")
+ protected String name;
+ @Column(name = "VALUE")
+ protected String value;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getStorageProviderId() {
+ return storageProviderId;
+ }
+
+ public void setStorageProviderId(String storageProviderId) {
+ this.storageProviderId = storageProviderId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (!(o instanceof FederatedUserAttributeEntity)) return false;
+
+ FederatedUserAttributeEntity that = (FederatedUserAttributeEntity) o;
+
+ if (!id.equals(that.getId())) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java
new file mode 100755
index 0000000..8066310
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@Entity
+@Table(name="FED_USER_CONSENT", uniqueConstraints = {
+ @UniqueConstraint(columnNames = {"USER_ID", "CLIENT_ID"})
+})
+@NamedQueries({
+ @NamedQuery(name="userFederatedConsentByUserAndClient", query="select consent from FederatedUserConsentEntity consent where consent.userId = :userId and consent.clientId = :clientId"),
+ @NamedQuery(name="userFederatedConsentsByUser", query="select consent from FederatedUserConsentEntity consent where consent.userId = :userId"),
+ @NamedQuery(name="deleteFederatedUserConsentsByRealm", query="delete from FederatedUserConsentEntity consent where consent.realmId=:realmId"),
+ @NamedQuery(name="deleteFederatedUserConsentsByStorageProvider", query="delete from FederatedUserConsentEntity e where e.storageProviderId=:storageProviderId"),
+ @NamedQuery(name="deleteFederatedUserConsentsByUser", query="delete from FederatedUserConsentEntity consent where consent.userId = :userId and consent.realmId = :realmId"),
+ @NamedQuery(name="deleteFederatedUserConsentsByClient", query="delete from FederatedUserConsentEntity consent where consent.clientId = :clientId"),
+})
+public class FederatedUserConsentEntity {
+
+ @Id
+ @Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
+ protected String id;
+
+ @Column(name = "USER_ID")
+ protected String userId;
+
+ @Column(name = "REALM_ID")
+ protected String realmId;
+
+ @Column(name = "STORAGE_PROVIDER_ID")
+ protected String storageProviderId;
+
+ @Column(name="CLIENT_ID")
+ protected String clientId;
+
+ @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "userConsent")
+ Collection<FederatedUserConsentRoleEntity> grantedRoles = new ArrayList<FederatedUserConsentRoleEntity>();
+
+ @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "userConsent")
+ Collection<FederatedUserConsentProtocolMapperEntity> grantedProtocolMappers = new ArrayList<FederatedUserConsentProtocolMapperEntity>();
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getStorageProviderId() {
+ return storageProviderId;
+ }
+
+ public void setStorageProviderId(String storageProviderId) {
+ this.storageProviderId = storageProviderId;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public Collection<FederatedUserConsentRoleEntity> getGrantedRoles() {
+ return grantedRoles;
+ }
+
+ public void setGrantedRoles(Collection<FederatedUserConsentRoleEntity> grantedRoles) {
+ this.grantedRoles = grantedRoles;
+ }
+
+ public Collection<FederatedUserConsentProtocolMapperEntity> getGrantedProtocolMappers() {
+ return grantedProtocolMappers;
+ }
+
+ public void setGrantedProtocolMappers(Collection<FederatedUserConsentProtocolMapperEntity> grantedProtocolMappers) {
+ this.grantedProtocolMappers = grantedProtocolMappers;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (!(o instanceof FederatedUserConsentEntity)) return false;
+
+ FederatedUserConsentEntity that = (FederatedUserConsentEntity) o;
+
+ if (!id.equals(that.getId())) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentProtocolMapperEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentProtocolMapperEntity.java
new file mode 100755
index 0000000..f7da2cc
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentProtocolMapperEntity.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NamedQueries({
+ @NamedQuery(name="deleteFederatedUserConsentProtMappersByRealm", query=
+ "delete from FederatedUserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.realmId = :realmId)"),
+ @NamedQuery(name="deleteFederatedUserConsentProtMappersByUser", query="delete from FederatedUserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.userId = :userId and consent.realmId = :realmId)"),
+ @NamedQuery(name="deleteFederatedUserConsentProtMappersByStorageProvider", query="delete from FederatedUserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.storageProviderId = :storageProviderId)"),
+ @NamedQuery(name="deleteFederatedUserConsentProtMappersByProtocolMapper", query="delete from FederatedUserConsentProtocolMapperEntity csm where csm.protocolMapperId = :protocolMapperId"),
+ @NamedQuery(name="deleteFederatedUserConsentProtMappersByClient", query="delete from FederatedUserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.clientId = :clientId)"),
+})
+@Entity
+@Table(name="FED_USER_CONSENT_PROT_MAPPER")
+@IdClass(FederatedUserConsentProtocolMapperEntity.Key.class)
+public class FederatedUserConsentProtocolMapperEntity {
+
+ @Id
+ @ManyToOne(fetch= FetchType.LAZY)
+ @JoinColumn(name = "USER_CONSENT_ID")
+ protected FederatedUserConsentEntity userConsent;
+
+ @Id
+ @Column(name="PROTOCOL_MAPPER_ID")
+ protected String protocolMapperId;
+
+ public FederatedUserConsentEntity getUserConsent() {
+ return userConsent;
+ }
+
+ public void setUserConsent(FederatedUserConsentEntity userConsent) {
+ this.userConsent = userConsent;
+ }
+
+ public String getProtocolMapperId() {
+ return protocolMapperId;
+ }
+
+ public void setProtocolMapperId(String protocolMapperId) {
+ this.protocolMapperId = protocolMapperId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (!(o instanceof FederatedUserConsentProtocolMapperEntity)) return false;
+
+ FederatedUserConsentProtocolMapperEntity that = (FederatedUserConsentProtocolMapperEntity)o;
+ Key myKey = new Key(this.userConsent, this.protocolMapperId);
+ Key hisKey = new Key(that.userConsent, that.protocolMapperId);
+ return myKey.equals(hisKey);
+ }
+
+ @Override
+ public int hashCode() {
+ Key myKey = new Key(this.userConsent, this.protocolMapperId);
+ return myKey.hashCode();
+ }
+
+ public static class Key implements Serializable {
+
+ protected FederatedUserConsentEntity userConsent;
+
+ protected String protocolMapperId;
+
+ public Key() {
+ }
+
+ public Key(FederatedUserConsentEntity userConsent, String protocolMapperId) {
+ this.userConsent = userConsent;
+ this.protocolMapperId = protocolMapperId;
+ }
+
+ public FederatedUserConsentEntity getUserConsent() {
+ return userConsent;
+ }
+
+ public String getProtocolMapperId() {
+ return protocolMapperId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Key key = (Key) o;
+
+ if (userConsent != null ? !userConsent.getId().equals(key.userConsent != null ? key.userConsent.getId() : null) : key.userConsent != null) return false;
+ if (protocolMapperId != null ? !protocolMapperId.equals(key.protocolMapperId) : key.protocolMapperId != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = userConsent != null ? userConsent.getId().hashCode() : 0;
+ result = 31 * result + (protocolMapperId != null ? protocolMapperId.hashCode() : 0);
+ return result;
+ }
+ }
+
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentRoleEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentRoleEntity.java
new file mode 100755
index 0000000..d74865d
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentRoleEntity.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NamedQueries({
+ @NamedQuery(name="deleteFederatedUserConsentRolesByRealm", query="delete from FederatedUserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.realmId = :realmId)"),
+ @NamedQuery(name="deleteFederatedUserConsentRolesByUser", query="delete from FederatedUserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.userId = :userId and consent.realmId = :realmId)"),
+ @NamedQuery(name="deleteFederatedUserConsentRolesByStorageProvider", query="delete from FederatedUserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.storageProviderId = :storageProviderId)"),
+ @NamedQuery(name="deleteFederatedUserConsentRolesByRole", query="delete from FederatedUserConsentRoleEntity grantedRole where grantedRole.roleId = :roleId"),
+ @NamedQuery(name="deleteFederatedUserConsentRolesByClient", query="delete from FederatedUserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.clientId = :clientId)"),
+})
+@Entity
+@Table(name="FED_USER_CONSENT_ROLE")
+@IdClass(FederatedUserConsentRoleEntity.Key.class)
+public class FederatedUserConsentRoleEntity {
+
+ @Id
+ @ManyToOne(fetch= FetchType.LAZY)
+ @JoinColumn(name = "USER_CONSENT_ID")
+ protected FederatedUserConsentEntity userConsent;
+
+ @Id
+ @Column(name="ROLE_ID")
+ protected String roleId;
+
+ public FederatedUserConsentEntity getUserConsent() {
+ return userConsent;
+ }
+
+ public void setUserConsent(FederatedUserConsentEntity userConsent) {
+ this.userConsent = userConsent;
+ }
+
+ public String getRoleId() {
+ return roleId;
+ }
+
+ public void setRoleId(String roleId) {
+ this.roleId = roleId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (!(o instanceof FederatedUserConsentRoleEntity)) return false;
+
+ FederatedUserConsentRoleEntity that = (FederatedUserConsentRoleEntity)o;
+ Key myKey = new Key(this.userConsent, this.roleId);
+ Key hisKey = new Key(that.userConsent, that.roleId);
+ return myKey.equals(hisKey);
+ }
+
+ @Override
+ public int hashCode() {
+ Key myKey = new Key(this.userConsent, this.roleId);
+ return myKey.hashCode();
+ }
+
+ public static class Key implements Serializable {
+
+ protected FederatedUserConsentEntity userConsent;
+
+ protected String roleId;
+
+ public Key() {
+ }
+
+ public Key(FederatedUserConsentEntity userConsent, String roleId) {
+ this.userConsent = userConsent;
+ this.roleId = roleId;
+ }
+
+ public FederatedUserConsentEntity getUserConsent() {
+ return userConsent;
+ }
+
+ public String getRoleId() {
+ return roleId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Key key = (Key) o;
+
+ if (userConsent != null ? !userConsent.getId().equals(key.userConsent != null ? key.userConsent.getId() : null) : key.userConsent != null) return false;
+ if (roleId != null ? !roleId.equals(key.roleId) : key.roleId != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = userConsent != null ? userConsent.getId().hashCode() : 0;
+ result = 31 * result + (roleId != null ? roleId.hashCode() : 0);
+ return result;
+ }
+ }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
new file mode 100755
index 0000000..996608b
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import org.keycloak.models.jpa.entities.UserEntity;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+ @NamedQuery(name="federatedUserCredentialByUser", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId"),
+ @NamedQuery(name="federatedUserCredentialByUserAndType", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"),
+ @NamedQuery(name="deleteFederatedUserCredentialByUser", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.realmId = :realmId"),
+ @NamedQuery(name="deleteFederatedUserCredentialByUserAndType", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"),
+ @NamedQuery(name="deleteFederatedUserCredentialByUserAndTypeAndDevice", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type and cred.device = :device"),
+ @NamedQuery(name="deleteFederatedUserCredentialsByRealm", query="delete from FederatedUserCredentialEntity cred where cred.realmId=:realmId"),
+ @NamedQuery(name="deleteFederatedUserCredentialsByStorageProvider", query="delete from FederatedUserCredentialEntity cred where cred.storageProviderId=:storageProviderId"),
+ @NamedQuery(name="deleteFederatedUserCredentialsByRealmAndLink", query="delete from FederatedUserCredentialEntity cred where cred.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
+
+})
+@Table(name="FED_USER_CREDENTIAL")
+@Entity
+public class FederatedUserCredentialEntity {
+ @Id
+ @Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
+ protected String id;
+
+ @Column(name="TYPE")
+ protected String type;
+ @Column(name="VALUE")
+ protected String value;
+ @Column(name="DEVICE")
+ protected String device;
+ @Column(name="SALT")
+ protected byte[] salt;
+ @Column(name="HASH_ITERATIONS")
+ protected int hashIterations;
+ @Column(name="CREATED_DATE")
+ protected Long createdDate;
+
+ @Column(name="USER_ID")
+ protected String userId;
+
+ @Column(name = "REALM_ID")
+ protected String realmId;
+
+ @Column(name = "STORAGE_PROVIDER_ID")
+ protected String storageProviderId;
+
+
+
+ @Column(name="COUNTER")
+ protected int counter;
+
+ @Column(name="ALGORITHM")
+ protected String algorithm;
+ @Column(name="DIGITS")
+ protected int digits;
+ @Column(name="PERIOD")
+ protected int period;
+
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getDevice() {
+ return device;
+ }
+
+ public void setDevice(String device) {
+ this.device = device;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getStorageProviderId() {
+ return storageProviderId;
+ }
+
+ public void setStorageProviderId(String storageProviderId) {
+ this.storageProviderId = storageProviderId;
+ }
+
+ public byte[] getSalt() {
+ return salt;
+ }
+
+ public void setSalt(byte[] salt) {
+ this.salt = salt;
+ }
+
+ public int getHashIterations() {
+ return hashIterations;
+ }
+
+ public void setHashIterations(int hashIterations) {
+ this.hashIterations = hashIterations;
+ }
+
+ public Long getCreatedDate() {
+ return createdDate;
+ }
+
+ public void setCreatedDate(Long createdDate) {
+ this.createdDate = createdDate;
+ }
+
+ public int getCounter() {
+ return counter;
+ }
+
+ public void setCounter(int counter) {
+ this.counter = counter;
+ }
+
+ public String getAlgorithm() {
+ return algorithm;
+ }
+
+ public void setAlgorithm(String algorithm) {
+ this.algorithm = algorithm;
+ }
+
+ public int getDigits() {
+ return digits;
+ }
+
+ public void setDigits(int digits) {
+ this.digits = digits;
+ }
+
+ public int getPeriod() {
+ return period;
+ }
+
+ public void setPeriod(int period) {
+ this.period = period;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (!(o instanceof FederatedUserCredentialEntity)) return false;
+
+ FederatedUserCredentialEntity that = (FederatedUserCredentialEntity) o;
+
+ if (!id.equals(that.getId())) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java
new file mode 100755
index 0000000..ecfaa49
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import org.keycloak.models.jpa.entities.UserEntity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+ @NamedQuery(name="feduserMemberOf", query="select m from FederatedUserGroupMembershipEntity m where m.userId = :userId and m.groupId = :groupId"),
+ @NamedQuery(name="feduserGroupMembership", query="select m from FederatedUserGroupMembershipEntity m where m.userId = :userId"),
+ @NamedQuery(name="fedgroupMembership", query="select g.userId from FederatedUserGroupMembershipEntity g where g.groupId = :groupId"),
+ @NamedQuery(name="feduserGroupIds", query="select m.groupId from FederatedUserGroupMembershipEntity m where m.userId = :userId"),
+ @NamedQuery(name="deleteFederatedUserGroupMembershipByRealm", query="delete from FederatedUserGroupMembershipEntity mapping where mapping.realmId=:realmId"),
+ @NamedQuery(name="deleteFederatedUserGroupMembershipByStorageProvider", query="delete from FederatedUserGroupMembershipEntity e where e.storageProviderId=:storageProviderId"),
+ @NamedQuery(name="deleteFederatedUserGroupMembershipsByRealmAndLink", query="delete from FederatedUserGroupMembershipEntity mapping where mapping.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
+ @NamedQuery(name="deleteFederatedUserGroupMembershipsByGroup", query="delete from FederatedUserGroupMembershipEntity m where m.groupId = :groupId"),
+ @NamedQuery(name="deleteFederatedUserGroupMembershipsByUser", query="delete from FederatedUserGroupMembershipEntity m where m.userId = :userId and m.realmId = :realmId")
+
+})
+@Table(name="FED_USER_GROUP_MEMBERSHIP")
+@Entity
+@IdClass(FederatedUserGroupMembershipEntity.Key.class)
+public class FederatedUserGroupMembershipEntity {
+
+ @Id
+ @Column(name = "USER_ID")
+ protected String userId;
+
+ @Id
+ @Column(name = "GROUP_ID")
+ protected String groupId;
+
+ @Column(name = "REALM_ID")
+ protected String realmId;
+
+ @Column(name = "STORAGE_PROVIDER_ID")
+ protected String storageProviderId;
+
+ public String getGroupId() {
+ return groupId;
+ }
+
+ public void setGroupId(String groupId) {
+ this.groupId = groupId;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getStorageProviderId() {
+ return storageProviderId;
+ }
+
+ public void setStorageProviderId(String storageProviderId) {
+ this.storageProviderId = storageProviderId;
+ }
+
+ public static class Key implements Serializable {
+
+ protected String userId;
+
+ protected String groupId;
+
+ public Key() {
+ }
+
+ public Key(String userId, String groupId) {
+ this.userId = userId;
+ this.groupId = groupId;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public String getGroupId() {
+ return groupId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Key key = (Key) o;
+
+ if (!groupId.equals(key.groupId)) return false;
+ if (!userId.equals(key.userId)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = userId.hashCode();
+ result = 31 * result + groupId.hashCode();
+ return result;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (!(o instanceof FederatedUserGroupMembershipEntity)) return false;
+
+ FederatedUserGroupMembershipEntity key = (FederatedUserGroupMembershipEntity) o;
+
+ if (!groupId.equals(key.groupId)) return false;
+ if (!userId.equals(key.userId)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = userId.hashCode();
+ result = 31 * result + groupId.hashCode();
+ return result;
+ }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java
new file mode 100755
index 0000000..f555628
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import org.keycloak.models.jpa.entities.UserEntity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+ @NamedQuery(name="getFederatedUserRequiredActionsByUser", query="select action from FederatedUserRequiredActionEntity action where action.userId = :userId and action.realmId=:realmId"),
+ @NamedQuery(name="deleteFederatedUserRequiredActionsByRealm", query="delete from FederatedUserRequiredActionEntity action where action.realmId=:realmId"),
+ @NamedQuery(name="deleteFederatedUserRequiredActionsByStorageProvider", query="delete from FederatedUserRequiredActionEntity e where e.storageProviderId=:storageProviderId"),
+ @NamedQuery(name="deleteFederatedUserRequiredActionsByRealmAndLink", query="delete from FederatedUserRequiredActionEntity action where action.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
+})
+@Entity
+@Table(name="FED_USER_REQUIRED_ACTION")
+@IdClass(FederatedUserRequiredActionEntity.Key.class)
+public class FederatedUserRequiredActionEntity {
+
+ @Id
+ @Column(name="USER_ID")
+ protected String userId;
+
+ @Id
+ @Column(name="REQUIRED_ACTION")
+ protected String action;
+
+ @Column(name = "REALM_ID")
+ protected String realmId;
+
+ @Column(name = "STORAGE_PROVIDER_ID")
+ protected String storageProviderId;
+
+ public String getAction() {
+ return action;
+ }
+
+ public void setAction(String action) {
+ this.action = action;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getStorageProviderId() {
+ return storageProviderId;
+ }
+
+ public void setStorageProviderId(String storageProviderId) {
+ this.storageProviderId = storageProviderId;
+ }
+
+ public static class Key implements Serializable {
+
+ protected String userId;
+
+ protected String action;
+
+ public Key() {
+ }
+
+ public Key(String user, String action) {
+ this.userId = user;
+ this.action = action;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public String getAction() {
+ return action;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Key key = (Key) o;
+
+ if (action != key.action) return false;
+ if (userId != null ? !userId.equals(key.userId != null ? key.userId : null) : key.userId != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = userId != null ? userId.hashCode() : 0;
+ result = 31 * result + (action != null ? action.hashCode() : 0);
+ return result;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (!(o instanceof FederatedUserRequiredActionEntity)) return false;
+
+ FederatedUserRequiredActionEntity key = (FederatedUserRequiredActionEntity) o;
+
+ if (action != key.action) return false;
+ if (userId != null ? !userId.equals(key.userId != null ? key.userId : null) : key.userId != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = userId != null ? userId.hashCode() : 0;
+ result = 31 * result + (action != null ? action.hashCode() : 0);
+ return result;
+ }
+
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java
new file mode 100755
index 0000000..719fce6
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import org.keycloak.models.jpa.entities.UserEntity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+ @NamedQuery(name="feduserHasRole", query="select m from FederatedUserRoleMappingEntity m where m.userId = :userId and m.roleId = :roleId"),
+ @NamedQuery(name="feduserRoleMappings", query="select m from FederatedUserRoleMappingEntity m where m.userId = :userId"),
+ @NamedQuery(name="deleteFederatedUserRoleMappingsByRealm", query="delete from FederatedUserRoleMappingEntity mapping where mapping.realmId=:realmId"),
+ @NamedQuery(name="deleteFederatedUserRoleMappingsByStorageProvider", query="delete from FederatedUserRoleMappingEntity e where e.storageProviderId=:storageProviderId"),
+ @NamedQuery(name="deleteFederatedUserRoleMappingsByRealmAndLink", query="delete from FederatedUserRoleMappingEntity mapping where mapping.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
+ @NamedQuery(name="deleteFederatedUserRoleMappingsByRole", query="delete from FederatedUserRoleMappingEntity m where m.roleId = :roleId"),
+ @NamedQuery(name="deleteFederatedUserRoleMappingsByUser", query="delete from FederatedUserRoleMappingEntity m where m.userId = :userId and m.realmId = :realmId"),
+
+})
+@Table(name="FED_USER_ROLE_MAPPING")
+@Entity
+@IdClass(FederatedUserRoleMappingEntity.Key.class)
+public class FederatedUserRoleMappingEntity {
+
+ @Id
+ @Column(name = "USER_ID")
+ protected String userId;
+
+ @Id
+ @Column(name = "ROLE_ID")
+ protected String roleId;
+
+ @Column(name = "REALM_ID")
+ protected String realmId;
+
+ @Column(name = "STORAGE_PROVIDER_ID")
+ protected String storageProviderId;
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getStorageProviderId() {
+ return storageProviderId;
+ }
+
+ public void setStorageProviderId(String storageProviderId) {
+ this.storageProviderId = storageProviderId;
+ }
+
+ public String getRoleId() {
+ return roleId;
+ }
+
+ public void setRoleId(String roleId) {
+ this.roleId = roleId;
+ }
+
+
+ public static class Key implements Serializable {
+
+ protected String userId;
+
+ protected String roleId;
+
+ public Key() {
+ }
+
+ public Key(String userId, String roleId) {
+ this.userId = userId;
+ this.roleId = roleId;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public String getRoleId() {
+ return roleId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Key key = (Key) o;
+
+ if (!roleId.equals(key.roleId)) return false;
+ if (!userId.equals(key.userId)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = userId.hashCode();
+ result = 31 * result + roleId.hashCode();
+ return result;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (!(o instanceof FederatedUserRoleMappingEntity)) return false;
+
+ FederatedUserRoleMappingEntity key = (FederatedUserRoleMappingEntity) o;
+
+ if (!roleId.equals(key.roleId)) return false;
+ if (!userId.equals(key.userId)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = userId.hashCode();
+ result = 31 * result + roleId.hashCode();
+ return result;
+ }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
new file mode 100644
index 0000000..4ec58bd
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
@@ -0,0 +1,734 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.jpa;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserConsentModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.jpa.entities.CredentialEntity;
+import org.keycloak.models.utils.FederatedCredentials;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.StorageId;
+import org.keycloak.storage.StorageProviderModel;
+import org.keycloak.storage.federated.UserAttributeFederatedStorage;
+import org.keycloak.storage.federated.UserBrokerLinkFederatedStorage;
+import org.keycloak.storage.federated.UserConsentFederatedStorage;
+import org.keycloak.storage.federated.UserCredentialsFederatedStorage;
+import org.keycloak.storage.federated.UserFederatedStorageProvider;
+import org.keycloak.storage.federated.UserGroupMembershipFederatedStorage;
+import org.keycloak.storage.federated.UserRequiredActionsFederatedStorage;
+import org.keycloak.storage.federated.UserRoleMappingsFederatedStorage;
+import org.keycloak.storage.jpa.entity.BrokerLinkEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserAttributeEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserConsentEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserRoleMappingEntity;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaUserFederatedStorageProvider implements
+ UserFederatedStorageProvider,
+ UserAttributeFederatedStorage,
+ UserBrokerLinkFederatedStorage,
+ UserConsentFederatedStorage,
+ UserCredentialsFederatedStorage,
+ UserGroupMembershipFederatedStorage,
+ UserRequiredActionsFederatedStorage,
+ UserRoleMappingsFederatedStorage {
+
+ private final KeycloakSession session;
+ protected EntityManager em;
+
+ public JpaUserFederatedStorageProvider(KeycloakSession session, EntityManager em) {
+ this.session = session;
+ this.em = em;
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+
+ @Override
+ public void setAttribute(RealmModel realm, UserModel user, String name, List<String> values) {
+ deleteAttribute(realm, user, name);
+ em.flush();
+ for (String value : values) {
+ persistAttributeValue(realm, user, name, value);
+ }
+ }
+
+ private void deleteAttribute(RealmModel realm, UserModel user, String name) {
+ em.createNamedQuery("deleteUserFederatedAttributesByUserAndName")
+ .setParameter("userId", user.getId())
+ .setParameter("realmId", realm.getId())
+ .setParameter("name", name)
+ .executeUpdate();
+ }
+
+ private void persistAttributeValue(RealmModel realm, UserModel user, String name, String value) {
+ FederatedUserAttributeEntity attr = new FederatedUserAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setName(name);
+ attr.setValue(value);
+ attr.setUserId(user.getId());
+ attr.setRealmId(realm.getId());
+ attr.setStorageProviderId(StorageId.resolveProviderId(user));
+ em.persist(attr);
+ }
+
+ @Override
+ public void setSingleAttribute(RealmModel realm, UserModel user, String name, String value) {
+ deleteAttribute(realm, user, name);
+ em.flush();
+ persistAttributeValue(realm, user, name, value);
+ }
+
+ @Override
+ public void removeAttribute(RealmModel realm, UserModel user, String name) {
+ deleteAttribute(realm, user, name);
+ em.flush();
+ }
+
+ @Override
+ public MultivaluedHashMap<String, String> getAttributes(RealmModel realm, UserModel user) {
+ TypedQuery<FederatedUserAttributeEntity> query = em.createNamedQuery("getFederatedAttributesByUser", FederatedUserAttributeEntity.class);
+ List<FederatedUserAttributeEntity> list = query
+ .setParameter("userId", user.getId())
+ .setParameter("realmId", realm.getId())
+ .getResultList();
+ MultivaluedHashMap<String, String> result = new MultivaluedHashMap<>();
+ for (FederatedUserAttributeEntity entity : list) {
+ result.add(entity.getName(), entity.getValue());
+
+ }
+ return result;
+ }
+
+ @Override
+ public String getUserByFederatedIdentity(FederatedIdentityModel link, RealmModel realm) {
+ TypedQuery<String> query = em.createNamedQuery("findUserByBrokerLinkAndRealm", String.class)
+ .setParameter("realmId", realm.getId())
+ .setParameter("identityProvider", link.getIdentityProvider())
+ .setParameter("brokerUserId", link.getUserId());
+ List<String> results = query.getResultList();
+ if (results.isEmpty()) {
+ return null;
+ } else if (results.size() > 1) {
+ throw new IllegalStateException("More results found for identityProvider=" + link.getIdentityProvider() +
+ ", userId=" + link.getUserId() + ", results=" + results);
+ } else {
+ return results.get(0);
+ }
+ }
+
+ @Override
+ public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel link) {
+ BrokerLinkEntity entity = new BrokerLinkEntity();
+ entity.setRealmId(realm.getId());
+ entity.setUserId(user.getId());
+ entity.setBrokerUserId(link.getUserId());
+ entity.setIdentityProvider(link.getIdentityProvider());
+ entity.setToken(link.getToken());
+ entity.setBrokerUserName(link.getUserName());
+ entity.setStorageProviderId(StorageId.resolveProviderId(user));
+ em.persist(entity);
+
+ }
+
+ @Override
+ public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
+ BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, socialProvider);
+ if (entity == null) return false;
+ em.remove(entity);
+ return true;
+ }
+
+ private BrokerLinkEntity getBrokerLinkEntity(RealmModel realm, UserModel user, String socialProvider) {
+ TypedQuery<BrokerLinkEntity> query = em.createNamedQuery("findBrokerLinkByUserAndProvider", BrokerLinkEntity.class)
+ .setParameter("userId", user.getId())
+ .setParameter("realmId", realm.getId())
+ .setParameter("identityProvider", socialProvider);
+ List<BrokerLinkEntity> results = query.getResultList();
+ return results.size() > 0 ? results.get(0) : null;
+ }
+
+ @Override
+ public void updateFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel model) {
+ BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, model.getIdentityProvider());
+ if (entity == null) return;
+ entity.setBrokerUserName(model.getUserName());
+ entity.setBrokerUserId(model.getUserId());
+ entity.setToken(model.getToken());
+ em.persist(entity);
+ em.flush();
+
+ }
+
+ @Override
+ public Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm) {
+ TypedQuery<BrokerLinkEntity> query = em.createNamedQuery("findBrokerLinkByUser", BrokerLinkEntity.class)
+ .setParameter("userId", user.getId());
+ List<BrokerLinkEntity> results = query.getResultList();
+ Set<FederatedIdentityModel> set = new HashSet<>();
+ for (BrokerLinkEntity entity : results) {
+ FederatedIdentityModel model = new FederatedIdentityModel(entity.getIdentityProvider(), entity.getBrokerUserId(), entity.getBrokerUserName(), entity.getToken());
+ set.add(model);
+ }
+ return set;
+ }
+
+ @Override
+ public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
+ BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, socialProvider);
+ if (entity == null) return null;
+ return new FederatedIdentityModel(entity.getIdentityProvider(), entity.getBrokerUserId(), entity.getBrokerUserName(), entity.getToken());
+ }
+
+ @Override
+ public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+ String clientId = consent.getClient().getId();
+
+ FederatedUserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
+ if (consentEntity != null) {
+ throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]");
+ }
+
+ consentEntity = new FederatedUserConsentEntity();
+ consentEntity.setId(KeycloakModelUtils.generateId());
+ consentEntity.setUserId(user.getId());
+ consentEntity.setClientId(clientId);
+ consentEntity.setRealmId(realm.getId());
+ consentEntity.setStorageProviderId(StorageId.resolveProviderId(user));
+ em.persist(consentEntity);
+ em.flush();
+
+ updateGrantedConsentEntity(consentEntity, consent);
+
+ }
+
+ @Override
+ public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId) {
+ FederatedUserConsentEntity entity = getGrantedConsentEntity(user, clientInternalId);
+ return toConsentModel(realm, entity);
+ }
+
+ @Override
+ public List<UserConsentModel> getConsents(RealmModel realm, UserModel user) {
+ TypedQuery<FederatedUserConsentEntity> query = em.createNamedQuery("userFederatedConsentsByUser", FederatedUserConsentEntity.class);
+ query.setParameter("userId", user.getId());
+ List<FederatedUserConsentEntity> results = query.getResultList();
+
+ List<UserConsentModel> consents = new ArrayList<UserConsentModel>();
+ for (FederatedUserConsentEntity entity : results) {
+ UserConsentModel model = toConsentModel(realm, entity);
+ consents.add(model);
+ }
+ return consents;
+ }
+
+ @Override
+ public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+ String clientId = consent.getClient().getId();
+
+ FederatedUserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
+ if (consentEntity == null) {
+ throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]");
+ }
+
+ updateGrantedConsentEntity(consentEntity, consent);
+
+ }
+
+ @Override
+ public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId) {
+ FederatedUserConsentEntity consentEntity = getGrantedConsentEntity(user, clientInternalId);
+ if (consentEntity == null) return false;
+
+ em.remove(consentEntity);
+ em.flush();
+ return true;
+ }
+
+ private FederatedUserConsentEntity getGrantedConsentEntity(UserModel user, String clientId) {
+ TypedQuery<FederatedUserConsentEntity> query = em.createNamedQuery("userFederatedConsentByUserAndClient", FederatedUserConsentEntity.class);
+ query.setParameter("userId", user.getId());
+ query.setParameter("clientId", clientId);
+ List<FederatedUserConsentEntity> results = query.getResultList();
+ if (results.size() > 1) {
+ throw new ModelException("More results found for user [" + user.getUsername() + "] and client [" + clientId + "]");
+ } else if (results.size() == 1) {
+ return results.get(0);
+ } else {
+ return null;
+ }
+ }
+
+
+ private UserConsentModel toConsentModel(RealmModel realm, FederatedUserConsentEntity entity) {
+ if (entity == null) {
+ return null;
+ }
+
+ ClientModel client = realm.getClientById(entity.getClientId());
+ if (client == null) {
+ throw new ModelException("Client with id " + entity.getClientId() + " is not available");
+ }
+ UserConsentModel model = new UserConsentModel(client);
+
+ Collection<FederatedUserConsentRoleEntity> grantedRoleEntities = entity.getGrantedRoles();
+ if (grantedRoleEntities != null) {
+ for (FederatedUserConsentRoleEntity grantedRole : grantedRoleEntities) {
+ RoleModel grantedRoleModel = realm.getRoleById(grantedRole.getRoleId());
+ if (grantedRoleModel != null) {
+ model.addGrantedRole(grantedRoleModel);
+ }
+ }
+ }
+
+ Collection<FederatedUserConsentProtocolMapperEntity> grantedProtocolMapperEntities = entity.getGrantedProtocolMappers();
+ if (grantedProtocolMapperEntities != null) {
+ for (FederatedUserConsentProtocolMapperEntity grantedProtMapper : grantedProtocolMapperEntities) {
+ ProtocolMapperModel protocolMapper = client.getProtocolMapperById(grantedProtMapper.getProtocolMapperId());
+ model.addGrantedProtocolMapper(protocolMapper);
+ }
+ }
+
+ return model;
+ }
+
+ // Update roles and protocolMappers to given consentEntity from the consentModel
+ private void updateGrantedConsentEntity(FederatedUserConsentEntity consentEntity, UserConsentModel consentModel) {
+ Collection<FederatedUserConsentProtocolMapperEntity> grantedProtocolMapperEntities = consentEntity.getGrantedProtocolMappers();
+ Collection<FederatedUserConsentProtocolMapperEntity> mappersToRemove = new HashSet<>(grantedProtocolMapperEntities);
+
+ for (ProtocolMapperModel protocolMapper : consentModel.getGrantedProtocolMappers()) {
+ FederatedUserConsentProtocolMapperEntity grantedProtocolMapperEntity = new FederatedUserConsentProtocolMapperEntity();
+ grantedProtocolMapperEntity.setUserConsent(consentEntity);
+ grantedProtocolMapperEntity.setProtocolMapperId(protocolMapper.getId());
+
+ // Check if it's already there
+ if (!grantedProtocolMapperEntities.contains(grantedProtocolMapperEntity)) {
+ em.persist(grantedProtocolMapperEntity);
+ em.flush();
+ grantedProtocolMapperEntities.add(grantedProtocolMapperEntity);
+ } else {
+ mappersToRemove.remove(grantedProtocolMapperEntity);
+ }
+ }
+ // Those mappers were no longer on consentModel and will be removed
+ for (FederatedUserConsentProtocolMapperEntity toRemove : mappersToRemove) {
+ grantedProtocolMapperEntities.remove(toRemove);
+ em.remove(toRemove);
+ }
+
+ Collection<FederatedUserConsentRoleEntity> grantedRoleEntities = consentEntity.getGrantedRoles();
+ Set<FederatedUserConsentRoleEntity> rolesToRemove = new HashSet<>(grantedRoleEntities);
+ for (RoleModel role : consentModel.getGrantedRoles()) {
+ FederatedUserConsentRoleEntity consentRoleEntity = new FederatedUserConsentRoleEntity();
+ consentRoleEntity.setUserConsent(consentEntity);
+ consentRoleEntity.setRoleId(role.getId());
+
+ // Check if it's already there
+ if (!grantedRoleEntities.contains(consentRoleEntity)) {
+ em.persist(consentRoleEntity);
+ em.flush();
+ grantedRoleEntities.add(consentRoleEntity);
+ } else {
+ rolesToRemove.remove(consentRoleEntity);
+ }
+ }
+ // Those roles were no longer on consentModel and will be removed
+ for (FederatedUserConsentRoleEntity toRemove : rolesToRemove) {
+ grantedRoleEntities.remove(toRemove);
+ em.remove(toRemove);
+ }
+
+ em.flush();
+ }
+
+
+
+ @Override
+ public List<UserCredentialValueModel> getCredentials(RealmModel realm, UserModel user) {
+ TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByUser", FederatedUserCredentialEntity.class)
+ .setParameter("userId", user.getId());
+ List<FederatedUserCredentialEntity> results = query.getResultList();
+ List<UserCredentialValueModel> list = new LinkedList<>();
+ for (FederatedUserCredentialEntity credEntity : results) {
+ UserCredentialValueModel credModel = new UserCredentialValueModel();
+ credModel.setId(credEntity.getId());
+ credModel.setType(credEntity.getType());
+ credModel.setDevice(credEntity.getDevice());
+ credModel.setValue(credEntity.getValue());
+ credModel.setCreatedDate(credEntity.getCreatedDate());
+ credModel.setSalt(credEntity.getSalt());
+ credModel.setHashIterations(credEntity.getHashIterations());
+ credModel.setCounter(credEntity.getCounter());
+ credModel.setAlgorithm(credEntity.getAlgorithm());
+ credModel.setDigits(credEntity.getDigits());
+ credModel.setPeriod(credEntity.getPeriod());
+
+ list.add(credModel);
+ }
+ return list;
+ }
+
+ @Override
+ public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel cred) {
+ FederatedCredentials.updateCredential(session, this, realm, user, cred);
+
+ }
+
+ @Override
+ public void updateCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred) {
+ FederatedUserCredentialEntity entity = null;
+ if (cred.getId() != null) entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
+ boolean newEntity = false;
+ if (entity == null) {
+ entity = new FederatedUserCredentialEntity();
+ entity.setId(KeycloakModelUtils.generateId());
+ newEntity = true;
+ }
+ entity.setUserId(user.getId());
+ entity.setRealmId(realm.getId());
+ entity.setStorageProviderId(StorageId.resolveProviderId(user));
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ Long createdDate = cred.getCreatedDate();
+ if (createdDate == null) createdDate = System.currentTimeMillis();
+ entity.setCreatedDate(createdDate);
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ if (newEntity) {
+ em.persist(entity);
+ }
+
+ }
+
+ @Override
+ public void removeCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred) {
+ FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
+ em.remove(entity);
+ }
+
+ @Override
+ public Set<GroupModel> getGroups(RealmModel realm, UserModel user) {
+ Set<GroupModel> set = new HashSet<>();
+ TypedQuery<FederatedUserGroupMembershipEntity> query = em.createNamedQuery("feduserGroupMembership", FederatedUserGroupMembershipEntity.class);
+ query.setParameter("userId", user.getId());
+ List<FederatedUserGroupMembershipEntity> results = query.getResultList();
+ if (results.size() == 0) return set;
+ for (FederatedUserGroupMembershipEntity entity : results) {
+ GroupModel group = realm.getGroupById(entity.getGroupId());
+ set.add(group);
+ }
+ return set;
+ }
+
+ @Override
+ public void joinGroup(RealmModel realm, UserModel user, GroupModel group) {
+ if (isMemberOf(realm, user, group)) return;
+ FederatedUserGroupMembershipEntity entity = new FederatedUserGroupMembershipEntity();
+ entity.setUserId(user.getId());
+ entity.setStorageProviderId(StorageId.resolveProviderId(user));
+ entity.setGroupId(group.getId());
+ entity.setRealmId(realm.getId());
+ em.persist(entity);
+
+ }
+
+ public boolean isMemberOf(RealmModel realm, UserModel user, GroupModel group) {
+ Set<GroupModel> roles = user.getGroups();
+ return KeycloakModelUtils.isMember(roles, group);
+ }
+
+
+ @Override
+ public void leaveGroup(RealmModel realm, UserModel user, GroupModel group) {
+ if (user == null || group == null) return;
+
+ TypedQuery<FederatedUserGroupMembershipEntity> query1 = em.createNamedQuery("feduserMemberOf", FederatedUserGroupMembershipEntity.class);
+ query1.setParameter("userId", user.getId());
+ query1.setParameter("groupId", group.getId());
+ TypedQuery<FederatedUserGroupMembershipEntity> query = query1;
+ List<FederatedUserGroupMembershipEntity> results = query.getResultList();
+ if (results.size() == 0) return;
+ for (FederatedUserGroupMembershipEntity entity : results) {
+ em.remove(entity);
+ }
+ em.flush();
+
+ }
+
+
+ @Override
+ public Set<String> getRequiredActions(RealmModel realm, UserModel user) {
+ Set<String> set = new HashSet<>();
+ List<FederatedUserRequiredActionEntity> values = getRequiredActionEntities(realm, user);
+ for (FederatedUserRequiredActionEntity entity : values) {
+ set.add(entity.getAction());
+ }
+
+ return set;
+
+ }
+
+ private List<FederatedUserRequiredActionEntity> getRequiredActionEntities(RealmModel realm, UserModel user) {
+ TypedQuery<FederatedUserRequiredActionEntity> query = em.createNamedQuery("getFederatedUserRequiredActionsByUser", FederatedUserRequiredActionEntity.class)
+ .setParameter("userId", user.getId())
+ .setParameter("realmId", realm.getId());
+ return query.getResultList();
+ }
+
+ @Override
+ public void addRequiredAction(RealmModel realm, UserModel user, String action) {
+ if (user.getRequiredActions().contains(action)) return;
+ FederatedUserRequiredActionEntity entity = new FederatedUserRequiredActionEntity();
+ entity.setUserId(user.getId());
+ entity.setRealmId(realm.getId());
+ entity.setStorageProviderId(StorageId.resolveProviderId(user));
+ entity.setAction(action);
+ em.persist(entity);
+
+ }
+
+ @Override
+ public void removeRequiredAction(RealmModel realm, UserModel user, String action) {
+ List<FederatedUserRequiredActionEntity> values = getRequiredActionEntities(realm, user);
+ for (FederatedUserRequiredActionEntity entity : values) {
+ if (action.equals(entity.getAction())) em.remove(entity);
+ }
+ em.flush();
+
+ }
+
+ @Override
+ public void grantRole(RealmModel realm, UserModel user, RoleModel role) {
+ if (user.hasRole(role)) return;
+ FederatedUserRoleMappingEntity entity = new FederatedUserRoleMappingEntity();
+ entity.setUserId(user.getId());
+ entity.setStorageProviderId(StorageId.resolveProviderId(user));
+ entity.setRealmId(realm.getId());
+ entity.setRoleId(role.getId());
+ em.persist(entity);
+
+ }
+
+ @Override
+ public Set<RoleModel> getRoleMappings(RealmModel realm, UserModel user) {
+ Set<RoleModel> set = new HashSet<>();
+ TypedQuery<FederatedUserRoleMappingEntity> query = em.createNamedQuery("feduserRoleMappings", FederatedUserRoleMappingEntity.class);
+ query.setParameter("userId", user.getId());
+ List<FederatedUserRoleMappingEntity> results = query.getResultList();
+ if (results.size() == 0) return set;
+ for (FederatedUserRoleMappingEntity entity : results) {
+ RoleModel role = realm.getRoleById(entity.getRoleId());
+ set.add(role);
+ }
+ return set;
+ }
+
+ @Override
+ public void deleteRoleMapping(RealmModel realm, UserModel user, RoleModel role) {
+ TypedQuery<FederatedUserRoleMappingEntity> query = em.createNamedQuery("feduserRoleMappings", FederatedUserRoleMappingEntity.class);
+ query.setParameter("userId", user.getId());
+ List<FederatedUserRoleMappingEntity> results = query.getResultList();
+ for (FederatedUserRoleMappingEntity entity : results) {
+ if (entity.getRoleId().equals(role.getId())) em.remove(entity);
+
+ }
+ em.flush();
+ }
+
+ @Override
+ public void preRemove(RealmModel realm) {
+ int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm")
+ .setParameter("realmId", realm.getId()).executeUpdate();
+ num = em.createNamedQuery("deleteFederatedUserConsentProtMappersByRealm")
+ .setParameter("realmId", realm.getId()).executeUpdate();
+ num = em.createNamedQuery("deleteFederatedUserConsentsByRealm")
+ .setParameter("realmId", realm.getId()).executeUpdate();
+ num = em.createNamedQuery("deleteFederatedUserRoleMappingsByRealm")
+ .setParameter("realmId", realm.getId()).executeUpdate();
+ num = em.createNamedQuery("deleteFederatedUserRequiredActionsByRealm")
+ .setParameter("realmId", realm.getId()).executeUpdate();
+ num = em.createNamedQuery("deleteBrokerLinkByRealm")
+ .setParameter("realmId", realm.getId()).executeUpdate();
+ num = em.createNamedQuery("deleteFederatedUserCredentialsByRealm")
+ .setParameter("realmId", realm.getId()).executeUpdate();
+ num = em.createNamedQuery("deleteUserFederatedAttributesByRealm")
+ .setParameter("realmId", realm.getId()).executeUpdate();
+ num = em.createNamedQuery("deleteFederatedUserGroupMembershipByRealm")
+ .setParameter("realmId", realm.getId()).executeUpdate();
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, UserFederationProviderModel link) {
+ int num = em.createNamedQuery("deleteFederatedUserRoleMappingsByRealmAndLink")
+ .setParameter("realmId", realm.getId())
+ .setParameter("link", link.getId())
+ .executeUpdate();
+ num = em.createNamedQuery("deleteFederatedUserRequiredActionsByRealmAndLink")
+ .setParameter("realmId", realm.getId())
+ .setParameter("link", link.getId())
+ .executeUpdate();
+ num = em.createNamedQuery("deleteBrokerLinkByRealmAndLink")
+ .setParameter("realmId", realm.getId())
+ .setParameter("link", link.getId())
+ .executeUpdate();
+ num = em.createNamedQuery("deleteFederatedUserCredentialsByRealmAndLink")
+ .setParameter("realmId", realm.getId())
+ .setParameter("link", link.getId())
+ .executeUpdate();
+ num = em.createNamedQuery("deleteUserFederatedAttributesByRealmAndLink")
+ .setParameter("realmId", realm.getId())
+ .setParameter("link", link.getId())
+ .executeUpdate();
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, RoleModel role) {
+ em.createNamedQuery("deleteFederatedUserRoleMappingsByRole").setParameter("roleId", role.getId()).executeUpdate();
+ em.createNamedQuery("deleteFederatedUserRoleMappingsByRole").setParameter("roleId", role.getId()).executeUpdate();
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, GroupModel group) {
+ em.createNamedQuery("deleteFederatedUserGroupMembershipsByGroup").setParameter("groupId", group.getId()).executeUpdate();
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, ClientModel client) {
+ em.createNamedQuery("deleteFederatedUserConsentProtMappersByClient").setParameter("clientId", client.getId()).executeUpdate();
+ em.createNamedQuery("deleteFederatedUserConsentRolesByClient").setParameter("clientId", client.getId()).executeUpdate();
+ em.createNamedQuery("deleteFederatedUserConsentsByClient").setParameter("clientId", client.getId()).executeUpdate();
+ }
+
+ @Override
+ public void preRemove(ProtocolMapperModel protocolMapper) {
+ em.createNamedQuery("deleteFederatedUserConsentProtMappersByProtocolMapper")
+ .setParameter("protocolMapperId", protocolMapper.getId())
+ .executeUpdate();
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, UserModel user) {
+ em.createNamedQuery("deleteBrokerLinkByUser")
+ .setParameter("userId", user.getId())
+ .setParameter("realmId", realm.getId())
+ .executeUpdate();
+ em.createNamedQuery("deleteUserFederatedAttributesByUser")
+ .setParameter("userId", user.getId())
+ .setParameter("realmId", realm.getId())
+ .executeUpdate();
+ em.createNamedQuery("deleteFederatedUserConsentProtMappersByUser")
+ .setParameter("userId", user.getId())
+ .setParameter("realmId", realm.getId())
+ .executeUpdate();
+ em.createNamedQuery("deleteFederatedUserConsentRolesByUser")
+ .setParameter("userId", user.getId())
+ .setParameter("realmId", realm.getId())
+ .executeUpdate();
+ em.createNamedQuery("deleteFederatedUserConsentsByUser")
+ .setParameter("userId", user.getId())
+ .setParameter("realmId", realm.getId())
+ .executeUpdate();
+ em.createNamedQuery("deleteFederatedUserCredentialByUser")
+ .setParameter("userId", user.getId())
+ .setParameter("realmId", realm.getId())
+ .executeUpdate();
+ em.createNamedQuery("deleteFederatedUserGroupMembershipsByUser")
+ .setParameter("userId", user.getId())
+ .setParameter("realmId", realm.getId())
+ .executeUpdate();
+ em.createNamedQuery("getFederatedUserRequiredActionsByUser")
+ .setParameter("userId", user.getId())
+ .setParameter("realmId", realm.getId())
+ .executeUpdate();
+ em.createNamedQuery("deleteFederatedUserRoleMappingsByUser")
+ .setParameter("userId", user.getId())
+ .setParameter("realmId", realm.getId())
+ .executeUpdate();
+
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, StorageProviderModel model) {
+ em.createNamedQuery("deleteBrokerLinkByStorageProvider")
+ .setParameter("storageProviderId", model.getId())
+ .executeUpdate();
+ em.createNamedQuery("deleteFederatedAttributesByStorageProvider")
+ .setParameter("storageProviderId", model.getId())
+ .executeUpdate();
+ em.createNamedQuery("deleteFederatedUserConsentProtMappersByStorageProvider")
+ .setParameter("storageProviderId", model.getId())
+ .executeUpdate();
+ em.createNamedQuery("deleteFederatedUserRoleMappingsByStorageProvider")
+ .setParameter("storageProviderId", model.getId())
+ .executeUpdate();
+ em.createNamedQuery("deleteFederatedUserConsentsByStorageProvider")
+ .setParameter("storageProviderId", model.getId())
+ .executeUpdate();
+ em.createNamedQuery("deleteFederatedUserCredentialsByStorageProvider")
+ .setParameter("storageProviderId", model.getId())
+ .executeUpdate();
+ em.createNamedQuery("deleteFederatedUserGroupMembershipByStorageProvider")
+ .setParameter("storageProviderId", model.getId())
+ .executeUpdate();
+ em.createNamedQuery("deleteFederatedUserRequiredActionsByStorageProvider")
+ .setParameter("storageProviderId", model.getId())
+ .executeUpdate();
+ em.createNamedQuery("deleteFederatedUserRoleMappingsByStorageProvider")
+ .setParameter("storageProviderId", model.getId())
+ .executeUpdate();
+
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProviderFactory.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProviderFactory.java
new file mode 100644
index 0000000..0a3218c
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProviderFactory.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.jpa;
+
+import org.keycloak.Config;
+import org.keycloak.connections.jpa.JpaConnectionProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.storage.federated.UserFederatedStorageProvider;
+import org.keycloak.storage.federated.UserFederatedStorageProviderFactory;
+
+import javax.persistence.EntityManager;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaUserFederatedStorageProviderFactory implements UserFederatedStorageProviderFactory {
+ @Override
+ public UserFederatedStorageProvider create(KeycloakSession session) {
+ EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
+ return new JpaUserFederatedStorageProvider(session, em);
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String getId() {
+ return "jpa";
+ }
+}
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml
new file mode 100755
index 0000000..79c3e9e
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+ ~ and other contributors as indicated by the @author tags.
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
+ <changeSet author="bburke@redhat.com" id="2.1.0">
+
+ <createTable tableName="BROKER_LINK">
+ <column name="IDENTITY_PROVIDER" type="VARCHAR(255)">
+ <constraints nullable="false" />
+ </column>
+ <column name="STORAGE_PROVIDER_ID" type="VARCHAR(255)">
+ </column>
+ <column name="REALM_ID" type="VARCHAR(36)">
+ <constraints nullable="false" />
+ </column>
+ <column name="BROKER_USER_ID" type="VARCHAR(255)" />
+ <column name="BROKER_USERNAME" type="VARCHAR(255)" />
+ <column name="TOKEN" type="TEXT" />
+ <column name="USER_ID" type="VARCHAR(255)">
+ <constraints nullable="false" />
+ </column>
+ </createTable>
+ <createTable tableName="FED_USER_ATTRIBUTE">
+ <column name="ID" type="VARCHAR(36)">
+ <constraints nullable="false" />
+ </column>
+ <column name="NAME" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="USER_ID" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="REALM_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="STORAGE_PROVIDER_ID" type="VARCHAR(36)"/>
+ <column name="VALUE" type="VARCHAR(2024)"/>
+ </createTable>
+ <createTable tableName="FED_USER_CONSENT">
+ <column name="ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="CLIENT_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="USER_ID" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="REALM_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="STORAGE_PROVIDER_ID" type="VARCHAR(36)"/>
+ </createTable>
+ <createTable tableName="FED_USER_CONSENT_ROLE">
+ <column name="USER_CONSENT_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="ROLE_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ </createTable>
+ <createTable tableName="FED_USER_CONSENT_PROT_MAPPER">
+ <column name="USER_CONSENT_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="PROTOCOL_MAPPER_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ </createTable>
+ <createTable tableName="FED_USER_CREDENTIAL">
+ <column name="ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="DEVICE" type="VARCHAR(255)"/>
+ <column name="HASH_ITERATIONS" type="INT"/>
+ <column name="SALT" type="BLOB(16)"/>
+ <column name="TYPE" type="VARCHAR(255)"/>
+ <column name="VALUE" type="VARCHAR(255)"/>
+ <column name="CREATED_DATE" type="BIGINT"/>
+ <column name="COUNTER" type="INT" defaultValueNumeric="0">
+ <constraints nullable="true"/>
+ </column>
+ <column name="DIGITS" type="INT" defaultValueNumeric="6">
+ <constraints nullable="true"/>
+ </column>
+ <column name="PERIOD" type="INT" defaultValueNumeric="30">
+ <constraints nullable="true"/>
+ </column>
+ <column name="ALGORITHM" type="VARCHAR(36)" defaultValue="HmacSHA1">
+ <constraints nullable="true"/>
+ </column>
+ <column name="USER_ID" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="REALM_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="STORAGE_PROVIDER_ID" type="VARCHAR(36)"/>
+ </createTable>
+ <createTable tableName="FED_USER_GROUP_MEMBERSHIP">
+ <column name="GROUP_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="USER_ID" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="REALM_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="STORAGE_PROVIDER_ID" type="VARCHAR(36)"/>
+ </createTable>
+ <createTable tableName="FED_USER_REQUIRED_ACTION">
+ <column name="REQUIRED_ACTION" type="VARCHAR(255)" defaultValue=" ">
+ <constraints nullable="false"/>
+ </column>
+ <column name="USER_ID" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="REALM_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="STORAGE_PROVIDER_ID" type="VARCHAR(36)"/>
+ </createTable>
+ <createTable tableName="FED_USER_ROLE_MAPPING">
+ <column name="ROLE_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="USER_ID" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="REALM_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="STORAGE_PROVIDER_ID" type="VARCHAR(36)"/>
+ </createTable>
+
+ <createTable tableName="STORAGE_PROVIDER_CONFIG">
+ <column name="STORAGE_PROVIDER_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="VALUE" type="VARCHAR(255)"/>
+ <column name="NAME" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ </createTable>
+ <createTable tableName="STORAGE_PROVIDER">
+ <column name="ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="DISPLAY_NAME" type="VARCHAR(255)"/>
+ <column name="PRIORITY" type="INT"/>
+ <column name="PROVIDER_NAME" type="VARCHAR(255)"/>
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ </createTable>
+
+
+
+
+ <addPrimaryKey columnNames="IDENTITY_PROVIDER, USER_ID" constraintName="CONSTR_BROKER_LINK_PK" tableName="BROKER_LINK" />
+ <addPrimaryKey columnNames="ID" constraintName="CONSTR_FED_USER_ATTR_PK" tableName="FED_USER_ATTRIBUTE"/>
+ <addPrimaryKey columnNames="ID" constraintName="CONSTR_FED_USER_CONSENT_PK" tableName="FED_USER_CONSENT"/>
+ <addPrimaryKey columnNames="USER_CONSENT_ID, ROLE_ID" constraintName="CONSTR_USER_CONSENT_ROLE_PK" tableName="FED_USER_CONSENT_ROLE"/>
+ <addPrimaryKey columnNames="USER_CONSENT_ID, PROTOCOL_MAPPER_ID" constraintName="CONSTR_USER_CONSENT_PROT_MAP_PK" tableName="FED_USER_CONSENT_PROT_MAPPER"/>
+ <!--
+ <addForeignKeyConstraint baseColumnNames="USER_CONSENT_ID" baseTableName="FED_USER_CONSENT_ROLE" constraintName="FK_FED_GRNTCSNT_ROLE_GR" referencedColumnNames="ID" referencedTableName="FED_USER_CONSENT"/>
+ <addForeignKeyConstraint baseColumnNames="USER_CONSENT_ID" baseTableName="FED_USER_CONSENT_PROT_MAPPER" constraintName="FK_FED_GRNTCSNT_PRM_GR" referencedColumnNames="ID" referencedTableName="FED_USER_CONSENT"/>
+ -->
+ <addPrimaryKey columnNames="ID" constraintName="CONSTR_FED_USER_CRED_PK" tableName="FED_USER_CREDENTIAL"/>
+ <addPrimaryKey columnNames="GROUP_ID, USER_ID" constraintName="CONSTR_FED_USER_GROUP" tableName="FED_USER_GROUP_MEMBERSHIP"/>
+ <addPrimaryKey columnNames="ROLE_ID, USER_ID" constraintName="CONSTR_FED_USER_ROLE" tableName="FED_USER_ROLE_MAPPING"/>
+ <addPrimaryKey columnNames="REQUIRED_ACTION, USER_ID" constraintName="CONSTR_FED_REQUIRED_ACTION" tableName="FED_USER_REQUIRED_ACTION"/>
+
+ <addPrimaryKey columnNames="ID" constraintName="CONSTR_STORAGE_PROVIDER_PK" tableName="STORAGE_PROVIDER"/>
+ <addPrimaryKey columnNames="STORAGE_PROVIDER_ID, NAME" constraintName="CONSTR_STORAGE_CONFIG" tableName="STORAGE_PROVIDER_CONFIG"/>
+ <!--
+ <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="STORAGE_PROVIDER" constraintName="FK_STORAGE_PROVIDER_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
+ -->
+ </changeSet>
+</databaseChangeLog>
\ No newline at end of file
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
index c8abe49..4dbc50b 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -32,6 +32,7 @@
<include file="META-INF/jpa-changelog-1.9.0.xml"/>
<include file="META-INF/jpa-changelog-1.9.1.xml"/>
<include file="META-INF/jpa-changelog-1.9.2.xml"/>
+ <include file="META-INF/jpa-changelog-2.1.0.xml"/>
<include file="META-INF/jpa-changelog-authz-master.xml"/>
</databaseChangeLog>
diff --git a/model/jpa/src/main/resources/META-INF/persistence.xml b/model/jpa/src/main/resources/META-INF/persistence.xml
index b90c0fc..d912033 100755
--- a/model/jpa/src/main/resources/META-INF/persistence.xml
+++ b/model/jpa/src/main/resources/META-INF/persistence.xml
@@ -25,6 +25,7 @@
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
<class>org.keycloak.models.jpa.entities.RealmAttributeEntity</class>
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
+ <class>org.keycloak.models.jpa.entities.StorageProviderEntity</class>
<class>org.keycloak.models.jpa.entities.UserFederationProviderEntity</class>
<class>org.keycloak.models.jpa.entities.UserFederationMapperEntity</class>
<class>org.keycloak.models.jpa.entities.RoleEntity</class>
@@ -64,6 +65,17 @@
<class>org.keycloak.authorization.jpa.entities.ResourceEntity</class>
<class>org.keycloak.authorization.jpa.entities.ScopeEntity</class>
<class>org.keycloak.authorization.jpa.entities.PolicyEntity</class>
+
+ <!-- User Federation Storage -->
+ <class>org.keycloak.storage.jpa.entity.BrokerLinkEntity</class>
+ <class>org.keycloak.storage.jpa.entity.FederatedUserAttributeEntity</class>
+ <class>org.keycloak.storage.jpa.entity.FederatedUserConsentEntity</class>
+ <class>org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity</class>
+ <class>org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity</class>
+ <class>org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity</class>
+ <class>org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity</class>
+ <class>org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity</class>
+ <class>org.keycloak.storage.jpa.entity.FederatedUserRoleMappingEntity</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
diff --git a/model/jpa/src/main/resources/META-INF/services/org.keycloak.storage.federated.UserFederatedStorageProviderFactory b/model/jpa/src/main/resources/META-INF/services/org.keycloak.storage.federated.UserFederatedStorageProviderFactory
new file mode 100644
index 0000000..c67277c
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/services/org.keycloak.storage.federated.UserFederatedStorageProviderFactory
@@ -0,0 +1 @@
+org.keycloak.storage.jpa.JpaUserFederatedStorageProviderFactory
\ No newline at end of file
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 1f26239..ad811d4 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
@@ -28,22 +28,28 @@ import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
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.storage.StorageProviderModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -140,7 +146,7 @@ public class MongoUserProvider implements UserProvider {
}
@Override
- public UserModel getUserByServiceAccountClient(ClientModel client) {
+ public UserModel getServiceAccount(ClientModel client) {
DBObject query = new QueryBuilder()
.and("serviceAccountClientLink").is(client.getId())
.and("realmId").is(client.getRealm().getId())
@@ -157,6 +163,17 @@ public class MongoUserProvider implements UserProvider {
return userModels;
}
+ @Override
+ public List<UserModel> getUsers(RealmModel realm) {
+ return getUsers(realm, false);
+ }
+
+ @Override
+ public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
+ return getUsers(realm, firstResult, maxResults, false);
+ }
+
+
@Override
public List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts) {
@@ -192,7 +209,8 @@ public class MongoUserProvider implements UserProvider {
}
@Override
- public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
+ public List<UserModel>
+ searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
search = search.trim();
Pattern caseInsensitivePattern = Pattern.compile("(?i:" + search + ")");
@@ -509,4 +527,112 @@ public class MongoUserProvider implements UserProvider {
// Not supported yet
return null;
}
+
+ @Override
+ public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+ String clientId = consent.getClient().getId();
+ if (getConsentEntityByClientId(user, clientId) != null) {
+ throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]");
+ }
+
+ MongoUserConsentEntity consentEntity = new MongoUserConsentEntity();
+ consentEntity.setUserId(user.getId());
+ consentEntity.setClientId(clientId);
+ fillEntityFromModel(consent, consentEntity);
+ getMongoStore().insertEntity(consentEntity, invocationContext);
+ }
+
+ @Override
+ public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientId) {
+ UserConsentEntity consentEntity = getConsentEntityByClientId(user, clientId);
+ return consentEntity!=null ? toConsentModel(realm, consentEntity) : null;
+ }
+
+ @Override
+ public List<UserConsentModel> getConsents(RealmModel realm, UserModel user) {
+ List<UserConsentModel> result = new ArrayList<UserConsentModel>();
+
+ DBObject query = new QueryBuilder()
+ .and("userId").is(user.getId())
+ .get();
+ List<MongoUserConsentEntity> grantedConsents = getMongoStore().loadEntities(MongoUserConsentEntity.class, query, invocationContext);
+
+ for (UserConsentEntity consentEntity : grantedConsents) {
+ UserConsentModel model = toConsentModel(realm, consentEntity);
+ result.add(model);
+ }
+
+ return result;
+ }
+
+ private MongoUserConsentEntity getConsentEntityByClientId(UserModel user, String clientId) {
+ DBObject query = new QueryBuilder()
+ .and("userId").is(user.getId())
+ .and("clientId").is(clientId)
+ .get();
+ return getMongoStore().loadSingleEntity(MongoUserConsentEntity.class, query, invocationContext);
+ }
+
+ private UserConsentModel toConsentModel(RealmModel realm, UserConsentEntity entity) {
+ ClientModel client = realm.getClientById(entity.getClientId());
+ if (client == null) {
+ throw new ModelException("Client with id " + entity.getClientId() + " is not available");
+ }
+ UserConsentModel model = new UserConsentModel(client);
+
+ for (String roleId : entity.getGrantedRoles()) {
+ RoleModel roleModel = realm.getRoleById(roleId);
+ if (roleModel != null) {
+ model.addGrantedRole(roleModel);
+ }
+ }
+
+ for (String protMapperId : entity.getGrantedProtocolMappers()) {
+ ProtocolMapperModel protocolMapper = client.getProtocolMapperById(protMapperId);
+ model.addGrantedProtocolMapper(protocolMapper);
+ }
+ return model;
+ }
+
+ // Fill roles and protocolMappers to entity
+ private void fillEntityFromModel(UserConsentModel consent, MongoUserConsentEntity consentEntity) {
+ List<String> roleIds = new LinkedList<String>();
+ for (RoleModel role : consent.getGrantedRoles()) {
+ roleIds.add(role.getId());
+ }
+ consentEntity.setGrantedRoles(roleIds);
+
+ List<String> protMapperIds = new LinkedList<String>();
+ for (ProtocolMapperModel protMapperModel : consent.getGrantedProtocolMappers()) {
+ protMapperIds.add(protMapperModel.getId());
+ }
+ consentEntity.setGrantedProtocolMappers(protMapperIds);
+ }
+
+ @Override
+ public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+ String clientId = consent.getClient().getId();
+ MongoUserConsentEntity consentEntity = getConsentEntityByClientId(user, clientId);
+ if (consentEntity == null) {
+ throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]");
+ } else {
+ fillEntityFromModel(consent, consentEntity);
+ getMongoStore().updateEntity(consentEntity, invocationContext);
+ }
+ }
+
+ @Override
+ public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientId) {
+ MongoUserConsentEntity entity = getConsentEntityByClientId(user, clientId);
+ if (entity == null) {
+ return false;
+ }
+
+ return getMongoStore().removeEntity(entity, invocationContext);
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, StorageProviderModel link) {
+
+ }
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 2dcda48..c532cbd 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -51,6 +51,7 @@ import org.keycloak.models.entities.IdentityProviderEntity;
import org.keycloak.models.entities.IdentityProviderMapperEntity;
import org.keycloak.models.entities.RequiredActionProviderEntity;
import org.keycloak.models.entities.RequiredCredentialEntity;
+import org.keycloak.models.entities.StorageProviderEntity;
import org.keycloak.models.entities.UserFederationMapperEntity;
import org.keycloak.models.entities.UserFederationProviderEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity;
@@ -59,6 +60,7 @@ import org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.StorageProviderModel;
import java.security.Key;
import java.security.PrivateKey;
@@ -991,6 +993,14 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
updateRealm();
}
+
+ private void removeFederationMappersForProvider(String federationProviderId) {
+ Set<UserFederationMapperEntity> mappers = getUserFederationMapperEntitiesByFederationProvider(federationProviderId);
+ for (UserFederationMapperEntity mapper : mappers) {
+ getMongoEntity().getUserFederationMappers().remove(mapper);
+ }
+ }
+
@Override
public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) {
KeycloakModelUtils.ensureUniqueDisplayName(displayName, null, getUserFederationProviders());
@@ -1032,14 +1042,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
updateRealm();
}
-
- private void removeFederationMappersForProvider(String federationProviderId) {
- Set<UserFederationMapperEntity> mappers = getUserFederationMapperEntitiesByFederationProvider(federationProviderId);
- for (UserFederationMapperEntity mapper : mappers) {
- getMongoEntity().getUserFederationMappers().remove(mapper);
- }
- }
-
@Override
public void updateUserFederationProvider(UserFederationProviderModel model) {
KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getUserFederationProviders());
@@ -1169,6 +1171,173 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
@Override
+ public StorageProviderModel addStorageProvider(StorageProviderModel model) {
+ KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), null, getStorageProviders());
+
+ StorageProviderEntity entity = new StorageProviderEntity();
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setPriority(model.getPriority());
+ entity.setProviderName(model.getProviderName());
+ entity.setConfig(model.getConfig());
+ String displayName = model.getDisplayName();
+ if (displayName == null) {
+ displayName = entity.getId();
+ }
+ entity.setDisplayName(displayName);
+ realm.getStorageProviders().add(entity);
+ updateRealm();
+
+ StorageProviderModel providerModel = new StorageProviderModel(entity.getId(), model.getProviderName(),
+ model.getConfig(), model.getPriority(), displayName);
+
+
+ return providerModel;
+ }
+
+ @Override
+ public void updateStorageProvider(StorageProviderModel provider) {
+ KeycloakModelUtils.ensureUniqueDisplayName(provider.getDisplayName(), provider, getStorageProviders());
+
+ Iterator<StorageProviderEntity> it = realm.getStorageProviders().iterator();
+ while (it.hasNext()) {
+ StorageProviderEntity entity = it.next();
+ if (entity.getId().equals(provider.getId())) {
+ entity.setProviderName(provider.getProviderName());
+ entity.setConfig(provider.getConfig());
+ entity.setPriority(provider.getPriority());
+ String displayName = provider.getDisplayName();
+ if (displayName != null) {
+ entity.setDisplayName(provider.getDisplayName());
+ }
+ }
+ }
+ updateRealm();
+
+ }
+
+ @Override
+ public void removeStorageProvider(StorageProviderModel provider) {
+ Iterator<StorageProviderEntity> it = realm.getStorageProviders().iterator();
+ while (it.hasNext()) {
+ StorageProviderEntity entity = it.next();
+ if (entity.getId().equals(provider.getId())) {
+ session.users().preRemove(this, new StorageProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName()
+ ));
+
+ it.remove();
+ }
+ }
+ updateRealm();
+
+ }
+
+ @Override
+ public void setStorageProviders(List<StorageProviderModel> providers) {
+ for (StorageProviderModel currentProvider : providers) {
+ KeycloakModelUtils.ensureUniqueDisplayName(currentProvider.getDisplayName(), currentProvider, providers);
+ }
+
+ List<StorageProviderEntity> existingProviders = realm.getStorageProviders();
+ List<StorageProviderEntity> toRemove = new LinkedList<>();
+ for (StorageProviderEntity entity : existingProviders) {
+ boolean found = false;
+ for (StorageProviderModel model : providers) {
+ if (entity.getId().equals(model.getId())) {
+ entity.setConfig(model.getConfig());
+ entity.setPriority(model.getPriority());
+ entity.setProviderName(model.getProviderName());
+ String displayName = model.getDisplayName();
+ if (displayName != null) {
+ entity.setDisplayName(displayName);
+ }
+ found = true;
+ break;
+ }
+
+ }
+ if (found) continue;
+ session.users().preRemove(this, new StorageProviderModel(entity.getId(), entity.getProviderName(),
+ entity.getConfig(), entity.getPriority(), entity.getDisplayName()));
+ toRemove.add(entity);
+ }
+
+ for (StorageProviderEntity entity : toRemove) {
+ realm.getStorageProviders().remove(entity);
+ }
+
+ List<StorageProviderModel> add = new LinkedList<>();
+ for (StorageProviderModel model : providers) {
+ boolean found = false;
+ for (StorageProviderEntity entity : realm.getStorageProviders()) {
+ if (entity.getId().equals(model.getId())) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) add.add(model);
+ }
+
+ for (StorageProviderModel model : add) {
+ StorageProviderEntity entity = new StorageProviderEntity();
+ if (model.getId() != null) {
+ entity.setId(model.getId());
+ } else {
+ String id = KeycloakModelUtils.generateId();
+ entity.setId(id);
+ model.setId(id);
+ }
+ entity.setProviderName(model.getProviderName());
+ entity.setConfig(model.getConfig());
+ entity.setPriority(model.getPriority());
+ String displayName = model.getDisplayName();
+ if (displayName == null) {
+ displayName = entity.getId();
+ }
+ entity.setDisplayName(displayName);
+ realm.getStorageProviders().add(entity);
+
+ }
+
+ updateRealm();
+
+ }
+
+ @Override
+ public List<StorageProviderModel> getStorageProviders() {
+ List<StorageProviderEntity> entities = realm.getStorageProviders();
+ if (entities.isEmpty()) return Collections.EMPTY_LIST;
+ List<StorageProviderEntity> copy = new LinkedList<>();
+ for (StorageProviderEntity entity : entities) {
+ copy.add(entity);
+
+ }
+ Collections.sort(copy, new Comparator<StorageProviderEntity>() {
+
+ @Override
+ public int compare(StorageProviderEntity o1, StorageProviderEntity o2) {
+ return o1.getPriority() - o2.getPriority();
+ }
+
+ });
+ List<StorageProviderModel> result = new LinkedList<>();
+ for (StorageProviderEntity entity : copy) {
+ result.add(new StorageProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName()
+ ));
+ }
+
+ return Collections.unmodifiableList(result);
+ }
+
+ @Override
+ public StorageProviderModel getStorageProvider(String id) {
+ for (StorageProviderEntity entity : realm.getStorageProviders()) {
+ if (entity.getId().equals(id)) return new StorageProviderModel(entity.getId(), entity.getProviderName(),
+ entity.getConfig(), entity.getPriority(), entity.getDisplayName());
+ }
+ return null;
+ }
+
+ @Override
public boolean isEventsEnabled() {
return realm.isEventsEnabled();
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index 601bc4a..b89ab01 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -566,108 +566,6 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
updateUser();
}
- @Override
- public void addConsent(UserConsentModel consent) {
- String clientId = consent.getClient().getId();
- if (getConsentEntityByClientId(clientId) != null) {
- throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]");
- }
-
- MongoUserConsentEntity consentEntity = new MongoUserConsentEntity();
- consentEntity.setUserId(getId());
- consentEntity.setClientId(clientId);
- fillEntityFromModel(consent, consentEntity);
- getMongoStore().insertEntity(consentEntity, invocationContext);
- }
-
- @Override
- public UserConsentModel getConsentByClient(String clientId) {
- UserConsentEntity consentEntity = getConsentEntityByClientId(clientId);
- return consentEntity!=null ? toConsentModel(consentEntity) : null;
- }
-
- @Override
- public List<UserConsentModel> getConsents() {
- List<UserConsentModel> result = new ArrayList<UserConsentModel>();
-
- DBObject query = new QueryBuilder()
- .and("userId").is(getId())
- .get();
- List<MongoUserConsentEntity> grantedConsents = getMongoStore().loadEntities(MongoUserConsentEntity.class, query, invocationContext);
-
- for (UserConsentEntity consentEntity : grantedConsents) {
- UserConsentModel model = toConsentModel(consentEntity);
- result.add(model);
- }
-
- return result;
- }
-
- private MongoUserConsentEntity getConsentEntityByClientId(String clientId) {
- DBObject query = new QueryBuilder()
- .and("userId").is(getId())
- .and("clientId").is(clientId)
- .get();
- return getMongoStore().loadSingleEntity(MongoUserConsentEntity.class, query, invocationContext);
- }
-
- private UserConsentModel toConsentModel(UserConsentEntity entity) {
- ClientModel client = realm.getClientById(entity.getClientId());
- if (client == null) {
- throw new ModelException("Client with id " + entity.getClientId() + " is not available");
- }
- UserConsentModel model = new UserConsentModel(client);
-
- for (String roleId : entity.getGrantedRoles()) {
- RoleModel roleModel = realm.getRoleById(roleId);
- if (roleModel != null) {
- model.addGrantedRole(roleModel);
- }
- }
-
- for (String protMapperId : entity.getGrantedProtocolMappers()) {
- ProtocolMapperModel protocolMapper = client.getProtocolMapperById(protMapperId);
- model.addGrantedProtocolMapper(protocolMapper);
- }
- return model;
- }
-
- // Fill roles and protocolMappers to entity
- private void fillEntityFromModel(UserConsentModel consent, MongoUserConsentEntity consentEntity) {
- List<String> roleIds = new LinkedList<String>();
- for (RoleModel role : consent.getGrantedRoles()) {
- roleIds.add(role.getId());
- }
- consentEntity.setGrantedRoles(roleIds);
-
- List<String> protMapperIds = new LinkedList<String>();
- for (ProtocolMapperModel protMapperModel : consent.getGrantedProtocolMappers()) {
- protMapperIds.add(protMapperModel.getId());
- }
- consentEntity.setGrantedProtocolMappers(protMapperIds);
- }
-
- @Override
- public void updateConsent(UserConsentModel consent) {
- String clientId = consent.getClient().getId();
- MongoUserConsentEntity consentEntity = getConsentEntityByClientId(clientId);
- if (consentEntity == null) {
- throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]");
- } else {
- fillEntityFromModel(consent, consentEntity);
- getMongoStore().updateEntity(consentEntity, invocationContext);
- }
- }
-
- @Override
- public boolean revokeConsentForClient(String clientId) {
- MongoUserConsentEntity entity = getConsentEntityByClientId(clientId);
- if (entity == null) {
- return false;
- }
-
- return getMongoStore().removeEntity(entity, invocationContext);
- }
@Override
public boolean equals(Object o) {
diff --git a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapper.java b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapper.java
index ce9c199..98fa98d 100644
--- a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapper.java
+++ b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapper.java
@@ -34,8 +34,8 @@ import org.keycloak.provider.Provider;
public interface UserFederationMapper extends Provider {
/**
- * Sync data from federation storage to Keycloak. It's useful just if mapper needs some data preloaded from federation storage (For example
- * load roles from federation provider and sync them to Keycloak database)
+ * Sync data from federated storage to Keycloak. It's useful just if mapper needs some data preloaded from federated storage (For example
+ * load roles from federated provider and sync them to Keycloak database)
*
* Applicable just if sync is supported (see UserFederationMapperFactory.getSyncConfig() )
*
@@ -48,7 +48,7 @@ public interface UserFederationMapper extends Provider {
UserFederationSyncResult syncDataFromFederationProviderToKeycloak(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm);
/**
- * Sync data from Keycloak back to federation storage
+ * Sync data from Keycloak back to federated storage
*
* @see UserFederationMapperFactory#getSyncConfig()
* @param mapperModel
diff --git a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
index 661462c..0efcf2e 100644
--- a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
+++ b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
@@ -32,7 +32,7 @@ import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresenta
public interface UserFederationMapperFactory extends ProviderFactory<UserFederationMapper>, ConfiguredProvider {
/**
- * Refers to providerName (type) of the federation provider, which this mapper can be used for. For example "ldap" or "kerberos"
+ * Refers to providerName (type) of the federated provider, which this mapper can be used for. For example "ldap" or "kerberos"
*
* @return providerName
*/
@@ -42,7 +42,7 @@ public interface UserFederationMapperFactory extends ProviderFactory<UserFederat
String getDisplayType();
/**
- * Specifies if mapper supports sync data from federation storage to keycloak and viceversa.
+ * Specifies if mapper supports sync data from federated storage to keycloak and viceversa.
* Also specifies messages to be displayed in admin console UI (For example "Sync roles from LDAP" etc)
*
* @return syncConfig representation
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java
index d83ae84..8d573c7 100755
--- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java
@@ -56,7 +56,7 @@ public class MigrateTo1_3_0 {
if (fedProvider.getProviderName().equals(LDAPConstants.LDAP_PROVIDER)) {
Map<String, String> config = fedProvider.getConfig();
- // Update config properties for LDAP federation provider
+ // Update config properties for LDAP federated provider
if (config.get(LDAPConstants.SEARCH_SCOPE) == null) {
config.put(LDAPConstants.SEARCH_SCOPE, String.valueOf(SearchControls.SUBTREE_SCOPE));
}
diff --git a/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java b/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java
index cc3356b..6780498 100755
--- a/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -19,6 +19,7 @@ package org.keycloak.models.entities;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -80,13 +81,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private String emailTheme;
// We are using names of defaultRoles (not ids)
- private List<String> defaultRoles = new ArrayList<String>();
- private List<String> defaultGroups = new ArrayList<String>();
+ private List<String> defaultRoles = new LinkedList<String>();
+ private List<String> defaultGroups = new LinkedList<String>();
- private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
- private List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
- private List<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
- private List<IdentityProviderEntity> identityProviders = new ArrayList<IdentityProviderEntity>();
+ private List<RequiredCredentialEntity> requiredCredentials = new LinkedList<>();
+ private List<StorageProviderEntity> storageProviders = new LinkedList<>();
+ private List<UserFederationProviderEntity> userFederationProviders = new LinkedList<UserFederationProviderEntity>();
+ private List<UserFederationMapperEntity> userFederationMappers = new LinkedList<UserFederationMapperEntity>();
+ private List<IdentityProviderEntity> identityProviders = new LinkedList<IdentityProviderEntity>();
private Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
private Map<String, String> smtpConfig = new HashMap<String, String>();
@@ -682,6 +684,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
public void setDefaultGroups(List<String> defaultGroups) {
this.defaultGroups = defaultGroups;
}
+
+ public List<StorageProviderEntity> getStorageProviders() {
+ return storageProviders;
+ }
+
+ public void setStorageProviders(List<StorageProviderEntity> storageProviders) {
+ this.storageProviders = storageProviders;
+ }
}
diff --git a/server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java b/server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java
new file mode 100755
index 0000000..3845ba0
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.entities;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class StorageProviderEntity extends AbstractIdentifiableEntity {
+ protected String providerName;
+ protected Map<String, String> config;
+ protected int priority;
+ protected String displayName;
+
+
+ public String getProviderName() {
+ return providerName;
+ }
+
+ public void setProviderName(String providerName) {
+ this.providerName = providerName;
+ }
+
+ public Map<String, String> getConfig() {
+ return config;
+ }
+
+ public void setConfig(Map<String, String> config) {
+ this.config = config;
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+
+ public void setPriority(int priority) {
+ this.priority = priority;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/FederatedIdentityModel.java b/server-spi/src/main/java/org/keycloak/models/FederatedIdentityModel.java
index dcda375..e7e4ee6 100755
--- a/server-spi/src/main/java/org/keycloak/models/FederatedIdentityModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/FederatedIdentityModel.java
@@ -57,4 +57,25 @@ public class FederatedIdentityModel {
public void setToken(String token) {
this.token = token;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FederatedIdentityModel that = (FederatedIdentityModel) o;
+
+ if (userId != null ? !userId.equals(that.userId) : that.userId != null) return false;
+ if (!identityProvider.equals(that.identityProvider)) return false;
+ return userName != null ? userName.equals(that.userName) : that.userName == null;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = userId != null ? userId.hashCode() : 0;
+ result = 31 * result + identityProvider.hashCode();
+ result = 31 * result + (userName != null ? userName.hashCode() : 0);
+ return result;
+ }
}
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
index 8521e30..1d71b1f 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
@@ -19,6 +19,8 @@ package org.keycloak.models;
import org.keycloak.provider.Provider;
import org.keycloak.scripting.ScriptingProvider;
+import org.keycloak.storage.federated.UserFederatedStorageProvider;
+import org.keycloak.storage.federated.UserFederatedStorageProviderFactory;
import java.util.Set;
@@ -73,11 +75,16 @@ public interface KeycloakSession {
*/
UserFederationManager users();
+ UserProvider userStorageManager();
+
/**
* Keycloak user storage. Non-federated, but possibly cache (if it is on) view of users.
*/
UserProvider userStorage();
+ UserFederatedStorageProvider userFederatedStorage();
+ UserProvider userLocalStorage();
+
/**
* Keycloak scripting support.
*/
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
index 65ab90e..88eb240 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
@@ -19,6 +19,7 @@ package org.keycloak.models;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.provider.ProviderEvent;
+import org.keycloak.storage.StorageProviderModel;
import java.security.Key;
import java.security.PrivateKey;
@@ -275,9 +276,16 @@ public interface RealmModel extends RoleContainerModel {
public IdentityProviderMapperModel getIdentityProviderMapperById(String id);
public IdentityProviderMapperModel getIdentityProviderMapperByName(String brokerAlias, String name);
+
+ StorageProviderModel addStorageProvider(StorageProviderModel model);
+ void updateStorageProvider(StorageProviderModel provider);
+ void removeStorageProvider(StorageProviderModel provider);
+ void setStorageProviders(List<StorageProviderModel> providers);
+ List<StorageProviderModel> getStorageProviders();
+ StorageProviderModel getStorageProvider(String id);
+
// Should return list sorted by UserFederationProviderModel.priority
List<UserFederationProviderModel> getUserFederationProviders();
-
UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync);
void updateUserFederationProvider(UserFederationProviderModel provider);
void removeUserFederationProvider(UserFederationProviderModel provider);
diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialAuthenticationProvider.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialAuthenticationProvider.java
new file mode 100644
index 0000000..a55145d
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialAuthenticationProvider.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserCredentialAuthenticationProvider {
+ Set<String> getSupportedCredentialAuthenticationTypes();
+ CredentialValidationOutput validCredential(KeycloakSession session, RealmModel realm, UserCredentialModel input);
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialValidatorProvider.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialValidatorProvider.java
new file mode 100644
index 0000000..13fc015
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialValidatorProvider.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserCredentialValidatorProvider {
+ boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input);
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialValueModel.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialValueModel.java
index 65d0c89..5ef6071 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserCredentialValueModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialValueModel.java
@@ -25,7 +25,7 @@ import java.io.Serializable;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserCredentialValueModel implements Serializable {
-
+ private String id;
private String type;
private String value;
private String device;
@@ -40,6 +40,14 @@ public class UserCredentialValueModel implements Serializable {
private int period;
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
public String getType() {
return type;
}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
index ef8d182..08a070d 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -20,6 +20,7 @@ package org.keycloak.models;
import org.jboss.logging.Logger;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.services.managers.UserManager;
+import org.keycloak.storage.StorageProviderModel;
import java.util.ArrayList;
import java.util.Arrays;
@@ -175,6 +176,38 @@ public class UserFederationManager implements UserProvider {
}
@Override
+ public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+ validateUser(realm, user);
+ session.userStorage().addConsent(realm, user, consent);
+
+ }
+
+ @Override
+ public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId) {
+ validateUser(realm, user);
+ return session.userStorage().getConsentByClient(realm, user, clientInternalId);
+ }
+
+ @Override
+ public List<UserConsentModel> getConsents(RealmModel realm, UserModel user) {
+ validateUser(realm, user);
+ return session.userStorage().getConsents(realm, user);
+ }
+
+ @Override
+ public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+ validateUser(realm, user);
+ session.userStorage().updateConsent(realm, user, consent);
+
+ }
+
+ @Override
+ public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId) {
+ validateUser(realm, user);
+ return session.userStorage().revokeConsentForClient(realm, user, clientInternalId);
+ }
+
+ @Override
public UserModel getUserById(String id, RealmModel realm) {
UserModel user = session.userStorage().getUserById(id, realm);
if (user != null) {
@@ -265,8 +298,8 @@ public class UserFederationManager implements UserProvider {
}
@Override
- public UserModel getUserByServiceAccountClient(ClientModel client) {
- UserModel user = session.userStorage().getUserByServiceAccountClient(client);
+ public UserModel getServiceAccount(ClientModel client) {
+ UserModel user = session.userStorage().getServiceAccount(client);
if (user != null) {
user = validateAndProxyUser(client.getRealm(), user);
}
@@ -280,6 +313,16 @@ public class UserFederationManager implements UserProvider {
}
@Override
+ public List<UserModel> getUsers(RealmModel realm) {
+ return getUsers(realm, false);
+ }
+
+ @Override
+ public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
+ return getUsers(realm, firstResult, maxResults, false);
+ }
+
+ @Override
public int getUsersCount(RealmModel realm) {
return session.userStorage().getUsersCount(realm);
}
@@ -442,6 +485,11 @@ public class UserFederationManager implements UserProvider {
session.userStorage().preRemove(protocolMapper);
}
+ @Override
+ public void preRemove(RealmModel realm, StorageProviderModel link) {
+
+ }
+
public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) {
if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
if (realm.getPasswordPolicy() != null) {
diff --git a/server-spi/src/main/java/org/keycloak/models/UserLookupProvider.java b/server-spi/src/main/java/org/keycloak/models/UserLookupProvider.java
new file mode 100644
index 0000000..3597f1e
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserLookupProvider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserLookupProvider {
+ UserModel getUserById(String id, RealmModel realm);
+
+ UserModel getUserByUsername(String username, RealmModel realm);
+
+ UserModel getUserByEmail(String email, RealmModel realm);
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserModel.java b/server-spi/src/main/java/org/keycloak/models/UserModel.java
index 013ff9c..28c35ab 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserModel.java
@@ -129,12 +129,6 @@ public interface UserModel extends RoleMapperModel {
String getServiceAccountClientLink();
void setServiceAccountClientLink(String clientInternalId);
- void addConsent(UserConsentModel consent);
- UserConsentModel getConsentByClient(String clientInternalId);
- List<UserConsentModel> getConsents();
- void updateConsent(UserConsentModel consent);
- boolean revokeConsentForClient(String clientInternalId);
-
public static enum RequiredAction {
VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD
}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserProvider.java b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
index 67c887f..d903799 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
@@ -18,56 +18,40 @@
package org.keycloak.models;
import org.keycloak.provider.Provider;
+import org.keycloak.storage.StorageProviderModel;
import java.util.List;
-import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public interface UserProvider extends Provider {
+public interface UserProvider extends Provider, UserLookupProvider, UserQueryProvider, UserCredentialValidatorProvider, UserUpdateProvider {
// Note: The reason there are so many query methods here is for layering a cache on top of an persistent KeycloakSession
- UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions);
- UserModel addUser(RealmModel realm, String username);
- boolean removeUser(RealmModel realm, UserModel user);
-
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink);
public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider);
void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel);
+ Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm);
+ FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm);
+ UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm);
- UserModel getUserById(String id, RealmModel realm);
- UserModel getUserByUsername(String username, RealmModel realm);
- UserModel getUserByEmail(String email, RealmModel realm);
+ void addConsent(RealmModel realm, UserModel user, UserConsentModel consent);
+ UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId);
+ List<UserConsentModel> getConsents(RealmModel realm, UserModel user);
+ void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent);
+ boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId);
- List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults);
- UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm);
- UserModel getUserByServiceAccountClient(ClientModel client);
+ UserModel getServiceAccount(ClientModel client);
List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts);
-
- // Service account is included for counts
- int getUsersCount(RealmModel realm);
- List<UserModel> getGroupMembers(RealmModel realm, GroupModel group);
List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts);
- List<UserModel> searchForUser(String search, RealmModel realm);
- List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults);
- List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm);
- List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults);
-
- // Searching by UserModel.attribute (not property)
- List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm);
-
- Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm);
- FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm);
-
- void grantToAllUsers(RealmModel realm, RoleModel role);
void preRemove(RealmModel realm);
void preRemove(RealmModel realm, UserFederationProviderModel link);
+ void preRemove(RealmModel realm, StorageProviderModel link);
void preRemove(RealmModel realm, RoleModel role);
void preRemove(RealmModel realm, GroupModel group);
@@ -75,9 +59,10 @@ public interface UserProvider extends Provider {
void preRemove(RealmModel realm, ClientModel client);
void preRemove(ProtocolMapperModel protocolMapper);
- boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input);
+
boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input);
CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input);
+
void close();
}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java b/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java
new file mode 100644
index 0000000..57a69ca
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserQueryProvider {
+
+ // Service account is included for counts
+ int getUsersCount(RealmModel realm);
+
+ List<UserModel> getUsers(RealmModel realm);
+ List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm);
+
+ List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults);
+ List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults);
+ List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults);
+
+
+
+
+
+ List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults);
+ List<UserModel> getGroupMembers(RealmModel realm, GroupModel group);
+
+ List<UserModel> searchForUser(String search, RealmModel realm);
+
+ // Searching by UserModel.attribute (not property)
+ List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm);
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserUpdateProvider.java b/server-spi/src/main/java/org/keycloak/models/UserUpdateProvider.java
new file mode 100644
index 0000000..a9efe97
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserUpdateProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserUpdateProvider {
+ UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions);
+
+ UserModel addUser(RealmModel realm, String username);
+
+ boolean removeUser(RealmModel realm, UserModel user);
+
+ void grantToAllUsers(RealmModel realm, RoleModel role);
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
index 074949f..c6b3762 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
@@ -48,7 +48,7 @@ public class CredentialValidation {
}
- /**
+ /**
* Will update password if hash iteration policy has changed
*
* @param realm
@@ -195,7 +195,7 @@ public class CredentialValidation {
return true;
}
- private static boolean validCredential(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel credential) {
+ public static boolean validCredential(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel credential) {
if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
if (!validPassword(session, realm, user, credential.getValue())) {
return false;
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentials.java b/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentials.java
new file mode 100644
index 0000000..69a823c
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentials.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models.utils;
+
+import org.keycloak.common.util.Time;
+import org.keycloak.hash.PasswordHashManager;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OTPPolicy;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.storage.federated.UserFederatedStorageProvider;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class FederatedCredentials {
+ public static void updateCredential(KeycloakSession session, UserFederatedStorageProvider provider, RealmModel realm, UserModel user, UserCredentialModel cred) {
+ if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
+ updatePasswordCredential(session, provider,realm, user, cred);
+ } else if (UserCredentialModel.isOtp(cred.getType())) {
+ updateOtpCredential(session, provider, realm, user, cred);
+ } else {
+ UserCredentialValueModel fedCred = getCredentialByType(provider, realm, user, cred.getType());
+ if (fedCred == null) {
+ fedCred.setCreatedDate(Time.toMillis(Time.currentTime()));
+ fedCred.setType(cred.getType());
+ fedCred.setDevice(cred.getDevice());
+ fedCred.setValue(cred.getValue());
+
+ } else {
+ fedCred.setValue(cred.getValue());
+ }
+ provider.updateCredential(realm, user, fedCred);
+ }
+ }
+
+ public static UserCredentialValueModel getCredentialByType(UserFederatedStorageProvider provider, RealmModel realm, UserModel user, String type) {
+ List<UserCredentialValueModel> creds = provider.getCredentials(realm, user);
+ for (UserCredentialValueModel cred : creds) {
+ if (cred.getType().equals(type)) return cred;
+ }
+ return null;
+ }
+
+ public static LinkedList<UserCredentialValueModel> getCredentialsByType(UserFederatedStorageProvider provider, RealmModel realm, UserModel user, String type) {
+ List<UserCredentialValueModel> creds = provider.getCredentials(realm, user);
+ LinkedList<UserCredentialValueModel> newCreds = new LinkedList<>();
+ for (UserCredentialValueModel cred : creds) {
+ if (cred.getType().equals(type)) newCreds.add(cred);
+ }
+ return newCreds;
+ }
+
+ public static void updatePasswordCredential(KeycloakSession session, UserFederatedStorageProvider provider, RealmModel realm, UserModel user, UserCredentialModel cred) {
+ UserCredentialValueModel fedCred = getCredentialByType(provider, realm, user, cred.getType());
+ if (fedCred == null) {
+ UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, cred.getValue());
+ newCred.setCreatedDate(Time.toMillis(Time.currentTime()));
+ newCred.setType(cred.getType());
+ newCred.setDevice(cred.getDevice());
+ provider.updateCredential(realm, user, newCred);
+ } else {
+ int expiredPasswordsPolicyValue = -1;
+ PasswordPolicy policy = realm.getPasswordPolicy();
+ if(policy != null) {
+ expiredPasswordsPolicyValue = policy.getExpiredPasswords();
+ }
+
+ if (expiredPasswordsPolicyValue != -1) {
+ fedCred.setType(UserCredentialModel.PASSWORD_HISTORY);
+
+ LinkedList<UserCredentialValueModel> credentialEntities = getCredentialsByType(provider, realm, user, UserCredentialModel.PASSWORD_HISTORY);
+ if (credentialEntities.size() > expiredPasswordsPolicyValue - 1) {
+ Collections.sort(credentialEntities, new Comparator<UserCredentialValueModel>() {
+ @Override
+ public int compare(UserCredentialValueModel o1, UserCredentialValueModel o2) {
+ if (o1.getCreatedDate().equals(o2.getCreatedDate())) return 0;
+ return o1.getCreatedDate() < o2.getCreatedDate() ? -1 : 1;
+ }
+ });
+ while (credentialEntities.size() > expiredPasswordsPolicyValue - 1) {
+ UserCredentialValueModel model = credentialEntities.removeFirst();
+ provider.removeCredential(realm, user, model);
+ }
+
+ }
+ provider.updateCredential(realm, user, fedCred);
+ fedCred = PasswordHashManager.encode(session, realm, cred.getValue());
+ fedCred.setCreatedDate(Time.toMillis(Time.currentTime()));
+ fedCred.setType(cred.getType());
+ fedCred.setDevice(cred.getDevice());
+ provider.updateCredential(realm, user, fedCred);
+ } else {
+ // clear password history as it is not required anymore
+ for (UserCredentialValueModel model : getCredentialsByType(provider, realm, user, UserCredentialModel.PASSWORD_HISTORY)) {
+ provider.removeCredential(realm, user, model);
+ }
+ UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, cred.getValue());
+ newCred.setCreatedDate(Time.toMillis(Time.currentTime()));
+ newCred.setType(cred.getType());
+ newCred.setDevice(cred.getDevice());
+ newCred.setId(fedCred.getId());
+ provider.updateCredential(realm, user, newCred);
+ }
+
+
+ }
+
+
+ }
+
+ public static void updateOtpCredential(KeycloakSession session, UserFederatedStorageProvider provider, RealmModel realm, UserModel user, UserCredentialModel cred) {
+ LinkedList<UserCredentialValueModel> credentialEntities = getCredentialsByType(provider, realm, user, UserCredentialModel.PASSWORD_HISTORY);
+
+ if (credentialEntities.isEmpty()) {
+ UserCredentialValueModel fedCred = new UserCredentialValueModel();
+ fedCred.setCreatedDate(Time.toMillis(Time.currentTime()));
+ fedCred.setType(cred.getType());
+ fedCred.setDevice(cred.getDevice());
+ fedCred.setValue(cred.getValue());
+ OTPPolicy otpPolicy = realm.getOTPPolicy();
+ fedCred.setAlgorithm(otpPolicy.getAlgorithm());
+ fedCred.setDigits(otpPolicy.getDigits());
+ fedCred.setCounter(otpPolicy.getInitialCounter());
+ fedCred.setPeriod(otpPolicy.getPeriod());
+ provider.updateCredential(realm, user, fedCred);
+ } else {
+ OTPPolicy policy = realm.getOTPPolicy();
+ if (cred.getDevice() == null) {
+ for (UserCredentialValueModel model : credentialEntities) provider.removeCredential(realm, user, model);
+ UserCredentialValueModel fedCred = new UserCredentialValueModel();
+ fedCred.setCreatedDate(Time.toMillis(Time.currentTime()));
+ fedCred.setType(cred.getType());
+ fedCred.setDevice(cred.getDevice());
+ fedCred.setDigits(policy.getDigits());
+ fedCred.setCounter(policy.getInitialCounter());
+ fedCred.setAlgorithm(policy.getAlgorithm());
+ fedCred.setValue(cred.getValue());
+ fedCred.setPeriod(policy.getPeriod());
+ provider.updateCredential(realm, user, fedCred);
+ } else {
+ UserCredentialValueModel fedCred = new UserCredentialValueModel();
+ for (UserCredentialValueModel model : credentialEntities) {
+ if (cred.getDevice().equals(model.getDevice())) {
+ fedCred = model;
+ break;
+ }
+ }
+ fedCred.setCreatedDate(Time.toMillis(Time.currentTime()));
+ fedCred.setType(cred.getType());
+ fedCred.setDevice(cred.getDevice());
+ fedCred.setDigits(policy.getDigits());
+ fedCred.setCounter(policy.getInitialCounter());
+ fedCred.setAlgorithm(policy.getAlgorithm());
+ fedCred.setValue(cred.getValue());
+ fedCred.setPeriod(policy.getPeriod());
+ provider.updateCredential(realm, user, fedCred);
+ }
+ }
+ }
+
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentialValidation.java b/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentialValidation.java
new file mode 100755
index 0000000..54df576
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentialValidation.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils;
+
+import org.keycloak.common.util.Time;
+import org.keycloak.hash.PasswordHashManager;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OTPPolicy;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.PasswordToken;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class FederatedCredentialValidation {
+
+ private static int hashIterations(RealmModel realm) {
+ PasswordPolicy policy = realm.getPasswordPolicy();
+ if (policy != null) {
+ return policy.getHashIterations();
+ }
+ return -1;
+
+ }
+
+ /**
+ * Will update password if hash iteration policy has changed
+ *
+ * @param realm
+ * @param user
+ * @param password
+ * @return
+ */
+ public static boolean validPassword(KeycloakSession session, RealmModel realm, UserModel user, String password, UserCredentialValueModel fedCred) {
+ return validateHashedCredential(session, realm, user, password, fedCred);
+
+ }
+
+
+ public static boolean validateHashedCredential(KeycloakSession session, RealmModel realm, UserModel user, String unhashedCredValue, UserCredentialValueModel credential) {
+ if (unhashedCredValue == null || unhashedCredValue.isEmpty()) {
+ return false;
+ }
+
+ boolean validated = PasswordHashManager.verify(session, realm, unhashedCredValue, credential);
+
+ if (validated) {
+ int iterations = hashIterations(realm);
+ if (iterations > -1 && iterations != credential.getHashIterations()) {
+
+ UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, unhashedCredValue);
+ session.userFederatedStorage().updateCredential(realm, user, newCred);
+ }
+
+ }
+ return validated;
+ }
+
+ public static boolean validPasswordToken(RealmModel realm, UserModel user, String encodedPasswordToken) {
+ try {
+ JWSInput jws = new JWSInput(encodedPasswordToken);
+ if (!RSAProvider.verify(jws, realm.getPublicKey())) {
+ return false;
+ }
+ PasswordToken passwordToken = jws.readJsonContent(PasswordToken.class);
+ if (!passwordToken.getRealm().equals(realm.getName())) {
+ return false;
+ }
+ if (!passwordToken.getUser().equals(user.getId())) {
+ return false;
+ }
+ if (Time.currentTime() - passwordToken.getTimestamp() > realm.getAccessCodeLifespanUserAction()) {
+ return false;
+ }
+ return true;
+ } catch (JWSInputException e) {
+ return false;
+ }
+ }
+
+ public static boolean validHOTP(KeycloakSession session, RealmModel realm, UserModel user, String otp, List<UserCredentialValueModel> fedCreds) {
+ UserCredentialValueModel passwordCred = null;
+ OTPPolicy policy = realm.getOTPPolicy();
+ HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());
+ for (UserCredentialValueModel cred : fedCreds) {
+ if (cred.getType().equals(UserCredentialModel.HOTP)) {
+ int counter = validator.validateHOTP(otp, cred.getValue(), cred.getCounter());
+ if (counter < 0) return false;
+ cred.setCounter(counter);
+ session.userFederatedStorage().updateCredential(realm, user, cred);
+ return true;
+ }
+ }
+ return false;
+
+ }
+
+ public static boolean validTOTP(RealmModel realm, UserModel user, String otp, List<UserCredentialValueModel> fedCreds) {
+ UserCredentialValueModel passwordCred = null;
+ OTPPolicy policy = realm.getOTPPolicy();
+ TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow());
+ for (UserCredentialValueModel cred : fedCreds) {
+ if (validator.validateTOTP(otp, cred.getValue().getBytes())) {
+ return true;
+ }
+ }
+ return false;
+
+ }
+ public static boolean validSecret(RealmModel realm, UserModel user, String secret, UserCredentialValueModel cred) {
+ return cred.getValue().equals(secret);
+
+ }
+
+ public static boolean validCredential(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel credential, List<UserCredentialValueModel> fedCreds) {
+ if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
+ if (!validPassword(session, realm, user, credential.getValue(), fedCreds.get(0))) {
+ return false;
+ }
+ } else if (credential.getType().equals(UserCredentialModel.PASSWORD_TOKEN)) {
+ if (!validPasswordToken(realm, user, credential.getValue())) {
+ return false;
+ }
+ } else if (credential.getType().equals(UserCredentialModel.TOTP)) {
+ if (!validTOTP(realm, user, credential.getValue(), fedCreds)) {
+ return false;
+ }
+ } else if (credential.getType().equals(UserCredentialModel.HOTP)) {
+ if (!validHOTP(session, realm, user, credential.getValue(), fedCreds)) {
+ return false;
+ }
+ } else if (credential.getType().equals(UserCredentialModel.SECRET)) {
+ if (!validSecret(realm, user, credential.getValue(), fedCreds.get(0))) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index 819c0db..55a7c02 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -44,6 +44,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.CertificateRepresentation;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.PemUtils;
+import org.keycloak.storage.StorageProviderModel;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
@@ -349,6 +350,43 @@ public final class KeycloakModelUtils {
* @param federationProviders
* @throws ModelDuplicateException if there is other provider with same displayName
*/
+ public static void ensureUniqueDisplayName(String displayName, StorageProviderModel myProvider, List<StorageProviderModel> federationProviders) throws ModelDuplicateException {
+ if (displayName != null) {
+
+ for (StorageProviderModel federationProvider : federationProviders) {
+ if (myProvider != null && (myProvider.equals(federationProvider) || (myProvider.getId() != null && myProvider.getId().equals(federationProvider.getId())))) {
+ continue;
+ }
+
+ if (displayName.equals(federationProvider.getDisplayName())) {
+ throw new ModelDuplicateException("There is already existing federation provider with display name: " + displayName);
+ }
+ }
+ }
+ }
+
+
+ public static StorageProviderModel findStorageProviderByDisplayName(String displayName, RealmModel realm) {
+ if (displayName == null) {
+ return null;
+ }
+
+ for (StorageProviderModel provider : realm.getStorageProviders()) {
+ if (displayName.equals(provider.getDisplayName())) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Ensure that displayName of myProvider (if not null) is unique and there is no other provider with same displayName in the list.
+ *
+ * @param displayName to check for duplications
+ * @param myProvider provider, which is excluded from the list (if present)
+ * @param federationProviders
+ * @throws ModelDuplicateException if there is other provider with same displayName
+ */
public static void ensureUniqueDisplayName(String displayName, UserFederationProviderModel myProvider, List<UserFederationProviderModel> federationProviders) throws ModelDuplicateException {
if (displayName != null) {
@@ -378,7 +416,6 @@ public final class KeycloakModelUtils {
return null;
}
-
public static UserFederationProviderModel findUserFederationProviderById(String fedProviderId, RealmModel realm) {
for (UserFederationProviderModel fedProvider : realm.getUserFederationProviders()) {
if (fedProviderId.equals(fedProvider.getId())) {
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 2516105..b36b96c 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -1299,7 +1299,7 @@ public class RepresentationToModel {
if (userRep.getClientConsents() != null) {
for (UserConsentRepresentation consentRep : userRep.getClientConsents()) {
UserConsentModel consentModel = toModel(newRealm, consentRep);
- user.addConsent(consentModel);
+ session.userStorage().addConsent(newRealm, user, consentModel);
}
}
if (userRep.getServiceAccountClientId() != null) {
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
index f2d66e2..9a8e4ab 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
@@ -235,31 +235,6 @@ public class UserModelDelegate implements UserModel {
delegate.setServiceAccountClientLink(clientInternalId);
}
- @Override
- public void addConsent(UserConsentModel consent) {
- delegate.addConsent(consent);
- }
-
- @Override
- public UserConsentModel getConsentByClient(String clientId) {
- return delegate.getConsentByClient(clientId);
- }
-
- @Override
- public List<UserConsentModel> getConsents() {
- return delegate.getConsents();
- }
-
- @Override
- public void updateConsent(UserConsentModel consent) {
- delegate.updateConsent(consent);
- }
-
- @Override
- public boolean revokeConsentForClient(String clientId) {
- return delegate.revokeConsentForClient(clientId);
- }
-
public UserModel getDelegate() {
return delegate;
}
diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java
new file mode 100644
index 0000000..114b0c3
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.adapter;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.storage.federated.UserFederatedStorageProvider;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class AbstractUserAdapter implements UserModel {
+ protected KeycloakSession session;
+ protected RealmModel realm;
+
+ public UserFederatedStorageProvider getFederatedStorage() {
+ return null;
+ }
+
+ @Override
+ public Set<String> getRequiredActions() {
+ return getFederatedStorage().getRequiredActions(realm, this);
+ }
+
+ @Override
+ public void addRequiredAction(String action) {
+ getFederatedStorage().addRequiredAction(realm, this, action);
+
+ }
+
+ @Override
+ public void removeRequiredAction(String action) {
+ getFederatedStorage().removeRequiredAction(realm, this, action);
+
+ }
+
+ @Override
+ public void addRequiredAction(RequiredAction action) {
+ getFederatedStorage().addRequiredAction(realm, this, action.name());
+
+ }
+
+ @Override
+ public void removeRequiredAction(RequiredAction action) {
+ getFederatedStorage().removeRequiredAction(realm, this, action.name());
+ }
+
+ @Override
+ public Set<GroupModel> getGroups() {
+ return null;
+ }
+
+ @Override
+ public void joinGroup(GroupModel group) {
+
+ }
+
+ @Override
+ public void leaveGroup(GroupModel group) {
+
+ }
+
+ @Override
+ public boolean isMemberOf(GroupModel group) {
+ return false;
+ }
+
+ @Override
+ public String getFederationLink() {
+ return null;
+ }
+
+ @Override
+ public void setFederationLink(String link) {
+
+ }
+
+ @Override
+ public String getServiceAccountClientLink() {
+ return null;
+ }
+
+ @Override
+ public void setServiceAccountClientLink(String clientInternalId) {
+
+ }
+
+ @Override
+ public Set<RoleModel> getRealmRoleMappings() {
+ return null;
+ }
+
+ @Override
+ public Set<RoleModel> getClientRoleMappings(ClientModel app) {
+ return null;
+ }
+
+ @Override
+ public boolean hasRole(RoleModel role) {
+ return false;
+ }
+
+ @Override
+ public void grantRole(RoleModel role) {
+
+ }
+
+ @Override
+ public Set<RoleModel> getRoleMappings() {
+ return null;
+ }
+
+ @Override
+ public void deleteRoleMapping(RoleModel role) {
+
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return false;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+
+ }
+
+ @Override
+ public boolean isOtpEnabled() {
+ return false;
+ }
+
+ @Override
+ public void setOtpEnabled(boolean totp) {
+
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java
new file mode 100755
index 0000000..4b2813a
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.changeset;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.entities.AbstractIdentifiableEntity;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserData {
+
+ private String id;
+ private boolean idChanged;
+ private String username;
+ private boolean usernameChanged;
+ private Long createdTimestamp;
+ private boolean createdTimestampChanged;
+ private String firstName;
+ private boolean firstNameChanged;
+ private String lastName;
+ private boolean lastNameChanged;
+ private String email;
+ private boolean emailChanged;
+ private boolean emailVerified;
+ private boolean emailVerifiedChanged;
+ private boolean totp;
+ private boolean totpChanged;
+ private boolean enabled;
+ private boolean enabledChanged;
+
+ private Set<String> roleIds = new HashSet<>();
+ private boolean rolesChanged;
+ private Set<String> groupIds = new HashSet<>();
+ private boolean groupsChanged;
+
+ private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
+ private boolean attributesChanged;
+ private Set<String> requiredActions = new HashSet<>();
+ private boolean requiredActionsChanged;
+ private List<UserCredentialValueModel> credentials = new LinkedList<>();
+ private boolean credentialsChanged;
+
+ public void rememberState() {
+ original = new UserData();
+ original.id = id;
+ original.username = username;
+ original.createdTimestamp = createdTimestamp;
+ original.firstName = firstName;
+ original.lastName = lastName;
+ original.email = email;
+ original.emailVerified = emailVerified;
+ original.totp = totp;
+ original.enabled = enabled;
+ original.attributes.putAll(attributes);
+ original.requiredActions.addAll(requiredActions);
+ original.credentials.addAll(credentials);
+ }
+
+ private UserData original = null;
+
+ public void clearChangeFlags() {
+ original = null;
+ idChanged = false;
+ usernameChanged = false;
+ createdTimestampChanged = false;
+ firstNameChanged = false;
+ lastNameChanged = false;
+ emailChanged = false;
+ emailVerifiedChanged = false;
+ totpChanged = false;
+ enabledChanged = false;
+ rolesChanged = false;
+ groupsChanged = false;
+ attributesChanged = false;
+ requiredActionsChanged = false;
+ credentialsChanged = false;
+ }
+
+ public boolean isChanged() {
+ return !idChanged
+ && !usernameChanged
+ && !createdTimestampChanged
+ && !firstNameChanged
+ && !lastNameChanged
+ && !emailChanged
+ && !emailVerifiedChanged
+ && !totpChanged
+ && !enabledChanged
+ && !rolesChanged
+ && !groupsChanged
+ && !attributesChanged
+ && !requiredActionsChanged
+ && !credentialsChanged;
+ }
+
+ public boolean isIdChanged() {
+ return idChanged;
+ }
+
+ public boolean isUsernameChanged() {
+ return usernameChanged;
+ }
+
+ public boolean isCreatedTimestampChanged() {
+ return createdTimestampChanged;
+ }
+
+ public boolean isFirstNameChanged() {
+ return firstNameChanged;
+ }
+
+ public boolean isLastNameChanged() {
+ return lastNameChanged;
+ }
+
+ public boolean isEmailChanged() {
+ return emailChanged;
+ }
+
+ public boolean isEmailVerifiedChanged() {
+ return emailVerifiedChanged;
+ }
+
+ public boolean isTotpChanged() {
+ return totpChanged;
+ }
+
+ public boolean isEnabledChanged() {
+ return enabledChanged;
+ }
+
+ public boolean isRolesChanged() {
+ return rolesChanged;
+ }
+
+ public boolean isGroupsChanged() {
+ return groupsChanged;
+ }
+
+ public boolean isAttributesChanged() {
+ return attributesChanged;
+ }
+
+ public boolean isRequiredActionsChanged() {
+ return requiredActionsChanged;
+ }
+
+ public boolean isCredentialsChanged() {
+ return credentialsChanged;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ idChanged = true;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ usernameChanged = true;
+ }
+
+ public Long getCreatedTimestamp() {
+ return createdTimestamp;
+ }
+
+ public void setCreatedTimestamp(Long timestamp) {
+ this.createdTimestamp = timestamp;
+ createdTimestampChanged = true;
+ }
+
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ firstNameChanged = true;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ lastNameChanged = true;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ emailChanged = true;
+ }
+
+ public boolean isEmailVerified() {
+ return emailVerified;
+ }
+
+ public void setEmailVerified(boolean emailVerified) {
+ this.emailVerified = emailVerified;
+ emailVerifiedChanged = true;
+ }
+
+ public boolean isTotp() {
+ return totp;
+ }
+
+ public void setTotp(boolean totp) {
+ this.totp = totp;
+ totpChanged = true;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ enabledChanged = true;
+ }
+
+ public Set<String> getRoleMappings() {
+ return Collections.unmodifiableSet(roleIds);
+ }
+
+ public void grantRole(String roleId) {
+ if (roleIds.contains(roleId)) return;
+ roleIds.add(roleId);
+ rolesChanged = true;
+ }
+
+ public void deleteRoleMapping(String roleId) {
+ if (!roleIds.contains(roleId)) return;
+ roleIds.remove(roleId);
+ rolesChanged = true;
+ }
+
+ public MultivaluedHashMap<String, String> getAttributes() {
+ return attributes;
+ }
+
+ public void setSingleAttribute(String name, String value) {
+ attributes.putSingle(name, value);
+ attributesChanged = true;
+
+ }
+ public void setAttribute(String name, List<String> values) {
+ attributes.put(name, values);
+ attributesChanged = true;
+ }
+ public void removeAttribute(String name) {
+ attributes.remove(name);
+ attributesChanged = true;
+ }
+
+
+
+ public Set<String> getRequiredActions() {
+ return Collections.unmodifiableSet(requiredActions);
+ }
+ public void addRequiredAction(String action) {
+ if (requiredActions.contains(action)) return;
+ requiredActions.add(action);
+ requiredActionsChanged = true;
+ }
+ public void removeRequiredAction(String action) {
+ if (!requiredActions.contains(action)) return;
+ requiredActions.remove(action);
+ requiredActionsChanged = true;
+ }
+
+ public List<UserCredentialValueModel> getCredentials() {
+ return Collections.unmodifiableList(credentials);
+ }
+
+ public void removeCredentialType(String type) {
+ Iterator<UserCredentialValueModel> it = credentials.iterator();
+ while (it.hasNext()) {
+ if (it.next().getType().equals(type)) {
+ it.remove();
+ credentialsChanged = true;
+ }
+ }
+
+ }
+
+ public void removeCredentialDevice(String type, String device) {
+ Iterator<UserCredentialValueModel> it = credentials.iterator();
+ while (it.hasNext()) {
+ UserCredentialValueModel next = it.next();
+ if (next.getType().equals(type) && next.getDevice().equals(device)) {
+ it.remove();
+ credentialsChanged = true;
+ }
+ }
+
+ }
+
+ public void setCredential(UserCredentialValueModel cred) {
+ removeCredentialType(cred.getType());
+ addCredential(cred);
+ }
+ public void addCredential(UserCredentialValueModel cred) {
+ credentials.add(cred);
+ credentialsChanged = true;
+ }
+
+ public Set<String> getGroupIds() {
+ return Collections.unmodifiableSet(groupIds);
+ }
+
+ public void joinGroup(String groupId) {
+ if (groupIds.contains(groupId)) return;
+ groupIds.add(groupId);
+ groupsChanged = true;
+ }
+
+ public void leaveGroup(String groupId) {
+ if (!groupIds.contains(groupId)) return;
+ groupIds.remove(groupId);
+ groupsChanged = true;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+
+ if (this.id == null) return false;
+
+ if (o == null || getClass() != o.getClass()) return false;
+
+ AbstractIdentifiableEntity that = (AbstractIdentifiableEntity) o;
+
+ if (!getId().equals(that.getId())) return false;
+
+ return true;
+
+ }
+
+ @Override
+ public int hashCode() {
+ return id!=null ? id.hashCode() : super.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s [ id=%s ]", getClass().getSimpleName(), getId());
+ }
+
+}
+
diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java
new file mode 100644
index 0000000..6ec78f2
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.changeset;
+
+import org.keycloak.common.util.Time;
+import org.keycloak.hash.PasswordHashManager;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OTPPolicy;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ *
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserDataAdapter implements UserModel {
+ protected UserData userData;
+ protected RealmModel realm;
+ protected KeycloakSession session;
+ protected Set<String> managedCredentialTypes;
+ protected List<UserCredentialModel> updatedManagedCredentials = new LinkedList<>();
+
+ public UserDataAdapter(KeycloakSession session, RealmModel realm, UserData userData) {
+ this.session = session;
+ this.realm = realm;
+ this.userData = userData;
+ this.userData.rememberState();
+ }
+
+ @Override
+ public String getId() {
+ return userData.getId();
+ }
+
+ @Override
+ public String getUsername() {
+ return userData.getUsername();
+ }
+
+ @Override
+ public void setUsername(String username) {
+ userData.setUsername(username);
+
+ }
+
+ @Override
+ public Long getCreatedTimestamp() {
+ return userData.getCreatedTimestamp();
+ }
+
+ @Override
+ public void setCreatedTimestamp(Long timestamp) {
+ userData.setCreatedTimestamp(timestamp);
+
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return userData.isEnabled();
+ }
+
+ @Override
+ public boolean isOtpEnabled() {
+ return userData.isTotp();
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ userData.setEnabled(enabled);
+
+ }
+
+ @Override
+ public void setSingleAttribute(String name, String value) {
+ userData.setSingleAttribute(name, value);
+
+ }
+
+ @Override
+ public void setAttribute(String name, List<String> values) {
+ userData.setAttribute(name, values);
+
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ userData.removeAttribute(name);
+
+ }
+
+ @Override
+ public String getFirstAttribute(String name) {
+ return userData.getAttributes().getFirst(name);
+ }
+
+ @Override
+ public List<String> getAttribute(String name) {
+ return userData.getAttributes().get(name);
+ }
+
+ @Override
+ public Map<String, List<String>> getAttributes() {
+ return userData.getAttributes();
+ }
+
+ @Override
+ public Set<String> getRequiredActions() {
+ return userData.getRequiredActions();
+ }
+
+ @Override
+ public void addRequiredAction(String action) {
+ userData.addRequiredAction(action);
+
+ }
+
+ @Override
+ public void removeRequiredAction(String action) {
+ userData.removeRequiredAction(action);
+
+ }
+
+ @Override
+ public void addRequiredAction(RequiredAction action) {
+ userData.addRequiredAction(action.name());
+
+ }
+
+ @Override
+ public void removeRequiredAction(RequiredAction action) {
+ userData.removeRequiredAction(action.name());
+
+ }
+
+ @Override
+ public String getFirstName() {
+ return userData.getFirstName();
+ }
+
+ @Override
+ public void setFirstName(String firstName) {
+ userData.setFirstName(firstName);
+
+ }
+
+ @Override
+ public String getLastName() {
+ return userData.getLastName();
+ }
+
+ @Override
+ public void setLastName(String lastName) {
+ userData.setLastName(lastName);
+
+ }
+
+ @Override
+ public String getEmail() {
+ return userData.getEmail();
+ }
+
+ @Override
+ public void setEmail(String email) {
+ userData.setEmail(email);
+
+ }
+
+ @Override
+ public boolean isEmailVerified() {
+ return userData.isEmailVerified();
+ }
+
+ @Override
+ public void setEmailVerified(boolean verified) {
+ userData.setEmailVerified(verified);
+
+ }
+
+ @Override
+ public void setOtpEnabled(boolean totp) {
+ userData.setTotp(totp);
+
+ }
+
+ @Override
+ public void updateCredential(UserCredentialModel cred) {
+
+ }
+
+ @Override
+ public List<UserCredentialValueModel> getCredentialsDirectly() {
+ return null;
+ }
+
+ @Override
+ public void updateCredentialDirectly(UserCredentialValueModel cred) {
+
+ }
+
+ @Override
+ public Set<GroupModel> getGroups() {
+ Set<String> groups = userData.getGroupIds();
+ Set<GroupModel> set = new HashSet<>();
+ for (String id : groups) {
+ GroupModel group = realm.getGroupById(id);
+ if (group != null) set.add(group);
+ }
+ return set;
+ }
+
+ @Override
+ public void joinGroup(GroupModel group) {
+ userData.joinGroup(group.getId());
+
+ }
+
+ @Override
+ public void leaveGroup(GroupModel group) {
+ userData.leaveGroup(group.getId());
+
+ }
+
+ @Override
+ public boolean isMemberOf(GroupModel group) {
+ Set<GroupModel> roles = getGroups();
+ return KeycloakModelUtils.isMember(roles, group);
+ }
+
+ @Override
+ public String getFederationLink() {
+ return null;
+ }
+
+ @Override
+ public void setFederationLink(String link) {
+
+ }
+
+ @Override
+ public String getServiceAccountClientLink() {
+ return null;
+ }
+
+ @Override
+ public void setServiceAccountClientLink(String clientInternalId) {
+
+ }
+
+ @Override
+ public Set<RoleModel> getRealmRoleMappings() {
+ Set<RoleModel> roleMappings = getRoleMappings();
+
+ Set<RoleModel> realmRoles = new HashSet<RoleModel>();
+ for (RoleModel role : roleMappings) {
+ RoleContainerModel container = role.getContainer();
+ if (container instanceof RealmModel) {
+ realmRoles.add(role);
+ }
+ }
+ return realmRoles;
+ }
+
+ @Override
+ public Set<RoleModel> getClientRoleMappings(ClientModel app) {
+ Set<RoleModel> roleMappings = getRoleMappings();
+
+ Set<RoleModel> roles = new HashSet<RoleModel>();
+ for (RoleModel role : roleMappings) {
+ RoleContainerModel container = role.getContainer();
+ if (container instanceof ClientModel) {
+ ClientModel appModel = (ClientModel)container;
+ if (appModel.getId().equals(app.getId())) {
+ roles.add(role);
+ }
+ }
+ }
+ return roles;
+ }
+
+ @Override
+ public boolean hasRole(RoleModel role) {
+ Set<RoleModel> roles = getRoleMappings();
+ return KeycloakModelUtils.hasRole(roles, role);
+ }
+
+ @Override
+ public void grantRole(RoleModel role) {
+ userData.grantRole(role.getId());
+
+ }
+
+ @Override
+ public Set<RoleModel> getRoleMappings() {
+ Set<String> roles = userData.getRoleMappings();
+ Set<RoleModel> set = new HashSet<>();
+ for (String id : roles) {
+ RoleModel role = realm.getRoleById(id);
+ if (role != null) set.add(role);
+ }
+ return set;
+ }
+
+ @Override
+ public void deleteRoleMapping(RoleModel role) {
+ userData.deleteRoleMapping(role.getId());
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java
new file mode 100644
index 0000000..741f3b7
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.changeset;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserDataCredentialValidator {
+ boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input);
+ boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java
new file mode 100644
index 0000000..5e80d5d
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.changeset;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.entities.UserEntity;
+import org.keycloak.storage.StorageId;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserDataLookup {
+ UserData getUserById(RealmModel realm, StorageId id);
+ UserData getUserByUsername(RealmModel realm, String username);
+ UserData getUserByEmail(RealmModel realm, String email);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java
new file mode 100644
index 0000000..e0ef387
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.changeset;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.entities.UserEntity;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserDataQuery {
+
+ // Service account is included for counts
+ int getUsersCount(RealmModel realm);
+
+ List<UserData> getUsers(RealmModel realm);
+ List<UserData> searchForUser(String search, RealmModel realm);
+ List<UserData> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm);
+
+ List<UserData> getUsers(RealmModel realm, int firstResult, int maxResults);
+ List<UserData> searchForUser(String search, RealmModel realm, int firstResult, int maxResults);
+ List<UserData> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults);
+
+
+
+ // Searching by UserModel.attribute (not property)
+ List<UserData> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java
new file mode 100644
index 0000000..e896b00
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.changeset;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.entities.UserEntity;
+import org.keycloak.storage.StorageId;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserDataStore {
+ void updateUser(RealmModel realm, UserData user);
+ void addUser(RealmModel realm, UserData user);
+ boolean removeUser(RealmModel realm, StorageId store);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/CredentialModel.java b/server-spi/src/main/java/org/keycloak/storage/federated/CredentialModel.java
new file mode 100644
index 0000000..7c4b735
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/CredentialModel.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CredentialModel {
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java
new file mode 100644
index 0000000..12bcabf
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserAttributeFederatedStorage {
+ void setSingleAttribute(RealmModel realm, UserModel user, String name, String value);
+ void setAttribute(RealmModel realm, UserModel user, String name, List<String> values);
+ void removeAttribute(RealmModel realm, UserModel user, String name);
+ MultivaluedHashMap<String, String> getAttributes(RealmModel realm, UserModel user);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java
new file mode 100644
index 0000000..6fcbabf
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserBrokerLinkFederatedStorage {
+ String getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm);
+ public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink);
+ public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider);
+ void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel);
+ Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm);
+ FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java
new file mode 100644
index 0000000..7a14b14
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserConsentModel;
+import org.keycloak.models.UserModel;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserConsentFederatedStorage {
+ void addConsent(RealmModel realm, UserModel user, UserConsentModel consent);
+ UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId);
+ List<UserConsentModel> getConsents(RealmModel realm, UserModel user);
+ void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent);
+ boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java
new file mode 100644
index 0000000..7239182
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserCredentialsFederatedStorage {
+ void updateCredential(RealmModel realm, UserModel user, UserCredentialModel cred);
+ void updateCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred);
+ void removeCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred);
+ List<UserCredentialValueModel> getCredentials(RealmModel realm, UserModel user);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java
new file mode 100755
index 0000000..46ae9db
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.federated;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.Provider;
+import org.keycloak.storage.StorageProviderModel;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserFederatedStorageProvider extends Provider,
+ UserAttributeFederatedStorage,
+ UserBrokerLinkFederatedStorage,
+ UserConsentFederatedStorage,
+ UserCredentialsFederatedStorage,
+ UserGroupMembershipFederatedStorage,
+ UserRequiredActionsFederatedStorage,
+ UserRoleMappingsFederatedStorage {
+
+ void preRemove(RealmModel realm);
+
+ void preRemove(RealmModel realm, UserFederationProviderModel link);
+
+ public void preRemove(RealmModel realm, GroupModel group);
+
+ void preRemove(RealmModel realm, RoleModel role);
+
+ void preRemove(RealmModel realm, ClientModel client);
+
+ void preRemove(ProtocolMapperModel protocolMapper);
+
+ void preRemove(RealmModel realm, UserModel user);
+
+ void preRemove(RealmModel realm, StorageProviderModel model);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java
new file mode 100644
index 0000000..667542c
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserFederatedStorageProviderFactory extends ProviderFactory<UserFederatedStorageProvider> {
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java
new file mode 100755
index 0000000..5475422
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.federated;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+import org.keycloak.storage.StorageProvider;
+import org.keycloak.storage.StorageProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserFederatedStorageProviderSpi implements Spi {
+
+ @Override
+ public boolean isInternal() {
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "userFederatedStorage";
+ }
+
+ @Override
+ public Class<? extends Provider> getProviderClass() {
+ return UserFederatedStorageProvider.class;
+ }
+
+ @Override
+ public Class<? extends ProviderFactory> getProviderFactoryClass() {
+ return UserFederatedStorageProviderFactory.class;
+ }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java
new file mode 100644
index 0000000..fef3cee
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserGroupMembershipFederatedStorage {
+ Set<GroupModel> getGroups(RealmModel realm, UserModel user);
+
+ void joinGroup(RealmModel realm,UserModel user, GroupModel group);
+
+ void leaveGroup(RealmModel realm,UserModel user, GroupModel group);
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java
new file mode 100644
index 0000000..1e14f78
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserRequiredActionsFederatedStorage {
+ Set<String> getRequiredActions(RealmModel realm, UserModel user);
+ void addRequiredAction(RealmModel realm,UserModel user, String action);
+ void removeRequiredAction(RealmModel realm,UserModel user, String action);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java
new file mode 100644
index 0000000..f63d9ec
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserRoleMappingsFederatedStorage {
+
+ void grantRole(RealmModel realm,UserModel user, RoleModel role);
+
+ Set<RoleModel> getRoleMappings(RealmModel realm,UserModel user);
+
+ void deleteRoleMapping(RealmModel realm, UserModel user, RoleModel role);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageId.java b/server-spi/src/main/java/org/keycloak/storage/StorageId.java
new file mode 100644
index 0000000..4ecac6e
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/StorageId.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage;
+
+import org.keycloak.models.UserModel;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class StorageId implements Serializable {
+ private String id;
+ private String providerId;
+ private String storageId;
+
+
+ public StorageId(String id) {
+ this.id = id;
+ if (!id.startsWith("f:")) {
+ storageId = id;
+ return;
+ }
+ int providerIndex = id.indexOf(':', 2);
+ providerId = id.substring(2, providerIndex);
+ storageId = id.substring(providerIndex + 1);
+
+ }
+
+ public static String resolveProviderId(UserModel user) {
+ return new StorageId(user.getId()).getProviderId();
+ }
+ public static boolean isLocalStorage(UserModel user) {
+ return new StorageId(user.getId()).getProviderId() == null;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getProviderId() {
+ return providerId;
+ }
+
+ public String getStorageId() {
+ return storageId;
+ }
+
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/StorageProvider.java
new file mode 100644
index 0000000..6746d5d
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/StorageProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.Provider;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface StorageProvider extends Provider {
+ StorageProviderModel getModel();
+
+ void preRemove(RealmModel realm);
+ void preRemove(RealmModel realm, GroupModel group);
+ void preRemove(RealmModel realm, RoleModel role);
+ void preRemove(RealmModel realm, StorageProviderModel model);
+
+}
+
diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java b/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java
new file mode 100755
index 0000000..3369da2
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.provider.ProviderFactory;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface StorageProviderFactory extends ProviderFactory<StorageProvider> {
+ boolean supports(Class<?> type);
+ /**
+ * called per Keycloak transaction.
+ *
+ * @param session
+ * @param model
+ * @return
+ */
+ StorageProvider getInstance(KeycloakSession session, StorageProviderModel model);
+
+ /**
+ * Config options to display in generic admin console page for federated
+ *
+ * @return
+ */
+ Set<String> getConfigurationOptions();
+
+ /**
+ * This is the name of the provider and will be showed in the admin console as an option.
+ *
+ * @return
+ */
+ @Override
+ String getId();
+
+ /**
+ * This method is never called and is only an artifact of ProviderFactory. Returning null with no implementation is recommended.
+ * @param session
+ * @return
+ */
+ @Override
+ StorageProvider create(KeycloakSession session);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java b/server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java
new file mode 100755
index 0000000..910df81
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Stored configuration of a User Storage provider instance.
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
+ */
+public class StorageProviderModel implements Serializable {
+
+ private String id;
+ private String providerName;
+ private Map<String, String> config = new HashMap<String, String>();
+ private int priority;
+ private String displayName;
+
+ public StorageProviderModel() {}
+
+ public StorageProviderModel(String id, String providerName, Map<String, String> config, int priority, String displayName) {
+ this.id = id;
+ this.providerName = providerName;
+ if (config != null) {
+ this.config.putAll(config);
+ }
+ this.priority = priority;
+ this.displayName = displayName;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getProviderName() {
+ return providerName;
+ }
+
+ public void setProviderName(String providerName) {
+ this.providerName = providerName;
+ }
+
+ public Map<String, String> getConfig() {
+ return config;
+ }
+
+ public void setConfig(Map<String, String> config) {
+ this.config = config;
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+
+ public void setPriority(int priority) {
+ this.priority = priority;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java b/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java
new file mode 100755
index 0000000..5d6d0da
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class StorageProviderSpi implements Spi {
+
+ @Override
+ public boolean isInternal() {
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "storage";
+ }
+
+ @Override
+ public Class<? extends Provider> getProviderClass() {
+ return StorageProvider.class;
+ }
+
+ @Override
+ public Class<? extends ProviderFactory> getProviderFactoryClass() {
+ return StorageProviderFactory.class;
+ }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
new file mode 100755
index 0000000..f740f76
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.CredentialValidationOutput;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserConsentModel;
+import org.keycloak.models.UserCredentialAuthenticationProvider;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValidatorProvider;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserLookupProvider;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserProvider;
+import org.keycloak.models.UserQueryProvider;
+import org.keycloak.models.UserUpdateProvider;
+import org.keycloak.models.utils.CredentialValidation;
+import org.keycloak.storage.federated.UserFederatedStorageProvider;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserStorageManager implements UserProvider {
+
+ private static final Logger logger = Logger.getLogger(UserStorageManager.class);
+
+ protected KeycloakSession session;
+
+ // Set of already validated/proxied federation users during this session. Key is user ID
+ private Map<String, UserModel> managedUsers = new HashMap<>();
+ private UserProvider localStorage = null;
+
+ public UserStorageManager(KeycloakSession session) {
+ this.session = session;
+ }
+
+ protected UserProvider localStorage() {
+ return session.userLocalStorage();
+ }
+
+ protected List<StorageProviderModel> getStorageProviders(RealmModel realm) {
+ return realm.getStorageProviders();
+ }
+
+ protected <T> T getFirstStorageProvider(RealmModel realm, Class<T> type) {
+ for (StorageProviderModel model : getStorageProviders(realm)) {
+ StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName());
+ if (factory.supports(type)) {
+ return type.cast(factory.getInstance(session, model));
+ }
+ }
+ return null;
+ }
+
+ protected <T> List<T> getStorageProviders(RealmModel realm, Class<T> type) {
+ List<T> list = new LinkedList<>();
+ for (StorageProviderModel model : getStorageProviders(realm)) {
+ StorageProviderFactory factory = (StorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName());
+ if (factory.supports(type)) {
+ list.add(type.cast(factory.getInstance(session, model)));
+ }
+
+
+ }
+ return list;
+ }
+
+
+ @Override
+ public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
+ UserUpdateProvider registry = getFirstStorageProvider(realm, UserUpdateProvider.class);
+ if (registry != null) {
+ return registry.addUser(realm, id, username, addDefaultRoles, addDefaultRequiredActions);
+ }
+ return localStorage().addUser(realm, id, username.toLowerCase(), addDefaultRoles, addDefaultRequiredActions);
+ }
+
+ @Override
+ public UserModel addUser(RealmModel realm, String username) {
+ UserUpdateProvider registry = getFirstStorageProvider(realm, UserUpdateProvider.class);
+ if (registry != null) {
+ return registry.addUser(realm, username);
+ }
+ return localStorage().addUser(realm, username.toLowerCase());
+ }
+
+ public StorageProvider getStorageProvider(StorageProviderModel model) {
+ StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName());
+ return factory.getInstance(session, model);
+ }
+
+ public StorageProvider getStorageProvider(RealmModel realm, String providerId) {
+ StorageProviderModel model = realm.getStorageProvider(providerId);
+ if (model == null) return null;
+ StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName());
+ if (factory == null) {
+ throw new ModelException("Could not find StorageProviderFactory for: " + model.getProviderName());
+ }
+ return factory.getInstance(session, model);
+ }
+
+ @Override
+ public boolean removeUser(RealmModel realm, UserModel user) {
+ StorageId storageId = new StorageId(user.getId());
+ if (storageId.getProviderId() == null) {
+ return localStorage().removeUser(realm, user);
+ }
+ UserUpdateProvider registry = (UserUpdateProvider)getStorageProvider(realm, storageId.getProviderId());
+ if (registry == null) {
+ throw new ModelException("Could not resolve StorageProvider: " + storageId.getProviderId());
+ }
+ return registry.removeUser(realm, user);
+
+ }
+
+ public UserFederatedStorageProvider getFederatedStorage() {
+ return session.userFederatedStorage();
+ }
+
+ @Override
+ public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) {
+ if (StorageId.isLocalStorage(user)) {
+ localStorage().addFederatedIdentity(realm, user, socialLink);
+ } else {
+ getFederatedStorage().addFederatedIdentity(realm, user, socialLink);
+ }
+ }
+
+ public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
+ if (StorageId.isLocalStorage(federatedUser)) {
+ localStorage().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel);
+
+ } else {
+ getFederatedStorage().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel);
+ }
+ }
+
+ @Override
+ public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
+ if (StorageId.isLocalStorage(user)) {
+ return localStorage().removeFederatedIdentity(realm, user, socialProvider);
+ } else {
+ return getFederatedStorage().removeFederatedIdentity(realm, user, socialProvider);
+ }
+ }
+
+ @Override
+ public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+ if (StorageId.isLocalStorage(user)) {
+ localStorage().addConsent(realm, user, consent);
+ } else {
+ getFederatedStorage().addConsent(realm, user, consent);
+ }
+
+ }
+
+ @Override
+ public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId) {
+ if (StorageId.isLocalStorage(user)) {
+ return localStorage().getConsentByClient(realm, user, clientInternalId);
+ } else {
+ return getFederatedStorage().getConsentByClient(realm, user, clientInternalId);
+ }
+ }
+
+ @Override
+ public List<UserConsentModel> getConsents(RealmModel realm, UserModel user) {
+ if (StorageId.isLocalStorage(user)) {
+ return localStorage().getConsents(realm, user);
+
+ } else {
+ return getFederatedStorage().getConsents(realm, user);
+ }
+ }
+
+ @Override
+ public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+ if (StorageId.isLocalStorage(user)) {
+ localStorage().updateConsent(realm, user, consent);
+ } else {
+ getFederatedStorage().updateConsent(realm, user, consent);
+ }
+
+ }
+
+ @Override
+ public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId) {
+ if (StorageId.isLocalStorage(user)) {
+ return localStorage().revokeConsentForClient(realm, user, clientInternalId);
+ } else {
+ return getFederatedStorage().revokeConsentForClient(realm, user, clientInternalId);
+ }
+ }
+
+ @Override
+ public UserModel getUserById(String id, RealmModel realm) {
+ StorageId storageId = new StorageId(id);
+ if (storageId.getProviderId() == null) {
+ return localStorage().getUserById(id, realm);
+ }
+ UserLookupProvider provider = (UserLookupProvider)getStorageProvider(realm, storageId.getProviderId());
+ return provider.getUserById(id, realm);
+ }
+
+ @Override
+ public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
+ return getGroupMembers(realm, group, -1, -1);
+ }
+
+ @Override
+ public UserModel getUserByUsername(String username, RealmModel realm) {
+ UserModel user = localStorage().getUserByUsername(username, realm);
+ if (user != null) return user;
+ for (UserLookupProvider provider : getStorageProviders(realm, UserLookupProvider.class)) {
+ user = provider.getUserByUsername(username, realm);
+ if (user != null) return user;
+ }
+ return null;
+ }
+
+ @Override
+ public UserModel getUserByEmail(String email, RealmModel realm) {
+ UserModel user = localStorage().getUserByEmail(email, realm);
+ if (user != null) return user;
+ for (UserLookupProvider provider : getStorageProviders(realm, UserLookupProvider.class)) {
+ user = provider.getUserByEmail(email, realm);
+ if (user != null) return user;
+ }
+ return null;
+ }
+
+ @Override
+ public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
+ UserModel user = localStorage().getUserByFederatedIdentity(socialLink, realm);
+ if (user != null) {
+ return user;
+ }
+ String id = getFederatedStorage().getUserByFederatedIdentity(socialLink, realm);
+ if (id != null) return getUserById(id, realm);
+ return null;
+ }
+
+ @Override
+ public UserModel getServiceAccount(ClientModel client) {
+ return localStorage().getServiceAccount(client);
+ }
+
+ @Override
+ public List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts) {
+ return getUsers(realm, 0, Integer.MAX_VALUE - 1, includeServiceAccounts);
+
+ }
+
+ @Override
+ public List<UserModel> getUsers(RealmModel realm) {
+ return getUsers(realm, false);
+ }
+
+ @Override
+ public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
+ return getUsers(realm, firstResult, maxResults, false);
+ }
+
+ @Override
+ public int getUsersCount(RealmModel realm) {
+ int size = localStorage().getUsersCount(realm);
+ for (UserQueryProvider provider : getStorageProviders(realm, UserQueryProvider.class)) {
+ size += provider.getUsersCount(realm);
+ }
+ return size;
+ }
+
+ interface PaginatedQuery {
+ List<UserModel> query(UserQueryProvider provider, int first, int max);
+ }
+
+ protected List<UserModel> query(PaginatedQuery pagedQuery, RealmModel realm, int firstResult, int maxResults) {
+ List<UserModel> results = new LinkedList<UserModel>();
+ if (maxResults == 0) return results;
+
+
+ List<UserQueryProvider> storageProviders = getStorageProviders(realm, UserQueryProvider.class);
+ LinkedList<UserQueryProvider> providers = new LinkedList<>();
+ if (providers.isEmpty()) {
+ return pagedQuery.query(localStorage(), firstResult, maxResults);
+ }
+ providers.add(localStorage());
+ providers.addAll(storageProviders);
+
+ int leftToRead = maxResults;
+ int leftToFirstResult = firstResult;
+
+ Iterator<UserQueryProvider> it = providers.iterator();
+ while (it.hasNext() && leftToRead != 0) {
+ UserQueryProvider provider = it.next();
+ boolean exhausted = false;
+ int index = 0;
+ if (leftToFirstResult > 0) {
+ do {
+ int toRead = Math.min(50, leftToFirstResult);
+ List<UserModel> tmp = pagedQuery.query(provider, index, toRead);
+ leftToFirstResult -= tmp.size();
+ index += tmp.size();
+ if (tmp.size() < toRead) {
+ exhausted = true;
+ break;
+ }
+ } while (leftToFirstResult > 0);
+ }
+ if (exhausted) continue;
+ List<UserModel> tmp = pagedQuery.query(provider, index, leftToRead);
+ results.addAll(tmp);
+ if (leftToRead > 0) leftToRead -= tmp.size();
+ }
+ return results;
+ }
+
+ @Override
+ public List<UserModel> getUsers(final RealmModel realm, int firstResult, int maxResults, final boolean includeServiceAccounts) {
+ return query(new PaginatedQuery() {
+ @Override
+ public List<UserModel> query(UserQueryProvider provider, int first, int max) {
+ if (provider instanceof UserProvider) { // it is local storage
+ return ((UserProvider)provider).getUsers(realm, first, max, includeServiceAccounts);
+ }
+ return provider.getUsers(realm, first, max);
+ }
+ }, realm, firstResult, maxResults);
+ }
+
+ @Override
+ public List<UserModel> searchForUser(String search, RealmModel realm) {
+ return searchForUser(search, realm, 0, Integer.MAX_VALUE - 1);
+ }
+
+ @Override
+ public List<UserModel> searchForUser(final String search, final RealmModel realm, int firstResult, int maxResults) {
+ return query(new PaginatedQuery() {
+ @Override
+ public List<UserModel> query(UserQueryProvider provider, int first, int max) {
+ return provider.searchForUser(search, realm, first, max);
+ }
+ }, realm, firstResult, maxResults);
+ }
+
+ @Override
+ public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm) {
+ return searchForUserByAttributes(attributes, realm, 0, Integer.MAX_VALUE - 1);
+ }
+
+ @Override
+ public List<UserModel> searchForUserByAttributes(final Map<String, String> attributes, final RealmModel realm, int firstResult, int maxResults) {
+ return query(new PaginatedQuery() {
+ @Override
+ public List<UserModel> query(UserQueryProvider provider, int first, int max) {
+ return provider.searchForUserByAttributes(attributes, realm, first, max);
+ }
+ }, realm, firstResult, maxResults);
+ }
+
+ @Override
+ public List<UserModel> searchForUserByUserAttribute(final String attrName, final String attrValue, RealmModel realm) {
+ return query(new PaginatedQuery() {
+ @Override
+ public List<UserModel> query(UserQueryProvider provider, int first, int max) {
+ return provider.searchForUserByUserAttribute(attrName, attrValue, realm);
+ }
+ }, realm,0, Integer.MAX_VALUE - 1);
+ }
+
+ @Override
+ public Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm) {
+ if (user == null) throw new IllegalStateException("Federated user no longer valid");
+ Set<FederatedIdentityModel> set = new HashSet<>();
+ if (StorageId.isLocalStorage(user)) {
+ set.addAll(localStorage().getFederatedIdentities(user, realm));
+ }
+ set.addAll(getFederatedStorage().getFederatedIdentities(user, realm));
+ return set;
+ }
+
+ @Override
+ public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
+ if (user == null) throw new IllegalStateException("Federated user no longer valid");
+ if (StorageId.isLocalStorage(user)) {
+ FederatedIdentityModel model = localStorage().getFederatedIdentity(user, socialProvider, realm);
+ if (model != null) return model;
+ }
+ return getFederatedStorage().getFederatedIdentity(user, socialProvider, realm);
+ }
+
+ @Override
+ public void grantToAllUsers(RealmModel realm, RoleModel role) {
+ // not federation-aware for now
+ List<UserUpdateProvider> storageProviders = getStorageProviders(realm, UserUpdateProvider.class);
+ LinkedList<UserUpdateProvider> providers = new LinkedList<>();
+ providers.add(localStorage());
+ providers.addAll(storageProviders);
+ for (UserUpdateProvider provider : providers) {
+ provider.grantToAllUsers(realm, role);
+ }
+ }
+
+ @Override
+ public List<UserModel> getGroupMembers(final RealmModel realm, final GroupModel group, int firstResult, int maxResults) {
+ return query(new PaginatedQuery() {
+ @Override
+ public List<UserModel> query(UserQueryProvider provider, int first, int max) {
+ return provider.getGroupMembers(realm, group, first, max);
+ }
+ }, realm, firstResult, maxResults);
+ }
+
+
+ @Override
+ public void preRemove(RealmModel realm) {
+ localStorage().preRemove(realm);
+ getFederatedStorage().preRemove(realm);
+ for (StorageProvider provider : getStorageProviders(realm, StorageProvider.class)) {
+ provider.preRemove(realm);
+ }
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, UserFederationProviderModel model) {
+ getFederatedStorage().preRemove(realm, model);
+ localStorage().preRemove(realm, model);
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, GroupModel group) {
+ localStorage().preRemove(realm, group);
+ getFederatedStorage().preRemove(realm, group);
+ for (StorageProvider provider : getStorageProviders(realm, StorageProvider.class)) {
+ provider.preRemove(realm, group);
+ }
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, RoleModel role) {
+ localStorage().preRemove(realm, role);
+ getFederatedStorage().preRemove(realm, role);
+ for (StorageProvider provider : getStorageProviders(realm, StorageProvider.class)) {
+ provider.preRemove(realm, role);
+ }
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, ClientModel client) {
+ localStorage().preRemove(realm, client);
+ getFederatedStorage().preRemove(realm, client);
+
+ }
+
+ @Override
+ public void preRemove(ProtocolMapperModel protocolMapper) {
+ localStorage().preRemove(protocolMapper);
+ getFederatedStorage().preRemove(protocolMapper);
+ }
+
+ @Override
+ public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
+ if (StorageId.isLocalStorage(user)) {
+ return localStorage().validCredentials(session, realm, user, input);
+ }
+ // make sure we hit the cache here!
+ List<UserCredentialValueModel> userCreds = user.getCredentialsDirectly();
+
+ LinkedList<UserCredentialModel> toValidate = new LinkedList<>();
+ toValidate.addAll(input);
+ Iterator<UserCredentialModel> it = toValidate.iterator();
+ boolean failedStoredCredential = false;
+ // we allow for multiple credentials of same type, i.e. multiple OTP devices
+ while (it.hasNext()) {
+ UserCredentialModel cred = it.next();
+ boolean credValidated = false;
+ for (UserCredentialValueModel userCred : userCreds) {
+ if (!userCred.getType().equals(cred.getType())) continue;
+ if (CredentialValidation.validCredential(session, realm, user, cred)) {
+ credValidated = true;
+ break;
+ } else {
+ failedStoredCredential = true;
+ }
+ }
+ if (credValidated) {
+ it.remove();
+ } else if (failedStoredCredential) {
+ return false;
+ }
+ }
+
+ if (toValidate.isEmpty()) return true;
+
+ StorageProvider provider = getStorageProvider(realm, StorageId.resolveProviderId(user));
+ if (!(provider instanceof UserCredentialValidatorProvider)) {
+ return false;
+ }
+ return ((UserCredentialValidatorProvider)provider).validCredentials(session, realm, user, toValidate);
+ }
+
+ @Override
+ public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) {
+ return validCredentials(session, realm, user, Arrays.asList(input));
+ }
+
+ @Override
+ public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
+ List<UserCredentialAuthenticationProvider> providers = getStorageProviders(realm, UserCredentialAuthenticationProvider.class);
+ if (providers.isEmpty()) return CredentialValidationOutput.failed();
+
+ CredentialValidationOutput result = null;
+ for (UserCredentialModel cred : input) {
+ UserCredentialAuthenticationProvider providerSupportingCreds = null;
+
+ // Find first provider, which supports required credential type
+ for (UserCredentialAuthenticationProvider provider : providers) {
+ if (provider.getSupportedCredentialAuthenticationTypes().contains(cred.getType())) {
+ providerSupportingCreds = provider;
+ break;
+ }
+ }
+
+ if (providerSupportingCreds == null) {
+ logger.warn("Don't have provider supporting credentials of type " + cred.getType());
+ return CredentialValidationOutput.failed();
+ }
+
+ logger.debug("Found provider [" + providerSupportingCreds + "] supporting credentials of type " + cred.getType());
+ CredentialValidationOutput currentResult = providerSupportingCreds.validCredential(session, realm, cred);
+ result = (result == null) ? currentResult : result.merge(currentResult);
+ }
+
+ // For now, validCredentials(realm, input) is not supported for local userProviders
+ return (result != null) ? result : CredentialValidationOutput.failed();
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, StorageProviderModel link) {
+
+ }
+
+ @Override
+ public void close() {
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java
new file mode 100644
index 0000000..58695c3
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage;
+
+import org.keycloak.models.RealmModel;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserStorageProvider {
+ void preRemove(RealmModel realm);
+
+}
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 9a84898..c5a5ebb 100755
--- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -16,6 +16,8 @@
#
org.keycloak.models.UserFederationSpi
+org.keycloak.storage.StorageProviderSpi
+org.keycloak.storage.federated.UserFederatedStorageProviderSpi
org.keycloak.mappers.UserFederationMapperSpi
org.keycloak.models.RealmSpi
org.keycloak.models.UserSessionSpi
diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
index 5feb31c..7d19e83 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
@@ -497,7 +497,7 @@ public class ResourceServerService {
umaProtectionRole = client.addRole(Constants.AUTHZ_UMA_PROTECTION);
}
- UserModel serviceAccount = this.session.users().getUserByServiceAccountClient(client);
+ UserModel serviceAccount = this.session.users().getServiceAccount(client);
if (!serviceAccount.hasRole(umaProtectionRole)) {
serviceAccount.grantRole(umaProtectionRole);
diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
index 9227854..5ddd431 100644
--- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
+++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
@@ -139,10 +139,10 @@ public class KeycloakIdentity implements Identity {
if (this.accessToken.getClientSession() != null) {
ClientSessionModel clientSession = this.keycloakSession.sessions().getClientSession(this.accessToken.getClientSession());
- clientUser = this.keycloakSession.users().getUserByServiceAccountClient(clientSession.getClient());
+ clientUser = this.keycloakSession.users().getServiceAccount(clientSession.getClient());
} else if (this.accessToken.getIssuedFor() != null) {
ClientModel clientModel = this.keycloakSession.realms().getClientById(this.accessToken.getIssuedFor(), this.realm);
- clientUser = this.keycloakSession.users().getUserByServiceAccountClient(clientModel);
+ clientUser = this.keycloakSession.users().getServiceAccount(clientModel);
}
diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
index cf0c5ca..01f1186 100755
--- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -319,7 +319,7 @@ public class ExportUtils {
userRep.setFederationLink(user.getFederationLink());
// Grants
- List<UserConsentModel> consents = user.getConsents();
+ List<UserConsentModel> consents = session.users().getConsents(realm, user);
LinkedList<UserConsentRepresentation> consentReps = new LinkedList<UserConsentRepresentation>();
for (UserConsentModel consent : consents) {
UserConsentRepresentation consentRep = ModelToRepresentation.toRepresentation(consent);
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java
index 0b8ec67..f845903 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java
@@ -64,7 +64,7 @@ public class ApplicationsBean {
MultivaluedHashMap<String, ClientRoleEntry> resourceRolesGranted = new MultivaluedHashMap<String, ClientRoleEntry>();
List<String> claimsGranted = new LinkedList<String>();
if (client.isConsentRequired()) {
- UserConsentModel consent = user.getConsentByClient(client.getId());
+ UserConsentModel consent = session.users().getConsentByClient(realm, user, client.getId());
if (consent != null) {
processRoles(consent.getGrantedRoles(), realmRolesGranted, resourceRolesGranted);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index 4f17cdd..f7d68dc 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -420,13 +420,13 @@ public class TokenEndpoint {
throw new ErrorResponseException("unauthorized_client", "Client not enabled to retrieve service account", Response.Status.UNAUTHORIZED);
}
- UserModel clientUser = session.users().getUserByServiceAccountClient(client);
+ UserModel clientUser = session.users().getServiceAccount(client);
if (clientUser == null || client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER) == null) {
// May need to handle bootstrap here as well
logger.debugf("Service account user for client '%s' not found or default protocol mapper for service account not found. Creating now", client.getClientId());
new ClientManager(new RealmManager(session)).enableServiceAccount(client);
- clientUser = session.users().getUserByServiceAccountClient(client);
+ clientUser = session.users().getServiceAccount(client);
}
String clientUsername = clientUser.getUsername();
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
index 22f84d4..8eb2a56 100644
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
@@ -22,6 +22,8 @@ import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.scripting.ScriptingProvider;
+import org.keycloak.storage.UserStorageManager;
+import org.keycloak.storage.federated.UserFederatedStorageProvider;
import java.util.*;
@@ -36,9 +38,11 @@ public class DefaultKeycloakSession implements KeycloakSession {
private final DefaultKeycloakTransactionManager transactionManager;
private RealmProvider model;
private UserProvider userModel;
+ private UserStorageManager userStorageManager;
private ScriptingProvider scriptingProvider;
private UserSessionProvider sessionProvider;
private UserFederationManager federationManager;
+ private UserFederatedStorageProvider userFederatedStorageProvider;
private KeycloakContext context;
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
@@ -87,6 +91,25 @@ public class DefaultKeycloakSession implements KeycloakSession {
}
@Override
+ public UserFederatedStorageProvider userFederatedStorage() {
+ if (userFederatedStorageProvider == null) {
+ userFederatedStorageProvider = getProvider(UserFederatedStorageProvider.class);
+ }
+ return userFederatedStorageProvider;
+ }
+
+ @Override
+ public UserProvider userLocalStorage() {
+ return getProvider(UserProvider.class);
+ }
+
+ @Override
+ public UserProvider userStorageManager() {
+ if (userStorageManager == null) userStorageManager = new UserStorageManager(this);
+ return userStorageManager;
+ }
+
+ @Override
public UserProvider userStorage() {
if (userModel == null) {
userModel = getUserProvider();
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
index 172de6e..b56af8d 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
@@ -154,7 +154,11 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
@Override
public <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String id) {
- return factoriesMap.get(clazz).get(id);
+ Map<String, ProviderFactory> map = factoriesMap.get(clazz);
+ if (map == null) {
+ return null;
+ }
+ return map.get(id);
}
@Override
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 32f249f..e210626 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -457,7 +457,7 @@ public class AuthenticationManager {
if (client.isConsentRequired()) {
- UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
+ UserConsentModel grantedConsent = session.users().getConsentByClient(realm, user, client.getId());
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
for (RoleModel r : accessCode.getRequestedRoles()) {
@@ -511,7 +511,7 @@ public class AuthenticationManager {
if (client.isConsentRequired()) {
- UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
+ UserConsentModel grantedConsent = session.users().getConsentByClient(realm, user, client.getId());
List<RoleModel> realmRoles = new LinkedList<>();
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<>();
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientManager.java b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
index 607123e..7bdf5b1 100644
--- a/services/src/main/java/org/keycloak/services/managers/ClientManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
@@ -103,7 +103,7 @@ public class ClientManager {
sessionsPersister.onClientRemoved(realm, client);
}
- UserModel serviceAccountUser = realmManager.getSession().users().getUserByServiceAccountClient(client);
+ UserModel serviceAccountUser = realmManager.getSession().users().getServiceAccount(client);
if (serviceAccountUser != null) {
new UserManager(realmManager.getSession()).removeUser(realm, serviceAccountUser);
}
@@ -150,7 +150,7 @@ public class ClientManager {
client.setServiceAccountsEnabled(true);
// Add dedicated user for this service account
- if (realmManager.getSession().users().getUserByServiceAccountClient(client) == null) {
+ if (realmManager.getSession().users().getServiceAccount(client) == null) {
String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + client.getClientId();
logger.debugf("Creating service account user '%s'", username);
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 4907707..251cd8e 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -500,7 +500,7 @@ public class AccountService extends AbstractSecuredLocalService {
// Revoke grant in UserModel
UserModel user = auth.getUser();
- user.revokeConsentForClient(client.getId());
+ session.users().revokeConsentForClient(realm, user, client.getId());
new UserSessionManager(session).revokeOfflineToken(user, client);
// Logout clientSessions for this user and client
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index 18023e8..94d4821 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -315,11 +315,11 @@ public class ClientResource {
throw new NotFoundException("Could not find client");
}
- UserModel user = session.users().getUserByServiceAccountClient(client);
+ UserModel user = session.users().getServiceAccount(client);
if (user == null) {
if (client.isServiceAccountsEnabled()) {
new ClientManager(new RealmManager(session)).enableServiceAccount(client);
- user = session.users().getUserByServiceAccountClient(client);
+ user = session.users().getServiceAccount(client);
} else {
throw new BadRequestException("Service account not enabled for the client '" + client.getClientId() + "'");
}
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 1b4586b..5788b42 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
@@ -523,7 +523,7 @@ public class UsersResource {
Set<ClientModel> offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user);
for (ClientModel client : realm.getClients()) {
- UserConsentModel consent = user.getConsentByClient(client.getId());
+ UserConsentModel consent = session.users().getConsentByClient(realm, user, client.getId());
boolean hasOfflineToken = offlineClients.contains(client);
if (consent == null && !hasOfflineToken) {
@@ -572,7 +572,7 @@ public class UsersResource {
}
ClientModel client = realm.getClientByClientId(clientId);
- boolean revokedConsent = user.revokeConsentForClient(client.getId());
+ boolean revokedConsent = session.users().revokeConsentForClient(realm, user, client.getId());
boolean revokedOfflineToken = new UserSessionManager(session).revokeOfflineToken(user, client);
if (revokedConsent) {
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index fab5815..cf8791e 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -667,10 +667,10 @@ public class LoginActionsService {
return response;
}
- UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
+ UserConsentModel grantedConsent = session.users().getConsentByClient(realm, user, client.getId());
if (grantedConsent == null) {
grantedConsent = new UserConsentModel(client);
- user.addConsent(grantedConsent);
+ session.users().addConsent(realm, user, grantedConsent);
}
for (RoleModel role : accessCode.getRequestedRoles()) {
grantedConsent.addGrantedRole(role);
@@ -680,7 +680,7 @@ public class LoginActionsService {
grantedConsent.addGrantedProtocolMapper(protocolMapper);
}
}
- user.updateConsent(grantedConsent);
+ session.users().updateConsent(realm, user, grantedConsent);
event.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED);
event.success();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
index 1af21f5..7683d0c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
@@ -72,7 +72,9 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
@Test
public void testDisabledUser() {
+ KeycloakSession session = brokerServerRule.startSession();
setUpdateProfileFirstLogin(session.realms().getRealmByName("realm-with-broker"), IdentityProviderRepresentation.UPFLM_OFF);
+ brokerServerRule.stopSession(session, true);
driver.navigate().to("http://localhost:8081/test-app");
loginPage.clickSocial(getProviderId());
@@ -81,7 +83,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
driver.navigate().to("http://localhost:8081/test-app/logout");
try {
- KeycloakSession session = brokerServerRule.startSession();
+ session = brokerServerRule.startSession();
session.users().getUserByUsername("test-user", session.realms().getRealmByName("realm-with-broker")).setEnabled(false);
brokerServerRule.stopSession(session, true);
@@ -93,7 +95,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
assertTrue(errorPage.isCurrent());
assertEquals("Account is disabled, contact admin.", errorPage.getError());
} finally {
- KeycloakSession session = brokerServerRule.startSession();
+ session = brokerServerRule.startSession();
session.users().getUserByUsername("test-user", session.realms().getRealmByName("realm-with-broker")).setEnabled(true);
brokerServerRule.stopSession(session, true);
}
@@ -101,7 +103,9 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
@Test
public void testTemporarilyDisabledUser() {
+ KeycloakSession session = brokerServerRule.startSession();
setUpdateProfileFirstLogin(session.realms().getRealmByName("realm-with-broker"), IdentityProviderRepresentation.UPFLM_OFF);
+ brokerServerRule.stopSession(session, true);
driver.navigate().to("http://localhost:8081/test-app");
loginPage.clickSocial(getProviderId());
@@ -109,7 +113,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
driver.navigate().to("http://localhost:8081/test-app/logout");
try {
- KeycloakSession session = brokerServerRule.startSession();
+ session = brokerServerRule.startSession();
RealmModel brokerRealm = session.realms().getRealmByName("realm-with-broker");
brokerRealm.setBruteForceProtected(true);
brokerRealm.setFailureFactor(2);
@@ -129,7 +133,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
assertTrue(errorPage.isCurrent());
assertEquals("Account is disabled, contact admin.", errorPage.getError());
} finally {
- KeycloakSession session = brokerServerRule.startSession();
+ session = brokerServerRule.startSession();
RealmModel brokerRealm = session.realms().getRealmByName("realm-with-broker");
brokerRealm.setBruteForceProtected(false);
brokerRealm.setFailureFactor(0);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
index 28663ff..d79d85f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
@@ -170,4 +170,14 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP
public void testAccountManagementLinkIdentity() {
super.testAccountManagementLinkIdentity();
}
+
+ @Test
+ public void testWithLinkedFederationProvider() throws Exception {
+ super.testWithLinkedFederationProvider();
+ }
+
+ @Test
+ public void testAccountManagementLinkedIdentityAlreadyExists() {
+ super.testAccountManagementLinkedIdentityAlreadyExists();
+ }
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
index 8beaffb..dedc8d1 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
@@ -357,15 +357,15 @@ public class ImportTest extends AbstractModelTest {
// Test user consents
admin = session.users().getUserByUsername("admin", realm);
- Assert.assertEquals(2, admin.getConsents().size());
+ Assert.assertEquals(2, session.users().getConsents(realm, admin).size());
- UserConsentModel appAdminConsent = admin.getConsentByClient(application.getId());
+ UserConsentModel appAdminConsent = session.users().getConsentByClient(realm, admin, application.getId());
Assert.assertEquals(2, appAdminConsent.getGrantedRoles().size());
Assert.assertTrue(appAdminConsent.getGrantedProtocolMappers() == null || appAdminConsent.getGrantedProtocolMappers().isEmpty());
Assert.assertTrue(appAdminConsent.isRoleGranted(realm.getRole("admin")));
Assert.assertTrue(appAdminConsent.isRoleGranted(application.getRole("app-admin")));
- UserConsentModel otherAppAdminConsent = admin.getConsentByClient(otherApp.getId());
+ UserConsentModel otherAppAdminConsent = session.users().getConsentByClient(realm, admin, otherApp.getId());
Assert.assertEquals(1, otherAppAdminConsent.getGrantedRoles().size());
Assert.assertEquals(1, otherAppAdminConsent.getGrantedProtocolMappers().size());
Assert.assertTrue(otherAppAdminConsent.isRoleGranted(realm.getRole("admin")));
@@ -382,8 +382,8 @@ public class ImportTest extends AbstractModelTest {
// Test service accounts
Assert.assertFalse(application.isServiceAccountsEnabled());
Assert.assertTrue(otherApp.isServiceAccountsEnabled());
- Assert.assertNull(session.users().getUserByServiceAccountClient(application));
- UserModel linked = session.users().getUserByServiceAccountClient(otherApp);
+ Assert.assertNull(session.users().getServiceAccount(application));
+ UserModel linked = session.users().getServiceAccount(otherApp);
Assert.assertNotNull(linked);
Assert.assertEquals("my-service-user", linked.getUsername());
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java
index 0a03398..2f71b23 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java
@@ -67,7 +67,7 @@ public class UserConsentModelTest extends AbstractModelTest {
johnFooGrant.addGrantedRole(realmRole);
johnFooGrant.addGrantedRole(barClientRole);
johnFooGrant.addGrantedProtocolMapper(fooMapper);
- john.addConsent(johnFooGrant);
+ realmManager.getSession().users().addConsent(realm, john, johnFooGrant);
UserConsentModel johnBarGrant = new UserConsentModel(barClient);
johnBarGrant.addGrantedProtocolMapper(barMapper);
@@ -75,17 +75,17 @@ public class UserConsentModelTest extends AbstractModelTest {
// Update should fail as grant doesn't yet exists
try {
- john.updateConsent(johnBarGrant);
+ realmManager.getSession().users().updateConsent(realm, john, johnBarGrant);
Assert.fail("Not expected to end here");
} catch (ModelException expected) {
}
- john.addConsent(johnBarGrant);
+ realmManager.getSession().users().addConsent(realm, john, johnBarGrant);
UserConsentModel maryFooGrant = new UserConsentModel(fooClient);
maryFooGrant.addGrantedRole(realmRole);
maryFooGrant.addGrantedProtocolMapper(fooMapper);
- mary.addConsent(maryFooGrant);
+ realmManager.getSession().users().addConsent(realm, mary, maryFooGrant);
commit();
}
@@ -99,27 +99,27 @@ public class UserConsentModelTest extends AbstractModelTest {
UserModel john = session.users().getUserByUsername("john", realm);
UserModel mary = session.users().getUserByUsername("mary", realm);
- UserConsentModel johnFooConsent = john.getConsentByClient(fooClient.getId());
+ UserConsentModel johnFooConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId());
Assert.assertEquals(johnFooConsent.getGrantedRoles().size(), 2);
Assert.assertEquals(johnFooConsent.getGrantedProtocolMappers().size(), 1);
Assert.assertTrue(isRoleGranted(realm, "realm-role", johnFooConsent));
Assert.assertTrue(isRoleGranted(barClient, "bar-client-role", johnFooConsent));
Assert.assertTrue(isMapperGranted(fooClient, "foo", johnFooConsent));
- UserConsentModel johnBarConsent = john.getConsentByClient(barClient.getId());
+ UserConsentModel johnBarConsent = realmManager.getSession().users().getConsentByClient(realm, john, barClient.getId());
Assert.assertEquals(johnBarConsent.getGrantedRoles().size(), 1);
Assert.assertEquals(johnBarConsent.getGrantedProtocolMappers().size(), 1);
Assert.assertTrue(isRoleGranted(realm, "realm-role", johnBarConsent));
Assert.assertTrue(isMapperGranted(barClient, "bar", johnBarConsent));
- UserConsentModel maryConsent = mary.getConsentByClient(fooClient.getId());
+ UserConsentModel maryConsent = realmManager.getSession().users().getConsentByClient(realm, mary, fooClient.getId());
Assert.assertEquals(maryConsent.getGrantedRoles().size(), 1);
Assert.assertEquals(maryConsent.getGrantedProtocolMappers().size(), 1);
Assert.assertTrue(isRoleGranted(realm, "realm-role", maryConsent));
Assert.assertFalse(isRoleGranted(barClient, "bar-client-role", maryConsent));
Assert.assertTrue(isMapperGranted(fooClient, "foo", maryConsent));
- Assert.assertNull(mary.getConsentByClient(barClient.getId()));
+ Assert.assertNull(realmManager.getSession().users().getConsentByClient(realm, mary, barClient.getId()));
}
@Test
@@ -130,10 +130,10 @@ public class UserConsentModelTest extends AbstractModelTest {
UserModel john = session.users().getUserByUsername("john", realm);
UserModel mary = session.users().getUserByUsername("mary", realm);
- List<UserConsentModel> johnConsents = john.getConsents();
+ List<UserConsentModel> johnConsents = realmManager.getSession().users().getConsents(realm, john);
Assert.assertEquals(2, johnConsents.size());
- List<UserConsentModel> maryConsents = mary.getConsents();
+ List<UserConsentModel> maryConsents = realmManager.getSession().users().getConsents(realm, mary);
Assert.assertEquals(1, maryConsents.size());
UserConsentModel maryConsent = maryConsents.get(0);
Assert.assertEquals(maryConsent.getClient().getId(), fooClient.getId());
@@ -149,7 +149,7 @@ public class UserConsentModelTest extends AbstractModelTest {
ClientModel fooClient = realm.getClientByClientId("foo-client");
UserModel john = session.users().getUserByUsername("john", realm);
- UserConsentModel johnConsent = john.getConsentByClient(fooClient.getId());
+ UserConsentModel johnConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId());
// Remove foo protocol mapper from johnConsent
ProtocolMapperModel protMapperModel = fooClient.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "foo");
@@ -162,14 +162,14 @@ public class UserConsentModelTest extends AbstractModelTest {
RoleModel newRealmRole = realm.addRole("new-realm-role");
johnConsent.addGrantedRole(newRealmRole);
- john.updateConsent(johnConsent);
+ realmManager.getSession().users().updateConsent(realm, john, johnConsent);
commit();
realm = realmManager.getRealm("original");
fooClient = realm.getClientByClientId("foo-client");
john = session.users().getUserByUsername("john", realm);
- johnConsent = john.getConsentByClient(fooClient.getId());
+ johnConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId());
Assert.assertEquals(johnConsent.getGrantedRoles().size(), 2);
Assert.assertEquals(johnConsent.getGrantedProtocolMappers().size(), 0);
@@ -184,13 +184,13 @@ public class UserConsentModelTest extends AbstractModelTest {
ClientModel fooClient = realm.getClientByClientId("foo-client");
UserModel john = session.users().getUserByUsername("john", realm);
- john.revokeConsentForClient(fooClient.getId());
+ realmManager.getSession().users().revokeConsentForClient(realm, john, fooClient.getId());
commit();
realm = realmManager.getRealm("original");
john = session.users().getUserByUsername("john", realm);
- Assert.assertNull(john.getConsentByClient(fooClient.getId()));
+ Assert.assertNull(realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId()));
}
@Test
@@ -213,7 +213,7 @@ public class UserConsentModelTest extends AbstractModelTest {
realm = realmManager.getRealm("original");
fooClient = realm.getClientByClientId("foo-client");
UserModel john = session.users().getUserByUsername("john", realm);
- UserConsentModel johnConsent = john.getConsentByClient(fooClient.getId());
+ UserConsentModel johnConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId());
Assert.assertEquals(johnConsent.getGrantedRoles().size(), 2);
Assert.assertEquals(johnConsent.getGrantedProtocolMappers().size(), 0);
@@ -232,7 +232,7 @@ public class UserConsentModelTest extends AbstractModelTest {
ClientModel fooClient = realm.getClientByClientId("foo-client");
ClientModel barClient = realm.getClientByClientId("bar-client");
UserModel john = session.users().getUserByUsername("john", realm);
- UserConsentModel johnConsent = john.getConsentByClient(fooClient.getId());
+ UserConsentModel johnConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId());
Assert.assertEquals(johnConsent.getGrantedRoles().size(), 1);
Assert.assertEquals(johnConsent.getGrantedProtocolMappers().size(), 1);
@@ -254,13 +254,13 @@ public class UserConsentModelTest extends AbstractModelTest {
UserModel john = session.users().getUserByUsername("john", realm);
- UserConsentModel johnFooConsent = john.getConsentByClient(fooClient.getId());
+ UserConsentModel johnFooConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId());
Assert.assertEquals(johnFooConsent.getGrantedRoles().size(), 1);
Assert.assertEquals(johnFooConsent.getGrantedProtocolMappers().size(), 1);
Assert.assertTrue(isRoleGranted(realm, "realm-role", johnFooConsent));
Assert.assertTrue(isMapperGranted(fooClient, "foo", johnFooConsent));
- Assert.assertNull(john.getConsentByClient(barClient.getId()));
+ Assert.assertNull(realmManager.getSession().users().getConsentByClient(realm, john, barClient.getId()));
}
private boolean isRoleGranted(RoleContainerModel roleContainer, String roleName, UserConsentModel consentModel) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
index 0841c1f..83ea5ad 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
@@ -271,7 +271,7 @@ public class UserModelTest extends AbstractModelTest {
user2.setLastName("Doe");
// Search
- Assert.assertNull(session.users().getUserByServiceAccountClient(client));
+ Assert.assertNull(session.users().getServiceAccount(client));
List<UserModel> users = session.users().searchForUser("John Doe", realm);
Assert.assertEquals(2, users.size());
Assert.assertTrue(users.contains(user1));
@@ -284,7 +284,7 @@ public class UserModelTest extends AbstractModelTest {
// Search and assert service account user not found
realm = realmManager.getRealmByName("original");
- UserModel searched = session.users().getUserByServiceAccountClient(client);
+ UserModel searched = session.users().getServiceAccount(client);
Assert.assertEquals(searched, user1);
users = session.users().searchForUser("John Doe", realm);
Assert.assertEquals(1, users.size());
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
index e484645..bbc9f27 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
@@ -635,7 +635,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
public UserRepresentation getUserByServiceAccountClient(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId) {
RealmModel realm = getRealmByName(realmName);
ClientModel client = realm.getClientByClientId(clientId);
- UserModel user = session.users().getUserByServiceAccountClient(client);
+ UserModel user = session.users().getServiceAccount(client);
if (user == null) return null;
return ModelToRepresentation.toRepresentation(user);
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
index c37291d..c42d254 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
@@ -23,6 +23,10 @@
"provider": "${keycloak.user.provider:jpa}"
},
+ "userFederatedStorage": {
+ "provider": "${keycloak.userFederatedStorage.provider:jpa}"
+ },
+
"userSessionPersister": {
"provider": "${keycloak.userSessionPersister.provider:jpa}"
},