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; 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.junit.Assert;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
@ -31,14 +38,9 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import java.io.BufferedWriter;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -69,22 +71,20 @@ public class LinkAndExchangeServlet extends HttpServlet {
public AccessTokenResponse doTokenExchange(String realm, String token, String requestedIssuer, public AccessTokenResponse doTokenExchange(String realm, String token, String requestedIssuer,
String clientId, String clientSecret) throws Exception { String clientId, String clientSecret) throws Exception {
try {
try (CloseableHttpClient client = (CloseableHttpClient) new HttpClientBuilder().disableTrustManager().build()) {
String exchangeUrl = KeycloakUriBuilder.fromUri(ServletTestUtils.getAuthServerUrlBase()) String exchangeUrl = KeycloakUriBuilder.fromUri(ServletTestUtils.getAuthServerUrlBase())
.path("/auth/realms/{realm}/protocol/openid-connect/token").build(realm).toString(); .path("/auth/realms/{realm}/protocol/openid-connect/token").build(realm).toString();
URL url = new URL(exchangeUrl); HttpPost post = new HttpPost(exchangeUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setDoOutput(true);
HashMap<String, String> parameters = new HashMap<>(); HashMap<String, String> parameters = new HashMap<>();
if (clientSecret != null) { if (clientSecret != null) {
String authorization = BasicAuthHelper.createHeader(clientId, clientSecret); 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 { } else {
parameters.put("client_id", clientId); parameters.put("client_id", clientId);
} }
parameters.put(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE); 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.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE);
parameters.put(OAuth2Constants.REQUESTED_ISSUER, requestedIssuer); parameters.put(OAuth2Constants.REQUESTED_ISSUER, requestedIssuer);
OutputStream os = conn.getOutputStream(); post.setEntity(new StringEntity(getPostDataString(parameters)));
BufferedWriter writer = new BufferedWriter( HttpResponse response = client.execute(post);
new OutputStreamWriter(os, "UTF-8")); int statusCode = response.getStatusLine().getStatusCode();
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;
if (statusCode == 200 || statusCode == 400) {
return JsonSerialization.readValue(EntityUtils.toString(response.getEntity()), AccessTokenResponse.class);
} else { } else {
throw new RuntimeException("Unknown error!"); 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.graphene.page.Page;
import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.spec.WebArchive; import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.RealmResource; 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.AbstractAdapterTest;
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest; import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; 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.utils.arquillian.ContainerConstants;
import org.keycloak.testsuite.broker.BrokerTestTools; import org.keycloak.testsuite.broker.BrokerTestTools;
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl; import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
@ -84,6 +85,7 @@ import java.net.URL;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import static org.keycloak.testsuite.Assert.assertEquals;
import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient; import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient;
import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT; import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
@ -187,7 +189,6 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
testRealms.add(realm); testRealms.add(realm);
realm = new RealmRepresentation(); realm = new RealmRepresentation();
realm.setRealm(PARENT_IDP); realm.setRealm(PARENT_IDP);
realm.setEnabled(true); realm.setEnabled(true);
@ -200,17 +201,36 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
parentApp.setFullScopeAllowed(true); parentApp.setFullScopeAllowed(true);
realm.setClients(new LinkedList<>()); realm.setClients(new LinkedList<>());
realm.getClients().add(parentApp); realm.getClients().add(parentApp);
testRealms.add(realm); testRealms.add(realm);
} }
@BeforeClass @Before
public static void enabled() { public void enableFeature() throws Exception {
ProfileAssume.assumeFeatureEnabled(Profile.Feature.TOKEN_EXCHANGE); 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() { public void addIdpUser() {
RealmResource realm = adminClient.realms().realm(PARENT_IDP); RealmResource realm = adminClient.realms().realm(PARENT_IDP);
UserRepresentation user = new UserRepresentation(); UserRepresentation user = new UserRepresentation();
@ -226,8 +246,6 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
private String childUserId = null; private String childUserId = null;
@Before
public void addChildUser() { public void addChildUser() {
RealmResource realm = adminClient.realms().realm(CHILD_IDP); RealmResource realm = adminClient.realms().realm(CHILD_IDP);
UserRepresentation user = new UserRepresentation(); UserRepresentation user = new UserRepresentation();
@ -307,7 +325,7 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
idp.setStoreToken(true); idp.setStoreToken(true);
realm.updateIdentityProvider(idp); realm.updateIdentityProvider(idp);
} }
@Before
public void createBroker() { public void createBroker() {
createParentChild(); createParentChild();
testingClient.server().run(BrokerLinkAndTokenExchangeTest::setupRealm); testingClient.server().run(BrokerLinkAndTokenExchangeTest::setupRealm);
@ -319,9 +337,9 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
@Test @Test
@UncaughtServerErrorExpected
public void testAccountLink() throws Exception { public void testAccountLink() throws Exception {
testingClient.server().run(BrokerLinkAndTokenExchangeTest::turnOnTokenStore); testingClient.server().run(BrokerLinkAndTokenExchangeTest::turnOnTokenStore);
RealmResource realm = adminClient.realms().realm(CHILD_IDP); RealmResource realm = adminClient.realms().realm(CHILD_IDP);
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity(); List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
Assert.assertTrue(links.isEmpty()); Assert.assertTrue(links.isEmpty());
@ -349,74 +367,76 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
Assert.assertFalse(links.isEmpty()); Assert.assertFalse(links.isEmpty());
// do exchange // do exchange
String accessToken = oauth.doGrantAccessTokenRequest(CHILD_IDP, "child", "password", null, ClientApp.DEPLOYMENT_NAME, "password").getAccessToken(); String accessToken = oauth.doGrantAccessTokenRequest(CHILD_IDP, "child", "password", null, ClientApp.DEPLOYMENT_NAME, "password").getAccessToken();
Client httpClient = ClientBuilder.newClient(); Client httpClient = ClientBuilder.newClient();
try {
WebTarget exchangeUrl = childTokenExchangeWebTarget(httpClient);
System.out.println("Exchange url: " + exchangeUrl.getUri().toString());
WebTarget exchangeUrl = childTokenExchangeWebTarget(httpClient); Response response = exchangeUrl.request()
System.out.println("Exchange url: " + exchangeUrl.getUri().toString()); .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")) Assert.assertEquals(200, response.getStatus());
.post(Entity.form( AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
new Form() response.close();
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE) String externalToken = tokenResponse.getToken();
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken) Assert.assertNotNull(externalToken);
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE) Assert.assertTrue(tokenResponse.getExpiresIn() > 0);
.param(OAuth2Constants.REQUESTED_ISSUER, PARENT_IDP) setTimeOffset((int) tokenResponse.getExpiresIn() + 1);
)); // test that token refresh happens
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 // 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(); Assert.assertEquals(200, response.getStatus());
response = exchangeUrl.request() tokenResponse = response.readEntity(AccessTokenResponse.class);
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password")) response.close();
.post(Entity.form( Assert.assertNotEquals(externalToken, tokenResponse.getToken());
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)
)); // test direct exchange
Assert.assertEquals(200, response.getStatus()); response = exchangeUrl.request()
tokenResponse = response.readEntity(AccessTokenResponse.class); .header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader("direct-exchanger", "secret"))
response.close(); .post(Entity.form(
Assert.assertNotEquals(externalToken, tokenResponse.getToken()); 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() Assert.assertEquals(200, response.getStatus());
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader("direct-exchanger", "secret")) tokenResponse = response.readEntity(AccessTokenResponse.class);
.post(Entity.form( response.close();
new Form() Assert.assertNotEquals(externalToken, tokenResponse.getToken());
.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());
logoutAll(); logoutAll();
realm.users().get(childUserId).removeFederatedIdentity(PARENT_IDP); realm.users().get(childUserId).removeFederatedIdentity(PARENT_IDP);
links = realm.users().get(childUserId).getFederatedIdentity(); links = realm.users().get(childUserId).getFederatedIdentity();
Assert.assertTrue(links.isEmpty()); Assert.assertTrue(links.isEmpty());
} finally {
httpClient.close();
}
} }
protected WebTarget childTokenExchangeWebTarget(Client httpClient) { protected WebTarget childTokenExchangeWebTarget(Client httpClient) {
@ -443,6 +463,7 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
} }
@Test @Test
@UncaughtServerErrorExpected
public void testAccountLinkNoTokenStore() throws Exception { public void testAccountLinkNoTokenStore() throws Exception {
testingClient.server().run(BrokerLinkAndTokenExchangeTest::turnOffTokenStore); testingClient.server().run(BrokerLinkAndTokenExchangeTest::turnOffTokenStore);
@ -472,7 +493,6 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
Assert.assertFalse(links.isEmpty()); Assert.assertFalse(links.isEmpty());
logoutAll(); logoutAll();
@ -488,6 +508,7 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
* @throws Exception * @throws Exception
*/ */
@Test @Test
@UncaughtServerErrorExpected
public void testExportImport() throws Exception { public void testExportImport() throws Exception {
testExternalExchange(); testExternalExchange();
testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID); testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
@ -507,219 +528,190 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest
testExternalExchange(); testExternalExchange();
} }
@Test @Test
@UncaughtServerErrorExpected
public void testExternalExchange() throws Exception { public void testExternalExchange() throws Exception {
RealmResource childRealm = adminClient.realms().realm(CHILD_IDP); RealmResource childRealm = adminClient.realms().realm(CHILD_IDP);
String accessToken = oauth.doGrantAccessTokenRequest(PARENT_IDP, PARENT2_USERNAME, "password", null, PARENT_CLIENT, "password").getAccessToken(); String accessToken = oauth.doGrantAccessTokenRequest(PARENT_IDP, PARENT2_USERNAME, "password", null, PARENT_CLIENT, "password").getAccessToken();
Assert.assertEquals(0, adminClient.realm(CHILD_IDP).getClientSessionStats().size()); Assert.assertEquals(0, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
Client httpClient = ClientBuilder.newClient(); Client httpClient = ClientBuilder.newClient();
WebTarget exchangeUrl = childTokenExchangeWebTarget(httpClient); try {
System.out.println("Exchange url: " + exchangeUrl.getUri().toString()); 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(); 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); 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)
)); String exchangedUserId = null;
Assert.assertEquals(200, response.getStatus()); String exchangedUsername = null;
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 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) Assert.assertEquals(200, response.getStatus());
.queryParam("id_token_hint", exchangedAccessToken) AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
.request() String exchangedAccessToken = tokenResponse.getToken();
.get(); JWSInput jws = new JWSInput(tokenResponse.getToken());
response.close(); AccessToken token = jws.readJsonContent(AccessToken.class);
response.close();
Assert.assertEquals(0, adminClient.realm(CHILD_IDP).getClientSessionStats().size()); exchangedUserId = token.getSubject();
exchangedUsername = token.getPreferredUsername();
} System.out.println("exchangedUserId: " + exchangedUserId);
IdentityProviderRepresentation rep = adminClient.realm(CHILD_IDP).identityProviders().get(PARENT_IDP).toRepresentation(); System.out.println("exchangedUsername: " + exchangedUsername);
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);
// test that we can exchange back to external token // test that we can exchange back to external token
response = exchangeUrl.request() response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password")) .header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form( .post(Entity.form(
new Form() new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE) .param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, tokenResponse.getToken()) .param(OAuth2Constants.SUBJECT_TOKEN, tokenResponse.getToken())
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE) .param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE)
.param(OAuth2Constants.REQUESTED_ISSUER, PARENT_IDP) .param(OAuth2Constants.REQUESTED_ISSUER, PARENT_IDP)
)); ));
Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(200, response.getStatus());
tokenResponse = response.readEntity(AccessTokenResponse.class); tokenResponse = response.readEntity(AccessTokenResponse.class);
Assert.assertEquals(accessToken, tokenResponse.getToken()); Assert.assertEquals(accessToken, tokenResponse.getToken());
response.close(); response.close();
Assert.assertEquals(1, adminClient.realm(CHILD_IDP).getClientSessionStats().size()); Assert.assertEquals(1, adminClient.realm(CHILD_IDP).getClientSessionStats().size());
// test logout // test logout
response = childLogoutWebTarget(httpClient) response = childLogoutWebTarget(httpClient)
.queryParam("id_token_hint", exchangedAccessToken) .queryParam("id_token_hint", exchangedAccessToken)
.request() .request()
.get(); .get();
response.close(); 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(); List<FederatedIdentityRepresentation> links = childRealm.users().get(exchangedUserId).getFederatedIdentity();
Assert.assertEquals(1, links.size()); Assert.assertEquals(1, links.size());
} }
{ {
// check that we can request an exchange again and that the previously linked user is obtained // check that we can request an exchange again and that the previously linked user is obtained
Response response = exchangeUrl.request() Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password")) .header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form( .post(Entity.form(
new Form() new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE) .param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken) .param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE) .param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE)
.param(OAuth2Constants.SUBJECT_ISSUER, PARENT_IDP) .param(OAuth2Constants.SUBJECT_ISSUER, PARENT_IDP)
)); ));
Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(200, response.getStatus());
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class); AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
String exchangedAccessToken = tokenResponse.getToken(); String exchangedAccessToken = tokenResponse.getToken();
JWSInput jws = new JWSInput(tokenResponse.getToken()); JWSInput jws = new JWSInput(tokenResponse.getToken());
AccessToken token = jws.readJsonContent(AccessToken.class); AccessToken token = jws.readJsonContent(AccessToken.class);
response.close(); response.close();
String exchanged2UserId = token.getSubject(); String exchanged2UserId = token.getSubject();
String exchanged2Username = token.getPreferredUsername(); 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(exchangedUserId, exchanged2UserId);
Assert.assertEquals(exchangedUsername, exchanged2Username); Assert.assertEquals(exchangedUsername, exchanged2Username);
// test logout // test logout
response = childLogoutWebTarget(httpClient) response = childLogoutWebTarget(httpClient)
.queryParam("id_token_hint", exchangedAccessToken) .queryParam("id_token_hint", exchangedAccessToken)
.request() .request()
.get(); .get();
response.close(); 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(); List<FederatedIdentityRepresentation> links = childRealm.users().get(exchangedUserId).getFederatedIdentity();
Assert.assertEquals(1, links.size()); Assert.assertEquals(1, links.size());
} }
{ {
// check that we can exchange without specifying an SUBJECT_ISSUER // check that we can exchange without specifying an SUBJECT_ISSUER
Response response = exchangeUrl.request() Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password")) .header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password"))
.post(Entity.form( .post(Entity.form(
new Form() new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE) .param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken) .param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE) .param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE)
)); ));
Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(200, response.getStatus());
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class); AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
String exchangedAccessToken = tokenResponse.getToken(); String exchangedAccessToken = tokenResponse.getToken();
JWSInput jws = new JWSInput(tokenResponse.getToken()); JWSInput jws = new JWSInput(tokenResponse.getToken());
AccessToken token = jws.readJsonContent(AccessToken.class); AccessToken token = jws.readJsonContent(AccessToken.class);
response.close(); response.close();
String exchanged2UserId = token.getSubject(); String exchanged2UserId = token.getSubject();
String exchanged2Username = token.getPreferredUsername(); 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(exchangedUserId, exchanged2UserId);
Assert.assertEquals(exchangedUsername, exchanged2Username); Assert.assertEquals(exchangedUsername, exchanged2Username);
// test logout // test logout
response = childLogoutWebTarget(httpClient) response = childLogoutWebTarget(httpClient)
.queryParam("id_token_hint", exchangedAccessToken) .queryParam("id_token_hint", exchangedAccessToken)
.request() .request()
.get(); .get();
response.close(); 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(); List<FederatedIdentityRepresentation> links = childRealm.users().get(exchangedUserId).getFederatedIdentity();
Assert.assertEquals(1, links.size()); Assert.assertEquals(1, links.size());
} }
// cleanup remove the user // cleanup remove the user
childRealm.users().get(exchangedUserId).remove(); childRealm.users().get(exchangedUserId).remove();
{ {
// test unauthorized client gets 403 // test unauthorized client gets 403
Response response = exchangeUrl.request() Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(UNAUTHORIZED_CHILD_CLIENT, "password")) .header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(UNAUTHORIZED_CHILD_CLIENT, "password"))
.post(Entity.form( .post(Entity.form(
new Form() new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE) .param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.SUBJECT_TOKEN, accessToken) .param(OAuth2Constants.SUBJECT_TOKEN, accessToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE) .param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE)
.param(OAuth2Constants.SUBJECT_ISSUER, PARENT_IDP) .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(); 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();
}
}
} }