[KEYCLOAK-3837] added session and account linking spring boot tests (#4564)
This commit is contained in:
parent
19f54111ec
commit
988d660083
11 changed files with 990 additions and 79 deletions
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -19,4 +19,8 @@ public class SpringAdminPage extends AbstractPage {
|
|||
public void open() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
public String getTestDivString() {
|
||||
return testDiv.getText();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
RealmResource realmResource = adminClient.realm(REALM_NAME);
|
||||
UserRepresentation userRep = ApiUtil.findUserByUsername(realmResource, USER_LOGIN);
|
||||
if (userRep != null) {
|
||||
providerRealm.users().get(userRep.getId()).remove();
|
||||
}
|
||||
|
||||
RealmResource childRealm = adminClient.realm(REALM_NAME);
|
||||
userRep = ApiUtil.findUserByUsername(childRealm, USER_LOGIN_2);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue