credential refactoring
This commit is contained in:
parent
84f5c0926b
commit
7209a95dce
106 changed files with 1590 additions and 1379 deletions
|
@ -17,6 +17,10 @@
|
||||||
|
|
||||||
package org.keycloak.representations.idm;
|
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>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
|
@ -45,6 +49,7 @@ public class CredentialRepresentation {
|
||||||
private Integer digits;
|
private Integer digits;
|
||||||
private Integer period;
|
private Integer period;
|
||||||
private Long createdDate;
|
private Long createdDate;
|
||||||
|
private MultivaluedHashMap<String, String> config;
|
||||||
|
|
||||||
// only used when updating a credential. Might set required action
|
// only used when updating a credential. Might set required action
|
||||||
protected Boolean temporary;
|
protected Boolean temporary;
|
||||||
|
@ -144,4 +149,12 @@ public class CredentialRepresentation {
|
||||||
public void setCreatedDate(Long createdDate) {
|
public void setCreatedDate(Long createdDate) {
|
||||||
this.createdDate = createdDate;
|
this.createdDate = createdDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MultivaluedHashMap<String, String> getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfig(MultivaluedHashMap<String, String> config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,5 +55,15 @@
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<finalName>authenticator-required-action-example</finalName>
|
<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>
|
||||||
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.models.AuthenticatorConfigModel;
|
import org.keycloak.models.AuthenticatorConfigModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserCredentialValueModel;
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.services.util.CookieHelper;
|
import org.keycloak.services.util.CookieHelper;
|
||||||
|
@ -96,15 +97,10 @@ public class SecretQuestionAuthenticator implements Authenticator {
|
||||||
protected boolean validateAnswer(AuthenticationFlowContext context) {
|
protected boolean validateAnswer(AuthenticationFlowContext context) {
|
||||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
String secret = formData.getFirst("secret_answer");
|
String secret = formData.getFirst("secret_answer");
|
||||||
UserCredentialValueModel cred = null;
|
UserCredentialModel input = new UserCredentialModel();
|
||||||
for (UserCredentialValueModel model : context.getUser().getCredentialsDirectly()) {
|
input.setType(SecretQuestionCredentialProvider.SECRET_QUESTION);
|
||||||
if (model.getType().equals(CREDENTIAL_TYPE)) {
|
input.setValue(secret);
|
||||||
cred = model;
|
return context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(), input);
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cred.getValue().equals(secret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -114,7 +110,7 @@ public class SecretQuestionAuthenticator implements Authenticator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
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
|
@Override
|
||||||
|
|
|
@ -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.toMillis(Time.currentTime()));
|
||||||
|
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) {
|
||||||
|
List<CredentialModel> creds = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
|
||||||
|
if (!creds.isEmpty()) user.getCachedWith().put(CACHE_KEY, creds.get(0));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ package org.keycloak.examples.authenticator;
|
||||||
|
|
||||||
import org.keycloak.authentication.RequiredActionContext;
|
import org.keycloak.authentication.RequiredActionContext;
|
||||||
import org.keycloak.authentication.RequiredActionProvider;
|
import org.keycloak.authentication.RequiredActionProvider;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserCredentialValueModel;
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -45,10 +46,10 @@ public class SecretQuestionRequiredAction implements RequiredActionProvider {
|
||||||
@Override
|
@Override
|
||||||
public void processAction(RequiredActionContext context) {
|
public void processAction(RequiredActionContext context) {
|
||||||
String answer = (context.getHttpRequest().getDecodedFormParameters().getFirst("secret_answer"));
|
String answer = (context.getHttpRequest().getDecodedFormParameters().getFirst("secret_answer"));
|
||||||
UserCredentialValueModel model = new UserCredentialValueModel();
|
UserCredentialModel input = new UserCredentialModel();
|
||||||
model.setValue(answer);
|
input.setType(SecretQuestionCredentialProvider.SECRET_QUESTION);
|
||||||
model.setType(SecretQuestionAuthenticator.CREDENTIAL_TYPE);
|
input.setValue(answer);
|
||||||
context.getUser().updateCredentialDirectly(model);
|
context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), input);
|
||||||
context.success();
|
context.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.examples.authenticator.SecretQuestionCredentialProviderFactory
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.examples.federation.properties;
|
package org.keycloak.examples.federation.properties;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -146,48 +147,29 @@ public abstract class BasePropertiesFederationProvider implements UserFederation
|
||||||
return properties.containsKey(local.getUsername());
|
return properties.containsKey(local.getUsername());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* hardcoded to only return PASSWORD
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Set<String> getSupportedCredentialTypes(UserModel user) {
|
|
||||||
return supportedCredentialTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getSupportedCredentialTypes() {
|
public Set<String> getSupportedCredentialTypes() {
|
||||||
return supportedCredentialTypes;
|
return supportedCredentialTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
|
||||||
for (UserCredentialModel cred : input) {
|
if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) return false;
|
||||||
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
|
||||||
|
UserCredentialModel cred = (UserCredentialModel)input;
|
||||||
String password = properties.getProperty(user.getUsername());
|
String password = properties.getProperty(user.getUsername());
|
||||||
if (password == null) return false;
|
if (password == null) return false;
|
||||||
return password.equals(cred.getValue());
|
return password.equals(cred.getValue());
|
||||||
} else {
|
|
||||||
return false; // invalid cred type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
|
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
|
||||||
for (UserCredentialModel cred : input) {
|
return getSupportedCredentialTypes().contains(credentialType);
|
||||||
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;
|
@Override
|
||||||
|
public boolean supportsCredentialType(String credentialType) {
|
||||||
|
return getSupportedCredentialTypes().contains(credentialType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -17,8 +17,10 @@
|
||||||
|
|
||||||
package org.keycloak.examples.federation.properties;
|
package org.keycloak.examples.federation.properties;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
@ -80,6 +82,13 @@ public class ClasspathPropertiesFederationProvider extends BasePropertiesFederat
|
||||||
throw new IllegalStateException("Remove not supported");
|
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) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,10 @@
|
||||||
|
|
||||||
package org.keycloak.examples.federation.properties;
|
package org.keycloak.examples.federation.properties;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
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) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,19 +39,4 @@ public class ReadonlyUserModelProxy extends UserModelDelegate {
|
||||||
throw new IllegalStateException("Username is readonly");
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,15 +70,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
|
||||||
return keycloakId;
|
return keycloakId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateCredential(UserCredentialModel cred) {
|
|
||||||
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
|
||||||
entity.setPassword(cred.getValue());
|
|
||||||
} else {
|
|
||||||
super.updateCredential(cred);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSingleAttribute(String name, String value) {
|
public void setSingleAttribute(String name, String value) {
|
||||||
if (name.equals("phone")) {
|
if (name.equals("phone")) {
|
||||||
|
|
|
@ -26,11 +26,14 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
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.KerberosUsernamePasswordAuthenticator;
|
||||||
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelReadOnlyException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
@ -40,6 +43,7 @@ import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.common.constants.KerberosConstants;
|
import org.keycloak.common.constants.KerberosConstants;
|
||||||
import org.keycloak.services.managers.UserManager;
|
import org.keycloak.services.managers.UserManager;
|
||||||
|
import org.keycloak.storage.adapter.AbstractUserAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @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));
|
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
|
@Override
|
||||||
public Set<String> getSupportedCredentialTypes() {
|
public Set<String> getSupportedCredentialTypes() {
|
||||||
Set<String> supportedCredTypes = new HashSet<String>();
|
Set<String> supportedCredTypes = new HashSet<String>();
|
||||||
|
@ -175,16 +154,38 @@ public class KerberosFederationProvider implements UserFederationProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
||||||
for (UserCredentialModel cred : input) {
|
if (!(input instanceof UserCredentialModel) || !CredentialModel.PASSWORD.equals(input.getType())) return false;
|
||||||
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
if (kerberosConfig.getEditMode() == EditMode.READ_ONLY) {
|
||||||
return validPassword(user.getUsername(), cred.getValue());
|
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 {
|
} else {
|
||||||
return false; // invalid cred type
|
return false; // invalid cred type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean validPassword(String username, String password) {
|
protected boolean validPassword(String username, String password) {
|
||||||
if (kerberosConfig.isAllowPasswordAuthentication()) {
|
if (kerberosConfig.isAllowPasswordAuthentication()) {
|
||||||
|
@ -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
|
@Override
|
||||||
public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential) {
|
public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential) {
|
||||||
if (credential.getType().equals(UserCredentialModel.KERBEROS)) {
|
if (credential.getType().equals(UserCredentialModel.KERBEROS)) {
|
||||||
|
|
|
@ -34,12 +34,4 @@ public class ReadOnlyKerberosUserModelDelegate extends UserModelDelegate {
|
||||||
this.provider = provider;
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
package org.keycloak.federation.ldap;
|
package org.keycloak.federation.ldap;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
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.KerberosUsernamePasswordAuthenticator;
|
||||||
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
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.kerberos.LDAPProviderKerberosConfig;
|
||||||
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
|
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
|
||||||
import org.keycloak.federation.ldap.mappers.LDAPMappersComparator;
|
import org.keycloak.federation.ldap.mappers.LDAPMappersComparator;
|
||||||
|
import org.keycloak.federation.ldap.mappers.PasswordUpdated;
|
||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
|
import org.keycloak.models.ModelReadOnlyException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
@ -73,6 +78,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
protected LDAPIdentityStore ldapIdentityStore;
|
protected LDAPIdentityStore ldapIdentityStore;
|
||||||
protected EditMode editMode;
|
protected EditMode editMode;
|
||||||
protected LDAPProviderKerberosConfig kerberosConfig;
|
protected LDAPProviderKerberosConfig kerberosConfig;
|
||||||
|
protected PasswordUpdated updater;
|
||||||
|
|
||||||
protected final Set<String> supportedCredentialTypes = new HashSet<>();
|
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() {
|
public KeycloakSession getSession() {
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
@ -139,20 +149,6 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
return proxied;
|
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
|
@Override
|
||||||
public Set<String> getSupportedCredentialTypes() {
|
public Set<String> getSupportedCredentialTypes() {
|
||||||
return new HashSet<String>(this.supportedCredentialTypes);
|
return new HashSet<String>(this.supportedCredentialTypes);
|
||||||
|
@ -409,20 +405,47 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
||||||
for (UserCredentialModel cred : input) {
|
if (!CredentialModel.PASSWORD.equals(input.getType()) || ! (input instanceof UserCredentialModel)) return false;
|
||||||
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
if (editMode == EditMode.READ_ONLY) {
|
||||||
return validPassword(realm, user, cred.getValue());
|
throw new ModelReadOnlyException("Federated storage is not writable");
|
||||||
} else {
|
|
||||||
return false; // invalid cred type
|
} 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;
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
|
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
|
||||||
return validCredentials(realm, user, Arrays.asList(input));
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
@Override
|
||||||
|
|
|
@ -50,14 +50,6 @@ public class ReadonlyLDAPUserModelDelegate extends UserModelDelegate implements
|
||||||
throw new ModelReadOnlyException("Federated storage is not writable");
|
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
|
@Override
|
||||||
public void setEmail(String email) {
|
public void setEmail(String email) {
|
||||||
throw new ModelReadOnlyException("Federated storage is not writable");
|
throw new ModelReadOnlyException("Federated storage is not writable");
|
||||||
|
|
|
@ -40,20 +40,4 @@ public class WritableLDAPUserModelDelegate extends UserModelDelegate implements
|
||||||
this.ldapObject = ldapObject;
|
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* 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.federation.ldap.mappers;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface PasswordUpdated {
|
||||||
|
void passwordUpdated(UserModel user, LDAPObject ldapUser, CredentialInput input);
|
||||||
|
}
|
|
@ -25,10 +25,12 @@ import java.util.regex.Pattern;
|
||||||
import javax.naming.AuthenticationException;
|
import javax.naming.AuthenticationException;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
||||||
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper;
|
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper;
|
||||||
|
import org.keycloak.federation.ldap.mappers.PasswordUpdated;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.models.RealmModel;
|
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>
|
* @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);
|
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) {
|
public MSADUserAccountControlMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) {
|
||||||
super(mapperModel, ldapProvider, realm);
|
super(mapperModel, ldapProvider, realm);
|
||||||
|
ldapProvider.setUpdater(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
@Override
|
||||||
public UserModel proxy(LDAPObject ldapUser, UserModel delegate) {
|
public UserModel proxy(LDAPObject ldapUser, UserModel delegate) {
|
||||||
return new MSADUserModelDelegate(delegate, ldapUser);
|
return new MSADUserModelDelegate(delegate, ldapUser);
|
||||||
|
@ -135,6 +158,22 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
|
||||||
return e;
|
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 {
|
public class MSADUserModelDelegate extends UserModelDelegate {
|
||||||
|
|
||||||
|
@ -151,7 +190,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
|
||||||
|
|
||||||
if (getPwdLastSet() > 0) {
|
if (getPwdLastSet() > 0) {
|
||||||
// Merge KC and MSAD
|
// Merge KC and MSAD
|
||||||
return kcEnabled && !getUserAccountControl().has(UserAccountControl.ACCOUNTDISABLE);
|
return kcEnabled && !getUserAccountControl(ldapUser).has(UserAccountControl.ACCOUNTDISABLE);
|
||||||
} else {
|
} 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
|
// 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;
|
return kcEnabled;
|
||||||
|
@ -166,44 +205,14 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
|
||||||
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && getPwdLastSet() > 0) {
|
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && getPwdLastSet() > 0) {
|
||||||
logger.debugf("Going to propagate enabled=%s for ldapUser '%s' to MSAD", enabled, ldapUser.getDn().toString());
|
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) {
|
if (enabled) {
|
||||||
control.remove(UserAccountControl.ACCOUNTDISABLE);
|
control.remove(UserAccountControl.ACCOUNTDISABLE);
|
||||||
} else {
|
} else {
|
||||||
control.add(UserAccountControl.ACCOUNTDISABLE);
|
control.add(UserAccountControl.ACCOUNTDISABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUserAccountControl(control);
|
updateUserAccountControl(ldapUser, 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,7 +252,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
|
||||||
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && RequiredAction.UPDATE_PASSWORD.toString().equals(action)) {
|
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && RequiredAction.UPDATE_PASSWORD.toString().equals(action)) {
|
||||||
|
|
||||||
// Don't set pwdLastSet in MSAD when it is new user
|
// 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)) {
|
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());
|
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();
|
Set<String> requiredActions = super.getRequiredActions();
|
||||||
|
|
||||||
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE) {
|
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 = new HashSet<>(requiredActions);
|
||||||
requiredActions.add(RequiredAction.UPDATE_PASSWORD.toString());
|
requiredActions.add(RequiredAction.UPDATE_PASSWORD.toString());
|
||||||
return requiredActions;
|
return requiredActions;
|
||||||
|
@ -276,20 +285,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
|
||||||
return pwdLastSet == null ? 0 : Long.parseLong(pwdLastSet);
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,22 +54,6 @@ public class ReadonlySSSDUserModelDelegate extends UserModelDelegate implements
|
||||||
throw new ModelReadOnlyException("Federated storage is not writable");
|
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
|
@Override
|
||||||
public void setEmail(String email) {
|
public void setEmail(String email) {
|
||||||
throw new ModelReadOnlyException("Federated storage is not writable");
|
throw new ModelReadOnlyException("Federated storage is not writable");
|
||||||
|
|
|
@ -19,11 +19,14 @@ package org.keycloak.federation.sssd;
|
||||||
|
|
||||||
import org.freedesktop.dbus.Variant;
|
import org.freedesktop.dbus.Variant;
|
||||||
import org.jboss.logging.Logger;
|
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.api.Sssd;
|
||||||
import org.keycloak.federation.sssd.impl.PAMAuthenticator;
|
import org.keycloak.federation.sssd.impl.PAMAuthenticator;
|
||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelReadOnlyException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
@ -162,30 +165,39 @@ public class SSSDFederationProvider implements UserFederationProvider {
|
||||||
return Sssd.getRawAttribute(attributes.get("mail")).equalsIgnoreCase(local.getEmail());
|
return Sssd.getRawAttribute(attributes.get("mail")).equalsIgnoreCase(local.getEmail());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getSupportedCredentialTypes(UserModel user) {
|
|
||||||
return supportedCredentialTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getSupportedCredentialTypes() {
|
public Set<String> getSupportedCredentialTypes() {
|
||||||
return supportedCredentialTypes;
|
return supportedCredentialTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
||||||
for (UserCredentialModel cred : input) {
|
if (!(input instanceof UserCredentialModel) || !CredentialModel.PASSWORD.equals(input.getType())) return false;
|
||||||
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
throw new ModelReadOnlyException("Federated storage is not writable");
|
||||||
PAMAuthenticator pam = factory.createPAMAuthenticator(user.getUsername(), cred.getValue());
|
|
||||||
return (pam.authenticate() != null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
|
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
|
||||||
return validCredentials(realm, user, Arrays.asList(input));
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
@Override
|
||||||
|
|
|
@ -253,24 +253,6 @@ public class UserAdapter implements CachedUserModel {
|
||||||
updated.setOtpEnabled(totp);
|
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
|
@Override
|
||||||
public String getFederationLink() {
|
public String getFederationLink() {
|
||||||
if (updated != null) return updated.getFederationLink();
|
if (updated != null) return updated.getFederationLink();
|
||||||
|
|
|
@ -627,16 +627,6 @@ public class UserCacheSession implements UserCache {
|
||||||
return getDelegate().removeFederatedIdentity(realm, user, socialProvider);
|
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
|
@Override
|
||||||
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
||||||
return getDelegate().validCredentials(session, realm, input);
|
return getDelegate().validCredentials(session, realm, input);
|
||||||
|
|
|
@ -44,7 +44,6 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
||||||
private String lastName;
|
private String lastName;
|
||||||
private String email;
|
private String email;
|
||||||
private boolean emailVerified;
|
private boolean emailVerified;
|
||||||
private List<UserCredentialValueModel> credentials = new LinkedList<>();
|
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private boolean totp;
|
private boolean totp;
|
||||||
private String federationLink;
|
private String federationLink;
|
||||||
|
@ -66,7 +65,6 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
||||||
this.attributes.putAll(user.getAttributes());
|
this.attributes.putAll(user.getAttributes());
|
||||||
this.email = user.getEmail();
|
this.email = user.getEmail();
|
||||||
this.emailVerified = user.isEmailVerified();
|
this.emailVerified = user.isEmailVerified();
|
||||||
this.credentials.addAll(user.getCredentialsDirectly());
|
|
||||||
this.enabled = user.isEnabled();
|
this.enabled = user.isEnabled();
|
||||||
this.totp = user.isOtpEnabled();
|
this.totp = user.isOtpEnabled();
|
||||||
this.federationLink = user.getFederationLink();
|
this.federationLink = user.getFederationLink();
|
||||||
|
@ -111,10 +109,6 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
||||||
return emailVerified;
|
return emailVerified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<UserCredentialValueModel> getCredentials() {
|
|
||||||
return credentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
|
@ -710,16 +710,6 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
||||||
return (entity != null) ? new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName(), entity.getToken()) : null;
|
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
|
@Override
|
||||||
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
||||||
// Not supported yet
|
// Not supported yet
|
||||||
|
@ -803,7 +793,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
||||||
entity.setUser(userRef);
|
entity.setUser(userRef);
|
||||||
em.persist(entity);
|
em.persist(entity);
|
||||||
MultivaluedHashMap<String, String> config = cred.getConfig();
|
MultivaluedHashMap<String, String> config = cred.getConfig();
|
||||||
if (config != null || !config.isEmpty()) {
|
if (config != null && !config.isEmpty()) {
|
||||||
|
|
||||||
for (String key : config.keySet()) {
|
for (String key : config.keySet()) {
|
||||||
List<String> values = config.getList(key);
|
List<String> values = config.getList(key);
|
||||||
|
@ -850,6 +840,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
||||||
model.setCreatedDate(entity.getCreatedDate());
|
model.setCreatedDate(entity.getCreatedDate());
|
||||||
model.setDevice(entity.getDevice());
|
model.setDevice(entity.getDevice());
|
||||||
model.setDigits(entity.getDigits());
|
model.setDigits(entity.getDigits());
|
||||||
|
model.setHashIterations(entity.getHashIterations());
|
||||||
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
|
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
|
||||||
model.setConfig(config);
|
model.setConfig(config);
|
||||||
for (CredentialAttributeEntity attr : entity.getCredentialAttributes()) {
|
for (CredentialAttributeEntity attr : entity.getCredentialAttributes()) {
|
||||||
|
|
|
@ -315,199 +315,6 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
|
||||||
user.setTotp(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
|
@Override
|
||||||
public Set<GroupModel> getGroups() {
|
public Set<GroupModel> getGroups() {
|
||||||
// we query ids only as the group might be cached and following the @ManyToOne will result in a load
|
// we query ids only as the group might be cached and following the @ManyToOne will result in a load
|
||||||
|
|
|
@ -47,6 +47,7 @@ import org.keycloak.storage.federated.UserGroupMembershipFederatedStorage;
|
||||||
import org.keycloak.storage.federated.UserRequiredActionsFederatedStorage;
|
import org.keycloak.storage.federated.UserRequiredActionsFederatedStorage;
|
||||||
import org.keycloak.storage.federated.UserRoleMappingsFederatedStorage;
|
import org.keycloak.storage.federated.UserRoleMappingsFederatedStorage;
|
||||||
import org.keycloak.storage.jpa.entity.BrokerLinkEntity;
|
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.FederatedUserAttributeEntity;
|
||||||
import org.keycloak.storage.jpa.entity.FederatedUserConsentEntity;
|
import org.keycloak.storage.jpa.entity.FederatedUserConsentEntity;
|
||||||
import org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity;
|
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
|
@Override
|
||||||
public void setAttribute(RealmModel realm, UserModel user, String name, List<String> values) {
|
public void setAttribute(RealmModel realm, UserModel user, String name, List<String> values) {
|
||||||
|
createIndex(realm, user);
|
||||||
deleteAttribute(realm, user, name);
|
deleteAttribute(realm, user, name);
|
||||||
em.flush();
|
em.flush();
|
||||||
for (String value : values) {
|
for (String value : values) {
|
||||||
|
@ -126,6 +142,7 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSingleAttribute(RealmModel realm, UserModel user, String name, String value) {
|
public void setSingleAttribute(RealmModel realm, UserModel user, String name, String value) {
|
||||||
|
createIndex(realm, user);
|
||||||
deleteAttribute(realm, user, name);
|
deleteAttribute(realm, user, name);
|
||||||
em.flush();
|
em.flush();
|
||||||
persistAttributeValue(realm, user, name, value);
|
persistAttributeValue(realm, user, name, value);
|
||||||
|
@ -133,6 +150,7 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeAttribute(RealmModel realm, UserModel user, String name) {
|
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);
|
deleteAttribute(realm, user, name);
|
||||||
em.flush();
|
em.flush();
|
||||||
}
|
}
|
||||||
|
@ -180,6 +198,7 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel link) {
|
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel link) {
|
||||||
|
createIndex(realm, user);
|
||||||
BrokerLinkEntity entity = new BrokerLinkEntity();
|
BrokerLinkEntity entity = new BrokerLinkEntity();
|
||||||
entity.setRealmId(realm.getId());
|
entity.setRealmId(realm.getId());
|
||||||
entity.setUserId(user.getId());
|
entity.setUserId(user.getId());
|
||||||
|
@ -211,6 +230,7 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel model) {
|
public void updateFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel model) {
|
||||||
|
createIndex(realm, user);
|
||||||
BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, model.getIdentityProvider());
|
BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, model.getIdentityProvider());
|
||||||
if (entity == null) return;
|
if (entity == null) return;
|
||||||
entity.setBrokerUserName(model.getUserName());
|
entity.setBrokerUserName(model.getUserName());
|
||||||
|
@ -243,6 +263,7 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
|
public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
|
||||||
|
createIndex(realm, user);
|
||||||
String clientId = consent.getClient().getId();
|
String clientId = consent.getClient().getId();
|
||||||
|
|
||||||
FederatedUserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
|
FederatedUserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
|
||||||
|
@ -285,6 +306,7 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
|
public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
|
||||||
|
createIndex(realm, user);
|
||||||
String clientId = consent.getClient().getId();
|
String clientId = consent.getClient().getId();
|
||||||
|
|
||||||
FederatedUserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
|
FederatedUserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
|
||||||
|
@ -432,12 +454,14 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel cred) {
|
public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel cred) {
|
||||||
|
createIndex(realm, user);
|
||||||
FederatedCredentials.updateCredential(session, this, realm, user, cred);
|
FederatedCredentials.updateCredential(session, this, realm, user, cred);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred) {
|
public void updateCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred) {
|
||||||
|
createIndex(realm, user);
|
||||||
FederatedUserCredentialEntity entity = null;
|
FederatedUserCredentialEntity entity = null;
|
||||||
if (cred.getId() != null) entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
|
if (cred.getId() != null) entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
|
||||||
boolean newEntity = false;
|
boolean newEntity = false;
|
||||||
|
@ -490,6 +514,7 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
@Override
|
@Override
|
||||||
public void joinGroup(RealmModel realm, UserModel user, GroupModel group) {
|
public void joinGroup(RealmModel realm, UserModel user, GroupModel group) {
|
||||||
if (isMemberOf(realm, user, group)) return;
|
if (isMemberOf(realm, user, group)) return;
|
||||||
|
createIndex(realm, user);
|
||||||
FederatedUserGroupMembershipEntity entity = new FederatedUserGroupMembershipEntity();
|
FederatedUserGroupMembershipEntity entity = new FederatedUserGroupMembershipEntity();
|
||||||
entity.setUserId(user.getId());
|
entity.setUserId(user.getId());
|
||||||
entity.setStorageProviderId(StorageId.resolveProviderId(user));
|
entity.setStorageProviderId(StorageId.resolveProviderId(user));
|
||||||
|
@ -553,6 +578,7 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addRequiredAction(RealmModel realm, UserModel user, String action) {
|
public void addRequiredAction(RealmModel realm, UserModel user, String action) {
|
||||||
|
createIndex(realm, user);
|
||||||
if (user.getRequiredActions().contains(action)) return;
|
if (user.getRequiredActions().contains(action)) return;
|
||||||
FederatedUserRequiredActionEntity entity = new FederatedUserRequiredActionEntity();
|
FederatedUserRequiredActionEntity entity = new FederatedUserRequiredActionEntity();
|
||||||
entity.setUserId(user.getId());
|
entity.setUserId(user.getId());
|
||||||
|
@ -576,6 +602,7 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
@Override
|
@Override
|
||||||
public void grantRole(RealmModel realm, UserModel user, RoleModel role) {
|
public void grantRole(RealmModel realm, UserModel user, RoleModel role) {
|
||||||
if (user.hasRole(role)) return;
|
if (user.hasRole(role)) return;
|
||||||
|
createIndex(realm, user);
|
||||||
FederatedUserRoleMappingEntity entity = new FederatedUserRoleMappingEntity();
|
FederatedUserRoleMappingEntity entity = new FederatedUserRoleMappingEntity();
|
||||||
entity.setUserId(user.getId());
|
entity.setUserId(user.getId());
|
||||||
entity.setStorageProviderId(StorageId.resolveProviderId(user));
|
entity.setStorageProviderId(StorageId.resolveProviderId(user));
|
||||||
|
@ -645,6 +672,7 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
|
public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
|
||||||
FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
|
FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
|
||||||
if (entity == null) return;
|
if (entity == null) return;
|
||||||
|
createIndex(realm, user);
|
||||||
entity.setAlgorithm(cred.getAlgorithm());
|
entity.setAlgorithm(cred.getAlgorithm());
|
||||||
entity.setCounter(cred.getCounter());
|
entity.setCounter(cred.getCounter());
|
||||||
entity.setCreatedDate(cred.getCreatedDate());
|
entity.setCreatedDate(cred.getCreatedDate());
|
||||||
|
@ -696,6 +724,7 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
|
public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
|
||||||
|
createIndex(realm, user);
|
||||||
FederatedUserCredentialEntity entity = new FederatedUserCredentialEntity();
|
FederatedUserCredentialEntity entity = new FederatedUserCredentialEntity();
|
||||||
String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
|
String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
|
||||||
entity.setId(id);
|
entity.setId(id);
|
||||||
|
@ -714,7 +743,7 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
entity.setStorageProviderId(StorageId.resolveProviderId(user));
|
entity.setStorageProviderId(StorageId.resolveProviderId(user));
|
||||||
em.persist(entity);
|
em.persist(entity);
|
||||||
MultivaluedHashMap<String, String> config = cred.getConfig();
|
MultivaluedHashMap<String, String> config = cred.getConfig();
|
||||||
if (config != null || !config.isEmpty()) {
|
if (config != null && !config.isEmpty()) {
|
||||||
|
|
||||||
for (String key : config.keySet()) {
|
for (String key : config.keySet()) {
|
||||||
List<String> values = config.getList(key);
|
List<String> values = config.getList(key);
|
||||||
|
@ -761,6 +790,7 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
model.setCreatedDate(entity.getCreatedDate());
|
model.setCreatedDate(entity.getCreatedDate());
|
||||||
model.setDevice(entity.getDevice());
|
model.setDevice(entity.getDevice());
|
||||||
model.setDigits(entity.getDigits());
|
model.setDigits(entity.getDigits());
|
||||||
|
model.setHashIterations(entity.getHashIterations());
|
||||||
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
|
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
|
||||||
model.setConfig(config);
|
model.setConfig(config);
|
||||||
for (FederatedUserCredentialAttributeEntity attr : entity.getCredentialAttributes()) {
|
for (FederatedUserCredentialAttributeEntity attr : entity.getCredentialAttributes()) {
|
||||||
|
@ -805,6 +835,15 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
return toModel(results.get(0));
|
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
|
@Override
|
||||||
public void preRemove(RealmModel realm) {
|
public void preRemove(RealmModel realm) {
|
||||||
int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm")
|
int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm")
|
||||||
|
@ -827,6 +866,8 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
num = em.createNamedQuery("deleteFederatedUserGroupMembershipByRealm")
|
num = em.createNamedQuery("deleteFederatedUserGroupMembershipByRealm")
|
||||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
|
num = em.createNamedQuery("deleteFederatedUsersByRealm")
|
||||||
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -855,6 +896,10 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
.setParameter("realmId", realm.getId())
|
.setParameter("realmId", realm.getId())
|
||||||
.setParameter("link", link.getId())
|
.setParameter("link", link.getId())
|
||||||
.executeUpdate();
|
.executeUpdate();
|
||||||
|
num = em.createNamedQuery("deleteFederatedUsersByRealmAndLink")
|
||||||
|
.setParameter("realmId", realm.getId())
|
||||||
|
.setParameter("link", link.getId())
|
||||||
|
.executeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -924,6 +969,10 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
.setParameter("userId", user.getId())
|
.setParameter("userId", user.getId())
|
||||||
.setParameter("realmId", realm.getId())
|
.setParameter("realmId", realm.getId())
|
||||||
.executeUpdate();
|
.executeUpdate();
|
||||||
|
em.createNamedQuery("deleteFederatedUserByUser")
|
||||||
|
.setParameter("userId", user.getId())
|
||||||
|
.setParameter("realmId", realm.getId())
|
||||||
|
.executeUpdate();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -961,6 +1010,9 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
em.createNamedQuery("deleteFederatedUserRoleMappingsByStorageProvider")
|
em.createNamedQuery("deleteFederatedUserRoleMappingsByStorageProvider")
|
||||||
.setParameter("storageProviderId", model.getId())
|
.setParameter("storageProviderId", model.getId())
|
||||||
.executeUpdate();
|
.executeUpdate();
|
||||||
|
em.createNamedQuery("deleteFederatedUsersByStorageProvider")
|
||||||
|
.setParameter("storageProviderId", model.getId())
|
||||||
|
.executeUpdate();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
39
model/jpa/src/main/resources/META-INF/jpa-changelog-2.3.0.xml
Executable file
39
model/jpa/src/main/resources/META-INF/jpa-changelog-2.3.0.xml
Executable file
|
@ -0,0 +1,39 @@
|
||||||
|
<?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"/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
|
@ -36,4 +36,5 @@
|
||||||
<include file="META-INF/jpa-changelog-authz-master.xml"/>
|
<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.1.0.xml"/>
|
||||||
<include file="META-INF/jpa-changelog-2.2.0.xml"/>
|
<include file="META-INF/jpa-changelog-2.2.0.xml"/>
|
||||||
|
<include file="META-INF/jpa-changelog-2.3.0.xml"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
|
|
||||||
<!-- User Federation Storage -->
|
<!-- User Federation Storage -->
|
||||||
<class>org.keycloak.storage.jpa.entity.BrokerLinkEntity</class>
|
<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.FederatedUserAttributeEntity</class>
|
||||||
<class>org.keycloak.storage.jpa.entity.FederatedUserConsentEntity</class>
|
<class>org.keycloak.storage.jpa.entity.FederatedUserConsentEntity</class>
|
||||||
<class>org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity</class>
|
<class>org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity</class>
|
||||||
|
|
|
@ -513,16 +513,6 @@ public class MongoUserProvider implements UserProvider {
|
||||||
getMongoStore().updateEntities(MongoUserConsentEntity.class, query, pull, invocationContext);
|
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
|
@Override
|
||||||
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
||||||
// Not supported yet
|
// Not supported yet
|
||||||
|
|
|
@ -254,205 +254,6 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
||||||
updateUser();
|
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() {
|
protected void updateUser() {
|
||||||
super.updateMongoEntity();
|
super.updateMongoEntity();
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.component.ComponentValidationException;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
import org.keycloak.storage.UserStorageProvider;
|
import org.keycloak.storage.UserStorageProvider;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -34,16 +35,7 @@ import java.util.Set;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface CredentialProviderFactory<T extends CredentialProvider> extends ComponentFactory<T, CredentialProvider> {
|
public interface CredentialProviderFactory<T extends CredentialProvider> extends ProviderFactory<CredentialProvider> {
|
||||||
/**
|
|
||||||
* called per Keycloak transaction.
|
|
||||||
*
|
|
||||||
* @param session
|
|
||||||
* @param model
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
T create(KeycloakSession session, ComponentModel model);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the name of the provider and will be showed in the admin console as an option.
|
* 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 {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -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> {
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ public interface MigrationModel {
|
||||||
/**
|
/**
|
||||||
* Must have the form of major.minor.micro as the version is parsed and numbers are compared
|
* Must have the form of major.minor.micro as the version is parsed and numbers are compared
|
||||||
*/
|
*/
|
||||||
String LATEST_VERSION = "2.1.0";
|
String LATEST_VERSION = "2.3.0";
|
||||||
|
|
||||||
String getStoredVersion();
|
String getStoredVersion();
|
||||||
void setStoredVersion(String version);
|
void setStoredVersion(String version);
|
||||||
|
|
|
@ -27,11 +27,68 @@ import java.util.Set;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface UserCredentialManager extends UserCredentialStore {
|
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);
|
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);
|
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);
|
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);
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,10 +58,6 @@ public class UserFederationManager implements UserProvider {
|
||||||
return registerWithFederation(realm, user);
|
return registerWithFederation(realm, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected UserFederationProvider getFederationProvider(UserFederationProviderModel model) {
|
|
||||||
return KeycloakModelUtils.getFederationProviderInstance(session, model);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel addUser(RealmModel realm, String username) {
|
public UserModel addUser(RealmModel realm, String username) {
|
||||||
UserModel user = session.userStorage().addUser(realm, username.toLowerCase());
|
UserModel user = session.userStorage().addUser(realm, username.toLowerCase());
|
||||||
|
@ -81,7 +77,11 @@ public class UserFederationManager implements UserProvider {
|
||||||
return user;
|
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;
|
if (user.getFederationLink() == null) return null;
|
||||||
for (UserFederationProviderModel fed : realm.getUserFederationProviders()) {
|
for (UserFederationProviderModel fed : realm.getUserFederationProviders()) {
|
||||||
if (fed.getId().equals(user.getFederationLink())) {
|
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())) {
|
if (managedUsers.containsKey(user.getId())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -488,84 +488,6 @@ public class UserFederationManager implements UserProvider {
|
||||||
session.userStorage().preRemove(protocolMapper);
|
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
|
@Override
|
||||||
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
||||||
List<UserFederationProviderModel> fedProviderModels = realm.getUserFederationProviders();
|
List<UserFederationProviderModel> fedProviderModels = realm.getUserFederationProviders();
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialInputUpdater;
|
||||||
|
import org.keycloak.credential.CredentialInputValidator;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -30,7 +32,8 @@ import java.util.Set;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface UserFederationProvider extends Provider {
|
@Deprecated
|
||||||
|
public interface UserFederationProvider extends Provider, CredentialInputValidator, CredentialInputUpdater {
|
||||||
|
|
||||||
public static final String USERNAME = UserModel.USERNAME;
|
public static final String USERNAME = UserModel.USERNAME;
|
||||||
public static final String EMAIL = UserModel.EMAIL;
|
public static final String EMAIL = UserModel.EMAIL;
|
||||||
|
@ -166,14 +169,6 @@ public interface UserFederationProvider extends Provider {
|
||||||
*/
|
*/
|
||||||
boolean isValid(RealmModel realm, UserModel local);
|
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,
|
* 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).
|
* who is going to authenticate (For example Kerberos authentication).
|
||||||
|
@ -182,18 +177,6 @@ public interface UserFederationProvider extends Provider {
|
||||||
*/
|
*/
|
||||||
Set<String> getSupportedCredentialTypes();
|
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
|
* Validate credentials of unknown user. The authenticated user is recognized based on provided credentials and returned back in CredentialValidationOutput
|
||||||
* @param realm
|
* @param realm
|
||||||
|
|
|
@ -112,12 +112,6 @@ public interface UserModel extends RoleMapperModel {
|
||||||
|
|
||||||
void setOtpEnabled(boolean totp);
|
void setOtpEnabled(boolean totp);
|
||||||
|
|
||||||
void updateCredential(UserCredentialModel cred);
|
|
||||||
|
|
||||||
List<UserCredentialValueModel> getCredentialsDirectly();
|
|
||||||
|
|
||||||
void updateCredentialDirectly(UserCredentialValueModel cred);
|
|
||||||
|
|
||||||
Set<GroupModel> getGroups();
|
Set<GroupModel> getGroups();
|
||||||
void joinGroup(GroupModel group);
|
void joinGroup(GroupModel group);
|
||||||
void leaveGroup(GroupModel group);
|
void leaveGroup(GroupModel group);
|
||||||
|
|
|
@ -35,7 +35,6 @@ import java.util.Set;
|
||||||
public interface UserProvider extends Provider,
|
public interface UserProvider extends Provider,
|
||||||
UserLookupProvider,
|
UserLookupProvider,
|
||||||
UserQueryProvider,
|
UserQueryProvider,
|
||||||
UserCredentialValidatorProvider,
|
|
||||||
UserRegistrationProvider {
|
UserRegistrationProvider {
|
||||||
// Note: The reason there are so many query methods here is for layering a cache on top of an persistent KeycloakSession
|
// Note: The reason there are so many query methods here is for layering a cache on top of an persistent KeycloakSession
|
||||||
|
|
||||||
|
@ -79,7 +78,6 @@ public interface UserProvider extends Provider,
|
||||||
void preRemove(ProtocolMapperModel protocolMapper);
|
void preRemove(ProtocolMapperModel protocolMapper);
|
||||||
|
|
||||||
|
|
||||||
boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input);
|
|
||||||
CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input);
|
CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,45 +38,6 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public class CredentialValidation {
|
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) {
|
public static boolean validPasswordToken(RealmModel realm, UserModel user, String encodedPasswordToken) {
|
||||||
try {
|
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) {
|
public static boolean validOTP(RealmModel realm, String token, String secret) {
|
||||||
OTPPolicy policy = realm.getOTPPolicy();
|
OTPPolicy policy = realm.getOTPPolicy();
|
||||||
if (policy.getType().equals(UserCredentialModel.TOTP)) {
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.authorization.store.ScopeStore;
|
||||||
import org.keycloak.authorization.store.StoreFactory;
|
import org.keycloak.authorization.store.StoreFactory;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.hash.Pbkdf2PasswordHashProvider;
|
import org.keycloak.hash.Pbkdf2PasswordHashProvider;
|
||||||
import org.keycloak.migration.migrators.MigrationUtils;
|
import org.keycloak.migration.migrators.MigrationUtils;
|
||||||
import org.keycloak.models.ClientTemplateModel;
|
import org.keycloak.models.ClientTemplateModel;
|
||||||
|
@ -1326,7 +1327,7 @@ public class RepresentationToModel {
|
||||||
user.addRequiredAction(UserModel.RequiredAction.valueOf(requiredAction));
|
user.addRequiredAction(UserModel.RequiredAction.valueOf(requiredAction));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createCredentials(userRep, user);
|
createCredentials(userRep, session, newRealm, user);
|
||||||
if (userRep.getFederatedIdentities() != null) {
|
if (userRep.getFederatedIdentities() != null) {
|
||||||
for (FederatedIdentityRepresentation identity : userRep.getFederatedIdentities()) {
|
for (FederatedIdentityRepresentation identity : userRep.getFederatedIdentities()) {
|
||||||
FederatedIdentityModel mappingModel = new FederatedIdentityModel(identity.getIdentityProvider(), identity.getUserId(), identity.getUserName());
|
FederatedIdentityModel mappingModel = new FederatedIdentityModel(identity.getIdentityProvider(), identity.getUserId(), identity.getUserName());
|
||||||
|
@ -1361,21 +1362,21 @@ public class RepresentationToModel {
|
||||||
return user;
|
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) {
|
if (userRep.getCredentials() != null) {
|
||||||
for (CredentialRepresentation cred : userRep.getCredentials()) {
|
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
|
// 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) {
|
if (cred.getValue() != null) {
|
||||||
UserCredentialModel plainTextCred = convertCredential(cred);
|
UserCredentialModel plainTextCred = convertCredential(cred);
|
||||||
user.updateCredential(plainTextCred);
|
session.userCredentialManager().updateCredential(realm, user, plainTextCred);
|
||||||
} else {
|
} else {
|
||||||
UserCredentialValueModel hashedCred = new UserCredentialValueModel();
|
CredentialModel hashedCred = new CredentialModel();
|
||||||
hashedCred.setType(cred.getType());
|
hashedCred.setType(cred.getType());
|
||||||
hashedCred.setDevice(cred.getDevice());
|
hashedCred.setDevice(cred.getDevice());
|
||||||
if (cred.getHashIterations() != null) hashedCred.setHashIterations(cred.getHashIterations());
|
if (cred.getHashIterations() != null) hashedCred.setHashIterations(cred.getHashIterations());
|
||||||
|
@ -1414,7 +1415,7 @@ public class RepresentationToModel {
|
||||||
hashedCred.setPeriod(30);
|
hashedCred.setPeriod(30);
|
||||||
}
|
}
|
||||||
hashedCred.setCreatedDate(cred.getCreatedDate());
|
hashedCred.setCreatedDate(cred.getCreatedDate());
|
||||||
user.updateCredentialDirectly(hashedCred);
|
session.userCredentialManager().createCredential(realm, user, hashedCred);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -170,21 +170,6 @@ public class UserModelDelegate implements UserModel {
|
||||||
delegate.setOtpEnabled(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
|
@Override
|
||||||
public Set<RoleModel> getRealmRoleMappings() {
|
public Set<RoleModel> getRealmRoleMappings() {
|
||||||
return delegate.getRealmRoleMappings();
|
return delegate.getRealmRoleMappings();
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.policy;
|
||||||
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.PasswordPolicy;
|
import org.keycloak.models.PasswordPolicy;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -36,9 +37,9 @@ public class DefaultPasswordPolicyManagerProvider implements PasswordPolicyManag
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PolicyError validate(UserModel user, String password) {
|
public PolicyError validate(RealmModel realm, UserModel user, String password) {
|
||||||
for (PasswordPolicyProvider p : getProviders(session)) {
|
for (PasswordPolicyProvider p : getProviders(realm, session)) {
|
||||||
PolicyError policyError = p.validate(user, password);
|
PolicyError policyError = p.validate(realm, user, password);
|
||||||
if (policyError != null) {
|
if (policyError != null) {
|
||||||
return policyError;
|
return policyError;
|
||||||
}
|
}
|
||||||
|
@ -62,8 +63,13 @@ public class DefaultPasswordPolicyManagerProvider implements PasswordPolicyManag
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<PasswordPolicyProvider> getProviders(KeycloakSession session) {
|
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<>();
|
LinkedList<PasswordPolicyProvider> list = new LinkedList<>();
|
||||||
PasswordPolicy policy = session.getContext().getRealm().getPasswordPolicy();
|
PasswordPolicy policy = realm.getPasswordPolicy();
|
||||||
for (String id : policy.getPolicies()) {
|
for (String id : policy.getPolicies()) {
|
||||||
PasswordPolicyProvider provider = session.getProvider(PasswordPolicyProvider.class, id);
|
PasswordPolicyProvider provider = session.getProvider(PasswordPolicyProvider.class, id);
|
||||||
list.add(provider);
|
list.add(provider);
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.policy;
|
package org.keycloak.policy;
|
||||||
|
|
||||||
import org.keycloak.models.KeycloakContext;
|
import org.keycloak.models.KeycloakContext;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,7 +47,7 @@ public class DigitsPasswordPolicyProvider implements PasswordPolicyProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PolicyError validate(UserModel user, String password) {
|
public PolicyError validate(RealmModel realm, UserModel user, String password) {
|
||||||
return validate(user.getUsername(), password);
|
return validate(user.getUsername(), password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.policy;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,7 +54,7 @@ public class ForceExpiredPasswordPolicyProviderFactory implements PasswordPolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PolicyError validate(UserModel user, String password) {
|
public PolicyError validate(RealmModel realm, UserModel user, String password) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.policy;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,7 +55,7 @@ public class HashAlgorithmPasswordPolicyProviderFactory implements PasswordPolic
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PolicyError validate(UserModel user, String password) {
|
public PolicyError validate(RealmModel realm, UserModel user, String password) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.policy;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,7 +51,7 @@ public class HashIterationsPasswordPolicyProviderFactory implements PasswordPoli
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PolicyError validate(UserModel user, String password) {
|
public PolicyError validate(RealmModel realm, UserModel user, String password) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,13 @@
|
||||||
|
|
||||||
package org.keycloak.policy;
|
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.hash.PasswordHashManager;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.PasswordPolicy;
|
import org.keycloak.models.PasswordPolicy;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserCredentialValueModel;
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
@ -34,6 +38,7 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public class HistoryPasswordPolicyProvider implements PasswordPolicyProvider {
|
public class HistoryPasswordPolicyProvider implements PasswordPolicyProvider {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(HistoryPasswordPolicyProvider.class);
|
||||||
private static final String ERROR_MESSAGE = "invalidPasswordHistoryMessage";
|
private static final String ERROR_MESSAGE = "invalidPasswordHistoryMessage";
|
||||||
|
|
||||||
private KeycloakSession session;
|
private KeycloakSession session;
|
||||||
|
@ -48,63 +53,30 @@ public class HistoryPasswordPolicyProvider implements PasswordPolicyProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PolicyError validate(UserModel user, String password) {
|
public PolicyError validate(RealmModel realm, UserModel user, String password) {
|
||||||
PasswordPolicy policy = session.getContext().getRealm().getPasswordPolicy();
|
PasswordPolicy policy = session.getContext().getRealm().getPasswordPolicy();
|
||||||
int passwordHistoryPolicyValue = policy.getPolicyConfig(HistoryPasswordPolicyProviderFactory.ID);
|
int passwordHistoryPolicyValue = policy.getPolicyConfig(HistoryPasswordPolicyProviderFactory.ID);
|
||||||
if (passwordHistoryPolicyValue != -1) {
|
if (passwordHistoryPolicyValue != -1) {
|
||||||
UserCredentialValueModel cred = getCredentialValueModel(user, UserCredentialModel.PASSWORD);
|
List<CredentialModel> storedPasswords = session.userCredentialManager().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD);
|
||||||
if (cred != null) {
|
for (CredentialModel cred : storedPasswords) {
|
||||||
if(PasswordHashManager.verify(session, policy, password, cred)) {
|
PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, cred.getAlgorithm());
|
||||||
|
if (hash == null) continue;
|
||||||
|
if (hash.verify(password, cred)) {
|
||||||
return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue);
|
return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
List<CredentialModel> passwordHistory = session.userCredentialManager().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD_HISTORY);
|
||||||
List<UserCredentialValueModel> passwordExpiredCredentials = getCredentialValueModels(user, passwordHistoryPolicyValue - 1,
|
for (CredentialModel cred : passwordHistory) {
|
||||||
UserCredentialModel.PASSWORD_HISTORY);
|
PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, cred.getAlgorithm());
|
||||||
for (UserCredentialValueModel credential : passwordExpiredCredentials) {
|
if (hash.verify(password, cred)) {
|
||||||
if (PasswordHashManager.verify(session, policy, password, credential)) {
|
|
||||||
return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue);
|
return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
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
|
@Override
|
||||||
public Object parseConfig(String value) {
|
public Object parseConfig(String value) {
|
||||||
return value != null ? Integer.parseInt(value) : HistoryPasswordPolicyProviderFactory.DEFAULT_VALUE;
|
return value != null ? Integer.parseInt(value) : HistoryPasswordPolicyProviderFactory.DEFAULT_VALUE;
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.policy;
|
package org.keycloak.policy;
|
||||||
|
|
||||||
import org.keycloak.models.KeycloakContext;
|
import org.keycloak.models.KeycloakContext;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,7 +41,7 @@ public class LengthPasswordPolicyProvider implements PasswordPolicyProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PolicyError validate(UserModel user, String password) {
|
public PolicyError validate(RealmModel realm, UserModel user, String password) {
|
||||||
return validate(user.getUsername(), password);
|
return validate(user.getUsername(), password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.policy;
|
package org.keycloak.policy;
|
||||||
|
|
||||||
import org.keycloak.models.KeycloakContext;
|
import org.keycloak.models.KeycloakContext;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,7 +47,7 @@ public class LowerCasePasswordPolicyProvider implements PasswordPolicyProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PolicyError validate(UserModel user, String password) {
|
public PolicyError validate(RealmModel realm, UserModel user, String password) {
|
||||||
return validate(user.getUsername(), password);
|
return validate(user.getUsername(), password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.policy;
|
package org.keycloak.policy;
|
||||||
|
|
||||||
import org.keycloak.models.KeycloakContext;
|
import org.keycloak.models.KeycloakContext;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,7 +43,7 @@ public class NotUsernamePasswordPolicyProvider implements PasswordPolicyProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PolicyError validate(UserModel user, String password) {
|
public PolicyError validate(RealmModel realm, UserModel user, String password) {
|
||||||
return validate(user.getUsername(), password);
|
return validate(user.getUsername(), password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.policy;
|
package org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ import org.keycloak.provider.Provider;
|
||||||
*/
|
*/
|
||||||
public interface PasswordPolicyManagerProvider extends 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);
|
PolicyError validate(String user, String password);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.policy;
|
package org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@ public interface PasswordPolicyProvider extends Provider {
|
||||||
String STRING_CONFIG_TYPE = "String";
|
String STRING_CONFIG_TYPE = "String";
|
||||||
String INT_CONFIG_TYPE = "int";
|
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);
|
PolicyError validate(String user, String password);
|
||||||
Object parseConfig(String value);
|
Object parseConfig(String value);
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.policy;
|
package org.keycloak.policy;
|
||||||
|
|
||||||
import org.keycloak.models.KeycloakContext;
|
import org.keycloak.models.KeycloakContext;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
@ -47,7 +48,7 @@ public class RegexPatternsPasswordPolicyProvider implements PasswordPolicyProvid
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PolicyError validate(UserModel user, String password) {
|
public PolicyError validate(RealmModel realm, UserModel user, String password) {
|
||||||
return validate(user.getUsername(), password);
|
return validate(user.getUsername(), password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.policy;
|
package org.keycloak.policy;
|
||||||
|
|
||||||
import org.keycloak.models.KeycloakContext;
|
import org.keycloak.models.KeycloakContext;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,7 +47,7 @@ public class SpecialCharsPasswordPolicyProvider implements PasswordPolicyProvide
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PolicyError validate(UserModel user, String password) {
|
public PolicyError validate(RealmModel realm, UserModel user, String password) {
|
||||||
return validate(user.getUsername(), password);
|
return validate(user.getUsername(), password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.policy;
|
package org.keycloak.policy;
|
||||||
|
|
||||||
import org.keycloak.models.KeycloakContext;
|
import org.keycloak.models.KeycloakContext;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,7 +47,7 @@ public class UpperCasePasswordPolicyProvider implements PasswordPolicyProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PolicyError validate(UserModel user, String password) {
|
public PolicyError validate(RealmModel realm, UserModel user, String password) {
|
||||||
return validate(user.getUsername(), password);
|
return validate(user.getUsername(), password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -386,23 +386,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
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -399,23 +399,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
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -27,6 +27,9 @@ import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
|
@ -40,6 +43,8 @@ public interface UserFederatedStorageProvider extends Provider,
|
||||||
UserRequiredActionsFederatedStorage,
|
UserRequiredActionsFederatedStorage,
|
||||||
UserRoleMappingsFederatedStorage {
|
UserRoleMappingsFederatedStorage {
|
||||||
|
|
||||||
|
List<String> getStoredUsers(RealmModel realm, int first, int max);
|
||||||
|
|
||||||
void preRemove(RealmModel realm);
|
void preRemove(RealmModel realm);
|
||||||
|
|
||||||
void preRemove(RealmModel realm, UserFederationProviderModel link);
|
void preRemove(RealmModel realm, UserFederationProviderModel link);
|
||||||
|
|
|
@ -63,3 +63,5 @@ org.keycloak.protocol.oidc.TokenIntrospectionSpi
|
||||||
org.keycloak.policy.PasswordPolicySpi
|
org.keycloak.policy.PasswordPolicySpi
|
||||||
org.keycloak.policy.PasswordPolicyManagerSpi
|
org.keycloak.policy.PasswordPolicyManagerSpi
|
||||||
org.keycloak.transaction.TransactionManagerLookupSpi
|
org.keycloak.transaction.TransactionManagerLookupSpi
|
||||||
|
org.keycloak.credential.hash.PasswordHashSpi
|
||||||
|
org.keycloak.credential.CredentialSpi
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.authentication.authenticators.browser;
|
||||||
import org.keycloak.authentication.AbstractFormAuthenticator;
|
import org.keycloak.authentication.AbstractFormAuthenticator;
|
||||||
import org.keycloak.authentication.AuthenticationFlowError;
|
import org.keycloak.authentication.AuthenticationFlowError;
|
||||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
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) {
|
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);
|
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
|
||||||
credentials.add(UserCredentialModel.password(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) {
|
if (!valid) {
|
||||||
context.getEvent().user(user);
|
context.getEvent().user(user);
|
||||||
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||||
|
|
|
@ -58,15 +58,14 @@ public class OTPFormAuthenticator extends AbstractUsernameFormAuthenticator impl
|
||||||
context.resetFlow();
|
context.resetFlow();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<UserCredentialModel> credentials = new LinkedList<>();
|
|
||||||
String password = inputData.getFirst(CredentialRepresentation.TOTP);
|
String password = inputData.getFirst(CredentialRepresentation.TOTP);
|
||||||
if (password == null) {
|
if (password == null) {
|
||||||
Response challengeResponse = challenge(context, null);
|
Response challengeResponse = challenge(context, null);
|
||||||
context.challenge(challengeResponse);
|
context.challenge(challengeResponse);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
credentials.add(UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), password));
|
boolean valid = context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(),
|
||||||
boolean valid = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), context.getUser(), credentials);
|
UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), password));
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
context.getEvent().user(context.getUser())
|
context.getEvent().user(context.getUser())
|
||||||
.error(Errors.INVALID_USER_CREDENTIALS);
|
.error(Errors.INVALID_USER_CREDENTIALS);
|
||||||
|
@ -91,7 +90,7 @@ public class OTPFormAuthenticator extends AbstractUsernameFormAuthenticator impl
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
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
|
@Override
|
||||||
|
|
|
@ -65,8 +65,7 @@ public class ValidateOTP extends AbstractDirectGrantAuthenticator {
|
||||||
context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse);
|
context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
credentials.add(UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), otp));
|
boolean valid = context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(), UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), otp));
|
||||||
boolean valid = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), context.getUser(), credentials);
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
context.getEvent().user(context.getUser());
|
context.getEvent().user(context.getUser());
|
||||||
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
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) {
|
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
|
@Override
|
||||||
|
|
|
@ -46,8 +46,7 @@ public class ValidatePassword extends AbstractDirectGrantAuthenticator {
|
||||||
MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
|
MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
List<UserCredentialModel> credentials = new LinkedList<>();
|
List<UserCredentialModel> credentials = new LinkedList<>();
|
||||||
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
|
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
|
||||||
credentials.add(UserCredentialModel.password(password));
|
boolean valid = context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(), UserCredentialModel.password(password));
|
||||||
boolean valid = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), context.getUser(), credentials);
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
context.getEvent().user(context.getUser());
|
context.getEvent().user(context.getUser());
|
||||||
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||||
|
|
|
@ -39,7 +39,7 @@ public class ResetOTP extends AbstractSetRequiredActionAuthenticator {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean configuredFor(AuthenticationFlowContext context) {
|
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
|
@Override
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class ResetPassword extends AbstractSetRequiredActionAuthenticator {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean configuredFor(AuthenticationFlowContext context) {
|
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
|
@Override
|
||||||
|
|
|
@ -97,7 +97,7 @@ public class RegistrationPassword implements FormAction, FormActionFactory {
|
||||||
credentials.setValue(password);
|
credentials.setValue(password);
|
||||||
UserModel user = context.getUser();
|
UserModel user = context.getUser();
|
||||||
try {
|
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) {
|
} catch (Exception me) {
|
||||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,10 @@ import org.keycloak.Config;
|
||||||
import org.keycloak.authentication.RequiredActionContext;
|
import org.keycloak.authentication.RequiredActionContext;
|
||||||
import org.keycloak.authentication.RequiredActionFactory;
|
import org.keycloak.authentication.RequiredActionFactory;
|
||||||
import org.keycloak.authentication.RequiredActionProvider;
|
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.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
|
@ -50,14 +54,14 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
|
||||||
public void evaluateTriggers(RequiredActionContext context) {
|
public void evaluateTriggers(RequiredActionContext context) {
|
||||||
int daysToExpirePassword = context.getRealm().getPasswordPolicy().getDaysToExpirePassword();
|
int daysToExpirePassword = context.getRealm().getPasswordPolicy().getDaysToExpirePassword();
|
||||||
if(daysToExpirePassword != -1) {
|
if(daysToExpirePassword != -1) {
|
||||||
for (UserCredentialValueModel entity : context.getUser().getCredentialsDirectly()) {
|
PasswordCredentialProvider passwordProvider = (PasswordCredentialProvider)context.getSession().getProvider(CredentialProvider.class, PasswordCredentialProviderFactory.PROVIDER_ID);
|
||||||
if (entity.getType().equals(UserCredentialModel.PASSWORD)) {
|
CredentialModel password = passwordProvider.getPassword(context.getRealm(), context.getUser());
|
||||||
|
if (password != null) {
|
||||||
if(entity.getCreatedDate() == null) {
|
if(password.getCreatedDate() == null) {
|
||||||
context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
context.getUser().addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
logger.debug("User is required to update password");
|
logger.debug("User is required to update password");
|
||||||
} else {
|
} else {
|
||||||
long timeElapsed = Time.toMillis(Time.currentTime()) - entity.getCreatedDate();
|
long timeElapsed = Time.toMillis(Time.currentTime()) - password.getCreatedDate();
|
||||||
long timeToExpire = TimeUnit.DAYS.toMillis(daysToExpirePassword);
|
long timeToExpire = TimeUnit.DAYS.toMillis(daysToExpirePassword);
|
||||||
|
|
||||||
if(timeElapsed > timeToExpire) {
|
if(timeElapsed > timeToExpire) {
|
||||||
|
@ -65,8 +69,6 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
|
||||||
logger.debug("User is required to update password");
|
logger.debug("User is required to update password");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,7 +109,7 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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();
|
context.success();
|
||||||
} catch (ModelException me) {
|
} catch (ModelException me) {
|
||||||
errorEvent.detail(Details.REASON, me.getMessage()).error(Errors.PASSWORD_REJECTED);
|
errorEvent.detail(Details.REASON, me.getMessage()).error(Errors.PASSWORD_REJECTED);
|
||||||
|
|
|
@ -77,14 +77,14 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
|
||||||
UserCredentialModel credentials = new UserCredentialModel();
|
UserCredentialModel credentials = new UserCredentialModel();
|
||||||
credentials.setType(context.getRealm().getOTPPolicy().getType());
|
credentials.setType(context.getRealm().getOTPPolicy().getType());
|
||||||
credentials.setValue(totpSecret);
|
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
|
// if type is HOTP, to update counter we execute validation based on supplied token
|
||||||
UserCredentialModel cred = new UserCredentialModel();
|
UserCredentialModel cred = new UserCredentialModel();
|
||||||
cred.setType(context.getRealm().getOTPPolicy().getType());
|
cred.setType(context.getRealm().getOTPPolicy().getType());
|
||||||
cred.setValue(totp);
|
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.getUser().setOtpEnabled(true);
|
||||||
context.success();
|
context.success();
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.keycloak.credential;
|
package org.keycloak.credential;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.OTPPolicy;
|
import org.keycloak.models.OTPPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -34,15 +35,15 @@ import java.util.List;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class LocalOTPCredentialManager implements CredentialInputValidator, CredentialInputUpdater, OnUserCache {
|
public class OTPCredentialProvider implements CredentialProvider, CredentialInputValidator, CredentialInputUpdater, OnUserCache {
|
||||||
private static final Logger logger = Logger.getLogger(LocalOTPCredentialManager.class);
|
private static final Logger logger = Logger.getLogger(OTPCredentialProvider.class);
|
||||||
|
|
||||||
protected KeycloakSession session;
|
protected KeycloakSession session;
|
||||||
|
|
||||||
protected List<CredentialModel> getCachedCredentials(UserModel user, String type) {
|
protected List<CredentialModel> getCachedCredentials(UserModel user, String type) {
|
||||||
if (!(user instanceof CachedUserModel)) return Collections.EMPTY_LIST;
|
if (!(user instanceof CachedUserModel)) return Collections.EMPTY_LIST;
|
||||||
CachedUserModel cached = (CachedUserModel)user;
|
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;
|
if (rtn == null) return Collections.EMPTY_LIST;
|
||||||
return rtn;
|
return rtn;
|
||||||
}
|
}
|
||||||
|
@ -54,11 +55,11 @@ public class LocalOTPCredentialManager implements CredentialInputValidator, Cred
|
||||||
@Override
|
@Override
|
||||||
public void onCache(RealmModel realm, CachedUserModel user) {
|
public void onCache(RealmModel realm, CachedUserModel user) {
|
||||||
List<CredentialModel> creds = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP);
|
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;
|
this.session = session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,10 +89,11 @@ public class LocalOTPCredentialManager implements CredentialInputValidator, Cred
|
||||||
model.setDigits(policy.getDigits());
|
model.setDigits(policy.getDigits());
|
||||||
model.setCounter(policy.getInitialCounter());
|
model.setCounter(policy.getInitialCounter());
|
||||||
model.setAlgorithm(policy.getAlgorithm());
|
model.setAlgorithm(policy.getAlgorithm());
|
||||||
model.setType(policy.getType());
|
model.setType(input.getType());
|
||||||
model.setValue(inputModel.getValue());
|
model.setValue(inputModel.getValue());
|
||||||
model.setDevice(inputModel.getDevice());
|
model.setDevice(inputModel.getDevice());
|
||||||
model.setPeriod(policy.getPeriod());
|
model.setPeriod(policy.getPeriod());
|
||||||
|
model.setCreatedDate(Time.toMillis(Time.currentTime()));
|
||||||
if (model.getId() == null) {
|
if (model.getId() == null) {
|
||||||
getCredentialStore().createCredential(realm, user, model);
|
getCredentialStore().createCredential(realm, user, model);
|
||||||
} else {
|
} else {
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.toMillis(Time.currentTime());
|
||||||
|
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) {
|
||||||
|
List<CredentialModel> passwords = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD);
|
||||||
|
if (passwords != null) {
|
||||||
|
user.getCachedWith().put(PASSWORD_CACHE_KEY, passwords);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,17 +22,24 @@ import org.keycloak.component.PrioritizedComponentModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialManager;
|
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.UserModel;
|
||||||
import org.keycloak.models.cache.CachedUserModel;
|
import org.keycloak.models.cache.CachedUserModel;
|
||||||
import org.keycloak.models.cache.OnUserCache;
|
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.StorageId;
|
||||||
import org.keycloak.storage.UserStorageManager;
|
import org.keycloak.storage.UserStorageManager;
|
||||||
import org.keycloak.storage.UserStorageProvider;
|
import org.keycloak.storage.UserStorageProvider;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @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);
|
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
|
@Override
|
||||||
public boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs) {
|
public boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs) {
|
||||||
|
@ -108,38 +119,47 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
// <deprecate>
|
||||||
if (toValidate.isEmpty()) return true;
|
UserFederationProvider link = session.users().getFederationLink(realm, user);
|
||||||
|
if (link != null) {
|
||||||
List<ComponentModel> components = getCredentialProviderComponents(realm);
|
session.users().validateUser(realm, user);
|
||||||
for (ComponentModel component : components) {
|
|
||||||
CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
|
|
||||||
if (!Types.supports(CredentialInputValidator.class, factory, CredentialProviderFactory.class)) continue;
|
|
||||||
Iterator<CredentialInput> it = toValidate.iterator();
|
Iterator<CredentialInput> it = toValidate.iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
CredentialInput input = it.next();
|
CredentialInput input = it.next();
|
||||||
CredentialInputValidator validator = (CredentialInputValidator)session.getAttribute(component.getId());
|
if (link.supportsCredentialType(input.getType())
|
||||||
if (validator == null) {
|
&& link.isValid(realm, user, input)) {
|
||||||
validator = (CredentialInputValidator)factory.create(session, component);
|
|
||||||
session.setAttribute(component.getId(), validator);
|
|
||||||
}
|
|
||||||
if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) {
|
|
||||||
it.remove();
|
it.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// </deprecate>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toValidate.isEmpty()) return true;
|
||||||
|
|
||||||
|
List<CredentialInputValidator> credentialProviders = getCredentialProviders(realm, CredentialInputValidator.class);
|
||||||
|
for (CredentialInputValidator validator : credentialProviders) {
|
||||||
|
Iterator<CredentialInput> it = toValidate.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
CredentialInput input = it.next();
|
||||||
|
if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) {
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
return toValidate.isEmpty();
|
return toValidate.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<ComponentModel> getCredentialProviderComponents(RealmModel realm) {
|
protected <T> List<T> getCredentialProviders(RealmModel realm, Class<T> type) {
|
||||||
List<ComponentModel> components = realm.getComponents(realm.getId(), CredentialProvider.class.getName());
|
List<T> list = new LinkedList<T>();
|
||||||
if (components.isEmpty()) return components;
|
for (ProviderFactory f : session.getKeycloakSessionFactory().getProviderFactories(CredentialProvider.class)) {
|
||||||
List<ComponentModel> copy = new LinkedList<>();
|
if (!Types.supports(CredentialInputUpdater.class, f, CredentialProviderFactory.class)) continue;
|
||||||
copy.addAll(components);
|
list.add((T)session.getProvider(CredentialProvider.class, f.getId()));
|
||||||
Collections.sort(copy, PrioritizedComponentModel.comparator);
|
}
|
||||||
return copy;
|
return list;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -153,22 +173,22 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
|
||||||
if (updater.updateCredential(realm, user, input)) return;
|
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);
|
List<CredentialInputUpdater> credentialProviders = getCredentialProviders(realm, CredentialInputUpdater.class);
|
||||||
for (ComponentModel component : components) {
|
for (CredentialInputUpdater updater : credentialProviders) {
|
||||||
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);
|
|
||||||
}
|
|
||||||
if (!updater.supportsCredentialType(input.getType())) continue;
|
if (!updater.supportsCredentialType(input.getType())) continue;
|
||||||
if (updater.updateCredential(realm, user, input)) return;
|
if (updater.updateCredential(realm, user, input)) return;
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void disableCredential(RealmModel realm, UserModel user, String credentialType) {
|
public void disableCredential(RealmModel realm, UserModel user, String credentialType) {
|
||||||
if (!StorageId.isLocalStorage(user)) {
|
if (!StorageId.isLocalStorage(user)) {
|
||||||
|
@ -180,21 +200,22 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
|
||||||
updater.disableCredentialType(realm, user, credentialType);
|
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;
|
if (!updater.supportsCredentialType(credentialType)) continue;
|
||||||
updater.disableCredentialType(realm, user, credentialType);
|
updater.disableCredentialType(realm, user, credentialType);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
|
public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
|
||||||
|
@ -207,39 +228,37 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
|
||||||
return true;
|
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);
|
return isConfiguredLocally(realm, user, type);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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)) {
|
if (validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCache(RealmModel realm, CachedUserModel user) {
|
public void onCache(RealmModel realm, CachedUserModel user) {
|
||||||
List<ComponentModel> components = getCredentialProviderComponents(realm);
|
List<OnUserCache> credentialProviders = getCredentialProviders(realm, OnUserCache.class);
|
||||||
for (ComponentModel component : components) {
|
for (OnUserCache validator : credentialProviders) {
|
||||||
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);
|
|
||||||
}
|
|
||||||
validator.onCache(realm, user);
|
validator.onCache(realm, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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() && PBKDF2_ALGORITHM.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ import org.keycloak.common.Version;
|
||||||
import org.keycloak.common.util.Base64;
|
import org.keycloak.common.util.Base64;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientTemplateModel;
|
import org.keycloak.models.ClientTemplateModel;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
|
@ -499,9 +500,9 @@ public class ExportUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Credentials
|
// Credentials
|
||||||
List<UserCredentialValueModel> creds = user.getCredentialsDirectly();
|
List<CredentialModel> creds = session.userCredentialManager().getStoredCredentials(realm, user);
|
||||||
List<CredentialRepresentation> credReps = new ArrayList<CredentialRepresentation>();
|
List<CredentialRepresentation> credReps = new ArrayList<CredentialRepresentation>();
|
||||||
for (UserCredentialValueModel cred : creds) {
|
for (CredentialModel cred : creds) {
|
||||||
CredentialRepresentation credRep = exportCredential(cred);
|
CredentialRepresentation credRep = exportCredential(cred);
|
||||||
credReps.add(credRep);
|
credReps.add(credRep);
|
||||||
}
|
}
|
||||||
|
@ -545,7 +546,7 @@ public class ExportUtils {
|
||||||
return socialLinkRep;
|
return socialLinkRep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CredentialRepresentation exportCredential(UserCredentialValueModel userCred) {
|
public static CredentialRepresentation exportCredential(CredentialModel userCred) {
|
||||||
CredentialRepresentation credRep = new CredentialRepresentation();
|
CredentialRepresentation credRep = new CredentialRepresentation();
|
||||||
credRep.setType(userCred.getType());
|
credRep.setType(userCred.getType());
|
||||||
credRep.setDevice(userCred.getDevice());
|
credRep.setDevice(userCred.getDevice());
|
||||||
|
@ -556,6 +557,7 @@ public class ExportUtils {
|
||||||
credRep.setAlgorithm(userCred.getAlgorithm());
|
credRep.setAlgorithm(userCred.getAlgorithm());
|
||||||
credRep.setDigits(userCred.getDigits());
|
credRep.setDigits(userCred.getDigits());
|
||||||
credRep.setCreatedDate(userCred.getCreatedDate());
|
credRep.setCreatedDate(userCred.getCreatedDate());
|
||||||
|
credRep.setConfig(userCred.getConfig());
|
||||||
return credRep;
|
return credRep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class TotpBean {
|
||||||
private final boolean enabled;
|
private final boolean enabled;
|
||||||
|
|
||||||
public TotpBean(KeycloakSession session, RealmModel realm, UserModel user) {
|
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.totpSecret = HmacOTP.generateSecret(20);
|
||||||
this.totpSecretEncoded = TotpUtils.encode(totpSecret);
|
this.totpSecretEncoded = TotpUtils.encode(totpSecret);
|
||||||
|
|
|
@ -97,7 +97,7 @@ public class HttpBasicAuthenticator implements AuthenticatorFactory {
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
String password = usernameAndPassword[1];
|
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) {
|
if (valid) {
|
||||||
context.getClientSession().setAuthenticatedUser(user);
|
context.getClientSession().setAuthenticatedUser(user);
|
||||||
|
|
|
@ -97,7 +97,7 @@ public interface ServicesLogger extends BasicLogger {
|
||||||
@Message(id=12, value="Failed to delete '%s'")
|
@Message(id=12, value="Failed to delete '%s'")
|
||||||
void failedToDeleteFile(String fileName);
|
void failedToDeleteFile(String fileName);
|
||||||
|
|
||||||
@LogMessage(level = DEBUG)
|
@LogMessage(level = WARN)
|
||||||
@Message(id=13, value="Failed authentication")
|
@Message(id=13, value="Failed authentication")
|
||||||
void failedAuthentication(@Cause Throwable t);
|
void failedAuthentication(@Cause Throwable t);
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,7 @@ public class ApplianceBootstrap {
|
||||||
UserCredentialModel usrCredModel = new UserCredentialModel();
|
UserCredentialModel usrCredModel = new UserCredentialModel();
|
||||||
usrCredModel.setType(UserCredentialModel.PASSWORD);
|
usrCredModel.setType(UserCredentialModel.PASSWORD);
|
||||||
usrCredModel.setValue(password);
|
usrCredModel.setValue(password);
|
||||||
session.users().updateCredential(realm, adminUser, usrCredModel);
|
session.userCredentialManager().updateCredential(realm, adminUser, usrCredModel);
|
||||||
|
|
||||||
RoleModel adminRole = realm.getRole(AdminRoles.ADMIN);
|
RoleModel adminRole = realm.getRole(AdminRoles.ADMIN);
|
||||||
adminUser.grantRole(adminRole);
|
adminUser.grantRole(adminRole);
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.services.resources;
|
package org.keycloak.services.resources;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.forms.account.AccountPages;
|
import org.keycloak.forms.account.AccountPages;
|
||||||
import org.keycloak.forms.account.AccountProvider;
|
import org.keycloak.forms.account.AccountProvider;
|
||||||
|
@ -81,6 +83,7 @@ import java.lang.reflect.Method;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -444,7 +447,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
csrfCheck(stateChecker);
|
csrfCheck(stateChecker);
|
||||||
|
|
||||||
UserModel user = auth.getUser();
|
UserModel user = auth.getUser();
|
||||||
user.setOtpEnabled(false);
|
session.userCredentialManager().disableCredential(realm, user, CredentialModel.OTP);
|
||||||
|
|
||||||
event.event(EventType.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
|
event.event(EventType.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
|
||||||
|
|
||||||
|
@ -564,7 +567,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
UserCredentialModel credentials = new UserCredentialModel();
|
UserCredentialModel credentials = new UserCredentialModel();
|
||||||
credentials.setType(realm.getOTPPolicy().getType());
|
credentials.setType(realm.getOTPPolicy().getType());
|
||||||
credentials.setValue(totpSecret);
|
credentials.setValue(totpSecret);
|
||||||
session.users().updateCredential(realm, user, credentials);
|
session.userCredentialManager().updateCredential(realm, user, credentials);
|
||||||
|
|
||||||
user.setOtpEnabled(true);
|
user.setOtpEnabled(true);
|
||||||
|
|
||||||
|
@ -572,7 +575,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
UserCredentialModel cred = new UserCredentialModel();
|
UserCredentialModel cred = new UserCredentialModel();
|
||||||
cred.setType(realm.getOTPPolicy().getType());
|
cred.setType(realm.getOTPPolicy().getType());
|
||||||
cred.setValue(totp);
|
cred.setValue(totp);
|
||||||
session.users().validCredentials(session, realm, user, cred);
|
session.userCredentialManager().isValid(realm, user, cred);
|
||||||
|
|
||||||
event.event(EventType.UPDATE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
|
event.event(EventType.UPDATE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
|
||||||
|
|
||||||
|
@ -624,7 +627,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
}
|
}
|
||||||
|
|
||||||
UserCredentialModel cred = UserCredentialModel.password(password);
|
UserCredentialModel cred = UserCredentialModel.password(password);
|
||||||
if (!session.users().validCredentials(session, realm, user, cred)) {
|
if (!session.userCredentialManager().isValid(realm, user, cred)) {
|
||||||
setReferrerOnPage();
|
setReferrerOnPage();
|
||||||
errorEvent.error(Errors.INVALID_USER_CREDENTIALS);
|
errorEvent.error(Errors.INVALID_USER_CREDENTIALS);
|
||||||
return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD);
|
return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD);
|
||||||
|
@ -644,7 +647,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
|
session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
|
||||||
} catch (ModelReadOnlyException mre) {
|
} catch (ModelReadOnlyException mre) {
|
||||||
setReferrerOnPage();
|
setReferrerOnPage();
|
||||||
errorEvent.error(Errors.NOT_ALLOWED);
|
errorEvent.error(Errors.NOT_ALLOWED);
|
||||||
|
@ -774,32 +777,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isPasswordSet(KeycloakSession session, RealmModel realm, UserModel user) {
|
public static boolean isPasswordSet(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
boolean passwordSet = false;
|
return session.userCredentialManager().isConfiguredFor(realm, user, CredentialModel.PASSWORD);
|
||||||
|
|
||||||
// See if password is set for user on linked UserFederationProvider
|
|
||||||
if (user.getFederationLink() != null) {
|
|
||||||
|
|
||||||
UserFederationProvider federationProvider = null;
|
|
||||||
for (UserFederationProviderModel fedProviderModel : realm.getUserFederationProviders()) {
|
|
||||||
if (fedProviderModel.getId().equals(user.getFederationLink())) {
|
|
||||||
federationProvider = KeycloakModelUtils.getFederationProviderInstance(session, fedProviderModel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (federationProvider != null) {
|
|
||||||
Set<String> supportedCreds = federationProvider.getSupportedCredentialTypes(user);
|
|
||||||
if (supportedCreds.contains(UserCredentialModel.PASSWORD)) {
|
|
||||||
passwordSet = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (UserCredentialValueModel c : user.getCredentialsDirectly()) {
|
|
||||||
if (c.getType().equals(CredentialRepresentation.PASSWORD)) {
|
|
||||||
passwordSet = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return passwordSet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] getReferrer() {
|
private String[] getReferrer() {
|
||||||
|
|
|
@ -405,7 +405,7 @@ public class KeycloakApplication extends Application {
|
||||||
} else {
|
} else {
|
||||||
UserModel user = session.users().addUser(realm, userRep.getUsername());
|
UserModel user = session.users().addUser(realm, userRep.getUsername());
|
||||||
user.setEnabled(userRep.isEnabled());
|
user.setEnabled(userRep.isEnabled());
|
||||||
RepresentationToModel.createCredentials(userRep, user);
|
RepresentationToModel.createCredentials(userRep, session, realm, user);
|
||||||
RepresentationToModel.createRoleMappings(userRep, user, realm);
|
RepresentationToModel.createRoleMappings(userRep, user, realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.jboss.resteasy.spi.NotFoundException;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.authentication.RequiredActionProvider;
|
import org.keycloak.authentication.RequiredActionProvider;
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.email.EmailException;
|
import org.keycloak.email.EmailException;
|
||||||
import org.keycloak.email.EmailTemplateProvider;
|
import org.keycloak.email.EmailTemplateProvider;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
|
@ -746,7 +747,7 @@ public class UsersResource {
|
||||||
|
|
||||||
UserCredentialModel cred = RepresentationToModel.convertCredential(pass);
|
UserCredentialModel cred = RepresentationToModel.convertCredential(pass);
|
||||||
try {
|
try {
|
||||||
session.users().updateCredential(realm, user, cred);
|
session.userCredentialManager().updateCredential(realm, user, cred);
|
||||||
} catch (IllegalStateException ise) {
|
} catch (IllegalStateException ise) {
|
||||||
throw new BadRequestException("Resetting to N old passwords is not allowed.");
|
throw new BadRequestException("Resetting to N old passwords is not allowed.");
|
||||||
} catch (ModelReadOnlyException mre) {
|
} catch (ModelReadOnlyException mre) {
|
||||||
|
@ -777,7 +778,7 @@ public class UsersResource {
|
||||||
throw new NotFoundException("User not found");
|
throw new NotFoundException("User not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
user.setOtpEnabled(false);
|
session.userCredentialManager().disableCredential(realm, user, CredentialModel.OTP);
|
||||||
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
|
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,15 +34,12 @@ import org.keycloak.models.cache.CachedUserModel;
|
||||||
import org.keycloak.models.cache.OnUserCache;
|
import org.keycloak.models.cache.OnUserCache;
|
||||||
import org.keycloak.storage.user.UserCredentialAuthenticationProvider;
|
import org.keycloak.storage.user.UserCredentialAuthenticationProvider;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.storage.user.UserCredentialValidatorProvider;
|
|
||||||
import org.keycloak.models.UserCredentialValueModel;
|
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.storage.user.UserLookupProvider;
|
import org.keycloak.storage.user.UserLookupProvider;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserProvider;
|
import org.keycloak.models.UserProvider;
|
||||||
import org.keycloak.storage.user.UserQueryProvider;
|
import org.keycloak.storage.user.UserQueryProvider;
|
||||||
import org.keycloak.storage.user.UserRegistrationProvider;
|
import org.keycloak.storage.user.UserRegistrationProvider;
|
||||||
import org.keycloak.models.utils.CredentialValidation;
|
|
||||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -532,52 +529,6 @@ public class UserStorageManager implements UserProvider, OnUserCache {
|
||||||
if (getFederatedStorage() != null) getFederatedStorage().preRemove(protocolMapper);
|
if (getFederatedStorage() != null) getFederatedStorage().preRemove(protocolMapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
|
||||||
if (StorageId.isLocalStorage(user)) {
|
|
||||||
return localStorage().validCredentials(session, realm, user, input);
|
|
||||||
}
|
|
||||||
// make sure we hit the cache here!
|
|
||||||
List<UserCredentialValueModel> userCreds = user.getCredentialsDirectly();
|
|
||||||
|
|
||||||
LinkedList<UserCredentialModel> toValidate = new LinkedList<>();
|
|
||||||
toValidate.addAll(input);
|
|
||||||
Iterator<UserCredentialModel> it = toValidate.iterator();
|
|
||||||
boolean failedStoredCredential = false;
|
|
||||||
// we allow for multiple credentials of same type, i.e. multiple OTP devices
|
|
||||||
while (it.hasNext()) {
|
|
||||||
UserCredentialModel cred = it.next();
|
|
||||||
boolean credValidated = false;
|
|
||||||
for (UserCredentialValueModel userCred : userCreds) {
|
|
||||||
if (!userCred.getType().equals(cred.getType())) continue;
|
|
||||||
if (CredentialValidation.validCredential(session, realm, user, cred)) {
|
|
||||||
credValidated = true;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
failedStoredCredential = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (credValidated) {
|
|
||||||
it.remove();
|
|
||||||
} else if (failedStoredCredential) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toValidate.isEmpty()) return true;
|
|
||||||
|
|
||||||
UserStorageProvider provider = getStorageProvider(session, realm, StorageId.resolveProviderId(user));
|
|
||||||
if (!(provider instanceof UserCredentialValidatorProvider)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return ((UserCredentialValidatorProvider)provider).validCredentials(session, realm, user, toValidate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) {
|
|
||||||
return validCredentials(session, realm, user, Arrays.asList(input));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
|
||||||
List<UserCredentialAuthenticationProvider> providers = getStorageProviders(session, realm, UserCredentialAuthenticationProvider.class);
|
List<UserCredentialAuthenticationProvider> providers = getStorageProviders(session, realm, UserCredentialAuthenticationProvider.class);
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.keycloak.credential.OTPCredentialProviderFactory
|
||||||
|
org.keycloak.credential.PasswordCredentialProviderFactory
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.credential.hash.Pbkdf2PasswordHashProvider
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.federation;
|
package org.keycloak.testsuite.federation;
|
||||||
|
|
||||||
|
import org.keycloak.credential.CredentialInput;
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -105,37 +107,49 @@ public class DummyUserFederationProvider implements UserFederationProvider {
|
||||||
return users.containsKey(username);
|
return users.containsKey(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getSupportedCredentialTypes(UserModel user) {
|
|
||||||
// Just user "test-user" is able to validate password with this federationProvider
|
|
||||||
if (user.getUsername().equals("test-user")) {
|
|
||||||
return Collections.singleton(UserCredentialModel.PASSWORD);
|
|
||||||
} else {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getSupportedCredentialTypes() {
|
public Set<String> getSupportedCredentialTypes() {
|
||||||
return Collections.singleton(UserCredentialModel.PASSWORD);
|
return Collections.singleton(UserCredentialModel.PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
||||||
if (user.getUsername().equals("test-user") && input.size() == 1) {
|
if (!(input instanceof UserCredentialModel) || !CredentialModel.PASSWORD.equals(input.getType())) return false;
|
||||||
UserCredentialModel password = input.get(0);
|
|
||||||
if (password.getType().equals(UserCredentialModel.PASSWORD)) {
|
|
||||||
return "secret".equals(password.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
|
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
|
||||||
return validCredentials(realm, user, Arrays.asList(input));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsCredentialType(String credentialType) {
|
||||||
|
return getSupportedCredentialTypes().contains(credentialType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
|
||||||
|
if (!CredentialModel.PASSWORD.equals(credentialType)) return false;
|
||||||
|
|
||||||
|
if (user.getUsername().equals("test-user")) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
|
||||||
|
if (user.getUsername().equals("test-user")) {
|
||||||
|
UserCredentialModel password = (UserCredentialModel)input;
|
||||||
|
if (password.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||||
|
return "secret".equals(password.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential) {
|
public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential) {
|
||||||
return CredentialValidationOutput.failed();
|
return CredentialValidationOutput.failed();
|
||||||
|
|
|
@ -578,7 +578,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
if (realm == null) return false;
|
if (realm == null) return false;
|
||||||
UserProvider userProvider = session.getProvider(UserProvider.class);
|
UserProvider userProvider = session.getProvider(UserProvider.class);
|
||||||
UserModel user = userProvider.getUserByUsername(userName, realm);
|
UserModel user = userProvider.getUserByUsername(userName, realm);
|
||||||
return userProvider.validCredentials(session, realm, user, UserCredentialModel.password(password));
|
return session.userCredentialManager().isValid(realm, user, UserCredentialModel.password(password));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.keycloak.testsuite.pages.LoginTotpPage;
|
||||||
import org.keycloak.testsuite.pages.RegisterPage;
|
import org.keycloak.testsuite.pages.RegisterPage;
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
@ -202,7 +203,9 @@ public class BruteForceTest extends TestRealmKeycloakTest {
|
||||||
events.clear();
|
events.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
} @Test
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testGrantMissingOtp() throws Exception {
|
public void testGrantMissingOtp() throws Exception {
|
||||||
{
|
{
|
||||||
String totpSecret = totp.generateTOTP("totpSecret");
|
String totpSecret = totp.generateTOTP("totpSecret");
|
||||||
|
@ -337,7 +340,7 @@ public class BruteForceTest extends TestRealmKeycloakTest {
|
||||||
.error(Errors.USER_TEMPORARILY_DISABLED)
|
.error(Errors.USER_TEMPORARILY_DISABLED)
|
||||||
.detail(Details.USERNAME, username)
|
.detail(Details.USERNAME, username)
|
||||||
.removeDetail(Details.CONSENT);
|
.removeDetail(Details.CONSENT);
|
||||||
if(userId != null) {
|
if (userId != null) {
|
||||||
event.user(userId);
|
event.user(userId);
|
||||||
}
|
}
|
||||||
event.assertEvent();
|
event.assertEvent();
|
||||||
|
@ -416,7 +419,7 @@ public class BruteForceTest extends TestRealmKeycloakTest {
|
||||||
events.clear();
|
events.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerUser(String username){
|
public void registerUser(String username) {
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
loginPage.clickRegister();
|
loginPage.clickRegister();
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
|
|
|
@ -30,6 +30,8 @@ import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.pages.LoginTotpPage;
|
import org.keycloak.testsuite.pages.LoginTotpPage;
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
@ -49,6 +51,9 @@ public class LoginHotpTest extends TestRealmKeycloakTest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||||
|
testRealm.setOtpPolicyType(UserCredentialModel.HOTP);
|
||||||
|
testRealm.setOtpPolicyAlgorithm(HmacOTP.DEFAULT_ALGORITHM);
|
||||||
|
testRealm.setOtpPolicyLookAheadWindow(2);
|
||||||
UserRepresentation user = RealmRepUtil.findUser(testRealm, "test-user@localhost");
|
UserRepresentation user = RealmRepUtil.findUser(testRealm, "test-user@localhost");
|
||||||
UserBuilder.edit(user)
|
UserBuilder.edit(user)
|
||||||
.hotpSecret("hotpSecret")
|
.hotpSecret("hotpSecret")
|
||||||
|
@ -79,9 +84,6 @@ public class LoginHotpTest extends TestRealmKeycloakTest {
|
||||||
@Before
|
@Before
|
||||||
public void before() throws MalformedURLException {
|
public void before() throws MalformedURLException {
|
||||||
RealmRepresentation testRealm = testRealm().toRepresentation();
|
RealmRepresentation testRealm = testRealm().toRepresentation();
|
||||||
testRealm.setOtpPolicyType(UserCredentialModel.HOTP);
|
|
||||||
testRealm.setOtpPolicyLookAheadWindow(2);
|
|
||||||
testRealm().update(testRealm);
|
|
||||||
|
|
||||||
policy = new OTPPolicy();
|
policy = new OTPPolicy();
|
||||||
policy.setAlgorithm(testRealm.getOtpPolicyAlgorithm());
|
policy.setAlgorithm(testRealm.getOtpPolicyAlgorithm());
|
||||||
|
@ -137,7 +139,7 @@ public class LoginHotpTest extends TestRealmKeycloakTest {
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
Assert.assertTrue(loginTotpPage.isCurrent());
|
Assert.assertTrue("expecting totpPage got: " + driver.getCurrentUrl(), loginTotpPage.isCurrent());
|
||||||
|
|
||||||
loginTotpPage.login(otp.generateHOTP("hotpSecret", counter++));
|
loginTotpPage.login(otp.generateHOTP("hotpSecret", counter++));
|
||||||
|
|
||||||
|
|
|
@ -354,7 +354,9 @@ public class LoginTest extends TestRealmKeycloakTest {
|
||||||
|
|
||||||
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||||
|
|
||||||
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
String currentUrl = driver.getCurrentUrl();
|
||||||
|
String pageSource = driver.getPageSource();
|
||||||
|
assertEquals("bad expectation, on page: " + currentUrl, RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||||
|
|
||||||
|
|
|
@ -114,6 +114,11 @@ public class KerberosLdapTest extends AbstractKerberosTest {
|
||||||
assertUser("hnelson", "hnelson@keycloak.org", "Horatio", "Nelson", false);
|
assertUser("hnelson", "hnelson@keycloak.org", "Horatio", "Nelson", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Override
|
||||||
|
public void usernamePasswordLoginTest() throws Exception {
|
||||||
|
super.usernamePasswordLoginTest();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void writableEditModeTest() throws Exception {
|
public void writableEditModeTest() throws Exception {
|
||||||
|
|
|
@ -68,7 +68,7 @@ public class FederationTestUtils {
|
||||||
creds.setType(CredentialRepresentation.PASSWORD);
|
creds.setType(CredentialRepresentation.PASSWORD);
|
||||||
creds.setValue(password);
|
creds.setValue(password);
|
||||||
|
|
||||||
user.updateCredential(creds);
|
session.userCredentialManager().updateCredential(realm, user, creds);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.junit.rules.RuleChain;
|
||||||
import org.junit.rules.TestRule;
|
import org.junit.rules.TestRule;
|
||||||
import org.junit.runners.MethodSorters;
|
import org.junit.runners.MethodSorters;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.federation.ldap.LDAPConfig;
|
import org.keycloak.federation.ldap.LDAPConfig;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
||||||
|
@ -687,7 +688,7 @@ public class FederationProvidersIntegrationTest {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
UserCredentialModel cred = UserCredentialModel.password("PoopyPoop1");
|
UserCredentialModel cred = UserCredentialModel.password("PoopyPoop1");
|
||||||
user.updateCredential(cred);
|
session.userCredentialManager().updateCredential(appRealm, user, cred);
|
||||||
Assert.fail("should fail");
|
Assert.fail("should fail");
|
||||||
} catch (ModelReadOnlyException e) {
|
} catch (ModelReadOnlyException e) {
|
||||||
|
|
||||||
|
@ -818,10 +819,10 @@ public class FederationProvidersIntegrationTest {
|
||||||
Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
|
Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
|
||||||
|
|
||||||
UserCredentialModel cred = UserCredentialModel.password("Candycand1");
|
UserCredentialModel cred = UserCredentialModel.password("Candycand1");
|
||||||
user.updateCredential(cred);
|
session.userCredentialManager().updateCredential(appRealm, user, cred);
|
||||||
UserCredentialValueModel userCredentialValueModel = user.getCredentialsDirectly().get(0);
|
CredentialModel userCredentialValueModel = session.userCredentialManager().getStoredCredentialsByType(appRealm, user, CredentialModel.PASSWORD).get(0);
|
||||||
Assert.assertEquals(UserCredentialModel.PASSWORD, userCredentialValueModel.getType());
|
Assert.assertEquals(UserCredentialModel.PASSWORD, userCredentialValueModel.getType());
|
||||||
Assert.assertTrue(session.users().validCredentials(session, appRealm, user, cred));
|
Assert.assertTrue(session.userCredentialManager().isValid(appRealm, user, cred));
|
||||||
|
|
||||||
// LDAP password is still unchanged
|
// LDAP password is still unchanged
|
||||||
LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, model);
|
LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, model);
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue