keycloak-aplcache

Merge pull request #2404 from mposolda/1.9.x-idpcache KEYCLOAK-2632

3/22/2016 5:35:26 PM

Details

diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedFederatedIdentityLinks.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedFederatedIdentityLinks.java
new file mode 100644
index 0000000..d52f971
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedFederatedIdentityLinks.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.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;
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
index 64f4ad5..0e46962 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
@@ -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;
 
@@ -85,6 +86,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() {
@@ -180,6 +182,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);
@@ -283,7 +293,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
@@ -350,12 +399,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
@@ -378,10 +457,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
@@ -392,16 +483,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);
     }