Merge pull request #3258 from patriot1burke/master

Credentials Refactoring
This commit is contained in:
Bill Burke 2016-09-24 10:22:05 -04:00 committed by GitHub
commit 370a6c1631
125 changed files with 1800 additions and 1624 deletions

View file

@ -17,6 +17,10 @@
package org.keycloak.representations.idm;
import org.keycloak.common.util.MultivaluedHashMap;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
@ -45,6 +49,7 @@ public class CredentialRepresentation {
private Integer digits;
private Integer period;
private Long createdDate;
private MultivaluedHashMap<String, String> config;
// only used when updating a credential. Might set required action
protected Boolean temporary;
@ -144,4 +149,12 @@ public class CredentialRepresentation {
public void setCreatedDate(Long createdDate) {
this.createdDate = createdDate;
}
public MultivaluedHashMap<String, String> getConfig() {
return config;
}
public void setConfig(MultivaluedHashMap<String, String> config) {
this.config = config;
}
}

View file

@ -123,10 +123,12 @@ public class UserRepresentation {
this.enabled = enabled;
}
@Deprecated
public Boolean isTotp() {
return totp;
}
@Deprecated
public void setTotp(Boolean totp) {
this.totp = totp;
}

View file

@ -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>

View file

@ -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.

View file

@ -55,5 +55,22 @@
<build>
<finalName>authenticator-required-action-example</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<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>

View file

@ -17,17 +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.UserCredentialValueModel;
import org.keycloak.models.UserCredentialModel;
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;
@ -86,25 +89,29 @@ 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");
UserCredentialValueModel cred = null;
for (UserCredentialValueModel model : context.getUser().getCredentialsDirectly()) {
if (model.getType().equals(CREDENTIAL_TYPE)) {
cred = model;
break;
}
}
return cred.getValue().equals(secret);
UserCredentialModel input = new UserCredentialModel();
input.setType(SecretQuestionCredentialProvider.SECRET_QUESTION);
input.setValue(secret);
return context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(), input);
}
@Override
@ -114,7 +121,7 @@ public class SecretQuestionAuthenticator implements Authenticator {
@Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return session.users().configuredForCredentialType(CREDENTIAL_TYPE, realm, user);
return session.userCredentialManager().isConfiguredFor(realm, user, SecretQuestionCredentialProvider.SECRET_QUESTION);
}
@Override

View file

@ -0,0 +1,117 @@
/*
* 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.examples.authenticator;
import org.keycloak.common.util.Time;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialInputUpdater;
import org.keycloak.credential.CredentialInputValidator;
import org.keycloak.credential.CredentialModel;
import org.keycloak.credential.CredentialProvider;
import org.keycloak.credential.PasswordCredentialProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.cache.CachedUserModel;
import org.keycloak.models.cache.OnUserCache;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SecretQuestionCredentialProvider implements CredentialProvider, CredentialInputValidator, CredentialInputUpdater, OnUserCache {
public static final String SECRET_QUESTION = "SECRET_QUESTION";
public static final String CACHE_KEY = SecretQuestionCredentialProvider.class.getName() + "." + SECRET_QUESTION;
protected KeycloakSession session;
public SecretQuestionCredentialProvider(KeycloakSession session) {
this.session = session;
}
public CredentialModel getSecret(RealmModel realm, UserModel user) {
CredentialModel secret = null;
if (user instanceof CachedUserModel) {
CachedUserModel cached = (CachedUserModel)user;
secret = (CredentialModel)cached.getCachedWith().get(CACHE_KEY);
} else {
List<CredentialModel> creds = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
if (!creds.isEmpty()) secret = creds.get(0);
}
return secret;
}
@Override
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
if (!SECRET_QUESTION.equals(input.getType())) return false;
if (!(input instanceof UserCredentialModel)) return false;
UserCredentialModel credInput = (UserCredentialModel) input;
List<CredentialModel> creds = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
if (creds.isEmpty()) {
CredentialModel secret = new CredentialModel();
secret.setType(SECRET_QUESTION);
secret.setValue(credInput.getValue());
secret.setCreatedDate(Time.currentTimeMillis());
session.userCredentialManager().createCredential(realm ,user, secret);
} else {
creds.get(0).setValue(credInput.getValue());
session.userCredentialManager().updateCredential(realm, user, creds.get(0));
}
session.getUserCache().evict(realm, user);
return true;
}
@Override
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
if (!SECRET_QUESTION.equals(credentialType)) return;
session.userCredentialManager().disableCredential(realm, user, credentialType);
session.getUserCache().evict(realm, user);
}
@Override
public boolean supportsCredentialType(String credentialType) {
return SECRET_QUESTION.equals(credentialType);
}
@Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
if (!SECRET_QUESTION.equals(credentialType)) return false;
return getSecret(realm, user) != null;
}
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
if (!SECRET_QUESTION.equals(input.getType())) return false;
if (!(input instanceof UserCredentialModel)) return false;
String secret = getSecret(realm, user).getValue();
return secret != null && ((UserCredentialModel)input).getValue().equals(secret);
}
@Override
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));
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.examples.authenticator;
import org.keycloak.credential.CredentialProvider;
import org.keycloak.credential.CredentialProviderFactory;
import org.keycloak.models.KeycloakSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SecretQuestionCredentialProviderFactory implements CredentialProviderFactory<SecretQuestionCredentialProvider> {
@Override
public String getId() {
return "secret-question";
}
@Override
public CredentialProvider create(KeycloakSession session) {
return new SecretQuestionCredentialProvider(session);
}
}

View file

@ -19,6 +19,7 @@ package org.keycloak.examples.authenticator;
import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import javax.ws.rs.core.Response;
@ -45,10 +46,10 @@ public class SecretQuestionRequiredAction implements RequiredActionProvider {
@Override
public void processAction(RequiredActionContext context) {
String answer = (context.getHttpRequest().getDecodedFormParameters().getFirst("secret_answer"));
UserCredentialValueModel model = new UserCredentialValueModel();
model.setValue(answer);
model.setType(SecretQuestionAuthenticator.CREDENTIAL_TYPE);
context.getUser().updateCredentialDirectly(model);
UserCredentialModel input = new UserCredentialModel();
input.setType(SecretQuestionCredentialProvider.SECRET_QUESTION);
input.setValue(answer);
context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), input);
context.success();
}

View file

@ -0,0 +1 @@
org.keycloak.examples.authenticator.SecretQuestionCredentialProviderFactory

View file

@ -17,6 +17,7 @@
package org.keycloak.examples.federation.properties;
import org.keycloak.credential.CredentialInput;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
@ -146,48 +147,29 @@ public abstract class BasePropertiesFederationProvider implements UserFederation
return properties.containsKey(local.getUsername());
}
/**
* hardcoded to only return PASSWORD
*
* @param user
* @return
*/
@Override
public Set<String> getSupportedCredentialTypes(UserModel user) {
return supportedCredentialTypes;
}
@Override
public Set<String> getSupportedCredentialTypes() {
return supportedCredentialTypes;
}
@Override
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
for (UserCredentialModel cred : input) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
String password = properties.getProperty(user.getUsername());
if (password == null) return false;
return password.equals(cred.getValue());
} else {
return false; // invalid cred type
}
}
return false;
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) return false;
UserCredentialModel cred = (UserCredentialModel)input;
String password = properties.getProperty(user.getUsername());
if (password == null) return false;
return password.equals(cred.getValue());
}
@Override
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
for (UserCredentialModel cred : input) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
String password = properties.getProperty(user.getUsername());
if (password == null) return false;
return password.equals(cred.getValue());
} else {
return false; // invalid cred type
}
}
return true;
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
return getSupportedCredentialTypes().contains(credentialType);
}
@Override
public boolean supportsCredentialType(String credentialType) {
return getSupportedCredentialTypes().contains(credentialType);
}
@Override

View file

@ -17,8 +17,10 @@
package org.keycloak.examples.federation.properties;
import org.keycloak.credential.CredentialInput;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
@ -80,6 +82,13 @@ public class ClasspathPropertiesFederationProvider extends BasePropertiesFederat
throw new IllegalStateException("Remove not supported");
}
@Override
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
return false;
}
@Override
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
}
}

View file

@ -17,8 +17,10 @@
package org.keycloak.examples.federation.properties;
import org.keycloak.credential.CredentialInput;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
@ -98,6 +100,13 @@ public class FilePropertiesFederationProvider extends BasePropertiesFederationPr
}
}
@Override
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
return false;
}
@Override
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
}
}

View file

@ -39,19 +39,4 @@ public class ReadonlyUserModelProxy extends UserModelDelegate {
throw new IllegalStateException("Username is readonly");
}
@Override
public void updateCredentialDirectly(UserCredentialValueModel cred) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
throw new IllegalStateException("Passwords are readonly");
}
super.updateCredentialDirectly(cred);
}
@Override
public void updateCredential(UserCredentialModel cred) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
throw new IllegalStateException("Passwords are readonly");
}
super.updateCredential(cred);
}
}

View file

@ -64,26 +64,4 @@ public class WritableUserModelProxy extends UserModelDelegate {
}
@Override
public void updateCredentialDirectly(UserCredentialValueModel cred) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
throw new IllegalStateException("Shouldn't be using this method");
}
super.updateCredentialDirectly(cred);
}
@Override
public void updateCredential(UserCredentialModel cred) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
synchronized (provider.getProperties()) {
if (!provider.getProperties().containsKey(delegate.getUsername())) {
throw new IllegalStateException("no user of that in properties file");
}
provider.getProperties().setProperty(delegate.getUsername(), cred.getValue());
provider.save();
}
} else {
super.updateCredential(cred);
}
}
}

View file

@ -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")

View file

@ -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");
}
}

View file

@ -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();
@ -70,26 +78,10 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
return keycloakId;
}
@Override
public void updateCredential(UserCredentialModel cred) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
entity.setPassword(cred.getValue());
} else {
super.updateCredential(cred);
}
}
@Override
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);
}
@ -99,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);
}
@ -115,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);
}
@ -131,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);
}
@ -148,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;
}

View file

@ -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>

View file

@ -26,11 +26,14 @@ import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialModel;
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
@ -40,6 +43,7 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.common.constants.KerberosConstants;
import org.keycloak.services.managers.UserManager;
import org.keycloak.storage.adapter.AbstractUserAdapter;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -142,31 +146,6 @@ public class KerberosFederationProvider implements UserFederationProvider {
return kerberosPrincipal.equalsIgnoreCase(local.getFirstAttribute(KERBEROS_PRINCIPAL));
}
@Override
public Set<String> getSupportedCredentialTypes(UserModel local) {
Set<String> supportedCredTypes = new HashSet<String>();
supportedCredTypes.add(UserCredentialModel.KERBEROS);
if (kerberosConfig.isAllowPasswordAuthentication()) {
boolean passwordSupported = true;
if (kerberosConfig.getEditMode() == EditMode.UNSYNCED ) {
// Password from KC database has preference over kerberos password
for (UserCredentialValueModel cred : local.getCredentialsDirectly()) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
passwordSupported = false;
}
}
}
if (passwordSupported) {
supportedCredTypes.add(UserCredentialModel.PASSWORD);
}
}
return supportedCredTypes;
}
@Override
public Set<String> getSupportedCredentialTypes() {
Set<String> supportedCredTypes = new HashSet<String>();
@ -175,15 +154,37 @@ public class KerberosFederationProvider implements UserFederationProvider {
}
@Override
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
for (UserCredentialModel cred : input) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
return validPassword(user.getUsername(), cred.getValue());
} else {
return false; // invalid cred type
}
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
if (!(input instanceof UserCredentialModel) || !CredentialModel.PASSWORD.equals(input.getType())) return false;
if (kerberosConfig.getEditMode() == EditMode.READ_ONLY) {
throw new ModelReadOnlyException("Can't change password in Keycloak database. Change password with your Kerberos server");
}
return false;
}
@Override
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
}
@Override
public boolean supportsCredentialType(String credentialType) {
return credentialType.equals(CredentialModel.KERBEROS) || (kerberosConfig.isAllowPasswordAuthentication() && credentialType.equals(CredentialModel.PASSWORD));
}
@Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
return supportsCredentialType(credentialType);
}
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
if (!(input instanceof UserCredentialModel)) return false;
if (input.getType().equals(UserCredentialModel.PASSWORD) && !session.userCredentialManager().isConfiguredLocally(realm, user, UserCredentialModel.PASSWORD)) {
return validPassword(user.getUsername(), ((UserCredentialModel)input).getValue());
} else {
return false; // invalid cred type
}
return true;
}
protected boolean validPassword(String username, String password) {
@ -195,11 +196,6 @@ public class KerberosFederationProvider implements UserFederationProvider {
}
}
@Override
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
return validCredentials(realm, user, Arrays.asList(input));
}
@Override
public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential) {
if (credential.getType().equals(UserCredentialModel.KERBEROS)) {

View file

@ -34,12 +34,4 @@ public class ReadOnlyKerberosUserModelDelegate extends UserModelDelegate {
this.provider = provider;
}
@Override
public void updateCredential(UserCredentialModel cred) {
if (provider.getSupportedCredentialTypes(delegate).contains(cred.getType())) {
throw new ModelReadOnlyException("Can't change password in Keycloak database. Change password with your Kerberos server");
}
delegate.updateCredential(cred);
}
}

View file

@ -18,6 +18,9 @@
package org.keycloak.federation.ldap;
import org.jboss.logging.Logger;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialInputUpdater;
import org.keycloak.credential.CredentialModel;
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
@ -28,12 +31,14 @@ import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.LDAPMappersComparator;
import org.keycloak.federation.ldap.mappers.PasswordUpdated;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
@ -73,6 +78,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
protected LDAPIdentityStore ldapIdentityStore;
protected EditMode editMode;
protected LDAPProviderKerberosConfig kerberosConfig;
protected PasswordUpdated updater;
protected final Set<String> supportedCredentialTypes = new HashSet<>();
@ -90,6 +96,10 @@ public class LDAPFederationProvider implements UserFederationProvider {
}
}
public void setUpdater(PasswordUpdated updater) {
this.updater = updater;
}
public KeycloakSession getSession() {
return session;
}
@ -139,20 +149,6 @@ public class LDAPFederationProvider implements UserFederationProvider {
return proxied;
}
@Override
public Set<String> getSupportedCredentialTypes(UserModel local) {
Set<String> supportedCredentialTypes = new HashSet<String>(this.supportedCredentialTypes);
if (editMode == EditMode.UNSYNCED ) {
for (UserCredentialValueModel cred : local.getCredentialsDirectly()) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
// User has changed password in KC local database. Use KC password instead of LDAP password
supportedCredentialTypes.remove(UserCredentialModel.PASSWORD);
}
}
}
return supportedCredentialTypes;
}
@Override
public Set<String> getSupportedCredentialTypes() {
return new HashSet<String>(this.supportedCredentialTypes);
@ -409,20 +405,47 @@ public class LDAPFederationProvider implements UserFederationProvider {
@Override
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
for (UserCredentialModel cred : input) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
return validPassword(realm, user, cred.getValue());
} else {
return false; // invalid cred type
}
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
if (!CredentialModel.PASSWORD.equals(input.getType()) || ! (input instanceof UserCredentialModel)) return false;
if (editMode == EditMode.READ_ONLY) {
throw new ModelReadOnlyException("Federated storage is not writable");
} else if (editMode == EditMode.WRITABLE) {
LDAPIdentityStore ldapIdentityStore = getLdapIdentityStore();
UserCredentialModel cred = (UserCredentialModel)input;
String password = cred.getValue();
LDAPObject ldapUser = loadAndValidateUser(realm, user);
ldapIdentityStore.updatePassword(ldapUser, password);
if (updater != null) updater.passwordUpdated(user, ldapUser, input);
return true;
} else {
return false;
}
return true;
}
@Override
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
return validCredentials(realm, user, Arrays.asList(input));
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
}
@Override
public boolean supportsCredentialType(String credentialType) {
return getSupportedCredentialTypes().contains(credentialType);
}
@Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
return getSupportedCredentialTypes().contains(credentialType);
}
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
if (!(input instanceof UserCredentialModel)) return false;
if (input.getType().equals(UserCredentialModel.PASSWORD) && !session.userCredentialManager().isConfiguredLocally(realm, user, UserCredentialModel.PASSWORD)) {
return validPassword(realm, user, ((UserCredentialModel)input).getValue());
} else {
return false; // invalid cred type
}
}
@Override

View file

@ -50,14 +50,6 @@ public class ReadonlyLDAPUserModelDelegate extends UserModelDelegate implements
throw new ModelReadOnlyException("Federated storage is not writable");
}
@Override
public void updateCredential(UserCredentialModel cred) {
if (provider.getSupportedCredentialTypes(delegate).contains(cred.getType())) {
throw new ModelReadOnlyException("Federated storage is not writable");
}
delegate.updateCredential(cred);
}
@Override
public void setEmail(String email) {
throw new ModelReadOnlyException("Federated storage is not writable");

View file

@ -40,20 +40,4 @@ public class WritableLDAPUserModelDelegate extends UserModelDelegate implements
this.ldapObject = ldapObject;
}
@Override
public void updateCredential(UserCredentialModel cred) {
if (!provider.getSupportedCredentialTypes(delegate).contains(cred.getType())) {
delegate.updateCredential(cred);
return;
}
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
String password = cred.getValue();
ldapIdentityStore.updatePassword(ldapObject, password);
} else {
logger.warnf("Don't know how to update credential of type [%s] for user [%s]", cred.getType(), delegate.getUsername());
}
}
}

View file

@ -14,20 +14,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.storage.user;
package org.keycloak.federation.ldap.mappers;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.credential.CredentialInput;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
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);
public interface PasswordUpdated {
void passwordUpdated(UserModel user, LDAPObject ldapUser, CredentialInput input);
}

View file

@ -25,10 +25,12 @@ import java.util.regex.Pattern;
import javax.naming.AuthenticationException;
import org.jboss.logging.Logger;
import org.keycloak.credential.CredentialInput;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.PasswordUpdated;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
@ -44,7 +46,7 @@ import org.keycloak.models.utils.UserModelDelegate;
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper implements PasswordUpdated {
private static final Logger logger = Logger.getLogger(MSADUserAccountControlMapper.class);
@ -53,6 +55,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
public MSADUserAccountControlMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) {
super(mapperModel, ldapProvider, realm);
ldapProvider.setUpdater(this);
}
@Override
@ -68,6 +71,26 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
}
}
@Override
public void passwordUpdated(UserModel user, LDAPObject ldapUser, CredentialInput input) {
logger.debugf("Going to update userAccountControl for ldap user '%s' after successful password update", ldapUser.getDn().toString());
// Normally it's read-only
ldapUser.removeReadOnlyAttributeName(LDAPConstants.PWD_LAST_SET);
ldapUser.setSingleAttribute(LDAPConstants.PWD_LAST_SET, "-1");
UserAccountControl control = getUserAccountControl(ldapUser);
control.remove(UserAccountControl.PASSWD_NOTREQD);
control.remove(UserAccountControl.PASSWORD_EXPIRED);
if (user.isEnabled()) {
control.remove(UserAccountControl.ACCOUNTDISABLE);
}
updateUserAccountControl(ldapUser, control);
}
@Override
public UserModel proxy(LDAPObject ldapUser, UserModel delegate) {
return new MSADUserModelDelegate(delegate, ldapUser);
@ -135,6 +158,22 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
return e;
}
protected UserAccountControl getUserAccountControl(LDAPObject ldapUser) {
String userAccountControl = ldapUser.getAttributeAsString(LDAPConstants.USER_ACCOUNT_CONTROL);
long longValue = userAccountControl == null ? 0 : Long.parseLong(userAccountControl);
return new UserAccountControl(longValue);
}
// Update user in LDAP
protected void updateUserAccountControl(LDAPObject ldapUser, UserAccountControl accountControl) {
String userAccountControlValue = String.valueOf(accountControl.getValue());
logger.debugf("Updating userAccountControl of user '%s' to value '%s'", ldapUser.getDn().toString(), userAccountControlValue);
ldapUser.setSingleAttribute(LDAPConstants.USER_ACCOUNT_CONTROL, userAccountControlValue);
ldapProvider.getLdapIdentityStore().update(ldapUser);
}
public class MSADUserModelDelegate extends UserModelDelegate {
@ -151,7 +190,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
if (getPwdLastSet() > 0) {
// Merge KC and MSAD
return kcEnabled && !getUserAccountControl().has(UserAccountControl.ACCOUNTDISABLE);
return kcEnabled && !getUserAccountControl(ldapUser).has(UserAccountControl.ACCOUNTDISABLE);
} else {
// If new MSAD user is created and pwdLastSet is still 0, MSAD account is in disabled state. So read just from Keycloak DB. User is not able to login via MSAD anyway
return kcEnabled;
@ -166,44 +205,14 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && getPwdLastSet() > 0) {
logger.debugf("Going to propagate enabled=%s for ldapUser '%s' to MSAD", enabled, ldapUser.getDn().toString());
UserAccountControl control = getUserAccountControl();
UserAccountControl control = getUserAccountControl(ldapUser);
if (enabled) {
control.remove(UserAccountControl.ACCOUNTDISABLE);
} else {
control.add(UserAccountControl.ACCOUNTDISABLE);
}
updateUserAccountControl(control);
}
}
@Override
public void updateCredential(UserCredentialModel cred) {
// Update LDAP password first
try {
super.updateCredential(cred);
} catch (ModelException me) {
me = processFailedPasswordUpdateException(me);
throw me;
}
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && cred.getType().equals(UserCredentialModel.PASSWORD)) {
logger.debugf("Going to update userAccountControl for ldap user '%s' after successful password update", ldapUser.getDn().toString());
// Normally it's read-only
ldapUser.removeReadOnlyAttributeName(LDAPConstants.PWD_LAST_SET);
ldapUser.setSingleAttribute(LDAPConstants.PWD_LAST_SET, "-1");
UserAccountControl control = getUserAccountControl();
control.remove(UserAccountControl.PASSWD_NOTREQD);
control.remove(UserAccountControl.PASSWORD_EXPIRED);
if (super.isEnabled()) {
control.remove(UserAccountControl.ACCOUNTDISABLE);
}
updateUserAccountControl(control);
updateUserAccountControl(ldapUser, control);
}
}
@ -243,7 +252,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && RequiredAction.UPDATE_PASSWORD.toString().equals(action)) {
// Don't set pwdLastSet in MSAD when it is new user
UserAccountControl accountControl = getUserAccountControl();
UserAccountControl accountControl = getUserAccountControl(ldapUser);
if (accountControl.getValue() != 0 && !accountControl.has(UserAccountControl.PASSWD_NOTREQD)) {
logger.debugf("Going to remove required action UPDATE_PASSWORD from MSAD for ldap user '%s' ", ldapUser.getDn().toString());
@ -261,7 +270,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
Set<String> requiredActions = super.getRequiredActions();
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE) {
if (getPwdLastSet() == 0 || getUserAccountControl().has(UserAccountControl.PASSWORD_EXPIRED)) {
if (getPwdLastSet() == 0 || getUserAccountControl(ldapUser).has(UserAccountControl.PASSWORD_EXPIRED)) {
requiredActions = new HashSet<>(requiredActions);
requiredActions.add(RequiredAction.UPDATE_PASSWORD.toString());
return requiredActions;
@ -276,20 +285,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
return pwdLastSet == null ? 0 : Long.parseLong(pwdLastSet);
}
protected UserAccountControl getUserAccountControl() {
String userAccountControl = ldapUser.getAttributeAsString(LDAPConstants.USER_ACCOUNT_CONTROL);
long longValue = userAccountControl == null ? 0 : Long.parseLong(userAccountControl);
return new UserAccountControl(longValue);
}
// Update user in LDAP
protected void updateUserAccountControl(UserAccountControl accountControl) {
String userAccountControlValue = String.valueOf(accountControl.getValue());
logger.debugf("Updating userAccountControl of user '%s' to value '%s'", ldapUser.getDn().toString(), userAccountControlValue);
ldapUser.setSingleAttribute(LDAPConstants.USER_ACCOUNT_CONTROL, userAccountControlValue);
ldapProvider.getLdapIdentityStore().update(ldapUser);
}
}
}

View file

@ -54,22 +54,6 @@ public class ReadonlySSSDUserModelDelegate extends UserModelDelegate implements
throw new ModelReadOnlyException("Federated storage is not writable");
}
@Override
public void updateCredentialDirectly(UserCredentialValueModel cred) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
throw new IllegalStateException("Federated storage is not writable");
}
super.updateCredentialDirectly(cred);
}
@Override
public void updateCredential(UserCredentialModel cred) {
if (provider.getSupportedCredentialTypes(delegate).contains(cred.getType())) {
throw new ModelReadOnlyException("Federated storage is not writable");
}
delegate.updateCredential(cred);
}
@Override
public void setEmail(String email) {
throw new ModelReadOnlyException("Federated storage is not writable");

View file

@ -19,11 +19,14 @@ package org.keycloak.federation.sssd;
import org.freedesktop.dbus.Variant;
import org.jboss.logging.Logger;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialModel;
import org.keycloak.federation.sssd.api.Sssd;
import org.keycloak.federation.sssd.impl.PAMAuthenticator;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
@ -162,30 +165,39 @@ public class SSSDFederationProvider implements UserFederationProvider {
return Sssd.getRawAttribute(attributes.get("mail")).equalsIgnoreCase(local.getEmail());
}
@Override
public Set<String> getSupportedCredentialTypes(UserModel user) {
return supportedCredentialTypes;
}
@Override
public Set<String> getSupportedCredentialTypes() {
return supportedCredentialTypes;
}
@Override
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
for (UserCredentialModel cred : input) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
PAMAuthenticator pam = factory.createPAMAuthenticator(user.getUsername(), cred.getValue());
return (pam.authenticate() != null);
}
}
return false;
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
if (!(input instanceof UserCredentialModel) || !CredentialModel.PASSWORD.equals(input.getType())) return false;
throw new ModelReadOnlyException("Federated storage is not writable");
}
@Override
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
return validCredentials(realm, user, Arrays.asList(input));
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
}
@Override
public boolean supportsCredentialType(String credentialType) {
return CredentialModel.PASSWORD.equals(credentialType);
}
@Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
return CredentialModel.PASSWORD.equals(credentialType);
}
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) return false;
UserCredentialModel cred = (UserCredentialModel)input;
PAMAuthenticator pam = factory.createPAMAuthenticator(user.getUsername(), cred.getValue());
return (pam.authenticate() != null);
}
@Override

View file

@ -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
@ -119,12 +121,6 @@ public class UserAdapter implements CachedUserModel {
return cached.isEnabled();
}
@Override
public boolean isOtpEnabled() {
if (updated != null) return updated.isOtpEnabled();
return cached.isTotp();
}
@Override
public void setEnabled(boolean enabled) {
getDelegateForUpdate();
@ -247,30 +243,6 @@ public class UserAdapter implements CachedUserModel {
updated.setEmailVerified(verified);
}
@Override
public void setOtpEnabled(boolean totp) {
getDelegateForUpdate();
updated.setOtpEnabled(totp);
}
@Override
public void updateCredential(UserCredentialModel cred) {
getDelegateForUpdate();
updated.updateCredential(cred);
}
@Override
public List<UserCredentialValueModel> getCredentialsDirectly() {
if (updated != null) return updated.getCredentialsDirectly();
return cached.getCredentials();
}
@Override
public void updateCredentialDirectly(UserCredentialValueModel cred) {
getDelegateForUpdate();
updated.updateCredentialDirectly(cred);
}
@Override
public String getFederationLink() {
if (updated != null) return updated.getFederationLink();

View file

@ -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
@ -627,16 +628,6 @@ public class UserCacheSession implements UserCache {
return getDelegate().removeFederatedIdentity(realm, user, socialProvider);
}
@Override
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
return getDelegate().validCredentials(session, realm, user, input);
}
@Override
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) {
return getDelegate().validCredentials(session, realm, user, input);
}
@Override
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
return getDelegate().validCredentials(session, realm, input);

View file

@ -44,9 +44,7 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
private String lastName;
private String email;
private boolean emailVerified;
private List<UserCredentialValueModel> credentials = new LinkedList<>();
private boolean enabled;
private boolean totp;
private String federationLink;
private String serviceAccountClientLink;
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
@ -66,9 +64,7 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
this.attributes.putAll(user.getAttributes());
this.email = user.getEmail();
this.emailVerified = user.isEmailVerified();
this.credentials.addAll(user.getCredentialsDirectly());
this.enabled = user.isEnabled();
this.totp = user.isOtpEnabled();
this.federationLink = user.getFederationLink();
this.serviceAccountClientLink = user.getServiceAccountClientLink();
this.requiredActions.addAll(user.getRequiredActions());
@ -111,18 +107,10 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
return emailVerified;
}
public List<UserCredentialValueModel> getCredentials() {
return credentials;
}
public boolean isEnabled() {
return enabled;
}
public boolean isTotp() {
return totp;
}
public MultivaluedHashMap<String, String> getAttributes() {
return attributes;
}

View file

@ -710,16 +710,6 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
return (entity != null) ? new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName(), entity.getToken()) : null;
}
@Override
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
return CredentialValidation.validCredentials(session, realm, user, input);
}
@Override
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) {
return CredentialValidation.validCredentials(session, realm, user, input);
}
@Override
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
// Not supported yet
@ -803,7 +793,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
entity.setUser(userRef);
em.persist(entity);
MultivaluedHashMap<String, String> config = cred.getConfig();
if (config != null || !config.isEmpty()) {
if (config != null && !config.isEmpty()) {
for (String key : config.keySet()) {
List<String> values = config.getList(key);
@ -850,6 +840,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
model.setCreatedDate(entity.getCreatedDate());
model.setDevice(entity.getDevice());
model.setDigits(entity.getDigits());
model.setHashIterations(entity.getHashIterations());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
model.setConfig(config);
for (CredentialAttributeEntity attr : entity.getCredentialAttributes()) {

View file

@ -113,11 +113,6 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
return user.isEnabled();
}
@Override
public boolean isOtpEnabled() {
return user.isTotp();
}
@Override
public void setEnabled(boolean enabled) {
user.setEnabled(enabled);
@ -310,204 +305,6 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
user.setEmailVerified(verified);
}
@Override
public void setOtpEnabled(boolean totp) {
user.setTotp(totp);
}
@Override
public void updateCredential(UserCredentialModel cred) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
updatePasswordCredential(cred);
} else if (UserCredentialModel.isOtp(cred.getType())){
updateOtpCredential(cred);
} else {
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
if (credentialEntity == null) {
credentialEntity = setCredentials(user, cred);
credentialEntity.setValue(cred.getValue());
em.persist(credentialEntity);
user.getCredentials().add(credentialEntity);
} else {
credentialEntity.setValue(cred.getValue());
}
}
em.flush();
}
private void updateOtpCredential(UserCredentialModel cred) {
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
if (credentialEntity == null) {
credentialEntity = setCredentials(user, cred);
credentialEntity.setValue(cred.getValue());
OTPPolicy otpPolicy = realm.getOTPPolicy();
credentialEntity.setAlgorithm(otpPolicy.getAlgorithm());
credentialEntity.setDigits(otpPolicy.getDigits());
credentialEntity.setCounter(otpPolicy.getInitialCounter());
credentialEntity.setPeriod(otpPolicy.getPeriod());
em.persist(credentialEntity);
user.getCredentials().add(credentialEntity);
} else {
OTPPolicy policy = realm.getOTPPolicy();
credentialEntity.setDigits(policy.getDigits());
credentialEntity.setCounter(policy.getInitialCounter());
credentialEntity.setAlgorithm(policy.getAlgorithm());
credentialEntity.setValue(cred.getValue());
credentialEntity.setPeriod(policy.getPeriod());
}
}
private void updatePasswordCredential(UserCredentialModel cred) {
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
if (credentialEntity == null) {
credentialEntity = setCredentials(user, cred);
setValue(credentialEntity, cred);
em.persist(credentialEntity);
user.getCredentials().add(credentialEntity);
} else {
int expiredPasswordsPolicyValue = -1;
PasswordPolicy policy = realm.getPasswordPolicy();
if(policy != null) {
expiredPasswordsPolicyValue = policy.getExpiredPasswords();
}
if (expiredPasswordsPolicyValue != -1) {
user.getCredentials().remove(credentialEntity);
credentialEntity.setType(UserCredentialModel.PASSWORD_HISTORY);
user.getCredentials().add(credentialEntity);
List<CredentialEntity> credentialEntities = getCredentialEntities(user, UserCredentialModel.PASSWORD_HISTORY);
if (credentialEntities.size() > expiredPasswordsPolicyValue - 1) {
user.getCredentials().removeAll(credentialEntities.subList(expiredPasswordsPolicyValue - 1, credentialEntities.size()));
}
credentialEntity = setCredentials(user, cred);
setValue(credentialEntity, cred);
em.persist(credentialEntity);
user.getCredentials().add(credentialEntity);
} else {
List<CredentialEntity> credentialEntities = getCredentialEntities(user, UserCredentialModel.PASSWORD_HISTORY);
if (credentialEntities != null && credentialEntities.size() > 0) {
user.getCredentials().removeAll(credentialEntities);
}
setValue(credentialEntity, cred);
}
}
}
private CredentialEntity setCredentials(UserEntity user, UserCredentialModel cred) {
CredentialEntity credentialEntity = new CredentialEntity();
credentialEntity.setId(KeycloakModelUtils.generateId());
credentialEntity.setType(cred.getType());
credentialEntity.setDevice(cred.getDevice());
credentialEntity.setUser(user);
return credentialEntity;
}
private void setValue(CredentialEntity credentialEntity, UserCredentialModel cred) {
UserCredentialValueModel encoded = PasswordHashManager.encode(session, realm, cred.getValue());
credentialEntity.setCreatedDate(Time.toMillis(Time.currentTime()));
credentialEntity.setAlgorithm(encoded.getAlgorithm());
credentialEntity.setValue(encoded.getValue());
credentialEntity.setSalt(encoded.getSalt());
credentialEntity.setHashIterations(encoded.getHashIterations());
}
private CredentialEntity getCredentialEntity(UserEntity userEntity, String credType) {
for (CredentialEntity entity : userEntity.getCredentials()) {
if (entity.getType().equals(credType)) {
return entity;
}
}
return null;
}
private List<CredentialEntity> getCredentialEntities(UserEntity userEntity, String credType) {
List<CredentialEntity> credentialEntities = new ArrayList<CredentialEntity>();
for (CredentialEntity entity : userEntity.getCredentials()) {
if (entity.getType().equals(credType)) {
credentialEntities.add(entity);
}
}
// Avoiding direct use of credSecond.getCreatedDate() - credFirst.getCreatedDate() to prevent Integer Overflow
// Orders from most recent to least recent
Collections.sort(credentialEntities, new Comparator<CredentialEntity>() {
public int compare(CredentialEntity credFirst, CredentialEntity credSecond) {
if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) {
return -1;
} else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) {
return 1;
} else {
return 0;
}
}
});
return credentialEntities;
}
@Override
public List<UserCredentialValueModel> getCredentialsDirectly() {
List<CredentialEntity> credentials = new ArrayList<>(user.getCredentials());
List<UserCredentialValueModel> result = new ArrayList<>();
for (CredentialEntity credEntity : credentials) {
UserCredentialValueModel credModel = new UserCredentialValueModel();
credModel.setType(credEntity.getType());
credModel.setDevice(credEntity.getDevice());
credModel.setValue(credEntity.getValue());
credModel.setCreatedDate(credEntity.getCreatedDate());
credModel.setSalt(credEntity.getSalt());
credModel.setHashIterations(credEntity.getHashIterations());
credModel.setCounter(credEntity.getCounter());
credModel.setAlgorithm(credEntity.getAlgorithm());
credModel.setDigits(credEntity.getDigits());
credModel.setPeriod(credEntity.getPeriod());
result.add(credModel);
}
return result;
}
@Override
public void updateCredentialDirectly(UserCredentialValueModel credModel) {
CredentialEntity credentialEntity = getCredentialEntity(user, credModel.getType());
if (credentialEntity == null) {
credentialEntity = new CredentialEntity();
credentialEntity.setId(KeycloakModelUtils.generateId());
credentialEntity.setType(credModel.getType());
credentialEntity.setCreatedDate(credModel.getCreatedDate());
credentialEntity.setUser(user);
em.persist(credentialEntity);
user.getCredentials().add(credentialEntity);
}
credentialEntity.setValue(credModel.getValue());
credentialEntity.setSalt(credModel.getSalt());
credentialEntity.setDevice(credModel.getDevice());
credentialEntity.setHashIterations(credModel.getHashIterations());
credentialEntity.setCounter(credModel.getCounter());
credentialEntity.setAlgorithm(credModel.getAlgorithm());
credentialEntity.setDigits(credModel.getDigits());
credentialEntity.setPeriod(credModel.getPeriod());
em.flush();
}
@Override
public Set<GroupModel> getGroups() {
// we query ids only as the group might be cached and following the @ManyToOne will result in a load

View file

@ -76,8 +76,6 @@ public class UserEntity {
protected String email;
@Column(name = "ENABLED")
protected boolean enabled;
@Column(name = "TOTP")
protected boolean totp;
@Column(name = "EMAIL_VERIFIED")
protected boolean emailVerified;
@ -168,14 +166,6 @@ public class UserEntity {
this.emailConstraint = emailConstraint;
}
public boolean isTotp() {
return totp;
}
public void setTotp(boolean totp) {
this.totp = totp;
}
public boolean isEmailVerified() {
return emailVerified;
}

View file

@ -47,6 +47,7 @@ import org.keycloak.storage.federated.UserGroupMembershipFederatedStorage;
import org.keycloak.storage.federated.UserRequiredActionsFederatedStorage;
import org.keycloak.storage.federated.UserRoleMappingsFederatedStorage;
import org.keycloak.storage.jpa.entity.BrokerLinkEntity;
import org.keycloak.storage.jpa.entity.FederatedUser;
import org.keycloak.storage.jpa.entity.FederatedUserAttributeEntity;
import org.keycloak.storage.jpa.entity.FederatedUserConsentEntity;
import org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity;
@ -95,9 +96,24 @@ public class JpaUserFederatedStorageProvider implements
}
/**
* We create an entry so that its easy to iterate over all things in the database. Specifically useful for export
*
*/
protected void createIndex(RealmModel realm, UserModel user) {
if (em.find(FederatedUser.class, user.getId()) == null) {
FederatedUser fedUser = new FederatedUser();
fedUser.setId(user.getId());
fedUser.setRealmId(realm.getId());
fedUser.setStorageProviderId(StorageId.resolveProviderId(user));
em.persist(fedUser);
}
}
@Override
public void setAttribute(RealmModel realm, UserModel user, String name, List<String> values) {
createIndex(realm, user);
deleteAttribute(realm, user, name);
em.flush();
for (String value : values) {
@ -126,6 +142,7 @@ public class JpaUserFederatedStorageProvider implements
@Override
public void setSingleAttribute(RealmModel realm, UserModel user, String name, String value) {
createIndex(realm, user);
deleteAttribute(realm, user, name);
em.flush();
persistAttributeValue(realm, user, name, value);
@ -133,6 +150,7 @@ public class JpaUserFederatedStorageProvider implements
@Override
public void removeAttribute(RealmModel realm, UserModel user, String name) {
// createIndex(realm, user); don't need to create an index for removal
deleteAttribute(realm, user, name);
em.flush();
}
@ -180,6 +198,7 @@ public class JpaUserFederatedStorageProvider implements
@Override
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel link) {
createIndex(realm, user);
BrokerLinkEntity entity = new BrokerLinkEntity();
entity.setRealmId(realm.getId());
entity.setUserId(user.getId());
@ -211,6 +230,7 @@ public class JpaUserFederatedStorageProvider implements
@Override
public void updateFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel model) {
createIndex(realm, user);
BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, model.getIdentityProvider());
if (entity == null) return;
entity.setBrokerUserName(model.getUserName());
@ -243,6 +263,7 @@ public class JpaUserFederatedStorageProvider implements
@Override
public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
createIndex(realm, user);
String clientId = consent.getClient().getId();
FederatedUserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
@ -285,6 +306,7 @@ public class JpaUserFederatedStorageProvider implements
@Override
public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
createIndex(realm, user);
String clientId = consent.getClient().getId();
FederatedUserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
@ -432,12 +454,14 @@ public class JpaUserFederatedStorageProvider implements
@Override
public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel cred) {
createIndex(realm, user);
FederatedCredentials.updateCredential(session, this, realm, user, cred);
}
@Override
public void updateCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred) {
createIndex(realm, user);
FederatedUserCredentialEntity entity = null;
if (cred.getId() != null) entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
boolean newEntity = false;
@ -490,6 +514,7 @@ public class JpaUserFederatedStorageProvider implements
@Override
public void joinGroup(RealmModel realm, UserModel user, GroupModel group) {
if (isMemberOf(realm, user, group)) return;
createIndex(realm, user);
FederatedUserGroupMembershipEntity entity = new FederatedUserGroupMembershipEntity();
entity.setUserId(user.getId());
entity.setStorageProviderId(StorageId.resolveProviderId(user));
@ -553,6 +578,7 @@ public class JpaUserFederatedStorageProvider implements
@Override
public void addRequiredAction(RealmModel realm, UserModel user, String action) {
createIndex(realm, user);
if (user.getRequiredActions().contains(action)) return;
FederatedUserRequiredActionEntity entity = new FederatedUserRequiredActionEntity();
entity.setUserId(user.getId());
@ -576,6 +602,7 @@ public class JpaUserFederatedStorageProvider implements
@Override
public void grantRole(RealmModel realm, UserModel user, RoleModel role) {
if (user.hasRole(role)) return;
createIndex(realm, user);
FederatedUserRoleMappingEntity entity = new FederatedUserRoleMappingEntity();
entity.setUserId(user.getId());
entity.setStorageProviderId(StorageId.resolveProviderId(user));
@ -645,6 +672,7 @@ public class JpaUserFederatedStorageProvider implements
public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
if (entity == null) return;
createIndex(realm, user);
entity.setAlgorithm(cred.getAlgorithm());
entity.setCounter(cred.getCounter());
entity.setCreatedDate(cred.getCreatedDate());
@ -696,6 +724,7 @@ public class JpaUserFederatedStorageProvider implements
@Override
public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
createIndex(realm, user);
FederatedUserCredentialEntity entity = new FederatedUserCredentialEntity();
String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
entity.setId(id);
@ -714,7 +743,7 @@ public class JpaUserFederatedStorageProvider implements
entity.setStorageProviderId(StorageId.resolveProviderId(user));
em.persist(entity);
MultivaluedHashMap<String, String> config = cred.getConfig();
if (config != null || !config.isEmpty()) {
if (config != null && !config.isEmpty()) {
for (String key : config.keySet()) {
List<String> values = config.getList(key);
@ -761,6 +790,7 @@ public class JpaUserFederatedStorageProvider implements
model.setCreatedDate(entity.getCreatedDate());
model.setDevice(entity.getDevice());
model.setDigits(entity.getDigits());
model.setHashIterations(entity.getHashIterations());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
model.setConfig(config);
for (FederatedUserCredentialAttributeEntity attr : entity.getCredentialAttributes()) {
@ -805,6 +835,15 @@ public class JpaUserFederatedStorageProvider implements
return toModel(results.get(0));
}
@Override
public List<String> getStoredUsers(RealmModel realm, int first, int max) {
TypedQuery<String> query = em.createNamedQuery("getFederatedUserIds", String.class)
.setParameter("realmId", realm.getId())
.setFirstResult(first)
.setMaxResults(max);
return query.getResultList();
}
@Override
public void preRemove(RealmModel realm) {
int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm")
@ -827,6 +866,8 @@ public class JpaUserFederatedStorageProvider implements
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteFederatedUserGroupMembershipByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteFederatedUsersByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
}
@Override
@ -855,6 +896,10 @@ public class JpaUserFederatedStorageProvider implements
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
.executeUpdate();
num = em.createNamedQuery("deleteFederatedUsersByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
.executeUpdate();
}
@Override
@ -924,6 +969,10 @@ public class JpaUserFederatedStorageProvider implements
.setParameter("userId", user.getId())
.setParameter("realmId", realm.getId())
.executeUpdate();
em.createNamedQuery("deleteFederatedUserByUser")
.setParameter("userId", user.getId())
.setParameter("realmId", realm.getId())
.executeUpdate();
}
@ -961,6 +1010,9 @@ public class JpaUserFederatedStorageProvider implements
em.createNamedQuery("deleteFederatedUserRoleMappingsByStorageProvider")
.setParameter("storageProviderId", model.getId())
.executeUpdate();
em.createNamedQuery("deleteFederatedUsersByStorageProvider")
.setParameter("storageProviderId", model.getId())
.executeUpdate();
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.storage.jpa.entity;
import org.keycloak.models.jpa.entities.UserAttributeEntity;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@NamedQueries({
@NamedQuery(name="getFederatedUserIds", query="select f.id from FederatedUser f where f.realmId=:realmId"),
@NamedQuery(name="deleteFederatedUserByUser", query="delete from FederatedUser f where f.id = :userId and f.realmId=:realmId"),
@NamedQuery(name="deleteFederatedUsersByRealm", query="delete from FederatedUser f where f.realmId=:realmId"),
@NamedQuery(name="deleteFederatedUsersByStorageProvider", query="delete from FederatedUser f where f.storageProviderId=:storageProviderId"),
@NamedQuery(name="deleteFederatedUsersByRealmAndLink", query="delete from FederatedUser f where f.id IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
})
@Entity
@Table(name="FEDERATED_USER")
public class FederatedUser {
@Id
@Column(name="ID")
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name = "REALM_ID")
protected String realmId;
@Column(name = "STORAGE_PROVIDER_ID")
protected String storageProviderId;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.realmId = realmId;
}
public String getStorageProviderId() {
return storageProviderId;
}
public void setStorageProviderId(String storageProviderId) {
this.storageProviderId = storageProviderId;
}
}

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ 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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="bburke@redhat.com" id="2.3.0">
<createTable tableName="FEDERATED_USER">
<column name="ID" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="STORAGE_PROVIDER_ID" type="VARCHAR(255)">
</column>
<column name="REALM_ID" type="VARCHAR(36)">
<constraints nullable="false" />
</column>
</createTable>
<addPrimaryKey columnNames="ID" constraintName="CONSTR_FEDERATED_USER" tableName="FEDERATED_USER"/>
<dropColumn tableName="USER_ENTITY" columnName="TOTP" />
</changeSet>
</databaseChangeLog>

View file

@ -36,4 +36,5 @@
<include file="META-INF/jpa-changelog-authz-master.xml"/>
<include file="META-INF/jpa-changelog-2.1.0.xml"/>
<include file="META-INF/jpa-changelog-2.2.0.xml"/>
<include file="META-INF/jpa-changelog-2.3.0.xml"/>
</databaseChangeLog>

View file

@ -70,6 +70,7 @@
<!-- User Federation Storage -->
<class>org.keycloak.storage.jpa.entity.BrokerLinkEntity</class>
<class>org.keycloak.storage.jpa.entity.FederatedUser</class>
<class>org.keycloak.storage.jpa.entity.FederatedUserAttributeEntity</class>
<class>org.keycloak.storage.jpa.entity.FederatedUserConsentEntity</class>
<class>org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity</class>

View file

@ -513,16 +513,6 @@ public class MongoUserProvider implements UserProvider {
getMongoStore().updateEntities(MongoUserConsentEntity.class, query, pull, invocationContext);
}
@Override
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
return CredentialValidation.validCredentials(session, realm, user, input);
}
@Override
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) {
return CredentialValidation.validCredentials(session, realm, user, input);
}
@Override
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
// Not supported yet

View file

@ -243,216 +243,6 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
getMongoStore().pullItemFromList(user, "requiredActions", actionName, invocationContext);
}
@Override
public boolean isOtpEnabled() {
return user.isTotp();
}
@Override
public void setOtpEnabled(boolean totp) {
user.setTotp(totp);
updateUser();
}
@Override
public void updateCredential(UserCredentialModel cred) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
updatePasswordCredential(cred);
} else if (UserCredentialModel.isOtp(cred.getType())){
updateOtpCredential(cred);
} else {
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
if (credentialEntity == null) {
credentialEntity = setCredentials(user, cred);
credentialEntity.setValue(cred.getValue());
user.getCredentials().add(credentialEntity);
} else {
credentialEntity.setValue(cred.getValue());
}
}
getMongoStore().updateEntity(user, invocationContext);
}
private void updateOtpCredential(UserCredentialModel cred) {
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
if (credentialEntity == null) {
credentialEntity = setCredentials(user, cred);
credentialEntity.setValue(cred.getValue());
OTPPolicy otpPolicy = realm.getOTPPolicy();
credentialEntity.setAlgorithm(otpPolicy.getAlgorithm());
credentialEntity.setDigits(otpPolicy.getDigits());
credentialEntity.setCounter(otpPolicy.getInitialCounter());
credentialEntity.setPeriod(otpPolicy.getPeriod());
user.getCredentials().add(credentialEntity);
} else {
credentialEntity.setValue(cred.getValue());
OTPPolicy policy = realm.getOTPPolicy();
credentialEntity.setDigits(policy.getDigits());
credentialEntity.setCounter(policy.getInitialCounter());
credentialEntity.setAlgorithm(policy.getAlgorithm());
credentialEntity.setPeriod(policy.getPeriod());
}
}
private void updatePasswordCredential(UserCredentialModel cred) {
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
if (credentialEntity == null) {
credentialEntity = setCredentials(user, cred);
setValue(credentialEntity, cred);
user.getCredentials().add(credentialEntity);
} else {
int expiredPasswordsPolicyValue = -1;
PasswordPolicy policy = realm.getPasswordPolicy();
if(policy != null) {
expiredPasswordsPolicyValue = policy.getExpiredPasswords();
}
if (expiredPasswordsPolicyValue != -1) {
user.getCredentials().remove(credentialEntity);
credentialEntity.setType(UserCredentialModel.PASSWORD_HISTORY);
user.getCredentials().add(credentialEntity);
List<CredentialEntity> credentialEntities = getCredentialEntities(user, UserCredentialModel.PASSWORD_HISTORY);
if (credentialEntities.size() > expiredPasswordsPolicyValue - 1) {
user.getCredentials().removeAll(credentialEntities.subList(expiredPasswordsPolicyValue - 1, credentialEntities.size()));
}
credentialEntity = setCredentials(user, cred);
setValue(credentialEntity, cred);
user.getCredentials().add(credentialEntity);
} else {
List<CredentialEntity> credentialEntities = getCredentialEntities(user, UserCredentialModel.PASSWORD_HISTORY);
if (credentialEntities != null && credentialEntities.size() > 0) {
user.getCredentials().removeAll(credentialEntities);
}
setValue(credentialEntity, cred);
}
}
}
private CredentialEntity setCredentials(MongoUserEntity user, UserCredentialModel cred) {
CredentialEntity credentialEntity = new CredentialEntity();
credentialEntity.setType(cred.getType());
credentialEntity.setDevice(cred.getDevice());
return credentialEntity;
}
private void setValue(CredentialEntity credentialEntity, UserCredentialModel cred) {
UserCredentialValueModel encoded = PasswordHashManager.encode(session, realm, cred.getValue());
credentialEntity.setCreatedDate(Time.toMillis(Time.currentTime()));
credentialEntity.setAlgorithm(encoded.getAlgorithm());
credentialEntity.setValue(encoded.getValue());
credentialEntity.setSalt(encoded.getSalt());
credentialEntity.setHashIterations(encoded.getHashIterations());
}
private CredentialEntity getCredentialEntity(MongoUserEntity userEntity, String credType) {
for (CredentialEntity entity : userEntity.getCredentials()) {
if (entity.getType().equals(credType)) {
return entity;
}
}
return null;
}
private List<CredentialEntity> getCredentialEntities(MongoUserEntity userEntity, String credType) {
List<CredentialEntity> credentialEntities = new ArrayList<CredentialEntity>();
for (CredentialEntity entity : userEntity.getCredentials()) {
if (entity.getType().equals(credType)) {
credentialEntities.add(entity);
}
}
// Avoiding direct use of credSecond.getCreatedDate() - credFirst.getCreatedDate() to prevent Integer Overflow
// Orders from most recent to least recent
Collections.sort(credentialEntities, new Comparator<CredentialEntity>() {
public int compare(CredentialEntity credFirst, CredentialEntity credSecond) {
if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) {
return -1;
} else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) {
return 1;
} else {
return 0;
}
}
});
return credentialEntities;
}
@Override
public List<UserCredentialValueModel> getCredentialsDirectly() {
List<CredentialEntity> credentials = user.getCredentials();
List<UserCredentialValueModel> result = new ArrayList<UserCredentialValueModel>();
for (CredentialEntity credEntity : credentials) {
UserCredentialValueModel credModel = new UserCredentialValueModel();
credModel.setType(credEntity.getType());
credModel.setDevice(credEntity.getDevice());
credModel.setCreatedDate(credEntity.getCreatedDate());
credModel.setValue(credEntity.getValue());
credModel.setSalt(credEntity.getSalt());
credModel.setHashIterations(credEntity.getHashIterations());
credModel.setAlgorithm(credEntity.getAlgorithm());
if (UserCredentialModel.isOtp(credEntity.getType())) {
credModel.setCounter(credEntity.getCounter());
if (credEntity.getAlgorithm() == null) {
// for migration where these values would be null
credModel.setAlgorithm(realm.getOTPPolicy().getAlgorithm());
} else {
credModel.setAlgorithm(credEntity.getAlgorithm());
}
if (credEntity.getDigits() == 0) {
// for migration where these values would be 0
credModel.setDigits(realm.getOTPPolicy().getDigits());
} else {
credModel.setDigits(credEntity.getDigits());
}
if (credEntity.getPeriod() == 0) {
// for migration where these values would be 0
credModel.setPeriod(realm.getOTPPolicy().getPeriod());
} else {
credModel.setPeriod(credEntity.getPeriod());
}
}
result.add(credModel);
}
return result;
}
@Override
public void updateCredentialDirectly(UserCredentialValueModel credModel) {
CredentialEntity credentialEntity = getCredentialEntity(user, credModel.getType());
if (credentialEntity == null) {
credentialEntity = new CredentialEntity();
credentialEntity.setType(credModel.getType());
credModel.setCreatedDate(credModel.getCreatedDate());
user.getCredentials().add(credentialEntity);
}
credentialEntity.setValue(credModel.getValue());
credentialEntity.setSalt(credModel.getSalt());
credentialEntity.setDevice(credModel.getDevice());
credentialEntity.setHashIterations(credModel.getHashIterations());
credentialEntity.setCounter(credModel.getCounter());
credentialEntity.setAlgorithm(credModel.getAlgorithm());
credentialEntity.setDigits(credModel.getDigits());
credentialEntity.setPeriod(credModel.getPeriod());
getMongoStore().updateEntity(user, invocationContext);
}
protected void updateUser() {
super.updateMongoEntity();
}

View file

@ -24,6 +24,7 @@ import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.storage.UserStorageProvider;
import java.util.Collections;
@ -34,16 +35,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface CredentialProviderFactory<T extends CredentialProvider> extends ComponentFactory<T, CredentialProvider> {
/**
* called per Keycloak transaction.
*
* @param session
* @param model
* @return
*/
T create(KeycloakSession session, ComponentModel model);
public interface CredentialProviderFactory<T extends CredentialProvider> extends ProviderFactory<CredentialProvider> {
/**
* This is the name of the provider and will be showed in the admin console as an option.
*
@ -67,19 +59,4 @@ public interface CredentialProviderFactory<T extends CredentialProvider> extends
}
@Override
default String getHelpText() {
return "";
}
@Override
default List<ProviderConfigProperty> getConfigProperties() {
return Collections.EMPTY_LIST;
}
@Override
default void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException {
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.credential;
import org.keycloak.credential.hash.PasswordHashProvider;
import org.keycloak.credential.hash.PasswordHashProviderFactory;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
*/
public class CredentialSpi implements Spi {
@Override
public boolean isInternal() {
return false;
}
@Override
public String getName() {
return "credential";
}
@Override
public Class<? extends Provider> getProviderClass() {
return CredentialProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return CredentialProviderFactory.class;
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.credential.hash;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.provider.Provider;
/**
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
*/
public interface PasswordHashProvider extends Provider {
boolean policyCheck(PasswordPolicy policy, CredentialModel credentia);
void encode(String rawPassword, PasswordPolicy policy, CredentialModel credential);
boolean verify(String rawPassword, CredentialModel credential);
}

View file

@ -0,0 +1,27 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.credential.hash;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
*/
public interface PasswordHashProviderFactory extends ProviderFactory<PasswordHashProvider> {
}

View file

@ -0,0 +1,48 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.credential.hash;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
*/
public class PasswordHashSpi implements Spi {
@Override
public boolean isInternal() {
return false;
}
@Override
public String getName() {
return "password-hashing";
}
@Override
public Class<? extends Provider> getProviderClass() {
return PasswordHashProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return PasswordHashProviderFactory.class;
}
}

View file

@ -27,11 +27,68 @@ import java.util.Set;
* @version $Revision: 1 $
*/
public interface UserCredentialManager extends UserCredentialStore {
/**
* Validates list of credentials. Will call UserStorageProvider and UserFederationProviders first, then loop through
* each CredentialProvider.
*
* @param realm
* @param user
* @param inputs
* @return
*/
boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs);
/**
* Validates list of credentials. Will call UserStorageProvider and UserFederationProviders first, then loop through
* each CredentialProvider.
*
* @param realm
* @param user
* @param inputs
* @return
*/
boolean isValid(RealmModel realm, UserModel user, CredentialInput... inputs);
/**
* Updates a credential. Will call UserStorageProvider and UserFederationProviders first, then loop through
* each CredentialProvider. Update is finished whenever any one provider returns true.
*
* @param realm
* @param user
* @return
*/
void updateCredential(RealmModel realm, UserModel user, CredentialInput input);
/**
* Calls disableCredential on UserStorageProvider and UserFederationProviders first, then loop through
* each CredentialProvider.
*
* @param realm
* @param user
* @param credentialType
*/
void disableCredential(RealmModel realm, UserModel user, String credentialType);
/**
* Checks to see if user has credential type configured. Looks in UserStorageProvider or UserFederationProvider first,
* then loops through each CredentialProvider.
*
* @param realm
* @param user
* @param type
* @return
*/
boolean isConfiguredFor(RealmModel realm, UserModel user, String type);
/**
* Only loops through each CredentialProvider to see if credential type is configured for the user.
* This allows UserStorageProvider and UserFederationProvider to look to abort isValid
*
* @param realm
* @param user
* @param type
* @return
*/
boolean isConfiguredLocally(RealmModel realm, UserModel user, String type);
}

View file

@ -58,10 +58,6 @@ public class UserFederationManager implements UserProvider {
return registerWithFederation(realm, user);
}
protected UserFederationProvider getFederationProvider(UserFederationProviderModel model) {
return KeycloakModelUtils.getFederationProviderInstance(session, model);
}
@Override
public UserModel addUser(RealmModel realm, String username) {
UserModel user = session.userStorage().addUser(realm, username.toLowerCase());
@ -81,7 +77,11 @@ public class UserFederationManager implements UserProvider {
return user;
}
protected UserFederationProvider getFederationLink(RealmModel realm, UserModel user) {
protected UserFederationProvider getFederationProvider(UserFederationProviderModel model) {
return KeycloakModelUtils.getFederationProviderInstance(session, model);
}
public UserFederationProvider getFederationLink(RealmModel realm, UserModel user) {
if (user.getFederationLink() == null) return null;
for (UserFederationProviderModel fed : realm.getUserFederationProviders()) {
if (fed.getId().equals(user.getFederationLink())) {
@ -112,7 +112,7 @@ public class UserFederationManager implements UserProvider {
}
protected void validateUser(RealmModel realm, UserModel user) {
public void validateUser(RealmModel realm, UserModel user) {
if (managedUsers.containsKey(user.getId())) {
return;
}
@ -488,84 +488,6 @@ public class UserFederationManager implements UserProvider {
session.userStorage().preRemove(protocolMapper);
}
public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) {
if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
if (realm.getPasswordPolicy() != null) {
PolicyError error = session.getProvider(PasswordPolicyManagerProvider.class).validate(user, credential.getValue());
if (error != null) throw new ModelException(error.getMessage(), error.getParameters());
}
}
user.updateCredential(credential);
}
@Override
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
UserFederationProvider link = getFederationLink(realm, user);
if (link != null) {
validateUser(realm, user);
Set<String> supportedCredentialTypes = link.getSupportedCredentialTypes(user);
if (supportedCredentialTypes.size() > 0) {
List<UserCredentialModel> fedCreds = new ArrayList<UserCredentialModel>();
List<UserCredentialModel> localCreds = new ArrayList<UserCredentialModel>();
for (UserCredentialModel cred : input) {
if (supportedCredentialTypes.contains(cred.getType())) {
fedCreds.add(cred);
} else {
localCreds.add(cred);
}
}
if (!link.validCredentials(realm, user, fedCreds)) {
return false;
}
return session.userStorage().validCredentials(session, realm, user, localCreds);
}
}
return session.userStorage().validCredentials(session, realm, user, input);
}
/**
* Is the user configured to use this credential type
*
* @return
*/
public boolean configuredForCredentialType(String type, RealmModel realm, UserModel user) {
UserFederationProvider link = getFederationLink(realm, user);
if (link != null) {
Set<String> supportedCredentialTypes = link.getSupportedCredentialTypes(user);
if (supportedCredentialTypes.contains(type)) return true;
}
if (UserCredentialModel.isOtp(type)) {
if (!user.isOtpEnabled()) return false;
}
List<UserCredentialValueModel> creds = user.getCredentialsDirectly();
for (UserCredentialValueModel cred : creds) {
if (cred.getType().equals(type)) {
if (UserCredentialModel.isOtp(type)) {
OTPPolicy otpPolicy = realm.getOTPPolicy();
if (!cred.getAlgorithm().equals(otpPolicy.getAlgorithm())
|| cred.getDigits() != otpPolicy.getDigits()) {
return false;
}
if (type.equals(UserCredentialModel.TOTP) && cred.getPeriod() != otpPolicy.getPeriod()) {
return false;
}
}
return true;
}
}
return false;
}
@Override
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) {
return validCredentials(session, realm, user, Arrays.asList(input));
}
@Override
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
List<UserFederationProviderModel> fedProviderModels = realm.getUserFederationProviders();

View file

@ -17,6 +17,8 @@
package org.keycloak.models;
import org.keycloak.credential.CredentialInputUpdater;
import org.keycloak.credential.CredentialInputValidator;
import org.keycloak.provider.Provider;
import java.util.List;
@ -30,7 +32,8 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface UserFederationProvider extends Provider {
@Deprecated
public interface UserFederationProvider extends Provider, CredentialInputValidator, CredentialInputUpdater {
public static final String USERNAME = UserModel.USERNAME;
public static final String EMAIL = UserModel.EMAIL;
@ -166,14 +169,6 @@ public interface UserFederationProvider extends Provider {
*/
boolean isValid(RealmModel realm, UserModel local);
/**
* What UserCredentialModel types should be handled by this provider for this user? Keycloak will only call
* validCredentials() with the credential types specified in this method.
*
* @return
*/
Set<String> getSupportedCredentialTypes(UserModel user);
/**
* What UserCredentialModel types should be handled by this provider? This is called in scenarios when we don't know user,
* who is going to authenticate (For example Kerberos authentication).
@ -182,18 +177,6 @@ public interface UserFederationProvider extends Provider {
*/
Set<String> getSupportedCredentialTypes();
/**
* Validate credentials for this user. This method will only be called with credential parameters supported
* by this provider
*
* @param realm
* @param user
* @param input
* @return
*/
boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input);
boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input);
/**
* Validate credentials of unknown user. The authenticated user is recognized based on provided credentials and returned back in CredentialValidationOutput
* @param realm

View file

@ -54,8 +54,6 @@ public interface UserModel extends RoleMapperModel {
boolean isEnabled();
boolean isOtpEnabled();
void setEnabled(boolean enabled);
/**
@ -110,14 +108,6 @@ public interface UserModel extends RoleMapperModel {
void setEmailVerified(boolean verified);
void setOtpEnabled(boolean totp);
void updateCredential(UserCredentialModel cred);
List<UserCredentialValueModel> getCredentialsDirectly();
void updateCredentialDirectly(UserCredentialValueModel cred);
Set<GroupModel> getGroups();
void joinGroup(GroupModel group);
void leaveGroup(GroupModel group);

View file

@ -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;
@ -35,7 +33,6 @@ import java.util.Set;
public interface UserProvider extends Provider,
UserLookupProvider,
UserQueryProvider,
UserCredentialValidatorProvider,
UserRegistrationProvider {
// Note: The reason there are so many query methods here is for layering a cache on top of an persistent KeycloakSession
@ -79,7 +76,6 @@ public interface UserProvider extends Provider,
void preRemove(ProtocolMapperModel protocolMapper);
boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input);
CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input);

View file

@ -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();
/**

View file

@ -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);
}

View file

@ -32,7 +32,6 @@ public class UserEntity extends AbstractIdentifiableEntity {
private String lastName;
private String email;
private boolean emailVerified;
private boolean totp;
private boolean enabled;
private String realmId;
@ -96,14 +95,6 @@ public class UserEntity extends AbstractIdentifiableEntity {
this.emailVerified = emailVerified;
}
public boolean isTotp() {
return totp;
}
public void setTotp(boolean totp) {
this.totp = totp;
}
public boolean isEnabled() {
return enabled;
}

View file

@ -38,45 +38,6 @@ import java.util.List;
*/
public class CredentialValidation {
/**
* Will update password if hash iteration policy has changed
*
* @param realm
* @param user
* @param password
* @return
*/
public static boolean validPassword(KeycloakSession session, RealmModel realm, UserModel user, String password) {
UserCredentialValueModel passwordCred = null;
for (UserCredentialValueModel cred : user.getCredentialsDirectly()) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
passwordCred = cred;
}
}
if (passwordCred == null) return false;
return validateHashedCredential(session, realm, user, password, passwordCred);
}
public static boolean validateHashedCredential(KeycloakSession session, RealmModel realm, UserModel user, String unhashedCredValue, UserCredentialValueModel credential) {
if (unhashedCredValue == null || unhashedCredValue.isEmpty()) {
return false;
}
boolean validated = PasswordHashManager.verify(session, realm, unhashedCredValue, credential);
if (validated) {
if (realm.getPasswordPolicy().getHashIterations() != credential.getHashIterations()) {
UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, unhashedCredValue);
user.updateCredentialDirectly(newCred);
}
}
return validated;
}
public static boolean validPasswordToken(RealmModel realm, UserModel user, String encodedPasswordToken) {
try {
@ -100,23 +61,6 @@ public class CredentialValidation {
}
}
public static boolean validHOTP(RealmModel realm, UserModel user, String otp) {
UserCredentialValueModel passwordCred = null;
OTPPolicy policy = realm.getOTPPolicy();
HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());
for (UserCredentialValueModel cred : user.getCredentialsDirectly()) {
if (cred.getType().equals(UserCredentialModel.HOTP)) {
int counter = validator.validateHOTP(otp, cred.getValue(), cred.getCounter());
if (counter < 0) return false;
cred.setCounter(counter);
user.updateCredentialDirectly(cred);
return true;
}
}
return false;
}
public static boolean validOTP(RealmModel realm, String token, String secret) {
OTPPolicy policy = realm.getOTPPolicy();
if (policy.getType().equals(UserCredentialModel.TOTP)) {
@ -130,84 +74,5 @@ public class CredentialValidation {
}
public static boolean validTOTP(RealmModel realm, UserModel user, String otp) {
UserCredentialValueModel passwordCred = null;
OTPPolicy policy = realm.getOTPPolicy();
TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow());
for (UserCredentialValueModel cred : user.getCredentialsDirectly()) {
if (cred.getType().equals(UserCredentialModel.TOTP)) {
if (validator.validateTOTP(otp, cred.getValue().getBytes())) {
return true;
}
}
}
return false;
}
public static boolean validSecret(RealmModel realm, UserModel user, String secret) {
for (UserCredentialValueModel cred : user.getCredentialsDirectly()) {
if (cred.getType().equals(UserCredentialModel.SECRET)) {
if (cred.getValue().equals(secret)) return true;
}
}
return false;
}
/**
* Must validate all credentials. FYI, password hashes may be rehashed and updated based on realm hash password policies.
*
* @param realm
* @param user
* @param credentials
* @return
*/
public static boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> credentials) {
for (UserCredentialModel credential : credentials) {
if (!validCredential(session, realm, user, credential)) return false;
}
return true;
}
/**
* Must validate all credentials. FYI, password hashes may be rehashed and updated based on realm hash password policies.
*
* @param realm
* @param user
* @param credentials
* @return
*/
public static boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... credentials) {
for (UserCredentialModel credential : credentials) {
if (!validCredential(session, realm, user, credential)) return false;
}
return true;
}
public static boolean validCredential(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel credential) {
if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
if (!validPassword(session, realm, user, credential.getValue())) {
return false;
}
} else if (credential.getType().equals(UserCredentialModel.PASSWORD_TOKEN)) {
if (!validPasswordToken(realm, user, credential.getValue())) {
return false;
}
} else if (credential.getType().equals(UserCredentialModel.TOTP)) {
if (!validTOTP(realm, user, credential.getValue())) {
return false;
}
} else if (credential.getType().equals(UserCredentialModel.HOTP)) {
if (!validHOTP(realm, user, credential.getValue())) {
return false;
}
} else if (credential.getType().equals(UserCredentialModel.SECRET)) {
if (!validSecret(realm, user, credential.getValue())) {
return false;
}
} else {
return false;
}
return true;
}
}

View file

@ -26,6 +26,7 @@ import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.AuthDetails;
@ -174,7 +175,7 @@ public class ModelToRepresentation {
}
public static UserRepresentation toRepresentation(UserModel user) {
public static UserRepresentation toRepresentation(KeycloakSession session, RealmModel realm, UserModel user) {
UserRepresentation rep = new UserRepresentation();
rep.setId(user.getId());
rep.setUsername(user.getUsername());
@ -184,7 +185,7 @@ public class ModelToRepresentation {
rep.setEmail(user.getEmail());
rep.setEnabled(user.isEnabled());
rep.setEmailVerified(user.isEmailVerified());
rep.setTotp(user.isOtpEnabled());
rep.setTotp(session.userCredentialManager().isConfiguredFor(realm, user, CredentialModel.OTP));
rep.setFederationLink(user.getFederationLink());
List<String> reqActions = new ArrayList<String>();

View file

@ -30,6 +30,7 @@ import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
import org.keycloak.hash.Pbkdf2PasswordHashProvider;
import org.keycloak.migration.migrators.MigrationUtils;
import org.keycloak.models.ClientTemplateModel;
@ -1348,7 +1349,6 @@ public class RepresentationToModel {
user.setFirstName(userRep.getFirstName());
user.setLastName(userRep.getLastName());
user.setFederationLink(userRep.getFederationLink());
if (userRep.isTotp() != null) user.setOtpEnabled(userRep.isTotp());
if (userRep.getAttributes() != null) {
for (Map.Entry<String, Object> entry : userRep.getAttributes().entrySet()) {
Object value = entry.getValue();
@ -1368,7 +1368,7 @@ public class RepresentationToModel {
user.addRequiredAction(UserModel.RequiredAction.valueOf(requiredAction));
}
}
createCredentials(userRep, user);
createCredentials(userRep, session, newRealm, user);
if (userRep.getFederatedIdentities() != null) {
for (FederatedIdentityRepresentation identity : userRep.getFederatedIdentities()) {
FederatedIdentityModel mappingModel = new FederatedIdentityModel(identity.getIdentityProvider(), identity.getUserId(), identity.getUserName());
@ -1403,21 +1403,21 @@ public class RepresentationToModel {
return user;
}
public static void createCredentials(UserRepresentation userRep, UserModel user) {
public static void createCredentials(UserRepresentation userRep, KeycloakSession session, RealmModel realm,UserModel user) {
if (userRep.getCredentials() != null) {
for (CredentialRepresentation cred : userRep.getCredentials()) {
updateCredential(user, cred);
updateCredential(session, realm, user, cred);
}
}
}
// Detect if it is "plain-text" or "hashed" representation and update model according to it
private static void updateCredential(UserModel user, CredentialRepresentation cred) {
private static void updateCredential(KeycloakSession session, RealmModel realm, UserModel user, CredentialRepresentation cred) {
if (cred.getValue() != null) {
UserCredentialModel plainTextCred = convertCredential(cred);
user.updateCredential(plainTextCred);
session.userCredentialManager().updateCredential(realm, user, plainTextCred);
} else {
UserCredentialValueModel hashedCred = new UserCredentialValueModel();
CredentialModel hashedCred = new CredentialModel();
hashedCred.setType(cred.getType());
hashedCred.setDevice(cred.getDevice());
if (cred.getHashIterations() != null) hashedCred.setHashIterations(cred.getHashIterations());
@ -1456,7 +1456,7 @@ public class RepresentationToModel {
hashedCred.setPeriod(30);
}
hashedCred.setCreatedDate(cred.getCreatedDate());
user.updateCredentialDirectly(hashedCred);
session.userCredentialManager().createCredential(realm, user, hashedCred);
}
}

View file

@ -60,11 +60,6 @@ public class UserModelDelegate implements UserModel {
return delegate.isEnabled();
}
@Override
public boolean isOtpEnabled() {
return delegate.isOtpEnabled();
}
@Override
public void setEnabled(boolean enabled) {
delegate.setEnabled(enabled);
@ -165,26 +160,6 @@ public class UserModelDelegate implements UserModel {
delegate.setEmailVerified(verified);
}
@Override
public void setOtpEnabled(boolean totp) {
delegate.setOtpEnabled(totp);
}
@Override
public void updateCredential(UserCredentialModel cred) {
delegate.updateCredential(cred);
}
@Override
public List<UserCredentialValueModel> getCredentialsDirectly() {
return delegate.getCredentialsDirectly();
}
@Override
public void updateCredentialDirectly(UserCredentialValueModel cred) {
delegate.updateCredentialDirectly(cred);
}
@Override
public Set<RoleModel> getRealmRoleMappings() {
return delegate.getRealmRoleMappings();

View file

@ -19,6 +19,7 @@ package org.keycloak.policy;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import java.util.LinkedList;
@ -36,9 +37,9 @@ public class DefaultPasswordPolicyManagerProvider implements PasswordPolicyManag
}
@Override
public PolicyError validate(UserModel user, String password) {
for (PasswordPolicyProvider p : getProviders(session)) {
PolicyError policyError = p.validate(user, password);
public PolicyError validate(RealmModel realm, UserModel user, String password) {
for (PasswordPolicyProvider p : getProviders(realm, session)) {
PolicyError policyError = p.validate(realm, user, password);
if (policyError != null) {
return policyError;
}
@ -62,8 +63,13 @@ public class DefaultPasswordPolicyManagerProvider implements PasswordPolicyManag
}
private List<PasswordPolicyProvider> getProviders(KeycloakSession session) {
return getProviders(session.getContext().getRealm(), session);
}
private List<PasswordPolicyProvider> getProviders(RealmModel realm, KeycloakSession session) {
LinkedList<PasswordPolicyProvider> list = new LinkedList<>();
PasswordPolicy policy = session.getContext().getRealm().getPasswordPolicy();
PasswordPolicy policy = realm.getPasswordPolicy();
for (String id : policy.getPolicies()) {
PasswordPolicyProvider provider = session.getProvider(PasswordPolicyProvider.class, id);
list.add(provider);

View file

@ -18,6 +18,7 @@
package org.keycloak.policy;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
/**
@ -46,7 +47,7 @@ public class DigitsPasswordPolicyProvider implements PasswordPolicyProvider {
}
@Override
public PolicyError validate(UserModel user, String password) {
public PolicyError validate(RealmModel realm, UserModel user, String password) {
return validate(user.getUsername(), password);
}

View file

@ -20,6 +20,7 @@ package org.keycloak.policy;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
/**
@ -53,7 +54,7 @@ public class ForceExpiredPasswordPolicyProviderFactory implements PasswordPolicy
}
@Override
public PolicyError validate(UserModel user, String password) {
public PolicyError validate(RealmModel realm, UserModel user, String password) {
return null;
}

View file

@ -20,6 +20,7 @@ package org.keycloak.policy;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
/**
@ -54,7 +55,7 @@ public class HashAlgorithmPasswordPolicyProviderFactory implements PasswordPolic
}
@Override
public PolicyError validate(UserModel user, String password) {
public PolicyError validate(RealmModel realm, UserModel user, String password) {
return null;
}

View file

@ -20,6 +20,7 @@ package org.keycloak.policy;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
/**
@ -50,7 +51,7 @@ public class HashIterationsPasswordPolicyProviderFactory implements PasswordPoli
}
@Override
public PolicyError validate(UserModel user, String password) {
public PolicyError validate(RealmModel realm, UserModel user, String password) {
return null;
}

View file

@ -17,9 +17,13 @@
package org.keycloak.policy;
import org.jboss.logging.Logger;
import org.keycloak.credential.CredentialModel;
import org.keycloak.credential.hash.PasswordHashProvider;
import org.keycloak.hash.PasswordHashManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
@ -34,6 +38,7 @@ import java.util.List;
*/
public class HistoryPasswordPolicyProvider implements PasswordPolicyProvider {
private static final Logger logger = Logger.getLogger(HistoryPasswordPolicyProvider.class);
private static final String ERROR_MESSAGE = "invalidPasswordHistoryMessage";
private KeycloakSession session;
@ -48,63 +53,30 @@ public class HistoryPasswordPolicyProvider implements PasswordPolicyProvider {
}
@Override
public PolicyError validate(UserModel user, String password) {
public PolicyError validate(RealmModel realm, UserModel user, String password) {
PasswordPolicy policy = session.getContext().getRealm().getPasswordPolicy();
int passwordHistoryPolicyValue = policy.getPolicyConfig(HistoryPasswordPolicyProviderFactory.ID);
if (passwordHistoryPolicyValue != -1) {
UserCredentialValueModel cred = getCredentialValueModel(user, UserCredentialModel.PASSWORD);
if (cred != null) {
if(PasswordHashManager.verify(session, policy, password, cred)) {
List<CredentialModel> storedPasswords = session.userCredentialManager().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD);
for (CredentialModel cred : storedPasswords) {
PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, cred.getAlgorithm());
if (hash == null) continue;
if (hash.verify(password, cred)) {
return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue);
}
}
List<UserCredentialValueModel> passwordExpiredCredentials = getCredentialValueModels(user, passwordHistoryPolicyValue - 1,
UserCredentialModel.PASSWORD_HISTORY);
for (UserCredentialValueModel credential : passwordExpiredCredentials) {
if (PasswordHashManager.verify(session, policy, password, credential)) {
List<CredentialModel> passwordHistory = session.userCredentialManager().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD_HISTORY);
for (CredentialModel cred : passwordHistory) {
PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, cred.getAlgorithm());
if (hash.verify(password, cred)) {
return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue);
}
}
}
return null;
}
private UserCredentialValueModel getCredentialValueModel(UserModel user, String credType) {
for (UserCredentialValueModel model : user.getCredentialsDirectly()) {
if (model.getType().equals(credType)) {
return model;
}
}
return null;
}
private List<UserCredentialValueModel> getCredentialValueModels(UserModel user, int expiredPasswordsPolicyValue, String credType) {
List<UserCredentialValueModel> credentialModels = new ArrayList<UserCredentialValueModel>();
for (UserCredentialValueModel model : user.getCredentialsDirectly()) {
if (model.getType().equals(credType)) {
credentialModels.add(model);
}
}
Collections.sort(credentialModels, new Comparator<UserCredentialValueModel>() {
public int compare(UserCredentialValueModel credFirst, UserCredentialValueModel credSecond) {
if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) {
return -1;
} else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) {
return 1;
} else {
return 0;
}
}
});
if (credentialModels.size() > expiredPasswordsPolicyValue) {
return credentialModels.subList(0, expiredPasswordsPolicyValue);
}
return credentialModels;
}
@Override
public Object parseConfig(String value) {
return value != null ? Integer.parseInt(value) : HistoryPasswordPolicyProviderFactory.DEFAULT_VALUE;

View file

@ -18,6 +18,7 @@
package org.keycloak.policy;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
/**
@ -40,7 +41,7 @@ public class LengthPasswordPolicyProvider implements PasswordPolicyProvider {
}
@Override
public PolicyError validate(UserModel user, String password) {
public PolicyError validate(RealmModel realm, UserModel user, String password) {
return validate(user.getUsername(), password);
}

View file

@ -18,6 +18,7 @@
package org.keycloak.policy;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
/**
@ -46,7 +47,7 @@ public class LowerCasePasswordPolicyProvider implements PasswordPolicyProvider {
}
@Override
public PolicyError validate(UserModel user, String password) {
public PolicyError validate(RealmModel realm, UserModel user, String password) {
return validate(user.getUsername(), password);
}

View file

@ -18,6 +18,7 @@
package org.keycloak.policy;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
/**
@ -42,7 +43,7 @@ public class NotUsernamePasswordPolicyProvider implements PasswordPolicyProvider
}
@Override
public PolicyError validate(UserModel user, String password) {
public PolicyError validate(RealmModel realm, UserModel user, String password) {
return validate(user.getUsername(), password);
}

View file

@ -17,6 +17,7 @@
package org.keycloak.policy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.Provider;
@ -25,7 +26,7 @@ import org.keycloak.provider.Provider;
*/
public interface PasswordPolicyManagerProvider extends Provider {
PolicyError validate(UserModel user, String password);
PolicyError validate(RealmModel realm, UserModel user, String password);
PolicyError validate(String user, String password);
}

View file

@ -17,6 +17,7 @@
package org.keycloak.policy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.Provider;
@ -28,7 +29,7 @@ public interface PasswordPolicyProvider extends Provider {
String STRING_CONFIG_TYPE = "String";
String INT_CONFIG_TYPE = "int";
PolicyError validate(UserModel user, String password);
PolicyError validate(RealmModel realm, UserModel user, String password);
PolicyError validate(String user, String password);
Object parseConfig(String value);

View file

@ -18,6 +18,7 @@
package org.keycloak.policy;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import java.util.regex.Matcher;
@ -47,7 +48,7 @@ public class RegexPatternsPasswordPolicyProvider implements PasswordPolicyProvid
}
@Override
public PolicyError validate(UserModel user, String password) {
public PolicyError validate(RealmModel realm, UserModel user, String password) {
return validate(user.getUsername(), password);
}

View file

@ -18,6 +18,7 @@
package org.keycloak.policy;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
/**
@ -46,7 +47,7 @@ public class SpecialCharsPasswordPolicyProvider implements PasswordPolicyProvide
}
@Override
public PolicyError validate(UserModel user, String password) {
public PolicyError validate(RealmModel realm, UserModel user, String password) {
return validate(user.getUsername(), password);
}

View file

@ -18,6 +18,7 @@
package org.keycloak.policy;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
/**
@ -46,7 +47,7 @@ public class UpperCasePasswordPolicyProvider implements PasswordPolicyProvider {
}
@Override
public PolicyError validate(UserModel user, String password) {
public PolicyError validate(RealmModel realm, UserModel user, String password) {
return validate(user.getUsername(), password);
}

View file

@ -223,17 +223,6 @@ public abstract class AbstractUserAdapter implements UserModel {
throw new ReadOnlyException("user is read only for this update");
}
@Override
public boolean isOtpEnabled() {
return false;
}
@Override
public void setOtpEnabled(boolean totp) {
throw new ReadOnlyException("user is read only for this update");
}
/**
* This method should not be overriden
*
@ -386,23 +375,6 @@ public abstract class AbstractUserAdapter implements UserModel {
}
@Override
public void updateCredential(UserCredentialModel cred) {
throw new ReadOnlyException("user is read only for this update");
}
@Override
public List<UserCredentialValueModel> getCredentialsDirectly() {
return Collections.EMPTY_LIST;
}
@Override
public void updateCredentialDirectly(UserCredentialValueModel cred) {
throw new ReadOnlyException("user is read only for this update");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -54,7 +54,6 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
public static String EMAIL_VERIFIED_ATTRIBUTE = "EMAIL_VERIFIED";
public static String CREATED_TIMESTAMP_ATTRIBUTE = "CREATED_TIMESTAMP";
public static String ENABLED_ATTRIBUTE = "ENABLED";
public static String OTP_ENABLED_ATTRIBUTE = "OTP_ENABLED";
protected KeycloakSession session;
@ -235,19 +234,6 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
setSingleAttribute(ENABLED_ATTRIBUTE, Boolean.toString(enabled));
}
@Override
public boolean isOtpEnabled() {
String val = getFirstAttribute(OTP_ENABLED_ATTRIBUTE);
if (val == null) return false;
else return Boolean.valueOf(val);
}
@Override
public void setOtpEnabled(boolean totp) {
setSingleAttribute(OTP_ENABLED_ATTRIBUTE, Boolean.toString(totp));
}
/**
* This method should not be overriden
*
@ -399,23 +385,6 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
}
@Override
public void updateCredential(UserCredentialModel cred) {
getFederatedStorage().updateCredential(realm, this, cred);
}
@Override
public List<UserCredentialValueModel> getCredentialsDirectly() {
return getFederatedStorage().getCredentials(realm, this);
}
@Override
public void updateCredentialDirectly(UserCredentialValueModel cred) {
getFederatedStorage().updateCredential(realm, this, cred);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -27,6 +27,9 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.Provider;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
@ -40,6 +43,8 @@ public interface UserFederatedStorageProvider extends Provider,
UserRequiredActionsFederatedStorage,
UserRoleMappingsFederatedStorage {
List<String> getStoredUsers(RealmModel realm, int first, int max);
void preRemove(RealmModel realm);
void preRemove(RealmModel realm, UserFederationProviderModel link);

View file

@ -63,3 +63,5 @@ org.keycloak.protocol.oidc.TokenIntrospectionSpi
org.keycloak.policy.PasswordPolicySpi
org.keycloak.policy.PasswordPolicyManagerSpi
org.keycloak.transaction.TransactionManagerLookupSpi
org.keycloak.credential.hash.PasswordHashSpi
org.keycloak.credential.CredentialSpi

View file

@ -20,6 +20,7 @@ package org.keycloak.authentication.authenticators.browser;
import org.keycloak.authentication.AbstractFormAuthenticator;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.credential.CredentialInput;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.models.ModelDuplicateException;
@ -167,10 +168,10 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
}
public boolean validatePassword(AuthenticationFlowContext context, UserModel user, MultivaluedMap<String, String> inputData) {
List<UserCredentialModel> credentials = new LinkedList<>();
List<CredentialInput> credentials = new LinkedList<>();
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
credentials.add(UserCredentialModel.password(password));
boolean valid = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), user, credentials);
boolean valid = context.getSession().userCredentialManager().isValid(context.getRealm(), user, credentials);
if (!valid) {
context.getEvent().user(user);
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);

View file

@ -58,15 +58,14 @@ public class OTPFormAuthenticator extends AbstractUsernameFormAuthenticator impl
context.resetFlow();
return;
}
List<UserCredentialModel> credentials = new LinkedList<>();
String password = inputData.getFirst(CredentialRepresentation.TOTP);
if (password == null) {
Response challengeResponse = challenge(context, null);
context.challenge(challengeResponse);
return;
}
credentials.add(UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), password));
boolean valid = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), context.getUser(), credentials);
boolean valid = context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(),
UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), password));
if (!valid) {
context.getEvent().user(context.getUser())
.error(Errors.INVALID_USER_CREDENTIALS);
@ -91,7 +90,7 @@ public class OTPFormAuthenticator extends AbstractUsernameFormAuthenticator impl
@Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return session.users().configuredForCredentialType(realm.getOTPPolicy().getType(), realm, user);
return session.userCredentialManager().isConfiguredFor(realm, user, realm.getOTPPolicy().getType());
}
@Override

View file

@ -65,8 +65,7 @@ public class ValidateOTP extends AbstractDirectGrantAuthenticator {
context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse);
return;
}
credentials.add(UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), otp));
boolean valid = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), context.getUser(), credentials);
boolean valid = context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(), UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), otp));
if (!valid) {
context.getEvent().user(context.getUser());
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
@ -89,7 +88,7 @@ public class ValidateOTP extends AbstractDirectGrantAuthenticator {
}
private boolean isConfigured(KeycloakSession session, RealmModel realm, UserModel user) {
return session.users().configuredForCredentialType(realm.getOTPPolicy().getType(), realm, user);
return session.userCredentialManager().isConfiguredFor(realm, user, realm.getOTPPolicy().getType());
}
@Override

View file

@ -46,8 +46,7 @@ public class ValidatePassword extends AbstractDirectGrantAuthenticator {
MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
List<UserCredentialModel> credentials = new LinkedList<>();
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
credentials.add(UserCredentialModel.password(password));
boolean valid = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), context.getUser(), credentials);
boolean valid = context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(), UserCredentialModel.password(password));
if (!valid) {
context.getEvent().user(context.getUser());
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);

View file

@ -39,7 +39,7 @@ public class ResetOTP extends AbstractSetRequiredActionAuthenticator {
}
protected boolean configuredFor(AuthenticationFlowContext context) {
return context.getSession().users().configuredForCredentialType(context.getRealm().getOTPPolicy().getType(), context.getRealm(), context.getUser());
return context.getSession().userCredentialManager().isConfiguredFor(context.getRealm(), context.getUser(), context.getRealm().getOTPPolicy().getType());
}
@Override

View file

@ -40,7 +40,7 @@ public class ResetPassword extends AbstractSetRequiredActionAuthenticator {
}
protected boolean configuredFor(AuthenticationFlowContext context) {
return context.getSession().users().configuredForCredentialType(UserCredentialModel.PASSWORD, context.getRealm(), context.getUser());
return context.getSession().userCredentialManager().isConfiguredFor(context.getRealm(), context.getUser(), UserCredentialModel.PASSWORD);
}
@Override

View file

@ -97,7 +97,7 @@ public class RegistrationPassword implements FormAction, FormActionFactory {
credentials.setValue(password);
UserModel user = context.getUser();
try {
context.getSession().users().updateCredential(context.getRealm(), user, UserCredentialModel.password(formData.getFirst("password")));
context.getSession().userCredentialManager().updateCredential(context.getRealm(), user, UserCredentialModel.password(formData.getFirst("password")));
} catch (Exception me) {
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
}

View file

@ -21,6 +21,10 @@ import org.keycloak.Config;
import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.credential.CredentialModel;
import org.keycloak.credential.CredentialProvider;
import org.keycloak.credential.PasswordCredentialProvider;
import org.keycloak.credential.PasswordCredentialProviderFactory;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
@ -50,22 +54,20 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
public void evaluateTriggers(RequiredActionContext context) {
int daysToExpirePassword = context.getRealm().getPasswordPolicy().getDaysToExpirePassword();
if(daysToExpirePassword != -1) {
for (UserCredentialValueModel entity : context.getUser().getCredentialsDirectly()) {
if (entity.getType().equals(UserCredentialModel.PASSWORD)) {
PasswordCredentialProvider passwordProvider = (PasswordCredentialProvider)context.getSession().getProvider(CredentialProvider.class, PasswordCredentialProviderFactory.PROVIDER_ID);
CredentialModel password = passwordProvider.getPassword(context.getRealm(), context.getUser());
if (password != null) {
if(password.getCreatedDate() == null) {
context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
logger.debug("User is required to update password");
} else {
long timeElapsed = Time.toMillis(Time.currentTime()) - password.getCreatedDate();
long timeToExpire = TimeUnit.DAYS.toMillis(daysToExpirePassword);
if(entity.getCreatedDate() == null) {
if(timeElapsed > timeToExpire) {
context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
logger.debug("User is required to update password");
} else {
long timeElapsed = Time.toMillis(Time.currentTime()) - entity.getCreatedDate();
long timeToExpire = TimeUnit.DAYS.toMillis(daysToExpirePassword);
if(timeElapsed > timeToExpire) {
context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
logger.debug("User is required to update password");
}
}
break;
}
}
}
@ -107,7 +109,7 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
}
try {
context.getSession().users().updateCredential(context.getRealm(), context.getUser(), UserCredentialModel.password(passwordNew));
context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), UserCredentialModel.password(passwordNew));
context.success();
} catch (ModelException me) {
errorEvent.detail(Details.REASON, me.getMessage()).error(Errors.PASSWORD_REJECTED);

View file

@ -77,16 +77,15 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
UserCredentialModel credentials = new UserCredentialModel();
credentials.setType(context.getRealm().getOTPPolicy().getType());
credentials.setValue(totpSecret);
context.getSession().users().updateCredential(context.getRealm(), context.getUser(), credentials);
context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), credentials);
// if type is HOTP, to update counter we execute validation based on supplied token
UserCredentialModel cred = new UserCredentialModel();
cred.setType(context.getRealm().getOTPPolicy().getType());
cred.setValue(totp);
context.getSession().users().validCredentials(context.getSession(), context.getRealm(), context.getUser(), cred);
context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(), cred);
context.getUser().setOtpEnabled(true);
context.success();
}

View file

@ -17,6 +17,7 @@
package org.keycloak.credential;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OTPPolicy;
import org.keycloak.models.RealmModel;
@ -34,15 +35,15 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class LocalOTPCredentialManager implements CredentialInputValidator, CredentialInputUpdater, OnUserCache {
private static final Logger logger = Logger.getLogger(LocalOTPCredentialManager.class);
public class OTPCredentialProvider implements CredentialProvider, CredentialInputValidator, CredentialInputUpdater, OnUserCache {
private static final Logger logger = Logger.getLogger(OTPCredentialProvider.class);
protected KeycloakSession session;
protected List<CredentialModel> getCachedCredentials(UserModel user, String type) {
if (!(user instanceof CachedUserModel)) return Collections.EMPTY_LIST;
CachedUserModel cached = (CachedUserModel)user;
List<CredentialModel> rtn = (List<CredentialModel>)cached.getCachedWith().get(LocalOTPCredentialManager.class.getName() + "." + type);
List<CredentialModel> rtn = (List<CredentialModel>)cached.getCachedWith().get(OTPCredentialProvider.class.getName() + "." + type);
if (rtn == null) return Collections.EMPTY_LIST;
return rtn;
}
@ -52,13 +53,13 @@ public class LocalOTPCredentialManager implements CredentialInputValidator, Cred
}
@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(LocalOTPCredentialManager.class.getName() + "." + CredentialModel.TOTP, creds);
user.getCachedWith().put(OTPCredentialProvider.class.getName() + "." + CredentialModel.TOTP, creds);
}
public LocalOTPCredentialManager(KeycloakSession session) {
public OTPCredentialProvider(KeycloakSession session) {
this.session = session;
}
@ -88,10 +89,11 @@ public class LocalOTPCredentialManager implements CredentialInputValidator, Cred
model.setDigits(policy.getDigits());
model.setCounter(policy.getInitialCounter());
model.setAlgorithm(policy.getAlgorithm());
model.setType(policy.getType());
model.setType(input.getType());
model.setValue(inputModel.getValue());
model.setDevice(inputModel.getDevice());
model.setPeriod(policy.getPeriod());
model.setCreatedDate(Time.currentTimeMillis());
if (model.getId() == null) {
getCredentialStore().createCredential(realm, user, model);
} else {
@ -194,6 +196,10 @@ public class LocalOTPCredentialManager implements CredentialInputValidator, Cred
}
String token = ((UserCredentialModel)input).getValue();
if (token == null) {
return false;
}
OTPPolicy policy = realm.getOTPPolicy();
if (realm.getOTPPolicy().getType().equals(CredentialModel.HOTP)) {
HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());

View file

@ -0,0 +1,37 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.credential;
import org.keycloak.models.KeycloakSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OTPCredentialProviderFactory implements CredentialProviderFactory<OTPCredentialProvider> {
public static final String PROVIDER_ID="keycloak-otp";
@Override
public OTPCredentialProvider create(KeycloakSession session) {
return new OTPCredentialProvider(session);
}
@Override
public String getId() {
return PROVIDER_ID;
}
}

View file

@ -0,0 +1,216 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.credential;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.credential.hash.PasswordHashProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.cache.CachedUserModel;
import org.keycloak.models.cache.OnUserCache;
import org.keycloak.policy.HashAlgorithmPasswordPolicyProviderFactory;
import org.keycloak.policy.PasswordPolicyManagerProvider;
import org.keycloak.policy.PolicyError;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class PasswordCredentialProvider implements CredentialProvider, CredentialInputValidator, CredentialInputUpdater, OnUserCache {
public static final String PASSWORD_CACHE_KEY = PasswordCredentialProvider.class.getName() + "." + CredentialModel.PASSWORD;
private static final Logger logger = Logger.getLogger(PasswordCredentialProvider.class);
protected KeycloakSession session;
public PasswordCredentialProvider(KeycloakSession session) {
this.session = session;
}
protected UserCredentialStore getCredentialStore() {
return session.userCredentialManager();
}
public CredentialModel getPassword(RealmModel realm, UserModel user) {
List<CredentialModel> passwords = null;
if (user instanceof CachedUserModel) {
CachedUserModel cached = (CachedUserModel)user;
passwords = (List<CredentialModel>)cached.getCachedWith().get(PASSWORD_CACHE_KEY);
} else {
passwords = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD);
}
if (passwords == null || passwords.isEmpty()) return null;
return passwords.get(0);
}
@Override
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
if (!supportsCredentialType(input.getType())) return false;
if (!(input instanceof UserCredentialModel)) {
logger.debug("Expected instance of UserCredentialModel for CredentialInput");
return false;
}
UserCredentialModel cred = (UserCredentialModel)input;
PasswordPolicy policy = realm.getPasswordPolicy();
PolicyError error = session.getProvider(PasswordPolicyManagerProvider.class).validate(realm, user, cred.getValue());
if (error != null) throw new ModelException(error.getMessage(), error.getParameters());
PasswordHashProvider hash = getHashProvider(policy);
if (hash == null) {
return false;
}
CredentialModel oldPassword = getPassword(realm, user);
expirePassword(realm, user, policy);
CredentialModel newPassword = new CredentialModel();
newPassword.setType(CredentialModel.PASSWORD);
long createdDate = Time.currentTimeMillis();
newPassword.setCreatedDate(createdDate);
hash.encode(cred.getValue(), policy, newPassword);
getCredentialStore().createCredential(realm, user, newPassword);
session.getUserCache().evict(realm, user);
return true;
}
protected void expirePassword(RealmModel realm, UserModel user, PasswordPolicy policy) {
CredentialModel oldPassword = getPassword(realm, user);
if (oldPassword == null) return;
int expiredPasswordsPolicyValue = policy.getExpiredPasswords();
if (expiredPasswordsPolicyValue > -1) {
List<CredentialModel> list = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD_HISTORY);
List<CredentialModel> history = new LinkedList<>();
history.addAll(list);
if (history.size() + 1 >= expiredPasswordsPolicyValue) {
Collections.sort(history, new Comparator<CredentialModel>() {
@Override
public int compare(CredentialModel o1, CredentialModel o2) {
long o1Date = o1.getCreatedDate() == null ? 0 : o1.getCreatedDate().longValue();
long o2Date = o2.getCreatedDate() == null ? 0 : o2.getCreatedDate().longValue();
if (o1Date > o2Date) return 1;
else if (o1Date < o2Date) return -1;
else return 0;
}
});
for (int i = 0; i < history.size() + 2 - expiredPasswordsPolicyValue; i++) {
getCredentialStore().removeStoredCredential(realm, user, history.get(i).getId());
}
}
oldPassword.setType(CredentialModel.PASSWORD_HISTORY);
getCredentialStore().updateCredential(realm, user, oldPassword);
} else {
session.userCredentialManager().removeStoredCredential(realm, user, oldPassword.getId());
}
}
protected PasswordHashProvider getHashProvider(PasswordPolicy policy) {
PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, policy.getHashAlgorithm());
if (hash == null) {
logger.warnv("Realm PasswordPolicy PasswordHashProvider {0} not found", policy.getHashAlgorithm());
return session.getProvider(PasswordHashProvider.class, HashAlgorithmPasswordPolicyProviderFactory.DEFAULT_VALUE);
}
return hash;
}
@Override
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
if (!supportsCredentialType(credentialType)) return;
PasswordPolicy policy = realm.getPasswordPolicy();
expirePassword(realm, user, policy);
}
@Override
public boolean supportsCredentialType(String credentialType) {
return credentialType.equals(CredentialModel.PASSWORD);
}
@Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
return getPassword(realm, user) != null;
}
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
if (! (input instanceof UserCredentialModel)) {
logger.debug("Expected instance of UserCredentialModel for CredentialInput");
return false;
}
UserCredentialModel cred = (UserCredentialModel)input;
if (cred.getValue() == null) {
logger.debugv("Input password was null for user {0} ", user.getUsername());
return false;
}
CredentialModel password = getPassword(realm, user);
if (password == null) {
logger.debugv("No password cached or stored for user {0} ", user.getUsername());
return false;
}
PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, password.getAlgorithm());
if (hash == null) {
logger.debugv("PasswordHashProvider {0} not found for user {1} ", password.getAlgorithm(), user.getUsername());
return false;
}
if (!hash.verify(cred.getValue(), password)) {
logger.debugv("Failed password validation for user {0} ", user.getUsername());
return false;
}
PasswordPolicy policy = realm.getPasswordPolicy();
if (policy == null) {
return true;
}
hash = getHashProvider(policy);
if (hash == null) {
return true;
}
if (hash.policyCheck(policy, password)) {
return true;
}
hash.encode(cred.getValue(), policy, password);
getCredentialStore().updateCredential(realm, user, password);
session.getUserCache().evict(realm, user);
return true;
}
@Override
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);
}
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.credential;
import org.keycloak.models.KeycloakSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class PasswordCredentialProviderFactory implements CredentialProviderFactory<PasswordCredentialProvider> {
public static final String PROVIDER_ID="keycloak-password";
@Override
public PasswordCredentialProvider create(KeycloakSession session) {
return new PasswordCredentialProvider(session);
}
@Override
public String getId() {
return PROVIDER_ID;
}
}

View file

@ -22,17 +22,24 @@ import org.keycloak.component.PrioritizedComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialManager;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
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.provider.ProviderFactory;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageManager;
import org.keycloak.storage.UserStorageProvider;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -89,6 +96,10 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
return getStoreForUser(user).getStoredCredentialByNameAndType(realm, user, name, type);
}
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput... inputs) {
return isValid(realm, user, Arrays.asList(inputs));
}
@Override
public boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs) {
@ -108,38 +119,47 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
}
}
}
} else {
// <deprecate>
UserFederationProvider link = session.users().getFederationLink(realm, user);
if (link != null) {
session.users().validateUser(realm, user);
Iterator<CredentialInput> it = toValidate.iterator();
while (it.hasNext()) {
CredentialInput input = it.next();
if (link.supportsCredentialType(input.getType())
&& link.isValid(realm, user, input)) {
it.remove();
}
}
}
// </deprecate>
}
if (toValidate.isEmpty()) return true;
List<ComponentModel> components = getCredentialProviderComponents(realm);
for (ComponentModel component : components) {
CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
if (!Types.supports(CredentialInputValidator.class, factory, CredentialProviderFactory.class)) continue;
List<CredentialInputValidator> credentialProviders = getCredentialProviders(realm, CredentialInputValidator.class);
for (CredentialInputValidator validator : credentialProviders) {
Iterator<CredentialInput> it = toValidate.iterator();
while (it.hasNext()) {
CredentialInput input = it.next();
CredentialInputValidator validator = (CredentialInputValidator)session.getAttribute(component.getId());
if (validator == null) {
validator = (CredentialInputValidator)factory.create(session, component);
session.setAttribute(component.getId(), validator);
}
if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) {
it.remove();
}
}
}
}
return toValidate.isEmpty();
}
protected List<ComponentModel> getCredentialProviderComponents(RealmModel realm) {
List<ComponentModel> components = realm.getComponents(realm.getId(), CredentialProvider.class.getName());
if (components.isEmpty()) return components;
List<ComponentModel> copy = new LinkedList<>();
copy.addAll(components);
Collections.sort(copy, PrioritizedComponentModel.comparator);
return copy;
protected <T> List<T> getCredentialProviders(RealmModel realm, Class<T> type) {
List<T> list = new LinkedList<T>();
for (ProviderFactory f : session.getKeycloakSessionFactory().getProviderFactories(CredentialProvider.class)) {
if (!Types.supports(CredentialInputUpdater.class, f, CredentialProviderFactory.class)) continue;
list.add((T)session.getProvider(CredentialProvider.class, f.getId()));
}
return list;
}
@Override
@ -153,21 +173,21 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
if (updater.updateCredential(realm, user, input)) return;
}
}
} else {
// <deprecated>
UserFederationProvider link = session.users().getFederationLink(realm, user);
if (link != null) {
if (link.updateCredential(realm, user, input)) return;
}
// </deprecated>
}
List<ComponentModel> components = getCredentialProviderComponents(realm);
for (ComponentModel component : components) {
CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
CredentialInputUpdater updater = (CredentialInputUpdater)session.getAttribute(component.getId());
if (updater == null) {
updater = (CredentialInputUpdater)factory.create(session, component);
session.setAttribute(component.getId(), updater);
}
List<CredentialInputUpdater> credentialProviders = getCredentialProviders(realm, CredentialInputUpdater.class);
for (CredentialInputUpdater updater : credentialProviders) {
if (!updater.supportsCredentialType(input.getType())) continue;
if (updater.updateCredential(realm, user, input)) return;
}
}
}
@Override
public void disableCredential(RealmModel realm, UserModel user, String credentialType) {
@ -180,21 +200,22 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
updater.disableCredentialType(realm, user, credentialType);
}
}
} else {
UserFederationProvider link = session.users().getFederationLink(realm, user);
if (link != null && link.getSupportedCredentialTypes().contains(credentialType)) {
link.disableCredentialType(realm, user, credentialType);
}
}
List<ComponentModel> components = getCredentialProviderComponents(realm);
for (ComponentModel component : components) {
CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
CredentialInputUpdater updater = (CredentialInputUpdater)session.getAttribute(component.getId());
if (updater == null) {
updater = (CredentialInputUpdater)factory.create(session, component);
session.setAttribute(component.getId(), updater);
}
List<CredentialInputUpdater> credentialProviders = getCredentialProviders(realm, CredentialInputUpdater.class);
for (CredentialInputUpdater updater : credentialProviders) {
if (!updater.supportsCredentialType(credentialType)) continue;
updater.disableCredentialType(realm, user, credentialType);
}
}
@Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
@ -207,39 +228,37 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
return true;
}
}
} else {
// <deprecate>
UserFederationProvider link = session.users().getFederationLink(realm, user);
if (link != null) {
if (link.isConfiguredFor(realm, user, type)) return true;
}
// </deprecate>
}
List<ComponentModel> components = getCredentialProviderComponents(realm);
for (ComponentModel component : components) {
CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
CredentialInputValidator validator = (CredentialInputValidator)session.getAttribute(component.getId());
if (validator == null) {
validator = (CredentialInputValidator)factory.create(session, component);
session.setAttribute(component.getId(), validator);
}
if (validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
return true;
}
}
return false;
return isConfiguredLocally(realm, user, type);
}
@Override
public void onCache(RealmModel realm, CachedUserModel user) {
List<ComponentModel> components = getCredentialProviderComponents(realm);
for (ComponentModel component : components) {
CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
if (!Types.supports(OnUserCache.class, factory, CredentialProviderFactory.class)) continue;
OnUserCache validator = (OnUserCache)session.getAttribute(component.getId());
if (validator == null) {
validator = (OnUserCache)factory.create(session, component);
session.setAttribute(component.getId(), validator);
public boolean isConfiguredLocally(RealmModel realm, UserModel user, String type) {
List<CredentialInputValidator> credentialProviders = getCredentialProviders(realm, CredentialInputValidator.class);
for (CredentialInputValidator validator : credentialProviders) {
if (validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
return true;
}
validator.onCache(realm, user);
}
}
return false;
}
@Override
public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
List<OnUserCache> credentialProviders = getCredentialProviders(realm, OnUserCache.class);
for (OnUserCache validator : credentialProviders) {
validator.onCache(realm, user, delegate);
}
}
@Override

View file

@ -0,0 +1,138 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.credential.hash;
import org.keycloak.Config;
import org.keycloak.common.util.Base64;
import org.keycloak.credential.CredentialModel;
import org.keycloak.credential.hash.PasswordHashProvider;
import org.keycloak.credential.hash.PasswordHashProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
/**
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
*/
public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory, PasswordHashProvider {
public static final String ID = "pbkdf2";
private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final int DERIVED_KEY_SIZE = 512;
public UserCredentialValueModel encode(String rawPassword, int iterations) {
byte[] salt = getSalt();
String encodedPassword = encode(rawPassword, iterations, salt);
UserCredentialValueModel credentials = new UserCredentialValueModel();
credentials.setAlgorithm(ID);
credentials.setType(UserCredentialModel.PASSWORD);
credentials.setSalt(salt);
credentials.setHashIterations(iterations);
credentials.setValue(encodedPassword);
return credentials;
}
public boolean verify(String rawPassword, UserCredentialValueModel credential) {
return encode(rawPassword, credential.getHashIterations(), credential.getSalt()).equals(credential.getValue());
}
@Override
public boolean policyCheck(PasswordPolicy policy, CredentialModel credential) {
return credential.getHashIterations() == policy.getHashIterations() && ID.equals(credential.getAlgorithm());
}
@Override
public void encode(String rawPassword, PasswordPolicy policy, CredentialModel credential) {
byte[] salt = getSalt();
String encodedPassword = encode(rawPassword, policy.getHashIterations(), salt);
credential.setAlgorithm(ID);
credential.setType(UserCredentialModel.PASSWORD);
credential.setSalt(salt);
credential.setHashIterations(policy.getHashIterations());
credential.setValue(encodedPassword);
}
@Override
public boolean verify(String rawPassword, CredentialModel credential) {
return encode(rawPassword, credential.getHashIterations(), credential.getSalt()).equals(credential.getValue());
}
@Override
public PasswordHashProvider create(KeycloakSession session) {
return this;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
public void close() {
}
@Override
public String getId() {
return ID;
}
private String encode(String rawPassword, int iterations, byte[] salt) {
KeySpec spec = new PBEKeySpec(rawPassword.toCharArray(), salt, iterations, DERIVED_KEY_SIZE);
try {
byte[] key = getSecretKeyFactory().generateSecret(spec).getEncoded();
return Base64.encodeBytes(key);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("Credential could not be encoded", e);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
private byte[] getSalt() {
byte[] buffer = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(buffer);
return buffer;
}
private SecretKeyFactory getSecretKeyFactory() {
try {
return SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("PBKDF2 algorithm not found", e);
}
}
}

View file

@ -34,6 +34,7 @@ import org.keycloak.common.Version;
import org.keycloak.common.util.Base64;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.FederatedIdentityModel;
@ -458,7 +459,7 @@ public class ExportUtils {
* @return fully exported user representation
*/
public static UserRepresentation exportUser(KeycloakSession session, RealmModel realm, UserModel user) {
UserRepresentation userRep = ModelToRepresentation.toRepresentation(user);
UserRepresentation userRep = ModelToRepresentation.toRepresentation(session, realm, user);
// Social links
Set<FederatedIdentityModel> socialLinks = session.users().getFederatedIdentities(user, realm);
@ -499,9 +500,9 @@ public class ExportUtils {
}
// Credentials
List<UserCredentialValueModel> creds = user.getCredentialsDirectly();
List<CredentialModel> creds = session.userCredentialManager().getStoredCredentials(realm, user);
List<CredentialRepresentation> credReps = new ArrayList<CredentialRepresentation>();
for (UserCredentialValueModel cred : creds) {
for (CredentialModel cred : creds) {
CredentialRepresentation credRep = exportCredential(cred);
credReps.add(credRep);
}
@ -545,7 +546,7 @@ public class ExportUtils {
return socialLinkRep;
}
public static CredentialRepresentation exportCredential(UserCredentialValueModel userCred) {
public static CredentialRepresentation exportCredential(CredentialModel userCred) {
CredentialRepresentation credRep = new CredentialRepresentation();
credRep.setType(userCred.getType());
credRep.setDevice(userCred.getDevice());
@ -556,6 +557,7 @@ public class ExportUtils {
credRep.setAlgorithm(userCred.getAlgorithm());
credRep.setDigits(userCred.getDigits());
credRep.setCreatedDate(userCred.getCreatedDate());
credRep.setConfig(userCred.getConfig());
return credRep;
}

View file

@ -41,7 +41,7 @@ public class TotpBean {
private final boolean enabled;
public TotpBean(KeycloakSession session, RealmModel realm, UserModel user) {
this.enabled = session.users().configuredForCredentialType(realm.getOTPPolicy().getType(), realm, user);
this.enabled = session.userCredentialManager().isConfiguredFor(realm, user, realm.getOTPPolicy().getType());
this.totpSecret = HmacOTP.generateSecret(20);
this.totpSecretEncoded = TotpUtils.encode(totpSecret);

View file

@ -279,7 +279,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
switch (page) {
case LOGIN_CONFIG_TOTP:
attributes.put("totp", new TotpBean(realm, user));
attributes.put("totp", new TotpBean(session, realm, user));
break;
case LOGIN_UPDATE_PROFILE:
UpdateProfileContext userCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);

View file

@ -16,6 +16,8 @@
*/
package org.keycloak.forms.login.freemarker.model;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.Base32;
@ -36,9 +38,8 @@ public class TotpBean {
private final String totpSecretQrCode;
private final boolean enabled;
public TotpBean(RealmModel realm, UserModel user) {
this.enabled = user.isOtpEnabled();
public TotpBean(KeycloakSession session, RealmModel realm, UserModel user) {
this.enabled = session.userCredentialManager().isConfiguredFor(realm, user, CredentialModel.OTP);
this.totpSecret = HmacOTP.generateSecret(20);
this.totpSecretEncoded = TotpUtils.encode(totpSecret);
this.totpSecretQrCode = TotpUtils.qrCode(totpSecret, realm, user);

View file

@ -97,7 +97,7 @@ public class HttpBasicAuthenticator implements AuthenticatorFactory {
if (user != null) {
String password = usernameAndPassword[1];
boolean valid = context.getSession().users().validCredentials(context.getSession(), realm, user, UserCredentialModel.password(password));
boolean valid = context.getSession().userCredentialManager().isValid(realm, user, UserCredentialModel.password(password));
if (valid) {
context.getClientSession().setAuthenticatedUser(user);

View file

@ -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());
}

View file

@ -97,7 +97,7 @@ public interface ServicesLogger extends BasicLogger {
@Message(id=12, value="Failed to delete '%s'")
void failedToDeleteFile(String fileName);
@LogMessage(level = DEBUG)
@LogMessage(level = WARN)
@Message(id=13, value="Failed authentication")
void failedAuthentication(@Cause Throwable t);

View file

@ -96,7 +96,7 @@ public class ApplianceBootstrap {
UserCredentialModel usrCredModel = new UserCredentialModel();
usrCredModel.setType(UserCredentialModel.PASSWORD);
usrCredModel.setValue(password);
session.users().updateCredential(realm, adminUser, usrCredModel);
session.userCredentialManager().updateCredential(realm, adminUser, usrCredModel);
RoleModel adminRole = realm.getRole(AdminRoles.ADMIN);
adminUser.grantRole(adminRole);

View file

@ -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;
}

Some files were not shown because too many files have changed in this diff Show more