Merge pull request #2607 from vramik/KEYCLOAK-2757

KEYCLOAK-2757 Add tests to cover custom auth flows
This commit is contained in:
Bill Burke 2016-04-15 16:28:05 -04:00
commit fdcc2d32f6
5 changed files with 557 additions and 1 deletions

View file

@ -36,6 +36,8 @@ public class LoginForm extends Form {
private AccountFields accountFields;
@Page
private PasswordFields passwordFields;
@Page
private TotpSetupForm totpForm;
@FindBy(name = "login")
private WebElement loginButton;
@ -119,5 +121,35 @@ public class LoginForm extends Form {
public void waitForLoginButtonPresent() {
waitUntilElement(loginButton).is().present();
}
public TotpSetupForm totpForm() {
return totpForm;
}
public class TotpSetupForm extends Form {
@FindBy(id = "totp")
private WebElement totpInputField;
@FindBy(id = "totpSecret")
private WebElement totpSecret;
@FindBy(xpath = ".//input[@value='Submit']")
private WebElement submit;
public void waitForTotpInputFieldPresent() {
waitUntilElement(totpInputField).is().present();
}
public void setTotp(String value) {
setInputValue(totpInputField, value);
}
public String getTotpSecret() {
return totpSecret.getAttribute(VALUE);
}
public void submit() {
submit.click();
}
}
}

View file

@ -0,0 +1,34 @@
/*
* 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.auth.page.login;
import org.keycloak.testsuite.auth.page.login.LoginForm.TotpSetupForm;
import org.openqa.selenium.support.FindBy;
/**
*
* @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
*/
public class OneTimeCode extends Authenticate {
@FindBy(id = "kc-totp-login-form")
private TotpSetupForm form;
public TotpSetupForm form() {
return form;
}
}

View file

@ -0,0 +1,75 @@
/*
* 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.account.custom;
import java.util.List;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Response;
import org.junit.Before;
import org.keycloak.admin.client.resource.AuthenticationManagementResource;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.testsuite.account.AbstractAccountManagementTest;
/**
*
* @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
*/
public abstract class AbstractCustomAccountManagementTest extends AbstractAccountManagementTest {
private AuthenticationManagementResource authMgmtResource;
@Override
public void setDefaultPageUriParameters() {
super.setDefaultPageUriParameters();
}
@Before
public void beforeTest() {
authMgmtResource = testRealmResource().flows();
}
protected AuthenticationManagementResource getAuthMgmtResource() {
return authMgmtResource;
}
protected void updateRequirement(String flowAlias, String provider, AuthenticationExecutionModel.Requirement requirement) {
AuthenticationExecutionInfoRepresentation exec = getExecution(flowAlias, provider);
exec.setRequirement(requirement.name());
authMgmtResource.updateExecutions(flowAlias, exec);
}
protected AuthenticationExecutionInfoRepresentation getExecution(String flowAlias, String provider) {
Response response = authMgmtResource.getExecutions(flowAlias);
List<AuthenticationExecutionInfoRepresentation> executionReps = response.readEntity(
new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
});
response.close();
for (AuthenticationExecutionInfoRepresentation exec : executionReps) {
if (provider.equals(exec.getProviderId())) {
return exec;
}
}
return null;
}
}

View file

@ -0,0 +1,77 @@
/*
* 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.account.custom;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Test;
import org.junit.Before;
import org.keycloak.models.AuthenticationExecutionModel.Requirement;
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
import org.keycloak.testsuite.console.page.AdminConsole;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
/**
*
* @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
*/
public class CustomAuthFlowCookieTest extends AbstractCustomAccountManagementTest {
@Page
private AdminConsole testRealmAdminConsolePage;
@Override
public void setDefaultPageUriParameters() {
super.setDefaultPageUriParameters();
}
@Before
@Override
public void beforeTest() {
super.beforeTest();
testRealmAdminConsolePage.setAdminRealm(TEST);
}
@Test
public void cookieAlternative() {
//test default setting of cookie provider
//login to account management
testRealmAccountManagementPage.navigateTo();
testRealmLoginPage.form().login(testUser);
//check SSO is working
//navigate to realm-management (different client of the same realm) and verify user is logged in
testRealmAdminConsolePage.navigateTo();
assertCurrentUrlStartsWith(testRealmAdminConsolePage);
}
@Test
public void disabledCookie() {
//disable cookie
updateRequirement("browser", "auth-cookie", Requirement.DISABLED);
//login to account management
testRealmAccountManagementPage.navigateTo();
testRealmLoginPage.form().login(testUser);
//SSO shouln't work
//navigate to realm-management and verify user is not logged in
testRealmAdminConsolePage.navigateTo();
assertCurrentUrlStartsWithLoginUrlOf(testRealmLoginPage);
}
}

View file

@ -0,0 +1,338 @@
/*
* 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.account.custom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ws.rs.core.Response;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.Before;
import static org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticator.DEFAULT_OTP_OUTCOME;
import static org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticator.FORCE;
import static org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticator.FORCE_OTP_FOR_HTTP_HEADER;
import static org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticator.FORCE_OTP_ROLE;
import static org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticator.OTP_CONTROL_USER_ATTRIBUTE;
import static org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticator.SKIP;
import static org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticator.SKIP_OTP_FOR_HTTP_HEADER;
import static org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticator.SKIP_OTP_ROLE;
import org.keycloak.models.AuthenticationExecutionModel.Requirement;
import static org.keycloak.models.UserModel.RequiredAction.CONFIGURE_TOTP;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.testsuite.admin.Users;
import org.keycloak.testsuite.auth.page.login.OneTimeCode;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
/**
*
* @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
*/
public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
private final TimeBasedOTP totp = new TimeBasedOTP();
@Page
private OneTimeCode testLoginOneTimeCodePage;
@Override
public void setDefaultPageUriParameters() {
super.setDefaultPageUriParameters();
testLoginOneTimeCodePage.setAuthRealm(testRealmPage);
}
@Before
@Override
public void beforeTest() {
super.beforeTest();
//set configure TOTP as required action to test user
List<String> requiredActions = new ArrayList<>();
requiredActions.add(CONFIGURE_TOTP.name());
testUser.setRequiredActions(requiredActions);
testRealmResource().users().get(testUser.getId()).update(testUser);
//configure OTP for test user
testRealmAccountManagementPage.navigateTo();
testRealmLoginPage.form().login(testUser);
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
String totpSecret = testRealmLoginPage.form().totpForm().getTotpSecret();
testRealmLoginPage.form().totpForm().setTotp(totp.generateTOTP(totpSecret));
testRealmLoginPage.form().totpForm().submit();
testRealmAccountManagementPage.signOut();
//verify that user has OTP configured
testUser = testRealmResource().users().get(testUser.getId()).toRepresentation();
Users.setPasswordFor(testUser, PASSWORD);
assertTrue(testUser.getRequiredActions().isEmpty());
}
@Test
public void requireOTPTest() {
updateRequirement("browser", "auth-otp-form", Requirement.REQUIRED);
testRealmAccountManagementPage.navigateTo();
testRealmLoginPage.form().login(testUser);
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
//verify that the page is login page, not totp setup
assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
}
@Test
public void conditionalOTPNoDefault() {
//prepare config - no configuration specified
Map<String, String> config = new HashMap<>();
setConditionalOTPForm(config);
//test OTP is required
testRealmAccountManagementPage.navigateTo();
testRealmLoginPage.form().login(testUser);
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
//verify that the page is login page, not totp setup
assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
}
@Test
public void conditionalOTPDefaultSkip() {
//prepare config - default skip
Map<String, String> config = new HashMap<>();
config.put(DEFAULT_OTP_OUTCOME, SKIP);
setConditionalOTPForm(config);
//test OTP is skipped
testRealmAccountManagementPage.navigateTo();
testRealmLoginPage.form().login(testUser);
assertCurrentUrlStartsWith(testRealmAccountManagementPage);
}
@Test
public void conditionalOTPDefaultForce() {
//prepare config - default force
Map<String, String> config = new HashMap<>();
config.put(DEFAULT_OTP_OUTCOME, FORCE);
setConditionalOTPForm(config);
//test OTP is forced
testRealmAccountManagementPage.navigateTo();
testRealmLoginPage.form().login(testUser);
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
//verify that the page is login page, not totp setup
assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
}
@Test
public void conditionalOTPUserAttributeSkip() {
//prepare config - user attribute, default to force
Map<String, String> config = new HashMap<>();
config.put(OTP_CONTROL_USER_ATTRIBUTE, "userSkipAttribute");
config.put(DEFAULT_OTP_OUTCOME, FORCE);
setConditionalOTPForm(config);
//add skip user attribute to user
Map<String, Object> userAttributes = new HashMap<>();
List<String> attributeValues = new ArrayList<>();
attributeValues.add("skip");
userAttributes.put("userSkipAttribute", attributeValues);
testUser.setAttributes(userAttributes);
testRealmResource().users().get(testUser.getId()).update(testUser);
//test OTP is skipped
testRealmAccountManagementPage.navigateTo();
testRealmLoginPage.form().login(testUser);
assertCurrentUrlStartsWith(testRealmAccountManagementPage);
}
@Test
public void conditionalOTPUserAttributeForce() {
//prepare config - user attribute, default to skip
Map<String, String> config = new HashMap<>();
config.put(OTP_CONTROL_USER_ATTRIBUTE, "userSkipAttribute");
config.put(DEFAULT_OTP_OUTCOME, SKIP);
setConditionalOTPForm(config);
//add force user attribute to user
Map<String, Object> userAttributes = new HashMap<>();
List<String> attributeValues = new ArrayList<>();
attributeValues.add("force");
userAttributes.put("userSkipAttribute", attributeValues);
testUser.setAttributes(userAttributes);
testRealmResource().users().get(testUser.getId()).update(testUser);
//test OTP is required
testRealmAccountManagementPage.navigateTo();
testRealmLoginPage.form().login(testUser);
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
//verify that the page is login page, not totp setup
assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
}
@Test
public void conditionalOTPRoleSkip() {
//prepare config - role, default to force
Map<String, String> config = new HashMap<>();
config.put(SKIP_OTP_ROLE, "otp_role");
config.put(DEFAULT_OTP_OUTCOME, FORCE);
setConditionalOTPForm(config);
//create role
RoleRepresentation role = new RoleRepresentation("otp_role", "", false);
testRealmResource().roles().create(role);
//obtain id
role = testRealmResource().roles().get("otp_role").toRepresentation();
//add role to user
List<RoleRepresentation> realmRoles = new ArrayList<>();
realmRoles.add(role);
testRealmResource().users().get(testUser.getId()).roles().realmLevel().add(realmRoles);
//test OTP is skipped
testRealmAccountManagementPage.navigateTo();
testRealmLoginPage.form().login(testUser);
assertCurrentUrlStartsWith(testRealmAccountManagementPage);
}
@Test
public void conditionalOTPRoleForce() {
//prepare config - role, default to skip
Map<String, String> config = new HashMap<>();
config.put(FORCE_OTP_ROLE, "otp_role");
config.put(DEFAULT_OTP_OUTCOME, SKIP);
setConditionalOTPForm(config);
//create role
RoleRepresentation role = new RoleRepresentation("otp_role", "", false);
testRealmResource().roles().create(role);
//obtain id
role = testRealmResource().roles().get("otp_role").toRepresentation();
//add role to user
List<RoleRepresentation> realmRoles = new ArrayList<>();
realmRoles.add(role);
testRealmResource().users().get(testUser.getId()).roles().realmLevel().add(realmRoles);
//test OTP is required
testRealmAccountManagementPage.navigateTo();
testRealmLoginPage.form().login(testUser);
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
//verify that the page is login page, not totp setup
assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
}
@Test
public void conditionalOTPRequestHeaderSkip() {
//prepare config - request header skip, default to force
Map<String, String> config = new HashMap<>();
String port = System.getProperty("auth.server.http.port", "8180");
config.put(SKIP_OTP_FOR_HTTP_HEADER, "Host: localhost:" + port);
config.put(DEFAULT_OTP_OUTCOME, FORCE);
setConditionalOTPForm(config);
//test OTP is skipped
testRealmAccountManagementPage.navigateTo();
testRealmLoginPage.form().login(testUser);
assertCurrentUrlStartsWith(testRealmAccountManagementPage);
}
@Test
public void conditionalOTPRequestHeaderForce() {
//prepare config - equest header force, default to skip
Map<String, String> config = new HashMap<>();
String port = System.getProperty("auth.server.http.port", "8180");
config.put(FORCE_OTP_FOR_HTTP_HEADER, "Host: localhost:" + port);
config.put(DEFAULT_OTP_OUTCOME, SKIP);
setConditionalOTPForm(config);
//test OTP is required
testRealmAccountManagementPage.navigateTo();
testRealmLoginPage.form().login(testUser);
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
//verify that the page is login page, not totp setup
assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
}
private void setConditionalOTPForm(Map<String, String> config) {
String flowAlias = "ConditionalOTPFlow";
String provider = "auth-conditional-otp-form";
//create flow
AuthenticationFlowRepresentation flow = new AuthenticationFlowRepresentation();
flow.setAlias(flowAlias);
flow.setDescription("");
flow.setProviderId("basic-flow");
flow.setTopLevel(true);
flow.setBuiltIn(false);
Response response = getAuthMgmtResource().createFlow(flow);
Assert.assertEquals(flowAlias + " create success", 201, response.getStatus());
response.close();
//add execution - username-password form
Map<String, String> data = new HashMap<>();
data.put("provider", "auth-username-password-form");
getAuthMgmtResource().addExecution(flowAlias, data);
//set username-password requirement to required
updateRequirement(flowAlias, "auth-username-password-form", Requirement.REQUIRED);
//add execution - conditional OTP
data.clear();
data.put("provider", provider);
getAuthMgmtResource().addExecution(flowAlias, data);
//set Conditional OTP requirement to required
updateRequirement(flowAlias, provider, Requirement.REQUIRED);
//update realm browser flow
RealmRepresentation realm = testRealmResource().toRepresentation();
realm.setBrowserFlow(flowAlias);
testRealmResource().update(realm);
//get executionId
String executionId = getExecution(flowAlias, provider).getId();
//prepare auth config
AuthenticatorConfigRepresentation authConfig = new AuthenticatorConfigRepresentation();
authConfig.setAlias("Config alias");
authConfig.setConfig(config);
//add auth config to the execution
response = getAuthMgmtResource().newExecutionConfig(executionId, authConfig);
Assert.assertEquals("new execution success", 201, response.getStatus());
response.close();
}
}