parent
6014070431
commit
9bb18400ad
10 changed files with 255 additions and 452 deletions
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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.SAMLPostLogin;
|
||||
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 java.text.MessageFormat;
|
||||
|
|
|
@ -34,7 +34,6 @@ import org.keycloak.admin.client.resource.AuthenticationManagementResource;
|
|||
import org.keycloak.admin.client.resource.RealmsResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
|
|
|
@ -24,7 +24,6 @@ import org.junit.Assert;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
|
@ -37,11 +36,10 @@ import org.keycloak.representations.idm.RealmRepresentation;
|
|||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
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.LoginTotpPage;
|
||||
import org.keycloak.testsuite.pages.RegisterPage;
|
||||
import org.keycloak.testsuite.util.AccountHelper;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
@ -93,9 +91,6 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
|||
@Page
|
||||
protected LoginConfigTotpPage totpPage;
|
||||
|
||||
@Page
|
||||
protected AccountTotpPage accountTotpPage;
|
||||
|
||||
@Page
|
||||
protected RegisterPage registerPage;
|
||||
|
||||
|
@ -111,7 +106,7 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
|||
|
||||
doAIA();
|
||||
|
||||
assertTrue(totpPage.isCurrent());
|
||||
totpPage.assertCurrent();
|
||||
|
||||
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
|
||||
|
||||
|
@ -374,7 +369,6 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
|||
}
|
||||
|
||||
@Test
|
||||
@DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228)
|
||||
public void setupTotpRegisteredAfterTotpRemoval() {
|
||||
// Register new user
|
||||
loginPage.open();
|
||||
|
@ -418,21 +412,11 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
|
|||
|
||||
// Login with one-time password
|
||||
loginTotpPage.login(totp.generateTOTP(totpCode));
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
|
||||
loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
|
||||
// Open account page
|
||||
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();
|
||||
// Remove google authenticator
|
||||
Assert.assertTrue(AccountHelper.deleteTotpAuthentication(testRealm(),"setupTotp2"));
|
||||
AccountHelper.logout(testRealm(),"setupTotp2");
|
||||
|
||||
// Try to login
|
||||
loginPage.open();
|
||||
|
|
|
@ -23,7 +23,6 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
|
@ -38,8 +37,6 @@ import org.keycloak.representations.idm.UserRepresentation;
|
|||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
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.RequestType;
|
||||
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.RegisterPage;
|
||||
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
|
||||
import org.keycloak.testsuite.util.AccountHelper;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
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>
|
||||
*/
|
||||
@DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228)
|
||||
public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
@Override
|
||||
|
@ -117,9 +114,6 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
|
|||
@Page
|
||||
protected LoginConfigTotpPage totpPage;
|
||||
|
||||
@Page
|
||||
protected AccountTotpPage accountTotpPage;
|
||||
|
||||
@Page
|
||||
protected RegisterPage registerPage;
|
||||
|
||||
|
@ -133,7 +127,7 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
String userId = events.expectRegister("setupTotp", "email@mail.com").assertEvent().getUserId();
|
||||
|
||||
assertTrue(totpPage.isCurrent());
|
||||
totpPage.assertCurrent();
|
||||
assertFalse(totpPage.isCancelDisplayed());
|
||||
|
||||
// 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();
|
||||
|
||||
assertTrue(totpPage.isCurrent());
|
||||
totpPage.assertCurrent();
|
||||
|
||||
// KEYCLOAK-11753 - Verify OTP label element present on "Configure OTP" required action form
|
||||
driver.findElement(By.id("userLabel"));
|
||||
|
@ -290,12 +284,9 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
|
|||
// Set OTP label to a custom value
|
||||
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()), customOtpLabel);
|
||||
|
||||
// Open account page & verify OTP authenticator with requested label was created
|
||||
accountTotpPage.open();
|
||||
accountTotpPage.assertCurrent();
|
||||
|
||||
String pageSource = driver.getPageSource();
|
||||
assertTrue(pageSource.contains(customOtpLabel));
|
||||
// Check if OTP credential is present
|
||||
Assert.assertTrue(AccountHelper.isTotpPresent(testRealm(), "setupTotpRegister"));
|
||||
Assert.assertTrue(AccountHelper.totpUserLabelComparator(testRealm(), "setupTotpRegister", customOtpLabel));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -450,18 +441,9 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
|
||||
// Open account page
|
||||
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();
|
||||
// Remove google authenticator
|
||||
Assert.assertTrue(AccountHelper.deleteTotpAuthentication(testRealm(),"setupTotp2"));
|
||||
AccountHelper.logout(testRealm(),"setupTotp2");
|
||||
|
||||
setOtpTimeOffset(TimeBasedOTP.DEFAULT_INTERVAL_SECONDS, totp);
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -18,53 +18,50 @@
|
|||
|
||||
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.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
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;
|
||||
import static org.wildfly.common.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
@DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228)
|
||||
public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
|
||||
public class BackwardsCompatibilityUserStorageTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
private String backwardsCompProviderId;
|
||||
|
||||
|
@ -77,15 +74,14 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
|
|||
@Page
|
||||
protected LoginTotpPage loginTotpPage;
|
||||
|
||||
@Page
|
||||
protected AccountTotpPage accountTotpSetupPage;
|
||||
|
||||
@Page
|
||||
protected LoginConfigTotpPage configureTotpRequiredActionPage;
|
||||
|
||||
|
||||
private TimeBasedOTP totp = new TimeBasedOTP();
|
||||
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void checkNotMapStorage() {
|
||||
ProfileAssume.assumeFeatureDisabled(Feature.MAP_STORAGE);
|
||||
|
@ -105,27 +101,29 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
|
|||
}
|
||||
|
||||
protected String addComponent(ComponentRepresentation component) {
|
||||
Response resp = testRealmResource().components().add(component);
|
||||
Response resp = testRealm().components().add(component);
|
||||
String id = ApiUtil.getCreatedId(resp);
|
||||
getCleanup().addComponentId(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
private void loginSuccessAndLogout(String username, String password) {
|
||||
testRealmAccountPage.navigateTo();
|
||||
loginPage.login(username, password);
|
||||
assertCurrentUrlStartsWith(testRealmAccountPage);
|
||||
testRealmAccountPage.logOut();
|
||||
private void loginSuccessAndLogout(String username, String password) throws URISyntaxException, IOException {
|
||||
TestAppHelper testAppHelper = new TestAppHelper(oauth, loginPage, appPage);
|
||||
|
||||
testAppHelper.login(username, password);
|
||||
appPage.assertCurrent();
|
||||
|
||||
assertTrue(testAppHelper.logout());
|
||||
}
|
||||
|
||||
public void loginBadPassword(String username) {
|
||||
testRealmAccountPage.navigateTo();
|
||||
testRealmLoginPage.form().login(username, "badpassword");
|
||||
assertCurrentUrlDoesntStartWith(testRealmAccountPage);
|
||||
loginPage.open();
|
||||
loginPage.login(username, "badpassword");
|
||||
loginPage.assertCurrent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoginSuccess() {
|
||||
public void testLoginSuccess() throws URISyntaxException, IOException {
|
||||
addUserAndResetPassword("tbrady", "goat");
|
||||
addUserAndResetPassword("tbrady2", "goat2");
|
||||
|
||||
|
@ -139,7 +137,7 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
|
|||
UserRepresentation user = new UserRepresentation();
|
||||
user.setEnabled(true);
|
||||
user.setUsername(username);
|
||||
Response response = testRealmResource().users().create(user);
|
||||
Response response = testRealm().users().create(user);
|
||||
String userId = ApiUtil.getCreatedId(response);
|
||||
|
||||
Assert.assertEquals(backwardsCompProviderId, new StorageId(userId).getProviderId());
|
||||
|
@ -150,14 +148,14 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
|
|||
passwordRep.setValue(password);
|
||||
passwordRep.setTemporary(false);
|
||||
|
||||
testRealmResource().users().get(userId).resetPassword(passwordRep);
|
||||
testRealm().users().get(userId).resetPassword(passwordRep);
|
||||
|
||||
return userId;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOTPUpdateAndLogin() {
|
||||
public void testOTPUpdateAndLogin() throws URISyntaxException, IOException {
|
||||
String userId = addUserAndResetPassword("otp1", "pass");
|
||||
getCleanup().addUserId(userId);
|
||||
|
||||
|
@ -171,58 +169,55 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
|
|||
assertUserDontHaveDBCredentials();
|
||||
assertUserHasOTPCredentialInUserStorage(true);
|
||||
|
||||
// Authenticate as the user with the hardcoded OTP. Should be supported
|
||||
loginPage.login("otp1", "pass");
|
||||
loginTotpPage.assertCurrent();
|
||||
loginTotpPage.login("123456");
|
||||
TestAppHelper testAppHelper = new TestAppHelper(oauth, loginPage, appPage);
|
||||
|
||||
assertCurrentUrlStartsWith(testRealmAccountPage);
|
||||
testRealmAccountPage.logOut();
|
||||
// Authenticate as the user with the hardcoded OTP. Should be supported
|
||||
testAppHelper.startLogin("otp1", "pass");
|
||||
loginTotpPage.login("123456");
|
||||
testAppHelper.completeLogin();
|
||||
|
||||
appPage.assertCurrent();
|
||||
|
||||
testAppHelper.logout();
|
||||
|
||||
// Authenticate as the user with bad OTP
|
||||
loginPage.login("otp1", "pass");
|
||||
testAppHelper.startLogin("otp1", "pass");
|
||||
loginTotpPage.assertCurrent();
|
||||
loginTotpPage.login("7123456");
|
||||
assertCurrentUrlDoesntStartWith(testRealmAccountPage);
|
||||
loginTotpPage.assertCurrent();
|
||||
Assert.assertNotNull(loginTotpPage.getInputError());
|
||||
|
||||
// Authenticate as the user with correct OTP
|
||||
loginTotpPage.login(totp.generateTOTP(totpSecret));
|
||||
assertCurrentUrlStartsWith(testRealmAccountPage);
|
||||
testRealmAccountPage.logOut();
|
||||
testAppHelper.completeLogin();
|
||||
appPage.assertCurrent();
|
||||
|
||||
assertTrue(testAppHelper.logout());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOTPSetupThroughAccountMgmtAndLogin() {
|
||||
public void testOTPSetupThroughAccountMgmtAndLogin() throws URISyntaxException, IOException {
|
||||
String userId = addUserAndResetPassword("otp1", "pass");
|
||||
getCleanup().addUserId(userId);
|
||||
|
||||
// Login as user to account mgmt
|
||||
accountTotpSetupPage.open();
|
||||
loginPage.login("otp1", "pass");
|
||||
|
||||
// Setup OTP
|
||||
String totpSecret = accountTotpSetupPage.getTotpSecret();
|
||||
accountTotpSetupPage.configure(totp.generateTOTP(totpSecret));
|
||||
String totpSecret = setupOTPForUserWithRequiredAction(userId);
|
||||
|
||||
assertUserDontHaveDBCredentials();
|
||||
assertUserHasOTPCredentialInUserStorage(true);
|
||||
|
||||
// Logout and assert user can login with hardcoded OTP
|
||||
accountTotpSetupPage.logout();
|
||||
loginPage.login("otp1", "pass");
|
||||
loginTotpPage.login("123456");
|
||||
assertCurrentUrlStartsWith(testRealmAccountPage);
|
||||
TestAppHelper testAppHelper = new TestAppHelper(oauth, loginPage, loginTotpPage, appPage);
|
||||
|
||||
// Login as user to account mgmt
|
||||
assertTrue(testAppHelper.login("otp1", "pass", "123456"));
|
||||
|
||||
// Logout and assert user can login with valid credential
|
||||
accountTotpSetupPage.logout();
|
||||
loginPage.login("otp1", "pass");
|
||||
loginTotpPage.login(totp.generateTOTP(totpSecret));
|
||||
assertCurrentUrlStartsWith(testRealmAccountPage);
|
||||
testAppHelper.logout();
|
||||
assertTrue(testAppHelper.login("otp1", "pass", totp.generateTOTP(totpSecret)));
|
||||
testAppHelper.logout();
|
||||
|
||||
// Delete OTP credential in account console
|
||||
accountTotpSetupPage.removeTotp();
|
||||
accountTotpSetupPage.logout();
|
||||
// Disable OTP credential in account console
|
||||
testRealm().users().get(userId).disableCredentialType(Collections.singletonList(OTPCredentialModel.TYPE));
|
||||
|
||||
assertUserDontHaveDBCredentials();
|
||||
assertUserHasOTPCredentialInUserStorage(false);
|
||||
|
@ -232,7 +227,7 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testDisableCredentialsInUserStorage() {
|
||||
public void testDisableCredentialsInUserStorage() throws URISyntaxException, IOException {
|
||||
String userId = addUserAndResetPassword("otp1", "pass");
|
||||
getCleanup().addUserId(userId);
|
||||
|
||||
|
@ -243,13 +238,13 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
|
|||
assertUserDontHaveDBCredentials();
|
||||
assertUserHasOTPCredentialInUserStorage(true);
|
||||
|
||||
UserResource user = testRealmResource().users().get(userId);
|
||||
UserResource user = testRealm().users().get(userId);
|
||||
|
||||
// Disable OTP credential for the user through REST endpoint
|
||||
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
|
||||
assertUserDontHaveDBCredentials();
|
||||
|
@ -266,29 +261,32 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
|
|||
getCleanup().addUserId(userId);
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
// 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
|
||||
UserResource user = testRealmResource().users().get(userId);
|
||||
UserResource user = testRealm().users().get(userId);
|
||||
UserRepresentation userRep = user.toRepresentation();
|
||||
userRep.setRequiredActions(Arrays.asList(UserModel.RequiredAction.CONFIGURE_TOTP.toString()));
|
||||
user.update(userRep);
|
||||
|
||||
TestAppHelper testAppHelper = new TestAppHelper(oauth, loginPage, appPage);
|
||||
|
||||
// Login as the user and setup OTP
|
||||
testRealmAccountPage.navigateTo();
|
||||
loginPage.login("otp1", "pass");
|
||||
testAppHelper.startLogin("otp1", "pass");
|
||||
|
||||
configureTotpRequiredActionPage.assertCurrent();
|
||||
String totpSecret = configureTotpRequiredActionPage.getTotpSecret();
|
||||
configureTotpRequiredActionPage.configure(totp.generateTOTP(totpSecret));
|
||||
assertCurrentUrlStartsWith(testRealmAccountPage);
|
||||
appPage.assertCurrent();
|
||||
|
||||
testAppHelper.completeLogin();
|
||||
|
||||
// Logout
|
||||
testRealmAccountPage.logOut();
|
||||
assertTrue(testAppHelper.logout());
|
||||
|
||||
return totpSecret;
|
||||
}
|
||||
|
@ -310,4 +308,9 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
|
|||
}, Boolean.class);
|
||||
Assert.assertEquals(expectedUserHasOTP, hasUserOTP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,14 +18,12 @@
|
|||
|
||||
package org.keycloak.testsuite.forms;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.UserManager;
|
||||
import org.keycloak.models.UserModel;
|
||||
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.RequiredActionProviderRepresentation;
|
||||
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.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.ErrorPage;
|
||||
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.PasswordPage;
|
||||
import org.keycloak.testsuite.pages.RegisterPage;
|
||||
import org.keycloak.testsuite.util.FlowUtil;
|
||||
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 org.keycloak.testsuite.util.*;
|
||||
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.util.Arrays;
|
||||
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.assertThat;
|
||||
|
||||
/**
|
||||
* 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:jlieskov@redhat.com">Jan Lieskovsky</a>
|
||||
*/
|
||||
public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycloakTest {
|
||||
public class ResetCredentialsAlternativeFlowsTest extends AbstractAppInitiatedActionTest {
|
||||
|
||||
private String userId;
|
||||
|
||||
|
@ -99,9 +85,6 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
|
|||
@Page
|
||||
protected LoginPasswordUpdatePage updatePasswordPage;
|
||||
|
||||
@Page
|
||||
protected AccountTotpPage accountTotpPage;
|
||||
|
||||
@Page
|
||||
protected LoginConfigTotpPage totpPage;
|
||||
|
||||
|
@ -136,6 +119,11 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
|
|||
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.
|
||||
//
|
||||
|
@ -236,7 +224,7 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
|
|||
loginUsernameOnlyPage.open();
|
||||
loginUsernameOnlyPage.login("login-test");
|
||||
|
||||
Assert.assertTrue(passwordPage.isCurrent());
|
||||
passwordPage.assertCurrent();
|
||||
|
||||
// Click "Forget password"
|
||||
passwordPage.clickResetPassword();
|
||||
|
@ -272,7 +260,7 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
|
|||
loginUsernameOnlyPage.open();
|
||||
loginUsernameOnlyPage.login(username);
|
||||
|
||||
Assert.assertTrue(passwordPage.isCurrent());
|
||||
passwordPage.assertCurrent();
|
||||
|
||||
// Click "Forget password"
|
||||
passwordPage.clickResetPassword();
|
||||
|
@ -344,7 +332,6 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
|
|||
|
||||
|
||||
@Test
|
||||
@DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228)
|
||||
public void resetCredentialsVerifyCustomOtpLabelSetProperly() {
|
||||
try {
|
||||
// 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
|
||||
loginPage.open();
|
||||
loginPage.login("login@test.com", "password");
|
||||
loginPage.login("login-test", "password");
|
||||
String code = new OAuthClient.AuthorizationEndpointResponse(oauth).getCode();
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
|
||||
|
||||
accountTotpPage.open();
|
||||
Assert.assertTrue(accountTotpPage.isCurrent());
|
||||
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
|
||||
oauth.idTokenHint(response.getIdToken()).openLogout();
|
||||
|
@ -375,26 +365,18 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
|
|||
loginPage.resetPassword();
|
||||
|
||||
// Should be on reset password page now. Provide email of the user & click Submit button
|
||||
Assert.assertTrue(resetPasswordPage.isCurrent());
|
||||
resetPasswordPage.changePassword("login@test.com");
|
||||
|
||||
// Since 'Send Reset Email' & 'Reset Password' authenticators got removed above,
|
||||
// the next action should be 'Reset OTP' -- verify that
|
||||
Assert.assertTrue(totpPage.isCurrent());
|
||||
resetPasswordPage.assertCurrent();
|
||||
resetPasswordPage.changePassword("login-test");
|
||||
|
||||
// Provide updated form of the OTP label, to be used within 'Reset OTP' (next) step
|
||||
customOtpLabel = "my-reset-otp-label";
|
||||
|
||||
// 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
|
||||
accountTotpPage.open();
|
||||
Assert.assertTrue(accountTotpPage.isCurrent());
|
||||
|
||||
// Verify OTP authenticator with requested label was created
|
||||
String pageSource = driver.getPageSource();
|
||||
Assert.assertTrue(pageSource.contains(customOtpLabel));
|
||||
// Check if OTP credential is present
|
||||
Assert.assertTrue(AccountHelper.totpUserLabelComparator(testRealm(), "login-test", customOtpLabel));
|
||||
|
||||
// Undo setup changes performed within the test
|
||||
} 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
|
||||
// (either via Account page or by registering new user), but required for each next created OTP credential
|
||||
@Test
|
||||
@DisableFeature(value = Profile.Feature.ACCOUNT2, skipRestart = true) // TODO remove this (KEYCLOAK-16228)
|
||||
public void deviceNameOptionalForFirstOTPCredentialButRequiredForEachNextOne() {
|
||||
// Enable 'Default Action' on 'Configure OTP' RA for the 'test' realm
|
||||
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
|
||||
loginPage.open();
|
||||
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
|
||||
final String emptyOtpLabel = "";
|
||||
accountTotpPage.configure(totp.generateTOTP(accountTotpPage.getTotpSecret()), emptyOtpLabel);
|
||||
|
||||
// Get the updated Account TOTP page source post OTP credential creation
|
||||
pageSource = driver.getPageSource();
|
||||
// Setup OTP
|
||||
doAIA();
|
||||
totpPage.assertCurrent();
|
||||
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()), emptyOtpLabel);
|
||||
assertKcActionStatus(SUCCESS);
|
||||
|
||||
// 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(""));
|
||||
accountTotpPage.removeTotp();
|
||||
Assert.assertTrue(AccountHelper.deleteTotpAuthentication(testRealm(), "login-test"));
|
||||
|
||||
// Logout
|
||||
driver.navigate().to(oauth.getLogoutUrl().build());
|
||||
|
@ -465,32 +434,20 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
|
|||
registerPage.assertCurrent();
|
||||
|
||||
registerPage.register("Bruce", "Wilson", "bwilson@keycloak.org", "bwilson", "password", "password");
|
||||
Assert.assertTrue(totpPage.isCurrent());
|
||||
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());
|
||||
totpPage.assertCurrent();
|
||||
|
||||
// Create OTP credential with empty label
|
||||
totpPage.configure(totp.generateTOTP(accountTotpPage.getTotpSecret()), emptyOtpLabel);
|
||||
|
||||
Assert.assertNull(totpPage.getAlertError());
|
||||
Assert.assertNull(totpPage.getInputCodeError());
|
||||
Assert.assertNull(totpPage.getInputLabelError());
|
||||
// Setup OTP
|
||||
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()), "");
|
||||
|
||||
// Assert user authenticated
|
||||
appPage.assertCurrent();
|
||||
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
||||
accountTotpPage.open();
|
||||
Assert.assertTrue(accountTotpPage.isCurrent());
|
||||
|
||||
// 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(""));;
|
||||
Assert.assertTrue(AccountHelper.isTotpPresent(testRealm(), "bwilson"));
|
||||
Assert.assertTrue(AccountHelper.totpUserLabelComparator(testRealm(), "bwilson", ""));
|
||||
|
||||
// Logout
|
||||
driver.navigate().to(oauth.getLogoutUrl().build());
|
||||
|
@ -504,49 +461,28 @@ public class ResetCredentialsAlternativeFlowsTest extends AbstractTestRealmKeycl
|
|||
loginPage.resetPassword();
|
||||
|
||||
// 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");
|
||||
|
||||
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
|
||||
// should fail with error since OTP label is required in this case already
|
||||
final String deviceNameLabelRequiredErrorMessage = "Please specify device name.";
|
||||
totpPage.configure(totp.generateTOTP(accountTotpPage.getTotpSecret()), emptyOtpLabel);
|
||||
Assert.assertTrue(totpPage.getInputLabelError().equals(deviceNameLabelRequiredErrorMessage));
|
||||
|
||||
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()), "");
|
||||
Assert.assertTrue(AccountHelper.totpCountEquals(testRealm(), "bwilson", 1));
|
||||
// Create 2nd OTP credential with valid (non-empty) Device Name label. This should pass
|
||||
final String secondOtpLabel = "My 2nd OTP device";
|
||||
totpPage.configure(totp.generateTOTP(accountTotpPage.getTotpSecret()), secondOtpLabel);
|
||||
|
||||
Assert.assertNull(totpPage.getAlertError());
|
||||
Assert.assertNull(totpPage.getInputCodeError());
|
||||
Assert.assertNull(totpPage.getInputLabelError());
|
||||
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()), secondOtpLabel);
|
||||
|
||||
// Assert user authenticated
|
||||
appPage.assertCurrent();
|
||||
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
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
|
||||
Assert.assertTrue(pageSource.contains(secondOtpLabel));
|
||||
Assert.assertTrue(AccountHelper.totpUserLabelComparator(testRealm(), "bwilson", secondOtpLabel));
|
||||
|
||||
// Remove both OTP credentials
|
||||
accountTotpPage.removeTotp();
|
||||
accountTotpPage.removeTotp();
|
||||
Assert.assertTrue(AccountHelper.deleteTotpAuthentication(testRealm(), "bwilson"));
|
||||
Assert.assertTrue(AccountHelper.deleteTotpAuthentication(testRealm(), "bwilson"));
|
||||
|
||||
// Logout
|
||||
driver.navigate().to(oauth.getLogoutUrl().build());
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.keycloak.admin.client.resource.RealmResource;
|
|||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.keycloak.models.credential.OTPCredentialModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
|
||||
|
@ -27,8 +28,19 @@ import java.util.Optional;
|
|||
|
||||
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) {
|
||||
UserResource user = realm.users().get(getUserId(realm, username));
|
||||
UserResource user = getUserResource(realm, username);
|
||||
|
||||
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) {
|
||||
UserResource user = realm.users().get(getUserId(realm, username));
|
||||
UserResource user = getUserResource(realm, username);
|
||||
List<Map<String, Object>> consents = user.getConsents();
|
||||
return consents;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static void logout(RealmResource realm, String username) {
|
||||
UserResource user = realm.users().get(getUserId(realm, username));
|
||||
UserResource user = getUserResource(realm, username);
|
||||
user.logout();
|
||||
}
|
||||
|
||||
private static String getUserId(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();
|
||||
return userRepresentation.getId();
|
||||
private static Optional<CredentialRepresentation> getOtpCredentials(UserResource user, String userLabel) {
|
||||
return user.credentials().stream().filter(c -> c.getType().equals(OTPCredentialModel.TYPE)).filter(l -> l.getUserLabel().equals(userLabel)).findFirst();
|
||||
}
|
||||
|
||||
private static Optional<CredentialRepresentation> getOtpCredentials(UserResource user) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.apache.http.client.methods.CloseableHttpResponse;
|
|||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.pages.LoginTotpPage;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
|
@ -28,6 +29,7 @@ import java.net.URISyntaxException;
|
|||
public class TestAppHelper {
|
||||
private OAuthClient oauth;
|
||||
private LoginPage loginPage;
|
||||
private LoginTotpPage loginTotpPage;
|
||||
private AppPage appPage;
|
||||
private String refreshToken;
|
||||
|
||||
|
@ -36,18 +38,47 @@ public class TestAppHelper {
|
|||
this.loginPage = loginPage;
|
||||
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 {
|
||||
loginPage.open();
|
||||
loginPage.login(username, password);
|
||||
startLogin(username, password);
|
||||
|
||||
if (loginPage.isCurrent()) {
|
||||
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);
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
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();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue