authenticator example updated
This commit is contained in:
parent
a1bcd0651d
commit
ff1326fe35
24 changed files with 175 additions and 136 deletions
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
||||
If you go to the Authentication menu item and go to the Flow tab, you will be able to view the currently
|
||||
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.
|
||||
have to copy an existing flow or create your own. Copy the "Browser" flow.
|
||||
|
||||
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.
|
||||
6. In your copy, click the "Actions" menu item and "Add Execution". Pick Secret Question
|
||||
|
||||
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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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");
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue