keycloak-aplcache
Changes
examples/providers/authenticator/README.md 32(+12 -20)
examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java 17(+14 -3)
examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java 2(+1 -1)
examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java 82(+71 -11)
examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProviderFactory.java 8(+8 -0)
examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java 43(+9 -34)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java 21(+11 -10)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java 2(+0 -2)
Details
diff --git a/distribution/demo-dist/src/main/xslt/standalone.xsl b/distribution/demo-dist/src/main/xslt/standalone.xsl
index ca45244..d67ce63 100755
--- a/distribution/demo-dist/src/main/xslt/standalone.xsl
+++ b/distribution/demo-dist/src/main/xslt/standalone.xsl
@@ -43,7 +43,7 @@
<xsl:copy>
<xsl:apply-templates select="node()[name(.)='datasource']"/>
<datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" use-java-context="true">
- <connection-url>jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</connection-url>
+ <xa-datasource-property name="URL">jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</xa-datasource-property>
<driver>h2</driver>
<security>
<user-name>sa</user-name>
diff --git a/examples/providers/authenticator/pom.xml b/examples/providers/authenticator/pom.xml
index 0123a91..16a95d6 100755
--- a/examples/providers/authenticator/pom.xml
+++ b/examples/providers/authenticator/pom.xml
@@ -64,6 +64,13 @@
<target>1.8</target>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.wildfly.plugins</groupId>
+ <artifactId>wildfly-maven-plugin</artifactId>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </plugin>
</plugins>
</build>
</project>
examples/providers/authenticator/README.md 32(+12 -20)
diff --git a/examples/providers/authenticator/README.md b/examples/providers/authenticator/README.md
index 54dc752..ef612cc 100755
--- a/examples/providers/authenticator/README.md
+++ b/examples/providers/authenticator/README.md
@@ -1,31 +1,23 @@
Example Custom Authenticator
===================================================
-This is an example of defining a custom Authenticator and Required action. This example is explained in the user documentation
-of Keycloak. To deploy, build this directory then take the jar and copy it to providers directory. Alternatively you can deploy as a module by running:
+1. First, Keycloak must be running.
- KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.secret-question --resources=target/authenticator-required-action-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-services,org.jboss.resteasy.resteasy-jaxrs,javax.ws.rs.api"
+2. Execute the follow. This will build the example and deploy it
-Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
+ $ mvn clean install wildfly:deploy
- <providers>
- ...
- <provider>module:org.keycloak.examples.secret-question</provider>
- </providers>
+3. Copy the secret-question.ftl and secret-question-config.ftl files to the themes/base/login directory.
-You then have to copy the secret-question.ftl and secret-question-config.ftl files to the themes/base/login directory.
+4. Login to admin console. Hit browser refresh if you are already logged in so that the new providers show up.
-After you do all this, you then have to reboot keycloak. When reboot is complete, you will need to log into
-the admin console to create a new flow with your new authenticator.
+5. Go to the Authentication menu item and go to the Flow tab, you will be able to view the currently
+ defined flows. You cannot modify an built in flows, so, to add the Authenticator you
+ have to copy an existing flow or create your own. Copy the "Browser" flow.
-If you go to the Authentication menu item and go to the Flow tab, you will be able to view the currently
-defined flows. You cannot modify an built in flows, so, to add the Authenticator you
-have to copy an existing flow or create your own.
+6. In your copy, click the "Actions" menu item and "Add Execution". Pick Secret Question
-Next you have to register your required action.
-Click on the Required Actions tab. Click on the Register button and choose your new Required Action.
-Your new required action should now be displayed and enabled in the required actions list.
+7. Next you have to register the required action that you created. Click on the Required Actions tab in the Authenticaiton menu.
+ Click on the Register button and choose your new Required Action.
+ Your new required action should now be displayed and enabled in the required actions list.
-I'm hoping the UI is intuitive enough so that you
-can figure out for yourself how to create a flow and add the Authenticator and Required Action. We're looking to add a screencast
-to show this in action.
diff --git a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java
index e25ff81..fb71a7c 100755
--- a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java
+++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java
@@ -17,18 +17,20 @@
package org.keycloak.examples.authenticator;
+import org.jboss.resteasy.spi.HttpResponse;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.Authenticator;
+import org.keycloak.common.util.ServerCookie;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
-import org.keycloak.services.util.CookieHelper;
import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.net.URI;
@@ -87,13 +89,22 @@ public class SecretQuestionAuthenticator implements Authenticator {
}
URI uri = context.getUriInfo().getBaseUriBuilder().path("realms").path(context.getRealm().getName()).build();
- CookieHelper.addCookie("SECRET_QUESTION_ANSWERED", "true",
+ addCookie("SECRET_QUESTION_ANSWERED", "true",
uri.getRawPath(),
null, null,
maxCookieAge,
false, true);
}
+ public static void addCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) {
+ HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
+ StringBuffer cookieBuf = new StringBuffer();
+ ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, comment, maxAge, secure, httpOnly);
+ String cookie = cookieBuf.toString();
+ response.getOutputHeaders().add(HttpHeaders.SET_COOKIE, cookie);
+ }
+
+
protected boolean validateAnswer(AuthenticationFlowContext context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
String secret = formData.getFirst("secret_answer");
diff --git a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java
index 693c0eb..520aeb0 100644
--- a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java
+++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java
@@ -110,7 +110,7 @@ public class SecretQuestionCredentialProvider implements CredentialProvider, Cre
}
@Override
- public void onCache(RealmModel realm, CachedUserModel user) {
+ public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
List<CredentialModel> creds = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
if (!creds.isEmpty()) user.getCachedWith().put(CACHE_KEY, creds.get(0));
}
diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java
index a9bbc9b..8f7f147 100644
--- a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java
+++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java
@@ -18,16 +18,21 @@ package org.keycloak.examples.storage.user;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialInput;
+import org.keycloak.credential.CredentialInputUpdater;
+import org.keycloak.credential.CredentialInputValidator;
+import org.keycloak.credential.CredentialModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
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.KeycloakModelUtils;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider;
-import org.keycloak.storage.user.UserCredentialValidatorProvider;
import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserQueryProvider;
import org.keycloak.storage.user.UserRegistrationProvider;
@@ -52,9 +57,13 @@ import java.util.Map;
public class EjbExampleUserStorageProvider implements UserStorageProvider,
UserLookupProvider,
UserRegistrationProvider,
- UserCredentialValidatorProvider,
- UserQueryProvider {
+ UserQueryProvider,
+ CredentialInputUpdater,
+ CredentialInputValidator,
+ OnUserCache
+{
private static final Logger logger = Logger.getLogger(EjbExampleUserStorageProvider.class);
+ public static final String PASSWORD_CACHE_KEY = UserAdapter.class.getName() + ".password";
@PersistenceContext
protected EntityManager em;
@@ -150,18 +159,69 @@ public class EjbExampleUserStorageProvider implements UserStorageProvider,
}
@Override
- public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
- // having a "password" attribute is a workaround so that passwords can be cached. All done for performance reasons...
- // If we override getCredentialsDirectly/updateCredentialsDirectly
- // then the realm passsword policy will/may try and overwrite the plain text password with a hash.
- // If you don't like this workaround, you can query the database every time to validate the password
- for (UserCredentialModel cred : input) {
- if (!UserCredentialModel.PASSWORD.equals(cred.getType())) return false;
- if (!cred.getValue().equals(user.getFirstAttribute("password"))) return false;
+ public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
+ String password = ((UserAdapter)delegate).getPassword();
+ if (password != null) {
+ user.getCachedWith().put(PASSWORD_CACHE_KEY, password);
}
+ }
+
+ @Override
+ public boolean supportsCredentialType(String credentialType) {
+ return CredentialModel.PASSWORD.equals(credentialType);
+ }
+
+ @Override
+ public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+ if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) return false;
+ UserCredentialModel cred = (UserCredentialModel)input;
+ UserAdapter adapter = getUserAdapter(user);
+ adapter.setPassword(cred.getValue());
+
return true;
}
+ public UserAdapter getUserAdapter(UserModel user) {
+ UserAdapter adapter = null;
+ if (user instanceof CachedUserModel) {
+ adapter = (UserAdapter)((CachedUserModel)user).getDelegateForUpdate();
+ } else {
+ adapter = (UserAdapter)user;
+ }
+ return adapter;
+ }
+
+ @Override
+ public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
+ if (!supportsCredentialType(credentialType)) return;
+
+ getUserAdapter(user).setPassword(null);
+
+ }
+
+ @Override
+ public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
+ return supportsCredentialType(credentialType) && getPassword(user) != null;
+ }
+
+ @Override
+ public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
+ if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) return false;
+ UserCredentialModel cred = (UserCredentialModel)input;
+ String password = getPassword(user);
+ return password != null && password.equals(cred.getValue());
+ }
+
+ public String getPassword(UserModel user) {
+ String password = null;
+ if (user instanceof CachedUserModel) {
+ password = (String)((CachedUserModel)user).getCachedWith().get(PASSWORD_CACHE_KEY);
+ } else if (user instanceof UserAdapter) {
+ password = ((UserAdapter)user).getPassword();
+ }
+ return password;
+ }
+
@Override
public int getUsersCount(RealmModel realm) {
Object count = em.createNamedQuery("getUserCount")
diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProviderFactory.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProviderFactory.java
index a1db65d..1e16100 100644
--- a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProviderFactory.java
+++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProviderFactory.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.examples.storage.user;
+import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
@@ -32,6 +33,7 @@ import java.util.List;
* @version $Revision: 1 $
*/
public class EjbExampleUserStorageProviderFactory implements UserStorageProviderFactory<EjbExampleUserStorageProvider> {
+ private static final Logger logger = Logger.getLogger(EjbExampleUserStorageProviderFactory.class);
@Override
@@ -56,4 +58,10 @@ public class EjbExampleUserStorageProviderFactory implements UserStorageProvider
public String getHelpText() {
return "JPA Example User Storage Provider";
}
+
+ @Override
+ public void close() {
+ logger.info("<<<<<< Closing factory");
+
+ }
}
diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java
index 84f4084..8c8bcd6 100644
--- a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java
+++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java
@@ -34,7 +34,7 @@ import java.util.Map;
* @version $Revision: 1 $
*/
public class UserAdapter extends AbstractUserAdapterFederatedStorage {
- private static final Logger logger = Logger.getLogger(EjbExampleUserStorageProvider.class);
+ private static final Logger logger = Logger.getLogger(UserAdapter.class);
protected UserEntity entity;
protected String keycloakId;
@@ -44,6 +44,14 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
keycloakId = StorageId.keycloakId(model, entity.getId());
}
+ public String getPassword() {
+ return entity.getPassword();
+ }
+
+ public void setPassword(String password) {
+ entity.setPassword(password);
+ }
+
@Override
public String getUsername() {
return entity.getUsername();
@@ -74,13 +82,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
public void setSingleAttribute(String name, String value) {
if (name.equals("phone")) {
entity.setPhone(value);
- } else if (name.equals("password")) {
- // ignore
-
- // having a "password" attribute is a workaround so that passwords can be cached. All done for performance reasons...
- // If we override getCredentialsDirectly/updateCredentialsDirectly
- // then the realm passsword policy will/may try and overwrite the plain text password with a hash.
- // If you don't like this workaround, you can query the database every time to validate the password
} else {
super.setSingleAttribute(name, value);
}
@@ -90,13 +91,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
public void removeAttribute(String name) {
if (name.equals("phone")) {
entity.setPhone(null);
- } else if (name.equals("password")) {
- // ignore
-
- // having a "password" attribute is a workaround so that passwords can be cached. All done for performance reasons...
- // If we override getCredentialsDirectly/updateCredentialsDirectly
- // then the realm passsword policy will/may try and overwrite the plain text password with a hash.
- // If you don't like this workaround, you can query the database every time to validate the password
} else {
super.removeAttribute(name);
}
@@ -106,13 +100,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
public void setAttribute(String name, List<String> values) {
if (name.equals("phone")) {
entity.setPhone(values.get(0));
- } else if (name.equals("password")) {
- // ignore
-
- // having a "password" attribute is a workaround so that passwords can be cached. All done for performance reasons...
- // If we override getCredentialsDirectly/updateCredentialsDirectly
- // then the realm passsword policy will/may try and overwrite the plain text password with a hash.
- // If you don't like this workaround, you can query the database every time to validate the password
} else {
super.setAttribute(name, values);
}
@@ -122,12 +109,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
public String getFirstAttribute(String name) {
if (name.equals("phone")) {
return entity.getPhone();
- } else if (name.equals("password")) {
- // having a "password" attribute is a workaround so that passwords can be cached. All done for performance reasons...
- // If we override getCredentialsDirectly/updateCredentialsDirectly
- // then the realm passsword policy will/may try and overwrite the plain text password with a hash.
- // If you don't like this workaround, you can query the database every time to validate the password
- return entity.getPassword();
} else {
return super.getFirstAttribute(name);
}
@@ -139,12 +120,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
MultivaluedHashMap<String, String> all = new MultivaluedHashMap<>();
all.putAll(attrs);
all.add("phone", entity.getPhone());
-
- // having a "password" attribute is a workaround so that passwords can be cached. All done for performance reasons...
- // If we override getCredentialsDirectly/updateCredentialsDirectly
- // then the realm passsword policy will/may try and overwrite the plain text password with a hash.
- // If you don't like this workaround, you can query the database every time to validate the password
- all.add("password", entity.getPassword());
return all;
}
diff --git a/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml b/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml
index 9894af4..51082e1 100644
--- a/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml
+++ b/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml
@@ -4,7 +4,7 @@
xsi:schemaLocation="
http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
- <persistence-unit name="user-storage-jpa-example">
+ <persistence-unit name="user-storage-jpa-example" transaction-type="JTA">
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<class>org.keycloak.examples.storage.user.UserEntity</class>
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
index 6483f2a..002761e 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
@@ -60,12 +60,14 @@ public class UserAdapter implements CachedUserModel {
this.realm = realm;
}
- protected void getDelegateForUpdate() {
+ @Override
+ public UserModel getDelegateForUpdate() {
if (updated == null) {
userProviderCache.registerUserInvalidation(realm, cached);
updated = userProviderCache.getDelegate().getUserById(getId(), realm);
if (updated == null) throw new IllegalStateException("Not found in database");
}
+ return updated;
}
@Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
index 9143052..7300768 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
@@ -168,21 +168,22 @@ public class UserCacheSession implements UserCache {
}
CachedUser cached = cache.get(id, CachedUser.class);
+ UserModel delegate = null;
boolean wasCached = cached != null;
if (cached == null) {
logger.trace("not cached");
Long loaded = cache.getCurrentRevision(id);
- UserModel model = getDelegate().getUserById(id, realm);
- if (model == null) {
+ delegate = getDelegate().getUserById(id, realm);
+ if (delegate == null) {
logger.trace("delegate returning null");
return null;
}
- cached = new CachedUser(loaded, realm, model);
+ cached = new CachedUser(loaded, realm, delegate);
cache.addRevisioned(cached, startupRevision);
}
logger.trace("returning new cache adapter");
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
- if (!wasCached) onCache(realm, adapter);
+ if (!wasCached) onCache(realm, adapter, delegate);
managedUsers.put(id, adapter);
return adapter;
}
@@ -251,24 +252,24 @@ public class UserCacheSession implements UserCache {
}
}
- protected UserAdapter getUserAdapter(RealmModel realm, String userId, Long loaded, UserModel model) {
+ protected UserAdapter getUserAdapter(RealmModel realm, String userId, Long loaded, UserModel delegate) {
CachedUser cached = cache.get(userId, CachedUser.class);
boolean wasCached = cached != null;
if (cached == null) {
- cached = new CachedUser(loaded, realm, model);
+ cached = new CachedUser(loaded, realm, delegate);
cache.addRevisioned(cached, startupRevision);
}
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
if (!wasCached) {
- onCache(realm, adapter);
+ onCache(realm, adapter, delegate);
}
return adapter;
}
- private void onCache(RealmModel realm, UserAdapter adapter) {
- ((OnUserCache)getDelegate()).onCache(realm, adapter);
- ((OnUserCache)session.userCredentialManager()).onCache(realm, adapter);
+ private void onCache(RealmModel realm, UserAdapter adapter, UserModel delegate) {
+ ((OnUserCache)getDelegate()).onCache(realm, adapter, delegate);
+ ((OnUserCache)session.userCredentialManager()).onCache(realm, adapter, delegate);
}
@Override
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
index 5d9c7cd..9ee5a6a 100644
--- a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
@@ -21,10 +21,24 @@ import org.keycloak.models.UserModel;
import java.util.concurrent.ConcurrentHashMap;
/**
+ * Cached users will implement this interface
+ *
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface CachedUserModel extends UserModel {
+
+ /**
+ * Invalidates the cache for this user and returns a delegate that represents the actual data provider
+ *
+ * @return
+ */
+ UserModel getDelegateForUpdate();
+
+ /**
+ * Invalidate the cache for this user
+ *
+ */
void invalidate();
/**
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java b/server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java
index e676ce1..319b5c5 100644
--- a/server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java
@@ -17,11 +17,12 @@
package org.keycloak.models.cache;
import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface OnUserCache {
- void onCache(RealmModel realm, CachedUserModel user);
+ void onCache(RealmModel realm, CachedUserModel user, UserModel delegate);
}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserProvider.java b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
index d6267c9..e1b64fa 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
@@ -18,9 +18,7 @@
package org.keycloak.models;
import org.keycloak.component.ComponentModel;
-import org.keycloak.credential.CredentialInput;
import org.keycloak.provider.Provider;
-import org.keycloak.storage.user.UserCredentialValidatorProvider;
import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserQueryProvider;
import org.keycloak.storage.user.UserRegistrationProvider;
diff --git a/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java b/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java
index b6dd489..f5c973a 100644
--- a/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java
+++ b/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java
@@ -53,7 +53,7 @@ public class OTPCredentialProvider implements CredentialProvider, CredentialInpu
}
@Override
- public void onCache(RealmModel realm, CachedUserModel user) {
+ public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
List<CredentialModel> creds = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP);
user.getCachedWith().put(OTPCredentialProvider.class.getName() + "." + CredentialModel.TOTP, creds);
diff --git a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
index 17803c1..4fc5856 100644
--- a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
+++ b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
@@ -206,7 +206,7 @@ public class PasswordCredentialProvider implements CredentialProvider, Credentia
}
@Override
- public void onCache(RealmModel realm, CachedUserModel user) {
+ public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
List<CredentialModel> passwords = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD);
if (passwords != null) {
user.getCachedWith().put(PASSWORD_CACHE_KEY, passwords);
diff --git a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java
index 915cbe7..fab3127 100644
--- a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java
+++ b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java
@@ -254,10 +254,10 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
}
@Override
- public void onCache(RealmModel realm, CachedUserModel user) {
+ public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
List<OnUserCache> credentialProviders = getCredentialProviders(realm, OnUserCache.class);
for (OnUserCache validator : credentialProviders) {
- validator.onCache(realm, user);
+ validator.onCache(realm, user, delegate);
}
}
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
index 36f9b7f..808b413 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
@@ -136,6 +136,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
@Override
public void undeploy(ProviderManager pm) {
+ logger.debug("undeploy");
// we make a copy to avoid concurrent access exceptions
Map<Class<? extends Provider>, Map<String, ProviderFactory>> copy = getFactoriesCopy();
MultivaluedHashMap<Class<? extends Provider>, ProviderFactory> factories = pm.getLoadedFactories();
@@ -144,6 +145,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
Map<String, ProviderFactory> registered = copy.get(entry.getKey());
for (ProviderFactory factory : entry.getValue()) {
undeployed.add(factory);
+ logger.debugv("undeploying {0} of id {1}", factory.getClass().getName(), factory.getId());
if (registered != null) {
registered.remove(factory.getId());
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index aa536f5..b188501 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -570,6 +570,10 @@ public class AuthenticationManager {
Set<String> requiredActions) {
for (String action : requiredActions) {
RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(action);
+ if (model == null) {
+ logger.warnv("Could not find configuration for Required Action {0}, did you forget to register it?", action);
+ continue;
+ }
if (!model.isEnabled()) {
continue;
}
diff --git a/services/src/main/java/org/keycloak/storage/UserStorageManager.java b/services/src/main/java/org/keycloak/storage/UserStorageManager.java
index d682627..8da231e 100755
--- a/services/src/main/java/org/keycloak/storage/UserStorageManager.java
+++ b/services/src/main/java/org/keycloak/storage/UserStorageManager.java
@@ -566,15 +566,15 @@ public class UserStorageManager implements UserProvider, OnUserCache {
}
@Override
- public void onCache(RealmModel realm, CachedUserModel user) {
+ public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
if (StorageId.isLocalStorage(user)) {
if (session.userLocalStorage() instanceof OnUserCache) {
- ((OnUserCache)session.userLocalStorage()).onCache(realm, user);
+ ((OnUserCache)session.userLocalStorage()).onCache(realm, user, delegate);
}
} else {
Object provider = getStorageProvider(session, realm, StorageId.resolveProviderId(user));
if (provider != null && provider instanceof OnUserCache) {
- ((OnUserCache)provider).onCache(realm, user);
+ ((OnUserCache)provider).onCache(realm, user, delegate);
}
}
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java
index 962262a..a9fe9ab 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java
@@ -26,12 +26,10 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
-import org.keycloak.storage.user.UserCredentialValidatorProvider;
import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserRegistrationProvider;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java
index 5a1db7e..bab2c4a 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java
@@ -29,7 +29,6 @@ import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.adapter.AbstractUserAdapter;
import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
-import org.keycloak.storage.user.UserCredentialValidatorProvider;
import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserQueryProvider;
diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
index c823e4f..cd7fc81 100755
--- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
+++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
@@ -21,22 +21,22 @@
<extension-module>org.jboss.as.connector</extension-module>
<subsystem xmlns="urn:jboss:domain:datasources:4.0">
<datasources>
- <datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
- <connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</connection-url>
+ <xa-datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
+ <xa-datasource-property name="URL">jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</xa-datasource-property>
<driver>h2</driver>
<security>
<user-name>sa</user-name>
<password>sa</password>
</security>
- </datasource>
- <datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
- <?KEYCLOAK_DS_CONNECTION_URL?>
+ </xa-datasource>
+ <xa-datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
+ <xa-datasource-property name="URL"><?KEYCLOAK_DS_CONNECTION_URL?></xa-datasource-property>
<driver>h2</driver>
<security>
<user-name>sa</user-name>
<password>sa</password>
</security>
- </datasource>
+ </xa-datasource>
<drivers>
<driver name="h2" module="com.h2database.h2">
<xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
@@ -46,12 +46,12 @@
</subsystem>
<supplement name="default">
<replacement placeholder="KEYCLOAK_DS_CONNECTION_URL">
- <connection-url>jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</connection-url>
+ jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE
</replacement>
</supplement>
<supplement name="domain">
<replacement placeholder="KEYCLOAK_DS_CONNECTION_URL">
- <connection-url>jdbc:h2:${jboss.server.data.dir}/../../shared-database/keycloak;AUTO_SERVER=TRUE</connection-url>
+ jdbc:h2:${jboss.server.data.dir}/../../shared-database/keycloak;AUTO_SERVER=TRUE
</replacement>
</supplement>
</config>