Details
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 8c4235c..e445d57 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 7621eac..bed37e6 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 d9a57cf..50972f4 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 1392328..5d467da 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 223a3f6..f4cf19c 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);
+ }
}