Remove AccountTotpPage from the testsuite (#17657)

Closes #15201
This commit is contained in:
Lukas Hanusovsky 2023-04-06 11:49:29 +02:00 committed by GitHub
parent 6014070431
commit 9bb18400ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 255 additions and 452 deletions

View file

@ -1,90 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.pages;
import org.keycloak.services.resources.account.AccountFormService;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import javax.ws.rs.core.UriBuilder;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AccountTotpPage extends AbstractAccountPage {
@FindBy(id = "totpSecret")
private WebElement totpSecret;
@FindBy(id = "totp")
private WebElement totpInput;
@FindBy(id = "userLabel")
private WebElement totpLabelInput;
@FindBy(css = "button[type=\"submit\"]")
private WebElement submitButton;
@FindBy(id = "remove-mobile")
private WebElement removeLink;
@FindBy(id = "mode-barcode")
private WebElement barcodeLink;
@FindBy(id = "mode-manual")
private WebElement manualLink;
private String getPath() {
return AccountFormService.totpUrl(UriBuilder.fromUri(getAuthServerRoot())).build("test").toString();
}
public void configure(String totp) {
totpInput.sendKeys(totp);
submitButton.click();
}
public void configure(String totp, String userLabel) {
totpInput.sendKeys(totp);
totpLabelInput.sendKeys(userLabel);
submitButton.click();
}
public String getTotpSecret() {
return totpSecret.getAttribute("value");
}
public boolean isCurrent() {
return driver.getTitle().contains("Account Management") && driver.getCurrentUrl().split("\\?")[0].endsWith("/account/totp");
}
public void open() {
driver.navigate().to(getPath());
}
public void removeTotp() {
removeLink.click();
}
public void clickManual() {
manualLink.click();
}
public void clickBarcode() {
barcodeLink.click();
}
}

View file

@ -28,6 +28,8 @@ import org.keycloak.testsuite.auth.page.account.Account;
import org.keycloak.testsuite.auth.page.login.OIDCLogin; import org.keycloak.testsuite.auth.page.login.OIDCLogin;
import org.keycloak.testsuite.auth.page.login.SAMLPostLogin; import org.keycloak.testsuite.auth.page.login.SAMLPostLogin;
import org.keycloak.testsuite.auth.page.login.SAMLRedirectLogin; import org.keycloak.testsuite.auth.page.login.SAMLRedirectLogin;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.RealmBuilder;
import org.openqa.selenium.Cookie; import org.openqa.selenium.Cookie;
import java.text.MessageFormat; import java.text.MessageFormat;

View file

@ -34,7 +34,6 @@ import org.keycloak.admin.client.resource.AuthenticationManagementResource;
import org.keycloak.admin.client.resource.RealmsResource; import org.keycloak.admin.client.resource.RealmsResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource; import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.common.Profile;
import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.models.RealmProvider; import org.keycloak.models.RealmProvider;

View file

@ -24,7 +24,6 @@ import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.Profile;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationExecutionModel;
@ -37,11 +36,10 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
import org.keycloak.testsuite.pages.AccountTotpPage;
import org.keycloak.testsuite.pages.LoginConfigTotpPage; import org.keycloak.testsuite.pages.LoginConfigTotpPage;
import org.keycloak.testsuite.pages.LoginTotpPage; import org.keycloak.testsuite.pages.LoginTotpPage;
import org.keycloak.testsuite.pages.RegisterPage; import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.util.AccountHelper;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
@ -93,9 +91,6 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
@Page @Page
protected LoginConfigTotpPage totpPage; protected LoginConfigTotpPage totpPage;
@Page
protected AccountTotpPage accountTotpPage;
@Page @Page
protected RegisterPage registerPage; protected RegisterPage registerPage;
@ -111,7 +106,7 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
doAIA(); doAIA();
assertTrue(totpPage.isCurrent()); totpPage.assertCurrent();
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret())); totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
@ -374,7 +369,6 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
} }
@Test @Test
@DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228)
public void setupTotpRegisteredAfterTotpRemoval() { public void setupTotpRegisteredAfterTotpRemoval() {
// Register new user // Register new user
loginPage.open(); loginPage.open();
@ -418,21 +412,11 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
// Login with one-time password // Login with one-time password
loginTotpPage.login(totp.generateTOTP(totpCode)); loginTotpPage.login(totp.generateTOTP(totpCode));
events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent(); // Remove google authenticator
Assert.assertTrue(AccountHelper.deleteTotpAuthentication(testRealm(),"setupTotp2"));
// Open account page AccountHelper.logout(testRealm(),"setupTotp2");
accountTotpPage.open();
accountTotpPage.assertCurrent();
// Remove google authentificator
accountTotpPage.removeTotp();
events.expectAccount(EventType.REMOVE_TOTP).user(userId).assertEvent();
// Logout
accountTotpPage.logout();
events.expectLogout(loginEvent.getSessionId()).user(userId).detail(Details.REDIRECT_URI, oauth.AUTH_SERVER_ROOT + "/realms/test/account/totp").assertEvent();
// Try to login // Try to login
loginPage.open(); loginPage.open();

View file

@ -23,7 +23,6 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.Profile;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationExecutionModel;
@ -38,8 +37,6 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
import org.keycloak.testsuite.pages.AccountTotpPage;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType; import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.LanguageComboboxAwarePage; import org.keycloak.testsuite.pages.LanguageComboboxAwarePage;
@ -48,6 +45,7 @@ import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginTotpPage; import org.keycloak.testsuite.pages.LoginTotpPage;
import org.keycloak.testsuite.pages.RegisterPage; import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.updaters.RealmAttributeUpdater; import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
import org.keycloak.testsuite.util.AccountHelper;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
@ -64,7 +62,6 @@ import static org.junit.Assert.assertTrue;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
@DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228)
public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest { public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
@Override @Override
@ -117,9 +114,6 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
@Page @Page
protected LoginConfigTotpPage totpPage; protected LoginConfigTotpPage totpPage;
@Page
protected AccountTotpPage accountTotpPage;
@Page @Page
protected RegisterPage registerPage; protected RegisterPage registerPage;
@ -133,7 +127,7 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
String userId = events.expectRegister("setupTotp", "email@mail.com").assertEvent().getUserId(); String userId = events.expectRegister("setupTotp", "email@mail.com").assertEvent().getUserId();
assertTrue(totpPage.isCurrent()); totpPage.assertCurrent();
assertFalse(totpPage.isCancelDisplayed()); assertFalse(totpPage.isCancelDisplayed());
// assert attempted-username not shown when setup TOTP // assert attempted-username not shown when setup TOTP
@ -278,7 +272,7 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
String userId = events.expectRegister("setupTotpRegister", "setupTotpRegister@mail.com").assertEvent().getUserId(); String userId = events.expectRegister("setupTotpRegister", "setupTotpRegister@mail.com").assertEvent().getUserId();
assertTrue(totpPage.isCurrent()); totpPage.assertCurrent();
// KEYCLOAK-11753 - Verify OTP label element present on "Configure OTP" required action form // KEYCLOAK-11753 - Verify OTP label element present on "Configure OTP" required action form
driver.findElement(By.id("userLabel")); driver.findElement(By.id("userLabel"));
@ -290,12 +284,9 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
// Set OTP label to a custom value // Set OTP label to a custom value
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()), customOtpLabel); totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()), customOtpLabel);
// Open account page & verify OTP authenticator with requested label was created // Check if OTP credential is present
accountTotpPage.open(); Assert.assertTrue(AccountHelper.isTotpPresent(testRealm(), "setupTotpRegister"));
accountTotpPage.assertCurrent(); Assert.assertTrue(AccountHelper.totpUserLabelComparator(testRealm(), "setupTotpRegister", customOtpLabel));
String pageSource = driver.getPageSource();
assertTrue(pageSource.contains(customOtpLabel));
} }
@Test @Test
@ -450,18 +441,9 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent(); loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
// Open account page // Remove google authenticator
accountTotpPage.open(); Assert.assertTrue(AccountHelper.deleteTotpAuthentication(testRealm(),"setupTotp2"));
accountTotpPage.assertCurrent(); AccountHelper.logout(testRealm(),"setupTotp2");
// Remove google authentificator
accountTotpPage.removeTotp();
events.expectAccount(EventType.REMOVE_TOTP).user(userId).assertEvent();
// Logout
accountTotpPage.logout();
events.expectLogout(loginEvent.getSessionId()).user(userId).detail(Details.REDIRECT_URI, oauth.AUTH_SERVER_ROOT + "/realms/test/account/totp").assertEvent();
setOtpTimeOffset(TimeBasedOTP.DEFAULT_INTERVAL_SECONDS, totp); setOtpTimeOffset(TimeBasedOTP.DEFAULT_INTERVAL_SECONDS, totp);

View file

@ -1,103 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.admin;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.common.Profile;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.OperationType;
import org.keycloak.models.credential.OTPCredentialModel;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
import org.keycloak.testsuite.pages.AccountTotpPage;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.LoginPage;
import java.util.List;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228)
public class UserTotpTest extends AbstractTestRealmKeycloakTest {
@Rule
public AssertEvents events = new AssertEvents(this);
@Page
protected AccountTotpPage totpPage;
@Page
protected AccountUpdateProfilePage profilePage;
@Page
protected LoginPage loginPage;
private TimeBasedOTP totp = new TimeBasedOTP();
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
}
@Test
public void setupTotp() {
totpPage.open();
loginPage.login("test-user@localhost", "password");
events.expectLogin().client("account").detail(Details.REDIRECT_URI, getAccountRedirectUrl() + "?path=totp").assertEvent();
Assert.assertTrue(totpPage.isCurrent());
Assert.assertFalse(driver.getPageSource().contains("Remove Google"));
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
Assert.assertEquals("Mobile authenticator configured.", profilePage.getSuccess());
events.expectAccount(EventType.UPDATE_TOTP).assertEvent();
Assert.assertTrue(driver.getPageSource().contains("pficon-delete"));
List<UserRepresentation> users = adminClient.realms().realm("test").users().search("test-user@localhost", null, null, null, 0, 1);
String userId = users.get(0).getId();
testingClient.testing().clearAdminEventQueue();
CredentialRepresentation totpCredential = adminClient.realms().realm("test").users().get(userId).credentials()
.stream().filter(c -> OTPCredentialModel.TYPE.equals(c.getType())).findFirst().get();
adminClient.realms().realm("test").users().get(userId).removeCredential(totpCredential.getId());
totpPage.open();
Assert.assertFalse(driver.getPageSource().contains("pficon-delete"));
AdminEventRepresentation event = testingClient.testing().pollAdminEvent();
Assert.assertNotNull(event);
Assert.assertEquals(OperationType.ACTION.name(), event.getOperationType());
Assert.assertEquals("users/" + userId + "/credentials/" + totpCredential.getId(), event.getResourcePath());
}
}

View file

@ -18,53 +18,50 @@
package org.keycloak.testsuite.federation.storage; package org.keycloak.testsuite.federation.storage;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.Profile.Feature;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.credential.OTPCredentialModel;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.federation.BackwardsCompatibilityUserStorageFactory;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginConfigTotpPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginTotpPage;
import org.keycloak.testsuite.util.TestAppHelper;
import javax.ws.rs.core.Response;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.ws.rs.core.Response; import static org.wildfly.common.Assert.assertTrue;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.Profile;
import org.keycloak.common.Profile.Feature;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.testsuite.AbstractAuthTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
import org.keycloak.testsuite.federation.BackwardsCompatibilityUserStorageFactory;
import org.keycloak.testsuite.pages.AccountTotpPage;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginConfigTotpPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginTotpPage;
import org.junit.BeforeClass;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
/** /**
* Test that userStorage implementation created in previous version is still compatible with latest Keycloak version * Test that userStorage implementation created in previous version is still compatible with latest Keycloak version
* *
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
@DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228) public class BackwardsCompatibilityUserStorageTest extends AbstractTestRealmKeycloakTest {
public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
private String backwardsCompProviderId; private String backwardsCompProviderId;
@ -77,15 +74,14 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
@Page @Page
protected LoginTotpPage loginTotpPage; protected LoginTotpPage loginTotpPage;
@Page
protected AccountTotpPage accountTotpSetupPage;
@Page @Page
protected LoginConfigTotpPage configureTotpRequiredActionPage; protected LoginConfigTotpPage configureTotpRequiredActionPage;
private TimeBasedOTP totp = new TimeBasedOTP(); private TimeBasedOTP totp = new TimeBasedOTP();
@BeforeClass @BeforeClass
public static void checkNotMapStorage() { public static void checkNotMapStorage() {
ProfileAssume.assumeFeatureDisabled(Feature.MAP_STORAGE); ProfileAssume.assumeFeatureDisabled(Feature.MAP_STORAGE);
@ -105,27 +101,29 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
} }
protected String addComponent(ComponentRepresentation component) { protected String addComponent(ComponentRepresentation component) {
Response resp = testRealmResource().components().add(component); Response resp = testRealm().components().add(component);
String id = ApiUtil.getCreatedId(resp); String id = ApiUtil.getCreatedId(resp);
getCleanup().addComponentId(id); getCleanup().addComponentId(id);
return id; return id;
} }
private void loginSuccessAndLogout(String username, String password) { private void loginSuccessAndLogout(String username, String password) throws URISyntaxException, IOException {
testRealmAccountPage.navigateTo(); TestAppHelper testAppHelper = new TestAppHelper(oauth, loginPage, appPage);
loginPage.login(username, password);
assertCurrentUrlStartsWith(testRealmAccountPage); testAppHelper.login(username, password);
testRealmAccountPage.logOut(); appPage.assertCurrent();
assertTrue(testAppHelper.logout());
} }
public void loginBadPassword(String username) { public void loginBadPassword(String username) {
testRealmAccountPage.navigateTo(); loginPage.open();
testRealmLoginPage.form().login(username, "badpassword"); loginPage.login(username, "badpassword");
assertCurrentUrlDoesntStartWith(testRealmAccountPage); loginPage.assertCurrent();
} }
@Test @Test
public void testLoginSuccess() { public void testLoginSuccess() throws URISyntaxException, IOException {
addUserAndResetPassword("tbrady", "goat"); addUserAndResetPassword("tbrady", "goat");
addUserAndResetPassword("tbrady2", "goat2"); addUserAndResetPassword("tbrady2", "goat2");
@ -139,7 +137,7 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
UserRepresentation user = new UserRepresentation(); UserRepresentation user = new UserRepresentation();
user.setEnabled(true); user.setEnabled(true);
user.setUsername(username); user.setUsername(username);
Response response = testRealmResource().users().create(user); Response response = testRealm().users().create(user);
String userId = ApiUtil.getCreatedId(response); String userId = ApiUtil.getCreatedId(response);
Assert.assertEquals(backwardsCompProviderId, new StorageId(userId).getProviderId()); Assert.assertEquals(backwardsCompProviderId, new StorageId(userId).getProviderId());
@ -150,14 +148,14 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
passwordRep.setValue(password); passwordRep.setValue(password);
passwordRep.setTemporary(false); passwordRep.setTemporary(false);
testRealmResource().users().get(userId).resetPassword(passwordRep); testRealm().users().get(userId).resetPassword(passwordRep);
return userId; return userId;
} }
@Test @Test
public void testOTPUpdateAndLogin() { public void testOTPUpdateAndLogin() throws URISyntaxException, IOException {
String userId = addUserAndResetPassword("otp1", "pass"); String userId = addUserAndResetPassword("otp1", "pass");
getCleanup().addUserId(userId); getCleanup().addUserId(userId);
@ -171,58 +169,55 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
assertUserDontHaveDBCredentials(); assertUserDontHaveDBCredentials();
assertUserHasOTPCredentialInUserStorage(true); assertUserHasOTPCredentialInUserStorage(true);
// Authenticate as the user with the hardcoded OTP. Should be supported TestAppHelper testAppHelper = new TestAppHelper(oauth, loginPage, appPage);
loginPage.login("otp1", "pass");
loginTotpPage.assertCurrent();
loginTotpPage.login("123456");
assertCurrentUrlStartsWith(testRealmAccountPage); // Authenticate as the user with the hardcoded OTP. Should be supported
testRealmAccountPage.logOut(); testAppHelper.startLogin("otp1", "pass");
loginTotpPage.login("123456");
testAppHelper.completeLogin();
appPage.assertCurrent();
testAppHelper.logout();
// Authenticate as the user with bad OTP // Authenticate as the user with bad OTP
loginPage.login("otp1", "pass"); testAppHelper.startLogin("otp1", "pass");
loginTotpPage.assertCurrent(); loginTotpPage.assertCurrent();
loginTotpPage.login("7123456"); loginTotpPage.login("7123456");
assertCurrentUrlDoesntStartWith(testRealmAccountPage); loginTotpPage.assertCurrent();
Assert.assertNotNull(loginTotpPage.getInputError()); Assert.assertNotNull(loginTotpPage.getInputError());
// Authenticate as the user with correct OTP // Authenticate as the user with correct OTP
loginTotpPage.login(totp.generateTOTP(totpSecret)); loginTotpPage.login(totp.generateTOTP(totpSecret));
assertCurrentUrlStartsWith(testRealmAccountPage); testAppHelper.completeLogin();
testRealmAccountPage.logOut(); appPage.assertCurrent();
assertTrue(testAppHelper.logout());
} }
@Test @Test
public void testOTPSetupThroughAccountMgmtAndLogin() { public void testOTPSetupThroughAccountMgmtAndLogin() throws URISyntaxException, IOException {
String userId = addUserAndResetPassword("otp1", "pass"); String userId = addUserAndResetPassword("otp1", "pass");
getCleanup().addUserId(userId); getCleanup().addUserId(userId);
// Login as user to account mgmt
accountTotpSetupPage.open();
loginPage.login("otp1", "pass");
// Setup OTP // Setup OTP
String totpSecret = accountTotpSetupPage.getTotpSecret(); String totpSecret = setupOTPForUserWithRequiredAction(userId);
accountTotpSetupPage.configure(totp.generateTOTP(totpSecret));
assertUserDontHaveDBCredentials(); assertUserDontHaveDBCredentials();
assertUserHasOTPCredentialInUserStorage(true); assertUserHasOTPCredentialInUserStorage(true);
// Logout and assert user can login with hardcoded OTP TestAppHelper testAppHelper = new TestAppHelper(oauth, loginPage, loginTotpPage, appPage);
accountTotpSetupPage.logout();
loginPage.login("otp1", "pass"); // Login as user to account mgmt
loginTotpPage.login("123456"); assertTrue(testAppHelper.login("otp1", "pass", "123456"));
assertCurrentUrlStartsWith(testRealmAccountPage);
// Logout and assert user can login with valid credential // Logout and assert user can login with valid credential
accountTotpSetupPage.logout(); testAppHelper.logout();
loginPage.login("otp1", "pass"); assertTrue(testAppHelper.login("otp1", "pass", totp.generateTOTP(totpSecret)));
loginTotpPage.login(totp.generateTOTP(totpSecret)); testAppHelper.logout();
assertCurrentUrlStartsWith(testRealmAccountPage);
// Delete OTP credential in account console // Disable OTP credential in account console
accountTotpSetupPage.removeTotp(); testRealm().users().get(userId).disableCredentialType(Collections.singletonList(OTPCredentialModel.TYPE));
accountTotpSetupPage.logout();
assertUserDontHaveDBCredentials(); assertUserDontHaveDBCredentials();
assertUserHasOTPCredentialInUserStorage(false); assertUserHasOTPCredentialInUserStorage(false);
@ -232,7 +227,7 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
} }
@Test @Test
public void testDisableCredentialsInUserStorage() { public void testDisableCredentialsInUserStorage() throws URISyntaxException, IOException {
String userId = addUserAndResetPassword("otp1", "pass"); String userId = addUserAndResetPassword("otp1", "pass");
getCleanup().addUserId(userId); getCleanup().addUserId(userId);
@ -243,13 +238,13 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
assertUserDontHaveDBCredentials(); assertUserDontHaveDBCredentials();
assertUserHasOTPCredentialInUserStorage(true); assertUserHasOTPCredentialInUserStorage(true);
UserResource user = testRealmResource().users().get(userId); UserResource user = testRealm().users().get(userId);
// Disable OTP credential for the user through REST endpoint // Disable OTP credential for the user through REST endpoint
UserRepresentation userRep = user.toRepresentation(); UserRepresentation userRep = user.toRepresentation();
Assert.assertNames(userRep.getDisableableCredentialTypes(), CredentialModel.OTP); Assert.assertNames(userRep.getDisableableCredentialTypes(), OTPCredentialModel.TYPE);
user.disableCredentialType(Collections.singletonList(CredentialModel.OTP)); user.disableCredentialType(Collections.singletonList(OTPCredentialModel.TYPE));
// User don't have OTP credential in userStorage anymore // User don't have OTP credential in userStorage anymore
assertUserDontHaveDBCredentials(); assertUserDontHaveDBCredentials();
@ -266,29 +261,32 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
getCleanup().addUserId(userId); getCleanup().addUserId(userId);
// Uses same parameters as admin console when searching users // Uses same parameters as admin console when searching users
List<UserRepresentation> users = testRealmResource().users().search("searching", 0, 20, true); List<UserRepresentation> users = testRealm().users().search("searching", 0, 20, true);
Assert.assertNames(users, "searching"); Assert.assertNames(users, "searching");
} }
// return created totpSecret // return created totpSecret
private String setupOTPForUserWithRequiredAction(String userId) { private String setupOTPForUserWithRequiredAction(String userId) throws URISyntaxException, IOException {
// Add required action to the user to reset OTP // Add required action to the user to reset OTP
UserResource user = testRealmResource().users().get(userId); UserResource user = testRealm().users().get(userId);
UserRepresentation userRep = user.toRepresentation(); UserRepresentation userRep = user.toRepresentation();
userRep.setRequiredActions(Arrays.asList(UserModel.RequiredAction.CONFIGURE_TOTP.toString())); userRep.setRequiredActions(Arrays.asList(UserModel.RequiredAction.CONFIGURE_TOTP.toString()));
user.update(userRep); user.update(userRep);
TestAppHelper testAppHelper = new TestAppHelper(oauth, loginPage, appPage);
// Login as the user and setup OTP // Login as the user and setup OTP
testRealmAccountPage.navigateTo(); testAppHelper.startLogin("otp1", "pass");
loginPage.login("otp1", "pass");
configureTotpRequiredActionPage.assertCurrent(); configureTotpRequiredActionPage.assertCurrent();
String totpSecret = configureTotpRequiredActionPage.getTotpSecret(); String totpSecret = configureTotpRequiredActionPage.getTotpSecret();
configureTotpRequiredActionPage.configure(totp.generateTOTP(totpSecret)); configureTotpRequiredActionPage.configure(totp.generateTOTP(totpSecret));
assertCurrentUrlStartsWith(testRealmAccountPage); appPage.assertCurrent();
testAppHelper.completeLogin();
// Logout // Logout
testRealmAccountPage.logOut(); assertTrue(testAppHelper.logout());
return totpSecret; return totpSecret;
} }
@ -310,4 +308,9 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
}, Boolean.class); }, Boolean.class);
Assert.assertEquals(expectedUserHasOTP, hasUserOTP); Assert.assertEquals(expectedUserHasOTP, hasUserOTP);
} }
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
}
} }

View file

@ -18,14 +18,12 @@
package org.keycloak.testsuite.forms; package org.keycloak.testsuite.forms;
import org.hamcrest.Matchers;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.common.Profile;
import org.keycloak.models.UserManager; import org.keycloak.models.UserManager;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.models.utils.DefaultAuthenticationFlows;
@ -34,11 +32,9 @@ import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
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.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.actions.AbstractAppInitiatedActionTest;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.admin.authentication.AbstractAuthenticationTest; import org.keycloak.testsuite.admin.authentication.AbstractAuthenticationTest;
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
import org.keycloak.testsuite.pages.AccountTotpPage;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.ErrorPage; import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.LoginConfigTotpPage; import org.keycloak.testsuite.pages.LoginConfigTotpPage;
@ -50,23 +46,13 @@ import org.keycloak.testsuite.pages.LoginUsernameOnlyPage;
import org.keycloak.testsuite.pages.LogoutConfirmPage; import org.keycloak.testsuite.pages.LogoutConfirmPage;
import org.keycloak.testsuite.pages.PasswordPage; import org.keycloak.testsuite.pages.PasswordPage;
import org.keycloak.testsuite.pages.RegisterPage; import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.util.FlowUtil; import org.keycloak.testsuite.util.*;
import org.keycloak.testsuite.util.GreenMailRule;
import org.keycloak.testsuite.util.MailUtils;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.URLUtils;
import org.keycloak.testsuite.util.UserBuilder;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/** /**
* Test for the various alternatives of reset-credentials flow or browser flow (non-default setup of the flows) * Test for the various alternatives of reset-credentials flow or browser flow (non-default setup of the flows)
@ -74,7 +60,7 @@ import static org.junit.Assert.assertThat;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
* @author <a href="mailto:jlieskov@redhat.com">Jan Lieskovsky</a> * @author <a href="mailto:jlieskov@redhat.com">Jan Lieskovsky</a>
*/ */
public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycloakTest { public class ResetCredentialsAlternativeFlowsTest extends AbstractAppInitiatedActionTest {
private String userId; private String userId;
@ -99,9 +85,6 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
@Page @Page
protected LoginPasswordUpdatePage updatePasswordPage; protected LoginPasswordUpdatePage updatePasswordPage;
@Page
protected AccountTotpPage accountTotpPage;
@Page @Page
protected LoginConfigTotpPage totpPage; protected LoginConfigTotpPage totpPage;
@ -136,6 +119,11 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
getCleanup().addUserId(userId); getCleanup().addUserId(userId);
} }
@Override
public String getAiaAction() {
return UserModel.RequiredAction.CONFIGURE_TOTP.name();
}
// Test with default reset-credentials flow and alternative browser flow with separate username and password screen. // Test with default reset-credentials flow and alternative browser flow with separate username and password screen.
// //
@ -236,7 +224,7 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
loginUsernameOnlyPage.open(); loginUsernameOnlyPage.open();
loginUsernameOnlyPage.login("login-test"); loginUsernameOnlyPage.login("login-test");
Assert.assertTrue(passwordPage.isCurrent()); passwordPage.assertCurrent();
// Click "Forget password" // Click "Forget password"
passwordPage.clickResetPassword(); passwordPage.clickResetPassword();
@ -272,7 +260,7 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
loginUsernameOnlyPage.open(); loginUsernameOnlyPage.open();
loginUsernameOnlyPage.login(username); loginUsernameOnlyPage.login(username);
Assert.assertTrue(passwordPage.isCurrent()); passwordPage.assertCurrent();
// Click "Forget password" // Click "Forget password"
passwordPage.clickResetPassword(); passwordPage.clickResetPassword();
@ -344,7 +332,6 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
@Test @Test
@DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228)
public void resetCredentialsVerifyCustomOtpLabelSetProperly() { public void resetCredentialsVerifyCustomOtpLabelSetProperly() {
try { try {
// Make a copy of the default Reset Credentials flow, but: // Make a copy of the default Reset Credentials flow, but:
@ -358,14 +345,17 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
// Login & set up the initial OTP code for the user // Login & set up the initial OTP code for the user
loginPage.open(); loginPage.open();
loginPage.login("login@test.com", "password"); loginPage.login("login-test", "password");
String code = new OAuthClient.AuthorizationEndpointResponse(oauth).getCode(); String code = new OAuthClient.AuthorizationEndpointResponse(oauth).getCode();
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password"); OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
accountTotpPage.open();
Assert.assertTrue(accountTotpPage.isCurrent());
String customOtpLabel = "my-original-otp-label"; String customOtpLabel = "my-original-otp-label";
accountTotpPage.configure(totp.generateTOTP(accountTotpPage.getTotpSecret()), customOtpLabel);
// Setup OTP
doAIA();
totpPage.assertCurrent();
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()), customOtpLabel);
assertKcActionStatus(SUCCESS);
// Logout // Logout
oauth.idTokenHint(response.getIdToken()).openLogout(); oauth.idTokenHint(response.getIdToken()).openLogout();
@ -375,26 +365,18 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
loginPage.resetPassword(); loginPage.resetPassword();
// Should be on reset password page now. Provide email of the user & click Submit button // Should be on reset password page now. Provide email of the user & click Submit button
Assert.assertTrue(resetPasswordPage.isCurrent()); resetPasswordPage.assertCurrent();
resetPasswordPage.changePassword("login@test.com"); resetPasswordPage.changePassword("login-test");
// Since 'Send Reset Email' & 'Reset Password' authenticators got removed above,
// the next action should be 'Reset OTP' -- verify that
Assert.assertTrue(totpPage.isCurrent());
// Provide updated form of the OTP label, to be used within 'Reset OTP' (next) step // Provide updated form of the OTP label, to be used within 'Reset OTP' (next) step
customOtpLabel = "my-reset-otp-label"; customOtpLabel = "my-reset-otp-label";
// Reset OTP label to a custom value as part of Reset Credentials flow // Reset OTP label to a custom value as part of Reset Credentials flow
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()), customOtpLabel); AccountHelper.updateTotpUserLabel(testRealm(), "login-test", customOtpLabel);
// Open OTP Authenticator account page // Open OTP Authenticator account page
accountTotpPage.open(); // Check if OTP credential is present
Assert.assertTrue(accountTotpPage.isCurrent()); Assert.assertTrue(AccountHelper.totpUserLabelComparator(testRealm(), "login-test", customOtpLabel));
// Verify OTP authenticator with requested label was created
String pageSource = driver.getPageSource();
Assert.assertTrue(pageSource.contains(customOtpLabel));
// Undo setup changes performed within the test // Undo setup changes performed within the test
} finally { } finally {
@ -406,7 +388,6 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
// KEYCLOAK-12168 Verify the 'Device Name' label is optional for the first OTP credential created // KEYCLOAK-12168 Verify the 'Device Name' label is optional for the first OTP credential created
// (either via Account page or by registering new user), but required for each next created OTP credential // (either via Account page or by registering new user), but required for each next created OTP credential
@Test @Test
@DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228)
public void deviceNameOptionalForFirstOTPCredentialButRequiredForEachNextOne() { public void deviceNameOptionalForFirstOTPCredentialButRequiredForEachNextOne() {
// Enable 'Default Action' on 'Configure OTP' RA for the 'test' realm // Enable 'Default Action' on 'Configure OTP' RA for the 'test' realm
RequiredActionProviderRepresentation otpRequiredAction = testRealm().flows().getRequiredAction("CONFIGURE_TOTP"); RequiredActionProviderRepresentation otpRequiredAction = testRealm().flows().getRequiredAction("CONFIGURE_TOTP");
@ -428,29 +409,17 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
// Login & set up the initial OTP code for the user // Login & set up the initial OTP code for the user
loginPage.open(); loginPage.open();
loginPage.login("login@test.com", "password"); loginPage.login("login@test.com", "password");
accountTotpPage.open();
Assert.assertTrue(accountTotpPage.isCurrent());
String pageSource = driver.getPageSource();
// Check the One-time code label is followed by asterisk character (since always required)
final String oneTimeCodeLabelFollowedByAsterisk = "(?s)<label for=\"totp\"((?!</span>).)+((?=<span class=\"required\">\\*).)*";
Assert.assertTrue(Pattern.compile(oneTimeCodeLabelFollowedByAsterisk).matcher(pageSource).find());
// Check the Device Name label is not followed by asterisk character (since optional if no OTP credential defined yet)
final String asteriskPrecededByDeviceNameLabel = "(?s)((?<=<label for=\"userLabel\").)+.*<span class=\"required\">\\s+\\*";
Assert.assertFalse(Pattern.compile(asteriskPrecededByDeviceNameLabel).matcher(pageSource).find());
// Create OTP credential with empty label // Create OTP credential with empty label
final String emptyOtpLabel = ""; final String emptyOtpLabel = "";
accountTotpPage.configure(totp.generateTOTP(accountTotpPage.getTotpSecret()), emptyOtpLabel);
// Get the updated Account TOTP page source post OTP credential creation // Setup OTP
pageSource = driver.getPageSource(); doAIA();
totpPage.assertCurrent();
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()), emptyOtpLabel);
assertKcActionStatus(SUCCESS);
// Check if OTP credential with empty label was created successfully Assert.assertTrue(AccountHelper.deleteTotpAuthentication(testRealm(), "login-test"));
assertThat(driver.findElements(By.className("provider")).stream()
.map(WebElement::getText).collect(Collectors.toList()), Matchers.hasItem(""));
accountTotpPage.removeTotp();
// Logout // Logout
driver.navigate().to(oauth.getLogoutUrl().build()); driver.navigate().to(oauth.getLogoutUrl().build());
@ -465,32 +434,20 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
registerPage.assertCurrent(); registerPage.assertCurrent();
registerPage.register("Bruce", "Wilson", "bwilson@keycloak.org", "bwilson", "password", "password"); registerPage.register("Bruce", "Wilson", "bwilson@keycloak.org", "bwilson", "password", "password");
Assert.assertTrue(totpPage.isCurrent()); totpPage.assertCurrent();
pageSource = driver.getPageSource();
// Check the One-time code label is required
Assert.assertTrue(Pattern.compile(oneTimeCodeLabelFollowedByAsterisk).matcher(pageSource).find());
// Check the Device Name label is optional
Assert.assertFalse(Pattern.compile(asteriskPrecededByDeviceNameLabel).matcher(pageSource).find());
// Create OTP credential with empty label // Create OTP credential with empty label
totpPage.configure(totp.generateTOTP(accountTotpPage.getTotpSecret()), emptyOtpLabel);
Assert.assertNull(totpPage.getAlertError()); // Setup OTP
Assert.assertNull(totpPage.getInputCodeError()); totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()), "");
Assert.assertNull(totpPage.getInputLabelError());
// Assert user authenticated // Assert user authenticated
appPage.assertCurrent(); appPage.assertCurrent();
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
accountTotpPage.open(); Assert.assertTrue(AccountHelper.isTotpPresent(testRealm(), "bwilson"));
Assert.assertTrue(accountTotpPage.isCurrent()); Assert.assertTrue(AccountHelper.totpUserLabelComparator(testRealm(), "bwilson", ""));
// Check if OTP credential with empty label was created successfully
assertThat(driver.findElements(By.className("provider")).stream()
.map(WebElement::getText).collect(Collectors.toList()), Matchers.hasItem(""));;
// Logout // Logout
driver.navigate().to(oauth.getLogoutUrl().build()); driver.navigate().to(oauth.getLogoutUrl().build());
@ -504,49 +461,28 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
loginPage.resetPassword(); loginPage.resetPassword();
// Should be on reset password page now. Provide email of previously registered user & click Submit button // Should be on reset password page now. Provide email of previously registered user & click Submit button
Assert.assertTrue(resetPasswordPage.isCurrent()); resetPasswordPage.assertCurrent();
resetPasswordPage.changePassword("bwilson@keycloak.org"); resetPasswordPage.changePassword("bwilson@keycloak.org");
pageSource = driver.getPageSource();
// Check the One-time code label is required
Assert.assertTrue(Pattern.compile(oneTimeCodeLabelFollowedByAsterisk).matcher(pageSource).find());
// Check the Device Name label is required (since one OTP credential already defined)
final String deviceNameLabelFollowedByAsterisk = "(?s)<label for=\"userLabel\"((?!</span>).)+((?=<span class=\"required\">\\*).)*";
Assert.assertTrue(Pattern.compile(deviceNameLabelFollowedByAsterisk).matcher(pageSource).find());
// Try to create another OTP credential with empty label again. This // Try to create another OTP credential with empty label again. This
// should fail with error since OTP label is required in this case already // should fail with error since OTP label is required in this case already
final String deviceNameLabelRequiredErrorMessage = "Please specify device name."; totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()), "");
totpPage.configure(totp.generateTOTP(accountTotpPage.getTotpSecret()), emptyOtpLabel); Assert.assertTrue(AccountHelper.totpCountEquals(testRealm(), "bwilson", 1));
Assert.assertTrue(totpPage.getInputLabelError().equals(deviceNameLabelRequiredErrorMessage));
// Create 2nd OTP credential with valid (non-empty) Device Name label. This should pass // Create 2nd OTP credential with valid (non-empty) Device Name label. This should pass
final String secondOtpLabel = "My 2nd OTP device"; final String secondOtpLabel = "My 2nd OTP device";
totpPage.configure(totp.generateTOTP(accountTotpPage.getTotpSecret()), secondOtpLabel); totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()), secondOtpLabel);
Assert.assertNull(totpPage.getAlertError());
Assert.assertNull(totpPage.getInputCodeError());
Assert.assertNull(totpPage.getInputLabelError());
// Assert user authenticated // Assert user authenticated
appPage.assertCurrent(); appPage.assertCurrent();
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
accountTotpPage.open();
Assert.assertTrue(accountTotpPage.isCurrent());
// Get the updated Account TOTP page source after both the OTP credentials were created
pageSource = driver.getPageSource();
// Verify 2nd OTP credential was successfully created too // Verify 2nd OTP credential was successfully created too
Assert.assertTrue(pageSource.contains(secondOtpLabel)); Assert.assertTrue(AccountHelper.totpUserLabelComparator(testRealm(), "bwilson", secondOtpLabel));
// Remove both OTP credentials // Remove both OTP credentials
accountTotpPage.removeTotp(); Assert.assertTrue(AccountHelper.deleteTotpAuthentication(testRealm(), "bwilson"));
accountTotpPage.removeTotp(); Assert.assertTrue(AccountHelper.deleteTotpAuthentication(testRealm(), "bwilson"));
// Logout // Logout
driver.navigate().to(oauth.getLogoutUrl().build()); driver.navigate().to(oauth.getLogoutUrl().build());

View file

@ -20,6 +20,7 @@ import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.keycloak.models.credential.OTPCredentialModel;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
@ -27,8 +28,19 @@ import java.util.Optional;
public class AccountHelper { public class AccountHelper {
private static UserResource getUserResource(RealmResource realm, String username) {
Optional<UserRepresentation> userResult = realm.users().search(username).stream().findFirst();
if (userResult.isEmpty()) {
throw new RuntimeException("User with username " + username + " not found");
}
UserRepresentation userRepresentation = userResult.get();
UserResource user = realm.users().get(userRepresentation.getId());
return user;
}
public static boolean updatePassword(RealmResource realm, String username, String password) { public static boolean updatePassword(RealmResource realm, String username, String password) {
UserResource user = realm.users().get(getUserId(realm, username)); UserResource user = getUserResource(realm, username);
CredentialRepresentation credentialRepresentation = CredentialBuilder.create().password(password).build(); CredentialRepresentation credentialRepresentation = CredentialBuilder.create().password(password).build();
@ -41,29 +53,76 @@ public class AccountHelper {
} }
public static List<Map<String, Object>> getUserConsents(RealmResource realm, String username) { public static List<Map<String, Object>> getUserConsents(RealmResource realm, String username) {
UserResource user = realm.users().get(getUserId(realm, username)); UserResource user = getUserResource(realm, username);
List<Map<String, Object>> consents = user.getConsents(); List<Map<String, Object>> consents = user.getConsents();
return consents; return consents;
} }
public static void revokeConsents(RealmResource realm, String username, String clientId) { public static void revokeConsents(RealmResource realm, String username, String clientId) {
UserResource user = realm.users().get(getUserId(realm, username)); UserResource user = getUserResource(realm, username);
user.revokeConsent(clientId); user.revokeConsent(clientId);
} }
public static void logout(RealmResource realm, String username) { public static void logout(RealmResource realm, String username) {
UserResource user = realm.users().get(getUserId(realm, username)); UserResource user = getUserResource(realm, username);
user.logout(); user.logout();
} }
private static String getUserId(RealmResource realm, String username) { private static Optional<CredentialRepresentation> getOtpCredentials(UserResource user, String userLabel) {
Optional<UserRepresentation> userResult = realm.users().search(username).stream().findFirst(); return user.credentials().stream().filter(c -> c.getType().equals(OTPCredentialModel.TYPE)).filter(l -> l.getUserLabel().equals(userLabel)).findFirst();
if (userResult.isEmpty()) {
throw new RuntimeException("User with username " + username + " not found");
} }
UserRepresentation userRepresentation = userResult.get(); private static Optional<CredentialRepresentation> getOtpCredentials(UserResource user) {
return userRepresentation.getId(); return user.credentials().stream().filter(c -> c.getType().equals(OTPCredentialModel.TYPE)).findFirst();
} }
private static long getOtpCredentialsCount(UserResource user) {
return user.credentials().stream().filter(c -> c.getType().equals(OTPCredentialModel.TYPE)).count();
}
public static boolean deleteTotpAuthentication(RealmResource realm, String username) {
UserResource user = getUserResource(realm, username);
Optional<CredentialRepresentation> credentials = getOtpCredentials(user);
if (credentials.isEmpty()) {
return false;
}
try {
user.removeCredential(credentials.get().getId());
return true;
} catch (Throwable t) {
return false;
}
}
public static boolean isTotpPresent(RealmResource realm, String username) {
UserResource user = getUserResource(realm, username);
Optional<CredentialRepresentation> credentials = getOtpCredentials(user);
return credentials.isPresent();
}
public static boolean totpCountEquals(RealmResource realm, String username, int count) {
UserResource user = getUserResource(realm, username);
return (int) getOtpCredentialsCount(user) == count;
}
public static boolean totpUserLabelComparator(RealmResource realm, String username, String userLabel) {
UserResource user = getUserResource(realm, username);
Optional<CredentialRepresentation> credentials = getOtpCredentials(user, userLabel);
return credentials.get().getUserLabel().equals(userLabel);
}
public static boolean updateTotpUserLabel(RealmResource realm, String username, String userLabel) {
UserResource user = getUserResource(realm, username);
Optional<CredentialRepresentation> credentials = getOtpCredentials(user);
try {
user.setCredentialUserLabel(credentials.get().getId(), userLabel);
return true;
} catch (Throwable t) {
return false;
}
}
} }

View file

@ -20,6 +20,7 @@ import org.apache.http.client.methods.CloseableHttpResponse;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginTotpPage;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.io.IOException; import java.io.IOException;
@ -28,6 +29,7 @@ import java.net.URISyntaxException;
public class TestAppHelper { public class TestAppHelper {
private OAuthClient oauth; private OAuthClient oauth;
private LoginPage loginPage; private LoginPage loginPage;
private LoginTotpPage loginTotpPage;
private AppPage appPage; private AppPage appPage;
private String refreshToken; private String refreshToken;
@ -36,18 +38,47 @@ public class TestAppHelper {
this.loginPage = loginPage; this.loginPage = loginPage;
this.appPage = appPage; this.appPage = appPage;
} }
public TestAppHelper(OAuthClient oauth, LoginPage loginPage, LoginTotpPage loginTotpPage, AppPage appPage) {
this.oauth = oauth;
this.loginPage = loginPage;
this.loginTotpPage = loginTotpPage;
this.appPage = appPage;
}
public boolean login(String username, String password) throws URISyntaxException, IOException { public boolean login(String username, String password) throws URISyntaxException, IOException {
loginPage.open(); startLogin(username, password);
loginPage.login(username, password);
if (loginPage.isCurrent()) { if (loginPage.isCurrent()) {
return false; return false;
} }
completeLogin();
return appPage.isCurrent();
}
public boolean startLogin(String username, String password) {
loginPage.open();
loginPage.login(username, password);
return appPage.isCurrent();
}
public void completeLogin() {
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password"); OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
refreshToken = tokenResponse.getRefreshToken(); refreshToken = tokenResponse.getRefreshToken();
}
public boolean login(String username, String password, String otp) throws URISyntaxException, IOException {
startLogin(username, password);
loginTotpPage.login(otp);
if (loginTotpPage.isCurrent()) {
return false;
}
completeLogin();
return appPage.isCurrent(); return appPage.isCurrent();
} }