scope parameter in refresh flow
Closes #12009 Signed-off-by: cgeorgilakis-grnet <cgeorgilakis@admin.grnet.gr>
This commit is contained in:
parent
f633041db3
commit
cf57af1d10
8 changed files with 179 additions and 26 deletions
|
@ -138,7 +138,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
|
|||
return token;
|
||||
}
|
||||
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
|
||||
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession, session);
|
||||
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, token.getScope(), session);
|
||||
AccessToken smallToken = getAccessTokenFromStoredData(token, userSession);
|
||||
return tokenManager.transformIntrospectionAccessToken(session, smallToken, userSession, clientSessionCtx);
|
||||
}
|
||||
|
|
|
@ -142,7 +142,7 @@ public class TokenManager {
|
|||
}
|
||||
|
||||
public TokenValidation validateToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm,
|
||||
RefreshToken oldToken, HttpHeaders headers) throws OAuthErrorException {
|
||||
RefreshToken oldToken, HttpHeaders headers, String oldTokenScope) throws OAuthErrorException {
|
||||
UserSessionModel userSession = null;
|
||||
boolean offline = TokenUtil.TOKEN_TYPE_OFFLINE.equals(oldToken.getType());
|
||||
|
||||
|
@ -215,9 +215,6 @@ public class TokenManager {
|
|||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
|
||||
}
|
||||
|
||||
// Setup clientScopes from refresh token to the context
|
||||
String oldTokenScope = oldToken.getScope();
|
||||
|
||||
// Case when offline token is migrated from previous version
|
||||
if (oldTokenScope == null && userSession.isOffline()) {
|
||||
logger.debugf("Migrating offline token of user '%s' for client '%s' of realm '%s'", user.getUsername(), client.getClientId(), realm.getName());
|
||||
|
@ -379,14 +376,24 @@ public class TokenManager {
|
|||
|
||||
|
||||
public AccessTokenResponseBuilder refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient,
|
||||
String encodedRefreshToken, EventBuilder event, HttpHeaders headers, HttpRequest request) throws OAuthErrorException {
|
||||
String encodedRefreshToken, EventBuilder event, HttpHeaders headers, HttpRequest request, String scopeParameter) throws OAuthErrorException {
|
||||
RefreshToken refreshToken = verifyRefreshToken(session, realm, authorizedClient, request, encodedRefreshToken, true);
|
||||
|
||||
event.user(refreshToken.getSubject()).session(refreshToken.getSessionState())
|
||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||
.detail(Details.REFRESH_TOKEN_TYPE, refreshToken.getType());
|
||||
// Setup clientScopes from refresh token to the context
|
||||
String oldTokenScope = refreshToken.getScope();
|
||||
//The requested scope MUST NOT include any scope not originally granted by the resource owner
|
||||
//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))
|
||||
.collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
TokenValidation validation = validateToken(session, uriInfo, connection, realm, refreshToken, headers);
|
||||
|
||||
TokenValidation validation = validateToken(session, uriInfo, connection, realm, refreshToken, headers, oldTokenScope);
|
||||
AuthenticatedClientSessionModel clientSession = validation.clientSessionCtx.getClientSession();
|
||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientModel(authorizedClient);
|
||||
|
||||
|
@ -408,7 +415,8 @@ public class TokenManager {
|
|||
AccessTokenResponseBuilder responseBuilder = responseBuilder(realm, authorizedClient, event, session,
|
||||
validation.userSession, validation.clientSessionCtx).accessToken(validation.newToken);
|
||||
if (clientConfig.isUseRefreshToken()) {
|
||||
responseBuilder.generateRefreshToken();
|
||||
//refresh token must have same scope as old refresh token (type, scope, expiration)
|
||||
responseBuilder.generateRefreshToken(refreshToken.getScope());
|
||||
}
|
||||
|
||||
if (validation.newToken.getAuthorization() != null
|
||||
|
@ -1093,6 +1101,22 @@ public class TokenManager {
|
|||
|
||||
ClientScopeModel offlineAccessScope = KeycloakModelUtils.getClientScopeByName(realm, OAuth2Constants.OFFLINE_ACCESS);
|
||||
boolean offlineTokenRequested = offlineAccessScope==null ? false : clientSessionCtx.getClientScopeIds().contains(offlineAccessScope.getId());
|
||||
generateRefreshToken(offlineTokenRequested);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccessTokenResponseBuilder generateRefreshToken(String scope) {
|
||||
if (accessToken == null) {
|
||||
throw new IllegalStateException("accessToken not set");
|
||||
}
|
||||
|
||||
boolean offlineTokenRequested = Arrays.asList(scope.split(" ")).contains(OAuth2Constants.OFFLINE_ACCESS) ;
|
||||
generateRefreshToken(offlineTokenRequested);
|
||||
refreshToken.setScope(scope);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void generateRefreshToken(boolean offlineTokenRequested) {
|
||||
if (offlineTokenRequested) {
|
||||
UserSessionManager sessionManager = new UserSessionManager(session);
|
||||
if (!sessionManager.isOfflineTokenAllowed(clientSessionCtx)) {
|
||||
|
@ -1111,7 +1135,6 @@ public class TokenManager {
|
|||
}
|
||||
refreshToken.id(KeycloakModelUtils.generateId());
|
||||
refreshToken.issuedNow();
|
||||
return this;
|
||||
}
|
||||
|
||||
private int getExpiration(boolean offline) {
|
||||
|
|
|
@ -551,6 +551,8 @@ public class TokenEndpoint {
|
|||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "No refresh token", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
String scopeParameter = getRequestedScopes();
|
||||
|
||||
try {
|
||||
session.clientPolicy().triggerOnEvent(new TokenRefreshContext(formParams));
|
||||
refreshToken = formParams.getFirst(OAuth2Constants.REFRESH_TOKEN);
|
||||
|
@ -562,7 +564,7 @@ public class TokenEndpoint {
|
|||
AccessTokenResponse res;
|
||||
try {
|
||||
// KEYCLOAK-6771 Certificate Bound Token
|
||||
TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.refreshAccessToken(session, session.getContext().getUri(), clientConnection, realm, client, refreshToken, event, headers, request);
|
||||
TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.refreshAccessToken(session, session.getContext().getUri(), clientConnection, realm, client, refreshToken, event, headers, request, scopeParameter);
|
||||
|
||||
checkAndBindMtlsHoKToken(responseBuilder, clientConfig.isUseRefreshToken());
|
||||
checkAndBindDPoPToken(responseBuilder, clientConfig.isUseRefreshToken() && (client.isPublicClient() || client.isBearerOnly()), Profile.isFeatureEnabled(Profile.Feature.DPOP));
|
||||
|
|
|
@ -263,8 +263,8 @@ public class UserInfoEndpoint {
|
|||
// Existence of authenticatedClientSession for our client already handled before
|
||||
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(clientModel.getId());
|
||||
|
||||
// Retrieve by latest scope parameter
|
||||
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession, session);
|
||||
// Retrieve by access token scope parameter
|
||||
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, token.getScope(), session);
|
||||
|
||||
AccessToken userInfo = new AccessToken();
|
||||
|
||||
|
|
|
@ -1007,6 +1007,9 @@ public class OAuthClient {
|
|||
if (refreshToken != null) {
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
|
||||
}
|
||||
if (scope != null) {
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.SCOPE, scope));
|
||||
}
|
||||
if (clientId != null && password != null) {
|
||||
String authorization = BasicAuthHelper.createHeader(clientId, password);
|
||||
post.setHeader("Authorization", authorization);
|
||||
|
|
|
@ -260,7 +260,7 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
|
||||
assertTrue(tokenResponse.getScope().contains(OAuth2Constants.OFFLINE_ACCESS));
|
||||
|
||||
String newRefreshTokenString = testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, sessionId, userId);
|
||||
String newRefreshTokenString = testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, sessionId, userId, false);
|
||||
|
||||
// Change offset to very big value to ensure offline session expires
|
||||
setTimeOffset(3000000);
|
||||
|
@ -281,7 +281,7 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
}
|
||||
|
||||
private String testRefreshWithOfflineToken(AccessToken oldToken, RefreshToken offlineToken, String offlineTokenString,
|
||||
final String sessionId, String userId) {
|
||||
final String sessionId, String userId, boolean scopeParameterExist) {
|
||||
// Change offset to big value to ensure userSession expired
|
||||
setTimeOffset(99999);
|
||||
assertFalse(oldToken.isActive());
|
||||
|
@ -301,11 +301,15 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
|
||||
// Assert new refreshToken in the response
|
||||
String newRefreshToken = response.getRefreshToken();
|
||||
RefreshToken newRefreshTokenFull = oauth.parseRefreshToken(newRefreshToken);
|
||||
Assert.assertNotNull(newRefreshToken);
|
||||
Assert.assertNotEquals(oldToken.getId(), refreshedToken.getId());
|
||||
|
||||
// Assert scope parameter contains "offline_access"
|
||||
assertTrue(response.getScope().contains(OAuth2Constants.OFFLINE_ACCESS));
|
||||
// scope parameter contains "offline_access" if not filter via scope parameter
|
||||
assertTrue(scopeParameterExist ? !refreshedToken.getScope().contains(OAuth2Constants.OFFLINE_ACCESS) : refreshedToken.getScope().contains(OAuth2Constants.OFFLINE_ACCESS));
|
||||
// Assert refresh token scope parameter contains "offline_access"
|
||||
assertTrue(newRefreshTokenFull.getScope().contains(OAuth2Constants.OFFLINE_ACCESS));
|
||||
Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, newRefreshTokenFull.getType());
|
||||
|
||||
Assert.assertEquals(userId, refreshedToken.getSubject());
|
||||
|
||||
|
@ -354,10 +358,10 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
|
||||
Assert.assertEquals(0, offlineToken.getExpiration());
|
||||
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), userId);
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), userId, false);
|
||||
|
||||
// Assert same token can be refreshed again
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), userId);
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), userId, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -389,7 +393,7 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
|
||||
Assert.assertEquals(0, offlineToken.getExpiration());
|
||||
|
||||
String offlineTokenString2 = testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), userId);
|
||||
String offlineTokenString2 = testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), userId, false);
|
||||
RefreshToken offlineToken2 = oauth.parseRefreshToken(offlineTokenString2);
|
||||
|
||||
// Assert second refresh with same refresh token will fail
|
||||
|
@ -438,7 +442,7 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
|
||||
Assert.assertEquals(0, offlineToken.getExpiration());
|
||||
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), serviceAccountUserId);
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), serviceAccountUserId, false);
|
||||
|
||||
// Now retrieve another offline token and verify that previous offline token is still valid
|
||||
tokenResponse = oauth.doClientCredentialsGrantAccessTokenRequest("secret1");
|
||||
|
@ -458,8 +462,8 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
.assertEvent();
|
||||
|
||||
// Refresh with both offline tokens is fine
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), serviceAccountUserId);
|
||||
testRefreshWithOfflineToken(token2, offlineToken2, offlineTokenString2, token2.getSessionState(), serviceAccountUserId);
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), serviceAccountUserId, false);
|
||||
testRefreshWithOfflineToken(token2, offlineToken2, offlineTokenString2, token2.getSessionState(), serviceAccountUserId, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -632,7 +636,7 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
assertEquals(200, offlineRefresh.getStatusCode());
|
||||
|
||||
// logout online session
|
||||
CloseableHttpResponse logoutResponse = oauth.scope("").doLogout(response.getRefreshToken(), "secret1");
|
||||
CloseableHttpResponse logoutResponse = oauth.scope(null).doLogout(response.getRefreshToken(), "secret1");
|
||||
assertEquals(204, logoutResponse.getStatusLine().getStatusCode());
|
||||
|
||||
// assert the online session is gone
|
||||
|
@ -832,6 +836,7 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, expectedIdTokenAlg);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "offline-client"), expectedAccessAlg);
|
||||
offlineTokenRequest(expectedRefreshAlg, expectedAccessAlg, expectedIdTokenAlg);
|
||||
offlineTokenRequestWithScopeParameter(expectedRefreshAlg, expectedAccessAlg, expectedIdTokenAlg);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "offline-client"), Algorithm.RS256);
|
||||
|
@ -973,7 +978,7 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
|
||||
Assert.assertEquals(0, offlineToken.getExpiration());
|
||||
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), serviceAccountUserId);
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), serviceAccountUserId, false);
|
||||
|
||||
// Now retrieve another offline token and decode that previous offline token is still valid
|
||||
tokenResponse = oauth.doClientCredentialsGrantAccessTokenRequest("secret1");
|
||||
|
@ -993,11 +998,63 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
.assertEvent();
|
||||
|
||||
// Refresh with both offline tokens is fine
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), serviceAccountUserId);
|
||||
testRefreshWithOfflineToken(token2, offlineToken2, offlineTokenString2, token2.getSessionState(), serviceAccountUserId);
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), serviceAccountUserId, false);
|
||||
testRefreshWithOfflineToken(token2, offlineToken2, offlineTokenString2, token2.getSessionState(), serviceAccountUserId, false);
|
||||
|
||||
}
|
||||
|
||||
private void offlineTokenRequestWithScopeParameter(String expectedRefreshAlg, String expectedAccessAlg, String expectedIdTokenAlg) throws Exception {
|
||||
ClientScopeRepresentation phoneScope = adminClient.realm("test").clientScopes().findAll().stream().filter((ClientScopeRepresentation clientScope) ->"phone".equals(clientScope.getName())).findFirst().get();
|
||||
ClientManager.realm(adminClient.realm("test")).clientId(oauth.getClientId()).addClientScope(phoneScope.getId(),false);
|
||||
oauth.scope(OAuth2Constants.OFFLINE_ACCESS+" phone");
|
||||
oauth.clientId("offline-client");
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doClientCredentialsGrantAccessTokenRequest("secret1");
|
||||
|
||||
JWSHeader header = null;
|
||||
String idToken = tokenResponse.getIdToken();
|
||||
String accessToken = tokenResponse.getAccessToken();
|
||||
String refreshToken = tokenResponse.getRefreshToken();
|
||||
if (idToken != null) {
|
||||
header = new JWSInput(idToken).getHeader();
|
||||
assertEquals(expectedIdTokenAlg, header.getAlgorithm().name());
|
||||
assertEquals("JWT", header.getType());
|
||||
assertNull(header.getContentType());
|
||||
}
|
||||
if (accessToken != null) {
|
||||
header = new JWSInput(accessToken).getHeader();
|
||||
assertEquals(expectedAccessAlg, header.getAlgorithm().name());
|
||||
assertEquals("JWT", header.getType());
|
||||
assertNull(header.getContentType());
|
||||
}
|
||||
if (refreshToken != null) {
|
||||
header = new JWSInput(refreshToken).getHeader();
|
||||
assertEquals(expectedRefreshAlg, header.getAlgorithm().name());
|
||||
assertEquals("JWT", header.getType());
|
||||
assertNull(header.getContentType());
|
||||
}
|
||||
|
||||
AccessToken token = oauth.verifyToken(tokenResponse.getAccessToken());
|
||||
String offlineTokenString = tokenResponse.getRefreshToken();
|
||||
RefreshToken offlineToken = oauth.parseRefreshToken(offlineTokenString);
|
||||
|
||||
events.expectClientLogin()
|
||||
.client("offline-client")
|
||||
.user(serviceAccountUserId)
|
||||
.session(token.getSessionState())
|
||||
.detail(Details.TOKEN_ID, token.getId())
|
||||
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
|
||||
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
|
||||
.detail(Details.USERNAME, ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + "offline-client")
|
||||
.assertEvent();
|
||||
|
||||
Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
|
||||
Assert.assertEquals(0, offlineToken.getExpiration());
|
||||
|
||||
//refresh token without sending offline_access scope => access token without it and same refresh token
|
||||
oauth.scope("phone");
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), serviceAccountUserId, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshTokenUserClientMaxLifespanSmallerThanSession() throws Exception {
|
||||
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||
|
|
|
@ -52,7 +52,9 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
|||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.representations.UserInfo;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||
|
@ -61,6 +63,7 @@ import org.keycloak.testsuite.AbstractKeycloakTest;
|
|||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.undertow.lb.SimpleUndertowLoadBalancer;
|
||||
import org.keycloak.testsuite.oidc.AbstractOIDCScopeTest;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
||||
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
|
||||
|
@ -106,6 +109,7 @@ import static org.keycloak.protocol.oidc.OIDCConfigAttributes.CLIENT_SESSION_MAX
|
|||
import static org.keycloak.testsuite.Assert.assertExpiration;
|
||||
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
|
||||
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED;
|
||||
import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT;
|
||||
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED;
|
||||
import static org.keycloak.testsuite.arquillian.AuthServerTestEnricher.getHttpAuthServerContextRoot;
|
||||
|
@ -495,6 +499,69 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
events.expectRefresh(refreshToken1.getId(), sessionId).assertEvent();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void refreshTokenReuseTokenWithoutRefreshTokensRevokedWithLessScopes() throws Exception {
|
||||
//add phone,address as optional scope and request them
|
||||
ClientScopeRepresentation phoneScope = adminClient.realm("test").clientScopes().findAll().stream().filter((ClientScopeRepresentation clientScope) ->"phone".equals(clientScope.getName())).findFirst().get();
|
||||
ClientScopeRepresentation addressScope = adminClient.realm("test").clientScopes().findAll().stream().filter((ClientScopeRepresentation clientScope) ->"address".equals(clientScope.getName())).findFirst().get();
|
||||
ClientManager.realm(adminClient.realm("test")).clientId(oauth.getClientId()).addClientScope(phoneScope.getId(),false);
|
||||
ClientManager.realm(adminClient.realm("test")).clientId(oauth.getClientId()).addClientScope(addressScope.getId(),false);
|
||||
|
||||
try {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
String optionalScope = "phone address";
|
||||
oauth.scope(optionalScope);
|
||||
OAuthClient.AccessTokenResponse response1 = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
|
||||
RefreshToken refreshToken1 = oauth.parseRefreshToken(response1.getRefreshToken());
|
||||
AbstractOIDCScopeTest.assertScopes("openid email phone address profile", refreshToken1.getScope());
|
||||
|
||||
setTimeOffset(2);
|
||||
|
||||
String scope = "email phone";
|
||||
oauth.scope(scope);
|
||||
OAuthClient.AccessTokenResponse response2 = oauth.doRefreshTokenRequest(response1.getRefreshToken(), "password");
|
||||
assertEquals(200, response2.getStatusCode());
|
||||
AbstractOIDCScopeTest.assertScopes("openid email phone profile", response2.getScope());
|
||||
RefreshToken refreshToken2 = oauth.parseRefreshToken(response2.getRefreshToken());
|
||||
assertNotNull(refreshToken2);
|
||||
AbstractOIDCScopeTest.assertScopes("openid email phone address profile", refreshToken2.getScope());
|
||||
|
||||
} finally {
|
||||
setTimeOffset(0);
|
||||
oauth.scope(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshTokenReuseTokenScopeParameterNotInRefreshToken() throws Exception {
|
||||
try {
|
||||
//scope parameter consists scope that is not part of scope refresh token => error thrown
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
OAuthClient.AccessTokenResponse response1 = oauth.doAccessTokenRequest(code, "password");
|
||||
RefreshToken refreshToken1 = oauth.parseRefreshToken(response1.getRefreshToken());
|
||||
AbstractOIDCScopeTest.assertScopes("openid email profile", refreshToken1.getScope());
|
||||
|
||||
setTimeOffset(2);
|
||||
|
||||
String scope = "openid email ssh_public_key";
|
||||
oauth.scope(scope);
|
||||
OAuthClient.AccessTokenResponse response2 = oauth.doRefreshTokenRequest(response1.getRefreshToken(), "password");
|
||||
assertEquals(400, response2.getStatusCode());
|
||||
assertEquals(OAuthErrorException.INVALID_SCOPE, response2.getError());
|
||||
|
||||
} finally {
|
||||
setTimeOffset(0);
|
||||
oauth.scope(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshTokenReuseTokenWithRefreshTokensRevoked() throws Exception {
|
||||
try {
|
||||
|
|
|
@ -579,6 +579,7 @@ public class OIDCScopeTest extends AbstractOIDCScopeTest {
|
|||
Assert.assertTrue(tokens2.accessToken.getRealmAccess().isUserInRole("role-2"));
|
||||
|
||||
// Ensure I can refresh refreshToken1. Just role1 is present
|
||||
oauth.scope(null);
|
||||
OAuthClient.AccessTokenResponse refreshResponse1 = oauth.doRefreshTokenRequest(tokens1.refreshToken, "password");
|
||||
Assert.assertEquals(200, refreshResponse1.getStatusCode());
|
||||
AccessToken accessToken1 = oauth.verifyToken(refreshResponse1.getAccessToken());
|
||||
|
|
Loading…
Reference in a new issue