Merge pull request #2413 from mposolda/master
Caching of identityProvider links
This commit is contained in:
commit
0dea5eac46
2 changed files with 155 additions and 4 deletions
|
@ -33,6 +33,7 @@ import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserProvider;
|
import org.keycloak.models.UserProvider;
|
||||||
import org.keycloak.models.cache.CacheUserProvider;
|
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.CachedUser;
|
||||||
import org.keycloak.models.cache.infinispan.entities.UserListQuery;
|
import org.keycloak.models.cache.infinispan.entities.UserListQuery;
|
||||||
|
|
||||||
|
@ -81,6 +82,7 @@ public class UserCacheSession implements CacheUserProvider {
|
||||||
invalidations.add(user.getId());
|
invalidations.add(user.getId());
|
||||||
if (user.getEmail() != null) invalidations.add(getUserByEmailCacheKey(realm.getId(), user.getEmail()));
|
if (user.getEmail() != null) invalidations.add(getUserByEmailCacheKey(realm.getId(), user.getEmail()));
|
||||||
invalidations.add(getUserByUsernameCacheKey(realm.getId(), user.getUsername()));
|
invalidations.add(getUserByUsernameCacheKey(realm.getId(), user.getUsername()));
|
||||||
|
if (realm.isIdentityFederationEnabled()) invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void runInvalidations() {
|
protected void runInvalidations() {
|
||||||
|
@ -176,6 +178,14 @@ public class UserCacheSession implements CacheUserProvider {
|
||||||
return realmId + ".email." + email;
|
return realmId + ".email." + email;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getUserByFederatedIdentityCacheKey(String realmId, FederatedIdentityModel socialLink) {
|
||||||
|
return realmId + ".idp." + socialLink.getIdentityProvider() + "." + socialLink.getUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFederatedIdentityLinksCacheKey(String userId) {
|
||||||
|
return userId + ".idplinks";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getUserByUsername(String username, RealmModel realm) {
|
public UserModel getUserByUsername(String username, RealmModel realm) {
|
||||||
logger.tracev("getUserByUsername: {0}", username);
|
logger.tracev("getUserByUsername: {0}", username);
|
||||||
|
@ -279,8 +289,47 @@ public class UserCacheSession implements CacheUserProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
|
public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
|
||||||
|
if (socialLink == null) return null;
|
||||||
|
if (!realm.isIdentityFederationEnabled()) return null;
|
||||||
|
|
||||||
|
if (realmInvalidations.contains(realm.getId())) {
|
||||||
return getDelegate().getUserByFederatedIdentity(socialLink, realm);
|
return getDelegate().getUserByFederatedIdentity(socialLink, realm);
|
||||||
}
|
}
|
||||||
|
String cacheKey = getUserByFederatedIdentityCacheKey(realm.getId(), socialLink);
|
||||||
|
if (invalidations.contains(cacheKey)) {
|
||||||
|
return getDelegate().getUserByFederatedIdentity(socialLink, realm);
|
||||||
|
}
|
||||||
|
UserListQuery query = cache.get(cacheKey, UserListQuery.class);
|
||||||
|
|
||||||
|
String userId = null;
|
||||||
|
if (query == null) {
|
||||||
|
Long loaded = cache.getCurrentRevision(cacheKey);
|
||||||
|
UserModel model = getDelegate().getUserByFederatedIdentity(socialLink, realm);
|
||||||
|
if (model == null) return null;
|
||||||
|
userId = model.getId();
|
||||||
|
query = new UserListQuery(loaded, cacheKey, realm, userId);
|
||||||
|
cache.addRevisioned(query, startupRevision);
|
||||||
|
if (invalidations.contains(userId)) return model;
|
||||||
|
if (managedUsers.containsKey(userId)) return managedUsers.get(userId);
|
||||||
|
|
||||||
|
CachedUser cached = cache.get(userId, CachedUser.class);
|
||||||
|
if (cached == null) {
|
||||||
|
cached = new CachedUser(loaded, realm, model);
|
||||||
|
cache.addRevisioned(cached, startupRevision);
|
||||||
|
}
|
||||||
|
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
|
||||||
|
managedUsers.put(userId, adapter);
|
||||||
|
return adapter;
|
||||||
|
} else {
|
||||||
|
userId = query.getUsers().iterator().next();
|
||||||
|
if (invalidations.contains(userId)) {
|
||||||
|
invalidations.add(cacheKey);
|
||||||
|
return getDelegate().getUserByFederatedIdentity(socialLink, realm);
|
||||||
|
|
||||||
|
}
|
||||||
|
return getUserById(userId, realm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||||
|
@ -346,14 +395,44 @@ public class UserCacheSession implements CacheUserProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm) {
|
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm) {
|
||||||
|
logger.tracev("getFederatedIdentities: {0}", user.getUsername());
|
||||||
|
|
||||||
|
String cacheKey = getFederatedIdentityLinksCacheKey(user.getId());
|
||||||
|
if (realmInvalidations.contains(realm.getId()) || invalidations.contains(user.getId()) || invalidations.contains(cacheKey)) {
|
||||||
return getDelegate().getFederatedIdentities(user, realm);
|
return getDelegate().getFederatedIdentities(user, realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CachedFederatedIdentityLinks cachedLinks = cache.get(cacheKey, CachedFederatedIdentityLinks.class);
|
||||||
|
|
||||||
|
if (cachedLinks == null) {
|
||||||
|
Long loaded = cache.getCurrentRevision(cacheKey);
|
||||||
|
Set<FederatedIdentityModel> federatedIdentities = getDelegate().getFederatedIdentities(user, realm);
|
||||||
|
cachedLinks = new CachedFederatedIdentityLinks(loaded, cacheKey, realm, federatedIdentities);
|
||||||
|
cache.addRevisioned(cachedLinks, startupRevision);
|
||||||
|
return federatedIdentities;
|
||||||
|
} else {
|
||||||
|
return new HashSet<>(cachedLinks.getFederatedIdentities());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
|
public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
|
||||||
|
logger.tracev("getFederatedIdentity: {0} {1}", user.getUsername(), socialProvider);
|
||||||
|
|
||||||
|
String cacheKey = getFederatedIdentityLinksCacheKey(user.getId());
|
||||||
|
if (realmInvalidations.contains(realm.getId()) || invalidations.contains(user.getId()) || invalidations.contains(cacheKey)) {
|
||||||
return getDelegate().getFederatedIdentity(user, socialProvider, realm);
|
return getDelegate().getFederatedIdentity(user, socialProvider, realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Set<FederatedIdentityModel> federatedIdentities = getFederatedIdentities(user, realm);
|
||||||
|
for (FederatedIdentityModel socialLink : federatedIdentities) {
|
||||||
|
if (socialLink.getIdentityProvider().equals(socialProvider)) {
|
||||||
|
return socialLink;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
||||||
UserModel user = getDelegate().addUser(realm, id, username, addDefaultRoles, addDefaultRoles);
|
UserModel user = getDelegate().addUser(realm, id, username, addDefaultRoles, addDefaultRoles);
|
||||||
|
@ -374,10 +453,22 @@ public class UserCacheSession implements CacheUserProvider {
|
||||||
|
|
||||||
protected void invalidateUser(RealmModel realm, UserModel user) {
|
protected void invalidateUser(RealmModel realm, UserModel user) {
|
||||||
// just in case the transaction is rolled back you need to invalidate the user and all cache queries for that user
|
// just in case the transaction is rolled back you need to invalidate the user and all cache queries for that user
|
||||||
|
|
||||||
|
if (realm.isIdentityFederationEnabled()) {
|
||||||
|
// Invalidate all keys for lookup this user by any identityProvider link
|
||||||
|
Set<FederatedIdentityModel> federatedIdentities = getFederatedIdentities(user, realm);
|
||||||
|
for (FederatedIdentityModel socialLink : federatedIdentities) {
|
||||||
|
String fedIdentityCacheKey = getUserByFederatedIdentityCacheKey(realm.getId(), socialLink);
|
||||||
|
invalidations.add(fedIdentityCacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate federationLinks of user
|
||||||
|
invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
invalidations.add(user.getId());
|
invalidations.add(user.getId());
|
||||||
if (user.getEmail() != null) invalidations.add(getUserByEmailCacheKey(realm.getId(), user.getEmail()));
|
if (user.getEmail() != null) invalidations.add(getUserByEmailCacheKey(realm.getId(), user.getEmail()));
|
||||||
invalidations.add(getUserByUsernameCacheKey(realm.getId(), user.getUsername()));
|
invalidations.add(getUserByUsernameCacheKey(realm.getId(), user.getUsername()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -388,16 +479,25 @@ public class UserCacheSession implements CacheUserProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) {
|
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) {
|
||||||
|
invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
|
||||||
getDelegate().addFederatedIdentity(realm, user, socialLink);
|
getDelegate().addFederatedIdentity(realm, user, socialLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
|
public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
|
||||||
|
invalidations.add(getFederatedIdentityLinksCacheKey(federatedUser.getId()));
|
||||||
getDelegate().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel);
|
getDelegate().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
|
public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
|
||||||
|
// Needs to invalidate both directions
|
||||||
|
FederatedIdentityModel socialLink = getFederatedIdentity(user, socialProvider, realm);
|
||||||
|
invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
|
||||||
|
if (socialLink != null) {
|
||||||
|
invalidations.add(getUserByFederatedIdentityCacheKey(realm.getId(), socialLink));
|
||||||
|
}
|
||||||
|
|
||||||
return getDelegate().removeFederatedIdentity(realm, user, socialProvider);
|
return getDelegate().removeFederatedIdentity(realm, user, socialProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.models.cache.infinispan.entities;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cache entry, which contains list of all identityProvider links for particular user. It needs to be updated every time when any
|
||||||
|
* federation link is added, removed or updated for the user
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class CachedFederatedIdentityLinks extends AbstractRevisioned implements InRealm {
|
||||||
|
|
||||||
|
private final String realmId;
|
||||||
|
private final Set<FederatedIdentityModel> federatedIdentities = new HashSet<>();
|
||||||
|
|
||||||
|
public CachedFederatedIdentityLinks(Long revision, String id, RealmModel realm, Set<FederatedIdentityModel> federatedIdentities) {
|
||||||
|
super(revision, id);
|
||||||
|
this.realmId = realm.getId();
|
||||||
|
this.federatedIdentities.addAll(federatedIdentities);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRealm() {
|
||||||
|
return realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<FederatedIdentityModel> getFederatedIdentities() {
|
||||||
|
return federatedIdentities;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue