Ignore include in token scope for refresh token
Closes #12326 Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
This commit is contained in:
parent
5e00fe8b10
commit
8c3f7cc6e9
4 changed files with 60 additions and 7 deletions
|
@ -54,6 +54,8 @@ public interface ClientSessionContext {
|
||||||
|
|
||||||
String getScopeString();
|
String getScopeString();
|
||||||
|
|
||||||
|
String getScopeString(boolean ignoreIncludeInTokenScope);
|
||||||
|
|
||||||
void setAttribute(String name, Object value);
|
void setAttribute(String name, Object value);
|
||||||
|
|
||||||
<T> T getAttribute(String attribute, Class<T> clazz);
|
<T> T getAttribute(String attribute, Class<T> clazz);
|
||||||
|
|
|
@ -1094,6 +1094,7 @@ public class TokenManager {
|
||||||
ClientScopeModel offlineAccessScope = KeycloakModelUtils.getClientScopeByName(realm, OAuth2Constants.OFFLINE_ACCESS);
|
ClientScopeModel offlineAccessScope = KeycloakModelUtils.getClientScopeByName(realm, OAuth2Constants.OFFLINE_ACCESS);
|
||||||
boolean offlineTokenRequested = offlineAccessScope==null ? false : clientSessionCtx.getClientScopeIds().contains(offlineAccessScope.getId());
|
boolean offlineTokenRequested = offlineAccessScope==null ? false : clientSessionCtx.getClientScopeIds().contains(offlineAccessScope.getId());
|
||||||
generateRefreshToken(offlineTokenRequested);
|
generateRefreshToken(offlineTokenRequested);
|
||||||
|
refreshToken.setScope(clientSessionCtx.getScopeString(true));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -166,8 +166,13 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getScopeString() {
|
public String getScopeString() {
|
||||||
|
return getScopeString(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getScopeString(boolean ignoreIncludeInTokenScope) {
|
||||||
if (Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES)) {
|
if (Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES)) {
|
||||||
String scopeParam = buildScopesStringFromAuthorizationRequest();
|
String scopeParam = buildScopesStringFromAuthorizationRequest(ignoreIncludeInTokenScope);
|
||||||
logger.tracef("Generated scope param with Dynamic Scopes enabled: %1s", scopeParam);
|
logger.tracef("Generated scope param with Dynamic Scopes enabled: %1s", scopeParam);
|
||||||
String scopeSent = clientSession.getNote(OAuth2Constants.SCOPE);
|
String scopeSent = clientSession.getNote(OAuth2Constants.SCOPE);
|
||||||
if (TokenUtil.isOIDCRequest(scopeSent)) {
|
if (TokenUtil.isOIDCRequest(scopeSent)) {
|
||||||
|
@ -178,7 +183,7 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
||||||
// Add both default and optional scopes to scope parameter. Don't add client itself
|
// Add both default and optional scopes to scope parameter. Don't add client itself
|
||||||
String scopeParam = getClientScopesStream()
|
String scopeParam = getClientScopesStream()
|
||||||
.filter(((Predicate<ClientScopeModel>) ClientModel.class::isInstance).negate())
|
.filter(((Predicate<ClientScopeModel>) ClientModel.class::isInstance).negate())
|
||||||
.filter(ClientScopeModel::isIncludeInTokenScope)
|
.filter(scope-> scope.isIncludeInTokenScope() || ignoreIncludeInTokenScope)
|
||||||
.map(ClientScopeModel::getName)
|
.map(ClientScopeModel::getName)
|
||||||
.collect(Collectors.joining(" "));
|
.collect(Collectors.joining(" "));
|
||||||
|
|
||||||
|
@ -196,12 +201,14 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
||||||
* they should be included in tokens or not.
|
* they should be included in tokens or not.
|
||||||
* Then return the scope name from the data stored in the RAR object representation.
|
* Then return the scope name from the data stored in the RAR object representation.
|
||||||
*
|
*
|
||||||
|
* @param ignoreIncludeInTokenScope ignore include in token scope from client scope options
|
||||||
|
*
|
||||||
* @return see description
|
* @return see description
|
||||||
*/
|
*/
|
||||||
private String buildScopesStringFromAuthorizationRequest() {
|
private String buildScopesStringFromAuthorizationRequest(boolean ignoreIncludeInTokenScope) {
|
||||||
return AuthorizationContextUtil.getAuthorizationRequestContextFromScopes(session, clientSession.getNote(OAuth2Constants.SCOPE)).getAuthorizationDetailEntries().stream()
|
return AuthorizationContextUtil.getAuthorizationRequestContextFromScopes(session, clientSession.getNote(OAuth2Constants.SCOPE)).getAuthorizationDetailEntries().stream()
|
||||||
.filter(authorizationDetails -> authorizationDetails.getSource().equals(AuthorizationRequestSource.SCOPE))
|
.filter(authorizationDetails -> authorizationDetails.getSource().equals(AuthorizationRequestSource.SCOPE))
|
||||||
.filter(authorizationDetails -> authorizationDetails.getClientScope().isIncludeInTokenScope())
|
.filter(authorizationDetails -> authorizationDetails.getClientScope().isIncludeInTokenScope() || ignoreIncludeInTokenScope)
|
||||||
.filter(authorizationDetails -> isClientScopePermittedForUser(authorizationDetails.getClientScope()))
|
.filter(authorizationDetails -> isClientScopePermittedForUser(authorizationDetails.getClientScope()))
|
||||||
.map(authorizationDetails -> authorizationDetails.getAuthorizationDetails().getScopeNameFromCustomData())
|
.map(authorizationDetails -> authorizationDetails.getAuthorizationDetails().getScopeNameFromCustomData())
|
||||||
.collect(Collectors.joining(" "));
|
.collect(Collectors.joining(" "));
|
||||||
|
|
|
@ -50,6 +50,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.SessionTimeoutHelper;
|
import org.keycloak.models.utils.SessionTimeoutHelper;
|
||||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
|
@ -537,7 +538,7 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
||||||
oauth.scope(optionalScope);
|
oauth.scope(optionalScope);
|
||||||
OAuthClient.AccessTokenResponse response1 = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
|
OAuthClient.AccessTokenResponse response1 = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
|
||||||
RefreshToken refreshToken1 = oauth.parseRefreshToken(response1.getRefreshToken());
|
RefreshToken refreshToken1 = oauth.parseRefreshToken(response1.getRefreshToken());
|
||||||
AbstractOIDCScopeTest.assertScopes("openid email phone address profile", refreshToken1.getScope());
|
AbstractOIDCScopeTest.assertScopes("openid basic email roles web-origins acr profile address phone", refreshToken1.getScope());
|
||||||
|
|
||||||
setTimeOffset(2);
|
setTimeOffset(2);
|
||||||
|
|
||||||
|
@ -548,7 +549,7 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
||||||
AbstractOIDCScopeTest.assertScopes("openid email phone profile", response2.getScope());
|
AbstractOIDCScopeTest.assertScopes("openid email phone profile", response2.getScope());
|
||||||
RefreshToken refreshToken2 = oauth.parseRefreshToken(response2.getRefreshToken());
|
RefreshToken refreshToken2 = oauth.parseRefreshToken(response2.getRefreshToken());
|
||||||
assertNotNull(refreshToken2);
|
assertNotNull(refreshToken2);
|
||||||
AbstractOIDCScopeTest.assertScopes("openid email phone address profile", refreshToken2.getScope());
|
AbstractOIDCScopeTest.assertScopes("openid acr roles phone address email profile basic web-origins", refreshToken2.getScope());
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
setTimeOffset(0);
|
setTimeOffset(0);
|
||||||
|
@ -566,7 +567,7 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
OAuthClient.AccessTokenResponse response1 = oauth.doAccessTokenRequest(code, "password");
|
OAuthClient.AccessTokenResponse response1 = oauth.doAccessTokenRequest(code, "password");
|
||||||
RefreshToken refreshToken1 = oauth.parseRefreshToken(response1.getRefreshToken());
|
RefreshToken refreshToken1 = oauth.parseRefreshToken(response1.getRefreshToken());
|
||||||
AbstractOIDCScopeTest.assertScopes("openid email profile", refreshToken1.getScope());
|
AbstractOIDCScopeTest.assertScopes("openid basic email roles web-origins acr profile", refreshToken1.getScope());
|
||||||
|
|
||||||
setTimeOffset(2);
|
setTimeOffset(2);
|
||||||
|
|
||||||
|
@ -582,6 +583,48 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void refreshWithOptionalClientScopeWithIncludeInTokenScopeDisabled() throws Exception {
|
||||||
|
//set roles client scope as optional
|
||||||
|
ClientScopeRepresentation rolesScope = ApiUtil.findClientScopeByName(adminClient.realm("test"), OIDCLoginProtocolFactory.ROLES_SCOPE).toRepresentation();
|
||||||
|
ClientManager.realm(adminClient.realm("test")).clientId(oauth.getClientId()).removeClientScope(rolesScope.getId(),true);
|
||||||
|
ClientManager.realm(adminClient.realm("test")).clientId(oauth.getClientId()).addClientScope(rolesScope.getId(),false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
oauth.scope("roles");
|
||||||
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
|
|
||||||
|
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||||
|
|
||||||
|
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
|
||||||
|
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
|
||||||
|
RefreshToken refreshToken = oauth.parseRefreshToken(response.getRefreshToken());
|
||||||
|
|
||||||
|
AbstractOIDCScopeTest.assertScopes("openid email profile", accessToken.getScope());
|
||||||
|
AbstractOIDCScopeTest.assertScopes("openid basic email roles web-origins acr profile", refreshToken.getScope());
|
||||||
|
|
||||||
|
Assert.assertNotNull(accessToken.getRealmAccess());
|
||||||
|
Assert.assertNotNull(accessToken.getResourceAccess());
|
||||||
|
|
||||||
|
oauth.scope(null);
|
||||||
|
|
||||||
|
response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "password");
|
||||||
|
|
||||||
|
accessToken = oauth.verifyToken(response.getAccessToken());
|
||||||
|
refreshToken = oauth.parseRefreshToken(response.getRefreshToken());
|
||||||
|
|
||||||
|
AbstractOIDCScopeTest.assertScopes("openid email profile", accessToken.getScope());
|
||||||
|
AbstractOIDCScopeTest.assertScopes("openid basic email roles web-origins acr profile", refreshToken.getScope());
|
||||||
|
|
||||||
|
Assert.assertNotNull(accessToken.getRealmAccess());
|
||||||
|
Assert.assertNotNull(accessToken.getResourceAccess());
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
ClientManager.realm(adminClient.realm("test")).clientId(oauth.getClientId()).removeClientScope(rolesScope.getId(),false);
|
||||||
|
ClientManager.realm(adminClient.realm("test")).clientId(oauth.getClientId()).addClientScope(rolesScope.getId(),true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void refreshTokenReuseTokenWithRefreshTokensRevoked() throws Exception {
|
public void refreshTokenReuseTokenWithRefreshTokensRevoked() throws Exception {
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in a new issue