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

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

View File

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

View File

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

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

View File

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

View File

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

View File

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