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", () => {
|
it("check offline token", () => {
|
||||||
sidebarPage.waitForPageLoad();
|
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);
|
listingPage.searchItem(clientId, false);
|
||||||
sidebarPage.waitForPageLoad();
|
sidebarPage.waitForPageLoad();
|
||||||
|
|
||||||
|
|
|
@ -145,6 +145,10 @@ public class ClientCredentialsGrantType extends OAuth2GrantTypeBase {
|
||||||
// Make refresh token generation optional, see KEYCLOAK-9551
|
// Make refresh token generation optional, see KEYCLOAK-9551
|
||||||
if (useRefreshToken) {
|
if (useRefreshToken) {
|
||||||
responseBuilder = responseBuilder.generateRefreshToken();
|
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 {
|
} else {
|
||||||
responseBuilder.getAccessToken().setSessionId(null);
|
responseBuilder.getAccessToken().setSessionId(null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,6 +138,10 @@ public class ResourceOwnerPasswordCredentialsGrantType extends OAuth2GrantTypeBa
|
||||||
boolean useRefreshToken = clientConfig.isUseRefreshToken();
|
boolean useRefreshToken = clientConfig.isUseRefreshToken();
|
||||||
if (useRefreshToken) {
|
if (useRefreshToken) {
|
||||||
responseBuilder.generateRefreshToken();
|
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);
|
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));
|
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"));
|
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"), "Offline Client", null, false, false, 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-without-base-url"), "Offline Client Without Base URL", null, false, false, true, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1709,9 +1709,9 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
|
||||||
assertFalse(applications.isEmpty());
|
assertFalse(applications.isEmpty());
|
||||||
|
|
||||||
Map<String, ClientRepresentation> apps = applications.stream().collect(Collectors.toMap(x -> x.getClientId(), x -> x));
|
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
|
@Test
|
||||||
|
|
|
@ -204,7 +204,7 @@ public class ClientStorageTest extends AbstractTestRealmKeycloakTest {
|
||||||
boolean directTested = false;
|
boolean directTested = false;
|
||||||
for (Map<String, String> entry : list) {
|
for (Map<String, String> entry : list) {
|
||||||
if (entry.get("clientId").equals("hardcoded-client")) {
|
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"));
|
Assert.assertEquals("2", entry.get("offline"));
|
||||||
hardTested = true;
|
hardTested = true;
|
||||||
} else if (entry.get("clientId").equals("test-app")) {
|
} else if (entry.get("clientId").equals("test-app")) {
|
||||||
|
@ -212,7 +212,7 @@ public class ClientStorageTest extends AbstractTestRealmKeycloakTest {
|
||||||
Assert.assertEquals("0", entry.get("offline"));
|
Assert.assertEquals("0", entry.get("offline"));
|
||||||
testAppTested = true;
|
testAppTested = true;
|
||||||
} else if (entry.get("clientId").equals("direct-grant")) {
|
} 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"));
|
Assert.assertEquals("2", entry.get("offline"));
|
||||||
directTested = true;
|
directTested = true;
|
||||||
}
|
}
|
||||||
|
@ -225,13 +225,13 @@ public class ClientStorageTest extends AbstractTestRealmKeycloakTest {
|
||||||
ClientModel hardcoded = realm.getClientByClientId("hardcoded-client");
|
ClientModel hardcoded = realm.getClientByClientId("hardcoded-client");
|
||||||
long activeUserSessions = session.sessions().getActiveUserSessions(realm, hardcoded);
|
long activeUserSessions = session.sessions().getActiveUserSessions(realm, hardcoded);
|
||||||
long offlineSessionsCount = session.sessions().getOfflineSessionsCount(realm, hardcoded);
|
long offlineSessionsCount = session.sessions().getOfflineSessionsCount(realm, hardcoded);
|
||||||
Assert.assertEquals(4, activeUserSessions);
|
Assert.assertEquals(2, activeUserSessions);
|
||||||
Assert.assertEquals(2, offlineSessionsCount);
|
Assert.assertEquals(2, offlineSessionsCount);
|
||||||
|
|
||||||
ClientModel direct = realm.getClientByClientId("direct-grant");
|
ClientModel direct = realm.getClientByClientId("direct-grant");
|
||||||
activeUserSessions = session.sessions().getActiveUserSessions(realm, direct);
|
activeUserSessions = session.sessions().getActiveUserSessions(realm, direct);
|
||||||
offlineSessionsCount = session.sessions().getOfflineSessionsCount(realm, direct);
|
offlineSessionsCount = session.sessions().getOfflineSessionsCount(realm, direct);
|
||||||
Assert.assertEquals(3, activeUserSessions);
|
Assert.assertEquals(1, activeUserSessions);
|
||||||
Assert.assertEquals(2, offlineSessionsCount);
|
Assert.assertEquals(2, offlineSessionsCount);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -333,6 +333,15 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
||||||
return newRefreshToken;
|
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
|
@Test
|
||||||
public void offlineTokenDirectGrantFlow() throws Exception {
|
public void offlineTokenDirectGrantFlow() throws Exception {
|
||||||
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||||
|
@ -346,7 +355,7 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
||||||
events.expectLogin()
|
events.expectLogin()
|
||||||
.client("offline-client")
|
.client("offline-client")
|
||||||
.user(userId)
|
.user(userId)
|
||||||
.session(token.getSessionState())
|
.session(token.getSessionId())
|
||||||
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||||
.detail(Details.TOKEN_ID, token.getId())
|
.detail(Details.TOKEN_ID, token.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_ID, offlineToken.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.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
|
||||||
Assert.assertNull(offlineToken.getExp());
|
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
|
// Assert same token can be refreshed again
|
||||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), userId);
|
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionId(), userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -434,7 +447,7 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
||||||
events.expectClientLogin()
|
events.expectClientLogin()
|
||||||
.client("offline-client")
|
.client("offline-client")
|
||||||
.user(serviceAccountUserId)
|
.user(serviceAccountUserId)
|
||||||
.session(token.getSessionState())
|
.session(token.getSessionId())
|
||||||
.detail(Details.TOKEN_ID, token.getId())
|
.detail(Details.TOKEN_ID, token.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
|
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
|
.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.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
|
||||||
Assert.assertNull(offlineToken.getExp());
|
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
|
// Now retrieve another offline token and verify that previous offline token is still valid
|
||||||
tokenResponse = oauth.doClientCredentialsGrantAccessTokenRequest("secret1");
|
tokenResponse = oauth.doClientCredentialsGrantAccessTokenRequest("secret1");
|
||||||
|
@ -456,16 +472,19 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
||||||
events.expectClientLogin()
|
events.expectClientLogin()
|
||||||
.client("offline-client")
|
.client("offline-client")
|
||||||
.user(serviceAccountUserId)
|
.user(serviceAccountUserId)
|
||||||
.session(token2.getSessionState())
|
.session(token2.getSessionId())
|
||||||
.detail(Details.TOKEN_ID, token2.getId())
|
.detail(Details.TOKEN_ID, token2.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_ID, offlineToken2.getId())
|
.detail(Details.REFRESH_TOKEN_ID, offlineToken2.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
|
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
|
||||||
.detail(Details.USERNAME, ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + "offline-client")
|
.detail(Details.USERNAME, ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + "offline-client")
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
|
|
||||||
|
// check only the offline session is created
|
||||||
|
checkNumberOfSessions(serviceAccountUserId, "offline-client", offlineToken2.getSessionId(), 0, 1);
|
||||||
|
|
||||||
// Refresh with both offline tokens is fine
|
// Refresh with both offline tokens is fine
|
||||||
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionState(), serviceAccountUserId);
|
testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, token.getSessionId(), serviceAccountUserId);
|
||||||
testRefreshWithOfflineToken(token2, offlineToken2, offlineTokenString2, token2.getSessionState(), serviceAccountUserId);
|
testRefreshWithOfflineToken(token2, offlineToken2, offlineTokenString2, token2.getSessionId(), serviceAccountUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in a new issue