Merge pull request #2413 from mposolda/master

Caching of identityProvider links
This commit is contained in:
Marek Posolda 2016-03-22 22:26:45 +01:00
commit 0dea5eac46
2 changed files with 155 additions and 4 deletions

View file

@ -33,6 +33,7 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.models.cache.infinispan.entities.CachedFederatedIdentityLinks;
import org.keycloak.models.cache.infinispan.entities.CachedUser;
import org.keycloak.models.cache.infinispan.entities.UserListQuery;
@ -81,6 +82,7 @@ public class UserCacheSession implements CacheUserProvider {
invalidations.add(user.getId());
if (user.getEmail() != null) invalidations.add(getUserByEmailCacheKey(realm.getId(), user.getEmail()));
invalidations.add(getUserByUsernameCacheKey(realm.getId(), user.getUsername()));
if (realm.isIdentityFederationEnabled()) invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
}
protected void runInvalidations() {
@ -176,6 +178,14 @@ public class UserCacheSession implements CacheUserProvider {
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
public UserModel getUserByUsername(String username, RealmModel realm) {
logger.tracev("getUserByUsername: {0}", username);
@ -279,7 +289,46 @@ public class UserCacheSession implements CacheUserProvider {
@Override
public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
return getDelegate().getUserByFederatedIdentity(socialLink, realm);
if (socialLink == null) return null;
if (!realm.isIdentityFederationEnabled()) return null;
if (realmInvalidations.contains(realm.getId())) {
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
@ -346,12 +395,42 @@ public class UserCacheSession implements CacheUserProvider {
@Override
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm) {
return getDelegate().getFederatedIdentities(user, 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);
}
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
public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
return getDelegate().getFederatedIdentity(user, socialProvider, 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);
}
Set<FederatedIdentityModel> federatedIdentities = getFederatedIdentities(user, realm);
for (FederatedIdentityModel socialLink : federatedIdentities) {
if (socialLink.getIdentityProvider().equals(socialProvider)) {
return socialLink;
}
}
return null;
}
@Override
@ -374,10 +453,22 @@ public class UserCacheSession implements CacheUserProvider {
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
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());
if (user.getEmail() != null) invalidations.add(getUserByEmailCacheKey(realm.getId(), user.getEmail()));
invalidations.add(getUserByUsernameCacheKey(realm.getId(), user.getUsername()));
}
@Override
@ -388,16 +479,25 @@ public class UserCacheSession implements CacheUserProvider {
@Override
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) {
invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
getDelegate().addFederatedIdentity(realm, user, socialLink);
}
@Override
public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
invalidations.add(getFederatedIdentityLinksCacheKey(federatedUser.getId()));
getDelegate().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel);
}
@Override
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);
}

View file

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