KEYCLOAK-10065 Merge preview features test: BrokerLinkAndTokenExchangeTest

This commit is contained in:
Martin Bartos RH 2019-05-31 10:20:40 +02:00 committed by Marek Posolda
parent 391f1bf069
commit ccd90d5fdc
2 changed files with 294 additions and 267 deletions

View file

@ -16,9 +16,16 @@
*/
package org.keycloak.testsuite.adapter.servlet;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.junit.Assert;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
@ -31,14 +38,9 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.HttpHeaders;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
@ -69,22 +71,20 @@ public class LinkAndExchangeServlet extends HttpServlet {
public AccessTokenResponse doTokenExchange(String realm, String token, String requestedIssuer,
String clientId, String clientSecret) throws Exception {
try {
try (CloseableHttpClient client = (CloseableHttpClient) new HttpClientBuilder().disableTrustManager().build()) {
String exchangeUrl = KeycloakUriBuilder.fromUri(ServletTestUtils.getAuthServerUrlBase())
.path("/auth/realms/{realm}/protocol/openid-connect/token").build(realm).toString();
URL url = new URL(exchangeUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setDoOutput(true);
HttpPost post = new HttpPost(exchangeUrl);
HashMap<String, String> parameters = new HashMap<>();
if (clientSecret != null) {
String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
conn.setRequestProperty(HttpHeaders.AUTHORIZATION, authorization);
post.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString());
post.setHeader(HttpHeaders.AUTHORIZATION, authorization);
} else {
parameters.put("client_id", clientId);
}
parameters.put(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE);
@ -92,27 +92,15 @@ public class LinkAndExchangeServlet extends HttpServlet {
parameters.put(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE);
parameters.put(OAuth2Constants.REQUESTED_ISSUER, requestedIssuer);
OutputStream os = conn.getOutputStream();
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(os, "UTF-8"));
writer.write(getPostDataString(parameters));
writer.flush();
writer.close();
os.close();
if (conn.getResponseCode() == 200) {
AccessTokenResponse tokenResponse = JsonSerialization.readValue(conn.getInputStream(), AccessTokenResponse.class);
conn.getInputStream().close();
return tokenResponse;
} else if (conn.getResponseCode() == 400) {
AccessTokenResponse tokenResponse = JsonSerialization.readValue(conn.getErrorStream(), AccessTokenResponse.class);
conn.getErrorStream().close();
return tokenResponse;
post.setEntity(new StringEntity(getPostDataString(parameters)));
HttpResponse response = client.execute(post);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200 || statusCode == 400) {
return JsonSerialization.readValue(EntityUtils.toString(response.getEntity()), AccessTokenResponse.class);
} else {
throw new RuntimeException("Unknown error!");
}
} finally {
}
}

View file

@ -22,9 +22,9 @@ import org.jboss.arquillian.container.test.api.TargetsContainer;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.RealmResource;
@ -59,6 +59,7 @@ import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.adapter.AbstractAdapterTest;
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
import org.keycloak.testsuite.broker.BrokerTestTools;
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
@ -84,6 +85,7 @@ import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import static org.keycloak.testsuite.Assert.assertEquals;
import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient;
import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
@ -187,7 +189,6 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
testRealms.add(realm);
realm = new RealmRepresentation();
realm.setRealm(PARENT_IDP);
realm.setEnabled(true);
@ -200,17 +201,36 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
parentApp.setFullScopeAllowed(true);
realm.setClients(new LinkedList<>());
realm.getClients().add(parentApp);
testRealms.add(realm);
}
@BeforeClass
public static void enabled() {
ProfileAssume.assumeFeatureEnabled(Profile.Feature.TOKEN_EXCHANGE);
@Before
public void enableFeature() throws Exception {
try {
addIdpUser();
addChildUser();
createBroker();
checkFeature(Response.Status.NOT_IMPLEMENTED.getStatusCode());
Response response = testingClient.testing().enableFeature(Profile.Feature.TOKEN_EXCHANGE.toString());
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
checkFeature(Response.Status.OK.getStatusCode());
ProfileAssume.assumeFeatureEnabled(Profile.Feature.TOKEN_EXCHANGE);
} catch (Exception e) {
disableFeature();
throw e;
}
}
@After
public void disableFeature() throws Exception {
Response response = testingClient.testing().disableFeature(Profile.Feature.TOKEN_EXCHANGE.toString());
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
checkFeature(Response.Status.NOT_IMPLEMENTED.getStatusCode());
}
@Before
public void addIdpUser() {
RealmResource realm = adminClient.realms().realm(PARENT_IDP);
UserRepresentation user = new UserRepresentation();
@ -226,8 +246,6 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
private String childUserId = null;
@Before
public void addChildUser() {
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
UserRepresentation user = new UserRepresentation();
@ -307,7 +325,7 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
idp.setStoreToken(true);
realm.updateIdentityProvider(idp);
}
@Before
public void createBroker() {
createParentChild();
testingClient.server().run(BrokerLinkAndTokenExchangeTest::setupRealm);
@ -319,9 +337,9 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
@Test
@UncaughtServerErrorExpected
public void testAccountLink() throws Exception {
testingClient.server().run(BrokerLinkAndTokenExchangeTest::turnOnTokenStore);
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
Assert.assertTrue(links.isEmpty());
@ -349,74 +367,76 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
Assert.assertFalse(links.isEmpty());
// do exchange
String accessToken = oauth.doGrantAccessTokenRequest(CHILD_IDP, "child", "password", null, ClientApp.DEPLOYMENT_NAME, "password").getAccessToken();
Client httpClient = ClientBuilder.newClient();
try {
WebTarget exchangeUrl = childTokenExchangeWebTarget(httpClient);
System.out.println("Exchange url: " + exchangeUrl.getUri().toString());
WebTarget exchangeUrl = childTokenExchangeWebTarget(httpClient);
System.out.println("Exchange url: " + exchangeUrl.getUri().toString());
Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE)
.param(OAuth2Constants.REQUESTED_ISSUER, PARENT_IDP)
Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE)
.param(OAuth2Constants.REQUESTED_ISSUER, PARENT_IDP)
));
Assert.assertEquals(200, response.getStatus());
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
response.close();
String externalToken = tokenResponse.getToken();
Assert.assertNotNull(externalToken);
Assert.assertTrue(tokenResponse.getExpiresIn() > 0);
setTimeOffset((int) tokenResponse.getExpiresIn() + 1);
));
Assert.assertEquals(200, response.getStatus());
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
response.close();
String externalToken = tokenResponse.getToken();
Assert.assertNotNull(externalToken);
Assert.assertTrue(tokenResponse.getExpiresIn() > 0);
setTimeOffset((int)tokenResponse.getExpiresIn() + 1);
// test that token refresh happens
// test that token refresh happens
// get access token again because we may have timed out
accessToken = oauth.doGrantAccessTokenRequest(CHILD_IDP, "child", "password", null, ClientApp.DEPLOYMENT_NAME, "password").getAccessToken();
response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE)
.param(OAuth2Constants.REQUESTED_ISSUER, PARENT_IDP)
// get access token again because we may have timed out
accessToken = oauth.doGrantAccessTokenRequest(CHILD_IDP, "child", "password", null, ClientApp.DEPLOYMENT_NAME, "password").getAccessToken();
response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE)
.param(OAuth2Constants.REQUESTED_ISSUER, PARENT_IDP)
));
Assert.assertEquals(200, response.getStatus());
tokenResponse = response.readEntity(AccessTokenResponse.class);
response.close();
Assert.assertNotEquals(externalToken, tokenResponse.getToken());
));
Assert.assertEquals(200, response.getStatus());
tokenResponse = response.readEntity(AccessTokenResponse.class);
response.close();
Assert.assertNotEquals(externalToken, tokenResponse.getToken());
// test direct exchange
response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader("direct-exchanger", "secret"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.REQUESTED_SUBJECT, "child")
.param(OAuth2Constants.REQUESTED_ISSUER, PARENT_IDP)
// test direct exchange
response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader("direct-exchanger", "secret"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.REQUESTED_SUBJECT, "child")
.param(OAuth2Constants.REQUESTED_ISSUER, PARENT_IDP)
));
Assert.assertEquals(200, response.getStatus());
tokenResponse = response.readEntity(AccessTokenResponse.class);
response.close();
Assert.assertNotEquals(externalToken, tokenResponse.getToken());
));
Assert.assertEquals(200, response.getStatus());
tokenResponse = response.readEntity(AccessTokenResponse.class);
response.close();
Assert.assertNotEquals(externalToken, tokenResponse.getToken());
logoutAll();
logoutAll();
realm.users().get(childUserId).removeFederatedIdentity(PARENT_IDP);
links = realm.users().get(childUserId).getFederatedIdentity();
Assert.assertTrue(links.isEmpty());
realm.users().get(childUserId).removeFederatedIdentity(PARENT_IDP);
links = realm.users().get(childUserId).getFederatedIdentity();
Assert.assertTrue(links.isEmpty());
} finally {
httpClient.close();
}
}
protected WebTarget childTokenExchangeWebTarget(Client httpClient) {
@ -443,6 +463,7 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
}
@Test
@UncaughtServerErrorExpected
public void testAccountLinkNoTokenStore() throws Exception {
testingClient.server().run(BrokerLinkAndTokenExchangeTest::turnOffTokenStore);
@ -472,7 +493,6 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
Assert.assertFalse(links.isEmpty());
logoutAll();
@ -488,6 +508,7 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
* @throws Exception
*/
@Test
@UncaughtServerErrorExpected
public void testExportImport() throws Exception {
testExternalExchange();
testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
@ -507,219 +528,190 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
testExternalExchange();
}
@Test
@UncaughtServerErrorExpected
public void testExternalExchange() throws Exception {
RealmResource childRealm = adminClient.realms().realm(CHILD_IDP);
String accessToken = oauth.doGrantAccessTokenRequest(PARENT_IDP, PARENT2_USERNAME, "password", null, PARENT_CLIENT, "password").getAccessToken();
Assert.assertEquals(0, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
Client httpClient = ClientBuilder.newClient();
WebTarget exchangeUrl = childTokenExchangeWebTarget(httpClient);
System.out.println("Exchange url: " + exchangeUrl.getUri().toString());
try {
WebTarget exchangeUrl = childTokenExchangeWebTarget(httpClient);
System.out.println("Exchange url: " + exchangeUrl.getUri().toString());
checkFeature(200);
{
IdentityProviderRepresentation rep = adminClient.realm(CHILD_IDP).identityProviders().get(PARENT_IDP).toRepresentation();
rep.getConfig().put(OIDCIdentityProviderConfig.VALIDATE_SIGNATURE, String.valueOf(false));
rep.getConfig().put(OIDCIdentityProviderConfig.VALIDATE_SIGNATURE, String.valueOf(true));
rep.getConfig().put(OIDCIdentityProviderConfig.USE_JWKS_URL, String.valueOf(true));
rep.getConfig().put(OIDCIdentityProviderConfig.JWKS_URL, parentJwksUrl());
String parentIssuer = UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT)
.path("/realms")
.path(PARENT_IDP)
.build().toString();
rep.getConfig().put("issuer", parentIssuer);
adminClient.realm(CHILD_IDP).identityProviders().get(PARENT_IDP).update(rep);
// test user info validation.
Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE)
.param(OAuth2Constants.SUBJECT_ISSUER, PARENT_IDP)
));
Assert.assertEquals(200, response.getStatus());
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
String exchangedAccessToken = tokenResponse.getToken();
Assert.assertNotNull(exchangedAccessToken);
response.close();
String exchangedUserId = null;
String exchangedUsername = null;
Assert.assertEquals(1, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
{
// test signature validation
Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE)
.param(OAuth2Constants.SUBJECT_ISSUER, PARENT_IDP)
// test logout
response = childLogoutWebTarget(httpClient)
.queryParam("id_token_hint", exchangedAccessToken)
.request()
.get();
response.close();
));
Assert.assertEquals(200, response.getStatus());
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
String exchangedAccessToken = tokenResponse.getToken();
JWSInput jws = new JWSInput(tokenResponse.getToken());
AccessToken token = jws.readJsonContent(AccessToken.class);
response.close();
Assert.assertEquals(0, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
exchangedUserId = token.getSubject();
exchangedUsername = token.getPreferredUsername();
}
IdentityProviderRepresentation rep = adminClient.realm(CHILD_IDP).identityProviders().get(PARENT_IDP).toRepresentation();
rep.getConfig().put(OIDCIdentityProviderConfig.VALIDATE_SIGNATURE, String.valueOf(true));
rep.getConfig().put(OIDCIdentityProviderConfig.USE_JWKS_URL, String.valueOf(true));
rep.getConfig().put(OIDCIdentityProviderConfig.JWKS_URL, parentJwksUrl());
String parentIssuer = UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT)
.path("/realms")
.path(PARENT_IDP)
.build().toString();
rep.getConfig().put("issuer", parentIssuer);
adminClient.realm(CHILD_IDP).identityProviders().get(PARENT_IDP).update(rep);
String exchangedUserId = null;
String exchangedUsername = null;
{
// test signature validation
Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE)
.param(OAuth2Constants.SUBJECT_ISSUER, PARENT_IDP)
));
Assert.assertEquals(200, response.getStatus());
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
String exchangedAccessToken = tokenResponse.getToken();
JWSInput jws = new JWSInput(tokenResponse.getToken());
AccessToken token = jws.readJsonContent(AccessToken.class);
response.close();
exchangedUserId = token.getSubject();
exchangedUsername = token.getPreferredUsername();
System.out.println("exchangedUserId: " + exchangedUserId);
System.out.println("exchangedUsername: " + exchangedUsername);
System.out.println("exchangedUserId: " + exchangedUserId);
System.out.println("exchangedUsername: " + exchangedUsername);
// test that we can exchange back to external token
response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, tokenResponse.getToken())
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE)
.param(OAuth2Constants.REQUESTED_ISSUER, PARENT_IDP)
// test that we can exchange back to external token
response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, tokenResponse.getToken())
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE)
.param(OAuth2Constants.REQUESTED_ISSUER, PARENT_IDP)
));
Assert.assertEquals(200, response.getStatus());
tokenResponse = response.readEntity(AccessTokenResponse.class);
Assert.assertEquals(accessToken, tokenResponse.getToken());
response.close();
));
Assert.assertEquals(200, response.getStatus());
tokenResponse = response.readEntity(AccessTokenResponse.class);
Assert.assertEquals(accessToken, tokenResponse.getToken());
response.close();
Assert.assertEquals(1, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
Assert.assertEquals(1, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
// test logout
response = childLogoutWebTarget(httpClient)
.queryParam("id_token_hint", exchangedAccessToken)
.request()
.get();
response.close();
// test logout
response = childLogoutWebTarget(httpClient)
.queryParam("id_token_hint", exchangedAccessToken)
.request()
.get();
response.close();
Assert.assertEquals(0, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
Assert.assertEquals(0, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
List<FederatedIdentityRepresentation> links = childRealm.users().get(exchangedUserId).getFederatedIdentity();
Assert.assertEquals(1, links.size());
}
{
// check that we can request an exchange again and that the previously linked user is obtained
Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE)
.param(OAuth2Constants.SUBJECT_ISSUER, PARENT_IDP)
List<FederatedIdentityRepresentation> links = childRealm.users().get(exchangedUserId).getFederatedIdentity();
Assert.assertEquals(1, links.size());
}
{
// check that we can request an exchange again and that the previously linked user is obtained
Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE)
.param(OAuth2Constants.SUBJECT_ISSUER, PARENT_IDP)
));
Assert.assertEquals(200, response.getStatus());
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
String exchangedAccessToken = tokenResponse.getToken();
JWSInput jws = new JWSInput(tokenResponse.getToken());
AccessToken token = jws.readJsonContent(AccessToken.class);
response.close();
));
Assert.assertEquals(200, response.getStatus());
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
String exchangedAccessToken = tokenResponse.getToken();
JWSInput jws = new JWSInput(tokenResponse.getToken());
AccessToken token = jws.readJsonContent(AccessToken.class);
response.close();
String exchanged2UserId = token.getSubject();
String exchanged2Username = token.getPreferredUsername();
String exchanged2UserId = token.getSubject();
String exchanged2Username = token.getPreferredUsername();
// assert that we get the same linked account as was previously imported
// assert that we get the same linked account as was previously imported
Assert.assertEquals(exchangedUserId, exchanged2UserId);
Assert.assertEquals(exchangedUsername, exchanged2Username);
Assert.assertEquals(exchangedUserId, exchanged2UserId);
Assert.assertEquals(exchangedUsername, exchanged2Username);
// test logout
response = childLogoutWebTarget(httpClient)
.queryParam("id_token_hint", exchangedAccessToken)
.request()
.get();
response.close();
// test logout
response = childLogoutWebTarget(httpClient)
.queryParam("id_token_hint", exchangedAccessToken)
.request()
.get();
response.close();
Assert.assertEquals(0, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
Assert.assertEquals(0, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
List<FederatedIdentityRepresentation> links = childRealm.users().get(exchangedUserId).getFederatedIdentity();
Assert.assertEquals(1, links.size());
}
{
// check that we can exchange without specifying an SUBJECT_ISSUER
Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE)
List<FederatedIdentityRepresentation> links = childRealm.users().get(exchangedUserId).getFederatedIdentity();
Assert.assertEquals(1, links.size());
}
{
// check that we can exchange without specifying an SUBJECT_ISSUER
Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE)
));
Assert.assertEquals(200, response.getStatus());
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
String exchangedAccessToken = tokenResponse.getToken();
JWSInput jws = new JWSInput(tokenResponse.getToken());
AccessToken token = jws.readJsonContent(AccessToken.class);
response.close();
));
Assert.assertEquals(200, response.getStatus());
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
String exchangedAccessToken = tokenResponse.getToken();
JWSInput jws = new JWSInput(tokenResponse.getToken());
AccessToken token = jws.readJsonContent(AccessToken.class);
response.close();
String exchanged2UserId = token.getSubject();
String exchanged2Username = token.getPreferredUsername();
String exchanged2UserId = token.getSubject();
String exchanged2Username = token.getPreferredUsername();
// assert that we get the same linked account as was previously imported
// assert that we get the same linked account as was previously imported
Assert.assertEquals(exchangedUserId, exchanged2UserId);
Assert.assertEquals(exchangedUsername, exchanged2Username);
Assert.assertEquals(exchangedUserId, exchanged2UserId);
Assert.assertEquals(exchangedUsername, exchanged2Username);
// test logout
response = childLogoutWebTarget(httpClient)
.queryParam("id_token_hint", exchangedAccessToken)
.request()
.get();
response.close();
// test logout
response = childLogoutWebTarget(httpClient)
.queryParam("id_token_hint", exchangedAccessToken)
.request()
.get();
response.close();
Assert.assertEquals(0, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
Assert.assertEquals(0, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
List<FederatedIdentityRepresentation> links = childRealm.users().get(exchangedUserId).getFederatedIdentity();
Assert.assertEquals(1, links.size());
}
// cleanup remove the user
childRealm.users().get(exchangedUserId).remove();
List<FederatedIdentityRepresentation> links = childRealm.users().get(exchangedUserId).getFederatedIdentity();
Assert.assertEquals(1, links.size());
}
// cleanup remove the user
childRealm.users().get(exchangedUserId).remove();
{
// test unauthorized client gets 403
Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(UNAUTHORIZED_CHILD_CLIENT, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE)
.param(OAuth2Constants.SUBJECT_ISSUER, PARENT_IDP)
{
// test unauthorized client gets 403
Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(UNAUTHORIZED_CHILD_CLIENT, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE)
.param(OAuth2Constants.SUBJECT_ISSUER, PARENT_IDP)
));
Assert.assertEquals(403, response.getStatus());
));
Assert.assertEquals(403, response.getStatus());
}
} finally {
httpClient.close();
}
}
@ -736,6 +728,53 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
WaitUtils.waitForPageToLoad();
}
private void checkFeature(int statusCode) throws Exception {
String accessToken = oauth.doGrantAccessTokenRequest(PARENT_IDP, PARENT2_USERNAME, "password", null, PARENT_CLIENT, "password").getAccessToken();
if (statusCode != Response.Status.NOT_IMPLEMENTED.getStatusCode()) {
Assert.assertEquals(0, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
}
Client httpClient = ClientBuilder.newClient();
try {
WebTarget exchangeUrl = childTokenExchangeWebTarget(httpClient);
{
IdentityProviderRepresentation rep = adminClient.realm(CHILD_IDP).identityProviders().get(PARENT_IDP).toRepresentation();
rep.getConfig().put(OIDCIdentityProviderConfig.VALIDATE_SIGNATURE, String.valueOf(false));
adminClient.realm(CHILD_IDP).identityProviders().get(PARENT_IDP).update(rep);
// test user info validation.
Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE)
.param(OAuth2Constants.SUBJECT_ISSUER, PARENT_IDP)
));
Assert.assertEquals(statusCode, response.getStatus());
if (statusCode != Response.Status.NOT_IMPLEMENTED.getStatusCode()) {
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
String exchangedAccessToken = tokenResponse.getToken();
Assert.assertNotNull(exchangedAccessToken);
response.close();
Assert.assertEquals(1, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
// test logout
response = childLogoutWebTarget(httpClient)
.queryParam("id_token_hint", exchangedAccessToken)
.request()
.get();
response.close();
Assert.assertEquals(0, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
}
}
} finally {
httpClient.close();
}
}
}