diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index e2df13b1f9..b6184db89f 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -417,7 +417,7 @@ public class TokenManager { } AccessTokenResponseBuilder responseBuilder = responseBuilder(realm, authorizedClient, event, session, - validation.userSession, validation.clientSessionCtx).accessToken(validation.newToken); + validation.userSession, validation.clientSessionCtx).offlineToken( TokenUtil.TOKEN_TYPE_OFFLINE.equals(refreshToken.getType())).accessToken(validation.newToken); if (clientConfig.isUseRefreshToken()) { //refresh token must have same scope as old refresh token (type, scope, expiration) responseBuilder.generateRefreshToken(refreshToken, clientSession); @@ -1049,6 +1049,7 @@ public class TokenManager { String codeHash; String stateHash; + boolean offlineToken = false; private AccessTokenResponse response; @@ -1089,6 +1090,11 @@ public class TokenManager { return this; } + public AccessTokenResponseBuilder offlineToken(boolean offlineToken) { + this.offlineToken = offlineToken; + return this; + } + public AccessTokenResponseBuilder generateAccessToken() { UserModel user = userSession.getUser(); accessToken = createClientAccessToken(session, realm, client, user, userSession, clientSessionCtx); @@ -1218,7 +1224,7 @@ public class TokenManager { } public boolean isOfflineToken() { - return refreshToken != null && TokenUtil.TOKEN_TYPE_OFFLINE.equals(refreshToken.getType()); + return offlineToken; } public AccessTokenResponse build() { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesExtendedEventTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesExtendedEventTest.java index 70ac36ad69..8a8ded6aaf 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesExtendedEventTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesExtendedEventTest.java @@ -81,7 +81,7 @@ import static org.junit.Assert.assertNull; /** * This test class is for testing a newly supported event for client policies. - * + * * @author Takashi Norimatsu */ @EnableFeature(value = Profile.Feature.CLIENT_SECRET_ROTATION) @@ -486,6 +486,58 @@ public class ClientPoliciesExtendedEventTest extends AbstractClientPoliciesTest assertEquals(findUserByUsername(adminClient.realm(REALM_NAME), TEST_USER_NAME).getId(), refreshedRefreshToken.getSubject()); } + @Test + public void testExtendedClientPolicyIntefacesForTokenRefreshResponseWithOffline() throws Exception { + String clientId = generateSuffixedName(CLIENT_NAME); + String clientSecret = "secret"; + String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { + clientRep.setSecret(clientSecret); + clientRep.setStandardFlowEnabled(Boolean.TRUE); + clientRep.setImplicitFlowEnabled(Boolean.TRUE); + clientRep.setPublicClient(Boolean.FALSE); + }); + adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build()); + + oauth.scope(OAuth2Constants.OFFLINE_ACCESS); + oauth.clientId(clientId); + oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD); + + EventRepresentation loginEvent = events.expectLogin().client(clientId).assertEvent(); + String code = new OAuthClient.AuthorizationEndpointResponse(oauth).getCode(); + + OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(code, clientSecret); + assertEquals(200, res.getStatusCode()); + AccessToken token = oauth.verifyToken(res.getAccessToken()); + assertNotNull(token); + assertNotNull(token.getSessionId()); + + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Le Premier Profil") + .addExecutor(SuppressRefreshTokenRotationExecutorFactory.PROVIDER_ID, null) + .toRepresentation() + ).toString(); + updateProfiles(json); + + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE) + .addCondition(ClientRolesConditionFactory.PROVIDER_ID, + createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); + + // delete the non-offline session to force the NPE + adminClient.realm(REALM_NAME).deleteSession(token.getSessionId(), false); + + String refreshTokenString = res.getRefreshToken(); + OAuthClient.AccessTokenResponse accessTokenResponseRefreshed = oauth.doRefreshTokenRequest(refreshTokenString, clientSecret); + assertEquals(200, accessTokenResponseRefreshed.getStatusCode()); + assertNull(accessTokenResponseRefreshed.getRefreshToken()); + } + @Test public void testExtendedClientPolicyIntefacesForServiceAccountTokenRequeponse() throws Exception { String clientId = "service-account-app";