Ensure issued_client_type is always added to successful token-exchange response (#31548)
- Compute issued_token_type response parameter based on requested_token_type and client configuration - `issued_token_type` is a required response parameter as per [RFC8693 2.2.1](https://datatracker.ietf.org/doc/html/rfc8693#section-2.2.1) - Added test to ClientTokenExchangeTest that requests an access-token as requested-token-type Fixes #31548 Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
This commit is contained in:
parent
a6c70d65ee
commit
282260dc95
2 changed files with 37 additions and 1 deletions
|
@ -437,11 +437,15 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider {
|
||||||
responseBuilder.getAccessToken().setSessionId(null);
|
responseBuilder.getAccessToken().setSessionId(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String issuedTokenType;
|
||||||
if (requestedTokenType.equals(OAuth2Constants.REFRESH_TOKEN_TYPE)
|
if (requestedTokenType.equals(OAuth2Constants.REFRESH_TOKEN_TYPE)
|
||||||
&& OIDCAdvancedConfigWrapper.fromClientModel(client).isUseRefreshToken()
|
&& OIDCAdvancedConfigWrapper.fromClientModel(client).isUseRefreshToken()
|
||||||
&& targetUserSession.getPersistenceState() != UserSessionModel.SessionPersistenceState.TRANSIENT) {
|
&& targetUserSession.getPersistenceState() != UserSessionModel.SessionPersistenceState.TRANSIENT) {
|
||||||
responseBuilder.generateRefreshToken();
|
responseBuilder.generateRefreshToken();
|
||||||
responseBuilder.getRefreshToken().issuedFor(client.getClientId());
|
responseBuilder.getRefreshToken().issuedFor(client.getClientId());
|
||||||
|
issuedTokenType = OAuth2Constants.REFRESH_TOKEN_TYPE;
|
||||||
|
} else {
|
||||||
|
issuedTokenType = OAuth2Constants.ACCESS_TOKEN_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
String scopeParam = clientSessionCtx.getClientSession().getNote(OAuth2Constants.SCOPE);
|
String scopeParam = clientSessionCtx.getClientSession().getNote(OAuth2Constants.SCOPE);
|
||||||
|
@ -450,6 +454,8 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessTokenResponse res = responseBuilder.build();
|
AccessTokenResponse res = responseBuilder.build();
|
||||||
|
res.setOtherClaims(OAuth2Constants.ISSUED_TOKEN_TYPE, issuedTokenType);
|
||||||
|
|
||||||
event.detail(Details.AUDIENCE, targetClient.getClientId())
|
event.detail(Details.AUDIENCE, targetClient.getClientId())
|
||||||
.user(targetUser);
|
.user(targetUser);
|
||||||
|
|
||||||
|
|
|
@ -304,6 +304,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
{
|
{
|
||||||
response = oauth.doTokenExchange(TEST, accessToken, "target", "client-exchanger", "secret");
|
response = oauth.doTokenExchange(TEST, accessToken, "target", "client-exchanger", "secret");
|
||||||
|
Assert.assertEquals(OAuth2Constants.REFRESH_TOKEN_TYPE, response.getIssuedTokenType());
|
||||||
String exchangedTokenString = response.getAccessToken();
|
String exchangedTokenString = response.getAccessToken();
|
||||||
TokenVerifier<AccessToken> verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class);
|
TokenVerifier<AccessToken> verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class);
|
||||||
AccessToken exchangedToken = verifier.parse().getToken();
|
AccessToken exchangedToken = verifier.parse().getToken();
|
||||||
|
@ -316,7 +317,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
{
|
{
|
||||||
response = oauth.doTokenExchange(TEST, accessToken, "target", "legal", "secret");
|
response = oauth.doTokenExchange(TEST, accessToken, "target", "legal", "secret");
|
||||||
|
Assert.assertEquals(OAuth2Constants.REFRESH_TOKEN_TYPE, response.getIssuedTokenType());
|
||||||
String exchangedTokenString = response.getAccessToken();
|
String exchangedTokenString = response.getAccessToken();
|
||||||
TokenVerifier<AccessToken> verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class);
|
TokenVerifier<AccessToken> verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class);
|
||||||
AccessToken exchangedToken = verifier.parse().getToken();
|
AccessToken exchangedToken = verifier.parse().getToken();
|
||||||
|
@ -332,6 +333,34 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExchangeRequestAccessTokenType() throws Exception {
|
||||||
|
testingClient.server().run(ClientTokenExchangeTest::setupRealm);
|
||||||
|
|
||||||
|
oauth.realm(TEST);
|
||||||
|
oauth.clientId("client-exchanger");
|
||||||
|
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "user", "password");
|
||||||
|
String accessToken = response.getAccessToken();
|
||||||
|
TokenVerifier<AccessToken> accessTokenVerifier = TokenVerifier.create(accessToken, AccessToken.class);
|
||||||
|
AccessToken token = accessTokenVerifier.parse().getToken();
|
||||||
|
Assert.assertNotNull(token.getSessionId());
|
||||||
|
Assert.assertEquals(token.getPreferredUsername(), "user");
|
||||||
|
assertTrue(token.getRealmAccess() == null || !token.getRealmAccess().isUserInRole("example"));
|
||||||
|
|
||||||
|
{
|
||||||
|
response = oauth.doTokenExchange(TEST, accessToken, "target", "client-exchanger", "secret", Map.of(OAuth2Constants.REQUESTED_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE));
|
||||||
|
Assert.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.assertEquals(token.getSessionId(), exchangedToken.getSessionId());
|
||||||
|
Assert.assertEquals("client-exchanger", exchangedToken.getIssuedFor());
|
||||||
|
Assert.assertEquals("target", exchangedToken.getAudience()[0]);
|
||||||
|
Assert.assertEquals(exchangedToken.getPreferredUsername(), "user");
|
||||||
|
assertTrue(exchangedToken.getRealmAccess().isUserInRole("example"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@UncaughtServerErrorExpected
|
@UncaughtServerErrorExpected
|
||||||
public void testExchangeUsingServiceAccount() throws Exception {
|
public void testExchangeUsingServiceAccount() throws Exception {
|
||||||
|
@ -347,6 +376,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
{
|
{
|
||||||
response = oauth.doTokenExchange(TEST, accessToken, "target", "my-service-account", "secret");
|
response = oauth.doTokenExchange(TEST, accessToken, "target", "my-service-account", "secret");
|
||||||
|
Assert.assertEquals(OAuth2Constants.ACCESS_TOKEN_TYPE, response.getIssuedTokenType());
|
||||||
String exchangedTokenString = response.getAccessToken();
|
String exchangedTokenString = response.getAccessToken();
|
||||||
TokenVerifier<AccessToken> verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class);
|
TokenVerifier<AccessToken> verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class);
|
||||||
AccessToken exchangedToken = verifier.parse().getToken();
|
AccessToken exchangedToken = verifier.parse().getToken();
|
||||||
|
|
Loading…
Reference in a new issue