KEYCLOAK-12860 KEYCLOAK-12875 Fix for Account REST Credentials to work with LDAP and social users
This commit is contained in:
parent
876086c846
commit
a76c496c23
15 changed files with 309 additions and 23 deletions
|
@ -2,6 +2,7 @@ package org.keycloak.authentication;
|
||||||
|
|
||||||
import org.keycloak.credential.CredentialProvider;
|
import org.keycloak.credential.CredentialProvider;
|
||||||
import org.keycloak.credential.CredentialTypeMetadata;
|
import org.keycloak.credential.CredentialTypeMetadata;
|
||||||
|
import org.keycloak.credential.CredentialTypeMetadataContext;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
|
@ -15,7 +16,10 @@ public class AuthenticationSelectionOption {
|
||||||
Authenticator authenticator = session.getProvider(Authenticator.class, authExec.getAuthenticator());
|
Authenticator authenticator = session.getProvider(Authenticator.class, authExec.getAuthenticator());
|
||||||
if (authenticator instanceof CredentialValidator) {
|
if (authenticator instanceof CredentialValidator) {
|
||||||
CredentialProvider credentialProvider = ((CredentialValidator) authenticator).getCredentialProvider(session);
|
CredentialProvider credentialProvider = ((CredentialValidator) authenticator).getCredentialProvider(session);
|
||||||
credentialTypeMetadata = credentialProvider.getCredentialTypeMetadata();
|
|
||||||
|
CredentialTypeMetadataContext ctx = CredentialTypeMetadataContext.builder()
|
||||||
|
.build(session);
|
||||||
|
credentialTypeMetadata = credentialProvider.getCredentialTypeMetadata(ctx);
|
||||||
} else {
|
} else {
|
||||||
credentialTypeMetadata = null;
|
credentialTypeMetadata = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,5 +50,5 @@ public interface CredentialProvider<T extends CredentialModel> extends Provider
|
||||||
return getCredentialFromModel(models.get(0));
|
return getCredentialFromModel(models.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
CredentialTypeMetadata getCredentialTypeMetadata();
|
CredentialTypeMetadata getCredentialTypeMetadata(CredentialTypeMetadataContext metadataContext);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ public class CredentialTypeMetadata implements Comparable<CredentialTypeMetadata
|
||||||
|
|
||||||
|
|
||||||
public enum Category {
|
public enum Category {
|
||||||
PASSWORD("password", 1),
|
BASIC_AUTHENTICATION("basic-authentication", 1),
|
||||||
TWO_FACTOR("two-factor", 2),
|
TWO_FACTOR("two-factor", 2),
|
||||||
PASSWORDLESS("passwordless", 3);
|
PASSWORDLESS("passwordless", 3);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class CredentialTypeMetadataContext {
|
||||||
|
|
||||||
|
private UserModel user;
|
||||||
|
|
||||||
|
private CredentialTypeMetadataContext() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return user, for which we create metadata. Could be null
|
||||||
|
*/
|
||||||
|
public UserModel getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CredentialTypeMetadataContext.CredentialTypeMetadataContextBuilder builder() {
|
||||||
|
return new CredentialTypeMetadataContext.CredentialTypeMetadataContextBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUILDER
|
||||||
|
|
||||||
|
public static class CredentialTypeMetadataContextBuilder {
|
||||||
|
|
||||||
|
private CredentialTypeMetadataContext instance = new CredentialTypeMetadataContext();
|
||||||
|
|
||||||
|
public CredentialTypeMetadataContext.CredentialTypeMetadataContextBuilder user(UserModel user) {
|
||||||
|
instance.user = user;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CredentialTypeMetadataContext build(KeycloakSession session) {
|
||||||
|
// Possible to have null user
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -142,7 +142,7 @@ public class OTPCredentialProvider implements CredentialProvider<OTPCredentialMo
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CredentialTypeMetadata getCredentialTypeMetadata() {
|
public CredentialTypeMetadata getCredentialTypeMetadata(CredentialTypeMetadataContext metadataContext) {
|
||||||
return CredentialTypeMetadata.builder()
|
return CredentialTypeMetadata.builder()
|
||||||
.type(getType())
|
.type(getType())
|
||||||
.category(CredentialTypeMetadata.Category.TWO_FACTOR)
|
.category(CredentialTypeMetadata.Category.TWO_FACTOR)
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package org.keycloak.credential;
|
package org.keycloak.credential;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory;
|
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.credential.hash.PasswordHashProvider;
|
import org.keycloak.credential.hash.PasswordHashProvider;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
|
@ -296,14 +295,23 @@ public class PasswordCredentialProvider implements CredentialProvider<PasswordCr
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CredentialTypeMetadata getCredentialTypeMetadata() {
|
public CredentialTypeMetadata getCredentialTypeMetadata(CredentialTypeMetadataContext metadataContext) {
|
||||||
return CredentialTypeMetadata.builder()
|
CredentialTypeMetadata.CredentialTypeMetadataBuilder metadataBuilder = CredentialTypeMetadata.builder()
|
||||||
.type(getType())
|
.type(getType())
|
||||||
.category(CredentialTypeMetadata.Category.PASSWORD)
|
.category(CredentialTypeMetadata.Category.BASIC_AUTHENTICATION)
|
||||||
.displayName("password")
|
.displayName("password-display-name")
|
||||||
.helpText("password-help-text")
|
.helpText("password-help-text")
|
||||||
.iconCssClass("kcAuthenticatorPasswordClass")
|
.iconCssClass("kcAuthenticatorPasswordClass");
|
||||||
.updateAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString())
|
|
||||||
|
// Check if we are creating or updating password
|
||||||
|
UserModel user = metadataContext.getUser();
|
||||||
|
if (user != null && session.userCredentialManager().isConfiguredFor(session.getContext().getRealm(), user, getType())) {
|
||||||
|
metadataBuilder.updateAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString());
|
||||||
|
} else {
|
||||||
|
metadataBuilder.createAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadataBuilder
|
||||||
.removeable(false)
|
.removeable(false)
|
||||||
.build(session);
|
.build(session);
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,7 +230,7 @@ public class WebAuthnCredentialProvider implements CredentialProvider<WebAuthnCr
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CredentialTypeMetadata getCredentialTypeMetadata() {
|
public CredentialTypeMetadata getCredentialTypeMetadata(CredentialTypeMetadataContext metadataContext) {
|
||||||
return CredentialTypeMetadata.builder()
|
return CredentialTypeMetadata.builder()
|
||||||
.type(getType())
|
.type(getType())
|
||||||
.category(CredentialTypeMetadata.Category.TWO_FACTOR)
|
.category(CredentialTypeMetadata.Category.TWO_FACTOR)
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class WebAuthnPasswordlessCredentialProvider extends WebAuthnCredentialPr
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CredentialTypeMetadata getCredentialTypeMetadata() {
|
public CredentialTypeMetadata getCredentialTypeMetadata(CredentialTypeMetadataContext metadataContext) {
|
||||||
return CredentialTypeMetadata.builder()
|
return CredentialTypeMetadata.builder()
|
||||||
.type(getType())
|
.type(getType())
|
||||||
.category(CredentialTypeMetadata.Category.PASSWORDLESS)
|
.category(CredentialTypeMetadata.Category.PASSWORDLESS)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
import org.keycloak.credential.CredentialModel;
|
import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.credential.CredentialProvider;
|
import org.keycloak.credential.CredentialProvider;
|
||||||
import org.keycloak.credential.CredentialTypeMetadata;
|
import org.keycloak.credential.CredentialTypeMetadata;
|
||||||
|
import org.keycloak.credential.CredentialTypeMetadataContext;
|
||||||
import org.keycloak.credential.PasswordCredentialProvider;
|
import org.keycloak.credential.PasswordCredentialProvider;
|
||||||
import org.keycloak.credential.PasswordCredentialProviderFactory;
|
import org.keycloak.credential.PasswordCredentialProviderFactory;
|
||||||
import org.keycloak.credential.UserCredentialStoreManager;
|
import org.keycloak.credential.UserCredentialStoreManager;
|
||||||
|
@ -31,6 +32,7 @@ import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -185,13 +187,29 @@ public class AccountCredentialResource {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
CredentialTypeMetadata metadata = credentialProvider.getCredentialTypeMetadata();
|
CredentialTypeMetadataContext ctx = CredentialTypeMetadataContext.builder()
|
||||||
|
.user(user)
|
||||||
|
.build(session);
|
||||||
|
CredentialTypeMetadata metadata = credentialProvider.getCredentialTypeMetadata(ctx);
|
||||||
|
|
||||||
List<CredentialRepresentation> userCredentialModels = filterUserCredentials ? null : models.stream()
|
List<CredentialRepresentation> userCredentialModels = filterUserCredentials ? null : models.stream()
|
||||||
.filter(credentialModel -> credentialProvider.getType().equals(credentialModel.getType()))
|
.filter(credentialModel -> credentialProvider.getType().equals(credentialModel.getType()))
|
||||||
.map(ModelToRepresentation::toRepresentation)
|
.map(ModelToRepresentation::toRepresentation)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (userCredentialModels != null && userCredentialModels.isEmpty() &&
|
||||||
|
session.userCredentialManager().isConfiguredFor(realm, user, credentialProviderType)) {
|
||||||
|
// In case user is federated in the userStorage, he may have credential configured on the userStorage side. We're
|
||||||
|
// creating "dummy" credential representing the credential provided by userStorage
|
||||||
|
CredentialRepresentation credential = new CredentialRepresentation();
|
||||||
|
credential.setId(credentialProviderType + "-id");
|
||||||
|
credential.setType(credentialProviderType);
|
||||||
|
credential.setCreatedDate(-1L);
|
||||||
|
credential.setPriority(0);
|
||||||
|
|
||||||
|
userCredentialModels = Collections.singletonList(credential);
|
||||||
|
}
|
||||||
|
|
||||||
CredentialContainer credType = new CredentialContainer(metadata, userCredentialModels);
|
CredentialContainer credType = new CredentialContainer(metadata, userCredentialModels);
|
||||||
credentialTypes.add(credType);
|
credentialTypes.add(credType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
<resource-root path="integration-arquillian-testsuite-providers-${project.version}.jar"/>
|
<resource-root path="integration-arquillian-testsuite-providers-${project.version}.jar"/>
|
||||||
</resources>
|
</resources>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<module name="com.fasterxml.jackson.core.jackson-core"/>
|
||||||
<module name="javax.api"/>
|
<module name="javax.api"/>
|
||||||
<module name="javax.ws.rs.api"/>
|
<module name="javax.ws.rs.api"/>
|
||||||
<module name="javax.servlet.api"/>
|
<module name="javax.servlet.api"/>
|
||||||
|
|
|
@ -20,10 +20,10 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.authentication.authenticators.browser.WebAuthnAuthenticatorFactory;
|
import org.keycloak.authentication.authenticators.browser.WebAuthnAuthenticatorFactory;
|
||||||
import org.keycloak.authentication.authenticators.browser.WebAuthnPasswordlessAuthenticatorFactory;
|
import org.keycloak.authentication.authenticators.browser.WebAuthnPasswordlessAuthenticatorFactory;
|
||||||
import org.keycloak.authentication.requiredactions.WebAuthnPasswordlessRegisterFactory;
|
import org.keycloak.authentication.requiredactions.WebAuthnPasswordlessRegisterFactory;
|
||||||
import org.keycloak.authentication.requiredactions.WebAuthnRegister;
|
|
||||||
import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory;
|
import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory;
|
||||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
import org.keycloak.credential.CredentialTypeMetadata;
|
import org.keycloak.credential.CredentialTypeMetadata;
|
||||||
|
@ -45,7 +45,6 @@ import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.representations.idm.ErrorRepresentation;
|
import org.keycloak.representations.idm.ErrorRepresentation;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||||
import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation;
|
import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation;
|
||||||
|
@ -70,7 +69,6 @@ import static org.junit.Assert.*;
|
||||||
import org.keycloak.services.resources.account.AccountCredentialResource.PasswordUpdate;
|
import org.keycloak.services.resources.account.AccountCredentialResource.PasswordUpdate;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||||
import org.keycloak.testsuite.util.WaitUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -91,6 +89,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateProfile() throws IOException {
|
public void testUpdateProfile() throws IOException {
|
||||||
UserRepresentation user = SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
|
UserRepresentation user = SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
|
||||||
|
String originalUsername = user.getUsername();
|
||||||
String originalFirstName = user.getFirstName();
|
String originalFirstName = user.getFirstName();
|
||||||
String originalLastName = user.getLastName();
|
String originalLastName = user.getLastName();
|
||||||
String originalEmail = user.getEmail();
|
String originalEmail = user.getEmail();
|
||||||
|
@ -157,6 +156,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
|
||||||
realmRep.setEditUsernameAllowed(true);
|
realmRep.setEditUsernameAllowed(true);
|
||||||
adminClient.realm("test").update(realmRep);
|
adminClient.realm("test").update(realmRep);
|
||||||
|
|
||||||
|
user.setUsername(originalUsername);
|
||||||
user.setFirstName(originalFirstName);
|
user.setFirstName(originalFirstName);
|
||||||
user.setLastName(originalLastName);
|
user.setLastName(originalLastName);
|
||||||
user.setEmail(originalEmail);
|
user.setEmail(originalEmail);
|
||||||
|
@ -316,8 +316,8 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
|
||||||
Assert.assertEquals(4, credentials.size());
|
Assert.assertEquals(4, credentials.size());
|
||||||
|
|
||||||
AccountCredentialResource.CredentialContainer password = credentials.get(0);
|
AccountCredentialResource.CredentialContainer password = credentials.get(0);
|
||||||
assertCredentialContainerExpected(password, PasswordCredentialModel.TYPE, CredentialTypeMetadata.Category.PASSWORD.toString(),
|
assertCredentialContainerExpected(password, PasswordCredentialModel.TYPE, CredentialTypeMetadata.Category.BASIC_AUTHENTICATION.toString(),
|
||||||
"password", "password-help-text", "kcAuthenticatorPasswordClass",
|
"password-display-name", "password-help-text", "kcAuthenticatorPasswordClass",
|
||||||
null, UserModel.RequiredAction.UPDATE_PASSWORD.toString(), false, 1);
|
null, UserModel.RequiredAction.UPDATE_PASSWORD.toString(), false, 1);
|
||||||
|
|
||||||
CredentialRepresentation password1 = password.getUserCredentials().get(0);
|
CredentialRepresentation password1 = password.getUserCredentials().get(0);
|
||||||
|
@ -443,6 +443,32 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCredentialsForUserWithoutPassword() throws IOException {
|
||||||
|
// This is just to call REST to ensure tokenUtil will authenticate user and create the tokens.
|
||||||
|
// We won't be able to authenticate later as user won't have password
|
||||||
|
List<AccountCredentialResource.CredentialContainer> credentials = getCredentials();
|
||||||
|
|
||||||
|
// Remove password from the user now
|
||||||
|
UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost");
|
||||||
|
for (CredentialRepresentation credential : user.credentials()) {
|
||||||
|
if (PasswordCredentialModel.TYPE.equals(credential.getType())) {
|
||||||
|
user.removeCredential(credential.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get credentials. Ensure user doesn't have password credential and create action is UPDATE_PASSWORD
|
||||||
|
credentials = getCredentials();
|
||||||
|
AccountCredentialResource.CredentialContainer password = credentials.get(0);
|
||||||
|
assertCredentialContainerExpected(password, PasswordCredentialModel.TYPE, CredentialTypeMetadata.Category.BASIC_AUTHENTICATION.toString(),
|
||||||
|
"password-display-name", "password-help-text", "kcAuthenticatorPasswordClass",
|
||||||
|
UserModel.RequiredAction.UPDATE_PASSWORD.toString(), null, false, 0);
|
||||||
|
|
||||||
|
// Re-add the password to the user
|
||||||
|
ApiUtil.resetUserPassword(user, "password", false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Sets new requirement and returns current requirement
|
// Sets new requirement and returns current requirement
|
||||||
private AuthenticationExecutionModel.Requirement setExecutionRequirement(String flowAlias, String executionDisplayName, AuthenticationExecutionModel.Requirement newRequirement) {
|
private AuthenticationExecutionModel.Requirement setExecutionRequirement(String flowAlias, String executionDisplayName, AuthenticationExecutionModel.Requirement newRequirement) {
|
||||||
List<AuthenticationExecutionInfoRepresentation> executionInfos = testRealm().flows().getExecutions(flowAlias);
|
List<AuthenticationExecutionInfoRepresentation> executionInfos = testRealm().flows().getExecutions(flowAlias);
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.testsuite.federation.ldap;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.credential.PasswordCredentialModel;
|
||||||
|
import org.keycloak.representations.account.UserRepresentation;
|
||||||
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
import org.keycloak.services.resources.account.AccountCredentialResource;
|
||||||
|
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||||
|
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||||
|
import org.keycloak.testsuite.util.LDAPRule;
|
||||||
|
import org.keycloak.testsuite.util.LDAPTestUtils;
|
||||||
|
import org.keycloak.testsuite.util.TokenUtil;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.keycloak.common.Profile.Feature.ACCOUNT_API;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
@EnableFeature(value = ACCOUNT_API, skipRestart = true)
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
|
public class LDAPAccountRestApiTest extends AbstractLDAPTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TokenUtil tokenUtil = new TokenUtil("johnkeycloak", "Password1");
|
||||||
|
|
||||||
|
@ClassRule
|
||||||
|
public static LDAPRule ldapRule = new LDAPRule();
|
||||||
|
|
||||||
|
protected CloseableHttpClient httpClient;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
httpClient = HttpClientBuilder.create().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() {
|
||||||
|
try {
|
||||||
|
httpClient.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected LDAPRule getLDAPRule() {
|
||||||
|
return ldapRule;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void afterImportTestRealm() {
|
||||||
|
testingClient.server().run(session -> {
|
||||||
|
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||||
|
RealmModel appRealm = ctx.getRealm();
|
||||||
|
|
||||||
|
// Delete all LDAP users and add some new for testing
|
||||||
|
LDAPTestUtils.removeAllLDAPUsers(ctx.getLdapProvider(), appRealm);
|
||||||
|
|
||||||
|
LDAPObject john = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
|
||||||
|
LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john, "Password1");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetProfile() throws IOException {
|
||||||
|
UserRepresentation user = SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
|
||||||
|
assertEquals("John", user.getFirstName());
|
||||||
|
assertEquals("Doe", user.getLastName());
|
||||||
|
assertEquals("john@email.org", user.getEmail());
|
||||||
|
assertFalse(user.isEmailVerified());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetCredentials() throws IOException {
|
||||||
|
List<AccountCredentialResource.CredentialContainer> credentials = getCredentials();
|
||||||
|
|
||||||
|
AccountCredentialResource.CredentialContainer password = credentials.get(0);
|
||||||
|
Assert.assertEquals(PasswordCredentialModel.TYPE, password.getType());
|
||||||
|
Assert.assertEquals(1, password.getUserCredentials().size());
|
||||||
|
CredentialRepresentation userPassword = password.getUserCredentials().get(0);
|
||||||
|
|
||||||
|
// Password won't have createdDate and any metadata set
|
||||||
|
Assert.assertEquals(PasswordCredentialModel.TYPE, userPassword.getType());
|
||||||
|
Assert.assertEquals(userPassword.getCreatedDate(), new Long(-1L));
|
||||||
|
Assert.assertNull(userPassword.getCredentialData());
|
||||||
|
Assert.assertNull(userPassword.getSecretData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAccountUrl(String resource) {
|
||||||
|
return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/realms/test/account" + (resource != null ? "/" + resource : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send REST request to get all credential containers and credentials of current user
|
||||||
|
private List<AccountCredentialResource.CredentialContainer> getCredentials() throws IOException {
|
||||||
|
return SimpleHttp.doGet(getAccountUrl("credentials"), httpClient)
|
||||||
|
.auth(tokenUtil.getToken()).asJson(new TypeReference<List<AccountCredentialResource.CredentialContainer>>() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -36,6 +36,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||||
import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation;
|
import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation;
|
||||||
import org.keycloak.testsuite.WebAuthnAssume;
|
import org.keycloak.testsuite.WebAuthnAssume;
|
||||||
|
import org.keycloak.testsuite.admin.Users;
|
||||||
import org.keycloak.testsuite.auth.page.login.OTPSetup;
|
import org.keycloak.testsuite.auth.page.login.OTPSetup;
|
||||||
import org.keycloak.testsuite.auth.page.login.UpdatePassword;
|
import org.keycloak.testsuite.auth.page.login.UpdatePassword;
|
||||||
import org.keycloak.testsuite.pages.webauthn.WebAuthnRegisterPage;
|
import org.keycloak.testsuite.pages.webauthn.WebAuthnRegisterPage;
|
||||||
|
@ -62,7 +63,7 @@ import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
|
||||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||||
*/
|
*/
|
||||||
public class SigningInTest extends BaseAccountPageTest {
|
public class SigningInTest extends BaseAccountPageTest {
|
||||||
public static final String PASSWORD_LABEL = "Password";
|
public static final String PASSWORD_LABEL = "My Password";
|
||||||
public static final String WEBAUTHN_FLOW_ID = "75e2390e-f296-49e6-acf8-6d21071d7e10";
|
public static final String WEBAUTHN_FLOW_ID = "75e2390e-f296-49e6-acf8-6d21071d7e10";
|
||||||
|
|
||||||
@Page
|
@Page
|
||||||
|
@ -148,7 +149,7 @@ public class SigningInTest extends BaseAccountPageTest {
|
||||||
|
|
||||||
assertEquals(3, signingInPage.getCategoriesCount());
|
assertEquals(3, signingInPage.getCategoriesCount());
|
||||||
|
|
||||||
assertEquals("Password", signingInPage.getCategoryTitle("password"));
|
assertEquals("Basic Authentication", signingInPage.getCategoryTitle("basic-authentication"));
|
||||||
assertEquals("Two-Factor Authentication", signingInPage.getCategoryTitle("two-factor"));
|
assertEquals("Two-Factor Authentication", signingInPage.getCategoryTitle("two-factor"));
|
||||||
assertEquals("Passwordless", signingInPage.getCategoryTitle("passwordless"));
|
assertEquals("Passwordless", signingInPage.getCategoryTitle("passwordless"));
|
||||||
|
|
||||||
|
@ -180,8 +181,35 @@ public class SigningInTest extends BaseAccountPageTest {
|
||||||
|
|
||||||
assertUserCredential(PASSWORD_LABEL, false, passwordCred);
|
assertUserCredential(PASSWORD_LABEL, false, passwordCred);
|
||||||
assertNotEquals(previousCreatedAt, passwordCred.getCreatedAt());
|
assertNotEquals(previousCreatedAt, passwordCred.getCreatedAt());
|
||||||
|
}
|
||||||
|
|
||||||
// TODO KEYCLOAK-12875 try to update/set up password when user has no password configured
|
@Test
|
||||||
|
public void updatePasswordTestForUserWithoutPassword() {
|
||||||
|
// Remove password from the user through admin REST API
|
||||||
|
String passwordId = testUserResource().credentials().get(0).getId();
|
||||||
|
testUserResource().removeCredential(passwordId);
|
||||||
|
|
||||||
|
// Refresh the page
|
||||||
|
refreshPageAndWaitForLoad();
|
||||||
|
|
||||||
|
// Test user doesn't have password set
|
||||||
|
assertTrue(passwordCredentialType.isSetUpLinkVisible());
|
||||||
|
assertFalse(passwordCredentialType.isSetUp());
|
||||||
|
|
||||||
|
// Set password
|
||||||
|
passwordCredentialType.clickSetUpLink();
|
||||||
|
updatePasswordPage.assertCurrent();
|
||||||
|
String originalPassword = Users.getPasswordOf(testUser);
|
||||||
|
updatePasswordPage.updatePasswords(originalPassword, originalPassword);
|
||||||
|
// TODO uncomment this once KEYCLOAK-12852 is resolved
|
||||||
|
// signingInPage.assertCurrent();
|
||||||
|
|
||||||
|
// Credential set-up now
|
||||||
|
assertFalse(passwordCredentialType.isSetUpLinkVisible());
|
||||||
|
assertTrue(passwordCredentialType.isSetUp());
|
||||||
|
SigningInPage.UserCredential passwordCred =
|
||||||
|
passwordCredentialType.getUserCredential(testUserResource().credentials().get(0).getId());
|
||||||
|
assertUserCredential(PASSWORD_LABEL, false, passwordCred);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -342,6 +342,7 @@ saml.post-form.js-disabled=JavaScript is disabled. We strongly recommend to enab
|
||||||
#authenticators
|
#authenticators
|
||||||
otp-display-name=Authenticator Application
|
otp-display-name=Authenticator Application
|
||||||
otp-help-text=Enter a verification code from authenticator application.
|
otp-help-text=Enter a verification code from authenticator application.
|
||||||
|
password-display-name=Password
|
||||||
password-help-text=Log in by entering your password.
|
password-help-text=Log in by entering your password.
|
||||||
auth-username-form-display-name=Username
|
auth-username-form-display-name=Username
|
||||||
auth-username-form-help-text=Start log in by entering your username
|
auth-username-form-help-text=Start log in by entering your username
|
||||||
|
|
|
@ -68,6 +68,9 @@ notSetUp={0} is not set up.
|
||||||
two-factor=Two-Factor Authentication
|
two-factor=Two-Factor Authentication
|
||||||
passwordless=Passwordless
|
passwordless=Passwordless
|
||||||
unknown=Unknown
|
unknown=Unknown
|
||||||
|
password-display-name=Password
|
||||||
|
password-help-text=Log in by entering your password.
|
||||||
|
password=My Password
|
||||||
otp-display-name=Authenticator Application
|
otp-display-name=Authenticator Application
|
||||||
otp-help-text=Enter a verification code from authenticator application.
|
otp-help-text=Enter a verification code from authenticator application.
|
||||||
webauthn-display-name=Security Key
|
webauthn-display-name=Security Key
|
||||||
|
@ -75,7 +78,6 @@ webauthn-help-text=Use your security key to log in.
|
||||||
webauthn-passwordless-display-name=Security Key
|
webauthn-passwordless-display-name=Security Key
|
||||||
webauthn-passwordless-help-text=Use your security key for passwordless log in.
|
webauthn-passwordless-help-text=Use your security key for passwordless log in.
|
||||||
basic-authentication=Basic Authentication
|
basic-authentication=Basic Authentication
|
||||||
basic-auth-help-text=Sign in with username and password.
|
|
||||||
|
|
||||||
# Applications page
|
# Applications page
|
||||||
applicationsPageTitle=Applications
|
applicationsPageTitle=Applications
|
||||||
|
|
Loading…
Reference in a new issue