Merge pull request #3209 from patriot1burke/master

KEYCLOAK-3440
This commit is contained in:
Bill Burke authored and GitHub committed 2016-09-08 09:10:54 -04:00
commit 2a5c778af5
66 files changed
+2931 -252

No files matched your search

@@ -18,34 +18,28 @@
package org.keycloak.models.cache.infinispan;
import org.infinispan.Cache;
import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachelistener.annotation.*;
import org.infinispan.notifications.cachelistener.event.*;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.models.cache.CacheUserProviderFactory;
import org.keycloak.models.cache.infinispan.entities.CachedUser;
import org.keycloak.models.cache.UserCache;
import org.keycloak.models.cache.UserCacheProviderFactory;
import org.keycloak.models.cache.infinispan.entities.Revisioned;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author <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;
@Override
public CacheUserProvider create(KeycloakSession session) {
public UserCache create(KeycloakSession session) {
lazyInit(session);
return new UserCacheSession(userCache, session);
}
@@ -28,6 +28,7 @@ import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.cache.CachedUserModel;
import org.keycloak.models.cache.infinispan.entities.CachedUser;
import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -39,12 +40,13 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class UserAdapter implements UserModel {
public class UserAdapter implements CachedUserModel {
protected UserModel updated;
protected CachedUser cached;
protected UserCacheSession userProviderCache;
@@ -65,6 +67,22 @@ public class UserAdapter implements UserModel {
if (updated == null) throw new IllegalStateException("Not found in database");
}
}
@Override
public void invalidate() {
getDelegateForUpdate();
}
@Override
public long getCacheTimestamp() {
return cached.getCacheTimestamp();
}
@Override
public ConcurrentHashMap getCachedWith() {
return cached.getCachedWith();
}
@Override
public String getId() {
if (updated != null) return updated.getId();
@@ -34,7 +34,9 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.models.cache.CachedUserModel;
import org.keycloak.models.cache.OnUserCache;
import org.keycloak.models.cache.UserCache;
import org.keycloak.models.cache.infinispan.entities.CachedFederatedIdentityLinks;
import org.keycloak.models.cache.infinispan.entities.CachedUser;
import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
@@ -47,7 +49,7 @@ import java.util.*;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class UserCacheSession implements CacheUserProvider {
public class UserCacheSession implements UserCache {
protected static final Logger logger = Logger.getLogger(UserCacheSession.class);
protected UserCacheManager cache;
protected KeycloakSession session;
@@ -73,7 +75,6 @@ public class UserCacheSession implements CacheUserProvider {
cache.clear();
}
@Override
public UserProvider getDelegate() {
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
if (delegate != null) return delegate;
@@ -89,6 +90,20 @@ public class UserCacheSession implements CacheUserProvider {
if (realm.isIdentityFederationEnabled()) invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
}
@Override
public void evict(RealmModel realm, UserModel user) {
if (user instanceof CachedUserModel) {
((CachedUserModel)user).invalidate();
} else {
invalidations.add(user.getId());
if (user.getEmail() != null) invalidations.add(getUserByEmailCacheKey(realm.getId(), user.getEmail()));
invalidations.add(getUserByUsernameCacheKey(realm.getId(), user.getUsername()));
if (realm.isIdentityFederationEnabled()) invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
}
}
protected void runInvalidations() {
for (String realmId : realmInvalidations) {
cache.invalidateRealmUsers(realmId, invalidations);
@@ -147,8 +162,13 @@ public class UserCacheSession implements CacheUserProvider {
logger.trace("registered for invalidation return delegate");
return getDelegate().getUserById(id, realm);
}
if (managedUsers.containsKey(id)) {
logger.trace("return managedusers");
return managedUsers.get(id);
}
CachedUser cached = cache.get(id, CachedUser.class);
boolean wasCached = cached != null;
if (cached == null) {
logger.trace("not cached");
Long loaded = cache.getCurrentRevision(id);
@@ -157,19 +177,12 @@ public class UserCacheSession implements CacheUserProvider {
logger.trace("delegate returning null");
return null;
}
if (managedUsers.containsKey(id)) {
logger.trace("return managedusers");
return managedUsers.get(id);
}
if (invalidations.contains(id)) return model;
cached = new CachedUser(loaded, realm, model);
cache.addRevisioned(cached, startupRevision);
} else if (managedUsers.containsKey(id)) {
logger.trace("return managedusers");
return managedUsers.get(id);
}
logger.trace("returning new cache adapter");
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
if (!wasCached) onCache(realm, adapter);
managedUsers.put(id, adapter);
return adapter;
}
@@ -223,13 +236,7 @@ public class UserCacheSession implements CacheUserProvider {
return managedUsers.get(userId);
}
CachedUser cached = cache.get(userId, CachedUser.class);
if (cached == null) {
cached = new CachedUser(loaded, realm, model);
cache.addRevisioned(cached, startupRevision);
}
logger.trace("return new cache adapter");
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
managedUsers.put(userId, adapter);
return adapter;
} else {
@@ -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
public UserModel getUserByEmail(String email, RealmModel realm) {
if (email == null) return null;
@@ -268,12 +295,7 @@ public class UserCacheSession implements CacheUserProvider {
if (invalidations.contains(userId)) return model;
if (managedUsers.containsKey(userId)) return managedUsers.get(userId);
CachedUser cached = cache.get(userId, CachedUser.class);
if (cached == null) {
cached = new CachedUser(loaded, realm, model);
cache.addRevisioned(cached, startupRevision);
}
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
managedUsers.put(userId, adapter);
return adapter;
} else {
@@ -316,12 +338,7 @@ public class UserCacheSession implements CacheUserProvider {
if (invalidations.contains(userId)) return model;
if (managedUsers.containsKey(userId)) return managedUsers.get(userId);
CachedUser cached = cache.get(userId, CachedUser.class);
if (cached == null) {
cached = new CachedUser(loaded, realm, model);
cache.addRevisioned(cached, startupRevision);
}
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
managedUsers.put(userId, adapter);
return adapter;
} else {
@@ -669,4 +686,5 @@ public class UserCacheSession implements CacheUserProvider {
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 {
private String id;
private Long revision;
private final long cacheTimestamp = System.currentTimeMillis();
public AbstractRevisioned(Long revision, String id) {
this.revision = revision;
@@ -30,4 +31,12 @@ public class AbstractRevisioned implements Revisioned, Serializable {
this.revision = revision;
}
/**
* When was this cached
*
* @return
*/
public long getCacheTimestamp() {
return cacheTimestamp;
}
}
@@ -36,7 +36,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class CachedUser extends AbstractRevisioned implements InRealm {
public class CachedUser extends AbstractExtendableRevisioned implements InRealm {
private String realm;
private String username;
private Long createdTimestamp;
@@ -15,4 +15,4 @@
# 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");
node1Cache.put("key", "node1");
System.out.println("node1 create entry");
node1Cache.put("key", "node111");
System.out.println("node2 create entry");
node2Cache.put("key", "node2");
System.out.println("node1 remove entry");
node1Cache.remove("key");
System.out.println("node2 remove entry");
node2Cache.remove("key");
System.out.println("node2 put entry");
node2Cache.put("key", "node2");
System.out.println("node2 evict entry");
@@ -118,6 +123,28 @@ public class ClusteredCacheBehaviorTest {
node2Cache.putForExternalRead("key", "common");
System.out.println("node2 remove entry");
node2Cache.remove("key");
System.out.println("node1 remove entry");
node1Cache.remove("key");
// test remove non-existing node 2, existing node 1
System.out.println("Test non existent remove");
System.out.println("node1 create entry");
node1Cache.put("key", "value");
System.out.println("node2 remove non-existent entry");
System.out.println("exists?: " + node2Cache.containsKey("key"));
node2Cache.remove("key");
// test clear
System.out.println("Test clear cache");
System.out.println("add key to node 1, key2 to node2");
node1Cache.putForExternalRead("key", "value");
node2Cache.putForExternalRead("key", "value");
node2Cache.putForExternalRead("key2", "value");
System.out.println("Clear from node1");
node1Cache.clear();
System.out.println("node 2 exists key2?: " + node2Cache.containsKey("key2"));
System.out.println("node 2 exists key?: " + node2Cache.containsKey("key"));
}
@@ -17,6 +17,8 @@
package org.keycloak.connections.jpa;
import org.jboss.logging.Logger;
import javax.persistence.EntityManager;
/**
@@ -24,6 +26,7 @@ import javax.persistence.EntityManager;
*/
public class DefaultJpaConnectionProvider implements JpaConnectionProvider {
private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProvider.class);
private final EntityManager em;
public DefaultJpaConnectionProvider(EntityManager em) {
@@ -37,6 +40,7 @@ public class DefaultJpaConnectionProvider implements JpaConnectionProvider {
@Override
public void close() {
logger.trace("DefaultJpaConnectionProvider close()");
em.close();
}
@@ -29,12 +29,22 @@ import java.util.Map;
import javax.naming.InitialContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.SynchronizationType;
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.engine.transaction.jta.platform.internal.AbstractJtaPlatform;
import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
import org.jboss.logging.Logger;
import org.keycloak.Config;
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.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -45,6 +55,7 @@ import org.keycloak.provider.ServerInfoAwareProviderFactory;
import org.keycloak.models.dblock.DBLockManager;
import org.keycloak.ServerStartupError;
import org.keycloak.timer.TimerProvider;
import org.keycloak.transaction.JtaTransactionManagerLookup;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -60,16 +71,29 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
private volatile EntityManagerFactory emf;
private Config.Scope config;
private Map<String,String> operationalInfo;
private Map<String, String> operationalInfo;
private boolean jtaEnabled;
private JtaTransactionManagerLookup jtaLookup;
private KeycloakSessionFactory factory;
@Override
public JpaConnectionProvider create(KeycloakSession session) {
logger.trace("Create JpaConnectionProvider");
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);
session.getTransactionManager().enlist(new JpaKeycloakTransaction(em));
if (!jtaEnabled) session.getTransactionManager().enlist(new JpaKeycloakTransaction(em));
return new DefaultJpaConnectionProvider(em);
}
@@ -92,85 +116,112 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
@Override
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) {
if (emf == null) {
synchronized (this) {
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");
if (dataSource != null) {
if (config.getBoolean("jta", false)) {
properties.put(AvailableSettings.JTA_DATASOURCE, dataSource);
String dataSource = config.get("dataSource");
if (dataSource != null) {
if (config.getBoolean("jta", jtaEnabled)) {
properties.put(AvailableSettings.JTA_DATASOURCE, dataSource);
} else {
properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource);
}
} else {
properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource);
}
} else {
properties.put(AvailableSettings.JDBC_URL, config.get("url"));
properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver"));
properties.put(AvailableSettings.JDBC_URL, config.get("url"));
properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver"));
String user = config.get("user");
if (user != null) {
properties.put(AvailableSettings.JDBC_USER, user);
}
String password = config.get("password");
if (password != null) {
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);
String user = config.get("user");
if (user != null) {
properties.put(AvailableSettings.JDBC_USER, user);
}
String password = config.get("password");
if (password != null) {
properties.put(AvailableSettings.JDBC_PASSWORD, password);
}
}
migration(migrationStrategy, initializeEmpty, schema, databaseUpdateFile, connection, session);
int globalStatsInterval = config.getInt("globalStatsInterval", -1);
if (globalStatsInterval != -1) {
properties.put("hibernate.generate_statistics", true);
String schema = getSchema();
if (schema != null) {
properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
}
logger.trace("Creating EntityManagerFactory");
emf = JpaUtils.createEntityManagerFactory(session, unitName, properties, getClass().getClassLoader());
logger.trace("EntityManagerFactory created");
MigrationStrategy migrationStrategy = getMigrationStrategy();
boolean initializeEmpty = config.getBoolean("initializeEmpty", true);
File databaseUpdateFile = getDatabaseUpdateFile();
if (globalStatsInterval != -1) {
startGlobalStats(session, globalStatsInterval);
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);
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) {
try {
operationalInfo = new LinkedHashMap<>();
DatabaseMetaData md = connection.getMetaData();
operationalInfo.put("databaseUrl",md.getURL());
operationalInfo.put("databaseUser", md.getUserName());
operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion());
operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion());
try {
operationalInfo = new LinkedHashMap<>();
DatabaseMetaData md = connection.getMetaData();
operationalInfo.put("databaseUrl", md.getURL());
operationalInfo.put("databaseUser", md.getUserName());
operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion());
operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion());
logger.debugf("Database info: %s", operationalInfo.toString());
} catch (SQLException e) {
logger.warn("Unable to prepare operational info due database exception: " + e.getMessage());
}
}
} catch (SQLException e) {
logger.warn("Unable to prepare operational info due database exception: " + e.getMessage());
}
}
protected String detectDialect(Connection connection) {
@@ -334,11 +385,11 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
public String getSchema() {
return config.get("schema");
}
@Override
public Map<String,String> getOperationalInfo() {
return operationalInfo;
}
public Map<String, String> getOperationalInfo() {
return operationalInfo;
}
private MigrationStrategy getMigrationStrategy() {
String migrationStrategy = config.get("migrationStrategy");
@@ -23,14 +23,13 @@ import java.sql.SQLException;
import liquibase.Liquibase;
import liquibase.exception.DatabaseException;
import liquibase.exception.LiquibaseException;
import liquibase.exception.LockException;
import liquibase.lockservice.LockService;
import org.jboss.logging.Logger;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.connections.jpa.JpaConnectionProviderFactory;
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.dblock.DBLockProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -57,6 +56,7 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
this.session = session;
}
private void lazyInit() {
if (!initialized) {
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
@@ -92,35 +92,41 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
@Override
public void waitForLock() {
lazyInit();
KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
while (maxAttempts > 0) {
try {
lockService.waitForLock();
factory.setHasLock(true);
this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
return;
} catch (LockRetryException le) {
// Indicates we should try to acquire lock again in different transaction
safeRollbackConnection();
restart();
maxAttempts--;
} catch (RuntimeException re) {
safeRollbackConnection();
safeCloseConnection();
throw re;
lazyInit();
while (maxAttempts > 0) {
try {
lockService.waitForLock();
factory.setHasLock(true);
this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
return;
} catch (LockRetryException le) {
// Indicates we should try to acquire lock again in different transaction
safeRollbackConnection();
restart();
maxAttempts--;
} catch (RuntimeException re) {
safeRollbackConnection();
safeCloseConnection();
throw re;
}
}
}
});
}
@Override
public void releaseLock() {
lazyInit();
KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
lazyInit();
lockService.releaseLock();
lockService.reset();
factory.setHasLock(false);
lockService.releaseLock();
lockService.reset();
factory.setHasLock(false);
});
}
@Override
@@ -136,21 +142,25 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
@Override
public void destroyLockInfo() {
lazyInit();
KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
lazyInit();
try {
this.lockService.destroy();
dbConnection.commit();
logger.debug("Destroyed lock table");
} catch (DatabaseException | SQLException de) {
logger.error("Failed to destroy lock table");
safeRollbackConnection();
}
try {
this.lockService.destroy();
dbConnection.commit();
logger.debug("Destroyed lock table");
} catch (DatabaseException | SQLException de) {
logger.error("Failed to destroy lock table");
safeRollbackConnection();
}
});
}
@Override
public void close() {
safeCloseConnection();
KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
safeCloseConnection();
});
}
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.PersistenceXmlParser;
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.ProxyClassLoader;
import org.keycloak.models.KeycloakSession;
@@ -46,8 +48,9 @@ public class JpaUtils {
return (schema==null) ? tableName : schema + "." + tableName;
}
public static EntityManagerFactory createEntityManagerFactory(KeycloakSession session, String unitName, Map<String, Object> properties, ClassLoader classLoader) {
PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(classLoader), PersistenceUnitTransactionType.RESOURCE_LOCAL);
public static EntityManagerFactory createEntityManagerFactory(KeycloakSession session, String unitName, Map<String, Object> properties, ClassLoader classLoader, boolean jta) {
PersistenceUnitTransactionType txType = jta ? PersistenceUnitTransactionType.JTA : PersistenceUnitTransactionType.RESOURCE_LOCAL;
PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(classLoader), txType);
List<ParsedPersistenceXmlDescriptor> persistenceUnits = parser.doResolve(properties);
for (ParsedPersistenceXmlDescriptor persistenceUnit : persistenceUnits) {
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
// to find and load the extra provided entities. Set the provided classloader as parent classloader.
persistenceUnit.setTransactionType(txType);
return Bootstrap.getEntityManagerFactoryBuilder(persistenceUnit, properties,
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;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialModel;
import org.keycloak.credential.UserCredentialStore;
import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
@@ -34,6 +38,8 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.jpa.entities.CredentialAttributeEntity;
import org.keycloak.models.jpa.entities.CredentialEntity;
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
import org.keycloak.models.jpa.entities.UserAttributeEntity;
import org.keycloak.models.jpa.entities.UserConsentEntity;
@@ -48,7 +54,9 @@ import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -58,7 +66,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class JpaUserProvider implements UserProvider {
public class JpaUserProvider implements UserProvider, UserCredentialStore {
private static final String EMAIL = "email";
private static final String USERNAME = "username";
@@ -367,6 +375,8 @@ public class JpaUserProvider implements UserProvider {
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteFederatedIdentityByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteCredentialAttributeByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteCredentialsByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteUserAttributesByRealm")
@@ -391,6 +401,10 @@ public class JpaUserProvider implements UserProvider {
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
.executeUpdate();
num = em.createNamedQuery("deleteCredentialAttributeByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
.executeUpdate();
num = em.createNamedQuery("deleteCredentialsByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
@@ -716,4 +730,174 @@ public class JpaUserProvider implements UserProvider {
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.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@@ -27,14 +28,19 @@ import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@NamedQueries({
@NamedQuery(name="credentialByUser", query="select cred from CredentialEntity cred where cred.user = :user"),
@NamedQuery(name="credentialByUserAndType", query="select cred from CredentialEntity cred where cred.user = :user and cred.type = :type"),
@NamedQuery(name="credentialByNameAndType", query="select cred from CredentialEntity cred where cred.user = :user and cred.type = :type and cred.device = :device"),
@NamedQuery(name="deleteCredentialsByRealm", query="delete from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId)"),
@NamedQuery(name="deleteCredentialsByRealmAndLink", query="delete from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
@@ -74,6 +80,8 @@ public class CredentialEntity {
@Column(name="PERIOD")
protected int period;
@OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy="credential")
protected Collection<CredentialAttributeEntity> credentialAttributes = new ArrayList<>();
public String getId() {
return id;
@@ -171,6 +179,14 @@ public class CredentialEntity {
this.period = period;
}
public Collection<CredentialAttributeEntity> getCredentialAttributes() {
return credentialAttributes;
}
public void setCredentialAttributes(Collection<CredentialAttributeEntity> credentialAttributes) {
this.credentialAttributes = credentialAttributes;
}
@Override
public boolean equals(Object o) {
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.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
import org.keycloak.credential.UserCredentialStore;
import org.keycloak.models.ClientModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
@@ -49,6 +51,7 @@ import org.keycloak.storage.jpa.entity.FederatedUserAttributeEntity;
import org.keycloak.storage.jpa.entity.FederatedUserConsentEntity;
import org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity;
import org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity;
import org.keycloak.storage.jpa.entity.FederatedUserCredentialAttributeEntity;
import org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity;
import org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity;
import org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity;
@@ -59,6 +62,7 @@ import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -75,7 +79,8 @@ public class JpaUserFederatedStorageProvider implements
UserCredentialsFederatedStorage,
UserGroupMembershipFederatedStorage,
UserRequiredActionsFederatedStorage,
UserRoleMappingsFederatedStorage {
UserRoleMappingsFederatedStorage,
UserCredentialStore {
private final KeycloakSession session;
protected EntityManager em;
@@ -606,6 +611,200 @@ public class JpaUserFederatedStorageProvider implements
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
public void preRemove(RealmModel realm) {
int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm")
@@ -620,6 +819,8 @@ public class JpaUserFederatedStorageProvider implements
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteBrokerLinkByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteFederatedCredentialAttributeByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteFederatedUserCredentialsByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteUserFederatedAttributesByRealm")
@@ -642,6 +843,10 @@ public class JpaUserFederatedStorageProvider implements
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
.executeUpdate();
num = em.createNamedQuery("deleteFederatedCredentialAttributeByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
.executeUpdate();
num = em.createNamedQuery("deleteFederatedUserCredentialsByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
@@ -699,6 +904,10 @@ public class JpaUserFederatedStorageProvider implements
.setParameter("userId", user.getId())
.setParameter("realmId", realm.getId())
.executeUpdate();
em.createNamedQuery("deleteFederatedCredentialAttributeByUser")
.setParameter("userId", user.getId())
.setParameter("realmId", realm.getId())
.executeUpdate();
em.createNamedQuery("deleteFederatedUserCredentialByUser")
.setParameter("userId", user.getId())
.setParameter("realmId", realm.getId())
@@ -737,6 +946,9 @@ public class JpaUserFederatedStorageProvider implements
em.createNamedQuery("deleteFederatedUserConsentsByStorageProvider")
.setParameter("storageProviderId", model.getId())
.executeUpdate();
em.createNamedQuery("deleteFederatedCredentialAttributeByStorageProvider")
.setParameter("storageProviderId", model.getId())
.executeUpdate();
em.createNamedQuery("deleteFederatedUserCredentialsByStorageProvider")
.setParameter("storageProviderId", model.getId())
.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;
import org.keycloak.models.jpa.entities.CredentialEntity;
import org.keycloak.models.jpa.entities.UserEntity;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@@ -29,7 +31,10 @@ import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -38,6 +43,7 @@ import javax.persistence.Table;
@NamedQueries({
@NamedQuery(name="federatedUserCredentialByUser", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId"),
@NamedQuery(name="federatedUserCredentialByUserAndType", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"),
@NamedQuery(name="federatedUserCredentialByNameAndType", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type and cred.device = :device"),
@NamedQuery(name="deleteFederatedUserCredentialByUser", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.realmId = :realmId"),
@NamedQuery(name="deleteFederatedUserCredentialByUserAndType", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"),
@NamedQuery(name="deleteFederatedUserCredentialByUserAndTypeAndDevice", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type and cred.device = :device"),
@@ -87,6 +93,8 @@ public class FederatedUserCredentialEntity {
protected int digits;
@Column(name="PERIOD")
protected int period;
@OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy="credential")
protected Collection<FederatedUserCredentialAttributeEntity> credentialAttributes = new ArrayList<>();
public String getId() {
@@ -201,6 +209,14 @@ public class FederatedUserCredentialEntity {
this.period = period;
}
public Collection<FederatedUserCredentialAttributeEntity> getCredentialAttributes() {
return credentialAttributes;
}
public void setCredentialAttributes(Collection<FederatedUserCredentialAttributeEntity> credentialAttributes) {
this.credentialAttributes = credentialAttributes;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -24,4 +24,39 @@
</addColumn>
</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>
@@ -22,6 +22,7 @@
<persistence-unit name="keycloak-default" transaction-type="RESOURCE_LOCAL">
<class>org.keycloak.models.jpa.entities.ClientEntity</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.RealmAttributeEntity</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.FederatedUserConsentProtocolMapperEntity</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.FederatedUserRequiredActionEntity</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.connections.mongo.api.MongoStore;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.credential.CredentialInput;
import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
@@ -635,4 +636,5 @@ public class MongoUserProvider implements UserProvider {
public void preRemove(RealmModel realm, ComponentModel component) {
}
}