commit
2a5c778af5
66 changed files with 2931 additions and 252 deletions
102
common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java
Executable file
102
common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java
Executable file
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class ConcurrentMultivaluedHashMap<K, V> extends ConcurrentHashMap<K, List<V>>
|
||||||
|
{
|
||||||
|
public void putSingle(K key, V value)
|
||||||
|
{
|
||||||
|
List<V> 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<V> valueList)
|
||||||
|
{
|
||||||
|
for (V value : valueList)
|
||||||
|
{
|
||||||
|
add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addFirst(K key, V value)
|
||||||
|
{
|
||||||
|
List<V> 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<V> values)
|
||||||
|
{
|
||||||
|
getList(key).addAll(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public V getFirst(K key)
|
||||||
|
{
|
||||||
|
List<V> list = get(key);
|
||||||
|
return list == null ? null : list.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final List<V> getList(K key)
|
||||||
|
{
|
||||||
|
List<V> list = get(key);
|
||||||
|
if (list == null)
|
||||||
|
put(key, list = new CopyOnWriteArrayList<V>());
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAll(ConcurrentMultivaluedHashMap<K, V> other)
|
||||||
|
{
|
||||||
|
for (Entry<K, List<V>> entry : other.entrySet())
|
||||||
|
{
|
||||||
|
getList(entry.getKey()).addAll(entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,6 +25,7 @@
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<module name="javax.transaction.api"/>
|
||||||
<module name="org.keycloak.keycloak-common"/>
|
<module name="org.keycloak.keycloak-common"/>
|
||||||
<module name="org.keycloak.keycloak-core"/>
|
<module name="org.keycloak.keycloak-core"/>
|
||||||
<module name="org.keycloak.keycloak-server-spi"/>
|
<module name="org.keycloak.keycloak-server-spi"/>
|
||||||
|
|
|
@ -33,5 +33,6 @@
|
||||||
<module name="javax.ws.rs.api"/>
|
<module name="javax.ws.rs.api"/>
|
||||||
<module name="org.apache.httpcomponents"/>
|
<module name="org.apache.httpcomponents"/>
|
||||||
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
|
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
|
||||||
|
<module name="javax.transaction.api"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</module>
|
</module>
|
||||||
|
|
|
@ -74,6 +74,11 @@
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.spec.javax.transaction</groupId>
|
||||||
|
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -18,34 +18,28 @@
|
||||||
package org.keycloak.models.cache.infinispan;
|
package org.keycloak.models.cache.infinispan;
|
||||||
|
|
||||||
import org.infinispan.Cache;
|
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.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.cache.CacheUserProvider;
|
import org.keycloak.models.cache.UserCache;
|
||||||
import org.keycloak.models.cache.CacheUserProviderFactory;
|
import org.keycloak.models.cache.UserCacheProviderFactory;
|
||||||
import org.keycloak.models.cache.infinispan.entities.CachedUser;
|
|
||||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
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;
|
protected volatile UserCacheManager userCache;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CacheUserProvider create(KeycloakSession session) {
|
public UserCache create(KeycloakSession session) {
|
||||||
lazyInit(session);
|
lazyInit(session);
|
||||||
return new UserCacheSession(userCache, session);
|
return new UserCacheSession(userCache, session);
|
||||||
}
|
}
|
|
@ -28,6 +28,7 @@ import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserCredentialValueModel;
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserModel;
|
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.CachedUser;
|
||||||
import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
|
import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
@ -39,12 +40,13 @@ import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class UserAdapter implements UserModel {
|
public class UserAdapter implements CachedUserModel {
|
||||||
protected UserModel updated;
|
protected UserModel updated;
|
||||||
protected CachedUser cached;
|
protected CachedUser cached;
|
||||||
protected UserCacheSession userProviderCache;
|
protected UserCacheSession userProviderCache;
|
||||||
|
@ -65,6 +67,22 @@ public class UserAdapter implements UserModel {
|
||||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
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
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
if (updated != null) return updated.getId();
|
if (updated != null) return updated.getId();
|
||||||
|
|
|
@ -34,7 +34,9 @@ import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserProvider;
|
import org.keycloak.models.UserProvider;
|
||||||
import org.keycloak.models.cache.CacheUserProvider;
|
import org.keycloak.models.cache.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.CachedFederatedIdentityLinks;
|
||||||
import org.keycloak.models.cache.infinispan.entities.CachedUser;
|
import org.keycloak.models.cache.infinispan.entities.CachedUser;
|
||||||
import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
|
import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
|
||||||
|
@ -47,7 +49,7 @@ import java.util.*;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class UserCacheSession implements CacheUserProvider {
|
public class UserCacheSession implements UserCache {
|
||||||
protected static final Logger logger = Logger.getLogger(UserCacheSession.class);
|
protected static final Logger logger = Logger.getLogger(UserCacheSession.class);
|
||||||
protected UserCacheManager cache;
|
protected UserCacheManager cache;
|
||||||
protected KeycloakSession session;
|
protected KeycloakSession session;
|
||||||
|
@ -73,7 +75,6 @@ public class UserCacheSession implements CacheUserProvider {
|
||||||
cache.clear();
|
cache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserProvider getDelegate() {
|
public UserProvider getDelegate() {
|
||||||
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
|
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
|
||||||
if (delegate != null) return delegate;
|
if (delegate != null) return delegate;
|
||||||
|
@ -89,6 +90,20 @@ public class UserCacheSession implements CacheUserProvider {
|
||||||
if (realm.isIdentityFederationEnabled()) invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
|
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() {
|
protected void runInvalidations() {
|
||||||
for (String realmId : realmInvalidations) {
|
for (String realmId : realmInvalidations) {
|
||||||
cache.invalidateRealmUsers(realmId, invalidations);
|
cache.invalidateRealmUsers(realmId, invalidations);
|
||||||
|
@ -147,8 +162,13 @@ public class UserCacheSession implements CacheUserProvider {
|
||||||
logger.trace("registered for invalidation return delegate");
|
logger.trace("registered for invalidation return delegate");
|
||||||
return getDelegate().getUserById(id, realm);
|
return getDelegate().getUserById(id, realm);
|
||||||
}
|
}
|
||||||
|
if (managedUsers.containsKey(id)) {
|
||||||
|
logger.trace("return managedusers");
|
||||||
|
return managedUsers.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
CachedUser cached = cache.get(id, CachedUser.class);
|
CachedUser cached = cache.get(id, CachedUser.class);
|
||||||
|
boolean wasCached = cached != null;
|
||||||
if (cached == null) {
|
if (cached == null) {
|
||||||
logger.trace("not cached");
|
logger.trace("not cached");
|
||||||
Long loaded = cache.getCurrentRevision(id);
|
Long loaded = cache.getCurrentRevision(id);
|
||||||
|
@ -157,19 +177,12 @@ public class UserCacheSession implements CacheUserProvider {
|
||||||
logger.trace("delegate returning null");
|
logger.trace("delegate returning null");
|
||||||
return 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);
|
cached = new CachedUser(loaded, realm, model);
|
||||||
cache.addRevisioned(cached, startupRevision);
|
cache.addRevisioned(cached, startupRevision);
|
||||||
} else if (managedUsers.containsKey(id)) {
|
|
||||||
logger.trace("return managedusers");
|
|
||||||
return managedUsers.get(id);
|
|
||||||
}
|
}
|
||||||
logger.trace("returning new cache adapter");
|
logger.trace("returning new cache adapter");
|
||||||
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
|
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
|
||||||
|
if (!wasCached) onCache(realm, adapter);
|
||||||
managedUsers.put(id, adapter);
|
managedUsers.put(id, adapter);
|
||||||
return adapter;
|
return adapter;
|
||||||
}
|
}
|
||||||
|
@ -223,13 +236,7 @@ public class UserCacheSession implements CacheUserProvider {
|
||||||
return managedUsers.get(userId);
|
return managedUsers.get(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
CachedUser cached = cache.get(userId, CachedUser.class);
|
UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
|
||||||
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);
|
|
||||||
managedUsers.put(userId, adapter);
|
managedUsers.put(userId, adapter);
|
||||||
return adapter;
|
return adapter;
|
||||||
} else {
|
} else {
|
||||||
|
@ -244,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
|
@Override
|
||||||
public UserModel getUserByEmail(String email, RealmModel realm) {
|
public UserModel getUserByEmail(String email, RealmModel realm) {
|
||||||
if (email == null) return null;
|
if (email == null) return null;
|
||||||
|
@ -268,12 +295,7 @@ public class UserCacheSession implements CacheUserProvider {
|
||||||
if (invalidations.contains(userId)) return model;
|
if (invalidations.contains(userId)) return model;
|
||||||
if (managedUsers.containsKey(userId)) return managedUsers.get(userId);
|
if (managedUsers.containsKey(userId)) return managedUsers.get(userId);
|
||||||
|
|
||||||
CachedUser cached = cache.get(userId, CachedUser.class);
|
UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
|
||||||
if (cached == null) {
|
|
||||||
cached = new CachedUser(loaded, realm, model);
|
|
||||||
cache.addRevisioned(cached, startupRevision);
|
|
||||||
}
|
|
||||||
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
|
|
||||||
managedUsers.put(userId, adapter);
|
managedUsers.put(userId, adapter);
|
||||||
return adapter;
|
return adapter;
|
||||||
} else {
|
} else {
|
||||||
|
@ -316,12 +338,7 @@ public class UserCacheSession implements CacheUserProvider {
|
||||||
if (invalidations.contains(userId)) return model;
|
if (invalidations.contains(userId)) return model;
|
||||||
if (managedUsers.containsKey(userId)) return managedUsers.get(userId);
|
if (managedUsers.containsKey(userId)) return managedUsers.get(userId);
|
||||||
|
|
||||||
CachedUser cached = cache.get(userId, CachedUser.class);
|
UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
|
||||||
if (cached == null) {
|
|
||||||
cached = new CachedUser(loaded, realm, model);
|
|
||||||
cache.addRevisioned(cached, startupRevision);
|
|
||||||
}
|
|
||||||
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
|
|
||||||
managedUsers.put(userId, adapter);
|
managedUsers.put(userId, adapter);
|
||||||
return adapter;
|
return adapter;
|
||||||
} else {
|
} else {
|
||||||
|
@ -669,4 +686,5 @@ public class UserCacheSession implements CacheUserProvider {
|
||||||
getDelegate().preRemove(realm, component);
|
getDelegate().preRemove(realm, component);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import java.io.Serializable;
|
||||||
public class AbstractRevisioned implements Revisioned, Serializable {
|
public class AbstractRevisioned implements Revisioned, Serializable {
|
||||||
private String id;
|
private String id;
|
||||||
private Long revision;
|
private Long revision;
|
||||||
|
private final long cacheTimestamp = System.currentTimeMillis();
|
||||||
|
|
||||||
public AbstractRevisioned(Long revision, String id) {
|
public AbstractRevisioned(Long revision, String id) {
|
||||||
this.revision = revision;
|
this.revision = revision;
|
||||||
|
@ -30,4 +31,12 @@ public class AbstractRevisioned implements Revisioned, Serializable {
|
||||||
this.revision = revision;
|
this.revision = revision;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When was this cached
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long getCacheTimestamp() {
|
||||||
|
return cacheTimestamp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ import java.util.Set;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class CachedUser extends AbstractRevisioned implements InRealm {
|
public class CachedUser extends AbstractExtendableRevisioned implements InRealm {
|
||||||
private String realm;
|
private String realm;
|
||||||
private String username;
|
private String username;
|
||||||
private Long createdTimestamp;
|
private Long createdTimestamp;
|
||||||
|
|
|
@ -15,4 +15,4 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
org.keycloak.models.cache.infinispan.InfinispanCacheUserProviderFactory
|
org.keycloak.models.cache.infinispan.InfinispanUserCacheProviderFactory
|
|
@ -101,14 +101,19 @@ public class ClusteredCacheBehaviorTest {
|
||||||
|
|
||||||
System.out.println("node1 create entry");
|
System.out.println("node1 create entry");
|
||||||
node1Cache.put("key", "node1");
|
node1Cache.put("key", "node1");
|
||||||
|
|
||||||
System.out.println("node1 create entry");
|
System.out.println("node1 create entry");
|
||||||
node1Cache.put("key", "node111");
|
node1Cache.put("key", "node111");
|
||||||
|
|
||||||
System.out.println("node2 create entry");
|
System.out.println("node2 create entry");
|
||||||
node2Cache.put("key", "node2");
|
node2Cache.put("key", "node2");
|
||||||
|
|
||||||
System.out.println("node1 remove entry");
|
System.out.println("node1 remove entry");
|
||||||
node1Cache.remove("key");
|
node1Cache.remove("key");
|
||||||
|
|
||||||
System.out.println("node2 remove entry");
|
System.out.println("node2 remove entry");
|
||||||
node2Cache.remove("key");
|
node2Cache.remove("key");
|
||||||
|
|
||||||
System.out.println("node2 put entry");
|
System.out.println("node2 put entry");
|
||||||
node2Cache.put("key", "node2");
|
node2Cache.put("key", "node2");
|
||||||
System.out.println("node2 evict entry");
|
System.out.println("node2 evict entry");
|
||||||
|
@ -118,6 +123,28 @@ public class ClusteredCacheBehaviorTest {
|
||||||
node2Cache.putForExternalRead("key", "common");
|
node2Cache.putForExternalRead("key", "common");
|
||||||
System.out.println("node2 remove entry");
|
System.out.println("node2 remove entry");
|
||||||
node2Cache.remove("key");
|
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"));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package org.keycloak.connections.jpa;
|
package org.keycloak.connections.jpa;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,6 +26,7 @@ import javax.persistence.EntityManager;
|
||||||
*/
|
*/
|
||||||
public class DefaultJpaConnectionProvider implements JpaConnectionProvider {
|
public class DefaultJpaConnectionProvider implements JpaConnectionProvider {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProvider.class);
|
||||||
private final EntityManager em;
|
private final EntityManager em;
|
||||||
|
|
||||||
public DefaultJpaConnectionProvider(EntityManager em) {
|
public DefaultJpaConnectionProvider(EntityManager em) {
|
||||||
|
@ -37,6 +40,7 @@ public class DefaultJpaConnectionProvider implements JpaConnectionProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
logger.trace("DefaultJpaConnectionProvider close()");
|
||||||
em.close();
|
em.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,12 +29,22 @@ import java.util.Map;
|
||||||
import javax.naming.InitialContext;
|
import javax.naming.InitialContext;
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import javax.persistence.EntityManagerFactory;
|
import javax.persistence.EntityManagerFactory;
|
||||||
|
import javax.persistence.SynchronizationType;
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
import javax.transaction.InvalidTransactionException;
|
||||||
|
import javax.transaction.Synchronization;
|
||||||
|
import javax.transaction.SystemException;
|
||||||
|
import javax.transaction.Transaction;
|
||||||
|
import javax.transaction.TransactionManager;
|
||||||
|
import javax.transaction.UserTransaction;
|
||||||
|
|
||||||
import org.hibernate.ejb.AvailableSettings;
|
import org.hibernate.ejb.AvailableSettings;
|
||||||
|
import org.hibernate.engine.transaction.jta.platform.internal.AbstractJtaPlatform;
|
||||||
|
import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||||
|
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
|
||||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
@ -45,6 +55,7 @@ import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||||
import org.keycloak.models.dblock.DBLockManager;
|
import org.keycloak.models.dblock.DBLockManager;
|
||||||
import org.keycloak.ServerStartupError;
|
import org.keycloak.ServerStartupError;
|
||||||
import org.keycloak.timer.TimerProvider;
|
import org.keycloak.timer.TimerProvider;
|
||||||
|
import org.keycloak.transaction.JtaTransactionManagerLookup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -60,16 +71,29 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||||
private volatile EntityManagerFactory emf;
|
private volatile EntityManagerFactory emf;
|
||||||
|
|
||||||
private Config.Scope config;
|
private Config.Scope config;
|
||||||
|
|
||||||
private Map<String,String> operationalInfo;
|
private Map<String, String> operationalInfo;
|
||||||
|
|
||||||
|
private boolean jtaEnabled;
|
||||||
|
private JtaTransactionManagerLookup jtaLookup;
|
||||||
|
|
||||||
|
private KeycloakSessionFactory factory;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JpaConnectionProvider create(KeycloakSession session) {
|
public JpaConnectionProvider create(KeycloakSession session) {
|
||||||
|
logger.trace("Create JpaConnectionProvider");
|
||||||
lazyInit(session);
|
lazyInit(session);
|
||||||
|
|
||||||
EntityManager em = emf.createEntityManager();
|
EntityManager em = null;
|
||||||
|
if (!jtaEnabled) {
|
||||||
|
logger.trace("enlisting EntityManager in JpaKeycloakTransaction");
|
||||||
|
em = emf.createEntityManager();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
em = emf.createEntityManager(SynchronizationType.SYNCHRONIZED);
|
||||||
|
}
|
||||||
em = PersistenceExceptionConverter.create(em);
|
em = PersistenceExceptionConverter.create(em);
|
||||||
session.getTransactionManager().enlist(new JpaKeycloakTransaction(em));
|
if (!jtaEnabled) session.getTransactionManager().enlist(new JpaKeycloakTransaction(em));
|
||||||
return new DefaultJpaConnectionProvider(em);
|
return new DefaultJpaConnectionProvider(em);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,85 +116,112 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
this.factory = factory;
|
||||||
|
checkJtaEnabled(factory);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void checkJtaEnabled(KeycloakSessionFactory factory) {
|
||||||
|
jtaLookup = (JtaTransactionManagerLookup) factory.getProviderFactory(JtaTransactionManagerLookup.class);
|
||||||
|
if (jtaLookup != null) {
|
||||||
|
if (jtaLookup.getTransactionManager() != null) {
|
||||||
|
jtaEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void lazyInit(KeycloakSession session) {
|
private void lazyInit(KeycloakSession session) {
|
||||||
if (emf == null) {
|
if (emf == null) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (emf == null) {
|
if (emf == null) {
|
||||||
logger.debug("Initializing JPA connections");
|
KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
|
||||||
|
logger.debug("Initializing JPA connections");
|
||||||
|
|
||||||
Map<String, Object> properties = new HashMap<String, Object>();
|
Map<String, Object> properties = new HashMap<String, Object>();
|
||||||
|
|
||||||
String unitName = "keycloak-default";
|
String unitName = "keycloak-default";
|
||||||
|
|
||||||
String dataSource = config.get("dataSource");
|
String dataSource = config.get("dataSource");
|
||||||
if (dataSource != null) {
|
if (dataSource != null) {
|
||||||
if (config.getBoolean("jta", false)) {
|
if (config.getBoolean("jta", jtaEnabled)) {
|
||||||
properties.put(AvailableSettings.JTA_DATASOURCE, dataSource);
|
properties.put(AvailableSettings.JTA_DATASOURCE, dataSource);
|
||||||
|
} else {
|
||||||
|
properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource);
|
properties.put(AvailableSettings.JDBC_URL, config.get("url"));
|
||||||
}
|
properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver"));
|
||||||
} else {
|
|
||||||
properties.put(AvailableSettings.JDBC_URL, config.get("url"));
|
|
||||||
properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver"));
|
|
||||||
|
|
||||||
String user = config.get("user");
|
String user = config.get("user");
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
properties.put(AvailableSettings.JDBC_USER, user);
|
properties.put(AvailableSettings.JDBC_USER, user);
|
||||||
}
|
}
|
||||||
String password = config.get("password");
|
String password = config.get("password");
|
||||||
if (password != null) {
|
if (password != null) {
|
||||||
properties.put(AvailableSettings.JDBC_PASSWORD, password);
|
properties.put(AvailableSettings.JDBC_PASSWORD, password);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
String schema = getSchema();
|
|
||||||
if (schema != null) {
|
|
||||||
properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
MigrationStrategy migrationStrategy = getMigrationStrategy();
|
|
||||||
boolean initializeEmpty = config.getBoolean("initializeEmpty", true);
|
|
||||||
File databaseUpdateFile = getDatabaseUpdateFile();
|
|
||||||
|
|
||||||
properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
|
|
||||||
properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
|
|
||||||
|
|
||||||
Connection connection = getConnection();
|
|
||||||
try{
|
|
||||||
prepareOperationalInfo(connection);
|
|
||||||
|
|
||||||
String driverDialect = detectDialect(connection);
|
|
||||||
if (driverDialect != null) {
|
|
||||||
properties.put("hibernate.dialect", driverDialect);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
migration(migrationStrategy, initializeEmpty, schema, databaseUpdateFile, connection, session);
|
String schema = getSchema();
|
||||||
|
if (schema != null) {
|
||||||
int globalStatsInterval = config.getInt("globalStatsInterval", -1);
|
properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
|
||||||
if (globalStatsInterval != -1) {
|
|
||||||
properties.put("hibernate.generate_statistics", true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.trace("Creating EntityManagerFactory");
|
MigrationStrategy migrationStrategy = getMigrationStrategy();
|
||||||
emf = JpaUtils.createEntityManagerFactory(session, unitName, properties, getClass().getClassLoader());
|
boolean initializeEmpty = config.getBoolean("initializeEmpty", true);
|
||||||
logger.trace("EntityManagerFactory created");
|
File databaseUpdateFile = getDatabaseUpdateFile();
|
||||||
|
|
||||||
if (globalStatsInterval != -1) {
|
properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
|
||||||
startGlobalStats(session, globalStatsInterval);
|
properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
|
||||||
|
|
||||||
|
Connection connection = getConnection();
|
||||||
|
try {
|
||||||
|
prepareOperationalInfo(connection);
|
||||||
|
|
||||||
|
String driverDialect = detectDialect(connection);
|
||||||
|
if (driverDialect != null) {
|
||||||
|
properties.put("hibernate.dialect", driverDialect);
|
||||||
|
}
|
||||||
|
|
||||||
|
migration(migrationStrategy, initializeEmpty, schema, databaseUpdateFile, connection, session);
|
||||||
|
|
||||||
|
int globalStatsInterval = config.getInt("globalStatsInterval", -1);
|
||||||
|
if (globalStatsInterval != -1) {
|
||||||
|
properties.put("hibernate.generate_statistics", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.trace("Creating EntityManagerFactory");
|
||||||
|
logger.tracev("***** create EMF jtaEnabled {0} ", jtaEnabled);
|
||||||
|
if (jtaEnabled) {
|
||||||
|
properties.put(org.hibernate.cfg.AvailableSettings.JTA_PLATFORM, new AbstractJtaPlatform() {
|
||||||
|
@Override
|
||||||
|
protected TransactionManager locateTransactionManager() {
|
||||||
|
return jtaLookup.getTransactionManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UserTransaction locateUserTransaction() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
emf = JpaUtils.createEntityManagerFactory(session, unitName, properties, getClass().getClassLoader(), jtaEnabled);
|
||||||
|
logger.trace("EntityManagerFactory created");
|
||||||
|
|
||||||
|
if (globalStatsInterval != -1) {
|
||||||
|
startGlobalStats(session, globalStatsInterval);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// Close after creating EntityManagerFactory to prevent in-mem databases from closing
|
||||||
|
if (connection != null) {
|
||||||
|
try {
|
||||||
|
connection.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.warn("Can't close connection", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
});
|
||||||
// Close after creating EntityManagerFactory to prevent in-mem databases from closing
|
|
||||||
if (connection != null) {
|
|
||||||
try {
|
|
||||||
connection.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logger.warn("Can't close connection", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,19 +233,19 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void prepareOperationalInfo(Connection connection) {
|
protected void prepareOperationalInfo(Connection connection) {
|
||||||
try {
|
try {
|
||||||
operationalInfo = new LinkedHashMap<>();
|
operationalInfo = new LinkedHashMap<>();
|
||||||
DatabaseMetaData md = connection.getMetaData();
|
DatabaseMetaData md = connection.getMetaData();
|
||||||
operationalInfo.put("databaseUrl",md.getURL());
|
operationalInfo.put("databaseUrl", md.getURL());
|
||||||
operationalInfo.put("databaseUser", md.getUserName());
|
operationalInfo.put("databaseUser", md.getUserName());
|
||||||
operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion());
|
operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion());
|
||||||
operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion());
|
operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion());
|
||||||
|
|
||||||
logger.debugf("Database info: %s", operationalInfo.toString());
|
logger.debugf("Database info: %s", operationalInfo.toString());
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
logger.warn("Unable to prepare operational info due database exception: " + e.getMessage());
|
logger.warn("Unable to prepare operational info due database exception: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected String detectDialect(Connection connection) {
|
protected String detectDialect(Connection connection) {
|
||||||
|
@ -334,11 +385,11 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||||
public String getSchema() {
|
public String getSchema() {
|
||||||
return config.get("schema");
|
return config.get("schema");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String,String> getOperationalInfo() {
|
public Map<String, String> getOperationalInfo() {
|
||||||
return operationalInfo;
|
return operationalInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MigrationStrategy getMigrationStrategy() {
|
private MigrationStrategy getMigrationStrategy() {
|
||||||
String migrationStrategy = config.get("migrationStrategy");
|
String migrationStrategy = config.get("migrationStrategy");
|
||||||
|
|
|
@ -23,14 +23,13 @@ import java.sql.SQLException;
|
||||||
import liquibase.Liquibase;
|
import liquibase.Liquibase;
|
||||||
import liquibase.exception.DatabaseException;
|
import liquibase.exception.DatabaseException;
|
||||||
import liquibase.exception.LiquibaseException;
|
import liquibase.exception.LiquibaseException;
|
||||||
import liquibase.exception.LockException;
|
|
||||||
import liquibase.lockservice.LockService;
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||||
import org.keycloak.connections.jpa.JpaConnectionProviderFactory;
|
import org.keycloak.connections.jpa.JpaConnectionProviderFactory;
|
||||||
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
|
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.dblock.DBLockProvider;
|
import org.keycloak.models.dblock.DBLockProvider;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
@ -57,6 +56,7 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void lazyInit() {
|
private void lazyInit() {
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
|
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
|
||||||
|
@ -92,35 +92,41 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void waitForLock() {
|
public void waitForLock() {
|
||||||
lazyInit();
|
KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
|
||||||
|
|
||||||
while (maxAttempts > 0) {
|
lazyInit();
|
||||||
try {
|
|
||||||
lockService.waitForLock();
|
while (maxAttempts > 0) {
|
||||||
factory.setHasLock(true);
|
try {
|
||||||
this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
|
lockService.waitForLock();
|
||||||
return;
|
factory.setHasLock(true);
|
||||||
} catch (LockRetryException le) {
|
this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
|
||||||
// Indicates we should try to acquire lock again in different transaction
|
return;
|
||||||
safeRollbackConnection();
|
} catch (LockRetryException le) {
|
||||||
restart();
|
// Indicates we should try to acquire lock again in different transaction
|
||||||
maxAttempts--;
|
safeRollbackConnection();
|
||||||
} catch (RuntimeException re) {
|
restart();
|
||||||
safeRollbackConnection();
|
maxAttempts--;
|
||||||
safeCloseConnection();
|
} catch (RuntimeException re) {
|
||||||
throw re;
|
safeRollbackConnection();
|
||||||
|
safeCloseConnection();
|
||||||
|
throw re;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releaseLock() {
|
public void releaseLock() {
|
||||||
lazyInit();
|
KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
|
||||||
|
lazyInit();
|
||||||
|
|
||||||
lockService.releaseLock();
|
lockService.releaseLock();
|
||||||
lockService.reset();
|
lockService.reset();
|
||||||
factory.setHasLock(false);
|
factory.setHasLock(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -136,21 +142,25 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroyLockInfo() {
|
public void destroyLockInfo() {
|
||||||
lazyInit();
|
KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
|
||||||
|
lazyInit();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.lockService.destroy();
|
this.lockService.destroy();
|
||||||
dbConnection.commit();
|
dbConnection.commit();
|
||||||
logger.debug("Destroyed lock table");
|
logger.debug("Destroyed lock table");
|
||||||
} catch (DatabaseException | SQLException de) {
|
} catch (DatabaseException | SQLException de) {
|
||||||
logger.error("Failed to destroy lock table");
|
logger.error("Failed to destroy lock table");
|
||||||
safeRollbackConnection();
|
safeRollbackConnection();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
safeCloseConnection();
|
KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
|
||||||
|
safeCloseConnection();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void safeRollbackConnection() {
|
private void safeRollbackConnection() {
|
||||||
|
|
|
@ -21,6 +21,8 @@ import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl;
|
||||||
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
|
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
|
||||||
import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
|
import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
|
||||||
import org.hibernate.jpa.boot.spi.Bootstrap;
|
import org.hibernate.jpa.boot.spi.Bootstrap;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
|
||||||
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
|
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
|
||||||
import org.keycloak.connections.jpa.entityprovider.ProxyClassLoader;
|
import org.keycloak.connections.jpa.entityprovider.ProxyClassLoader;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -46,8 +48,9 @@ public class JpaUtils {
|
||||||
return (schema==null) ? tableName : schema + "." + tableName;
|
return (schema==null) ? tableName : schema + "." + tableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static EntityManagerFactory createEntityManagerFactory(KeycloakSession session, String unitName, Map<String, Object> properties, ClassLoader classLoader) {
|
public static EntityManagerFactory createEntityManagerFactory(KeycloakSession session, String unitName, Map<String, Object> properties, ClassLoader classLoader, boolean jta) {
|
||||||
PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(classLoader), PersistenceUnitTransactionType.RESOURCE_LOCAL);
|
PersistenceUnitTransactionType txType = jta ? PersistenceUnitTransactionType.JTA : PersistenceUnitTransactionType.RESOURCE_LOCAL;
|
||||||
|
PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(classLoader), txType);
|
||||||
List<ParsedPersistenceXmlDescriptor> persistenceUnits = parser.doResolve(properties);
|
List<ParsedPersistenceXmlDescriptor> persistenceUnits = parser.doResolve(properties);
|
||||||
for (ParsedPersistenceXmlDescriptor persistenceUnit : persistenceUnits) {
|
for (ParsedPersistenceXmlDescriptor persistenceUnit : persistenceUnits) {
|
||||||
if (persistenceUnit.getName().equals(unitName)) {
|
if (persistenceUnit.getName().equals(unitName)) {
|
||||||
|
@ -58,6 +61,7 @@ public class JpaUtils {
|
||||||
}
|
}
|
||||||
// Now build the entity manager factory, supplying a proxy classloader, so Hibernate will be able
|
// Now build the entity manager factory, supplying a proxy classloader, so Hibernate will be able
|
||||||
// to find and load the extra provided entities. Set the provided classloader as parent classloader.
|
// to find and load the extra provided entities. Set the provided classloader as parent classloader.
|
||||||
|
persistenceUnit.setTransactionType(txType);
|
||||||
return Bootstrap.getEntityManagerFactoryBuilder(persistenceUnit, properties,
|
return Bootstrap.getEntityManagerFactoryBuilder(persistenceUnit, properties,
|
||||||
new ProxyClassLoader(providedEntities, classLoader)).build();
|
new ProxyClassLoader(providedEntities, classLoader)).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @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<String, String> attrs = cred.getConfig();
|
||||||
|
MultivaluedHashMap<String, String> config = cred.getConfig();
|
||||||
|
if (config == null) config = new MultivaluedHashMap<>();
|
||||||
|
|
||||||
|
Iterator<CredentialAttributeEntity> it = entity.getCredentialAttributes().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
CredentialAttributeEntity attr = it.next();
|
||||||
|
List<String> 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<String> values = config.getList(key);
|
||||||
|
List<String> 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<String, String> config = cred.getConfig();
|
||||||
|
if (config != null || !config.isEmpty()) {
|
||||||
|
|
||||||
|
for (String key : config.keySet()) {
|
||||||
|
List<String> 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<String, String> config = new MultivaluedHashMap<>();
|
||||||
|
model.setConfig(config);
|
||||||
|
for (CredentialAttributeEntity attr : entity.getCredentialAttributes()) {
|
||||||
|
config.add(attr.getName(), attr.getValue());
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
|
||||||
|
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
||||||
|
TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUser", CredentialEntity.class)
|
||||||
|
.setParameter("user", userEntity);
|
||||||
|
List<CredentialEntity> results = query.getResultList();
|
||||||
|
List<CredentialModel> rtn = new LinkedList<>();
|
||||||
|
for (CredentialEntity entity : results) {
|
||||||
|
rtn.add(toModel(entity));
|
||||||
|
}
|
||||||
|
return rtn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
|
||||||
|
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
||||||
|
TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUserAndType", CredentialEntity.class)
|
||||||
|
.setParameter("type", type)
|
||||||
|
.setParameter("user", userEntity);
|
||||||
|
List<CredentialEntity> results = query.getResultList();
|
||||||
|
List<CredentialModel> 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<CredentialEntity> query = em.createNamedQuery("credentialByNameAndType", CredentialEntity.class)
|
||||||
|
.setParameter("type", type)
|
||||||
|
.setParameter("device", name)
|
||||||
|
.setParameter("user", userEntity);
|
||||||
|
List<CredentialEntity> results = query.getResultList();
|
||||||
|
if (results.isEmpty()) return null;
|
||||||
|
return toModel(results.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class JpaUserCredentialStoreFactory implements ProviderFactory<UserCredentialStore> {
|
||||||
|
|
||||||
|
@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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,7 +17,11 @@
|
||||||
|
|
||||||
package org.keycloak.models.jpa;
|
package org.keycloak.models.jpa;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.component.ComponentModel;
|
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.ClientModel;
|
||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
|
@ -34,6 +38,8 @@ import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserProvider;
|
import org.keycloak.models.UserProvider;
|
||||||
|
import org.keycloak.models.jpa.entities.CredentialAttributeEntity;
|
||||||
|
import org.keycloak.models.jpa.entities.CredentialEntity;
|
||||||
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
|
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserAttributeEntity;
|
import org.keycloak.models.jpa.entities.UserAttributeEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserConsentEntity;
|
import org.keycloak.models.jpa.entities.UserConsentEntity;
|
||||||
|
@ -48,7 +54,9 @@ import javax.persistence.EntityManager;
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -58,7 +66,7 @@ import java.util.Set;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class JpaUserProvider implements UserProvider {
|
public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
||||||
|
|
||||||
private static final String EMAIL = "email";
|
private static final String EMAIL = "email";
|
||||||
private static final String USERNAME = "username";
|
private static final String USERNAME = "username";
|
||||||
|
@ -367,6 +375,8 @@ public class JpaUserProvider implements UserProvider {
|
||||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
num = em.createNamedQuery("deleteFederatedIdentityByRealm")
|
num = em.createNamedQuery("deleteFederatedIdentityByRealm")
|
||||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
|
num = em.createNamedQuery("deleteCredentialAttributeByRealm")
|
||||||
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
num = em.createNamedQuery("deleteCredentialsByRealm")
|
num = em.createNamedQuery("deleteCredentialsByRealm")
|
||||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
num = em.createNamedQuery("deleteUserAttributesByRealm")
|
num = em.createNamedQuery("deleteUserAttributesByRealm")
|
||||||
|
@ -391,6 +401,10 @@ public class JpaUserProvider implements UserProvider {
|
||||||
.setParameter("realmId", realm.getId())
|
.setParameter("realmId", realm.getId())
|
||||||
.setParameter("link", link.getId())
|
.setParameter("link", link.getId())
|
||||||
.executeUpdate();
|
.executeUpdate();
|
||||||
|
num = em.createNamedQuery("deleteCredentialAttributeByRealmAndLink")
|
||||||
|
.setParameter("realmId", realm.getId())
|
||||||
|
.setParameter("link", link.getId())
|
||||||
|
.executeUpdate();
|
||||||
num = em.createNamedQuery("deleteCredentialsByRealmAndLink")
|
num = em.createNamedQuery("deleteCredentialsByRealmAndLink")
|
||||||
.setParameter("realmId", realm.getId())
|
.setParameter("realmId", realm.getId())
|
||||||
.setParameter("link", link.getId())
|
.setParameter("link", link.getId())
|
||||||
|
@ -716,4 +730,174 @@ public class JpaUserProvider implements UserProvider {
|
||||||
public void preRemove(RealmModel realm, ComponentModel component) {
|
public void preRemove(RealmModel realm, ComponentModel component) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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<String, String> attrs = cred.getConfig();
|
||||||
|
MultivaluedHashMap<String, String> config = cred.getConfig();
|
||||||
|
if (config == null) config = new MultivaluedHashMap<>();
|
||||||
|
|
||||||
|
Iterator<CredentialAttributeEntity> it = entity.getCredentialAttributes().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
CredentialAttributeEntity attr = it.next();
|
||||||
|
List<String> 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<String> values = config.getList(key);
|
||||||
|
List<String> 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<String, String> config = cred.getConfig();
|
||||||
|
if (config != null || !config.isEmpty()) {
|
||||||
|
|
||||||
|
for (String key : config.keySet()) {
|
||||||
|
List<String> 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<String, String> config = new MultivaluedHashMap<>();
|
||||||
|
model.setConfig(config);
|
||||||
|
for (CredentialAttributeEntity attr : entity.getCredentialAttributes()) {
|
||||||
|
config.add(attr.getName(), attr.getValue());
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
|
||||||
|
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
||||||
|
TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUser", CredentialEntity.class)
|
||||||
|
.setParameter("user", userEntity);
|
||||||
|
List<CredentialEntity> results = query.getResultList();
|
||||||
|
List<CredentialModel> rtn = new LinkedList<>();
|
||||||
|
for (CredentialEntity entity : results) {
|
||||||
|
rtn.add(toModel(entity));
|
||||||
|
}
|
||||||
|
return rtn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
|
||||||
|
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
||||||
|
TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUserAndType", CredentialEntity.class)
|
||||||
|
.setParameter("type", type)
|
||||||
|
.setParameter("user", userEntity);
|
||||||
|
List<CredentialEntity> results = query.getResultList();
|
||||||
|
List<CredentialModel> 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<CredentialEntity> query = em.createNamedQuery("credentialByNameAndType", CredentialEntity.class)
|
||||||
|
.setParameter("type", type)
|
||||||
|
.setParameter("device", name)
|
||||||
|
.setParameter("user", userEntity);
|
||||||
|
List<CredentialEntity> results = query.getResultList();
|
||||||
|
if (results.isEmpty()) return null;
|
||||||
|
return toModel(results.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* 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.entities;
|
||||||
|
|
||||||
|
import javax.persistence.Access;
|
||||||
|
import javax.persistence.AccessType;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
import javax.persistence.NamedQueries;
|
||||||
|
import javax.persistence.NamedQuery;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
@NamedQueries({
|
||||||
|
@NamedQuery(name="getCredentialAttribute", query="select attr from CredentialAttributeEntity attr where attr.credential = :credential"),
|
||||||
|
@NamedQuery(name="deleteCredentialAttributeByCredential", query="delete from CredentialAttributeEntity attr where attr.credential = :credential"),
|
||||||
|
@NamedQuery(name="deleteCredentialAttributeByRealm", query="delete from CredentialAttributeEntity attr where attr.credential IN (select cred from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId))"),
|
||||||
|
@NamedQuery(name="deleteCredentialAttributeByRealmAndLink", query="delete from CredentialAttributeEntity attr where attr.credential IN (select cred from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link))"),
|
||||||
|
@NamedQuery(name="deleteCredentialAttributeByUser", query="delete from CredentialAttributeEntity attr where attr.credential IN (select cred from CredentialEntity cred where cred.user = :user)"),
|
||||||
|
})
|
||||||
|
@Table(name="CREDENTIAL_ATTRIBUTE")
|
||||||
|
@Entity
|
||||||
|
public class CredentialAttributeEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column(name="ID", length = 36)
|
||||||
|
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
|
||||||
|
protected String id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch= FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "CREDENTIAL_ID")
|
||||||
|
protected CredentialEntity credential;
|
||||||
|
|
||||||
|
@Column(name = "NAME")
|
||||||
|
protected String name;
|
||||||
|
@Column(name = "VALUE")
|
||||||
|
protected String value;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CredentialEntity getCredential() {
|
||||||
|
return credential;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCredential(CredentialEntity credential) {
|
||||||
|
this.credential = credential;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null) return false;
|
||||||
|
if (!(o instanceof CredentialAttributeEntity)) return false;
|
||||||
|
|
||||||
|
CredentialAttributeEntity that = (CredentialAttributeEntity) o;
|
||||||
|
|
||||||
|
if (!id.equals(that.getId())) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return id.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ package org.keycloak.models.jpa.entities;
|
||||||
|
|
||||||
import javax.persistence.Access;
|
import javax.persistence.Access;
|
||||||
import javax.persistence.AccessType;
|
import javax.persistence.AccessType;
|
||||||
|
import javax.persistence.CascadeType;
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.FetchType;
|
import javax.persistence.FetchType;
|
||||||
|
@ -27,14 +28,19 @@ import javax.persistence.JoinColumn;
|
||||||
import javax.persistence.ManyToOne;
|
import javax.persistence.ManyToOne;
|
||||||
import javax.persistence.NamedQueries;
|
import javax.persistence.NamedQueries;
|
||||||
import javax.persistence.NamedQuery;
|
import javax.persistence.NamedQuery;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
@NamedQueries({
|
@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="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="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)")
|
@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)")
|
||||||
|
|
||||||
|
@ -74,6 +80,8 @@ public class CredentialEntity {
|
||||||
@Column(name="PERIOD")
|
@Column(name="PERIOD")
|
||||||
protected int period;
|
protected int period;
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy="credential")
|
||||||
|
protected Collection<CredentialAttributeEntity> credentialAttributes = new ArrayList<>();
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
|
@ -171,6 +179,14 @@ public class CredentialEntity {
|
||||||
this.period = period;
|
this.period = period;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Collection<CredentialAttributeEntity> getCredentialAttributes() {
|
||||||
|
return credentialAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCredentialAttributes(Collection<CredentialAttributeEntity> credentialAttributes) {
|
||||||
|
this.credentialAttributes = credentialAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @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<String, String> attrs = cred.getConfig();
|
||||||
|
MultivaluedHashMap<String, String> config = cred.getConfig();
|
||||||
|
if (config == null) config = new MultivaluedHashMap<>();
|
||||||
|
|
||||||
|
Iterator<FederatedUserCredentialAttributeEntity> it = entity.getCredentialAttributes().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
FederatedUserCredentialAttributeEntity attr = it.next();
|
||||||
|
List<String> 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<String> values = config.getList(key);
|
||||||
|
List<String> 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<String, String> config = cred.getConfig();
|
||||||
|
if (config != null || !config.isEmpty()) {
|
||||||
|
|
||||||
|
for (String key : config.keySet()) {
|
||||||
|
List<String> 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<String, String> config = new MultivaluedHashMap<>();
|
||||||
|
model.setConfig(config);
|
||||||
|
for (FederatedUserCredentialAttributeEntity attr : entity.getCredentialAttributes()) {
|
||||||
|
config.add(attr.getName(), attr.getValue());
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
|
||||||
|
TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByUser", FederatedUserCredentialEntity.class)
|
||||||
|
.setParameter("userId", user.getId());
|
||||||
|
List<FederatedUserCredentialEntity> results = query.getResultList();
|
||||||
|
List<CredentialModel> rtn = new LinkedList<>();
|
||||||
|
for (FederatedUserCredentialEntity entity : results) {
|
||||||
|
rtn.add(toModel(entity));
|
||||||
|
}
|
||||||
|
return rtn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
|
||||||
|
TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByUserAndType", FederatedUserCredentialEntity.class)
|
||||||
|
.setParameter("type", type)
|
||||||
|
.setParameter("userId", user.getId());
|
||||||
|
List<FederatedUserCredentialEntity> results = query.getResultList();
|
||||||
|
List<CredentialModel> 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<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByNameAndType", FederatedUserCredentialEntity.class)
|
||||||
|
.setParameter("type", type)
|
||||||
|
.setParameter("device", name)
|
||||||
|
.setParameter("userId", user.getId());
|
||||||
|
List<FederatedUserCredentialEntity> results = query.getResultList();
|
||||||
|
if (results.isEmpty()) return null;
|
||||||
|
return toModel(results.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,8 @@ package org.keycloak.storage.jpa;
|
||||||
|
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
|
import org.keycloak.credential.UserCredentialStore;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
|
@ -49,6 +51,7 @@ import org.keycloak.storage.jpa.entity.FederatedUserAttributeEntity;
|
||||||
import org.keycloak.storage.jpa.entity.FederatedUserConsentEntity;
|
import org.keycloak.storage.jpa.entity.FederatedUserConsentEntity;
|
||||||
import org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity;
|
import org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity;
|
||||||
import org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity;
|
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.FederatedUserCredentialEntity;
|
||||||
import org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity;
|
import org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity;
|
||||||
import org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity;
|
import org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity;
|
||||||
|
@ -59,6 +62,7 @@ import javax.persistence.TypedQuery;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -75,7 +79,8 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
UserCredentialsFederatedStorage,
|
UserCredentialsFederatedStorage,
|
||||||
UserGroupMembershipFederatedStorage,
|
UserGroupMembershipFederatedStorage,
|
||||||
UserRequiredActionsFederatedStorage,
|
UserRequiredActionsFederatedStorage,
|
||||||
UserRoleMappingsFederatedStorage {
|
UserRoleMappingsFederatedStorage,
|
||||||
|
UserCredentialStore {
|
||||||
|
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
protected EntityManager em;
|
protected EntityManager em;
|
||||||
|
@ -606,6 +611,200 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
em.flush();
|
em.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeCredential(RealmModel realm, String id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel getCredentialById(String id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CredentialModel> getCredentials(RealmModel realm) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CredentialModel> getUserCredentials(RealmModel realm, UserModel user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CredentialModel> getCredentialsByType(RealmModel realm, UserModel user, String type) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialModel getCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
|
||||||
|
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<String, String> attrs = cred.getConfig();
|
||||||
|
MultivaluedHashMap<String, String> config = cred.getConfig();
|
||||||
|
if (config == null) config = new MultivaluedHashMap<>();
|
||||||
|
|
||||||
|
Iterator<FederatedUserCredentialAttributeEntity> it = entity.getCredentialAttributes().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
FederatedUserCredentialAttributeEntity attr = it.next();
|
||||||
|
List<String> 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<String> values = config.getList(key);
|
||||||
|
List<String> 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<String, String> config = cred.getConfig();
|
||||||
|
if (config != null || !config.isEmpty()) {
|
||||||
|
|
||||||
|
for (String key : config.keySet()) {
|
||||||
|
List<String> 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<String, String> config = new MultivaluedHashMap<>();
|
||||||
|
model.setConfig(config);
|
||||||
|
for (FederatedUserCredentialAttributeEntity attr : entity.getCredentialAttributes()) {
|
||||||
|
config.add(attr.getName(), attr.getValue());
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
|
||||||
|
TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByUser", FederatedUserCredentialEntity.class)
|
||||||
|
.setParameter("userId", user.getId());
|
||||||
|
List<FederatedUserCredentialEntity> results = query.getResultList();
|
||||||
|
List<CredentialModel> rtn = new LinkedList<>();
|
||||||
|
for (FederatedUserCredentialEntity entity : results) {
|
||||||
|
rtn.add(toModel(entity));
|
||||||
|
}
|
||||||
|
return rtn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
|
||||||
|
TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByUserAndType", FederatedUserCredentialEntity.class)
|
||||||
|
.setParameter("type", type)
|
||||||
|
.setParameter("userId", user.getId());
|
||||||
|
List<FederatedUserCredentialEntity> results = query.getResultList();
|
||||||
|
List<CredentialModel> 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<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByNameAndType", FederatedUserCredentialEntity.class)
|
||||||
|
.setParameter("type", type)
|
||||||
|
.setParameter("device", name)
|
||||||
|
.setParameter("userId", user.getId());
|
||||||
|
List<FederatedUserCredentialEntity> results = query.getResultList();
|
||||||
|
if (results.isEmpty()) return null;
|
||||||
|
return toModel(results.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preRemove(RealmModel realm) {
|
public void preRemove(RealmModel realm) {
|
||||||
int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm")
|
int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm")
|
||||||
|
@ -620,6 +819,8 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
num = em.createNamedQuery("deleteBrokerLinkByRealm")
|
num = em.createNamedQuery("deleteBrokerLinkByRealm")
|
||||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
|
num = em.createNamedQuery("deleteFederatedCredentialAttributeByRealm")
|
||||||
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
num = em.createNamedQuery("deleteFederatedUserCredentialsByRealm")
|
num = em.createNamedQuery("deleteFederatedUserCredentialsByRealm")
|
||||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
num = em.createNamedQuery("deleteUserFederatedAttributesByRealm")
|
num = em.createNamedQuery("deleteUserFederatedAttributesByRealm")
|
||||||
|
@ -642,6 +843,10 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
.setParameter("realmId", realm.getId())
|
.setParameter("realmId", realm.getId())
|
||||||
.setParameter("link", link.getId())
|
.setParameter("link", link.getId())
|
||||||
.executeUpdate();
|
.executeUpdate();
|
||||||
|
num = em.createNamedQuery("deleteFederatedCredentialAttributeByRealmAndLink")
|
||||||
|
.setParameter("realmId", realm.getId())
|
||||||
|
.setParameter("link", link.getId())
|
||||||
|
.executeUpdate();
|
||||||
num = em.createNamedQuery("deleteFederatedUserCredentialsByRealmAndLink")
|
num = em.createNamedQuery("deleteFederatedUserCredentialsByRealmAndLink")
|
||||||
.setParameter("realmId", realm.getId())
|
.setParameter("realmId", realm.getId())
|
||||||
.setParameter("link", link.getId())
|
.setParameter("link", link.getId())
|
||||||
|
@ -699,6 +904,10 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
.setParameter("userId", user.getId())
|
.setParameter("userId", user.getId())
|
||||||
.setParameter("realmId", realm.getId())
|
.setParameter("realmId", realm.getId())
|
||||||
.executeUpdate();
|
.executeUpdate();
|
||||||
|
em.createNamedQuery("deleteFederatedCredentialAttributeByUser")
|
||||||
|
.setParameter("userId", user.getId())
|
||||||
|
.setParameter("realmId", realm.getId())
|
||||||
|
.executeUpdate();
|
||||||
em.createNamedQuery("deleteFederatedUserCredentialByUser")
|
em.createNamedQuery("deleteFederatedUserCredentialByUser")
|
||||||
.setParameter("userId", user.getId())
|
.setParameter("userId", user.getId())
|
||||||
.setParameter("realmId", realm.getId())
|
.setParameter("realmId", realm.getId())
|
||||||
|
@ -737,6 +946,9 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
em.createNamedQuery("deleteFederatedUserConsentsByStorageProvider")
|
em.createNamedQuery("deleteFederatedUserConsentsByStorageProvider")
|
||||||
.setParameter("storageProviderId", model.getId())
|
.setParameter("storageProviderId", model.getId())
|
||||||
.executeUpdate();
|
.executeUpdate();
|
||||||
|
em.createNamedQuery("deleteFederatedCredentialAttributeByStorageProvider")
|
||||||
|
.setParameter("storageProviderId", model.getId())
|
||||||
|
.executeUpdate();
|
||||||
em.createNamedQuery("deleteFederatedUserCredentialsByStorageProvider")
|
em.createNamedQuery("deleteFederatedUserCredentialsByStorageProvider")
|
||||||
.setParameter("storageProviderId", model.getId())
|
.setParameter("storageProviderId", model.getId())
|
||||||
.executeUpdate();
|
.executeUpdate();
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* 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.entity;
|
||||||
|
|
||||||
|
import org.keycloak.models.jpa.entities.CredentialEntity;
|
||||||
|
|
||||||
|
import javax.persistence.Access;
|
||||||
|
import javax.persistence.AccessType;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
import javax.persistence.NamedQueries;
|
||||||
|
import javax.persistence.NamedQuery;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
@NamedQueries({
|
||||||
|
@NamedQuery(name="deleteFederatedCredentialAttributeByCredential", query="delete from FederatedUserCredentialAttributeEntity attr where attr.credential = :credential"),
|
||||||
|
@NamedQuery(name="deleteFederatedCredentialAttributeByStorageProvider", query="delete from FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.storageProviderId=:storageProviderId)"),
|
||||||
|
@NamedQuery(name="deleteFederatedCredentialAttributeByRealm", query="delete from FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.realmId=:realmId)"),
|
||||||
|
@NamedQuery(name="deleteFederatedCredentialAttributeByRealmAndLink", query="delete from FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link))"),
|
||||||
|
@NamedQuery(name="deleteFederatedCredentialAttributeByUser", query="delete from FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.realmId = :realmId)"),
|
||||||
|
})
|
||||||
|
@Table(name="FED_CREDENTIAL_ATTRIBUTE")
|
||||||
|
@Entity
|
||||||
|
public class FederatedUserCredentialAttributeEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column(name="ID", length = 36)
|
||||||
|
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
|
||||||
|
protected String id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch= FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "CREDENTIAL_ID")
|
||||||
|
protected FederatedUserCredentialEntity credential;
|
||||||
|
|
||||||
|
@Column(name = "NAME")
|
||||||
|
protected String name;
|
||||||
|
@Column(name = "VALUE")
|
||||||
|
protected String value;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FederatedUserCredentialEntity getCredential() {
|
||||||
|
return credential;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCredential(FederatedUserCredentialEntity credential) {
|
||||||
|
this.credential = credential;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null) return false;
|
||||||
|
if (!(o instanceof FederatedUserCredentialAttributeEntity)) return false;
|
||||||
|
|
||||||
|
FederatedUserCredentialAttributeEntity that = (FederatedUserCredentialAttributeEntity) o;
|
||||||
|
|
||||||
|
if (!id.equals(that.getId())) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return id.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,10 +17,12 @@
|
||||||
|
|
||||||
package org.keycloak.storage.jpa.entity;
|
package org.keycloak.storage.jpa.entity;
|
||||||
|
|
||||||
|
import org.keycloak.models.jpa.entities.CredentialEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserEntity;
|
import org.keycloak.models.jpa.entities.UserEntity;
|
||||||
|
|
||||||
import javax.persistence.Access;
|
import javax.persistence.Access;
|
||||||
import javax.persistence.AccessType;
|
import javax.persistence.AccessType;
|
||||||
|
import javax.persistence.CascadeType;
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.FetchType;
|
import javax.persistence.FetchType;
|
||||||
|
@ -29,7 +31,10 @@ import javax.persistence.JoinColumn;
|
||||||
import javax.persistence.ManyToOne;
|
import javax.persistence.ManyToOne;
|
||||||
import javax.persistence.NamedQueries;
|
import javax.persistence.NamedQueries;
|
||||||
import javax.persistence.NamedQuery;
|
import javax.persistence.NamedQuery;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -38,6 +43,7 @@ import javax.persistence.Table;
|
||||||
@NamedQueries({
|
@NamedQueries({
|
||||||
@NamedQuery(name="federatedUserCredentialByUser", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId"),
|
@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="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="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="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"),
|
@NamedQuery(name="deleteFederatedUserCredentialByUserAndTypeAndDevice", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type and cred.device = :device"),
|
||||||
|
@ -87,6 +93,8 @@ public class FederatedUserCredentialEntity {
|
||||||
protected int digits;
|
protected int digits;
|
||||||
@Column(name="PERIOD")
|
@Column(name="PERIOD")
|
||||||
protected int period;
|
protected int period;
|
||||||
|
@OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy="credential")
|
||||||
|
protected Collection<FederatedUserCredentialAttributeEntity> credentialAttributes = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
|
@ -201,6 +209,14 @@ public class FederatedUserCredentialEntity {
|
||||||
this.period = period;
|
this.period = period;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Collection<FederatedUserCredentialAttributeEntity> getCredentialAttributes() {
|
||||||
|
return credentialAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCredentialAttributes(Collection<FederatedUserCredentialAttributeEntity> credentialAttributes) {
|
||||||
|
this.credentialAttributes = credentialAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -24,4 +24,39 @@
|
||||||
</addColumn>
|
</addColumn>
|
||||||
</changeSet>
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet author="bburke@redhat.com" id="2.2.0">
|
||||||
|
<createTable tableName="CREDENTIAL_ATTRIBUTE">
|
||||||
|
<column name="ID" type="VARCHAR(36)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="CREDENTIAL_ID" type="VARCHAR(36)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="NAME" type="VARCHAR(255)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="VALUE" type="VARCHAR(4000)"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<createTable tableName="FED_CREDENTIAL_ATTRIBUTE">
|
||||||
|
<column name="ID" type="VARCHAR(36)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="CREDENTIAL_ID" type="VARCHAR(36)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="NAME" type="VARCHAR(255)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="VALUE" type="VARCHAR(4000)"/>
|
||||||
|
</createTable>
|
||||||
|
<modifyDataType tableName="CREDENTIAL" columnName="VALUE" newDataType="VARCHAR(4000)"/>
|
||||||
|
|
||||||
|
<addForeignKeyConstraint baseColumnNames="CREDENTIAL_ID" baseTableName="FED_CREDENTIAL_ATTRIBUTE" constraintName="FK_FED_CRED_ATTR" referencedColumnNames="ID" referencedTableName="FED_USER_CREDENTIAL"/>
|
||||||
|
<addForeignKeyConstraint baseColumnNames="CREDENTIAL_ID" baseTableName="CREDENTIAL_ATTRIBUTE" constraintName="FK_CRED_ATTR" referencedColumnNames="ID" referencedTableName="CREDENTIAL"/>
|
||||||
|
|
||||||
|
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
|
@ -22,6 +22,7 @@
|
||||||
<persistence-unit name="keycloak-default" transaction-type="RESOURCE_LOCAL">
|
<persistence-unit name="keycloak-default" transaction-type="RESOURCE_LOCAL">
|
||||||
<class>org.keycloak.models.jpa.entities.ClientEntity</class>
|
<class>org.keycloak.models.jpa.entities.ClientEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.CredentialEntity</class>
|
<class>org.keycloak.models.jpa.entities.CredentialEntity</class>
|
||||||
|
<class>org.keycloak.models.jpa.entities.CredentialAttributeEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
|
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RealmAttributeEntity</class>
|
<class>org.keycloak.models.jpa.entities.RealmAttributeEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
|
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
|
||||||
|
@ -74,6 +75,7 @@
|
||||||
<class>org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity</class>
|
<class>org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity</class>
|
||||||
<class>org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity</class>
|
<class>org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity</class>
|
||||||
<class>org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity</class>
|
<class>org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity</class>
|
||||||
|
<class>org.keycloak.storage.jpa.entity.FederatedUserCredentialAttributeEntity</class>
|
||||||
<class>org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity</class>
|
<class>org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity</class>
|
||||||
<class>org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity</class>
|
<class>org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity</class>
|
||||||
<class>org.keycloak.storage.jpa.entity.FederatedUserRoleMappingEntity</class>
|
<class>org.keycloak.storage.jpa.entity.FederatedUserRoleMappingEntity</class>
|
||||||
|
|
|
@ -24,6 +24,7 @@ import com.mongodb.QueryBuilder;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.connections.mongo.api.MongoStore;
|
import org.keycloak.connections.mongo.api.MongoStore;
|
||||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
|
@ -635,4 +636,5 @@ public class MongoUserProvider implements UserProvider {
|
||||||
public void preRemove(RealmModel realm, ComponentModel component) {
|
public void preRemove(RealmModel realm, ComponentModel component) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
1
pom.xml
1
pom.xml
|
@ -13,6 +13,7 @@
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
~ See the License for the specific language governing permissions and
|
~ See the License for the specific language governing permissions and
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* 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.component;
|
||||||
|
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.storage.UserStorageProviderModel;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class PrioritizedComponentModel extends ComponentModel {
|
||||||
|
public static Comparator<ComponentModel> comparator = new Comparator<ComponentModel>() {
|
||||||
|
@Override
|
||||||
|
public int compare(ComponentModel o1, ComponentModel o2) {
|
||||||
|
return parsePriority(o1) - parsePriority(o2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public PrioritizedComponentModel(ComponentModel copy) {
|
||||||
|
super(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrioritizedComponentModel() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int parsePriority(ComponentModel component) {
|
||||||
|
String priority = component.getConfig().getFirst("priority");
|
||||||
|
if (priority == null) return 0;
|
||||||
|
return Integer.valueOf(priority);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPriority() {
|
||||||
|
return parsePriority(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriority(int priority) {
|
||||||
|
getConfig().putSingle("priority", Integer.toString(priority));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface CredentialInput {
|
||||||
|
String getType();
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface CredentialInputUpdater {
|
||||||
|
boolean supportsCredentialType(String credentialType);
|
||||||
|
boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input);
|
||||||
|
void disableCredentialType(RealmModel realm, UserModel user, String credentialType);
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* 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.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface CredentialInputValidator {
|
||||||
|
boolean supportsCredentialType(String credentialType);
|
||||||
|
boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType);
|
||||||
|
boolean isValid(RealmModel realm, UserModel user, CredentialInput input);
|
||||||
|
|
||||||
|
}
|
158
server-spi/src/main/java/org/keycloak/credential/CredentialModel.java
Executable file
158
server-spi/src/main/java/org/keycloak/credential/CredentialModel.java
Executable file
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
* 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.MultivaluedHashMap;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used just in cases when we want to "directly" update or retrieve the hash or salt of user credential (For example during export/import)
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
private String device;
|
||||||
|
private byte[] salt;
|
||||||
|
private int hashIterations;
|
||||||
|
private Long createdDate;
|
||||||
|
|
||||||
|
// otp stuff
|
||||||
|
private int counter;
|
||||||
|
private String algorithm;
|
||||||
|
private int digits;
|
||||||
|
private int period;
|
||||||
|
private MultivaluedHashMap<String, String> config;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDevice() {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDevice(String device) {
|
||||||
|
this.device = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getSalt() {
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSalt(byte[] salt) {
|
||||||
|
this.salt = salt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHashIterations() {
|
||||||
|
return hashIterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHashIterations(int iterations) {
|
||||||
|
this.hashIterations = iterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCreatedDate() {
|
||||||
|
return createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedDate(Long createdDate) {
|
||||||
|
this.createdDate = createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCounter() {
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCounter(int counter) {
|
||||||
|
this.counter = counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlgorithm() {
|
||||||
|
return algorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlgorithm(String algorithm) {
|
||||||
|
this.algorithm = algorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDigits() {
|
||||||
|
return digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDigits(int digits) {
|
||||||
|
this.digits = digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPeriod() {
|
||||||
|
return period;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPeriod(int period) {
|
||||||
|
this.period = period;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultivaluedHashMap<String, String> getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfig(MultivaluedHashMap<String, String> config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.provider.Provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface CredentialProvider extends Provider {
|
||||||
|
@Override
|
||||||
|
default
|
||||||
|
void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* 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.Config;
|
||||||
|
import org.keycloak.component.ComponentFactory;
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.component.ComponentValidationException;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
import org.keycloak.storage.UserStorageProvider;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface CredentialProviderFactory<T extends CredentialProvider> extends ComponentFactory<T, CredentialProvider> {
|
||||||
|
/**
|
||||||
|
* called per Keycloak transaction.
|
||||||
|
*
|
||||||
|
* @param session
|
||||||
|
* @param model
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
T create(KeycloakSession session, ComponentModel model);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the name of the provider and will be showed in the admin console as an option.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void init(Config.Scope config) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void postInit(KeycloakSessionFactory factory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getHelpText() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.credential;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface UserCredentialStore extends Provider {
|
||||||
|
void updateCredential(RealmModel realm, UserModel user, CredentialModel cred);
|
||||||
|
CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred);
|
||||||
|
boolean removeStoredCredential(RealmModel realm, UserModel user, String id);
|
||||||
|
CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id);
|
||||||
|
List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user);
|
||||||
|
List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type);
|
||||||
|
CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type);
|
||||||
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import org.keycloak.models.cache.UserCache;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.scripting.ScriptingProvider;
|
import org.keycloak.scripting.ScriptingProvider;
|
||||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||||
|
@ -74,6 +75,7 @@ public interface KeycloakSession {
|
||||||
Object removeAttribute(String attribute);
|
Object removeAttribute(String attribute);
|
||||||
void setAttribute(String name, Object value);
|
void setAttribute(String name, Object value);
|
||||||
|
|
||||||
|
|
||||||
void enlistForClose(Provider provider);
|
void enlistForClose(Provider provider);
|
||||||
|
|
||||||
KeycloakSessionFactory getKeycloakSessionFactory();
|
KeycloakSessionFactory getKeycloakSessionFactory();
|
||||||
|
@ -101,7 +103,14 @@ public interface KeycloakSession {
|
||||||
void close();
|
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
|
* @return
|
||||||
*/
|
*/
|
||||||
|
@ -115,8 +124,10 @@ public interface KeycloakSession {
|
||||||
*/
|
*/
|
||||||
UserProvider userStorageManager();
|
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();
|
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.
|
* Hybrid storage for UserStorageProviders that can't store a specific piece of keycloak data in their external storage.
|
||||||
|
* No cache in front.
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -23,6 +23,21 @@ package org.keycloak.models;
|
||||||
*/
|
*/
|
||||||
public interface KeycloakTransactionManager extends KeycloakTransaction {
|
public interface KeycloakTransactionManager extends KeycloakTransaction {
|
||||||
|
|
||||||
|
enum JTAPolicy {
|
||||||
|
/**
|
||||||
|
* Do not interact with JTA at all
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
NOT_SUPPORTED,
|
||||||
|
/**
|
||||||
|
* A new JTA Transaction will be created when Keycloak TM begin() is called. If an existing JTA transaction
|
||||||
|
* exists, it is suspended and resumed after the Keycloak transaction finishes.
|
||||||
|
*/
|
||||||
|
REQUIRES_NEW,
|
||||||
|
}
|
||||||
|
|
||||||
|
JTAPolicy getJTAPolicy();
|
||||||
|
void setJTAPolicy(JTAPolicy policy);
|
||||||
void enlist(KeycloakTransaction transaction);
|
void enlist(KeycloakTransaction transaction);
|
||||||
void enlistAfterCompletion(KeycloakTransaction transaction);
|
void enlistAfterCompletion(KeycloakTransaction transaction);
|
||||||
|
|
||||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface UserCredentialManager extends UserCredentialStore {
|
||||||
|
boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> 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);
|
||||||
|
|
||||||
|
}
|
|
@ -17,23 +17,26 @@
|
||||||
|
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class UserCredentialModel {
|
public class UserCredentialModel implements CredentialInput {
|
||||||
public static final String PASSWORD = "password";
|
public static final String PASSWORD = CredentialModel.PASSWORD;
|
||||||
public static final String PASSWORD_HISTORY = "password-history";
|
public static final String PASSWORD_HISTORY = CredentialModel.PASSWORD_HISTORY;
|
||||||
public static final String PASSWORD_TOKEN = "password-token";
|
public static final String PASSWORD_TOKEN = CredentialModel.PASSWORD_TOKEN;
|
||||||
|
|
||||||
// Secret is same as password but it is not hashed
|
// Secret is same as password but it is not hashed
|
||||||
public static final String SECRET = "secret";
|
public static final String SECRET = CredentialModel.SECRET;
|
||||||
public static final String TOTP = "totp";
|
public static final String TOTP = CredentialModel.TOTP;
|
||||||
public static final String HOTP = "hotp";
|
public static final String HOTP = CredentialModel.HOTP;
|
||||||
public static final String CLIENT_CERT = "cert";
|
public static final String CLIENT_CERT = CredentialModel.CLIENT_CERT;
|
||||||
public static final String KERBEROS = "kerberos";
|
public static final String KERBEROS = CredentialModel.KERBEROS;
|
||||||
|
|
||||||
protected String type;
|
protected String type;
|
||||||
protected String value;
|
protected String value;
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.models;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.policy.PasswordPolicyManagerProvider;
|
import org.keycloak.policy.PasswordPolicyManagerProvider;
|
||||||
import org.keycloak.policy.PolicyError;
|
import org.keycloak.policy.PolicyError;
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.storage.user.UserCredentialValidatorProvider;
|
import org.keycloak.storage.user.UserCredentialValidatorProvider;
|
||||||
import org.keycloak.storage.user.UserLookupProvider;
|
import org.keycloak.storage.user.UserLookupProvider;
|
||||||
|
@ -85,4 +86,5 @@ public interface UserProvider extends Provider,
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
void preRemove(RealmModel realm, ComponentModel component);
|
void preRemove(RealmModel realm, ComponentModel component);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,11 +39,11 @@ public class CacheUserProviderSpi implements Spi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends Provider> getProviderClass() {
|
public Class<? extends Provider> getProviderClass() {
|
||||||
return CacheUserProvider.class;
|
return UserCache.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
return CacheUserProviderFactory.class;
|
return UserCacheProviderFactory.class;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
43
server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
vendored
Normal file
43
server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
vendored
Normal file
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @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();
|
||||||
|
}
|
7
server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java → server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java
vendored
Executable file → Normal file
7
server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java → server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java
vendored
Executable file → Normal file
|
@ -14,17 +14,14 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.models.cache;
|
package org.keycloak.models.cache;
|
||||||
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserProvider;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface CacheUserProvider extends UserProvider {
|
public interface OnUserCache {
|
||||||
void clear();
|
void onCache(RealmModel realm, CachedUserModel user);
|
||||||
UserProvider getDelegate();
|
|
||||||
}
|
}
|
36
server-spi/src/main/java/org/keycloak/models/cache/UserCache.java
vendored
Executable file
36
server-spi/src/main/java/org/keycloak/models/cache/UserCache.java
vendored
Executable file
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface UserCache extends UserProvider {
|
||||||
|
/**
|
||||||
|
* Evict user from cache.
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
*/
|
||||||
|
void evict(RealmModel realm, UserModel user);
|
||||||
|
void clear();
|
||||||
|
}
|
|
@ -23,6 +23,6 @@ import org.keycloak.provider.ProviderFactory;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface CacheUserProviderFactory extends ProviderFactory<CacheUserProvider> {
|
public interface UserCacheProviderFactory extends ProviderFactory<UserCache> {
|
||||||
|
|
||||||
}
|
}
|
|
@ -44,8 +44,14 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||||
import org.keycloak.common.util.CertificateUtils;
|
import org.keycloak.common.util.CertificateUtils;
|
||||||
import org.keycloak.common.util.PemUtils;
|
import org.keycloak.common.util.PemUtils;
|
||||||
|
import org.keycloak.transaction.JtaTransactionManagerLookup;
|
||||||
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import javax.naming.InitialContext;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import javax.transaction.InvalidTransactionException;
|
||||||
|
import javax.transaction.SystemException;
|
||||||
|
import javax.transaction.Transaction;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
@ -56,6 +62,7 @@ import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.sql.DriverManager;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -63,6 +70,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of helper methods, which are useful in various model implementations.
|
* Set of helper methods, which are useful in various model implementations.
|
||||||
|
@ -303,6 +311,7 @@ public final class KeycloakModelUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String getMasterRealmAdminApplicationClientId(String realmName) {
|
public static String getMasterRealmAdminApplicationClientId(String realmName) {
|
||||||
return realmName + "-realm";
|
return realmName + "-realm";
|
||||||
}
|
}
|
||||||
|
@ -651,4 +660,33 @@ public final class KeycloakModelUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void suspendJtaTransaction(KeycloakSessionFactory factory, Runnable runnable) {
|
||||||
|
JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup)factory.getProviderFactory(JtaTransactionManagerLookup.class);
|
||||||
|
Transaction suspended = null;
|
||||||
|
try {
|
||||||
|
if (lookup != null) {
|
||||||
|
if (lookup.getTransactionManager() != null) {
|
||||||
|
try {
|
||||||
|
suspended = lookup.getTransactionManager().suspend();
|
||||||
|
} catch (SystemException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runnable.run();
|
||||||
|
} finally {
|
||||||
|
if (suspended != null) {
|
||||||
|
try {
|
||||||
|
lookup.getTransactionManager().resume(suspended);
|
||||||
|
} catch (InvalidTransactionException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (SystemException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,8 @@
|
||||||
|
|
||||||
package org.keycloak.storage;
|
package org.keycloak.storage;
|
||||||
|
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.component.PrioritizedComponentModel;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stored configuration of a User Storage provider instance.
|
* Stored configuration of a User Storage provider instance.
|
||||||
|
@ -32,14 +26,7 @@ import java.util.Map;
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
|
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
|
||||||
*/
|
*/
|
||||||
public class UserStorageProviderModel extends ComponentModel {
|
public class UserStorageProviderModel extends PrioritizedComponentModel {
|
||||||
|
|
||||||
public static Comparator<UserStorageProviderModel> comparator = new Comparator<UserStorageProviderModel>() {
|
|
||||||
@Override
|
|
||||||
public int compare(UserStorageProviderModel o1, UserStorageProviderModel o2) {
|
|
||||||
return o1.getPriority() - o2.getPriority();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public UserStorageProviderModel() {
|
public UserStorageProviderModel() {
|
||||||
setProviderType(UserStorageProvider.class.getName());
|
setProviderType(UserStorageProvider.class.getName());
|
||||||
|
@ -49,14 +36,4 @@ public class UserStorageProviderModel extends ComponentModel {
|
||||||
super(copy);
|
super(copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPriority() {
|
|
||||||
String priority = getConfig().getFirst("priority");
|
|
||||||
if (priority == null) return 0;
|
|
||||||
return Integer.valueOf(priority);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPriority(int priority) {
|
|
||||||
getConfig().putSingle("priority", Integer.toString(priority));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.storage.federated;
|
package org.keycloak.storage.federated;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserCredentialValueModel;
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
|
@ -28,8 +29,20 @@ import java.util.List;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface UserCredentialsFederatedStorage {
|
public interface UserCredentialsFederatedStorage {
|
||||||
|
// deprecated
|
||||||
void updateCredential(RealmModel realm, UserModel user, UserCredentialModel cred);
|
void updateCredential(RealmModel realm, UserModel user, UserCredentialModel cred);
|
||||||
void updateCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred);
|
void updateCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred);
|
||||||
void removeCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred);
|
void removeCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred);
|
||||||
List<UserCredentialValueModel> getCredentials(RealmModel realm, UserModel user);
|
List<UserCredentialValueModel> getCredentials(RealmModel realm, UserModel user);
|
||||||
|
|
||||||
|
// new
|
||||||
|
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<CredentialModel> getCredentials(RealmModel realm);
|
||||||
|
List<CredentialModel> getUserCredentials(RealmModel realm, UserModel user);
|
||||||
|
List<CredentialModel> getCredentialsByType(RealmModel realm, UserModel user, String type);
|
||||||
|
CredentialModel getCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,4 +61,5 @@ org.keycloak.authorization.AuthorizationSpi
|
||||||
org.keycloak.models.cache.authorization.CachedStoreFactorySpi
|
org.keycloak.models.cache.authorization.CachedStoreFactorySpi
|
||||||
org.keycloak.protocol.oidc.TokenIntrospectionSpi
|
org.keycloak.protocol.oidc.TokenIntrospectionSpi
|
||||||
org.keycloak.policy.PasswordPolicySpi
|
org.keycloak.policy.PasswordPolicySpi
|
||||||
org.keycloak.policy.PasswordPolicyManagerSpi
|
org.keycloak.policy.PasswordPolicyManagerSpi
|
||||||
|
org.keycloak.transaction.TransactionManagerLookupSpi
|
||||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class LocalOTPCredentialManager implements CredentialInputValidator, CredentialInputUpdater, OnUserCache {
|
||||||
|
private static final Logger logger = Logger.getLogger(LocalOTPCredentialManager.class);
|
||||||
|
|
||||||
|
protected KeycloakSession session;
|
||||||
|
|
||||||
|
protected List<CredentialModel> getCachedCredentials(UserModel user, String type) {
|
||||||
|
if (!(user instanceof CachedUserModel)) return Collections.EMPTY_LIST;
|
||||||
|
CachedUserModel cached = (CachedUserModel)user;
|
||||||
|
List<CredentialModel> rtn = (List<CredentialModel>)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<CredentialModel> 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<CredentialModel> hotp = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.HOTP);
|
||||||
|
for (CredentialModel cred : hotp) {
|
||||||
|
getCredentialStore().removeStoredCredential(realm, user, cred.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (disableTOTP) {
|
||||||
|
List<CredentialModel> 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<CredentialModel> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @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<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
|
||||||
|
return getStoreForUser(user).getStoredCredentials(realm, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CredentialModel> 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<CredentialInput> inputs) {
|
||||||
|
|
||||||
|
List<CredentialInput> 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<CredentialInput> 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<ComponentModel> 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<CredentialInput> 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<ComponentModel> getCredentialProviderComponents(RealmModel realm) {
|
||||||
|
List<ComponentModel> components = realm.getComponents(realm.getId(), CredentialProvider.class.getName());
|
||||||
|
if (components.isEmpty()) return components;
|
||||||
|
List<ComponentModel> 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<ComponentModel> 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<ComponentModel> 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<ComponentModel> 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<ComponentModel> 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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,15 +16,18 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.services;
|
package org.keycloak.services;
|
||||||
|
|
||||||
|
import org.keycloak.credential.UserCredentialStore;
|
||||||
|
import org.keycloak.credential.UserCredentialStoreManager;
|
||||||
import org.keycloak.models.*;
|
import org.keycloak.models.*;
|
||||||
import org.keycloak.models.cache.CacheRealmProvider;
|
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.Provider;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
import org.keycloak.scripting.ScriptingProvider;
|
import org.keycloak.scripting.ScriptingProvider;
|
||||||
import org.keycloak.storage.UserStorageManager;
|
import org.keycloak.storage.UserStorageManager;
|
||||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||||
|
|
||||||
|
import javax.transaction.TransactionManager;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,6 +43,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
||||||
private RealmProvider model;
|
private RealmProvider model;
|
||||||
private UserProvider userModel;
|
private UserProvider userModel;
|
||||||
private UserStorageManager userStorageManager;
|
private UserStorageManager userStorageManager;
|
||||||
|
private UserCredentialStoreManager userCredentialStorageManager;
|
||||||
private ScriptingProvider scriptingProvider;
|
private ScriptingProvider scriptingProvider;
|
||||||
private UserSessionProvider sessionProvider;
|
private UserSessionProvider sessionProvider;
|
||||||
private UserFederationManager federationManager;
|
private UserFederationManager federationManager;
|
||||||
|
@ -48,7 +52,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
||||||
|
|
||||||
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
|
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
this.transactionManager = new DefaultKeycloakTransactionManager();
|
this.transactionManager = new DefaultKeycloakTransactionManager(this);
|
||||||
federationManager = new UserFederationManager(this);
|
federationManager = new UserFederationManager(this);
|
||||||
context = new DefaultKeycloakContext(this);
|
context = new DefaultKeycloakContext(this);
|
||||||
}
|
}
|
||||||
|
@ -68,7 +72,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserProvider getUserProvider() {
|
private UserProvider getUserProvider() {
|
||||||
CacheUserProvider cache = getProvider(CacheUserProvider.class);
|
UserCache cache = getProvider(UserCache.class);
|
||||||
if (cache != null) {
|
if (cache != null) {
|
||||||
return cache;
|
return cache;
|
||||||
} else {
|
} else {
|
||||||
|
@ -76,6 +80,12 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserCache getUserCache() {
|
||||||
|
return getProvider(UserCache.class);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enlistForClose(Provider provider) {
|
public void enlistForClose(Provider provider) {
|
||||||
closable.add(provider);
|
closable.add(provider);
|
||||||
|
@ -125,6 +135,12 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
||||||
return userStorageManager;
|
return userStorageManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserCredentialManager userCredentialManager() {
|
||||||
|
if (userCredentialStorageManager == null) userCredentialStorageManager = new UserCredentialStoreManager(this);
|
||||||
|
return userCredentialStorageManager;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserProvider userStorage() {
|
public UserProvider userStorage() {
|
||||||
if (userModel == null) {
|
if (userModel == null) {
|
||||||
|
|
|
@ -49,7 +49,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
|
||||||
private Map<Class<? extends Provider>, String> provider = new HashMap<>();
|
private Map<Class<? extends Provider>, String> provider = new HashMap<>();
|
||||||
private volatile Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<>();
|
private volatile Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<>();
|
||||||
protected CopyOnWriteArrayList<ProviderEventListener> listeners = new CopyOnWriteArrayList<>();
|
protected CopyOnWriteArrayList<ProviderEventListener> listeners = new CopyOnWriteArrayList<>();
|
||||||
private TransactionManager tm;
|
|
||||||
|
|
||||||
// TODO: Likely should be changed to int and use Time.currentTime() to be compatible with all our "time" reps
|
// TODO: Likely should be changed to int and use Time.currentTime() to be compatible with all our "time" reps
|
||||||
protected long serverStartupTimestamp;
|
protected long serverStartupTimestamp;
|
||||||
|
@ -97,8 +96,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
|
||||||
// make the session factory ready for hot deployment
|
// make the session factory ready for hot deployment
|
||||||
ProviderManagerRegistry.SINGLETON.setDeployer(this);
|
ProviderManagerRegistry.SINGLETON.setDeployer(this);
|
||||||
|
|
||||||
JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup)getProviderFactory(JtaTransactionManagerLookup.class);
|
|
||||||
if (lookup != null) tm = lookup.getTransactionManager();
|
|
||||||
}
|
}
|
||||||
protected Map<Class<? extends Provider>, Map<String, ProviderFactory>> getFactoriesCopy() {
|
protected Map<Class<? extends Provider>, Map<String, ProviderFactory>> getFactoriesCopy() {
|
||||||
Map<Class<? extends Provider>, Map<String, ProviderFactory>> copy = new HashMap<>();
|
Map<Class<? extends Provider>, Map<String, ProviderFactory>> copy = new HashMap<>();
|
||||||
|
@ -282,9 +279,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
|
||||||
|
|
||||||
public KeycloakSession create() {
|
public KeycloakSession create() {
|
||||||
KeycloakSession session = new DefaultKeycloakSession(this);
|
KeycloakSession session = new DefaultKeycloakSession(this);
|
||||||
if (tm != null) {
|
|
||||||
session.getTransactionManager().enlist(new JtaTransactionWrapper(tm));
|
|
||||||
}
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,13 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.services;
|
package org.keycloak.services;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakTransaction;
|
import org.keycloak.models.KeycloakTransaction;
|
||||||
import org.keycloak.models.KeycloakTransactionManager;
|
import org.keycloak.models.KeycloakTransactionManager;
|
||||||
|
import org.keycloak.transaction.JtaTransactionManagerLookup;
|
||||||
|
import org.keycloak.transaction.JtaTransactionWrapper;
|
||||||
|
|
||||||
|
import javax.transaction.TransactionManager;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -34,6 +38,12 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
|
||||||
private List<KeycloakTransaction> afterCompletion = new LinkedList<KeycloakTransaction>();
|
private List<KeycloakTransaction> afterCompletion = new LinkedList<KeycloakTransaction>();
|
||||||
private boolean active;
|
private boolean active;
|
||||||
private boolean rollback;
|
private boolean rollback;
|
||||||
|
private KeycloakSession session;
|
||||||
|
private JTAPolicy jtaPolicy = JTAPolicy.REQUIRES_NEW;
|
||||||
|
|
||||||
|
public DefaultKeycloakTransactionManager(KeycloakSession session) {
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enlist(KeycloakTransaction transaction) {
|
public void enlist(KeycloakTransaction transaction) {
|
||||||
|
@ -62,12 +72,31 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
|
||||||
prepare.add(transaction);
|
prepare.add(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JTAPolicy getJTAPolicy() {
|
||||||
|
return jtaPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setJTAPolicy(JTAPolicy policy) {
|
||||||
|
jtaPolicy = policy;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void begin() {
|
public void begin() {
|
||||||
if (active) {
|
if (active) {
|
||||||
throw new IllegalStateException("Transaction already active");
|
throw new IllegalStateException("Transaction already active");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (jtaPolicy == JTAPolicy.REQUIRES_NEW) {
|
||||||
|
JtaTransactionManagerLookup jtaLookup = session.getProvider(JtaTransactionManagerLookup.class);
|
||||||
|
TransactionManager tm = jtaLookup.getTransactionManager();
|
||||||
|
if (tm != null) {
|
||||||
|
enlist(new JtaTransactionWrapper(tm));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (KeycloakTransaction tx : transactions) {
|
for (KeycloakTransaction tx : transactions) {
|
||||||
tx.begin();
|
tx.begin();
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,10 +45,13 @@ import org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner;
|
||||||
import org.keycloak.services.util.JsonConfigProvider;
|
import org.keycloak.services.util.JsonConfigProvider;
|
||||||
import org.keycloak.services.util.ObjectMapperResolver;
|
import org.keycloak.services.util.ObjectMapperResolver;
|
||||||
import org.keycloak.timer.TimerProvider;
|
import org.keycloak.timer.TimerProvider;
|
||||||
|
import org.keycloak.transaction.JtaTransactionManagerLookup;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.keycloak.common.util.SystemEnvProperties;
|
import org.keycloak.common.util.SystemEnvProperties;
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.transaction.SystemException;
|
||||||
|
import javax.transaction.Transaction;
|
||||||
import javax.ws.rs.core.Application;
|
import javax.ws.rs.core.Application;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
@ -155,11 +158,28 @@ public class KeycloakApplication extends Application {
|
||||||
// Migrate model, bootstrap master realm, import realms and create admin user. This is done with acquired dbLock
|
// Migrate model, bootstrap master realm, import realms and create admin user. This is done with acquired dbLock
|
||||||
protected ExportImportManager migrateAndBootstrap() {
|
protected ExportImportManager migrateAndBootstrap() {
|
||||||
ExportImportManager exportImportManager;
|
ExportImportManager exportImportManager;
|
||||||
|
logger.debug("Calling migrateModel");
|
||||||
migrateModel();
|
migrateModel();
|
||||||
|
|
||||||
|
logger.debug("bootstrap");
|
||||||
KeycloakSession session = sessionFactory.create();
|
KeycloakSession session = sessionFactory.create();
|
||||||
try {
|
try {
|
||||||
session.getTransactionManager().begin();
|
session.getTransactionManager().begin();
|
||||||
|
JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup) sessionFactory.getProviderFactory(JtaTransactionManagerLookup.class);
|
||||||
|
if (lookup != null) {
|
||||||
|
if (lookup.getTransactionManager() != null) {
|
||||||
|
try {
|
||||||
|
Transaction transaction = lookup.getTransactionManager().getTransaction();
|
||||||
|
logger.debugv("bootstrap current transaction? {0}", transaction != null);
|
||||||
|
if (transaction != null) {
|
||||||
|
logger.debugv("bootstrap current transaction status? {0}", transaction.getStatus());
|
||||||
|
}
|
||||||
|
} catch (SystemException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
|
ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
|
||||||
exportImportManager = new ExportImportManager(session);
|
exportImportManager = new ExportImportManager(session);
|
||||||
|
|
|
@ -34,8 +34,6 @@ import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.events.admin.ResourceType;
|
import org.keycloak.events.admin.ResourceType;
|
||||||
import org.keycloak.exportimport.ClientDescriptionConverter;
|
import org.keycloak.exportimport.ClientDescriptionConverter;
|
||||||
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
|
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.ClientModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -44,7 +42,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.cache.CacheRealmProvider;
|
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.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.models.utils.RepresentationToModel;
|
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.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.GroupRepresentation;
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
|
||||||
import org.keycloak.representations.idm.PartialImportRepresentation;
|
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
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.managers.UsersSyncManager;
|
||||||
import org.keycloak.services.ErrorResponse;
|
import org.keycloak.services.ErrorResponse;
|
||||||
import org.keycloak.services.resources.admin.RealmAuth.Resource;
|
import org.keycloak.services.resources.admin.RealmAuth.Resource;
|
||||||
import org.keycloak.timer.TimerProvider;
|
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.DELETE;
|
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.Response.Status;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
@ -866,7 +860,7 @@ public class RealmAdminResource {
|
||||||
public void clearUserCache() {
|
public void clearUserCache() {
|
||||||
auth.requireManage();
|
auth.requireManage();
|
||||||
|
|
||||||
CacheUserProvider cache = session.getProvider(CacheUserProvider.class);
|
UserCache cache = session.getProvider(UserCache.class);
|
||||||
if (cache != null) {
|
if (cache != null) {
|
||||||
cache.clear();
|
cache.clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,6 +184,8 @@ public class UsersResource {
|
||||||
return ErrorResponse.exists("User is read only!");
|
return ErrorResponse.exists("User is read only!");
|
||||||
} catch (ModelException me) {
|
} catch (ModelException me) {
|
||||||
return ErrorResponse.exists("Could not update user!");
|
return ErrorResponse.exists("Could not update user!");
|
||||||
|
} catch (Exception me) { // JPA may be committed by JTA which can't
|
||||||
|
return ErrorResponse.exists("Could not update user!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,8 @@ import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserConsentModel;
|
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.storage.user.UserCredentialAuthenticationProvider;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.storage.user.UserCredentialValidatorProvider;
|
import org.keycloak.storage.user.UserCredentialValidatorProvider;
|
||||||
|
@ -56,7 +58,7 @@ import java.util.Set;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class UserStorageManager implements UserProvider {
|
public class UserStorageManager implements UserProvider, OnUserCache {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(UserStorageManager.class);
|
private static final Logger logger = Logger.getLogger(UserStorageManager.class);
|
||||||
|
|
||||||
|
@ -70,22 +72,22 @@ public class UserStorageManager implements UserProvider {
|
||||||
return session.userLocalStorage();
|
return session.userLocalStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<UserStorageProviderModel> getStorageProviders(RealmModel realm) {
|
public static List<UserStorageProviderModel> getStorageProviders(RealmModel realm) {
|
||||||
return realm.getUserStorageProviders();
|
return realm.getUserStorageProviders();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <T> T getFirstStorageProvider(RealmModel realm, Class<T> type) {
|
public static <T> T getFirstStorageProvider(KeycloakSession session, RealmModel realm, Class<T> type) {
|
||||||
for (UserStorageProviderModel model : getStorageProviders(realm)) {
|
for (UserStorageProviderModel model : getStorageProviders(realm)) {
|
||||||
UserStorageProviderFactory factory = (UserStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId());
|
UserStorageProviderFactory factory = (UserStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId());
|
||||||
|
|
||||||
if (Types.supports(type, factory, UserStorageProviderFactory.class)) {
|
if (Types.supports(type, factory, UserStorageProviderFactory.class)) {
|
||||||
return type.cast(getStorageProviderInstance(model, factory));
|
return type.cast(getStorageProviderInstance(session, model, factory));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserStorageProvider getStorageProviderInstance(UserStorageProviderModel model, UserStorageProviderFactory factory) {
|
public static UserStorageProvider getStorageProviderInstance(KeycloakSession session, UserStorageProviderModel model, UserStorageProviderFactory factory) {
|
||||||
UserStorageProvider instance = (UserStorageProvider)session.getAttribute(model.getId());
|
UserStorageProvider instance = (UserStorageProvider)session.getAttribute(model.getId());
|
||||||
if (instance != null) return instance;
|
if (instance != null) return instance;
|
||||||
instance = factory.create(session, model);
|
instance = factory.create(session, model);
|
||||||
|
@ -95,12 +97,12 @@ public class UserStorageManager implements UserProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected <T> List<T> getStorageProviders(RealmModel realm, Class<T> type) {
|
public static <T> List<T> getStorageProviders(KeycloakSession session, RealmModel realm, Class<T> type) {
|
||||||
List<T> list = new LinkedList<>();
|
List<T> list = new LinkedList<>();
|
||||||
for (UserStorageProviderModel model : getStorageProviders(realm)) {
|
for (UserStorageProviderModel model : getStorageProviders(realm)) {
|
||||||
UserStorageProviderFactory factory = (UserStorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId());
|
UserStorageProviderFactory factory = (UserStorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId());
|
||||||
if (Types.supports(type, factory, UserStorageProviderFactory.class)) {
|
if (Types.supports(type, factory, UserStorageProviderFactory.class)) {
|
||||||
list.add(type.cast(getStorageProviderInstance(model, factory)));
|
list.add(type.cast(getStorageProviderInstance(session, model, factory)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,21 +118,21 @@ public class UserStorageManager implements UserProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel addUser(RealmModel realm, String username) {
|
public UserModel addUser(RealmModel realm, String username) {
|
||||||
UserRegistrationProvider registry = getFirstStorageProvider(realm, UserRegistrationProvider.class);
|
UserRegistrationProvider registry = getFirstStorageProvider(session, realm, UserRegistrationProvider.class);
|
||||||
if (registry != null) {
|
if (registry != null) {
|
||||||
return registry.addUser(realm, username);
|
return registry.addUser(realm, username);
|
||||||
}
|
}
|
||||||
return localStorage().addUser(realm, username.toLowerCase());
|
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);
|
ComponentModel model = realm.getComponent(componentId);
|
||||||
if (model == null) return null;
|
if (model == null) return null;
|
||||||
UserStorageProviderFactory factory = (UserStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId());
|
UserStorageProviderFactory factory = (UserStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId());
|
||||||
if (factory == null) {
|
if (factory == null) {
|
||||||
throw new ModelException("Could not find UserStorageProviderFactory for: " + model.getProviderId());
|
throw new ModelException("Could not find UserStorageProviderFactory for: " + model.getProviderId());
|
||||||
}
|
}
|
||||||
return getStorageProviderInstance(new UserStorageProviderModel(model), factory);
|
return getStorageProviderInstance(session, new UserStorageProviderModel(model), factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -140,7 +142,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
if (storageId.getProviderId() == null) {
|
if (storageId.getProviderId() == null) {
|
||||||
return localStorage().removeUser(realm, user);
|
return localStorage().removeUser(realm, user);
|
||||||
}
|
}
|
||||||
UserRegistrationProvider registry = (UserRegistrationProvider)getStorageProvider(realm, storageId.getProviderId());
|
UserRegistrationProvider registry = (UserRegistrationProvider)getStorageProvider(session, realm, storageId.getProviderId());
|
||||||
if (registry == null) {
|
if (registry == null) {
|
||||||
throw new ModelException("Could not resolve StorageProvider: " + storageId.getProviderId());
|
throw new ModelException("Could not resolve StorageProvider: " + storageId.getProviderId());
|
||||||
}
|
}
|
||||||
|
@ -233,7 +235,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
if (storageId.getProviderId() == null) {
|
if (storageId.getProviderId() == null) {
|
||||||
return localStorage().getUserById(id, realm);
|
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);
|
return provider.getUserById(id, realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +248,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
public UserModel getUserByUsername(String username, RealmModel realm) {
|
public UserModel getUserByUsername(String username, RealmModel realm) {
|
||||||
UserModel user = localStorage().getUserByUsername(username, realm);
|
UserModel user = localStorage().getUserByUsername(username, realm);
|
||||||
if (user != null) return user;
|
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);
|
user = provider.getUserByUsername(username, realm);
|
||||||
if (user != null) return user;
|
if (user != null) return user;
|
||||||
}
|
}
|
||||||
|
@ -257,7 +259,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
public UserModel getUserByEmail(String email, RealmModel realm) {
|
public UserModel getUserByEmail(String email, RealmModel realm) {
|
||||||
UserModel user = localStorage().getUserByEmail(email, realm);
|
UserModel user = localStorage().getUserByEmail(email, realm);
|
||||||
if (user != null) return user;
|
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);
|
user = provider.getUserByEmail(email, realm);
|
||||||
if (user != null) return user;
|
if (user != null) return user;
|
||||||
}
|
}
|
||||||
|
@ -299,7 +301,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
@Override
|
@Override
|
||||||
public int getUsersCount(RealmModel realm) {
|
public int getUsersCount(RealmModel realm) {
|
||||||
int size = localStorage().getUsersCount(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);
|
size += provider.getUsersCount(realm);
|
||||||
}
|
}
|
||||||
return size;
|
return size;
|
||||||
|
@ -315,7 +317,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
List<UserQueryProvider> storageProviders = getStorageProviders(realm, UserQueryProvider.class);
|
List<UserQueryProvider> storageProviders = getStorageProviders(session, realm, UserQueryProvider.class);
|
||||||
// we can skip rest of method if there are no storage providers
|
// we can skip rest of method if there are no storage providers
|
||||||
if (storageProviders.isEmpty()) {
|
if (storageProviders.isEmpty()) {
|
||||||
return pagedQuery.query(localStorage(), firstResult, maxResults);
|
return pagedQuery.query(localStorage(), firstResult, maxResults);
|
||||||
|
@ -447,7 +449,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
@Override
|
@Override
|
||||||
public void grantToAllUsers(RealmModel realm, RoleModel role) {
|
public void grantToAllUsers(RealmModel realm, RoleModel role) {
|
||||||
// not federation-aware for now
|
// not federation-aware for now
|
||||||
List<UserRegistrationProvider> storageProviders = getStorageProviders(realm, UserRegistrationProvider.class);
|
List<UserRegistrationProvider> storageProviders = getStorageProviders(session, realm, UserRegistrationProvider.class);
|
||||||
LinkedList<UserRegistrationProvider> providers = new LinkedList<>();
|
LinkedList<UserRegistrationProvider> providers = new LinkedList<>();
|
||||||
providers.add(localStorage());
|
providers.add(localStorage());
|
||||||
providers.addAll(storageProviders);
|
providers.addAll(storageProviders);
|
||||||
|
@ -482,7 +484,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
public void preRemove(RealmModel realm) {
|
public void preRemove(RealmModel realm) {
|
||||||
localStorage().preRemove(realm);
|
localStorage().preRemove(realm);
|
||||||
getFederatedStorage().preRemove(realm);
|
getFederatedStorage().preRemove(realm);
|
||||||
for (UserStorageProvider provider : getStorageProviders(realm, UserStorageProvider.class)) {
|
for (UserStorageProvider provider : getStorageProviders(session, realm, UserStorageProvider.class)) {
|
||||||
provider.preRemove(realm);
|
provider.preRemove(realm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -497,7 +499,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
public void preRemove(RealmModel realm, GroupModel group) {
|
public void preRemove(RealmModel realm, GroupModel group) {
|
||||||
localStorage().preRemove(realm, group);
|
localStorage().preRemove(realm, group);
|
||||||
getFederatedStorage().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);
|
provider.preRemove(realm, group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -506,7 +508,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
public void preRemove(RealmModel realm, RoleModel role) {
|
public void preRemove(RealmModel realm, RoleModel role) {
|
||||||
localStorage().preRemove(realm, role);
|
localStorage().preRemove(realm, role);
|
||||||
getFederatedStorage().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);
|
provider.preRemove(realm, role);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -558,7 +560,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
|
|
||||||
if (toValidate.isEmpty()) return true;
|
if (toValidate.isEmpty()) return true;
|
||||||
|
|
||||||
UserStorageProvider provider = getStorageProvider(realm, StorageId.resolveProviderId(user));
|
UserStorageProvider provider = getStorageProvider(session, realm, StorageId.resolveProviderId(user));
|
||||||
if (!(provider instanceof UserCredentialValidatorProvider)) {
|
if (!(provider instanceof UserCredentialValidatorProvider)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -572,7 +574,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
||||||
List<UserCredentialAuthenticationProvider> providers = getStorageProviders(realm, UserCredentialAuthenticationProvider.class);
|
List<UserCredentialAuthenticationProvider> providers = getStorageProviders(session, realm, UserCredentialAuthenticationProvider.class);
|
||||||
if (providers.isEmpty()) return CredentialValidationOutput.failed();
|
if (providers.isEmpty()) return CredentialValidationOutput.failed();
|
||||||
|
|
||||||
CredentialValidationOutput result = null;
|
CredentialValidationOutput result = null;
|
||||||
|
@ -606,7 +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
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -16,7 +16,9 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.transaction;
|
package org.keycloak.transaction;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.KeycloakTransaction;
|
import org.keycloak.models.KeycloakTransaction;
|
||||||
|
import org.keycloak.storage.UserStorageManager;
|
||||||
|
|
||||||
import javax.transaction.InvalidTransactionException;
|
import javax.transaction.InvalidTransactionException;
|
||||||
import javax.transaction.NotSupportedException;
|
import javax.transaction.NotSupportedException;
|
||||||
|
@ -31,16 +33,22 @@ import javax.transaction.UserTransaction;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class JtaTransactionWrapper implements KeycloakTransaction {
|
public class JtaTransactionWrapper implements KeycloakTransaction {
|
||||||
|
private static final Logger logger = Logger.getLogger(JtaTransactionWrapper.class);
|
||||||
protected TransactionManager tm;
|
protected TransactionManager tm;
|
||||||
protected Transaction ut;
|
protected Transaction ut;
|
||||||
protected Transaction suspended;
|
protected Transaction suspended;
|
||||||
|
protected Exception ended;
|
||||||
|
|
||||||
public JtaTransactionWrapper(TransactionManager tm) {
|
public JtaTransactionWrapper(TransactionManager tm) {
|
||||||
this.tm = tm;
|
this.tm = tm;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
suspended = tm.suspend();
|
suspended = tm.suspend();
|
||||||
|
logger.debug("new JtaTransactionWrapper");
|
||||||
|
logger.debugv("was existing? {0}", suspended != null);
|
||||||
tm.begin();
|
tm.begin();
|
||||||
ut = tm.getTransaction();
|
ut = tm.getTransaction();
|
||||||
|
//ended = new Exception();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
@ -53,16 +61,20 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
|
||||||
@Override
|
@Override
|
||||||
public void commit() {
|
public void commit() {
|
||||||
try {
|
try {
|
||||||
ut.commit();
|
logger.debug("JtaTransactionWrapper commit");
|
||||||
|
tm.commit();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void rollback() {
|
public void rollback() {
|
||||||
try {
|
try {
|
||||||
ut.rollback();
|
logger.debug("JtaTransactionWrapper rollback");
|
||||||
|
tm.rollback();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -74,7 +86,7 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
|
||||||
@Override
|
@Override
|
||||||
public void setRollbackOnly() {
|
public void setRollbackOnly() {
|
||||||
try {
|
try {
|
||||||
ut.setRollbackOnly();
|
tm.setRollbackOnly();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
@ -83,7 +95,7 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
|
||||||
@Override
|
@Override
|
||||||
public boolean getRollbackOnly() {
|
public boolean getRollbackOnly() {
|
||||||
try {
|
try {
|
||||||
return ut.getStatus() == Status.STATUS_MARKED_ROLLBACK;
|
return tm.getStatus() == Status.STATUS_MARKED_ROLLBACK;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
@ -92,15 +104,28 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
try {
|
try {
|
||||||
return ut.getStatus() == Status.STATUS_ACTIVE;
|
return tm.getStatus() == Status.STATUS_ACTIVE;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finalize() throws Throwable {
|
||||||
|
if (ended != null) {
|
||||||
|
logger.error("TX didn't close at position", ended);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
protected void end() {
|
protected void end() {
|
||||||
|
ended = null;
|
||||||
|
logger.debug("JtaTransactionWrapper end");
|
||||||
if (suspended != null) {
|
if (suspended != null) {
|
||||||
try {
|
try {
|
||||||
|
logger.debug("JtaTransactionWrapper resuming suspended");
|
||||||
tm.resume(suspended);
|
tm.resume(suspended);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|
|
@ -18,5 +18,4 @@
|
||||||
org.keycloak.exportimport.ClientDescriptionConverterSpi
|
org.keycloak.exportimport.ClientDescriptionConverterSpi
|
||||||
org.keycloak.wellknown.WellKnownSpi
|
org.keycloak.wellknown.WellKnownSpi
|
||||||
org.keycloak.services.clientregistration.ClientRegistrationSpi
|
org.keycloak.services.clientregistration.ClientRegistrationSpi
|
||||||
org.keycloak.transaction.TransactionManagerLookupSpi
|
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<password>sa</password>
|
<password>sa</password>
|
||||||
</security>
|
</security>
|
||||||
</datasource>
|
</datasource>
|
||||||
<datasource jndi-name="java:jboss/datasources/KeycloakDS" jta="false" pool-name="KeycloakDS" enabled="true" use-java-context="true">
|
<datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
|
||||||
<?KEYCLOAK_DS_CONNECTION_URL?>
|
<?KEYCLOAK_DS_CONNECTION_URL?>
|
||||||
<driver>h2</driver>
|
<driver>h2</driver>
|
||||||
<security>
|
<security>
|
||||||
|
|
Loading…
Reference in a new issue