fixes for new user fed spi

This commit is contained in:
Bill Burke 2016-07-07 10:35:35 -04:00
parent a19469aba5
commit 7e5a5f79cf
24 changed files with 320 additions and 156 deletions

View file

@ -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 $
*/

View file

@ -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);

View file

@ -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();

View file

@ -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

View file

@ -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 {

View file

@ -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

View file

@ -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)")

View file

@ -48,6 +48,7 @@ import java.io.Serializable;
public class FederatedUserRoleMappingEntity {
@Id
@Column(name = "USER_ID")
protected String userId;
@Id

View file

@ -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>
</addColumn>
<createTable tableName="CLIENT_TEMPLATE">
<column name="ID" type="VARCHAR(36)">
<constraints nullable="false"/>
<column name="STORAGE_PROVIDER_ID" type="VARCHAR(255)">
</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">
<constraints nullable="false"/>
<column name="REALM_ID" type="VARCHAR(36)">
<constraints nullable="false" />
</column>
<column name="CONSENT_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="STANDARD_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="true">
<constraints nullable="false"/>
</column>
<column name="IMPLICIT_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="DIRECT_ACCESS_GRANTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="SERVICE_ACCOUNTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="FRONTCHANNEL_LOGOUT" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="BEARER_ONLY" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="PUBLIC_CLIENT" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
<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="CLIENT_TEMPLATE_ATTRIBUTES">
<column name="TEMPLATE_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
<createTable tableName="FED_USER_ATTRIBUTE">
<column name="ID" type="VARCHAR(36)">
<constraints nullable="false" />
</column>
<column name="VALUE" type="VARCHAR(2048)"/>
<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="TEMPLATE_SCOPE_MAPPING">
<column name="TEMPLATE_ID" type="VARCHAR(36)">
<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>
<dropNotNullConstraint tableName="PROTOCOL_MAPPER" columnName="CLIENT_ID" columnDataType="VARCHAR(36)"/>
<addColumn tableName="CLIENT">
<column name="CLIENT_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="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="USE_TEMPLATE_CONFIG" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="USE_TEMPLATE_SCOPE" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="USE_TEMPLATE_MAPPERS" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</addColumn>
<addColumn tableName="PROTOCOL_MAPPER">
<column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
<column name="DIGITS" type="INT" defaultValueNumeric="6">
<constraints nullable="true"/>
</column>
</addColumn>
<createTable tableName="REALM_CLIENT_TEMPLATE">
<column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
<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>
<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"/>
<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>
<update tableName="CREDENTIAL">
<column name="ALGORITHM" type="VARCHAR(36)" value="pbkdf2" />
<where>TYPE in ('password-history', 'password') AND ALGORITHM is NULL</where>
</update>
<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>
<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>
</changeSet>
</databaseChangeLog>

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1 @@
org.keycloak.storage.jpa.JpaUserFederatedStorageProviderFactory

View file

@ -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 + ")");

View file

@ -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);

View file

@ -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);
}

View file

@ -33,7 +33,7 @@ public class StorageProviderSpi implements Spi {
@Override
public String getName() {
return "userFederation";
return "storage";
}
@Override

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -23,6 +23,10 @@
"provider": "${keycloak.user.provider:jpa}"
},
"userFederatedStorage": {
"provider": "${keycloak.userFederatedStorage.provider:jpa}"
},
"userSessionPersister": {
"provider": "${keycloak.userSessionPersister.provider:jpa}"
},

View file

@ -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);

View file

@ -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();
}
}

View file

@ -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