diff --git a/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java b/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java
new file mode 100755
index 0000000000..56226e0241
--- /dev/null
+++ b/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java
@@ -0,0 +1,102 @@
+/*
+ * 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.common.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+@SuppressWarnings("serial")
+public class ConcurrentMultivaluedHashMap extends ConcurrentHashMap>
+{
+ public void putSingle(K key, V value)
+ {
+ List list = new CopyOnWriteArrayList<>();
+ list.add(value);
+ put(key, list);
+ }
+
+ public void addAll(K key, V... newValues)
+ {
+ for (V value : newValues)
+ {
+ add(key, value);
+ }
+ }
+
+ public void addAll(K key, List valueList)
+ {
+ for (V value : valueList)
+ {
+ add(key, value);
+ }
+ }
+
+ public void addFirst(K key, V value)
+ {
+ List list = get(key);
+ if (list == null)
+ {
+ add(key, value);
+ }
+ else
+ {
+ list.add(0, value);
+ }
+ }
+ public final void add(K key, V value)
+ {
+ getList(key).add(value);
+ }
+
+
+ public final void addMultiple(K key, Collection values)
+ {
+ getList(key).addAll(values);
+ }
+
+ public V getFirst(K key)
+ {
+ List list = get(key);
+ return list == null ? null : list.get(0);
+ }
+
+ public final List getList(K key)
+ {
+ List list = get(key);
+ if (list == null)
+ put(key, list = new CopyOnWriteArrayList());
+ return list;
+ }
+
+ public void addAll(ConcurrentMultivaluedHashMap other)
+ {
+ for (Entry> entry : other.entrySet())
+ {
+ getList(entry.getKey()).addAll(entry.getValue());
+ }
+ }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCacheProviderFactory.java
similarity index 79%
rename from model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
rename to model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCacheProviderFactory.java
index e0409b1aa5..c5205381bf 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCacheProviderFactory.java
@@ -18,34 +18,28 @@
package org.keycloak.models.cache.infinispan;
import org.infinispan.Cache;
-import org.infinispan.notifications.Listener;
-import org.infinispan.notifications.cachelistener.annotation.*;
-import org.infinispan.notifications.cachelistener.event.*;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
-import org.keycloak.models.cache.CacheUserProvider;
-import org.keycloak.models.cache.CacheUserProviderFactory;
-import org.keycloak.models.cache.infinispan.entities.CachedUser;
+import org.keycloak.models.cache.UserCache;
+import org.keycloak.models.cache.UserCacheProviderFactory;
import org.keycloak.models.cache.infinispan.entities.Revisioned;
-import java.util.concurrent.ConcurrentHashMap;
-
/**
* @author Stian Thorgersen
*/
-public class InfinispanCacheUserProviderFactory implements CacheUserProviderFactory {
+public class InfinispanUserCacheProviderFactory implements UserCacheProviderFactory {
- private static final Logger log = Logger.getLogger(InfinispanCacheUserProviderFactory.class);
+ private static final Logger log = Logger.getLogger(InfinispanUserCacheProviderFactory.class);
protected volatile UserCacheManager userCache;
@Override
- public CacheUserProvider create(KeycloakSession session) {
+ public UserCache create(KeycloakSession session) {
lazyInit(session);
return new UserCacheSession(userCache, session);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
index d697ad236e..ea6698cf83 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
@@ -28,6 +28,7 @@ import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.CachedUserModel;
import org.keycloak.models.cache.infinispan.entities.CachedUser;
import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -39,12 +40,13 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class UserAdapter implements UserModel {
+public class UserAdapter implements CachedUserModel {
protected UserModel updated;
protected CachedUser cached;
protected UserCacheSession userProviderCache;
@@ -65,6 +67,22 @@ public class UserAdapter implements UserModel {
if (updated == null) throw new IllegalStateException("Not found in database");
}
}
+
+ @Override
+ public void invalidate() {
+ getDelegateForUpdate();
+ }
+
+ @Override
+ public long getCacheTimestamp() {
+ return cached.getCacheTimestamp();
+ }
+
+ @Override
+ public ConcurrentHashMap getCachedWith() {
+ return cached.getCachedWith();
+ }
+
@Override
public String getId() {
if (updated != null) return updated.getId();
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 3d966d1b62..b38d9aab1b 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
@@ -20,7 +20,6 @@ package org.keycloak.models.cache.infinispan;
import org.jboss.logging.Logger;
import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.component.ComponentModel;
-import org.keycloak.credential.CredentialInput;
import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
@@ -35,7 +34,9 @@ import org.keycloak.models.UserCredentialModel;
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.CachedUserModel;
+import org.keycloak.models.cache.OnUserCache;
+import org.keycloak.models.cache.UserCache;
import org.keycloak.models.cache.infinispan.entities.CachedFederatedIdentityLinks;
import org.keycloak.models.cache.infinispan.entities.CachedUser;
import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
@@ -48,7 +49,7 @@ import java.util.*;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class UserCacheSession implements CacheUserProvider {
+public class UserCacheSession implements UserCache {
protected static final Logger logger = Logger.getLogger(UserCacheSession.class);
protected UserCacheManager cache;
protected KeycloakSession session;
@@ -74,7 +75,6 @@ public class UserCacheSession implements CacheUserProvider {
cache.clear();
}
- @Override
public UserProvider getDelegate() {
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
if (delegate != null) return delegate;
@@ -90,6 +90,20 @@ public class UserCacheSession implements CacheUserProvider {
if (realm.isIdentityFederationEnabled()) invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
}
+ @Override
+ public void evict(RealmModel realm, UserModel user) {
+ if (user instanceof CachedUserModel) {
+ ((CachedUserModel)user).invalidate();
+ } else {
+ 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() {
for (String realmId : realmInvalidations) {
cache.invalidateRealmUsers(realmId, invalidations);
@@ -148,8 +162,13 @@ public class UserCacheSession implements CacheUserProvider {
logger.trace("registered for invalidation return delegate");
return getDelegate().getUserById(id, realm);
}
+ if (managedUsers.containsKey(id)) {
+ logger.trace("return managedusers");
+ return managedUsers.get(id);
+ }
CachedUser cached = cache.get(id, CachedUser.class);
+ boolean wasCached = cached != null;
if (cached == null) {
logger.trace("not cached");
Long loaded = cache.getCurrentRevision(id);
@@ -158,19 +177,12 @@ public class UserCacheSession implements CacheUserProvider {
logger.trace("delegate returning null");
return null;
}
- if (managedUsers.containsKey(id)) {
- logger.trace("return managedusers");
- return managedUsers.get(id);
- }
- if (invalidations.contains(id)) return model;
cached = new CachedUser(loaded, realm, model);
cache.addRevisioned(cached, startupRevision);
- } else if (managedUsers.containsKey(id)) {
- logger.trace("return managedusers");
- return managedUsers.get(id);
}
logger.trace("returning new cache adapter");
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
+ if (!wasCached) onCache(realm, adapter);
managedUsers.put(id, adapter);
return adapter;
}
@@ -224,13 +236,7 @@ public class UserCacheSession implements CacheUserProvider {
return managedUsers.get(userId);
}
- CachedUser cached = cache.get(userId, CachedUser.class);
- if (cached == null) {
- cached = new CachedUser(loaded, realm, model);
- cache.addRevisioned(cached, startupRevision);
- }
- logger.trace("return new cache adapter");
- UserAdapter adapter = new UserAdapter(cached, this, session, realm);
+ UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
managedUsers.put(userId, adapter);
return adapter;
} else {
@@ -245,6 +251,26 @@ public class UserCacheSession implements CacheUserProvider {
}
}
+ protected UserAdapter getUserAdapter(RealmModel realm, String userId, Long loaded, UserModel model) {
+ CachedUser cached = cache.get(userId, CachedUser.class);
+ boolean wasCached = cached != null;
+ if (cached == null) {
+ cached = new CachedUser(loaded, realm, model);
+ cache.addRevisioned(cached, startupRevision);
+ }
+ UserAdapter adapter = new UserAdapter(cached, this, session, realm);
+ if (!wasCached) {
+ onCache(realm, adapter);
+ }
+ return adapter;
+
+ }
+
+ private void onCache(RealmModel realm, UserAdapter adapter) {
+ ((OnUserCache)getDelegate()).onCache(realm, adapter);
+ ((OnUserCache)session.userCredentialManager()).onCache(realm, adapter);
+ }
+
@Override
public UserModel getUserByEmail(String email, RealmModel realm) {
if (email == null) return null;
@@ -269,12 +295,7 @@ public class UserCacheSession implements CacheUserProvider {
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);
+ UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
managedUsers.put(userId, adapter);
return adapter;
} else {
@@ -317,12 +338,7 @@ public class UserCacheSession implements CacheUserProvider {
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);
+ UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
managedUsers.put(userId, adapter);
return adapter;
} else {
@@ -671,24 +687,4 @@ public class UserCacheSession implements CacheUserProvider {
}
- @Override
- public boolean isValid(RealmModel realm, UserModel user, List inputs) {
- return getDelegate().isValid(realm, user, inputs);
- }
-
- @Override
- public void updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
- getDelegate().updateCredential(realm, user, input);
- }
-
- @Override
- public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
- return getDelegate().isConfiguredFor(realm, user, type);
- }
-
- @Override
- public Set requiredActionsFor(RealmModel realm, UserModel user, String type) {
- return getDelegate().requiredActionsFor(realm, user, type);
- }
-
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractExtendableRevisioned.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractExtendableRevisioned.java
new file mode 100644
index 0000000000..9940732841
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractExtendableRevisioned.java
@@ -0,0 +1,40 @@
+/*
+ * 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.concurrent.ConcurrentHashMap;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public abstract class AbstractExtendableRevisioned extends AbstractRevisioned {
+ protected ConcurrentHashMap cachedWith = new ConcurrentHashMap();
+
+ public AbstractExtendableRevisioned(Long revision, String id) {
+ super(revision, id);
+ }
+
+ /**
+ * Cache things along with this cachable object
+ *
+ * @return
+ */
+ public ConcurrentHashMap getCachedWith() {
+ return cachedWith;
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java
index 89166202c5..ed49ddff1e 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java
@@ -9,6 +9,7 @@ import java.io.Serializable;
public class AbstractRevisioned implements Revisioned, Serializable {
private String id;
private Long revision;
+ private final long cacheTimestamp = System.currentTimeMillis();
public AbstractRevisioned(Long revision, String id) {
this.revision = revision;
@@ -30,4 +31,12 @@ public class AbstractRevisioned implements Revisioned, Serializable {
this.revision = revision;
}
+ /**
+ * When was this cached
+ *
+ * @return
+ */
+ public long getCacheTimestamp() {
+ return cacheTimestamp;
+ }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
index 89049cae99..7c24594885 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
@@ -36,7 +36,7 @@ import java.util.Set;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class CachedUser extends AbstractRevisioned implements InRealm {
+public class CachedUser extends AbstractExtendableRevisioned implements InRealm {
private String realm;
private String username;
private Long createdTimestamp;
diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheUserProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.UserCacheProviderFactory
similarity index 91%
rename from model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheUserProviderFactory
rename to model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.UserCacheProviderFactory
index f680e3a4c3..e558cf21e9 100755
--- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheUserProviderFactory
+++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.UserCacheProviderFactory
@@ -15,4 +15,4 @@
# limitations under the License.
#
-org.keycloak.models.cache.infinispan.InfinispanCacheUserProviderFactory
\ No newline at end of file
+org.keycloak.models.cache.infinispan.InfinispanUserCacheProviderFactory
\ No newline at end of file
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 de6e587d0a..4c5ccc47d0 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
@@ -101,14 +101,19 @@ public class ClusteredCacheBehaviorTest {
System.out.println("node1 create entry");
node1Cache.put("key", "node1");
+
System.out.println("node1 create entry");
node1Cache.put("key", "node111");
+
System.out.println("node2 create entry");
node2Cache.put("key", "node2");
+
System.out.println("node1 remove entry");
node1Cache.remove("key");
+
System.out.println("node2 remove entry");
node2Cache.remove("key");
+
System.out.println("node2 put entry");
node2Cache.put("key", "node2");
System.out.println("node2 evict entry");
@@ -118,6 +123,28 @@ public class ClusteredCacheBehaviorTest {
node2Cache.putForExternalRead("key", "common");
System.out.println("node2 remove entry");
node2Cache.remove("key");
+ System.out.println("node1 remove entry");
+ node1Cache.remove("key");
+
+ // test remove non-existing node 2, existing node 1
+ System.out.println("Test non existent remove");
+ System.out.println("node1 create entry");
+ node1Cache.put("key", "value");
+ System.out.println("node2 remove non-existent entry");
+ System.out.println("exists?: " + node2Cache.containsKey("key"));
+ node2Cache.remove("key");
+
+ // test clear
+ System.out.println("Test clear cache");
+ System.out.println("add key to node 1, key2 to node2");
+ node1Cache.putForExternalRead("key", "value");
+ node2Cache.putForExternalRead("key", "value");
+ node2Cache.putForExternalRead("key2", "value");
+ System.out.println("Clear from node1");
+ node1Cache.clear();
+ System.out.println("node 2 exists key2?: " + node2Cache.containsKey("key2"));
+ System.out.println("node 2 exists key?: " + node2Cache.containsKey("key"));
+
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java
new file mode 100644
index 0000000000..d3539fe8d7
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java
@@ -0,0 +1,220 @@
+/*
+ * 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.jpa;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.credential.UserCredentialStore;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.jpa.entities.CredentialAttributeEntity;
+import org.keycloak.models.jpa.entities.CredentialEntity;
+import org.keycloak.models.jpa.entities.UserEntity;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class JpaUserCredentialStore implements UserCredentialStore {
+
+ private final KeycloakSession session;
+ protected final EntityManager em;
+
+ public JpaUserCredentialStore(KeycloakSession session, EntityManager em) {
+ this.session = session;
+ this.em = em;
+ }
+
+ @Override
+ public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ CredentialEntity entity = em.find(CredentialEntity.class, cred.getId());
+ if (entity == null) return;
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) {
+
+ } else {
+ MultivaluedHashMap attrs = cred.getConfig();
+ MultivaluedHashMap config = cred.getConfig();
+ if (config == null) config = new MultivaluedHashMap<>();
+
+ Iterator it = entity.getCredentialAttributes().iterator();
+ while (it.hasNext()) {
+ CredentialAttributeEntity attr = it.next();
+ List values = config.getList(attr.getName());
+ if (values == null || !values.contains(attr.getValue())) {
+ em.remove(attr);
+ it.remove();
+ } else {
+ attrs.add(attr.getName(), attr.getValue());
+ }
+
+ }
+ for (String key : config.keySet()) {
+ List values = config.getList(key);
+ List attrValues = attrs.getList(key);
+ for (String val : values) {
+ if (attrValues == null || !attrValues.contains(val)) {
+ CredentialAttributeEntity attr = new CredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+ }
+
+ }
+
+ }
+
+ @Override
+ public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ CredentialEntity entity = new CredentialEntity();
+ String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
+ entity.setId(id);
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ UserEntity userRef = em.getReference(UserEntity.class, user.getId());
+ entity.setUser(userRef);
+ em.persist(entity);
+ MultivaluedHashMap config = cred.getConfig();
+ if (config != null || !config.isEmpty()) {
+
+ for (String key : config.keySet()) {
+ List values = config.getList(key);
+ for (String val : values) {
+ CredentialAttributeEntity attr = new CredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+
+ }
+ return toModel(entity);
+ }
+
+ @Override
+ public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
+ CredentialEntity entity = em.find(CredentialEntity.class, id);
+ if (entity == null) return false;
+ em.remove(entity);
+ return true;
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
+ CredentialEntity entity = em.find(CredentialEntity.class, id);
+ if (entity == null) return null;
+ CredentialModel model = toModel(entity);
+ return model;
+ }
+
+ protected CredentialModel toModel(CredentialEntity entity) {
+ CredentialModel model = new CredentialModel();
+ model.setId(entity.getId());
+ model.setType(entity.getType());
+ model.setValue(entity.getValue());
+ model.setAlgorithm(entity.getAlgorithm());
+ model.setSalt(entity.getSalt());
+ model.setPeriod(entity.getPeriod());
+ model.setCounter(entity.getCounter());
+ model.setCreatedDate(entity.getCreatedDate());
+ model.setDevice(entity.getDevice());
+ model.setDigits(entity.getDigits());
+ MultivaluedHashMap config = new MultivaluedHashMap<>();
+ model.setConfig(config);
+ for (CredentialAttributeEntity attr : entity.getCredentialAttributes()) {
+ config.add(attr.getName(), attr.getValue());
+ }
+ return model;
+ }
+
+ @Override
+ public List getStoredCredentials(RealmModel realm, UserModel user) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+ TypedQuery query = em.createNamedQuery("credentialByUser", CredentialEntity.class)
+ .setParameter("user", userEntity);
+ List results = query.getResultList();
+ List rtn = new LinkedList<>();
+ for (CredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+ TypedQuery query = em.createNamedQuery("credentialByUserAndType", CredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("user", userEntity);
+ List results = query.getResultList();
+ List rtn = new LinkedList<>();
+ for (CredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+ TypedQuery query = em.createNamedQuery("credentialByNameAndType", CredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("device", name)
+ .setParameter("user", userEntity);
+ List results = query.getResultList();
+ if (results.isEmpty()) return null;
+ return toModel(results.get(0));
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStoreFactory.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStoreFactory.java
new file mode 100755
index 0000000000..550299ba8b
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStoreFactory.java
@@ -0,0 +1,59 @@
+/*
+ * 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.jpa;
+
+import org.keycloak.Config;
+import org.keycloak.connections.jpa.JpaConnectionProvider;
+import org.keycloak.credential.UserCredentialStore;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderFactory;
+
+import javax.persistence.EntityManager;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class JpaUserCredentialStoreFactory implements ProviderFactory {
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public String getId() {
+ return "jpa";
+ }
+
+ @Override
+ public UserCredentialStore create(KeycloakSession session) {
+ EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
+ return new JpaUserCredentialStore(session, em);
+ }
+
+ @Override
+ public void close() {
+ }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index 30752d3bee..d44c6fe171 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -17,8 +17,11 @@
package org.keycloak.models.jpa;
+import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialInput;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.credential.UserCredentialStore;
import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
@@ -35,6 +38,8 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
+import org.keycloak.models.jpa.entities.CredentialAttributeEntity;
+import org.keycloak.models.jpa.entities.CredentialEntity;
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
import org.keycloak.models.jpa.entities.UserAttributeEntity;
import org.keycloak.models.jpa.entities.UserConsentEntity;
@@ -51,6 +56,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -60,7 +66,7 @@ import java.util.Set;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class JpaUserProvider implements UserProvider {
+public class JpaUserProvider implements UserProvider, UserCredentialStore {
private static final String EMAIL = "email";
private static final String USERNAME = "username";
@@ -724,24 +730,174 @@ public class JpaUserProvider implements UserProvider {
public void preRemove(RealmModel realm, ComponentModel component) {
}
- @Override
- public boolean isValid(RealmModel realm, UserModel user, List inputs) {
- return false;
- }
@Override
- public void updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+ public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ CredentialEntity entity = em.find(CredentialEntity.class, cred.getId());
+ if (entity == null) return;
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) {
+
+ } else {
+ MultivaluedHashMap attrs = cred.getConfig();
+ MultivaluedHashMap config = cred.getConfig();
+ if (config == null) config = new MultivaluedHashMap<>();
+
+ Iterator it = entity.getCredentialAttributes().iterator();
+ while (it.hasNext()) {
+ CredentialAttributeEntity attr = it.next();
+ List values = config.getList(attr.getName());
+ if (values == null || !values.contains(attr.getValue())) {
+ em.remove(attr);
+ it.remove();
+ } else {
+ attrs.add(attr.getName(), attr.getValue());
+ }
+
+ }
+ for (String key : config.keySet()) {
+ List values = config.getList(key);
+ List attrValues = attrs.getList(key);
+ for (String val : values) {
+ if (attrValues == null || !attrValues.contains(val)) {
+ CredentialAttributeEntity attr = new CredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+ }
+
+ }
}
@Override
- public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
- return false;
+ public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ CredentialEntity entity = new CredentialEntity();
+ String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
+ entity.setId(id);
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ UserEntity userRef = em.getReference(UserEntity.class, user.getId());
+ entity.setUser(userRef);
+ em.persist(entity);
+ MultivaluedHashMap config = cred.getConfig();
+ if (config != null || !config.isEmpty()) {
+
+ for (String key : config.keySet()) {
+ List values = config.getList(key);
+ for (String val : values) {
+ CredentialAttributeEntity attr = new CredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+
+ }
+ return toModel(entity);
}
@Override
- public Set requiredActionsFor(RealmModel realm, UserModel user, String type) {
- return Collections.EMPTY_SET;
+ public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
+ CredentialEntity entity = em.find(CredentialEntity.class, id);
+ if (entity == null) return false;
+ em.remove(entity);
+ return true;
}
+ @Override
+ public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
+ CredentialEntity entity = em.find(CredentialEntity.class, id);
+ if (entity == null) return null;
+ CredentialModel model = toModel(entity);
+ return model;
+ }
+
+ protected CredentialModel toModel(CredentialEntity entity) {
+ CredentialModel model = new CredentialModel();
+ model.setId(entity.getId());
+ model.setType(entity.getType());
+ model.setValue(entity.getValue());
+ model.setAlgorithm(entity.getAlgorithm());
+ model.setSalt(entity.getSalt());
+ model.setPeriod(entity.getPeriod());
+ model.setCounter(entity.getCounter());
+ model.setCreatedDate(entity.getCreatedDate());
+ model.setDevice(entity.getDevice());
+ model.setDigits(entity.getDigits());
+ MultivaluedHashMap config = new MultivaluedHashMap<>();
+ model.setConfig(config);
+ for (CredentialAttributeEntity attr : entity.getCredentialAttributes()) {
+ config.add(attr.getName(), attr.getValue());
+ }
+ return model;
+ }
+
+ @Override
+ public List getStoredCredentials(RealmModel realm, UserModel user) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+ TypedQuery query = em.createNamedQuery("credentialByUser", CredentialEntity.class)
+ .setParameter("user", userEntity);
+ List results = query.getResultList();
+ List rtn = new LinkedList<>();
+ for (CredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+ TypedQuery query = em.createNamedQuery("credentialByUserAndType", CredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("user", userEntity);
+ List results = query.getResultList();
+ List rtn = new LinkedList<>();
+ for (CredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+ TypedQuery query = em.createNamedQuery("credentialByNameAndType", CredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("device", name)
+ .setParameter("user", userEntity);
+ List results = query.getResultList();
+ if (results.isEmpty()) return null;
+ return toModel(results.get(0));
+ }
+
+
+
+
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
index 2d048ee0d2..4c8f0b3e83 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
@@ -38,7 +38,9 @@ import java.util.Collection;
* @version $Revision: 1 $
*/
@NamedQueries({
+ @NamedQuery(name="credentialByUser", query="select cred from CredentialEntity cred where cred.user = :user"),
@NamedQuery(name="credentialByUserAndType", query="select cred from CredentialEntity cred where cred.user = :user and cred.type = :type"),
+ @NamedQuery(name="credentialByNameAndType", query="select cred from CredentialEntity cred where cred.user = :user and cred.type = :type and cred.device = :device"),
@NamedQuery(name="deleteCredentialsByRealm", query="delete from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId)"),
@NamedQuery(name="deleteCredentialsByRealmAndLink", query="delete from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaFederatedUserCredentialStore.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaFederatedUserCredentialStore.java
new file mode 100644
index 0000000000..13fa03218f
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaFederatedUserCredentialStore.java
@@ -0,0 +1,218 @@
+/*
+ * 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.storage.jpa;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.credential.UserCredentialStore;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.StorageId;
+import org.keycloak.storage.jpa.entity.FederatedUserCredentialAttributeEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class JpaFederatedUserCredentialStore implements UserCredentialStore {
+
+ private final KeycloakSession session;
+ protected final EntityManager em;
+
+ public JpaFederatedUserCredentialStore(KeycloakSession session, EntityManager em) {
+ this.session = session;
+ this.em = em;
+ }
+
+ @Override
+ public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
+ if (entity == null) return;
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) {
+
+ } else {
+ MultivaluedHashMap attrs = cred.getConfig();
+ MultivaluedHashMap config = cred.getConfig();
+ if (config == null) config = new MultivaluedHashMap<>();
+
+ Iterator it = entity.getCredentialAttributes().iterator();
+ while (it.hasNext()) {
+ FederatedUserCredentialAttributeEntity attr = it.next();
+ List values = config.getList(attr.getName());
+ if (values == null || !values.contains(attr.getValue())) {
+ em.remove(attr);
+ it.remove();
+ } else {
+ attrs.add(attr.getName(), attr.getValue());
+ }
+
+ }
+ for (String key : config.keySet()) {
+ List values = config.getList(key);
+ List attrValues = attrs.getList(key);
+ for (String val : values) {
+ if (attrValues == null || !attrValues.contains(val)) {
+ FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+ }
+
+ }
+
+ }
+
+ @Override
+ public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ FederatedUserCredentialEntity entity = new FederatedUserCredentialEntity();
+ String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
+ entity.setId(id);
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ entity.setUserId(user.getId());
+ entity.setRealmId(realm.getId());
+ entity.setStorageProviderId(StorageId.resolveProviderId(user));
+ em.persist(entity);
+ MultivaluedHashMap config = cred.getConfig();
+ if (config != null || !config.isEmpty()) {
+
+ for (String key : config.keySet()) {
+ List values = config.getList(key);
+ for (String val : values) {
+ FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+
+ }
+ return toModel(entity);
+ }
+
+ @Override
+ public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
+ FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id);
+ if (entity == null) return false;
+ em.remove(entity);
+ return true;
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
+ FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id);
+ if (entity == null) return null;
+ CredentialModel model = toModel(entity);
+ return model;
+ }
+
+ protected CredentialModel toModel(FederatedUserCredentialEntity entity) {
+ CredentialModel model = new CredentialModel();
+ model.setId(entity.getId());
+ model.setType(entity.getType());
+ model.setValue(entity.getValue());
+ model.setAlgorithm(entity.getAlgorithm());
+ model.setSalt(entity.getSalt());
+ model.setPeriod(entity.getPeriod());
+ model.setCounter(entity.getCounter());
+ model.setCreatedDate(entity.getCreatedDate());
+ model.setDevice(entity.getDevice());
+ model.setDigits(entity.getDigits());
+ MultivaluedHashMap config = new MultivaluedHashMap<>();
+ model.setConfig(config);
+ for (FederatedUserCredentialAttributeEntity attr : entity.getCredentialAttributes()) {
+ config.add(attr.getName(), attr.getValue());
+ }
+ return model;
+ }
+
+ @Override
+ public List getStoredCredentials(RealmModel realm, UserModel user) {
+ TypedQuery query = em.createNamedQuery("federatedUserCredentialByUser", FederatedUserCredentialEntity.class)
+ .setParameter("userId", user.getId());
+ List results = query.getResultList();
+ List rtn = new LinkedList<>();
+ for (FederatedUserCredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
+ TypedQuery query = em.createNamedQuery("federatedUserCredentialByUserAndType", FederatedUserCredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("userId", user.getId());
+ List results = query.getResultList();
+ List rtn = new LinkedList<>();
+ for (FederatedUserCredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+ TypedQuery query = em.createNamedQuery("federatedUserCredentialByNameAndType", FederatedUserCredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("device", name)
+ .setParameter("userId", user.getId());
+ List results = query.getResultList();
+ if (results.isEmpty()) return null;
+ return toModel(results.get(0));
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
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 d9d7de9903..1c72da0744 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
@@ -19,6 +19,7 @@ package org.keycloak.storage.jpa;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
+import org.keycloak.credential.UserCredentialStore;
import org.keycloak.models.ClientModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
@@ -50,6 +51,7 @@ import org.keycloak.storage.jpa.entity.FederatedUserAttributeEntity;
import org.keycloak.storage.jpa.entity.FederatedUserConsentEntity;
import org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity;
import org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserCredentialAttributeEntity;
import org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity;
import org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity;
import org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity;
@@ -60,6 +62,7 @@ import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -76,7 +79,8 @@ public class JpaUserFederatedStorageProvider implements
UserCredentialsFederatedStorage,
UserGroupMembershipFederatedStorage,
UserRequiredActionsFederatedStorage,
- UserRoleMappingsFederatedStorage {
+ UserRoleMappingsFederatedStorage,
+ UserCredentialStore {
private final KeycloakSession session;
protected EntityManager em;
@@ -607,16 +611,6 @@ public class JpaUserFederatedStorageProvider implements
em.flush();
}
- @Override
- public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
-
- }
-
- @Override
- public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
- return null;
- }
-
@Override
public boolean removeCredential(RealmModel realm, String id) {
return false;
@@ -647,6 +641,170 @@ public class JpaUserFederatedStorageProvider implements
return null;
}
+ @Override
+ public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
+ if (entity == null) return;
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) {
+
+ } else {
+ MultivaluedHashMap attrs = cred.getConfig();
+ MultivaluedHashMap config = cred.getConfig();
+ if (config == null) config = new MultivaluedHashMap<>();
+
+ Iterator it = entity.getCredentialAttributes().iterator();
+ while (it.hasNext()) {
+ FederatedUserCredentialAttributeEntity attr = it.next();
+ List values = config.getList(attr.getName());
+ if (values == null || !values.contains(attr.getValue())) {
+ em.remove(attr);
+ it.remove();
+ } else {
+ attrs.add(attr.getName(), attr.getValue());
+ }
+
+ }
+ for (String key : config.keySet()) {
+ List values = config.getList(key);
+ List attrValues = attrs.getList(key);
+ for (String val : values) {
+ if (attrValues == null || !attrValues.contains(val)) {
+ FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+ }
+
+ }
+
+ }
+
+ @Override
+ public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ FederatedUserCredentialEntity entity = new FederatedUserCredentialEntity();
+ String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
+ entity.setId(id);
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ entity.setUserId(user.getId());
+ entity.setRealmId(realm.getId());
+ entity.setStorageProviderId(StorageId.resolveProviderId(user));
+ em.persist(entity);
+ MultivaluedHashMap config = cred.getConfig();
+ if (config != null || !config.isEmpty()) {
+
+ for (String key : config.keySet()) {
+ List values = config.getList(key);
+ for (String val : values) {
+ FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+
+ }
+ return toModel(entity);
+ }
+
+ @Override
+ public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
+ FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id);
+ if (entity == null) return false;
+ em.remove(entity);
+ return true;
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
+ FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id);
+ if (entity == null) return null;
+ CredentialModel model = toModel(entity);
+ return model;
+ }
+
+ protected CredentialModel toModel(FederatedUserCredentialEntity entity) {
+ CredentialModel model = new CredentialModel();
+ model.setId(entity.getId());
+ model.setType(entity.getType());
+ model.setValue(entity.getValue());
+ model.setAlgorithm(entity.getAlgorithm());
+ model.setSalt(entity.getSalt());
+ model.setPeriod(entity.getPeriod());
+ model.setCounter(entity.getCounter());
+ model.setCreatedDate(entity.getCreatedDate());
+ model.setDevice(entity.getDevice());
+ model.setDigits(entity.getDigits());
+ MultivaluedHashMap config = new MultivaluedHashMap<>();
+ model.setConfig(config);
+ for (FederatedUserCredentialAttributeEntity attr : entity.getCredentialAttributes()) {
+ config.add(attr.getName(), attr.getValue());
+ }
+ return model;
+ }
+
+ @Override
+ public List getStoredCredentials(RealmModel realm, UserModel user) {
+ TypedQuery query = em.createNamedQuery("federatedUserCredentialByUser", FederatedUserCredentialEntity.class)
+ .setParameter("userId", user.getId());
+ List results = query.getResultList();
+ List rtn = new LinkedList<>();
+ for (FederatedUserCredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
+ TypedQuery query = em.createNamedQuery("federatedUserCredentialByUserAndType", FederatedUserCredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("userId", user.getId());
+ List results = query.getResultList();
+ List rtn = new LinkedList<>();
+ for (FederatedUserCredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+ TypedQuery query = em.createNamedQuery("federatedUserCredentialByNameAndType", FederatedUserCredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("device", name)
+ .setParameter("userId", user.getId());
+ List results = query.getResultList();
+ if (results.isEmpty()) return null;
+ return toModel(results.get(0));
+ }
+
@Override
public void preRemove(RealmModel realm) {
int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm")
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
index da305b48b7..52a757cd8f 100755
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
@@ -43,6 +43,7 @@ import java.util.Collection;
@NamedQueries({
@NamedQuery(name="federatedUserCredentialByUser", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId"),
@NamedQuery(name="federatedUserCredentialByUserAndType", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"),
+ @NamedQuery(name="federatedUserCredentialByNameAndType", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type and cred.device = :device"),
@NamedQuery(name="deleteFederatedUserCredentialByUser", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.realmId = :realmId"),
@NamedQuery(name="deleteFederatedUserCredentialByUserAndType", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"),
@NamedQuery(name="deleteFederatedUserCredentialByUserAndTypeAndDevice", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type and cred.device = :device"),
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 3888c7ab74..9ad2c1ea36 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
@@ -637,23 +637,4 @@ public class MongoUserProvider implements UserProvider {
}
- @Override
- public boolean isValid(RealmModel realm, UserModel user, List inputs) {
- return false;
- }
-
- @Override
- public void updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
-
- }
-
- @Override
- public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
- return false;
- }
-
- @Override
- public Set requiredActionsFor(RealmModel realm, UserModel user, String type) {
- return Collections.EMPTY_SET;
- }
}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java
index e24870c096..3456f8f5e1 100644
--- a/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java
@@ -27,6 +27,6 @@ import java.util.Set;
*/
public interface CredentialInputUpdater {
boolean supportsCredentialType(String credentialType);
- Set requiredActionsFor(RealmModel realm, UserModel user, String credentialType);
- void updateCredential(RealmModel realm, UserModel user, CredentialInput input);
+ boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input);
+ void disableCredentialType(RealmModel realm, UserModel user, String credentialType);
}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java b/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java
index 082c64b770..24b7772398 100755
--- a/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java
@@ -29,6 +29,20 @@ import java.util.Map;
* @author Marek Posolda
*/
public class CredentialModel implements Serializable {
+ public static final String PASSWORD = "password";
+ public static final String PASSWORD_HISTORY = "password-history";
+ public static final String PASSWORD_TOKEN = "password-token";
+
+ // Secret is same as password but it is not hashed
+ public static final String SECRET = "secret";
+ public static final String TOTP = "totp";
+ public static final String HOTP = "hotp";
+ public static final String CLIENT_CERT = "cert";
+ public static final String KERBEROS = "kerberos";
+ public static final String OTP = "otp";
+
+
+
private String id;
private String type;
private String value;
diff --git a/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java b/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java
index df497f1ad5..c99873911c 100644
--- a/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java
+++ b/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java
@@ -29,10 +29,9 @@ import java.util.List;
public interface UserCredentialStore extends Provider {
void updateCredential(RealmModel realm, UserModel user, CredentialModel cred);
CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred);
- boolean removeCredential(RealmModel realm, String id);
- CredentialModel getCredentialById(String id);
- List getCredentials(RealmModel realm);
- List getCredentials(RealmModel realm, UserModel user);
- List getCredentialsByType(RealmModel realm, UserModel user, String type);
- CredentialModel getCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type);
+ boolean removeStoredCredential(RealmModel realm, UserModel user, String id);
+ CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id);
+ List getStoredCredentials(RealmModel realm, UserModel user);
+ List getStoredCredentialsByType(RealmModel realm, UserModel user, String type);
+ CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type);
}
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
index 8b6dcd31d0..2a1ad0ff4c 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
@@ -17,6 +17,7 @@
package org.keycloak.models;
+import org.keycloak.models.cache.UserCache;
import org.keycloak.provider.Provider;
import org.keycloak.scripting.ScriptingProvider;
import org.keycloak.storage.federated.UserFederatedStorageProvider;
@@ -74,6 +75,7 @@ public interface KeycloakSession {
Object removeAttribute(String attribute);
void setAttribute(String name, Object value);
+
void enlistForClose(Provider provider);
KeycloakSessionFactory getKeycloakSessionFactory();
@@ -101,7 +103,14 @@ public interface KeycloakSession {
void close();
/**
- * A cached view of all users in system.
+ * The user cache
+ *
+ * @return may be null if cache is disabled
+ */
+ UserCache getUserCache();
+
+ /**
+ * A possibly cached view of all users in system.
*
* @return
*/
@@ -115,8 +124,10 @@ public interface KeycloakSession {
*/
UserProvider userStorageManager();
+ UserCredentialManager userCredentialManager();
+
/**
- * A cached view of all users in system that does NOT include users available from the deprecated UserFederationProvider SPI.
+ * A possibly cached view of all users in system that does NOT include users available from the deprecated UserFederationProvider SPI.
*/
UserProvider userStorage();
@@ -129,6 +140,7 @@ public interface KeycloakSession {
/**
* Hybrid storage for UserStorageProviders that can't store a specific piece of keycloak data in their external storage.
+ * No cache in front.
*
* @return
*/
diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java
new file mode 100644
index 0000000000..2da5716f92
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import org.keycloak.credential.CredentialInput;
+import org.keycloak.credential.UserCredentialStore;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public interface UserCredentialManager extends UserCredentialStore {
+ boolean isValid(RealmModel realm, UserModel user, List inputs);
+
+ void updateCredential(RealmModel realm, UserModel user, CredentialInput input);
+ void disableCredential(RealmModel realm, UserModel user, String credentialType);
+
+ boolean isConfiguredFor(RealmModel realm, UserModel user, String type);
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java
index fa967074c0..4be355d19a 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java
@@ -18,6 +18,7 @@
package org.keycloak.models;
import org.keycloak.credential.CredentialInput;
+import org.keycloak.credential.CredentialModel;
import java.util.UUID;
@@ -26,16 +27,16 @@ import java.util.UUID;
* @version $Revision: 1 $
*/
public class UserCredentialModel implements CredentialInput {
- public static final String PASSWORD = "password";
- public static final String PASSWORD_HISTORY = "password-history";
- public static final String PASSWORD_TOKEN = "password-token";
+ public static final String PASSWORD = CredentialModel.PASSWORD;
+ public static final String PASSWORD_HISTORY = CredentialModel.PASSWORD_HISTORY;
+ public static final String PASSWORD_TOKEN = CredentialModel.PASSWORD_TOKEN;
// Secret is same as password but it is not hashed
- public static final String SECRET = "secret";
- public static final String TOTP = "totp";
- public static final String HOTP = "hotp";
- public static final String CLIENT_CERT = "cert";
- public static final String KERBEROS = "kerberos";
+ public static final String SECRET = CredentialModel.SECRET;
+ public static final String TOTP = CredentialModel.TOTP;
+ public static final String HOTP = CredentialModel.HOTP;
+ public static final String CLIENT_CERT = CredentialModel.CLIENT_CERT;
+ public static final String KERBEROS = CredentialModel.KERBEROS;
protected String type;
protected String value;
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
index 46c748edfd..e1037053fc 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -445,27 +445,6 @@ public class UserFederationManager implements UserProvider {
session.userStorage().grantToAllUsers(realm, role);
}
- @Override
- public boolean isValid(RealmModel realm, UserModel user, List inputs) {
- return session.userStorage().isValid(realm, user, inputs);
- }
-
- @Override
- public void updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
- session.userStorage().updateCredential(realm, user, input);
-
- }
-
- @Override
- public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
- return session.userStorage().isConfiguredFor(realm, user, type);
- }
-
- @Override
- public Set requiredActionsFor(RealmModel realm, UserModel user, String type) {
- return session.userStorage().requiredActionsFor(realm, user, type);
- }
-
@Override
public void preRemove(RealmModel realm) {
for (UserFederationProviderModel federation : realm.getUserFederationProviders()) {
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 5dbc04ea34..642131b518 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
@@ -87,11 +87,4 @@ public interface UserProvider extends Provider,
void preRemove(RealmModel realm, ComponentModel component);
- boolean isValid(RealmModel realm, UserModel user, List inputs);
-
- void updateCredential(RealmModel realm, UserModel user, CredentialInput input);
-
- boolean isConfiguredFor(RealmModel realm, UserModel user, String type);
-
- Set requiredActionsFor(RealmModel realm, UserModel user, String type);
}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java b/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java
index c6723fba97..a0efa39e91 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java
@@ -39,11 +39,11 @@ public class CacheUserProviderSpi implements Spi {
@Override
public Class extends Provider> getProviderClass() {
- return CacheUserProvider.class;
+ return UserCache.class;
}
@Override
public Class extends ProviderFactory> getProviderFactoryClass() {
- return CacheUserProviderFactory.class;
+ return UserCacheProviderFactory.class;
}
}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
new file mode 100644
index 0000000000..5d9c7cd278
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+import org.keycloak.models.UserModel;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public interface CachedUserModel extends UserModel {
+ void invalidate();
+
+ /**
+ * When was the user loaded from database.
+ *
+ * @return
+ */
+ long getCacheTimestamp();
+
+ /**
+ * Returns a map that contains custom things that are cached along with the user. You can write to this map.
+ *
+ * @return
+ */
+ ConcurrentHashMap getCachedWith();
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java b/server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java
old mode 100755
new mode 100644
similarity index 85%
rename from server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java
rename to server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java
index 63c97061d6..e676ce14e6
--- a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java
@@ -14,17 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.keycloak.models.cache;
import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserProvider;
/**
* @author Bill Burke
* @version $Revision: 1 $
*/
-public interface CacheUserProvider extends UserProvider {
- void clear();
- UserProvider getDelegate();
+public interface OnUserCache {
+ void onCache(RealmModel realm, CachedUserModel user);
}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java b/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java
new file mode 100755
index 0000000000..f309079d94
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserProvider;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public interface UserCache extends UserProvider {
+ /**
+ * Evict user from cache.
+ *
+ * @param user
+ */
+ void evict(RealmModel realm, UserModel user);
+ void clear();
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderFactory.java b/server-spi/src/main/java/org/keycloak/models/cache/UserCacheProviderFactory.java
similarity index 90%
rename from server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderFactory.java
rename to server-spi/src/main/java/org/keycloak/models/cache/UserCacheProviderFactory.java
index 37208e4711..fdf4fd881d 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderFactory.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/UserCacheProviderFactory.java
@@ -23,6 +23,6 @@ import org.keycloak.provider.ProviderFactory;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public interface CacheUserProviderFactory extends ProviderFactory {
+public interface UserCacheProviderFactory extends ProviderFactory {
}
diff --git a/services/src/main/java/org/keycloak/credential/LocalOTPCredentialManager.java b/services/src/main/java/org/keycloak/credential/LocalOTPCredentialManager.java
new file mode 100644
index 0000000000..6d9baf0345
--- /dev/null
+++ b/services/src/main/java/org/keycloak/credential/LocalOTPCredentialManager.java
@@ -0,0 +1,224 @@
+/*
+ * 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.credential;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OTPPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.CachedUserModel;
+import org.keycloak.models.cache.OnUserCache;
+import org.keycloak.models.utils.HmacOTP;
+import org.keycloak.models.utils.TimeBasedOTP;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class LocalOTPCredentialManager implements CredentialInputValidator, CredentialInputUpdater, OnUserCache {
+ private static final Logger logger = Logger.getLogger(LocalOTPCredentialManager.class);
+
+ protected KeycloakSession session;
+
+ protected List getCachedCredentials(UserModel user, String type) {
+ if (!(user instanceof CachedUserModel)) return Collections.EMPTY_LIST;
+ CachedUserModel cached = (CachedUserModel)user;
+ List rtn = (List)cached.getCachedWith().get(LocalOTPCredentialManager.class.getName() + "." + type);
+ if (rtn == null) return Collections.EMPTY_LIST;
+ return rtn;
+ }
+
+ protected UserCredentialStore getCredentialStore() {
+ return session.userCredentialManager();
+ }
+
+ @Override
+ public void onCache(RealmModel realm, CachedUserModel user) {
+ List creds = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP);
+ user.getCachedWith().put(LocalOTPCredentialManager.class.getName() + "." + CredentialModel.TOTP, creds);
+
+ }
+
+ public LocalOTPCredentialManager(KeycloakSession session) {
+ this.session = session;
+ }
+
+ @Override
+ public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+ if (!supportsCredentialType(input.getType())) return false;
+
+ if (!(input instanceof UserCredentialModel)) {
+ logger.debug("Expected instance of UserCredentialModel for CredentialInput");
+ return false;
+ }
+ UserCredentialModel inputModel = (UserCredentialModel)input;
+ CredentialModel model = null;
+ if (inputModel.getDevice() != null) {
+ model = getCredentialStore().getStoredCredentialByNameAndType(realm, user, inputModel.getDevice(), CredentialModel.TOTP);
+ if (model == null) {
+ model = getCredentialStore().getStoredCredentialByNameAndType(realm, user, inputModel.getDevice(), CredentialModel.HOTP);
+ }
+ }
+ if (model == null) {
+ // delete all existing
+ disableCredentialType(realm, user, CredentialModel.OTP);
+ model = new CredentialModel();
+ }
+
+ OTPPolicy policy = realm.getOTPPolicy();
+ model.setDigits(policy.getDigits());
+ model.setCounter(policy.getInitialCounter());
+ model.setAlgorithm(policy.getAlgorithm());
+ model.setType(policy.getType());
+ model.setValue(inputModel.getValue());
+ model.setDevice(inputModel.getDevice());
+ model.setPeriod(policy.getPeriod());
+ if (model.getId() == null) {
+ getCredentialStore().createCredential(realm, user, model);
+ } else {
+ getCredentialStore().updateCredential(realm, user, model);
+ }
+ session.getUserCache().evict(realm, user);
+ return true;
+
+
+
+ }
+
+ @Override
+ public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
+ boolean disableTOTP = false, disableHOTP = false;
+ if (CredentialModel.OTP.equals(credentialType)) {
+ disableTOTP = true;
+ disableHOTP = true;
+ } else if (CredentialModel.HOTP.equals(credentialType)) {
+ disableHOTP = true;
+
+ } else if (CredentialModel.TOTP.equals(credentialType)) {
+ disableTOTP = true;
+ }
+ if (disableHOTP) {
+ List hotp = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.HOTP);
+ for (CredentialModel cred : hotp) {
+ getCredentialStore().removeStoredCredential(realm, user, cred.getId());
+ }
+
+ }
+ if (disableTOTP) {
+ List totp = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP);
+ if (!totp.isEmpty()) {
+ for (CredentialModel cred : totp) {
+ getCredentialStore().removeStoredCredential(realm, user, cred.getId());
+ }
+ }
+
+ }
+ if (disableTOTP || disableHOTP) {
+ session.getUserCache().evict(realm, user);
+ }
+ }
+
+ @Override
+ public boolean supportsCredentialType(String credentialType) {
+ return CredentialModel.OTP.equals(credentialType)
+ || CredentialModel.HOTP.equals(credentialType)
+ || CredentialModel.TOTP.equals(credentialType);
+ }
+
+ @Override
+ public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
+ if (!supportsCredentialType(credentialType)) return false;
+ if (CredentialModel.OTP.equals(credentialType)) {
+ if (realm.getOTPPolicy().getType().equals(CredentialModel.HOTP)) {
+ return configuredForHOTP(realm, user);
+ } else {
+ return configuredForTOTP(realm, user);
+ }
+ } else if (CredentialModel.HOTP.equals(credentialType)) {
+ return configuredForHOTP(realm, user);
+
+ } else if (CredentialModel.TOTP.equals(credentialType)) {
+ return configuredForTOTP(realm, user);
+ } else {
+ return false;
+ }
+
+ }
+
+ protected boolean configuredForHOTP(RealmModel realm, UserModel user) {
+ return !getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.HOTP).isEmpty();
+ }
+
+ protected boolean configuredForTOTP(RealmModel realm, UserModel user) {
+ return !getCachedCredentials(user, CredentialModel.TOTP).isEmpty()
+ || !getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP).isEmpty();
+ }
+
+ public static boolean validOTP(RealmModel realm, String token, String secret) {
+ OTPPolicy policy = realm.getOTPPolicy();
+ if (policy.getType().equals(UserCredentialModel.TOTP)) {
+ TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow());
+ return validator.validateTOTP(token, secret.getBytes());
+ } else {
+ HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());
+ int c = validator.validateHOTP(token, secret, policy.getInitialCounter());
+ return c > -1;
+ }
+
+ }
+
+ @Override
+ public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
+ if (! (input instanceof UserCredentialModel)) {
+ logger.debug("Expected instance of UserCredentialModel for CredentialInput");
+ return false;
+
+ }
+ String token = ((UserCredentialModel)input).getValue();
+ OTPPolicy policy = realm.getOTPPolicy();
+ if (realm.getOTPPolicy().getType().equals(CredentialModel.HOTP)) {
+ HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());
+ for (CredentialModel cred : getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.HOTP)) {
+ int counter = validator.validateHOTP(token, cred.getValue(), cred.getCounter());
+ if (counter < 0) continue;
+ cred.setCounter(counter);
+ getCredentialStore().updateCredential(realm, user, cred);
+ return true;
+ }
+ } else {
+ TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow());
+ List creds = getCachedCredentials(user, CredentialModel.TOTP);
+ if (creds.isEmpty()) {
+ creds = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP);
+ } else {
+ logger.debugv("Cache hit for TOTP for user {0}", user.getUsername());
+ }
+ for (CredentialModel cred : creds) {
+ if (validator.validateTOTP(token, cred.getValue().getBytes())) {
+ return true;
+ }
+ }
+
+ }
+ return false;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java
new file mode 100644
index 0000000000..5f67768378
--- /dev/null
+++ b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java
@@ -0,0 +1,249 @@
+/*
+ * 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.credential;
+
+import org.keycloak.common.util.reflections.Types;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.PrioritizedComponentModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialManager;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.CachedUserModel;
+import org.keycloak.models.cache.OnUserCache;
+import org.keycloak.storage.StorageId;
+import org.keycloak.storage.UserStorageManager;
+import org.keycloak.storage.UserStorageProvider;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class UserCredentialStoreManager implements UserCredentialManager, OnUserCache {
+ protected KeycloakSession session;
+
+ public UserCredentialStoreManager(KeycloakSession session) {
+ this.session = session;
+ }
+
+ protected UserCredentialStore getStoreForUser(UserModel user) {
+ if (StorageId.isLocalStorage(user)) {
+ return (UserCredentialStore)session.userLocalStorage();
+ } else {
+ return (UserCredentialStore)session.userFederatedStorage();
+ }
+ }
+
+ @Override
+ public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ getStoreForUser(user).updateCredential(realm, user, cred);
+
+ }
+
+ @Override
+ public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ return getStoreForUser(user).createCredential(realm, user, cred);
+ }
+
+ @Override
+ public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
+ return getStoreForUser(user).removeStoredCredential(realm, user, id);
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
+ return getStoreForUser(user).getStoredCredentialById(realm, user, id);
+ }
+
+ @Override
+ public List getStoredCredentials(RealmModel realm, UserModel user) {
+ return getStoreForUser(user).getStoredCredentials(realm, user);
+ }
+
+ @Override
+ public List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
+ return getStoreForUser(user).getStoredCredentialsByType(realm, user, type);
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+ return getStoreForUser(user).getStoredCredentialByNameAndType(realm, user, name, type);
+ }
+
+
+ @Override
+ public boolean isValid(RealmModel realm, UserModel user, List inputs) {
+
+ List toValidate = new LinkedList<>();
+ toValidate.addAll(inputs);
+ if (!StorageId.isLocalStorage(user)) {
+ String providerId = StorageId.resolveProviderId(user);
+ UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId);
+ if (provider instanceof CredentialInputValidator) {
+ Iterator it = toValidate.iterator();
+ while (it.hasNext()) {
+ CredentialInput input = it.next();
+ CredentialInputValidator validator = (CredentialInputValidator)provider;
+ if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) {
+ it.remove();
+ }
+ }
+ }
+ }
+
+ if (toValidate.isEmpty()) return true;
+
+ List components = getCredentialProviderComponents(realm);
+ for (ComponentModel component : components) {
+ CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+ if (!Types.supports(CredentialInputValidator.class, factory, CredentialProviderFactory.class)) continue;
+ Iterator it = toValidate.iterator();
+ while (it.hasNext()) {
+ CredentialInput input = it.next();
+ CredentialInputValidator validator = (CredentialInputValidator)session.getAttribute(component.getId());
+ if (validator == null) {
+ validator = (CredentialInputValidator)factory.create(session, component);
+ session.setAttribute(component.getId(), validator);
+ }
+ if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) {
+ it.remove();
+ }
+ }
+ }
+
+ return toValidate.isEmpty();
+ }
+
+ protected List getCredentialProviderComponents(RealmModel realm) {
+ List components = realm.getComponents(realm.getId(), CredentialProvider.class.getName());
+ if (components.isEmpty()) return components;
+ List copy = new LinkedList<>();
+ copy.addAll(components);
+ Collections.sort(copy, PrioritizedComponentModel.comparator);
+ return copy;
+ }
+
+ @Override
+ public void updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+ if (!StorageId.isLocalStorage(user)) {
+ String providerId = StorageId.resolveProviderId(user);
+ UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId);
+ if (provider instanceof CredentialInputUpdater) {
+ CredentialInputUpdater updater = (CredentialInputUpdater)provider;
+ if (updater.supportsCredentialType(input.getType())) {
+ if (updater.updateCredential(realm, user, input)) return;
+ }
+ }
+ }
+
+ List components = getCredentialProviderComponents(realm);
+ for (ComponentModel component : components) {
+ CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+ if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
+ CredentialInputUpdater updater = (CredentialInputUpdater)session.getAttribute(component.getId());
+ if (updater == null) {
+ updater = (CredentialInputUpdater)factory.create(session, component);
+ session.setAttribute(component.getId(), updater);
+ }
+ if (!updater.supportsCredentialType(input.getType())) continue;
+ if (updater.updateCredential(realm, user, input)) return;
+ }
+
+ }
+ @Override
+ public void disableCredential(RealmModel realm, UserModel user, String credentialType) {
+ if (!StorageId.isLocalStorage(user)) {
+ String providerId = StorageId.resolveProviderId(user);
+ UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId);
+ if (provider instanceof CredentialInputUpdater) {
+ CredentialInputUpdater updater = (CredentialInputUpdater)provider;
+ if (updater.supportsCredentialType(credentialType)) {
+ updater.disableCredentialType(realm, user, credentialType);
+ }
+ }
+ }
+
+ List components = getCredentialProviderComponents(realm);
+ for (ComponentModel component : components) {
+ CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+ if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
+ CredentialInputUpdater updater = (CredentialInputUpdater)session.getAttribute(component.getId());
+ if (updater == null) {
+ updater = (CredentialInputUpdater)factory.create(session, component);
+ session.setAttribute(component.getId(), updater);
+ }
+ if (!updater.supportsCredentialType(credentialType)) continue;
+ updater.disableCredentialType(realm, user, credentialType);
+ }
+
+ }
+ @Override
+ public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
+ if (!StorageId.isLocalStorage(user)) {
+ String providerId = StorageId.resolveProviderId(user);
+ UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId);
+ if (provider instanceof CredentialInputValidator) {
+ CredentialInputValidator validator = (CredentialInputValidator)provider;
+ if (validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
+ return true;
+ }
+ }
+ }
+
+ List components = getCredentialProviderComponents(realm);
+ for (ComponentModel component : components) {
+ CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+ if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
+ CredentialInputValidator validator = (CredentialInputValidator)session.getAttribute(component.getId());
+ if (validator == null) {
+ validator = (CredentialInputValidator)factory.create(session, component);
+ session.setAttribute(component.getId(), validator);
+ }
+ if (validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
+ return true;
+ }
+ }
+ return false;
+
+ }
+
+ @Override
+ public void onCache(RealmModel realm, CachedUserModel user) {
+ List components = getCredentialProviderComponents(realm);
+ for (ComponentModel component : components) {
+ CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+ if (!Types.supports(OnUserCache.class, factory, CredentialProviderFactory.class)) continue;
+ OnUserCache validator = (OnUserCache)session.getAttribute(component.getId());
+ if (validator == null) {
+ validator = (OnUserCache)factory.create(session, component);
+ session.setAttribute(component.getId(), validator);
+ }
+ validator.onCache(realm, user);
+ }
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
index 1fae787117..539bafbb5a 100644
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
@@ -16,9 +16,11 @@
*/
package org.keycloak.services;
+import org.keycloak.credential.UserCredentialStore;
+import org.keycloak.credential.UserCredentialStoreManager;
import org.keycloak.models.*;
import org.keycloak.models.cache.CacheRealmProvider;
-import org.keycloak.models.cache.CacheUserProvider;
+import org.keycloak.models.cache.UserCache;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.scripting.ScriptingProvider;
@@ -40,6 +42,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
private RealmProvider model;
private UserProvider userModel;
private UserStorageManager userStorageManager;
+ private UserCredentialStoreManager userCredentialStorageManager;
private ScriptingProvider scriptingProvider;
private UserSessionProvider sessionProvider;
private UserFederationManager federationManager;
@@ -68,7 +71,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
}
private UserProvider getUserProvider() {
- CacheUserProvider cache = getProvider(CacheUserProvider.class);
+ UserCache cache = getProvider(UserCache.class);
if (cache != null) {
return cache;
} else {
@@ -76,6 +79,12 @@ public class DefaultKeycloakSession implements KeycloakSession {
}
}
+ @Override
+ public UserCache getUserCache() {
+ return getProvider(UserCache.class);
+
+ }
+
@Override
public void enlistForClose(Provider provider) {
closable.add(provider);
@@ -125,6 +134,12 @@ public class DefaultKeycloakSession implements KeycloakSession {
return userStorageManager;
}
+ @Override
+ public UserCredentialManager userCredentialManager() {
+ if (userCredentialStorageManager == null) userCredentialStorageManager = new UserCredentialStoreManager(this);
+ return userCredentialStorageManager;
+ }
+
@Override
public UserProvider userStorage() {
if (userModel == null) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 4fd941b728..7d4922340d 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -34,8 +34,6 @@ import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.exportimport.ClientDescriptionConverter;
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
-import org.keycloak.jose.jws.JWSBuilder;
-import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
@@ -44,7 +42,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.cache.CacheRealmProvider;
-import org.keycloak.models.cache.CacheUserProvider;
+import org.keycloak.models.cache.UserCache;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
@@ -55,7 +53,6 @@ import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
-import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.PartialImportRepresentation;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
@@ -67,7 +64,6 @@ import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.UsersSyncManager;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.resources.admin.RealmAuth.Resource;
-import org.keycloak.timer.TimerProvider;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -85,8 +81,6 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
-import java.security.PrivateKey;
-import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@@ -866,7 +860,7 @@ public class RealmAdminResource {
public void clearUserCache() {
auth.requireManage();
- CacheUserProvider cache = session.getProvider(CacheUserProvider.class);
+ UserCache cache = session.getProvider(UserCache.class);
if (cache != null) {
cache.clear();
}
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java b/services/src/main/java/org/keycloak/storage/UserStorageManager.java
similarity index 71%
rename from server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
rename to services/src/main/java/org/keycloak/storage/UserStorageManager.java
index 6620f49aa4..e0b2ffd3c2 100755
--- a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
+++ b/services/src/main/java/org/keycloak/storage/UserStorageManager.java
@@ -20,12 +20,6 @@ package org.keycloak.storage;
import org.jboss.logging.Logger;
import org.keycloak.common.util.reflections.Types;
import org.keycloak.component.ComponentModel;
-import org.keycloak.component.PrioritizedComponentModel;
-import org.keycloak.credential.CredentialInput;
-import org.keycloak.credential.CredentialInputUpdater;
-import org.keycloak.credential.CredentialInputValidator;
-import org.keycloak.credential.CredentialProvider;
-import org.keycloak.credential.CredentialProviderFactory;
import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
@@ -36,6 +30,8 @@ import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserConsentModel;
+import org.keycloak.models.cache.CachedUserModel;
+import org.keycloak.models.cache.OnUserCache;
import org.keycloak.storage.user.UserCredentialAuthenticationProvider;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.storage.user.UserCredentialValidatorProvider;
@@ -62,7 +58,7 @@ import java.util.Set;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class UserStorageManager implements UserProvider {
+public class UserStorageManager implements UserProvider, OnUserCache {
private static final Logger logger = Logger.getLogger(UserStorageManager.class);
@@ -76,22 +72,22 @@ public class UserStorageManager implements UserProvider {
return session.userLocalStorage();
}
- protected List getStorageProviders(RealmModel realm) {
+ public static List getStorageProviders(RealmModel realm) {
return realm.getUserStorageProviders();
}
- protected T getFirstStorageProvider(RealmModel realm, Class type) {
+ public static T getFirstStorageProvider(KeycloakSession session, RealmModel realm, Class type) {
for (UserStorageProviderModel model : getStorageProviders(realm)) {
UserStorageProviderFactory factory = (UserStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId());
if (Types.supports(type, factory, UserStorageProviderFactory.class)) {
- return type.cast(getStorageProviderInstance(model, factory));
+ return type.cast(getStorageProviderInstance(session, model, factory));
}
}
return null;
}
- protected UserStorageProvider getStorageProviderInstance(UserStorageProviderModel model, UserStorageProviderFactory factory) {
+ public static UserStorageProvider getStorageProviderInstance(KeycloakSession session, UserStorageProviderModel model, UserStorageProviderFactory factory) {
UserStorageProvider instance = (UserStorageProvider)session.getAttribute(model.getId());
if (instance != null) return instance;
instance = factory.create(session, model);
@@ -101,12 +97,12 @@ public class UserStorageManager implements UserProvider {
}
- protected List getStorageProviders(RealmModel realm, Class type) {
+ public static List getStorageProviders(KeycloakSession session, RealmModel realm, Class type) {
List list = new LinkedList<>();
for (UserStorageProviderModel model : getStorageProviders(realm)) {
UserStorageProviderFactory factory = (UserStorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId());
if (Types.supports(type, factory, UserStorageProviderFactory.class)) {
- list.add(type.cast(getStorageProviderInstance(model, factory)));
+ list.add(type.cast(getStorageProviderInstance(session, model, factory)));
}
@@ -122,21 +118,21 @@ public class UserStorageManager implements UserProvider {
@Override
public UserModel addUser(RealmModel realm, String username) {
- UserRegistrationProvider registry = getFirstStorageProvider(realm, UserRegistrationProvider.class);
+ UserRegistrationProvider registry = getFirstStorageProvider(session, realm, UserRegistrationProvider.class);
if (registry != null) {
return registry.addUser(realm, username);
}
return localStorage().addUser(realm, username.toLowerCase());
}
- public UserStorageProvider getStorageProvider(RealmModel realm, String componentId) {
+ public static UserStorageProvider getStorageProvider(KeycloakSession session, RealmModel realm, String componentId) {
ComponentModel model = realm.getComponent(componentId);
if (model == null) return null;
UserStorageProviderFactory factory = (UserStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId());
if (factory == null) {
throw new ModelException("Could not find UserStorageProviderFactory for: " + model.getProviderId());
}
- return getStorageProviderInstance(new UserStorageProviderModel(model), factory);
+ return getStorageProviderInstance(session, new UserStorageProviderModel(model), factory);
}
@Override
@@ -146,7 +142,7 @@ public class UserStorageManager implements UserProvider {
if (storageId.getProviderId() == null) {
return localStorage().removeUser(realm, user);
}
- UserRegistrationProvider registry = (UserRegistrationProvider)getStorageProvider(realm, storageId.getProviderId());
+ UserRegistrationProvider registry = (UserRegistrationProvider)getStorageProvider(session, realm, storageId.getProviderId());
if (registry == null) {
throw new ModelException("Could not resolve StorageProvider: " + storageId.getProviderId());
}
@@ -239,7 +235,7 @@ public class UserStorageManager implements UserProvider {
if (storageId.getProviderId() == null) {
return localStorage().getUserById(id, realm);
}
- UserLookupProvider provider = (UserLookupProvider)getStorageProvider(realm, storageId.getProviderId());
+ UserLookupProvider provider = (UserLookupProvider)getStorageProvider(session, realm, storageId.getProviderId());
return provider.getUserById(id, realm);
}
@@ -252,7 +248,7 @@ public class UserStorageManager implements UserProvider {
public UserModel getUserByUsername(String username, RealmModel realm) {
UserModel user = localStorage().getUserByUsername(username, realm);
if (user != null) return user;
- for (UserLookupProvider provider : getStorageProviders(realm, UserLookupProvider.class)) {
+ for (UserLookupProvider provider : getStorageProviders(session, realm, UserLookupProvider.class)) {
user = provider.getUserByUsername(username, realm);
if (user != null) return user;
}
@@ -263,7 +259,7 @@ public class UserStorageManager implements UserProvider {
public UserModel getUserByEmail(String email, RealmModel realm) {
UserModel user = localStorage().getUserByEmail(email, realm);
if (user != null) return user;
- for (UserLookupProvider provider : getStorageProviders(realm, UserLookupProvider.class)) {
+ for (UserLookupProvider provider : getStorageProviders(session, realm, UserLookupProvider.class)) {
user = provider.getUserByEmail(email, realm);
if (user != null) return user;
}
@@ -305,7 +301,7 @@ public class UserStorageManager implements UserProvider {
@Override
public int getUsersCount(RealmModel realm) {
int size = localStorage().getUsersCount(realm);
- for (UserQueryProvider provider : getStorageProviders(realm, UserQueryProvider.class)) {
+ for (UserQueryProvider provider : getStorageProviders(session, realm, UserQueryProvider.class)) {
size += provider.getUsersCount(realm);
}
return size;
@@ -321,7 +317,7 @@ public class UserStorageManager implements UserProvider {
- List storageProviders = getStorageProviders(realm, UserQueryProvider.class);
+ List storageProviders = getStorageProviders(session, realm, UserQueryProvider.class);
// we can skip rest of method if there are no storage providers
if (storageProviders.isEmpty()) {
return pagedQuery.query(localStorage(), firstResult, maxResults);
@@ -453,7 +449,7 @@ public class UserStorageManager implements UserProvider {
@Override
public void grantToAllUsers(RealmModel realm, RoleModel role) {
// not federation-aware for now
- List storageProviders = getStorageProviders(realm, UserRegistrationProvider.class);
+ List storageProviders = getStorageProviders(session, realm, UserRegistrationProvider.class);
LinkedList providers = new LinkedList<>();
providers.add(localStorage());
providers.addAll(storageProviders);
@@ -488,7 +484,7 @@ public class UserStorageManager implements UserProvider {
public void preRemove(RealmModel realm) {
localStorage().preRemove(realm);
getFederatedStorage().preRemove(realm);
- for (UserStorageProvider provider : getStorageProviders(realm, UserStorageProvider.class)) {
+ for (UserStorageProvider provider : getStorageProviders(session, realm, UserStorageProvider.class)) {
provider.preRemove(realm);
}
}
@@ -503,7 +499,7 @@ public class UserStorageManager implements UserProvider {
public void preRemove(RealmModel realm, GroupModel group) {
localStorage().preRemove(realm, group);
getFederatedStorage().preRemove(realm, group);
- for (UserStorageProvider provider : getStorageProviders(realm, UserStorageProvider.class)) {
+ for (UserStorageProvider provider : getStorageProviders(session, realm, UserStorageProvider.class)) {
provider.preRemove(realm, group);
}
}
@@ -512,7 +508,7 @@ public class UserStorageManager implements UserProvider {
public void preRemove(RealmModel realm, RoleModel role) {
localStorage().preRemove(realm, role);
getFederatedStorage().preRemove(realm, role);
- for (UserStorageProvider provider : getStorageProviders(realm, UserStorageProvider.class)) {
+ for (UserStorageProvider provider : getStorageProviders(session, realm, UserStorageProvider.class)) {
provider.preRemove(realm, role);
}
}
@@ -564,7 +560,7 @@ public class UserStorageManager implements UserProvider {
if (toValidate.isEmpty()) return true;
- UserStorageProvider provider = getStorageProvider(realm, StorageId.resolveProviderId(user));
+ UserStorageProvider provider = getStorageProvider(session, realm, StorageId.resolveProviderId(user));
if (!(provider instanceof UserCredentialValidatorProvider)) {
return false;
}
@@ -578,7 +574,7 @@ public class UserStorageManager implements UserProvider {
@Override
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
- List providers = getStorageProviders(realm, UserCredentialAuthenticationProvider.class);
+ List providers = getStorageProviders(session, realm, UserCredentialAuthenticationProvider.class);
if (providers.isEmpty()) return CredentialValidationOutput.failed();
CredentialValidationOutput result = null;
@@ -612,154 +608,23 @@ public class UserStorageManager implements UserProvider {
}
+ @Override
+ public void onCache(RealmModel realm, CachedUserModel user) {
+ if (StorageId.isLocalStorage(user)) {
+ if (session.userLocalStorage() instanceof OnUserCache) {
+ ((OnUserCache)session.userLocalStorage()).onCache(realm, user);
+ }
+ } else {
+ Object provider = getStorageProvider(session, realm, StorageId.resolveProviderId(user));
+ if (provider != null && provider instanceof OnUserCache) {
+ ((OnUserCache)provider).onCache(realm, user);
+ }
+ }
+ }
+
@Override
public void close() {
}
- @Override
- public boolean isValid(RealmModel realm, UserModel user, List inputs) {
-
- List toValidate = new LinkedList<>();
- toValidate.addAll(inputs);
- if (!StorageId.isLocalStorage(user)) {
- String providerId = StorageId.resolveProviderId(user);
- UserStorageProvider provider = getStorageProvider(realm, providerId);
- if (provider instanceof CredentialInputValidator) {
- Iterator it = toValidate.iterator();
- while (it.hasNext()) {
- CredentialInput input = it.next();
- CredentialInputValidator validator = (CredentialInputValidator)provider;
- if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) {
- it.remove();
- }
- }
- }
- }
-
- if (toValidate.isEmpty()) return true;
-
- List components = getCredentialProviderComponents(realm);
- for (ComponentModel component : components) {
- CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
- if (!Types.supports(CredentialInputValidator.class, factory, CredentialProviderFactory.class)) continue;
- Iterator it = toValidate.iterator();
- while (it.hasNext()) {
- CredentialInput input = it.next();
- CredentialInputValidator validator = (CredentialInputValidator)session.getAttribute(component.getId());
- if (validator == null) {
- validator = (CredentialInputValidator)factory.create(session, component);
- session.setAttribute(component.getId(), validator);
- }
- if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) {
- it.remove();
- }
- }
- }
-
- return toValidate.isEmpty();
- }
-
- protected List getCredentialProviderComponents(RealmModel realm) {
- List components = realm.getComponents(realm.getId(), CredentialProvider.class.getName());
- Collections.sort(components, PrioritizedComponentModel.comparator);
- return components;
- }
-
- @Override
- public void updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
- if (!StorageId.isLocalStorage(user)) {
- String providerId = StorageId.resolveProviderId(user);
- UserStorageProvider provider = getStorageProvider(realm, providerId);
- if (provider instanceof CredentialInputUpdater) {
- CredentialInputUpdater updater = (CredentialInputUpdater)provider;
- if (updater.supportsCredentialType(input.getType())) {
- updater.updateCredential(realm, user, input);
- return;
- }
- }
- }
-
- List components = getCredentialProviderComponents(realm);
- for (ComponentModel component : components) {
- CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
- if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
- CredentialInputUpdater updater = (CredentialInputUpdater)session.getAttribute(component.getId());
- if (updater == null) {
- updater = (CredentialInputUpdater)factory.create(session, component);
- session.setAttribute(component.getId(), updater);
- }
- if (!updater.supportsCredentialType(input.getType())) continue;
- updater.updateCredential(realm, user, input);
- return;
- }
-
- }
- @Override
- public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
- if (!StorageId.isLocalStorage(user)) {
- String providerId = StorageId.resolveProviderId(user);
- UserStorageProvider provider = getStorageProvider(realm, providerId);
- if (provider instanceof CredentialInputValidator) {
- CredentialInputValidator validator = (CredentialInputValidator)provider;
- if (validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
- return true;
- }
- }
- }
-
- List components = getCredentialProviderComponents(realm);
- for (ComponentModel component : components) {
- CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
- if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
- CredentialInputValidator validator = (CredentialInputValidator)session.getAttribute(component.getId());
- if (validator == null) {
- validator = (CredentialInputValidator)factory.create(session, component);
- session.setAttribute(component.getId(), validator);
- }
- if (validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
- return true;
- }
- }
- return false;
-
- }
-
- @Override
- public Set requiredActionsFor(RealmModel realm, UserModel user, String type) {
- Set requiredActionsFor = Collections.EMPTY_SET;
- if (!StorageId.isLocalStorage(user)) {
- String providerId = StorageId.resolveProviderId(user);
- UserStorageProvider provider = getStorageProvider(realm, providerId);
- if (provider instanceof CredentialInputUpdater) {
- CredentialInputUpdater updater = (CredentialInputUpdater)provider;
- if (updater.supportsCredentialType(type)) {
- requiredActionsFor = updater.requiredActionsFor(realm, user, type);
- if (!requiredActionsFor.isEmpty()) {
- return requiredActionsFor;
- }
- }
- }
- }
-
- List components = getCredentialProviderComponents(realm);
- for (ComponentModel component : components) {
- CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
- if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
- CredentialInputUpdater updater = (CredentialInputUpdater)session.getAttribute(component.getId());
- if (updater == null) {
- updater = (CredentialInputUpdater)factory.create(session, component);
- session.setAttribute(component.getId(), updater);
- }
- if (updater.supportsCredentialType(type)) {
- requiredActionsFor = updater.requiredActionsFor(realm, user, type);
- if (!requiredActionsFor.isEmpty()) {
- return requiredActionsFor;
- }
- }
- }
- return requiredActionsFor;
-
- }
-
}