KEYCLOAK-4201 Offline tokens become useless when accessing admin REST API

This commit is contained in:
mposolda 2017-01-12 22:40:11 +01:00
parent c4cce147e2
commit 93157e49d5
5 changed files with 66 additions and 7 deletions

View file

@ -78,8 +78,8 @@ public class Keycloak {
return new Keycloak(serverUrl, realm, username, password, clientId, null, PASSWORD, null, null); return new Keycloak(serverUrl, realm, username, password, clientId, null, PASSWORD, null, null);
} }
public static Keycloak getInstance(String serverUrl, String realm, String clientId, String authtoken) { public static Keycloak getInstance(String serverUrl, String realm, String clientId, String authToken) {
return new Keycloak(serverUrl, realm, null, null, clientId, null, PASSWORD, null, null); return new Keycloak(serverUrl, realm, null, null, clientId, null, PASSWORD, null, authToken);
} }
public RealmsResource realms() { public RealmsResource realms() {

View file

@ -39,7 +39,7 @@ public interface RoleMappingResource {
@Path("realm") @Path("realm")
public RoleScopeResource realmLevel(); public RoleScopeResource realmLevel();
@Path("clients/{clientId}") @Path("clients/{clientUUID}")
public RoleScopeResource clientLevel(@PathParam("clientId") String clientId); public RoleScopeResource clientLevel(@PathParam("clientUUID") String clientUUID);
} }

View file

@ -61,7 +61,7 @@ public class AppAuthManager extends AuthenticationManager {
public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
String tokenString = extractAuthorizationHeaderToken(headers); String tokenString = extractAuthorizationHeaderToken(headers);
if (tokenString == null) return null; 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; return authResult;
} }

View file

@ -108,6 +108,15 @@ public class AuthenticationManager {
return userSession.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout() > currentTime && max > currentTime; 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) { public static void expireUserSessionCookie(KeycloakSession session, UserSessionModel userSession, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, ClientConnection connection) {
try { try {
// check to see if any identity cookie is set with the same session and expire it if necessary // 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(); 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) { if (authResult == null) {
expireIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection()); expireIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection());
return null; 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, 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 { try {
TokenVerifier verifier = TokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(checkActive).checkTokenType(checkTokenType); TokenVerifier verifier = TokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(checkActive).checkTokenType(checkTokenType);
String kid = verifier.getHeader().getKeyId(); String kid = verifier.getHeader().getKeyId();
@ -729,6 +738,14 @@ public class AuthenticationManager {
UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState()); UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
if (!isSessionValid(realm, userSession)) { 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); if (userSession != null) backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true);
logger.debug("User session not active"); logger.debug("User session not active");
return null; return null;

View file

@ -23,12 +23,15 @@ import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants; 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.RealmResource;
import org.keycloak.admin.client.resource.RoleResource; import org.keycloak.admin.client.resource.RoleResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.constants.ServiceAccountConstants; import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
@ -40,6 +43,9 @@ import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AssertEvents; 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.pages.LoginPage;
import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.ClientManager; import org.keycloak.testsuite.util.ClientManager;
@ -432,4 +438,40 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
testUser.roles().realmLevel().add(Collections.singletonList(offlineAccess)); 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);
}
} }