/*
* 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.forms;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.io.IOUtils;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.authentication.authenticators.browser.ScriptBasedAuthenticatorFactory;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.util.ExecutionBuilder;
import org.keycloak.testsuite.util.FlowBuilder;
import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.UserBuilder;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.Map;
/**
* Tests for {@link org.keycloak.authentication.authenticators.browser.ScriptBasedAuthenticator}
*
* @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
*/
public class ScriptAuthenticatorTest extends AbstractFlowTest {
@Page
protected LoginPage loginPage;
@Rule
public AssertEvents events = new AssertEvents(this);
private AuthenticationFlowRepresentation flow;
public static final String EXECUTION_ID = "scriptAuth";
@BeforeClass
public static void enabled() {
ProfileAssume.assumePreview();
}
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
UserRepresentation failUser = UserBuilder.create()
.id("fail")
.username("fail")
.email("fail@test.com")
.enabled(true)
.password("password")
.build();
UserRepresentation okayUser = UserBuilder.create()
.id("user")
.username("user")
.email("user@test.com")
.enabled(true)
.password("password")
.build();
RealmBuilder.edit(testRealm)
.user(failUser)
.user(okayUser);
}
@Before
public void configureFlows() throws Exception {
if (testContext.isInitialized()) {
return;
}
String scriptFlow = "scriptBrowser";
AuthenticationFlowRepresentation scriptBrowserFlow = FlowBuilder.create()
.alias(scriptFlow)
.description("dummy pass through registration")
.providerId("basic-flow")
.topLevel(true)
.builtIn(false)
.build();
Response createFlowResponse = testRealm().flows().createFlow(scriptBrowserFlow);
Assert.assertEquals(201, createFlowResponse.getStatus());
RealmRepresentation realm = testRealm().toRepresentation();
realm.setBrowserFlow(scriptFlow);
realm.setDirectGrantFlow(scriptFlow);
testRealm().update(realm);
this.flow = findFlowByAlias(scriptFlow);
AuthenticationExecutionRepresentation usernamePasswordFormExecution = ExecutionBuilder.create()
.id("username password form")
.parentFlow(this.flow.getId())
.requirement(AuthenticationExecutionModel.Requirement.REQUIRED.name())
.authenticator(UsernamePasswordFormFactory.PROVIDER_ID)
.build();
AuthenticationExecutionRepresentation authScriptExecution = ExecutionBuilder.create()
.id(EXECUTION_ID)
.parentFlow(this.flow.getId())
.requirement(AuthenticationExecutionModel.Requirement.REQUIRED.name())
.authenticator(ScriptBasedAuthenticatorFactory.PROVIDER_ID)
.build();
Response addExecutionResponse = testRealm().flows().addExecution(usernamePasswordFormExecution);
Assert.assertEquals(201, addExecutionResponse.getStatus());
addExecutionResponse.close();
addExecutionResponse = testRealm().flows().addExecution(authScriptExecution);
Assert.assertEquals(201, addExecutionResponse.getStatus());
addExecutionResponse.close();
testContext.setInitialized(true);
}
/**
* KEYCLOAK-3491
*/
@Test
public void loginShouldWorkWithScriptAuthenticator() {
addConfigFromFile("/scripts/authenticator-example.js");
loginPage.open();
loginPage.login("user", "password");
events.expectLogin().user("user").detail(Details.USERNAME, "user").assertEvent();
}
/**
* KEYCLOAK-3491
*/
@Test
public void loginShouldFailWithScriptAuthenticator() {
addConfigFromFile("/scripts/authenticator-example.js");
loginPage.open();
loginPage.login("fail", "password");
events.expect(EventType.LOGIN_ERROR).user((String) null).error(Errors.USER_NOT_FOUND).assertEvent();
}
/**
* KEYCLOAK-4505
*/
@Test
public void scriptWithClientSession() {
addConfigFromFile("/scripts/client-session-test.js", ImmutableMap.of(
"realm", "test",
"clientId", "test-app",
"authMethod", "openid-connect"));
loginPage.open();
loginPage.login("user", "password");
events.expectLogin().user("user").detail(Details.USERNAME, "user").assertEvent();
}
private void addConfigFromFile(String filename) {
addConfigFromFile(filename, null);
}
private void addConfigFromFile(String filename, Map<String, String> parameters) {
String alias = filename.substring(filename.lastIndexOf("/") + 1);
String script = loadFile(filename, parameters);
Response newExecutionConfigResponse = testRealm().flows().
newExecutionConfig(EXECUTION_ID, createScriptAuthConfig(EXECUTION_ID, alias, script, "script based authenticator"));
newExecutionConfigResponse.close();
Assert.assertEquals(201, newExecutionConfigResponse.getStatus());
}
private String loadFile(String filename, Map<String, String> parameters) {
String script = null;
try {
script = IOUtils.toString(getClass().getResourceAsStream(filename));
} catch (IOException e) {
throw new RuntimeException(e);
}
if (parameters != null) {
for (Map.Entry<String, String> entry : parameters.entrySet()) {
script = script.replaceAll("\\$\\{" + entry.getKey() + "}", entry.getValue());
}
}
return script;
}
private AuthenticatorConfigRepresentation createScriptAuthConfig(String alias, String scriptName, String script, String scriptDescription) {
AuthenticatorConfigRepresentation configRep = new AuthenticatorConfigRepresentation();
configRep.setAlias(alias);
configRep.getConfig().put("scriptCode", script);
configRep.getConfig().put("scriptName", scriptName);
configRep.getConfig().put("scriptDescription", scriptDescription);
return configRep;
}
}