Remove online session for offline access in direct access grants and client credentials
Closes #32650 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
parent
96b6cb4506
commit
13655007a6
6 changed files with 43 additions and 24 deletions
|
@ -84,14 +84,6 @@ describe("Sessions test", () => {
|
|||
it("check offline token", () => {
|
||||
sidebarPage.waitForPageLoad();
|
||||
|
||||
listingPage.searchItem(clientId, false);
|
||||
sidebarPage.waitForPageLoad();
|
||||
// Log out the associated online session of the user
|
||||
commonPage
|
||||
.tableUtils()
|
||||
.checkRowItemExists(username)
|
||||
.selectRowItemAction(username, "Sign out");
|
||||
|
||||
listingPage.searchItem(clientId, false);
|
||||
sidebarPage.waitForPageLoad();
|
||||
|
||||
|
|
|
@ -145,6 +145,10 @@ public class ClientCredentialsGrantType extends OAuth2GrantTypeBase {
|
|||
// Make refresh token generation optional, see KEYCLOAK-9551
|
||||
if (useRefreshToken) {
|
||||
responseBuilder = responseBuilder.generateRefreshToken();
|
||||
if (TokenUtil.TOKEN_TYPE_OFFLINE.equals(responseBuilder.getRefreshToken().getType())) {
|
||||
// for client credentials the online session can be removed
|
||||
session.sessions().removeUserSession(realm, userSession);
|
||||
}
|
||||
} else {
|
||||
responseBuilder.getAccessToken().setSessionId(null);
|
||||
}
|
||||
|
|
|
@ -138,6 +138,10 @@ public class ResourceOwnerPasswordCredentialsGrantType extends OAuth2GrantTypeBa
|
|||
boolean useRefreshToken = clientConfig.isUseRefreshToken();
|
||||
if (useRefreshToken) {
|
||||
responseBuilder.generateRefreshToken();
|
||||
if (TokenUtil.TOKEN_TYPE_OFFLINE.equals(responseBuilder.getRefreshToken().getType())) {
|
||||
// for direct access grants the online session can be removed
|
||||
session.sessions().removeUserSession(realm, userSession);
|
||||
}
|
||||
}
|
||||
|
||||
String scopeParam = clientSessionCtx.getClientSession().getNote(OAuth2Constants.SCOPE);
|
||||
|
|
|
@ -1181,8 +1181,8 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
|
|||
Map<String, ClientRepresentation> apps = applications.stream().collect(Collectors.toMap(x -> x.getClientId(), x -> x));
|
||||
assertThat(apps.keySet(), containsInAnyOrder("offline-client", "offline-client-without-base-url", "always-display-client", "direct-grant"));
|
||||
|
||||
assertClientRep(apps.get("offline-client"), "Offline Client", null, false, true, true, null, offlineClientAppUri);
|
||||
assertClientRep(apps.get("offline-client-without-base-url"), "Offline Client Without Base URL", null, false, true, true, null, null);
|
||||
assertClientRep(apps.get("offline-client"), "Offline Client", null, false, false, true, null, offlineClientAppUri);
|
||||
assertClientRep(apps.get("offline-client-without-base-url"), "Offline Client Without Base URL", null, false, false, true, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1709,9 +1709,9 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
|
|||
assertFalse(applications.isEmpty());
|
||||
|
||||
Map<String, ClientRepresentation> apps = applications.stream().collect(Collectors.toMap(x -> x.getClientId(), x -> x));
|
||||
assertThat(apps.keySet(), containsInAnyOrder("offline-client", "always-display-client", "direct-grant"));
|
||||
assertThat(apps.keySet(), containsInAnyOrder("always-display-client", "direct-grant"));
|
||||
|
||||
assertClientRep(apps.get("offline-client"), "Offline Client", null, false, true, false, null, offlineClientAppUri);
|
||||
assertNull(apps.get("offline-client"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -204,7 +204,7 @@ public class ClientStorageTest extends AbstractTestRealmKeycloakTest {
|
|||
boolean directTested = false;
|
||||
for (Map<String, String> entry : list) {
|
||||
if (entry.get("clientId").equals("hardcoded-client")) {
|
||||
Assert.assertEquals("4", entry.get("active"));
|
||||
Assert.assertEquals("2", entry.get("active"));
|
||||
Assert.assertEquals("2", entry.get("offline"));
|
||||
hardTested = true;
|
||||
} else if (entry.get("clientId").equals("test-app")) {
|
||||
|
@ -212,7 +212,7 @@ public class ClientStorageTest extends AbstractTestRealmKeycloakTest {
|
|||
Assert.assertEquals("0", entry.get("offline"));
|
||||
testAppTested = true;
|
||||
} else if (entry.get("clientId").equals("direct-grant")) {
|
||||
Assert.assertEquals("3", entry.get("active"));
|
||||
Assert.assertEquals("1", entry.get("active"));
|
||||
Assert.assertEquals("2", entry.get("offline"));
|
||||
directTested = true;
|
||||
}
|
||||
|
@ -225,13 +225,13 @@ public class ClientStorageTest extends AbstractTestRealmKeycloakTest {
|
|||
ClientModel hardcoded = realm.getClientByClientId("hardcoded-client");
|
||||
long activeUserSessions = session.sessions().getActiveUserSessions(realm, hardcoded);
|
||||
long offlineSessionsCount = session.sessions().getOfflineSessionsCount(realm, hardcoded);
|
||||
Assert.assertEquals(4, activeUserSessions);
|
||||
Assert.assertEquals(2, activeUserSessions);
|
||||
Assert.assertEquals(2, offlineSessionsCount);
|
||||
|
||||
ClientModel direct = realm.getClientByClientId("direct-grant");
|
||||
activeUserSessions = session.sessions().getActiveUserSessions(realm, direct);
|
||||
offlineSessionsCount = session.sessions().getOfflineSessionsCount(realm, direct);
|
||||
Assert.assertEquals(3, activeUserSessions);
|
||||
Assert.assertEquals(1, activeUserSessions);
|
||||
Assert.assertEquals(2, offlineSessionsCount);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -333,6 +333,15 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
return newRefreshToken;
|
||||
}
|
||||
|
||||
private void checkNumberOfSessions(String userId, String clientId, String sessionId, int onlineSessions, int offlineSessions) {
|
||||
RealmResource realm = adminClient.realm("test");
|
||||
String clientUuid = ApiUtil.findClientByClientId(realm, clientId).toRepresentation().getId();
|
||||
Assert.assertEquals(onlineSessions, realm.users().get(userId).getUserSessions()
|
||||
.stream().filter(s -> sessionId.equals(s.getId())).count());
|
||||
Assert.assertEquals(offlineSessions, realm.users().get(userId).getOfflineSessions(clientUuid)
|
||||
.stream().filter(s -> sessionId.equals(s.getId())).count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void offlineTokenDirectGrantFlow() throws Exception {
|
||||
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||
|
@ -346,7 +355,7 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
events.expectLogin()
|
||||
.client("offline-client")
|
||||
.user(userId)
|
||||
.session(token.getSessionState())
|
||||
.session(token.getSessionId())
|
||||
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.TOKEN_ID, token.getId())
|
||||
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
|
||||
|
@ -360,10 +369,14 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
|
||||
Assert.assertNull(offlineToken.getExp());
|
||||
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), userId);
|
||||
// check only the offline session is created
|
||||
checkNumberOfSessions(userId, "offline-client", offlineToken.getSessionId(), 0, 1);
|
||||
|
||||
// refresh token
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionId(), userId);
|
||||
|
||||
// Assert same token can be refreshed again
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), userId);
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionId(), userId);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -434,7 +447,7 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
events.expectClientLogin()
|
||||
.client("offline-client")
|
||||
.user(serviceAccountUserId)
|
||||
.session(token.getSessionState())
|
||||
.session(token.getSessionId())
|
||||
.detail(Details.TOKEN_ID, token.getId())
|
||||
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
|
||||
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
|
||||
|
@ -444,7 +457,10 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
|
||||
Assert.assertNull(offlineToken.getExp());
|
||||
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), serviceAccountUserId);
|
||||
// check only the offline session is created
|
||||
checkNumberOfSessions(serviceAccountUserId, "offline-client", offlineToken.getSessionId(), 0, 1);
|
||||
|
||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionId(), serviceAccountUserId);
|
||||
|
||||
// Now retrieve another offline token and verify that previous offline token is still valid
|
||||
tokenResponse = oauth.doClientCredentialsGrantAccessTokenRequest("secret1");
|
||||
|
@ -456,16 +472,19 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
events.expectClientLogin()
|
||||
.client("offline-client")
|
||||
.user(serviceAccountUserId)
|
||||
.session(token2.getSessionState())
|
||||
.session(token2.getSessionId())
|
||||
.detail(Details.TOKEN_ID, token2.getId())
|
||||
.detail(Details.REFRESH_TOKEN_ID, offlineToken2.getId())
|
||||
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
|
||||
.detail(Details.USERNAME, ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + "offline-client")
|
||||
.assertEvent();
|
||||
|
||||
// check only the offline session is created
|
||||
checkNumberOfSessions(serviceAccountUserId, "offline-client", offlineToken2.getSessionId(), 0, 1);
|
||||
|
||||
// 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.getSessionId(), serviceAccountUserId);
|
||||
testRefreshWithOfflineToken(token2, offlineToken2, offlineTokenString2, token2.getSessionId(), serviceAccountUserId);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in a new issue