Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@ public TokenExchangeRequest tokenExchangeRequest(String subjectToken) {
return new TokenExchangeRequest(subjectToken, this);
}

public TokenExchangeRequest tokenExchangeRequest(String subjectToken, String subjectTokenType) {
return new TokenExchangeRequest(subjectToken, subjectTokenType, this);
}

/**
* @deprecated Set clientId and clientSecret using {@link #client(String, String)} and use {@link #tokenExchangeRequest(String)}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,25 @@
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class TokenExchangeRequest extends AbstractHttpPostRequest<TokenExchangeRequest, AccessTokenResponse> {

private final String subjectToken;
private final String subjectTokenType;
private List<String> audience;
private Map<String, String> additionalParams;

TokenExchangeRequest(String subjectToken, OAuthClient client) {
super(client);
this.subjectToken = subjectToken;
this.subjectTokenType = OAuth2Constants.ACCESS_TOKEN_TYPE;
}

TokenExchangeRequest(String subjectToken, String subjectTokenType, OAuthClient client) {
super(client);
this.subjectToken = subjectToken;
this.subjectTokenType = subjectTokenType;
}

@Override
Expand All @@ -38,7 +47,7 @@ protected void initRequest() {
parameter(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE);

parameter(OAuth2Constants.SUBJECT_TOKEN, subjectToken);
parameter(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE);
parameter(OAuth2Constants.SUBJECT_TOKEN_TYPE, subjectTokenType != null ? subjectTokenType : OAuth2Constants.ACCESS_TOKEN_TYPE);

if (audience != null) {
audience.forEach(a -> parameter(OAuth2Constants.AUDIENCE, a));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
import org.keycloak.testsuite.util.oauth.TokenExchangeRequest;
import org.keycloak.util.TokenUtil;

import java.util.Collections;
Expand Down Expand Up @@ -90,6 +90,83 @@ private AccessTokenResponse tokenExchange(String subjectToken, String clientId,
return oauth.tokenExchangeRequest(subjectToken).client(clientId, secret).audience(audience).additionalParams(additionalParams).send();
}

@Test
@UncaughtServerErrorExpected
public void testSubjectTokenType() throws Exception {
oauth.realm(TEST);
String accessToken = resourceOwnerLogin("john", "password", "subject-client", "secret");

TokenExchangeRequest request = oauth.tokenExchangeRequest(accessToken, OAuth2Constants.ACCESS_TOKEN_TYPE);
AccessTokenResponse response = request.send();
assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode());

request = oauth.tokenExchangeRequest(accessToken, OAuth2Constants.REFRESH_TOKEN_TYPE);
response = request.send();
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
assertEquals(OAuthErrorException.INVALID_REQUEST, response.getError());

request = oauth.tokenExchangeRequest(accessToken, OAuth2Constants.ID_TOKEN_TYPE);
response = request.send();
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
assertEquals(OAuthErrorException.INVALID_REQUEST, response.getError());

request = oauth.tokenExchangeRequest(accessToken, OAuth2Constants.SAML2_TOKEN_TYPE);
response = request.send();
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
assertEquals(OAuthErrorException.INVALID_REQUEST, response.getError());

request = oauth.tokenExchangeRequest(accessToken, OAuth2Constants.JWT_TOKEN_TYPE);
response = request.send();
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
assertEquals(OAuthErrorException.INVALID_REQUEST, response.getError());

request = oauth.tokenExchangeRequest(accessToken, "WRONG_TOKEN_TYPE");
response = request.send();
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
assertEquals(OAuthErrorException.INVALID_REQUEST, response.getError());
}

@Test
@UncaughtServerErrorExpected
public void testRequestedTokenType() throws Exception {
oauth.realm(TEST);
String accessToken = resourceOwnerLogin("john", "password", "subject-client", "secret");

AccessTokenResponse response = tokenExchange(accessToken, "requester-client", "secret", null, Map.of(OAuth2Constants.REQUESTED_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE));
assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode());
assertNotNull(response.getAccessToken());
assertEquals(TokenUtil.TOKEN_TYPE_BEARER, response.getTokenType());
assertEquals(OAuth2Constants.ACCESS_TOKEN_TYPE, response.getIssuedTokenType());

response = tokenExchange(accessToken, "requester-client", "secret", null, Map.of(OAuth2Constants.REQUESTED_TOKEN_TYPE, OAuth2Constants.REFRESH_TOKEN_TYPE));
assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode());
assertNotNull(response.getAccessToken());
assertEquals(TokenUtil.TOKEN_TYPE_BEARER, response.getTokenType());
assertEquals(OAuth2Constants.REFRESH_TOKEN_TYPE, response.getIssuedTokenType());

response = tokenExchange(accessToken, "requester-client", "secret", null, Map.of(OAuth2Constants.REQUESTED_TOKEN_TYPE, OAuth2Constants.ID_TOKEN_TYPE));
assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode());
assertNotNull(response.getAccessToken());
assertEquals(TokenUtil.TOKEN_TYPE_NA, response.getTokenType());
assertEquals(OAuth2Constants.ID_TOKEN_TYPE, response.getIssuedTokenType());

response = tokenExchange(accessToken, "requester-client", "secret", null, Map.of(OAuth2Constants.REQUESTED_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE));
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
assertEquals(OAuthErrorException.INVALID_REQUEST, response.getError());
assertEquals("requested_token_type unsupported", response.getErrorDescription());

//TODO: saml token type should not be supported
// response = tokenExchange(accessToken, "requester-client", "secret", null, Map.of(OAuth2Constants.REQUESTED_TOKEN_TYPE, OAuth2Constants.SAML2_TOKEN_TYPE));
// assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
// assertEquals(OAuthErrorException.INVALID_REQUEST, response.getError());
//assertEquals("requested_token_type unsupported", response.getErrorDescription());

response = tokenExchange(accessToken, "requester-client", "secret", null, Map.of(OAuth2Constants.REQUESTED_TOKEN_TYPE, "WRONG_TOKEN_TYPE"));
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
assertEquals(OAuthErrorException.INVALID_REQUEST, response.getError());
assertEquals("requested_token_type unsupported", response.getErrorDescription());
}

@Test
@UncaughtServerErrorExpected
public void testExchange() throws Exception {
Expand Down Expand Up @@ -138,7 +215,7 @@ public void testExchangeForIdToken() throws Exception {
.parse().getToken();
assertEquals(TokenUtil.TOKEN_TYPE_BEARER, exchangedToken.getType());

Assert.assertNotNull("ID Token is null, but was expected to be present", response.getIdToken());
assertNotNull("ID Token is null, but was expected to be present", response.getIdToken());
IDToken exchangedIdToken = TokenVerifier.create(response.getIdToken(), IDToken.class)
.parse().getToken();
assertEquals(TokenUtil.TOKEN_TYPE_ID, exchangedIdToken.getType());
Expand All @@ -150,15 +227,15 @@ public void testExchangeForIdToken() throws Exception {
oauth.scope(null);
response = tokenExchange(accessToken, "requester-client", "secret", null, Map.of(OAuth2Constants.REQUESTED_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE));
assertEquals(OAuth2Constants.ACCESS_TOKEN_TYPE, response.getIssuedTokenType());
Assert.assertNotNull(response.getAccessToken());
Assert.assertNull("ID Token was present, but should not be present", response.getIdToken());
assertNotNull(response.getAccessToken());
assertNull("ID Token was present, but should not be present", response.getIdToken());

// Exchange request requesting id-token. ID Token should be issued inside "access_token" parameter (as per token-exchange specification https://datatracker.ietf.org/doc/html/rfc8693#name-successful-response - parameter "access_token")
response = tokenExchange(accessToken, "requester-client", "secret", null, Map.of(OAuth2Constants.REQUESTED_TOKEN_TYPE, OAuth2Constants.ID_TOKEN_TYPE));
assertEquals(OAuth2Constants.ID_TOKEN_TYPE, response.getIssuedTokenType());
assertEquals(TokenUtil.TOKEN_TYPE_NA, response.getTokenType());
Assert.assertNotNull(response.getAccessToken());
Assert.assertNull("ID Token was present, but should not be present", response.getIdToken());
assertNotNull(response.getAccessToken());
assertNull("ID Token was present, but should not be present", response.getIdToken());

exchangedIdToken = TokenVerifier.create(response.getAccessToken(), IDToken.class)
.parse().getToken();
Expand All @@ -176,13 +253,13 @@ public void testExchangeUsingServiceAccount() throws Exception {
String accessToken = response.getAccessToken();
TokenVerifier<AccessToken> accessTokenVerifier = TokenVerifier.create(accessToken, AccessToken.class);
AccessToken token = accessTokenVerifier.parse().getToken();
Assert.assertNull(token.getSessionId());
assertNull(token.getSessionId());
response = tokenExchange(accessToken, "requester-client", "secret", null, null);
assertEquals(OAuth2Constants.ACCESS_TOKEN_TYPE, response.getIssuedTokenType());
String exchangedTokenString = response.getAccessToken();
TokenVerifier<AccessToken> verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class);
AccessToken exchangedToken = verifier.parse().getToken();
Assert.assertNull(exchangedToken.getSessionId());
assertNull(exchangedToken.getSessionId());
assertEquals("requester-client", exchangedToken.getIssuedFor());

}
Expand Down Expand Up @@ -256,9 +333,9 @@ public void testClientExchangeToItselfWithConsents() throws Exception {
public void testExchangeWithPublicClient() throws Exception {
String accessToken = resourceOwnerLogin("john", "password","subject-client", "secret");
AccessTokenResponse response = tokenExchange(accessToken, "requester-client-public", null, null, null);
org.junit.Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
org.junit.Assert.assertEquals(OAuthErrorException.INVALID_CLIENT, response.getError());
org.junit.Assert.assertEquals("Public client is not allowed to exchange token", response.getErrorDescription());
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
assertEquals(OAuthErrorException.INVALID_CLIENT, response.getError());
assertEquals("Public client is not allowed to exchange token", response.getErrorDescription());
}

@Test
Expand All @@ -281,14 +358,14 @@ public void testUnavailableAudienceRequested() throws Exception {
String accessToken = resourceOwnerLogin("john", "password","subject-client", "secret");
// request invalid client audience
AccessTokenResponse response = tokenExchange(accessToken, "requester-client", "secret", List.of("target-client1", "invalid-client"), null);
org.junit.Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
org.junit.Assert.assertEquals(OAuthErrorException.INVALID_CLIENT, response.getError());
org.junit.Assert.assertEquals("Audience not found", response.getErrorDescription());
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
assertEquals(OAuthErrorException.INVALID_CLIENT, response.getError());
assertEquals("Audience not found", response.getErrorDescription());
// The "target-client3" is valid client, but audience unavailable to the user. Request not allowed
response = tokenExchange(accessToken, "requester-client", "secret", List.of("target-client1", "target-client3"), null);
org.junit.Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
org.junit.Assert.assertEquals(OAuthErrorException.INVALID_REQUEST, response.getError());
org.junit.Assert.assertEquals("Requested audience not available: target-client3", response.getErrorDescription());
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
assertEquals(OAuthErrorException.INVALID_REQUEST, response.getError());
assertEquals("Requested audience not available: target-client3", response.getErrorDescription());
}

@Test
Expand All @@ -299,24 +376,24 @@ public void testScopeNotAllowed() throws Exception {
oauth.scope("optional-scope3");
AccessTokenResponse response = tokenExchange(accessToken, "requester-client", "secret", List.of("target-client1", "target-client3"), null);
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
org.junit.Assert.assertEquals(OAuthErrorException.INVALID_SCOPE, response.getError());
org.junit.Assert.assertEquals("Invalid scopes: optional-scope3", response.getErrorDescription());
assertEquals(OAuthErrorException.INVALID_SCOPE, response.getError());
assertEquals("Invalid scopes: optional-scope3", response.getErrorDescription());

//scope that doesn't exist
oauth.scope("bad-scope");
response = tokenExchange(accessToken, "requester-client", "secret", List.of("target-client1", "target-client3"), null);
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
org.junit.Assert.assertEquals(OAuthErrorException.INVALID_SCOPE, response.getError());
org.junit.Assert.assertEquals("Invalid scopes: bad-scope", response.getErrorDescription());
assertEquals(OAuthErrorException.INVALID_SCOPE, response.getError());
assertEquals("Invalid scopes: bad-scope", response.getErrorDescription());
}

@Test
public void testScopeFilter() throws Exception {
String accessToken = resourceOwnerLogin("john", "password","subject-client", "secret");
AccessTokenResponse response = tokenExchange(accessToken, "requester-client", "secret", List.of("target-client2"), null);
org.junit.Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
org.junit.Assert.assertEquals(OAuthErrorException.INVALID_REQUEST, response.getError());
org.junit.Assert.assertEquals("Requested audience not available: target-client2", response.getErrorDescription());
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
assertEquals(OAuthErrorException.INVALID_REQUEST, response.getError());
assertEquals("Requested audience not available: target-client2", response.getErrorDescription());

oauth.scope("optional-scope2");
response = tokenExchange(accessToken, "requester-client", "secret", List.of("target-client2"), null);
Expand Down Expand Up @@ -344,7 +421,7 @@ public void testScopeParamIncludedAudienceIncludedRefreshToken() throws Exceptio
oauth.scope("optional-scope2");
AccessTokenResponse response = tokenExchange(accessToken, "requester-client", "secret", List.of("target-client1"), Collections.singletonMap(OAuth2Constants.REQUESTED_TOKEN_TYPE, OAuth2Constants.REFRESH_TOKEN_TYPE));
assertAudiencesAndScopes(response, List.of("target-client1"), List.of("default-scope1", "optional-scope2"));
org.junit.Assert.assertNotNull(response.getRefreshToken());
assertNotNull(response.getRefreshToken());

oauth.client("requester-client", "secret");
response = oauth.doRefreshTokenRequest(response.getRefreshToken());
Expand Down Expand Up @@ -376,7 +453,7 @@ private void assertAudiencesAndScopes(AccessTokenResponse tokenExchangeResponse,
TokenVerifier<AccessToken> accessTokenVerifier = TokenVerifier.create(tokenExchangeResponse.getAccessToken(), AccessToken.class);
AccessToken token = accessTokenVerifier.parse().getToken();
if (expectedAudiences == null) {
org.junit.Assert.assertNull("Expected token to not contain audience", token.getAudience());
assertNull("Expected token to not contain audience", token.getAudience());
} else {
assertAudiences(token, expectedAudiences);
}
Expand Down