keycloak-memoizeit
Changes
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/FederatedUserGroupMembershipEntity.java 3(+2 -1)
model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java 2(+1 -1)
model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java 1(+1 -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 3(+2 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java 12(+8 -4)
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 e41913d..d272a5a 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/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/storage/jpa/entity/BrokerLinkEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java
index 90ca5f1..a6b05cd 100755
--- 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
@@ -21,6 +21,7 @@ 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;
@@ -32,7 +33,7 @@ import java.io.Serializable;
*/
@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"),
+ @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"),
@@ -45,6 +46,7 @@ import java.io.Serializable;
public class BrokerLinkEntity {
@Id
+ @Column(name = "USER_ID")
private String userId;
@Id
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
index 18aab7f..8066310 100755
--- 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
@@ -39,12 +39,12 @@ import java.util.Collection;
@UniqueConstraint(columnNames = {"USER_ID", "CLIENT_ID"})
})
@NamedQueries({
- @NamedQuery(name="userFederatedConsentByUserAndClient", query="select consent from UserConsentEntity consent where consent.userId = :userId and consent.clientId = :clientId"),
- @NamedQuery(name="userFederatedConsentsByUser", query="select consent from UserConsentEntity consent where consent.userId = :userId"),
- @NamedQuery(name="deleteFederatedUserConsentsByRealm", query="delete from UserConsentEntity consent where consent.realmId=:realmId"),
+ @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 UserConsentEntity consent where consent.userId = :userId and consent.realmId = :realmId"),
- @NamedQuery(name="deleteFederatedUserConsentsByClient", query="delete from UserConsentEntity consent where consent.clientId = :clientId"),
+ @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 {
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
index cbbdf4e..ecfaa49 100755
--- 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
@@ -35,7 +35,7 @@ import java.io.Serializable;
@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.user from FederatedUserGroupMembershipEntity g where g.groupId = :groupId"),
+ @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"),
@@ -50,6 +50,7 @@ import java.io.Serializable;
public class FederatedUserGroupMembershipEntity {
@Id
+ @Column(name = "USER_ID")
protected String userId;
@Id
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
index c39a311..f555628 100755
--- 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
@@ -36,7 +36,7 @@ import java.io.Serializable;
* @version $Revision: 1 $
*/
@NamedQueries({
- @NamedQuery(name="getFederatedUserRequiredActionsByUser", query="select action from FederatedUserRequiredActionEntity action where action.userId = :userId and attr.realmId=:realmId"),
+ @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)")
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
index 78ef299..719fce6 100755
--- 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
@@ -48,6 +48,7 @@ import java.io.Serializable;
public class FederatedUserRoleMappingEntity {
@Id
+ @Column(name = "USER_ID")
protected String userId;
@Id
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
index b206cc5..4ec58bd 100644
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
@@ -149,8 +149,8 @@ public class JpaUserFederatedStorageProvider implements
@Override
public String getUserByFederatedIdentity(FederatedIdentityModel link, RealmModel realm) {
- TypedQuery<String> query = em.createNamedQuery("findBrokerLinkByUserAndProvider", String.class)
- .setParameter("realmid", realm.getId())
+ 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();
@@ -180,15 +180,16 @@ public class JpaUserFederatedStorageProvider implements
@Override
public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
- BrokerLinkEntity entity = getBrokerLinkEntity(user, socialProvider);
+ BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, socialProvider);
if (entity == null) return false;
em.remove(entity);
return true;
}
- private BrokerLinkEntity getBrokerLinkEntity(UserModel user, String socialProvider) {
+ 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;
@@ -196,7 +197,7 @@ public class JpaUserFederatedStorageProvider implements
@Override
public void updateFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel model) {
- BrokerLinkEntity entity = getBrokerLinkEntity(user, model.getIdentityProvider());
+ BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, model.getIdentityProvider());
if (entity == null) return;
entity.setBrokerUserName(model.getUserName());
entity.setBrokerUserId(model.getUserId());
@@ -221,7 +222,7 @@ public class JpaUserFederatedStorageProvider implements
@Override
public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
- BrokerLinkEntity entity = getBrokerLinkEntity(user, socialProvider);
+ BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, socialProvider);
if (entity == null) return null;
return new FederatedIdentityModel(entity.getIdentityProvider(), entity.getBrokerUserId(), entity.getBrokerUserName(), entity.getToken());
}
@@ -239,6 +240,7 @@ public class JpaUserFederatedStorageProvider implements
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();
@@ -588,7 +590,7 @@ public class JpaUserFederatedStorageProvider implements
@Override
public void preRemove(RealmModel realm) {
- int num = em.createNamedQuery("deleteUserConsentRolesByRealm")
+ int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteFederatedUserConsentProtMappersByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
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
index 79ba2fc..79c3e9e 100755
--- 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
@@ -17,131 +17,178 @@
-->
<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="mposolda@redhat.com" id="1.8.0">
+ <changeSet author="bburke@redhat.com" id="2.1.0">
- <addColumn tableName="IDENTITY_PROVIDER">
- <column name="POST_BROKER_LOGIN_FLOW_ID" type="VARCHAR(36)">
- <constraints nullable="true"/>
+ <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>
- </addColumn>
- <createTable tableName="CLIENT_TEMPLATE">
+ <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"/>
+ <constraints nullable="false" />
</column>
- <column name="NAME" type="VARCHAR(255)"/>
- <column name="REALM_ID" type="VARCHAR(36)"/>
- <column name="DESCRIPTION" type="VARCHAR(255)"/>
- <column name="PROTOCOL" type="VARCHAR(255)"/>
- <column name="FULL_SCOPE_ALLOWED" type="BOOLEAN" defaultValueBoolean="false">
+ <column name="NAME" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
- <column name="CONSENT_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
+ <column name="USER_ID" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
- <column name="STANDARD_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="true">
+ <column name="REALM_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
- <column name="IMPLICIT_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
+ <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="DIRECT_ACCESS_GRANTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
+ <column name="CLIENT_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
- <column name="SERVICE_ACCOUNTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
+ <column name="USER_ID" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
- <column name="FRONTCHANNEL_LOGOUT" type="BOOLEAN" defaultValueBoolean="false">
+ <column name="REALM_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
- <column name="BEARER_ONLY" type="BOOLEAN" defaultValueBoolean="false">
+ <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="PUBLIC_CLIENT" type="BOOLEAN" defaultValueBoolean="false">
+ <column name="ROLE_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
</createTable>
- <createTable tableName="CLIENT_TEMPLATE_ATTRIBUTES">
- <column name="TEMPLATE_ID" type="VARCHAR(36)">
+ <createTable tableName="FED_USER_CONSENT_PROT_MAPPER">
+ <column name="USER_CONSENT_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
- <column name="VALUE" type="VARCHAR(2048)"/>
- <column name="NAME" type="VARCHAR(255)">
+ <column name="PROTOCOL_MAPPER_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
</createTable>
- <createTable tableName="TEMPLATE_SCOPE_MAPPING">
- <column name="TEMPLATE_ID" type="VARCHAR(36)">
+ <createTable tableName="FED_USER_CREDENTIAL">
+ <column name="ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
- <column name="ROLE_ID" type="VARCHAR(36)">
+ <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>
-
-
-
- <dropNotNullConstraint tableName="PROTOCOL_MAPPER" columnName="CLIENT_ID" columnDataType="VARCHAR(36)"/>
- <addColumn tableName="CLIENT">
- <column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
- <constraints nullable="true"/>
+ <createTable tableName="FED_USER_GROUP_MEMBERSHIP">
+ <column name="GROUP_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
</column>
- <column name="USE_TEMPLATE_CONFIG" type="BOOLEAN" defaultValueBoolean="false">
+ <column name="USER_ID" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
- <column name="USE_TEMPLATE_SCOPE" type="BOOLEAN" defaultValueBoolean="false">
+ <column name="REALM_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
- <column name="USE_TEMPLATE_MAPPERS" type="BOOLEAN" defaultValueBoolean="false">
+ <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>
- </addColumn>
- <addColumn tableName="PROTOCOL_MAPPER">
- <column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
- <constraints nullable="true"/>
+ <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>
- </addColumn>
- <createTable tableName="REALM_CLIENT_TEMPLATE">
- <column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
+ <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>
- <addPrimaryKey columnNames="ID" constraintName="PK_CLI_TEMPLATE" tableName="CLIENT_TEMPLATE"/>
- <addUniqueConstraint columnNames="REALM_ID,NAME" constraintName="UK_CLI_TEMPLATE" tableName="CLIENT_TEMPLATE"/>
- <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="CLIENT_TEMPLATE" constraintName="FK_REALM_CLI_TMPLT" referencedColumnNames="ID" referencedTableName="REALM"/>
- <addForeignKeyConstraint baseColumnNames="CLIENT_TEMPLATE_ID" baseTableName="PROTOCOL_MAPPER" constraintName="FK_CLI_TMPLT_MAPPER" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
- <addForeignKeyConstraint baseColumnNames="CLIENT_TEMPLATE_ID" baseTableName="CLIENT" constraintName="FK_CLI_TMPLT_CLIENT" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
- <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_RLM" referencedColumnNames="ID" referencedTableName="REALM"/>
- <addForeignKeyConstraint baseColumnNames="CLIENT_TEMPLATE_ID" baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_CLI" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
- <addPrimaryKey columnNames="TEMPLATE_ID, ROLE_ID" constraintName="PK_TEMPLATE_SCOPE" tableName="TEMPLATE_SCOPE_MAPPING"/>
- <addForeignKeyConstraint baseColumnNames="TEMPLATE_ID" baseTableName="TEMPLATE_SCOPE_MAPPING" constraintName="FK_TEMPL_SCOPE_TEMPL" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
- <addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="TEMPLATE_SCOPE_MAPPING" constraintName="FK_TEMPL_SCOPE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
- <addPrimaryKey columnNames="TEMPLATE_ID, NAME" constraintName="PK_CL_TMPL_ATTR" tableName="CLIENT_TEMPLATE_ATTRIBUTES"/>
- <addForeignKeyConstraint baseColumnNames="TEMPLATE_ID" baseTableName="CLIENT_TEMPLATE_ATTRIBUTES" constraintName="FK_CL_TEMPL_ATTR_TEMPL" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
-
- <update tableName="CREDENTIAL">
- <column name="ALGORITHM" type="VARCHAR(36)" value="pbkdf2" />
- <where>TYPE in ('password-history', 'password') AND ALGORITHM is NULL</where>
- </update>
+ <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>
- </changeSet>
- <changeSet id="1.8.0-2" author="keycloak">
- <dropDefaultValue tableName="CREDENTIAL" columnName="ALGORITHM" columnDataType="VARCHAR(36)"/>
- <update tableName="CREDENTIAL">
- <column name="ALGORITHM" type="VARCHAR(36)" value="pbkdf2" />
- <where>TYPE in ('password-history', 'password') AND ALGORITHM = 'HmacSHA1'</where>
- </update>
- <!-- Sybase specific hacks -->
- <modifySql dbms="sybase">
- <regExpReplace replace=".*(SET DEFAULT NULL)" with="SELECT 1" />
- </modifySql>
+ <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 4f20a50..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
@@ -209,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 + ")");
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 16e6634..d903799 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
@@ -47,10 +47,6 @@ public interface UserProvider extends Provider, UserLookupProvider, UserQueryPro
UserModel getServiceAccount(ClientModel client);
List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts);
List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts);
- List<UserModel> searchForUser(String search, RealmModel realm);
-
- // Searching by UserModel.attribute (not property)
- List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm);
void preRemove(RealmModel realm);
diff --git a/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java b/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java
index 3f476bf..57a69ca 100644
--- a/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java
@@ -41,4 +41,9 @@ public interface UserQueryProvider {
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/storage/StorageProviderSpi.java b/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java
index 42a9060..5d6d0da 100755
--- a/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java
+++ b/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java
@@ -33,7 +33,7 @@ public class StorageProviderSpi implements Spi {
@Override
public String getName() {
- return "userFederation";
+ return "storage";
}
@Override
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
index aa438a3..f740f76 100755
--- a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
+++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
@@ -43,6 +43,7 @@ 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;
@@ -152,43 +153,77 @@ public class UserStorageManager implements UserProvider {
@Override
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) {
- getFederatedStorage().addFederatedIdentity(realm, user, 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) {
- getFederatedStorage().updateFederatedIdentity(realm, federatedUser, 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) {
- return getFederatedStorage().removeFederatedIdentity(realm, user, 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) {
- getFederatedStorage().addConsent(realm, user, 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) {
- return getFederatedStorage().getConsentByClient(realm, user, 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) {
- return getFederatedStorage().getConsents(realm, 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) {
- getFederatedStorage().updateConsent(realm, user, 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) {
- return getFederatedStorage().revokeConsentForClient(realm, user, clientInternalId);
+ if (StorageId.isLocalStorage(user)) {
+ return localStorage().revokeConsentForClient(realm, user, clientInternalId);
+ } else {
+ return getFederatedStorage().revokeConsentForClient(realm, user, clientInternalId);
+ }
}
@Override
@@ -334,24 +369,10 @@ public class UserStorageManager implements UserProvider {
@Override
public List<UserModel> searchForUser(final String search, final RealmModel realm, int firstResult, int maxResults) {
- final Map<String, String> attributes = new HashMap<String, String>();
- int spaceIndex = search.lastIndexOf(' ');
- if (spaceIndex > -1) {
- String firstName = search.substring(0, spaceIndex).trim();
- String lastName = search.substring(spaceIndex).trim();
- attributes.put(UserModel.FIRST_NAME, firstName);
- attributes.put(UserModel.LAST_NAME, lastName);
- } else if (search.indexOf('@') > -1) {
- attributes.put(UserModel.USERNAME, search.trim().toLowerCase());
- attributes.put(UserModel.EMAIL, search.trim().toLowerCase());
- } else {
- attributes.put(UserModel.LAST_NAME, search.trim());
- attributes.put(UserModel.USERNAME, search.trim().toLowerCase());
- }
- return query(new PaginatedQuery() {
+ return query(new PaginatedQuery() {
@Override
public List<UserModel> query(UserQueryProvider provider, int first, int max) {
- return provider.searchForUserByAttributes(attributes, realm, first, max);
+ return provider.searchForUser(search, realm, first, max);
}
}, realm, firstResult, maxResults);
}
@@ -372,26 +393,32 @@ public class UserStorageManager implements UserProvider {
}
@Override
- public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
- Map<String, String> attributes = new HashMap<>();
- attributes.put(attrName, attrValue);
- return searchForUserByAttributes(attributes, realm);
+ 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)) {
- return localStorage().getFederatedIdentities(user, realm);
+ set.addAll(localStorage().getFederatedIdentities(user, realm));
}
- return getFederatedStorage().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)) {
- return localStorage().getFederatedIdentity(user, socialProvider, realm);
+ FederatedIdentityModel model = localStorage().getFederatedIdentity(user, socialProvider, realm);
+ if (model != null) return model;
}
return getFederatedStorage().getFederatedIdentity(user, socialProvider, realm);
}
@@ -430,6 +457,7 @@ public class UserStorageManager implements UserProvider {
@Override
public void preRemove(RealmModel realm, UserFederationProviderModel model) {
+ getFederatedStorage().preRemove(realm, model);
localStorage().preRemove(realm, model);
}
@@ -454,11 +482,14 @@ public class UserStorageManager implements UserProvider {
@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
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 89c6e2d..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,7 @@
#
org.keycloak.models.UserFederationSpi
+org.keycloak.storage.StorageProviderSpi
org.keycloak.storage.federated.UserFederatedStorageProviderSpi
org.keycloak.mappers.UserFederationMapperSpi
org.keycloak.models.RealmSpi
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
index 3e5529e..8eb2a56 100644
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
@@ -42,6 +42,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
private ScriptingProvider scriptingProvider;
private UserSessionProvider sessionProvider;
private UserFederationManager federationManager;
+ private UserFederatedStorageProvider userFederatedStorageProvider;
private KeycloakContext context;
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
@@ -91,7 +92,10 @@ public class DefaultKeycloakSession implements KeycloakSession {
@Override
public UserFederatedStorageProvider userFederatedStorage() {
- return null;
+ if (userFederatedStorageProvider == null) {
+ userFederatedStorageProvider = getProvider(UserFederatedStorageProvider.class);
+ }
+ return userFederatedStorageProvider;
}
@Override
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/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/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties
index c2bc22c..306d48e 100755
--- a/testsuite/integration/src/test/resources/log4j.properties
+++ b/testsuite/integration/src/test/resources/log4j.properties
@@ -21,7 +21,7 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %t [%c] %m%n
-log4j.logger.org.keycloak=info
+log4j.logger.org.keycloak=debug
# Enable to view events
# log4j.logger.org.keycloak.events=debug
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}"
},