/*
* 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.adapter;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.common.util.Time;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.KeycloakServer;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.WebDriver;
import javax.ws.rs.core.UriBuilder;
import java.net.URL;
/**
* KEYCLOAK-702
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class CookieTokenStoreAdapterTest {
public static final String LOGIN_URL = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString();
@ClassRule
public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
@Override
protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
// Other tests may left Time offset uncleared, which could cause issues
Time.setOffset(0);
RealmRepresentation representation = KeycloakServer.loadJson(getClass().getResourceAsStream("/adapter-test/demorealm.json"), RealmRepresentation.class);
manager.importRealm(representation);
URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
createApplicationDeployment()
.name("customer-portal").contextPath("/customer-portal")
.servletClass(CustomerServlet.class).adapterConfigPath(url.getPath())
.role("user").deployApplication();
url = getClass().getResource("/adapter-test/cust-app-cookie-keycloak.json");
createApplicationDeployment()
.name("customer-cookie-portal").contextPath("/customer-cookie-portal")
.servletClass(CustomerServlet.class).adapterConfigPath(url.getPath())
.role("user").deployApplication();
url = getClass().getResource("/adapter-test/customer-db-keycloak.json");
createApplicationDeployment()
.name("customer-db").contextPath("/customer-db")
.servletClass(CustomerDatabaseServlet.class).adapterConfigPath(url.getPath())
.role("user")
.errorPage(null).deployApplication();
}
};
@Rule
public WebRule webRule = new WebRule(this);
@WebResource
protected WebDriver driver;
@WebResource
protected OAuthClient oauth;
@WebResource
protected LoginPage loginPage;
@Test
public void testTokenInCookieSSO() throws Throwable {
// Login
String tokenCookie = loginToCustomerCookiePortal();
// SSO to second app
driver.navigate().to("http://localhost:8081/customer-portal");
assertLogged();
// return to customer-cookie-portal and assert still same cookie (accessToken didn't expire)
driver.navigate().to("http://localhost:8081/customer-cookie-portal");
assertLogged();
String tokenCookie2 = driver.manage().getCookieNamed(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE).getValue();
Assert.assertEquals(tokenCookie, tokenCookie2);
// Logout with httpServletRequest
logoutFromCustomerCookiePortal();
// Also should be logged-out from the second app
driver.navigate().to("http://localhost:8081/customer-portal");
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
}
@Test
public void testTokenInCookieRefresh() throws Throwable {
try {
// Set token timeout 1 sec
KeycloakSession session = keycloakRule.startSession();
RealmModel realm = session.realms().getRealmByName("demo");
int originalTokenTimeout = realm.getAccessTokenLifespan();
realm.setAccessTokenLifespan(3);
session.getTransactionManager().commit();
session.close();
// login to customer-cookie-portal
String tokenCookie1 = loginToCustomerCookiePortal();
// Simulate waiting 4 seconds (Running testsuite in real env like Wildfly or EAP may still require to do Thread.sleep to really wait 4 seconds...)
Time.setOffset(4);
//Thread.sleep(4000);
// assert cookie was refreshed
driver.navigate().to("http://localhost:8081/customer-cookie-portal");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-cookie-portal");
assertLogged();
String tokenCookie2 = driver.manage().getCookieNamed(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE).getValue();
Assert.assertNotEquals(tokenCookie1, tokenCookie2);
// login to 2nd app and logout from it
driver.navigate().to("http://localhost:8081/customer-portal");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
assertLogged();
driver.navigate().to("http://localhost:8081/customer-portal/logout");
Assert.assertTrue(driver.getPageSource().contains("servlet logout ok"));
driver.navigate().to("http://localhost:8081/customer-portal");
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
// Simulate another 4 seconds
Time.setOffset(8);
// assert not logged in customer-cookie-portal
driver.navigate().to("http://localhost:8081/customer-cookie-portal");
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
// Change timeout back
Time.setOffset(0);
session = keycloakRule.startSession();
realm = session.realms().getRealmByName("demo");
realm.setAccessTokenLifespan(originalTokenTimeout);
session.getTransactionManager().commit();
session.close();
} finally {
Time.setOffset(0);
}
}
@Test
public void testInvalidTokenCookie() throws Throwable {
// Login
String tokenCookie = loginToCustomerCookiePortal();
String changedTokenCookie = tokenCookie.replace("a", "b");
// change cookie to invalid value
driver.manage().addCookie(new Cookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, changedTokenCookie, "/customer-cookie-portal"));
// visit page and assert re-logged and cookie was refreshed
driver.navigate().to("http://localhost:8081/customer-cookie-portal");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-cookie-portal");
String currentCookie = driver.manage().getCookieNamed(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE).getValue();
Assert.assertNotEquals(currentCookie, tokenCookie);
Assert.assertNotEquals(currentCookie, changedTokenCookie);
// logout
logoutFromCustomerCookiePortal();
}
// login to customer-cookie-portal and return the KEYCLOAK_ADAPTER_STATE cookie established on adapter
private String loginToCustomerCookiePortal() {
driver.navigate().to("http://localhost:8081/customer-cookie-portal");
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
loginPage.login("bburke@redhat.com", "password");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-cookie-portal");
assertLogged();
// Assert no JSESSIONID cookie
Assert.assertNull(driver.manage().getCookieNamed("JSESSIONID"));
return driver.manage().getCookieNamed(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE).getValue();
}
private void logoutFromCustomerCookiePortal() {
driver.navigate().to("http://localhost:8081/customer-cookie-portal/logout");
Assert.assertTrue(driver.getPageSource().contains("servlet logout ok"));
Assert.assertNull(driver.manage().getCookieNamed(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE));
driver.navigate().to("http://localhost:8081/customer-cookie-portal");
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
}
private void assertLogged() {
String pageSource = driver.getPageSource();
Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
}
}