authenticator example updated

This commit is contained in:
Bill Burke 2016-09-23 16:50:08 -04:00
parent a1bcd0651d
commit ff1326fe35
24 changed files with 175 additions and 136 deletions

View file

@ -43,7 +43,7 @@
<xsl:copy> <xsl:copy>
<xsl:apply-templates select="node()[name(.)='datasource']"/> <xsl:apply-templates select="node()[name(.)='datasource']"/>
<datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" use-java-context="true"> <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> <driver>h2</driver>
<security> <security>
<user-name>sa</user-name> <user-name>sa</user-name>

View file

@ -1,31 +1,23 @@
Example Custom Authenticator Example Custom Authenticator
=================================================== ===================================================
This is an example of defining a custom Authenticator and Required action. This example is explained in the user documentation 1. First, Keycloak must be running.
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:
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> 3. Copy the secret-question.ftl and secret-question-config.ftl files to the themes/base/login directory.
...
<provider>module:org.keycloak.examples.secret-question</provider>
</providers>
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 5. Go to the Authentication menu item and go to the Flow tab, you will be able to view the currently
the admin console to create a new flow with your new authenticator. 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 6. In your copy, click the "Actions" menu item and "Add Execution". Pick Secret Question
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.
Next you have to register your required action. 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 Required Actions tab. Click on the Register button and choose your new Required Action. 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. 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.

View file

@ -64,6 +64,13 @@
<target>1.8</target> <target>1.8</target>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

View file

@ -17,18 +17,20 @@
package org.keycloak.examples.authenticator; 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.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError; import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.Authenticator; import org.keycloak.authentication.Authenticator;
import org.keycloak.common.util.ServerCookie;
import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.services.util.CookieHelper;
import javax.ws.rs.core.Cookie; import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.net.URI; 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(); 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(), uri.getRawPath(),
null, null, null, null,
maxCookieAge, maxCookieAge,
false, true); 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) { protected boolean validateAnswer(AuthenticationFlowContext context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters(); MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
String secret = formData.getFirst("secret_answer"); String secret = formData.getFirst("secret_answer");

View file

@ -110,7 +110,7 @@ public class SecretQuestionCredentialProvider implements CredentialProvider, Cre
} }
@Override @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); List<CredentialModel> creds = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
if (!creds.isEmpty()) user.getCachedWith().put(CACHE_KEY, creds.get(0)); if (!creds.isEmpty()) user.getCachedWith().put(CACHE_KEY, creds.get(0));
} }

View file

@ -18,16 +18,21 @@ package org.keycloak.examples.storage.user;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialInputUpdater;
import org.keycloak.credential.CredentialInputValidator;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel; 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.models.utils.KeycloakModelUtils;
import org.keycloak.storage.StorageId; import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.user.UserCredentialValidatorProvider;
import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserQueryProvider; import org.keycloak.storage.user.UserQueryProvider;
import org.keycloak.storage.user.UserRegistrationProvider; import org.keycloak.storage.user.UserRegistrationProvider;
@ -52,9 +57,13 @@ import java.util.Map;
public class EjbExampleUserStorageProvider implements UserStorageProvider, public class EjbExampleUserStorageProvider implements UserStorageProvider,
UserLookupProvider, UserLookupProvider,
UserRegistrationProvider, UserRegistrationProvider,
UserCredentialValidatorProvider, UserQueryProvider,
UserQueryProvider { CredentialInputUpdater,
CredentialInputValidator,
OnUserCache
{
private static final Logger logger = Logger.getLogger(EjbExampleUserStorageProvider.class); private static final Logger logger = Logger.getLogger(EjbExampleUserStorageProvider.class);
public static final String PASSWORD_CACHE_KEY = UserAdapter.class.getName() + ".password";
@PersistenceContext @PersistenceContext
protected EntityManager em; protected EntityManager em;
@ -150,18 +159,69 @@ public class EjbExampleUserStorageProvider implements UserStorageProvider,
} }
@Override @Override
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) { public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
// having a "password" attribute is a workaround so that passwords can be cached. All done for performance reasons... String password = ((UserAdapter)delegate).getPassword();
// If we override getCredentialsDirectly/updateCredentialsDirectly if (password != null) {
// then the realm passsword policy will/may try and overwrite the plain text password with a hash. user.getCachedWith().put(PASSWORD_CACHE_KEY, password);
// 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;
} }
}
@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; 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 @Override
public int getUsersCount(RealmModel realm) { public int getUsersCount(RealmModel realm) {
Object count = em.createNamedQuery("getUserCount") Object count = em.createNamedQuery("getUserCount")

View file

@ -16,6 +16,7 @@
*/ */
package org.keycloak.examples.storage.user; package org.keycloak.examples.storage.user;
import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -32,6 +33,7 @@ import java.util.List;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class EjbExampleUserStorageProviderFactory implements UserStorageProviderFactory<EjbExampleUserStorageProvider> { public class EjbExampleUserStorageProviderFactory implements UserStorageProviderFactory<EjbExampleUserStorageProvider> {
private static final Logger logger = Logger.getLogger(EjbExampleUserStorageProviderFactory.class);
@Override @Override
@ -56,4 +58,10 @@ public class EjbExampleUserStorageProviderFactory implements UserStorageProvider
public String getHelpText() { public String getHelpText() {
return "JPA Example User Storage Provider"; return "JPA Example User Storage Provider";
} }
@Override
public void close() {
logger.info("<<<<<< Closing factory");
}
} }

View file

@ -34,7 +34,7 @@ import java.util.Map;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class UserAdapter extends AbstractUserAdapterFederatedStorage { 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 UserEntity entity;
protected String keycloakId; protected String keycloakId;
@ -44,6 +44,14 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
keycloakId = StorageId.keycloakId(model, entity.getId()); keycloakId = StorageId.keycloakId(model, entity.getId());
} }
public String getPassword() {
return entity.getPassword();
}
public void setPassword(String password) {
entity.setPassword(password);
}
@Override @Override
public String getUsername() { public String getUsername() {
return entity.getUsername(); return entity.getUsername();
@ -74,13 +82,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
public void setSingleAttribute(String name, String value) { public void setSingleAttribute(String name, String value) {
if (name.equals("phone")) { if (name.equals("phone")) {
entity.setPhone(value); 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 { } else {
super.setSingleAttribute(name, value); super.setSingleAttribute(name, value);
} }
@ -90,13 +91,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
public void removeAttribute(String name) { public void removeAttribute(String name) {
if (name.equals("phone")) { if (name.equals("phone")) {
entity.setPhone(null); 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 { } else {
super.removeAttribute(name); super.removeAttribute(name);
} }
@ -106,13 +100,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
public void setAttribute(String name, List<String> values) { public void setAttribute(String name, List<String> values) {
if (name.equals("phone")) { if (name.equals("phone")) {
entity.setPhone(values.get(0)); 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 { } else {
super.setAttribute(name, values); super.setAttribute(name, values);
} }
@ -122,12 +109,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
public String getFirstAttribute(String name) { public String getFirstAttribute(String name) {
if (name.equals("phone")) { if (name.equals("phone")) {
return entity.getPhone(); 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 { } else {
return super.getFirstAttribute(name); return super.getFirstAttribute(name);
} }
@ -139,12 +120,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
MultivaluedHashMap<String, String> all = new MultivaluedHashMap<>(); MultivaluedHashMap<String, String> all = new MultivaluedHashMap<>();
all.putAll(attrs); all.putAll(attrs);
all.add("phone", entity.getPhone()); 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; return all;
} }

View file

@ -4,7 +4,7 @@
xsi:schemaLocation=" xsi:schemaLocation="
http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> 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> <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<class>org.keycloak.examples.storage.user.UserEntity</class> <class>org.keycloak.examples.storage.user.UserEntity</class>

View file

@ -60,12 +60,14 @@ public class UserAdapter implements CachedUserModel {
this.realm = realm; this.realm = realm;
} }
protected void getDelegateForUpdate() { @Override
public UserModel getDelegateForUpdate() {
if (updated == null) { if (updated == null) {
userProviderCache.registerUserInvalidation(realm, cached); userProviderCache.registerUserInvalidation(realm, cached);
updated = userProviderCache.getDelegate().getUserById(getId(), realm); updated = userProviderCache.getDelegate().getUserById(getId(), realm);
if (updated == null) throw new IllegalStateException("Not found in database"); if (updated == null) throw new IllegalStateException("Not found in database");
} }
return updated;
} }
@Override @Override

View file

@ -168,21 +168,22 @@ public class UserCacheSession implements UserCache {
} }
CachedUser cached = cache.get(id, CachedUser.class); CachedUser cached = cache.get(id, CachedUser.class);
UserModel delegate = null;
boolean wasCached = cached != null; boolean wasCached = cached != null;
if (cached == null) { if (cached == null) {
logger.trace("not cached"); logger.trace("not cached");
Long loaded = cache.getCurrentRevision(id); Long loaded = cache.getCurrentRevision(id);
UserModel model = getDelegate().getUserById(id, realm); delegate = getDelegate().getUserById(id, realm);
if (model == null) { if (delegate == null) {
logger.trace("delegate returning null"); logger.trace("delegate returning null");
return null; return null;
} }
cached = new CachedUser(loaded, realm, model); cached = new CachedUser(loaded, realm, delegate);
cache.addRevisioned(cached, startupRevision); cache.addRevisioned(cached, startupRevision);
} }
logger.trace("returning new cache adapter"); logger.trace("returning new cache adapter");
UserAdapter adapter = new UserAdapter(cached, this, session, realm); UserAdapter adapter = new UserAdapter(cached, this, session, realm);
if (!wasCached) onCache(realm, adapter); if (!wasCached) onCache(realm, adapter, delegate);
managedUsers.put(id, adapter); managedUsers.put(id, adapter);
return 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); CachedUser cached = cache.get(userId, CachedUser.class);
boolean wasCached = cached != null; boolean wasCached = cached != null;
if (cached == null) { if (cached == null) {
cached = new CachedUser(loaded, realm, model); cached = new CachedUser(loaded, realm, delegate);
cache.addRevisioned(cached, startupRevision); cache.addRevisioned(cached, startupRevision);
} }
UserAdapter adapter = new UserAdapter(cached, this, session, realm); UserAdapter adapter = new UserAdapter(cached, this, session, realm);
if (!wasCached) { if (!wasCached) {
onCache(realm, adapter); onCache(realm, adapter, delegate);
} }
return adapter; return adapter;
} }
private void onCache(RealmModel realm, UserAdapter adapter) { private void onCache(RealmModel realm, UserAdapter adapter, UserModel delegate) {
((OnUserCache)getDelegate()).onCache(realm, adapter); ((OnUserCache)getDelegate()).onCache(realm, adapter, delegate);
((OnUserCache)session.userCredentialManager()).onCache(realm, adapter); ((OnUserCache)session.userCredentialManager()).onCache(realm, adapter, delegate);
} }
@Override @Override

View file

@ -18,9 +18,7 @@
package org.keycloak.models; package org.keycloak.models;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialInput;
import org.keycloak.provider.Provider; import org.keycloak.provider.Provider;
import org.keycloak.storage.user.UserCredentialValidatorProvider;
import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserQueryProvider; import org.keycloak.storage.user.UserQueryProvider;
import org.keycloak.storage.user.UserRegistrationProvider; import org.keycloak.storage.user.UserRegistrationProvider;

View file

@ -21,10 +21,24 @@ import org.keycloak.models.UserModel;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
* Cached users will implement this interface
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public interface CachedUserModel extends UserModel { 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(); void invalidate();
/** /**

View file

@ -17,11 +17,12 @@
package org.keycloak.models.cache; package org.keycloak.models.cache;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public interface OnUserCache { public interface OnUserCache {
void onCache(RealmModel realm, CachedUserModel user); void onCache(RealmModel realm, CachedUserModel user, UserModel delegate);
} }

View file

@ -1,33 +0,0 @@
/*
* 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.user;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface UserCredentialValidatorProvider {
boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input);
}

View file

@ -53,7 +53,7 @@ public class OTPCredentialProvider implements CredentialProvider, CredentialInpu
} }
@Override @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); List<CredentialModel> creds = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP);
user.getCachedWith().put(OTPCredentialProvider.class.getName() + "." + CredentialModel.TOTP, creds); user.getCachedWith().put(OTPCredentialProvider.class.getName() + "." + CredentialModel.TOTP, creds);

View file

@ -206,7 +206,7 @@ public class PasswordCredentialProvider implements CredentialProvider, Credentia
} }
@Override @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); List<CredentialModel> passwords = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD);
if (passwords != null) { if (passwords != null) {
user.getCachedWith().put(PASSWORD_CACHE_KEY, passwords); user.getCachedWith().put(PASSWORD_CACHE_KEY, passwords);

View file

@ -254,10 +254,10 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
} }
@Override @Override
public void onCache(RealmModel realm, CachedUserModel user) { public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
List<OnUserCache> credentialProviders = getCredentialProviders(realm, OnUserCache.class); List<OnUserCache> credentialProviders = getCredentialProviders(realm, OnUserCache.class);
for (OnUserCache validator : credentialProviders) { for (OnUserCache validator : credentialProviders) {
validator.onCache(realm, user); validator.onCache(realm, user, delegate);
} }
} }

View file

@ -136,6 +136,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
@Override @Override
public void undeploy(ProviderManager pm) { public void undeploy(ProviderManager pm) {
logger.debug("undeploy");
// we make a copy to avoid concurrent access exceptions // we make a copy to avoid concurrent access exceptions
Map<Class<? extends Provider>, Map<String, ProviderFactory>> copy = getFactoriesCopy(); Map<Class<? extends Provider>, Map<String, ProviderFactory>> copy = getFactoriesCopy();
MultivaluedHashMap<Class<? extends Provider>, ProviderFactory> factories = pm.getLoadedFactories(); 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()); Map<String, ProviderFactory> registered = copy.get(entry.getKey());
for (ProviderFactory factory : entry.getValue()) { for (ProviderFactory factory : entry.getValue()) {
undeployed.add(factory); undeployed.add(factory);
logger.debugv("undeploying {0} of id {1}", factory.getClass().getName(), factory.getId());
if (registered != null) { if (registered != null) {
registered.remove(factory.getId()); registered.remove(factory.getId());
} }

View file

@ -570,6 +570,10 @@ public class AuthenticationManager {
Set<String> requiredActions) { Set<String> requiredActions) {
for (String action : requiredActions) { for (String action : requiredActions) {
RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(action); 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()) { if (!model.isEnabled()) {
continue; continue;
} }

View file

@ -566,15 +566,15 @@ public class UserStorageManager implements UserProvider, OnUserCache {
} }
@Override @Override
public void onCache(RealmModel realm, CachedUserModel user) { public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
if (StorageId.isLocalStorage(user)) { if (StorageId.isLocalStorage(user)) {
if (session.userLocalStorage() instanceof OnUserCache) { if (session.userLocalStorage() instanceof OnUserCache) {
((OnUserCache)session.userLocalStorage()).onCache(realm, user); ((OnUserCache)session.userLocalStorage()).onCache(realm, user, delegate);
} }
} else { } else {
Object provider = getStorageProvider(session, realm, StorageId.resolveProviderId(user)); Object provider = getStorageProvider(session, realm, StorageId.resolveProviderId(user));
if (provider != null && provider instanceof OnUserCache) { if (provider != null && provider instanceof OnUserCache) {
((OnUserCache)provider).onCache(realm, user); ((OnUserCache)provider).onCache(realm, user, delegate);
} }
} }
} }

View file

@ -26,12 +26,10 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.storage.StorageId; import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage; import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
import org.keycloak.storage.user.UserCredentialValidatorProvider;
import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserRegistrationProvider; import org.keycloak.storage.user.UserRegistrationProvider;

View file

@ -29,7 +29,6 @@ import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.adapter.AbstractUserAdapter; import org.keycloak.storage.adapter.AbstractUserAdapter;
import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage; import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
import org.keycloak.storage.user.UserCredentialValidatorProvider;
import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserQueryProvider; import org.keycloak.storage.user.UserQueryProvider;

View file

@ -21,22 +21,22 @@
<extension-module>org.jboss.as.connector</extension-module> <extension-module>org.jboss.as.connector</extension-module>
<subsystem xmlns="urn:jboss:domain:datasources:4.0"> <subsystem xmlns="urn:jboss:domain:datasources:4.0">
<datasources> <datasources>
<datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true"> <xa-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-property name="URL">jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</xa-datasource-property>
<driver>h2</driver> <driver>h2</driver>
<security> <security>
<user-name>sa</user-name> <user-name>sa</user-name>
<password>sa</password> <password>sa</password>
</security> </security>
</datasource> </xa-datasource>
<datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true"> <xa-datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
<?KEYCLOAK_DS_CONNECTION_URL?> <xa-datasource-property name="URL"><?KEYCLOAK_DS_CONNECTION_URL?></xa-datasource-property>
<driver>h2</driver> <driver>h2</driver>
<security> <security>
<user-name>sa</user-name> <user-name>sa</user-name>
<password>sa</password> <password>sa</password>
</security> </security>
</datasource> </xa-datasource>
<drivers> <drivers>
<driver name="h2" module="com.h2database.h2"> <driver name="h2" module="com.h2database.h2">
<xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class> <xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
@ -46,12 +46,12 @@
</subsystem> </subsystem>
<supplement name="default"> <supplement name="default">
<replacement placeholder="KEYCLOAK_DS_CONNECTION_URL"> <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> </replacement>
</supplement> </supplement>
<supplement name="domain"> <supplement name="domain">
<replacement placeholder="KEYCLOAK_DS_CONNECTION_URL"> <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> </replacement>
</supplement> </supplement>
</config> </config>