keycloak-aplcache
Changes
testsuite/integration-arquillian/test-apps/spring-boot-adapter/src/main/java/org/keycloak/AdminController.java 95(+89 -6)
testsuite/integration-arquillian/test-apps/spring-boot-adapter/src/main/resources/templates/linking.html 9(+9 -0)
testsuite/integration-arquillian/test-apps/spring-boot-adapter/src/main/resources/templates/session.html 9(+9 -0)
testsuite/integration-arquillian/tests/other/springboot-tests/src/main/java/org/keycloak/testsuite/springboot/LinkingPage.java 24(+24 -0)
testsuite/integration-arquillian/tests/other/springboot-tests/src/main/java/org/keycloak/testsuite/springboot/SessionPage.java 29(+29 -0)
testsuite/integration-arquillian/tests/other/springboot-tests/src/main/java/org/keycloak/testsuite/springboot/SpringAdminPage.java 4(+4 -0)
testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/AbstractSpringBootTest.java 81(+33 -48)
testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/AccountLinkSpringBootTest.java 560(+560 -0)
testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/BasicSpringBootTest.java 35(+35 -0)
Details
diff --git a/testsuite/integration-arquillian/test-apps/spring-boot-adapter/src/main/java/org/keycloak/AdminController.java b/testsuite/integration-arquillian/test-apps/spring-boot-adapter/src/main/java/org/keycloak/AdminController.java
index 3b9ccc4..2857172 100644
--- a/testsuite/integration-arquillian/test-apps/spring-boot-adapter/src/main/java/org/keycloak/AdminController.java
+++ b/testsuite/integration-arquillian/test-apps/spring-boot-adapter/src/main/java/org/keycloak/AdminController.java
@@ -1,28 +1,41 @@
package org.keycloak;
-import java.io.IOException;
-import java.util.Map;
-
-import javax.servlet.http.HttpServletRequest;
-
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.Time;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
+import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.util.JsonSerialization;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
-import org.springframework.util.NumberUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.WebRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+import java.util.UUID;
+
@Controller
@RequestMapping(path = "/admin")
public class AdminController {
+
+ private static Logger logger = LoggerFactory.getLogger(AdminController.class);
@RequestMapping(path = "/TokenServlet", method = RequestMethod.GET)
public String showTokens(WebRequest req, Model model, @RequestParam Map<String, String> attributes) throws IOException {
@@ -56,4 +69,74 @@ public class AdminController {
return "tokens";
}
+
+ @RequestMapping(path = "/SessionServlet", method = RequestMethod.GET)
+ public String sessionServlet(WebRequest webRequest, Model model) {
+ String counterString = (String) webRequest.getAttribute("counter", RequestAttributes.SCOPE_SESSION);
+ int counter = 0;
+ try {
+ counter = Integer.parseInt(counterString, 10);
+ }
+ catch (NumberFormatException ignored) {
+ }
+
+ model.addAttribute("counter", counter);
+
+ webRequest.setAttribute("counter", Integer.toString(counter+1), RequestAttributes.SCOPE_SESSION);
+
+ return "session";
+ }
+
+ @RequestMapping(path = "/LinkServlet", method = RequestMethod.GET)
+ public String tokenController(WebRequest webRequest,
+ @RequestParam Map<String, String> attributes,
+ Model model) {
+
+ ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
+ HttpSession httpSession = attr.getRequest().getSession(true);
+
+// response.addHeader("Cache-Control", "no-cache");
+
+ String responseAttr = attributes.get("response");
+
+ if (StringUtils.isEmpty(responseAttr)) {
+ String provider = attributes.get("provider");
+ String realm = attributes.get("realm");
+ KeycloakSecurityContext keycloakSession =
+ (KeycloakSecurityContext) webRequest.getAttribute(
+ KeycloakSecurityContext.class.getName(),
+ RequestAttributes.SCOPE_REQUEST);
+ AccessToken token = keycloakSession.getToken();
+ String clientId = token.getAudience()[0];
+ String nonce = UUID.randomUUID().toString();
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ String input = nonce + token.getSessionState() + clientId + provider;
+ byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8));
+ String hash = Base64Url.encode(check);
+ httpSession.setAttribute("hash", hash);
+ String redirectUri = KeycloakUriBuilder.fromUri("http://localhost:8280/admin/LinkServlet")
+ .queryParam("response", "true").build().toString();
+ String accountLinkUrl = KeycloakUriBuilder.fromUri("http://localhost:8180/")
+ .path("/auth/realms/{realm}/broker/{provider}/link")
+ .queryParam("nonce", nonce)
+ .queryParam("hash", hash)
+ .queryParam("client_id", token.getIssuedFor())
+ .queryParam("redirect_uri", redirectUri).build(realm, provider).toString();
+
+ return "redirect:" + accountLinkUrl;
+ } else {
+ String error = attributes.get("link_error");
+ if (StringUtils.isEmpty(error))
+ model.addAttribute("error", "Account linked");
+ else
+ model.addAttribute("error", error);
+
+ return "linking";
+ }
+ }
}
diff --git a/testsuite/integration-arquillian/test-apps/spring-boot-adapter/src/main/resources/templates/linking.html b/testsuite/integration-arquillian/test-apps/spring-boot-adapter/src/main/resources/templates/linking.html
new file mode 100644
index 0000000..6c7d5bd
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/spring-boot-adapter/src/main/resources/templates/linking.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org/">
+<head>
+ <title>Linking page result</title>
+</head>
+<body>
+ <span id="error" th:text="${error}"/>
+</body>
+</html>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/spring-boot-adapter/src/main/resources/templates/session.html b/testsuite/integration-arquillian/test-apps/spring-boot-adapter/src/main/resources/templates/session.html
new file mode 100644
index 0000000..9a7e52f
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/spring-boot-adapter/src/main/resources/templates/session.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html xmlns:th="http://www.thymeleaf.org/">
+<head>
+ <title>session counter page</title>
+</head>
+ <body>
+ <span id="counter" th:text="${counter}"></span>
+ </body>
+</html>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/springboot-tests/src/main/java/org/keycloak/testsuite/springboot/LinkingPage.java b/testsuite/integration-arquillian/tests/other/springboot-tests/src/main/java/org/keycloak/testsuite/springboot/LinkingPage.java
new file mode 100644
index 0000000..620cc68
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/springboot-tests/src/main/java/org/keycloak/testsuite/springboot/LinkingPage.java
@@ -0,0 +1,24 @@
+package org.keycloak.testsuite.springboot;
+
+import org.keycloak.testsuite.pages.AbstractPage;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+public class LinkingPage extends AbstractPage {
+
+ @FindBy(id = "error")
+ private WebElement errorMessage;
+
+ @Override
+ public boolean isCurrent() {
+ return driver.getTitle().equalsIgnoreCase("linking page result");
+ }
+
+ @Override
+ public void open() throws Exception {
+ }
+
+ public String getErrorMessage() {
+ return errorMessage.getText();
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/other/springboot-tests/src/main/java/org/keycloak/testsuite/springboot/SessionPage.java b/testsuite/integration-arquillian/tests/other/springboot-tests/src/main/java/org/keycloak/testsuite/springboot/SessionPage.java
new file mode 100644
index 0000000..f8c420c
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/springboot-tests/src/main/java/org/keycloak/testsuite/springboot/SessionPage.java
@@ -0,0 +1,29 @@
+package org.keycloak.testsuite.springboot;
+
+import org.apache.commons.lang3.math.NumberUtils;
+import org.keycloak.testsuite.pages.AbstractPage;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+public class SessionPage extends AbstractPage {
+
+ public static final String PAGE_TITLE = "session counter page";
+
+ @FindBy(id = "counter")
+ private WebElement counterElement;
+
+ @Override
+ public boolean isCurrent() {
+ return driver.getTitle().equalsIgnoreCase(PAGE_TITLE);
+ }
+
+ @Override
+ public void open() throws Exception {
+ }
+
+ public int getCounter() {
+ String counterString = counterElement.getText();
+
+ return NumberUtils.toInt(counterString, 0);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/other/springboot-tests/src/main/java/org/keycloak/testsuite/springboot/SpringAdminPage.java b/testsuite/integration-arquillian/tests/other/springboot-tests/src/main/java/org/keycloak/testsuite/springboot/SpringAdminPage.java
index 8ce5e75..30e2b52 100644
--- a/testsuite/integration-arquillian/tests/other/springboot-tests/src/main/java/org/keycloak/testsuite/springboot/SpringAdminPage.java
+++ b/testsuite/integration-arquillian/tests/other/springboot-tests/src/main/java/org/keycloak/testsuite/springboot/SpringAdminPage.java
@@ -19,4 +19,8 @@ public class SpringAdminPage extends AbstractPage {
public void open() throws Exception {
}
+
+ public String getTestDivString() {
+ return testDiv.getText();
+ }
}
diff --git a/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/AbstractSpringBootTest.java b/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/AbstractSpringBootTest.java
index 5b15077..ad953b0 100644
--- a/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/AbstractSpringBootTest.java
+++ b/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/AbstractSpringBootTest.java
@@ -36,32 +36,27 @@ import org.openqa.selenium.By;
public abstract class AbstractSpringBootTest extends AbstractKeycloakTest {
- protected static final String REALM_ID = "cd8ee421-5100-41ba-95dd-b27c8e5cf042";
+ static final String REALM_ID = "cd8ee421-5100-41ba-95dd-b27c8e5cf042";
- protected static final String REALM_NAME = "test";
+ static final String REALM_NAME = "test";
- protected static final String CLIENT_ID = "spring-boot-app";
- protected static final String SECRET = "e3789ac5-bde6-4957-a7b0-612823dac101";
+ static final String CLIENT_ID = "spring-boot-app";
+ static final String SECRET = "e3789ac5-bde6-4957-a7b0-612823dac101";
- protected static final String APPLICATION_URL = "http://localhost:8280";
- protected static final String BASE_URL = APPLICATION_URL + "/admin";
+ static final String APPLICATION_URL = "http://localhost:8280";
+ static final String BASE_URL = APPLICATION_URL + "/admin";
- protected static final String USER_LOGIN = "testuser";
- protected static final String USER_EMAIL = "user@email.test";
- protected static final String USER_PASSWORD = "user-password";
+ static final String USER_LOGIN = "testuser";
+ static final String USER_EMAIL = "user@email.test";
+ static final String USER_PASSWORD = "user-password";
- protected static final String USER_LOGIN_2 = "testuser2";
- protected static final String USER_EMAIL_2 = "user2@email.test";
- protected static final String USER_PASSWORD_2 = "user2-password";
+ static final String CORRECT_ROLE = "admin";
- protected static final String CORRECT_ROLE = "admin";
- protected static final String INCORRECT_ROLE = "wrong-admin";
-
- protected static final String REALM_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5" +
+ static final String REALM_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5" +
"mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi7" +
"9NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB";
- protected static final String REALM_PRIVATE_KEY = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3Bj" +
+ static final String REALM_PRIVATE_KEY = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3Bj" +
"LGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vj" +
"O2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jY" +
"lQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn" +
@@ -72,16 +67,16 @@ public abstract class AbstractSpringBootTest extends AbstractKeycloakTest {
"N39fOYAlo+nTixgeW7X8Y=";
@Page
- protected LoginPage loginPage;
+ LoginPage loginPage;
@Page
- protected SpringApplicationPage applicationPage;
+ SpringApplicationPage applicationPage;
@Page
- protected SpringAdminPage adminPage;
+ SpringAdminPage adminPage;
@Page
- protected TokenPage tokenPage;
+ TokenPage tokenPage;
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
@@ -117,7 +112,7 @@ public abstract class AbstractSpringBootTest extends AbstractKeycloakTest {
return clientRepresentation;
}
- private void addUser(String login, String email, String password, String... roles) {
+ void addUser(String login, String email, String password, String... roles) {
UserRepresentation userRepresentation = new UserRepresentation();
userRepresentation.setUsername(login);
@@ -149,14 +144,14 @@ public abstract class AbstractSpringBootTest extends AbstractKeycloakTest {
return result;
}
- protected String logoutPage(String redirectUrl) {
+ String logoutPage(String redirectUrl) {
return getAuthRoot(suiteContext)
+ "/auth/realms/" + REALM_NAME
+ "/protocol/" + "openid-connect"
+ "/logout?redirect_uri=" + encodeUrl(redirectUrl);
}
- protected void setAdapterAndServerTimeOffset(int timeOffset, String url) {
+ void setAdapterAndServerTimeOffset(int timeOffset, String url) {
setTimeOffset(timeOffset);
String timeOffsetUri = UriBuilder.fromUri(url)
@@ -167,51 +162,41 @@ public abstract class AbstractSpringBootTest extends AbstractKeycloakTest {
WaitUtils.waitUntilElement(By.tagName("body")).is().visible();
}
- protected String getCorrectUserId() {
- return adminClient.realms().realm(REALM_NAME).users().search(USER_LOGIN)
- .get(0).getId();
- }
-
- @Before
public void createRoles() {
RealmResource realm = realmsResouce().realm(REALM_NAME);
RoleRepresentation correct = new RoleRepresentation(CORRECT_ROLE, CORRECT_ROLE, false);
realm.roles().create(correct);
-
- RoleRepresentation incorrect = new RoleRepresentation(INCORRECT_ROLE, INCORRECT_ROLE, false);
- realm.roles().create(incorrect);
}
- @Before
public void addUsers() {
addUser(USER_LOGIN, USER_EMAIL, USER_PASSWORD, CORRECT_ROLE);
- addUser(USER_LOGIN_2, USER_EMAIL_2, USER_PASSWORD_2, INCORRECT_ROLE);
}
- @After
public void cleanupUsers() {
- RealmResource providerRealm = adminClient.realm(REALM_NAME);
- UserRepresentation userRep = ApiUtil.findUserByUsername(providerRealm, USER_LOGIN);
- if (userRep != null) {
- providerRealm.users().get(userRep.getId()).remove();
- }
-
- RealmResource childRealm = adminClient.realm(REALM_NAME);
- userRep = ApiUtil.findUserByUsername(childRealm, USER_LOGIN_2);
+ RealmResource realmResource = adminClient.realm(REALM_NAME);
+ UserRepresentation userRep = ApiUtil.findUserByUsername(realmResource, USER_LOGIN);
if (userRep != null) {
- childRealm.users().get(userRep.getId()).remove();
+ realmResource.users().get(userRep.getId()).remove();
}
}
- @After
public void cleanupRoles() {
RealmResource realm = realmsResouce().realm(REALM_NAME);
RoleResource correctRole = realm.roles().get(CORRECT_ROLE);
correctRole.remove();
+ }
- RoleResource incorrectRole = realm.roles().get(INCORRECT_ROLE);
- incorrectRole.remove();
+ @Before
+ public void setUp() {
+ createRoles();
+ addUsers();
+ }
+
+ @After
+ public void tearDown() {
+ cleanupUsers();
+ cleanupRoles();
}
}
diff --git a/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/AccountLinkSpringBootTest.java b/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/AccountLinkSpringBootTest.java
new file mode 100644
index 0000000..64d62bd
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/AccountLinkSpringBootTest.java
@@ -0,0 +1,560 @@
+package org.keycloak.testsuite.springboot;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.models.Constants;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.idm.*;
+import org.keycloak.testsuite.ActionURIUtils;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
+import org.keycloak.testsuite.broker.BrokerTestTools;
+import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
+import org.keycloak.testsuite.util.OAuthClient;
+import org.keycloak.testsuite.util.WaitUtils;
+import org.keycloak.util.JsonSerialization;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.UriBuilder;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
+import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT_LINKS;
+import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient;
+
+public class AccountLinkSpringBootTest extends AbstractSpringBootTest {
+
+ private static final String PARENT_REALM = "parent-realm";
+
+ private static final String LINKING_URL = BASE_URL + "/LinkServlet";
+
+ private static final String PARENT_USERNAME = "parent-username";
+ private static final String PARENT_PASSWORD = "parent-password";
+
+ private static final String CHILD_USERNAME_1 = "child-username-1";
+ private static final String CHILD_PASSWORD_1 = "child-password-1";
+
+ private static final String CHILD_USERNAME_2 = "child-username-2";
+ private static final String CHILD_PASSWORD_2 = "child-password-2";
+
+ @Page
+ private LinkingPage linkingPage;
+
+ @Page
+ private AccountUpdateProfilePage profilePage;
+
+ @Page
+ private LoginUpdateProfilePage loginUpdateProfilePage;
+
+ @Page
+ private ErrorPage errorPage;
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ RealmRepresentation realm = new RealmRepresentation();
+ realm.setRealm(REALM_NAME);
+ realm.setEnabled(true);
+ realm.setPublicKey(REALM_PUBLIC_KEY);
+ realm.setPrivateKey(REALM_PRIVATE_KEY);
+ realm.setAccessTokenLifespan(600);
+ realm.setAccessCodeLifespan(10);
+ realm.setAccessCodeLifespanUserAction(6000);
+ realm.setSslRequired("external");
+ ClientRepresentation servlet = new ClientRepresentation();
+ servlet.setClientId(CLIENT_ID);
+ servlet.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+ servlet.setAdminUrl(LINKING_URL);
+ servlet.setDirectAccessGrantsEnabled(true);
+ servlet.setBaseUrl(LINKING_URL);
+ servlet.setRedirectUris(new LinkedList<>());
+ servlet.getRedirectUris().add(LINKING_URL + "/*");
+ servlet.setSecret(SECRET);
+ servlet.setFullScopeAllowed(true);
+ realm.setClients(new LinkedList<>());
+ realm.getClients().add(servlet);
+ testRealms.add(realm);
+
+ realm = new RealmRepresentation();
+ realm.setRealm(PARENT_REALM);
+ realm.setEnabled(true);
+
+ testRealms.add(realm);
+ }
+
+ @Override
+ public void addUsers() {
+ addIdpUser();
+ addChildUser();
+ }
+
+ @Override
+ public void cleanupUsers() {
+ }
+
+ @Override
+ public void createRoles() {
+ }
+
+ @Override
+ protected boolean isImportAfterEachMethod() {
+ return true;
+ }
+
+ public void addIdpUser() {
+ RealmResource realm = adminClient.realms().realm(PARENT_REALM);
+ UserRepresentation user = new UserRepresentation();
+ user.setUsername(PARENT_USERNAME);
+ user.setEnabled(true);
+ createUserAndResetPasswordWithAdminClient(realm, user, PARENT_PASSWORD);
+ }
+
+ private String childUserId = null;
+
+ public void addChildUser() {
+ RealmResource realm = adminClient.realms().realm(REALM_NAME);
+ UserRepresentation user = new UserRepresentation();
+ user.setUsername(CHILD_USERNAME_1);
+ user.setEnabled(true);
+ childUserId = createUserAndResetPasswordWithAdminClient(realm, user, CHILD_PASSWORD_1);
+ UserRepresentation user2 = new UserRepresentation();
+ user2.setUsername(CHILD_USERNAME_2);
+ user2.setEnabled(true);
+ String user2Id = createUserAndResetPasswordWithAdminClient(realm, user2, CHILD_PASSWORD_2);
+
+ // have to add a role as undertow default auth manager doesn't like "*". todo we can remove this eventually as undertow fixes this in later versions
+ realm.roles().create(new RoleRepresentation(CORRECT_ROLE, null, false));
+ RoleRepresentation role = realm.roles().get(CORRECT_ROLE).toRepresentation();
+ List<RoleRepresentation> roles = new LinkedList<>();
+ roles.add(role);
+ realm.users().get(childUserId).roles().realmLevel().add(roles);
+ realm.users().get(user2Id).roles().realmLevel().add(roles);
+ ClientRepresentation brokerService = realm.clients().findByClientId(Constants.BROKER_SERVICE_CLIENT_ID).get(0);
+ role = realm.clients().get(brokerService.getId()).roles().get(Constants.READ_TOKEN_ROLE).toRepresentation();
+ roles.clear();
+ roles.add(role);
+ realm.users().get(childUserId).roles().clientLevel(brokerService.getId()).add(roles);
+ realm.users().get(user2Id).roles().clientLevel(brokerService.getId()).add(roles);
+ }
+
+ @Before
+ public void createParentChild() {
+ BrokerTestTools.createKcOidcBroker(adminClient, REALM_NAME, PARENT_REALM, suiteContext);
+ }
+
+
+ @Test
+ public void testErrorConditions() throws Exception {
+ RealmResource realm = adminClient.realms().realm(REALM_NAME);
+ List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertTrue(links.isEmpty());
+
+ ClientRepresentation client = adminClient.realms().realm(REALM_NAME).clients().findByClientId(CLIENT_ID).get(0);
+
+ UriBuilder redirectUri = UriBuilder.fromUri(LINKING_URL).queryParam("response", "true");
+
+ UriBuilder directLinking = UriBuilder.fromUri(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth")
+ .path("realms/{child-realm}/broker/{provider}/link")
+ .queryParam("client_id", CLIENT_ID)
+ .queryParam("redirect_uri", redirectUri.build())
+ .queryParam("hash", Base64Url.encode("crap".getBytes()))
+ .queryParam("nonce", UUID.randomUUID().toString());
+
+ String linkUrl = directLinking
+ .build(REALM_NAME, PARENT_REALM).toString();
+
+ // test that child user cannot log into parent realm
+
+ navigateTo(linkUrl);
+ Assert.assertTrue(loginPage.isCurrent(REALM_NAME));
+ loginPage.login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
+
+ Assert.assertTrue(driver.getCurrentUrl().contains("link_error=not_logged_in"));
+
+ logoutAll();
+
+ // now log in
+
+ navigateTo(LINKING_URL + "?response=true");
+ Assert.assertTrue(loginPage.isCurrent(REALM_NAME));
+ loginPage.login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
+ Assert.assertTrue("Must be on linking page", linkingPage.isCurrent());
+ Assert.assertEquals("account linked", linkingPage.getErrorMessage().toLowerCase());
+
+ // now test CSRF with bad hash.
+
+ navigateTo(linkUrl);
+
+ Assert.assertTrue(driver.getPageSource().contains("We're sorry..."));
+
+ logoutAll();
+
+ // now log in again with client that does not have scope
+
+ String accountId = adminClient.realms().realm(REALM_NAME).clients().findByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).get(0).getId();
+ RoleRepresentation manageAccount = adminClient.realms().realm(REALM_NAME).clients().get(accountId).roles().get(MANAGE_ACCOUNT).toRepresentation();
+ RoleRepresentation manageLinks = adminClient.realms().realm(REALM_NAME).clients().get(accountId).roles().get(MANAGE_ACCOUNT_LINKS).toRepresentation();
+ RoleRepresentation userRole = adminClient.realms().realm(REALM_NAME).roles().get(CORRECT_ROLE).toRepresentation();
+
+ client.setFullScopeAllowed(false);
+ ClientResource clientResource = adminClient.realms().realm(REALM_NAME).clients().get(client.getId());
+ clientResource.update(client);
+
+ List<RoleRepresentation> roles = new LinkedList<>();
+ roles.add(userRole);
+ clientResource.getScopeMappings().realmLevel().add(roles);
+
+ navigateTo(LINKING_URL + "?response=true");
+ Assert.assertTrue(loginPage.isCurrent(REALM_NAME));
+ loginPage.login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
+ Assert.assertTrue(linkingPage.isCurrent());
+ Assert.assertEquals("account linked", linkingPage.getErrorMessage().toLowerCase());
+
+ UriBuilder linkBuilder = UriBuilder.fromUri(LINKING_URL);
+ String clientLinkUrl = linkBuilder.clone()
+ .queryParam("realm", REALM_NAME)
+ .queryParam("provider", PARENT_REALM).build().toString();
+
+ navigateTo(clientLinkUrl);
+
+ Assert.assertTrue(driver.getCurrentUrl().contains("error=not_allowed"));
+
+ logoutAll();
+
+ // add MANAGE_ACCOUNT_LINKS scope should pass.
+
+ links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertTrue(links.isEmpty());
+
+ roles = new LinkedList<>();
+ roles.add(manageLinks);
+ clientResource.getScopeMappings().clientLevel(accountId).add(roles);
+
+ navigateTo(clientLinkUrl);
+ Assert.assertTrue(loginPage.isCurrent(REALM_NAME));
+ loginPage.login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
+ Assert.assertTrue(loginPage.isCurrent(PARENT_REALM));
+ loginPage.login(PARENT_USERNAME, PARENT_PASSWORD);
+
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(linkBuilder.toTemplate()));
+ Assert.assertTrue(driver.getPageSource().contains("Account linked"));
+
+ links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertFalse(links.isEmpty());
+
+ realm.users().get(childUserId).removeFederatedIdentity(PARENT_REALM);
+ links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertTrue(links.isEmpty());
+
+ clientResource.getScopeMappings().clientLevel(accountId).remove(roles);
+
+ logoutAll();
+
+ navigateTo(clientLinkUrl);
+ Assert.assertTrue(loginPage.isCurrent(REALM_NAME));
+ loginPage.login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
+
+ Assert.assertTrue(driver.getCurrentUrl().contains("link_error=not_allowed"));
+
+ logoutAll();
+
+ // add MANAGE_ACCOUNT scope should pass
+
+ links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertTrue(links.isEmpty());
+
+ roles = new LinkedList<>();
+ roles.add(manageAccount);
+ clientResource.getScopeMappings().clientLevel(accountId).add(roles);
+
+ navigateTo(clientLinkUrl);
+ Assert.assertTrue(loginPage.isCurrent(REALM_NAME));
+ loginPage.login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
+ Assert.assertTrue(loginPage.isCurrent(PARENT_REALM));
+ loginPage.login(PARENT_USERNAME, PARENT_PASSWORD);
+
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(linkBuilder.toTemplate()));
+ Assert.assertTrue(driver.getPageSource().contains("Account linked"));
+
+ links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertFalse(links.isEmpty());
+
+ realm.users().get(childUserId).removeFederatedIdentity(PARENT_REALM);
+ links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertTrue(links.isEmpty());
+
+ clientResource.getScopeMappings().clientLevel(accountId).remove(roles);
+
+ logoutAll();
+
+ navigateTo(clientLinkUrl);
+ Assert.assertTrue(loginPage.isCurrent(REALM_NAME));
+ loginPage.login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
+
+ Assert.assertTrue(driver.getCurrentUrl().contains("link_error=not_allowed"));
+
+ logoutAll();
+
+
+ // undo fullScopeAllowed
+
+ client = adminClient.realms().realm(REALM_NAME).clients().findByClientId(CLIENT_ID).get(0);
+ client.setFullScopeAllowed(true);
+ clientResource.update(client);
+
+ links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertTrue(links.isEmpty());
+
+ logoutAll();
+ }
+
+ @Test
+ public void testAccountLink() throws Exception {
+ RealmResource realm = adminClient.realms().realm(REALM_NAME);
+ List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertTrue(links.isEmpty());
+
+ UriBuilder linkBuilder = UriBuilder.fromUri(LINKING_URL);
+ String linkUrl = linkBuilder.clone()
+ .queryParam("realm", REALM_NAME)
+ .queryParam("provider", PARENT_REALM).build().toString();
+ log.info("linkUrl: " + linkUrl);
+ navigateTo(linkUrl);
+ Assert.assertTrue(loginPage.isCurrent(REALM_NAME));
+ Assert.assertTrue(driver.getPageSource().contains(PARENT_REALM));
+ loginPage.login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
+ Assert.assertTrue(loginPage.isCurrent(PARENT_REALM));
+ loginPage.login(PARENT_USERNAME, PARENT_PASSWORD);
+ log.info("After linking: " + driver.getCurrentUrl());
+ log.info(driver.getPageSource());
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(linkBuilder.toTemplate()));
+ Assert.assertTrue(driver.getPageSource().contains("Account linked"));
+
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest(
+ REALM_NAME,
+ CHILD_USERNAME_1,
+ CHILD_PASSWORD_1,
+ null,
+ CLIENT_ID,
+ SECRET);
+ Assert.assertNotNull(response.getAccessToken());
+ Assert.assertNull(response.getError());
+ Client httpClient = ClientBuilder.newClient();
+ String firstToken = getToken(response, httpClient);
+ Assert.assertNotNull(firstToken);
+
+ navigateTo(linkUrl);
+ Assert.assertTrue(driver.getPageSource().contains("Account linked"));
+ String nextToken = getToken(response, httpClient);
+ Assert.assertNotNull(nextToken);
+ Assert.assertNotEquals(firstToken, nextToken);
+
+ links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertFalse(links.isEmpty());
+
+ realm.users().get(childUserId).removeFederatedIdentity(PARENT_REALM);
+ links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertTrue(links.isEmpty());
+
+ logoutAll();
+ }
+
+ @Test
+ public void testLinkOnlyProvider() throws Exception {
+ RealmResource realm = adminClient.realms().realm(REALM_NAME);
+ IdentityProviderRepresentation rep = realm.identityProviders().get(PARENT_REALM).toRepresentation();
+ rep.setLinkOnly(true);
+ realm.identityProviders().get(PARENT_REALM).update(rep);
+
+ try {
+ List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertTrue(links.isEmpty());
+
+ UriBuilder linkBuilder = UriBuilder.fromUri(LINKING_URL);
+ String linkUrl = linkBuilder.clone()
+ .queryParam("realm", REALM_NAME)
+ .queryParam("provider", PARENT_REALM).build().toString();
+ navigateTo(linkUrl);
+ Assert.assertTrue(loginPage.isCurrent(REALM_NAME));
+
+ // should not be on login page. This is what we are testing
+ Assert.assertFalse(driver.getPageSource().contains(PARENT_REALM));
+
+ // now test that we can still link.
+ loginPage.login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
+ Assert.assertTrue(loginPage.isCurrent(PARENT_REALM));
+ loginPage.login(PARENT_USERNAME, PARENT_PASSWORD);
+ log.info("After linking: " + driver.getCurrentUrl());
+ log.info(driver.getPageSource());
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(linkBuilder.toTemplate()));
+ Assert.assertTrue(driver.getPageSource().contains("Account linked"));
+
+ links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertFalse(links.isEmpty());
+
+ realm.users().get(childUserId).removeFederatedIdentity(PARENT_REALM);
+ links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertTrue(links.isEmpty());
+
+ logoutAll();
+
+ log.info("testing link-only attack");
+
+ navigateTo(linkUrl);
+ Assert.assertTrue(loginPage.isCurrent(REALM_NAME));
+
+ log.info("login page uri is: " + driver.getCurrentUrl());
+
+ // ok, now scrape the code from page
+ String pageSource = driver.getPageSource();
+ String action = ActionURIUtils.getActionURIFromPageSource(pageSource);
+ System.out.println("action uri: " + action);
+
+ Map<String, String> queryParams = ActionURIUtils.parseQueryParamsFromActionURI(action);
+ System.out.println("query params: " + queryParams);
+
+ // now try and use the code to login to remote link-only idp
+
+ String uri = "/auth/realms/" + REALM_NAME + "/broker/" + PARENT_REALM + "/login";
+
+ uri = UriBuilder.fromUri(AuthServerTestEnricher.getAuthServerContextRoot())
+ .path(uri)
+ .queryParam(OAuth2Constants.CODE, queryParams.get(OAuth2Constants.CODE))
+ .queryParam(Constants.CLIENT_ID, queryParams.get(Constants.CLIENT_ID))
+ .build().toString();
+
+ log.info("hack uri: " + uri);
+
+ navigateTo(uri);
+
+ Assert.assertTrue(driver.getPageSource().contains("Could not send authentication request to identity provider."));
+ } finally {
+ rep.setLinkOnly(false);
+ realm.identityProviders().get(PARENT_REALM).update(rep);
+ }
+ }
+
+ @Test
+ public void testAccountNotLinkedAutomatically() throws Exception {
+ RealmResource realm = adminClient.realms().realm(REALM_NAME);
+ List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertTrue(links.isEmpty());
+
+ // Login to account mgmt first
+ profilePage.open(REALM_NAME);
+ WaitUtils.waitForPageToLoad();
+
+ Assert.assertTrue(loginPage.isCurrent(REALM_NAME));
+ loginPage.login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
+ profilePage.assertCurrent();
+
+ // Now in another tab, open login screen with "prompt=login" . Login screen will be displayed even if I have SSO cookie
+ UriBuilder linkBuilder = UriBuilder.fromUri(LINKING_URL);
+ String linkUrl = linkBuilder.clone()
+ .queryParam(OIDCLoginProtocol.PROMPT_PARAM, OIDCLoginProtocol.PROMPT_VALUE_LOGIN)
+ .build().toString();
+
+ navigateTo(linkUrl);
+ Assert.assertTrue(loginPage.isCurrent(REALM_NAME));
+ loginPage.clickSocial(PARENT_REALM);
+ Assert.assertTrue(loginPage.isCurrent(PARENT_REALM));
+ loginPage.login(PARENT_USERNAME, PARENT_PASSWORD);
+
+ // Test I was not automatically linked.
+ links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertTrue(links.isEmpty());
+
+ loginUpdateProfilePage.assertCurrent();
+ loginUpdateProfilePage.update("Joe", "Doe", "joe@parent.com");
+
+ errorPage.assertCurrent();
+ Assert.assertEquals("You are already authenticated as different user '"
+ + CHILD_USERNAME_1
+ + "' in this session. Please logout first.", errorPage.getError());
+
+ logoutAll();
+
+ // Remove newly created user
+ String newUserId = ApiUtil.findUserByUsername(realm, PARENT_USERNAME).getId();
+ getCleanup(REALM_NAME).addUserId(newUserId);
+ }
+
+ @Test
+ public void testAccountLinkingExpired() throws Exception {
+ RealmResource realm = adminClient.realms().realm(REALM_NAME);
+ List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertTrue(links.isEmpty());
+
+ // Login to account mgmt first
+ profilePage.open(REALM_NAME);
+ WaitUtils.waitForPageToLoad();
+
+ Assert.assertTrue(loginPage.isCurrent(REALM_NAME));
+ loginPage.login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
+ profilePage.assertCurrent();
+
+ // Now in another tab, request account linking
+ UriBuilder linkBuilder = UriBuilder.fromUri(LINKING_URL);
+ String linkUrl = linkBuilder.clone()
+ .queryParam("realm", REALM_NAME)
+ .queryParam("provider", PARENT_REALM).build().toString();
+ navigateTo(linkUrl);
+
+ Assert.assertTrue(loginPage.isCurrent(PARENT_REALM));
+
+ // Logout "child" userSession in the meantime (for example through admin request)
+ realm.logoutAll();
+
+ // Finish login on parent.
+ loginPage.login(PARENT_USERNAME, PARENT_PASSWORD);
+
+ // Test I was not automatically linked
+ links = realm.users().get(childUserId).getFederatedIdentity();
+ Assert.assertTrue(links.isEmpty());
+
+ errorPage.assertCurrent();
+ Assert.assertEquals("Requested broker account linking, but current session is no longer valid.", errorPage.getError());
+
+ logoutAll();
+ }
+
+ private void navigateTo(String uri) {
+ driver.navigate().to(uri);
+ WaitUtils.waitForPageToLoad();
+ }
+
+ public void logoutAll() {
+ String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder()).build(REALM_NAME).toString();
+ navigateTo(logoutUri);
+ logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder()).build(PARENT_REALM).toString();
+ navigateTo(logoutUri);
+ }
+
+ private String getToken(OAuthClient.AccessTokenResponse response, Client httpClient) throws Exception {
+ log.info("target here is " + OAuthClient.AUTH_SERVER_ROOT);
+ String idpToken = httpClient.target(OAuthClient.AUTH_SERVER_ROOT)
+ .path("realms")
+ .path(REALM_NAME)
+ .path("broker")
+ .path(PARENT_REALM)
+ .path("token")
+ .request()
+ .header("Authorization", "Bearer " + response.getAccessToken())
+ .get(String.class);
+ AccessTokenResponse res = JsonSerialization.readValue(idpToken, AccessTokenResponse.class);
+ return res.getToken();
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/BasicSpringBootTest.java b/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/BasicSpringBootTest.java
index 6aea719..7e812b0 100644
--- a/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/BasicSpringBootTest.java
+++ b/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/BasicSpringBootTest.java
@@ -1,9 +1,44 @@
package org.keycloak.testsuite.springboot;
+import org.junit.After;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
+import org.keycloak.admin.client.resource.RolesResource;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.admin.ApiUtil;
public class BasicSpringBootTest extends AbstractSpringBootTest {
+
+ private static final String USER_LOGIN_2 = "testuser2";
+ private static final String USER_EMAIL_2 = "user2@email.test";
+ private static final String USER_PASSWORD_2 = "user2-password";
+
+ private static final String INCORRECT_ROLE = "wrong-admin";
+
+ @Before
+ public void addIncorrectUser() {
+ RolesResource rolesResource = adminClient.realm(REALM_NAME).roles();
+
+ RoleRepresentation role = new RoleRepresentation(INCORRECT_ROLE, INCORRECT_ROLE, false);
+
+ rolesResource.create(role);
+
+ addUser(USER_LOGIN_2, USER_EMAIL_2, USER_PASSWORD_2, INCORRECT_ROLE);
+ }
+
+ @After
+ public void removeUser() {
+ UserRepresentation user = ApiUtil.findUserByUsername(adminClient.realm(REALM_NAME), USER_LOGIN_2);
+
+ if (user != null) {
+ adminClient.realm(REALM_NAME).users().delete(user.getId());
+ }
+
+ adminClient.realm(REALM_NAME).roles().deleteRole(INCORRECT_ROLE);
+ }
+
@Test
public void testCorrectUser() {
driver.navigate().to(APPLICATION_URL + "/index.html");
diff --git a/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/OfflineTokenSpringBootTest.java b/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/OfflineTokenSpringBootTest.java
index 5ac950f..9fdc0f7 100644
--- a/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/OfflineTokenSpringBootTest.java
+++ b/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/OfflineTokenSpringBootTest.java
@@ -7,8 +7,10 @@ import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
+import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.Urls;
import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.pages.AccountApplicationsPage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.util.ClientManager;
@@ -22,7 +24,7 @@ import java.util.List;
import static org.keycloak.testsuite.util.WaitUtils.pause;
public class OfflineTokenSpringBootTest extends AbstractSpringBootTest {
- private static final String SERVLET_URI = APPLICATION_URL + "/admin/TokenServlet";
+ private static final String SERVLET_URL = BASE_URL + "/TokenServlet";
@Rule
public AssertEvents events = new AssertEvents(this);
@@ -35,7 +37,7 @@ public class OfflineTokenSpringBootTest extends AbstractSpringBootTest {
@Test
public void testTokens() {
- String servletUri = UriBuilder.fromUri(SERVLET_URI)
+ String servletUri = UriBuilder.fromUri(SERVLET_URL)
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.OFFLINE_ACCESS)
.build().toString();
driver.navigate().to(servletUri);
@@ -45,31 +47,31 @@ public class OfflineTokenSpringBootTest extends AbstractSpringBootTest {
WaitUtils.waitUntilElement(By.tagName("body")).is().visible();
- Assert.assertTrue(tokenPage.isCurrent());
+ Assert.assertTrue("Must be on tokens page", tokenPage.isCurrent());
- Assert.assertEquals(tokenPage.getRefreshToken().getType(), TokenUtil.TOKEN_TYPE_OFFLINE);
- Assert.assertEquals(tokenPage.getRefreshToken().getExpiration(), 0);
+ Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, tokenPage.getRefreshToken().getType());
+ Assert.assertEquals(0, tokenPage.getRefreshToken().getExpiration());
String accessTokenId = tokenPage.getAccessToken().getId();
String refreshTokenId = tokenPage.getRefreshToken().getId();
- setAdapterAndServerTimeOffset(9999, SERVLET_URI);
+ setAdapterAndServerTimeOffset(9999, SERVLET_URL);
- driver.navigate().to(SERVLET_URI);
+ driver.navigate().to(SERVLET_URL);
Assert.assertTrue("Must be on tokens page", tokenPage.isCurrent());
- Assert.assertNotEquals(tokenPage.getRefreshToken().getId(), refreshTokenId);
- Assert.assertNotEquals(tokenPage.getAccessToken().getId(), accessTokenId);
+ Assert.assertNotEquals(refreshTokenId, tokenPage.getRefreshToken().getId());
+ Assert.assertNotEquals(accessTokenId, tokenPage.getAccessToken().getId());
- setAdapterAndServerTimeOffset(0, SERVLET_URI);
+ setAdapterAndServerTimeOffset(0, SERVLET_URL);
- driver.navigate().to(logoutPage(SERVLET_URI));
+ driver.navigate().to(logoutPage(SERVLET_URL));
Assert.assertTrue("Must be on login page", loginPage.isCurrent());
}
@Test
public void testRevoke() {
// Login to servlet first with offline token
- String servletUri = UriBuilder.fromUri(SERVLET_URI)
+ String servletUri = UriBuilder.fromUri(SERVLET_URL)
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.OFFLINE_ACCESS)
.build().toString();
driver.navigate().to(servletUri);
@@ -81,10 +83,10 @@ public class OfflineTokenSpringBootTest extends AbstractSpringBootTest {
Assert.assertEquals(tokenPage.getRefreshToken().getType(), TokenUtil.TOKEN_TYPE_OFFLINE);
// Assert refresh works with increased time
- setAdapterAndServerTimeOffset(9999, SERVLET_URI);
- driver.navigate().to(SERVLET_URI);
+ setAdapterAndServerTimeOffset(9999, SERVLET_URL);
+ driver.navigate().to(SERVLET_URL);
Assert.assertTrue("Must be on token page", tokenPage.isCurrent());
- setAdapterAndServerTimeOffset(0, SERVLET_URI);
+ setAdapterAndServerTimeOffset(0, SERVLET_URL);
events.clear();
@@ -98,14 +100,18 @@ public class OfflineTokenSpringBootTest extends AbstractSpringBootTest {
pause(500);
Assert.assertEquals(accountAppPage.getApplications().get(CLIENT_ID).getAdditionalGrants().size(), 0);
- events.expect(EventType.REVOKE_GRANT).realm(REALM_ID).user(getCorrectUserId())
+ UserRepresentation userRepresentation =
+ ApiUtil.findUserByUsername(realmsResouce().realm(REALM_NAME), USER_LOGIN);
+ Assert.assertNotNull("User should exist", userRepresentation);
+
+ events.expect(EventType.REVOKE_GRANT).realm(REALM_ID).user(userRepresentation.getId())
.client("account").detail(Details.REVOKED_CLIENT, CLIENT_ID).assertEvent();
// Assert refresh doesn't work now (increase time one more time)
- setAdapterAndServerTimeOffset(9999, SERVLET_URI);
- driver.navigate().to(SERVLET_URI);
+ setAdapterAndServerTimeOffset(9999, SERVLET_URL);
+ driver.navigate().to(SERVLET_URL);
loginPage.assertCurrent();
- setAdapterAndServerTimeOffset(0, SERVLET_URI);
+ setAdapterAndServerTimeOffset(0, SERVLET_URL);
}
@Test
@@ -113,17 +119,15 @@ public class OfflineTokenSpringBootTest extends AbstractSpringBootTest {
ClientManager.realm(adminClient.realm(REALM_NAME)).clientId(CLIENT_ID).consentRequired(true);
// Assert grant page doesn't have 'Offline Access' role when offline token is not requested
- driver.navigate().to(SERVLET_URI);
+ driver.navigate().to(SERVLET_URL);
loginPage.login(USER_LOGIN, USER_PASSWORD);
oauthGrantPage.assertCurrent();
WaitUtils.waitUntilElement(By.xpath("//body")).text().not().contains("Offline access");
oauthGrantPage.cancel();
- // Assert grant page has 'Offline Access' role now
- String servletUri = UriBuilder.fromUri(SERVLET_URI)
+ driver.navigate().to(UriBuilder.fromUri(SERVLET_URL)
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.OFFLINE_ACCESS)
- .build().toString();
- driver.navigate().to(servletUri);
+ .build().toString());
WaitUtils.waitUntilElement(By.tagName("body")).is().visible();
loginPage.login(USER_LOGIN, USER_PASSWORD);
@@ -143,7 +147,7 @@ public class OfflineTokenSpringBootTest extends AbstractSpringBootTest {
Assert.assertTrue(offlineClient.getAdditionalGrants().contains("Offline Token"));
//This was necessary to be introduced, otherwise other testcases will fail
- driver.navigate().to(logoutPage(SERVLET_URI));
+ driver.navigate().to(logoutPage(SERVLET_URL));
loginPage.assertCurrent();
events.clear();
diff --git a/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/SessionSpringBootTest.java b/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/SessionSpringBootTest.java
new file mode 100644
index 0000000..a0a4982
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/SessionSpringBootTest.java
@@ -0,0 +1,169 @@
+package org.keycloak.testsuite.springboot;
+
+import org.jboss.arquillian.drone.api.annotation.Drone;
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.auth.page.account.Sessions;
+import org.keycloak.testsuite.util.SecondBrowser;
+import org.keycloak.testsuite.util.WaitUtils;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+
+public class SessionSpringBootTest extends AbstractSpringBootTest {
+
+ private static final String SERVLET_URL = BASE_URL + "/SessionServlet";
+
+ static final String USER_LOGIN_CORRECT_2 = "testcorrectuser2";
+ static final String USER_EMAIL_CORRECT_2 = "usercorrect2@email.test";
+ static final String USER_PASSWORD_CORRECT_2 = "testcorrectpassword2";
+
+ @Page
+ private SessionPage sessionPage;
+
+ @Drone
+ @SecondBrowser
+ private WebDriver driver2;
+
+ @Page
+ private Sessions realmSessions;
+
+ @Override
+ public void setDefaultPageUriParameters() {
+ super.setDefaultPageUriParameters();
+ realmSessions.setAuthRealm(REALM_NAME);
+ }
+
+ private void loginAndCheckSession() {
+ driver.navigate().to(SERVLET_URL);
+ Assert.assertTrue("Must be on login page", loginPage.isCurrent());
+ loginPage.login(USER_LOGIN, USER_PASSWORD);
+ WaitUtils.waitUntilElement(By.tagName("body")).is().visible();
+ Assert.assertTrue("Must be on servlet page", sessionPage.isCurrent());
+ Assert.assertEquals("Counter must be 0", 0, sessionPage.getCounter());
+
+ driver.navigate().to(SERVLET_URL);
+ Assert.assertEquals("Counter now must be 1", 1, sessionPage.getCounter());
+ }
+
+ private boolean checkCounterInSource(WebDriver driver, int counter) {
+ return driver.getPageSource().replaceAll("\\s", "")
+ .contains("<spanid=\"counter\">" + counter + "</span>");
+ }
+
+ @Before
+ public void addUserCorrect2() {
+ addUser(USER_LOGIN_CORRECT_2, USER_EMAIL_CORRECT_2, USER_PASSWORD_CORRECT_2, CORRECT_ROLE);
+ }
+
+ @After
+ public void removeUserCorrect2() {
+ UserRepresentation userRep = ApiUtil.findUserByUsername(realmsResouce().realm(REALM_NAME), USER_LOGIN_CORRECT_2);
+ if (userRep != null) {
+ realmsResouce().realm(REALM_NAME).users().get(userRep.getId()).remove();
+ }
+ }
+
+ @Test
+ public void testSingleSessionInvalidated() {
+
+ loginAndCheckSession();
+
+ // cannot pass to loginAndCheckSession becayse loginPage is not working together with driver2, therefore copypasta
+ driver2.navigate().to(SERVLET_URL);
+ log.info("current title is " + driver2.getTitle());
+ Assert.assertTrue("Must be on login page", driver2.getTitle().toLowerCase().startsWith("log in to"));
+ driver2.findElement(By.id("username")).sendKeys(USER_LOGIN);
+ driver2.findElement(By.id("password")).sendKeys(USER_PASSWORD);
+ driver2.findElement(By.id("password")).submit();
+ Assert.assertTrue("Must be on session page", driver2.getTitle().equals(SessionPage.PAGE_TITLE));
+ Assert.assertTrue("Counter must be 0", checkCounterInSource(driver2, 0));
+ // Counter increased now
+ driver2.navigate().to(SERVLET_URL);
+ Assert.assertTrue("Counter must be 1", checkCounterInSource(driver2, 1));
+
+ // Logout in browser1
+ driver.navigate().to(logoutPage(SERVLET_URL));
+
+ // Assert that I am logged out in browser1
+ driver.navigate().to(SERVLET_URL);
+ Assert.assertTrue("Must be on login page", loginPage.isCurrent());
+
+ // Assert that I am still logged in browser2 and same session is still preserved
+ driver2.navigate().to(SERVLET_URL);
+ Assert.assertTrue("Must be on session page", driver2.getTitle().equals(SessionPage.PAGE_TITLE));
+ Assert.assertTrue("Counter must be 2", checkCounterInSource(driver2, 2));
+
+ driver2.navigate().to(logoutPage(SERVLET_URL));
+ Assert.assertTrue("Must be on login page", driver2.getTitle().toLowerCase().startsWith("log in to"));
+
+ }
+
+ @Test
+ public void testSessionInvalidatedAfterFailedRefresh() {
+ RealmResource realmResource = adminClient.realm(REALM_NAME);
+ RealmRepresentation realmRep = realmResource.toRepresentation();
+ ClientResource clientResource = null;
+ for (ClientRepresentation clientRep : realmResource.clients().findAll()) {
+ if (CLIENT_ID.equals(clientRep.getClientId())) {
+ clientResource = realmResource.clients().get(clientRep.getId());
+ }
+ }
+ Assert.assertNotNull(clientResource);
+ clientResource.toRepresentation().setAdminUrl("");
+ int origTokenLifespan = realmRep.getAccessCodeLifespan();
+ realmRep.setAccessCodeLifespan(1);
+ realmResource.update(realmRep);
+
+ // Login
+ loginAndCheckSession();
+
+ // Logout
+ String logoutUri = logoutPage(SERVLET_URL);
+ driver.navigate().to(logoutUri);
+
+ // Assert that http session was invalidated
+ driver.navigate().to(SERVLET_URL);
+ Assert.assertTrue("Must be on login page", loginPage.isCurrent());
+ loginPage.login(USER_LOGIN, USER_PASSWORD);
+ Assert.assertTrue("Must be on session page", sessionPage.isCurrent());
+ Assert.assertEquals("Counter must be 0", 0, sessionPage.getCounter());
+
+ clientResource.toRepresentation().setAdminUrl(BASE_URL);
+ realmRep.setAccessCodeLifespan(origTokenLifespan);
+ realmResource.update(realmRep);
+ }
+
+ @Test
+ public void testAdminApplicationLogout() {
+ loginAndCheckSession();
+
+ // logout user2 with admin client
+ UserRepresentation correct2 = realmsResouce().realm(REALM_NAME)
+ .users().search(USER_LOGIN_CORRECT_2, null, null, null, null, null).get(0);
+ realmsResouce().realm(REALM_NAME).users().get(correct2.getId()).logout();
+
+ // user1 should be still logged with original httpSession in our browser window
+ driver.navigate().to(SERVLET_URL);
+ Assert.assertTrue("Must be on session page", sessionPage.isCurrent());
+ Assert.assertEquals("Counter must be 2", 2, sessionPage.getCounter());
+ driver.navigate().to(logoutPage(SERVLET_URL));
+ }
+
+ @Test
+ public void testAccountManagementSessionsLogout() {
+ loginAndCheckSession();
+ realmSessions.navigateTo();
+ realmSessions.logoutAll();
+ // Assert I need to login again (logout was propagated to the app)
+ loginAndCheckSession();
+ }
+}