Fix refresh token scope in refresh token flow with scope request parameter

Closes #28463

Signed-off-by: cgeorgilakis-grnet <cgeorgilakis@admin.grnet.gr>
This commit is contained in:
cgeorgilakis-grnet 2024-04-05 14:11:26 +03:00 committed by Pedro Igor
parent 4c2542b91f
commit 89263f5255
2 changed files with 14 additions and 8 deletions

View file

@ -389,7 +389,7 @@ public class TokenManager {
//if scope parameter is not null, remove every scope that is not part of scope parameter
if (scopeParameter != null && ! scopeParameter.isEmpty()) {
Set<String> scopeParamScopes = Arrays.stream(scopeParameter.split(" ")).collect(Collectors.toSet());
oldTokenScope = Arrays.stream(oldTokenScope.split(" ")).filter(sc -> scopeParamScopes.contains(sc) || sc.equals(OAuth2Constants.OFFLINE_ACCESS))
oldTokenScope = Arrays.stream(oldTokenScope.split(" ")).filter(sc -> scopeParamScopes.contains(sc))
.collect(Collectors.joining(" "));
}
@ -415,7 +415,7 @@ public class TokenManager {
validation.userSession, validation.clientSessionCtx).accessToken(validation.newToken);
if (clientConfig.isUseRefreshToken()) {
//refresh token must have same scope as old refresh token (type, scope, expiration)
responseBuilder.generateRefreshToken(refreshToken.getScope());
responseBuilder.generateRefreshToken(refreshToken.getScope(), clientSession);
}
if (validation.newToken.getAuthorization() != null
@ -1099,12 +1099,14 @@ public class TokenManager {
return this;
}
public AccessTokenResponseBuilder generateRefreshToken(String scope) {
public AccessTokenResponseBuilder generateRefreshToken(String scope, AuthenticatedClientSessionModel clientSession) {
if (accessToken == null) {
throw new IllegalStateException("accessToken not set");
}
boolean offlineTokenRequested = Arrays.asList(scope.split(" ")).contains(OAuth2Constants.OFFLINE_ACCESS) ;
if (offlineTokenRequested)
clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, scope, session);
generateRefreshToken(offlineTokenRequested);
refreshToken.setScope(scope);
return this;

View file

@ -305,7 +305,7 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
Assert.assertNotNull(newRefreshToken);
Assert.assertNotEquals(oldToken.getId(), refreshedToken.getId());
// scope parameter contains "offline_access" if not filter via scope parameter
// scope parameter either does not exist either contains offline_access
assertTrue(refreshedToken.getScope().contains(OAuth2Constants.OFFLINE_ACCESS));
// Assert refresh token scope parameter contains "offline_access"
assertTrue(newRefreshTokenFull.getScope().contains(OAuth2Constants.OFFLINE_ACCESS));
@ -1050,10 +1050,6 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
Assert.assertEquals(0, offlineToken.getExpiration());
//refresh token without sending offline_access scope
oauth.scope("phone");
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), serviceAccountUserId);
}
@Test
@ -1306,6 +1302,14 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
oauth.scope("openid");
response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "secret1");
assertEquals(200, response.getStatusCode());
AccessToken token = oauth.verifyToken(response.getAccessToken());
// access token scope does not contain offline_access due to luck of it in scope request parameter
assertFalse(token.getScope().contains(OAuth2Constants.OFFLINE_ACCESS));
RefreshToken offlineToken = oauth.parseRefreshToken(response.getRefreshToken());
// refresh token scope are always equal to original refresh token scope
Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
assertTrue(offlineToken.getScope().contains(OAuth2Constants.OFFLINE_ACCESS));
}
finally {
ClientManager.realm(adminClient.realm("test")).clientId("offline-client").fullScopeAllowed(true);