diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java index 8c4235cbf5..e445d57697 100755 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java @@ -78,8 +78,8 @@ public class Keycloak { return new Keycloak(serverUrl, realm, username, password, clientId, null, PASSWORD, null, null); } - public static Keycloak getInstance(String serverUrl, String realm, String clientId, String authtoken) { - return new Keycloak(serverUrl, realm, null, null, clientId, null, PASSWORD, null, null); + public static Keycloak getInstance(String serverUrl, String realm, String clientId, String authToken) { + return new Keycloak(serverUrl, realm, null, null, clientId, null, PASSWORD, null, authToken); } public RealmsResource realms() { diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleMappingResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleMappingResource.java index 7621eace3b..bed37e6f62 100755 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleMappingResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleMappingResource.java @@ -39,7 +39,7 @@ public interface RoleMappingResource { @Path("realm") public RoleScopeResource realmLevel(); - @Path("clients/{clientId}") - public RoleScopeResource clientLevel(@PathParam("clientId") String clientId); + @Path("clients/{clientUUID}") + public RoleScopeResource clientLevel(@PathParam("clientUUID") String clientUUID); } diff --git a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java index d9a57cf765..50972f4931 100755 --- a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java @@ -61,7 +61,7 @@ public class AppAuthManager extends AuthenticationManager { public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { String tokenString = extractAuthorizationHeaderToken(headers); if (tokenString == null) return null; - AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, true, tokenString, headers); + AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, true, false, tokenString, headers); return authResult; } diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index 1392328361..5d467da5f6 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -108,6 +108,15 @@ public class AuthenticationManager { return userSession.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout() > currentTime && max > currentTime; } + public static boolean isOfflineSessionValid(RealmModel realm, UserSessionModel userSession) { + if (userSession == null) { + logger.debug("No offline user session"); + return false; + } + int currentTime = Time.currentTime(); + return userSession.getLastSessionRefresh() + realm.getOfflineSessionIdleTimeout() > currentTime; + } + public static void expireUserSessionCookie(KeycloakSession session, UserSessionModel userSession, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, ClientConnection connection) { try { // check to see if any identity cookie is set with the same session and expire it if necessary @@ -390,7 +399,7 @@ public class AuthenticationManager { } String tokenString = cookie.getValue(); - AuthResult authResult = verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(), checkActive, false, tokenString, session.getContext().getRequestHeaders()); + AuthResult authResult = verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(), checkActive, false, true, tokenString, session.getContext().getRequestHeaders()); if (authResult == null) { expireIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection()); return null; @@ -691,7 +700,7 @@ public class AuthenticationManager { protected static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType, - String tokenString, HttpHeaders headers) { + boolean isCookie, String tokenString, HttpHeaders headers) { try { TokenVerifier verifier = TokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(checkActive).checkTokenType(checkTokenType); String kid = verifier.getHeader().getKeyId(); @@ -729,6 +738,14 @@ public class AuthenticationManager { UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState()); if (!isSessionValid(realm, userSession)) { + // Check if accessToken was for the offline session. + if (!isCookie) { + UserSessionModel offlineUserSession = session.sessions().getUserSession(realm, token.getSessionState()); + if (isOfflineSessionValid(realm, offlineUserSession)) { + return new AuthResult(user, offlineUserSession, token); + } + } + if (userSession != null) backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true); logger.debug("User session not active"); return null; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java index 223a3f65ef..f4cf19ca2a 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java @@ -23,12 +23,15 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RoleResource; import org.keycloak.admin.client.resource.UserResource; import org.keycloak.common.constants.ServiceAccountConstants; import org.keycloak.events.Details; import org.keycloak.events.Errors; +import org.keycloak.models.AdminRoles; import org.keycloak.models.Constants; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.AccessToken; @@ -40,6 +43,9 @@ import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; +import org.keycloak.testsuite.auth.page.AuthRealm; import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.ClientManager; @@ -432,4 +438,40 @@ public class OfflineTokenTest extends AbstractKeycloakTest { testUser.roles().realmLevel().add(Collections.singletonList(offlineAccess)); } + + /** + * KEYCLOAK-4201 + * + * @throws Exception + */ + @Test + public void offlineTokenAdminRESTAccess() throws Exception { + // Grant "view-realm" role to user + RealmResource appRealm = adminClient.realm("test"); + ClientResource realmMgmt = ApiUtil.findClientByClientId(appRealm, Constants.REALM_MANAGEMENT_CLIENT_ID); + String realmMgmtUuid = realmMgmt.toRepresentation().getId(); + RoleRepresentation roleRep = realmMgmt.roles().get(AdminRoles.VIEW_REALM).toRepresentation(); + + UserResource testUser = findUserByUsernameId(appRealm, "test-user@localhost"); + testUser.roles().clientLevel(realmMgmtUuid).add(Collections.singletonList(roleRep)); + + // Login with offline token now + oauth.scope(OAuth2Constants.OFFLINE_ACCESS); + oauth.clientId("offline-client"); + OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("secret1", "test-user@localhost", "password"); + + events.clear(); + + // Set the time offset, so that "normal" userSession expires + setTimeOffset(86400); + + // Refresh with the offline token + tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "secret1"); + + // Use accessToken to admin REST request + Keycloak offlineTokenAdmin = Keycloak.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth", + AuthRealm.MASTER, Constants.ADMIN_CLI_CLIENT_ID, tokenResponse.getAccessToken()); + RealmRepresentation testRealm = offlineTokenAdmin.realm("test").toRepresentation(); + Assert.assertNotNull(testRealm); + } }