use of org.keycloak.models.UserSessionModel in project keycloak by keycloak.
the class InfinispanUserSessionProvider method getUserSessionWithPredicate.
@Override
public UserSessionModel getUserSessionWithPredicate(RealmModel realm, String id, boolean offline, Predicate<UserSessionModel> predicate) {
UserSessionModel userSession = getUserSession(realm, id, offline);
if (userSession == null) {
return null;
}
// We have userSession, which passes predicate. No need for remote lookup.
if (predicate.test(userSession)) {
log.debugf("getUserSessionWithPredicate(%s): found in local cache", id);
return userSession;
}
// Try lookup userSession from remoteCache
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline);
RemoteCache remoteCache = InfinispanUtil.getRemoteCache(cache);
if (remoteCache != null) {
SessionEntityWrapper<UserSessionEntity> remoteSessionEntityWrapper = (SessionEntityWrapper<UserSessionEntity>) remoteCache.get(id);
if (remoteSessionEntityWrapper != null) {
UserSessionEntity remoteSessionEntity = remoteSessionEntityWrapper.getEntity();
log.debugf("getUserSessionWithPredicate(%s): remote cache contains session entity %s", id, remoteSessionEntity);
UserSessionModel remoteSessionAdapter = wrap(realm, remoteSessionEntity, offline);
if (predicate.test(remoteSessionAdapter)) {
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline);
// Remote entity contains our predicate. Update local cache with the remote entity
SessionEntityWrapper<UserSessionEntity> sessionWrapper = remoteSessionEntity.mergeRemoteEntityWithLocalEntity(tx.get(id));
// Replace entity just in ispn cache. Skip remoteStore
cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_STORE, Flag.SKIP_CACHE_LOAD, Flag.IGNORE_RETURN_VALUES).replace(id, sessionWrapper);
tx.reloadEntityInCurrentTransaction(realm, id, sessionWrapper);
// Recursion. We should have it locally now
return getUserSessionWithPredicate(realm, id, offline, predicate);
} else {
log.debugf("getUserSessionWithPredicate(%s): found, but predicate doesn't pass", id);
return null;
}
} else {
log.debugf("getUserSessionWithPredicate(%s): not found", id);
// Session not available on remoteCache. Was already removed there. So removing locally too.
// TODO: Can be optimized to skip calling remoteCache.remove
removeUserSession(realm, userSession);
return null;
}
} else {
log.debugf("getUserSessionWithPredicate(%s): remote cache not available", id);
return null;
}
}
use of org.keycloak.models.UserSessionModel in project keycloak by keycloak.
the class AuthenticationManager method backchannelLogout.
/**
* @param session
* @param realm
* @param userSession
* @param uriInfo
* @param connection
* @param headers
* @param logoutBroker
* @param offlineSession
*
* @return BackchannelLogoutResponse with logout information
*/
public static BackchannelLogoutResponse backchannelLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers, boolean logoutBroker, boolean offlineSession) {
BackchannelLogoutResponse backchannelLogoutResponse = new BackchannelLogoutResponse();
if (userSession == null) {
backchannelLogoutResponse.setLocalLogoutSucceeded(true);
return backchannelLogoutResponse;
}
UserModel user = userSession.getUser();
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
userSession.setState(UserSessionModel.State.LOGGING_OUT);
}
logger.debugv("Logging out: {0} ({1}) offline: {2}", user.getUsername(), userSession.getId(), userSession.isOffline());
boolean expireUserSessionCookieSucceeded = expireUserSessionCookie(session, userSession, realm, uriInfo, headers, connection);
final AuthenticationSessionManager asm = new AuthenticationSessionManager(session);
AuthenticationSessionModel logoutAuthSession = createOrJoinLogoutSession(session, realm, asm, userSession, false);
boolean userSessionOnlyHasLoggedOutClients = false;
try {
backchannelLogoutResponse = backchannelLogoutAll(session, realm, userSession, logoutAuthSession, uriInfo, headers, logoutBroker);
userSessionOnlyHasLoggedOutClients = checkUserSessionOnlyHasLoggedOutClients(realm, userSession, logoutAuthSession);
} finally {
RootAuthenticationSessionModel rootAuthSession = logoutAuthSession.getParentSession();
rootAuthSession.removeAuthenticationSessionByTabId(logoutAuthSession.getTabId());
}
userSession.setState(UserSessionModel.State.LOGGED_OUT);
if (offlineSession) {
new UserSessionManager(session).revokeOfflineUserSession(userSession);
// Check if "online" session still exists and remove it too
String onlineUserSessionId = userSession.getNote(CORRESPONDING_SESSION_ID);
UserSessionModel onlineUserSession = (onlineUserSessionId != null) ? session.sessions().getUserSession(realm, onlineUserSessionId) : session.sessions().getUserSession(realm, userSession.getId());
if (onlineUserSession != null) {
session.sessions().removeUserSession(realm, onlineUserSession);
}
} else {
session.sessions().removeUserSession(realm, userSession);
}
backchannelLogoutResponse.setLocalLogoutSucceeded(expireUserSessionCookieSucceeded && userSessionOnlyHasLoggedOutClients);
return backchannelLogoutResponse;
}
use of org.keycloak.models.UserSessionModel in project keycloak by keycloak.
the class AuthenticationManager method browserLogoutAllClients.
private static Response browserLogoutAllClients(UserSessionModel userSession, KeycloakSession session, RealmModel realm, HttpHeaders headers, UriInfo uriInfo, AuthenticationSessionModel logoutAuthSession) {
Map<Boolean, List<AuthenticatedClientSessionModel>> acss = userSession.getAuthenticatedClientSessions().values().stream().filter(clientSession -> !Objects.equals(AuthenticationSessionModel.Action.LOGGED_OUT.name(), clientSession.getAction()) && !Objects.equals(AuthenticationSessionModel.Action.LOGGING_OUT.name(), clientSession.getAction())).filter(clientSession -> clientSession.getProtocol() != null).collect(Collectors.partitioningBy(clientSession -> clientSession.getClient().isFrontchannelLogout()));
final List<AuthenticatedClientSessionModel> backendLogoutSessions = acss.get(false) == null ? Collections.emptyList() : acss.get(false);
backendLogoutSessions.forEach(acs -> backchannelLogoutClientSession(session, realm, acs, logoutAuthSession, uriInfo, headers));
final List<AuthenticatedClientSessionModel> redirectClients = acss.get(true) == null ? Collections.emptyList() : acss.get(true);
for (AuthenticatedClientSessionModel nextRedirectClient : redirectClients) {
Response response = frontchannelLogoutClientSession(session, realm, nextRedirectClient, logoutAuthSession, uriInfo, headers);
if (response != null) {
return response;
}
}
return null;
}
use of org.keycloak.models.UserSessionModel in project keycloak by keycloak.
the class AuthenticationManager method frontchannelLogoutClientSession.
private static Response frontchannelLogoutClientSession(KeycloakSession session, RealmModel realm, AuthenticatedClientSessionModel clientSession, AuthenticationSessionModel logoutAuthSession, UriInfo uriInfo, HttpHeaders headers) {
UserSessionModel userSession = clientSession.getUserSession();
ClientModel client = clientSession.getClient();
if (!client.isFrontchannelLogout() || AuthenticationSessionModel.Action.LOGGED_OUT.name().equals(clientSession.getAction())) {
return null;
}
final AuthenticationSessionModel.Action logoutState = getClientLogoutAction(logoutAuthSession, client.getId());
if (logoutState == AuthenticationSessionModel.Action.LOGGED_OUT || logoutState == AuthenticationSessionModel.Action.LOGGING_OUT) {
return null;
}
try {
session.clientPolicy().triggerOnEvent(new LogoutRequestContext());
} catch (ClientPolicyException cpe) {
throw new ErrorResponseException(cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus());
}
try {
setClientLogoutAction(logoutAuthSession, client.getId(), AuthenticationSessionModel.Action.LOGGING_OUT);
String authMethod = clientSession.getProtocol();
// must be a keycloak service like account
if (authMethod == null)
return null;
logger.debugv("frontchannel logout to: {0}", client.getClientId());
LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
protocol.setRealm(realm).setHttpHeaders(headers).setUriInfo(uriInfo);
Response response = protocol.frontchannelLogout(userSession, clientSession);
if (response != null) {
logger.debug("returning frontchannel logout request to client");
if (!AuthenticationSessionModel.Action.LOGGING_OUT.name().equals(clientSession.getAction())) {
setClientLogoutAction(logoutAuthSession, client.getId(), AuthenticationSessionModel.Action.LOGGED_OUT);
}
return response;
}
} catch (Exception e) {
ServicesLogger.LOGGER.failedToLogoutClient(e);
}
return null;
}
use of org.keycloak.models.UserSessionModel in project keycloak by keycloak.
the class LastSessionRefreshCrossDCTest method testLastSessionRefreshUpdate.
@Test
public void testLastSessionRefreshUpdate(@JmxInfinispanCacheStatistics(dc = DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName = InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics sessionCacheDc1Stats, @JmxInfinispanCacheStatistics(dc = DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName = InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics sessionCacheDc2Stats, @JmxInfinispanCacheStatistics(dc = DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName = InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME) InfinispanStatistics clientSessionCacheDc1Stats, @JmxInfinispanCacheStatistics(dc = DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName = InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME) InfinispanStatistics clientSessionCacheDc2Stats) {
// Set the infinispan testTimeService on all started auth servers
setInfinispanTestTimeServiceOnAllStartedAuthServers();
try {
// Ensure to remove all current sessions and offline sessions
setTimeOffset(10000000);
getTestingClientForStartedNodeInDc(0).testing("test").removeExpired("test");
getTestingClientForStartedNodeInDc(1).testing("test").removeExpired("test");
setTimeOffset(0);
sessionCacheDc1Stats.reset();
sessionCacheDc2Stats.reset();
clientSessionCacheDc1Stats.reset();
clientSessionCacheDc2Stats.reset();
// Disable DC2 on loadbalancer
disableDcOnLoadBalancer(DC.SECOND);
// Get statistics
AtomicLong sessionStoresDc1 = new AtomicLong(getStores(sessionCacheDc1Stats));
AtomicLong sessionStoresDc2 = new AtomicLong(getStores(sessionCacheDc2Stats));
AtomicLong clientSessionStoresDc1 = new AtomicLong(getStores(clientSessionCacheDc1Stats));
AtomicLong clientSessionStoresDc2 = new AtomicLong(getStores(clientSessionCacheDc2Stats));
AtomicInteger lsrDc1 = new AtomicInteger(-1);
AtomicInteger lsrDc2 = new AtomicInteger(-1);
// Login
OAuthClient.AuthorizationEndpointResponse response1 = oauth.doLogin("test-user@localhost", "password");
String code = response1.getCode();
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
Assert.assertNotNull(tokenResponse.getAccessToken());
String sessionId = oauth.verifyToken(tokenResponse.getAccessToken()).getSessionState();
String refreshToken1 = tokenResponse.getRefreshToken();
// Assert statistics - sessions created on both DCs and created on remoteCaches too
assertStatistics("After session created", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats, sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2, lsrDc1, lsrDc2, true, true, true, false);
// Set time offset
setTimeOffset(100);
// refresh token on DC1
tokenResponse = oauth.doRefreshTokenRequest(refreshToken1, "password");
String refreshToken2 = tokenResponse.getRefreshToken();
Assert.assertNotNull(refreshToken2);
// Assert statistics - sessions updated on both DC1 and DC2. RemoteCaches not updated
assertStatistics("After refresh at time 100", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats, sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2, lsrDc1, lsrDc2, true, true, false, false);
// Set time offset
setTimeOffset(110);
// refresh token on DC1
tokenResponse = oauth.doRefreshTokenRequest(refreshToken1, "password");
String refreshToken3 = tokenResponse.getRefreshToken();
Assert.assertNotNull(refreshToken3);
// Assert statistics - sessions updated just on DC1.
// Update of DC2 is postponed (It's just 10 seconds since last message). RemoteCaches not updated
assertStatistics("After refresh at time 110", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats, sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2, lsrDc1, lsrDc2, true, false, false, false);
// 31 minutes after "100". Session should be still valid and not yet expired (RefreshToken will be invalid due the expiration on the JWT itself. Hence not testing refresh here)
setTimeOffset(1960);
boolean sessionValid = getTestingClientForStartedNodeInDc(1).server("test").fetch((KeycloakSession session) -> {
RealmModel realm = session.realms().getRealmByName("test");
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
return AuthenticationManager.isSessionValid(realm, userSession);
}, Boolean.class);
Assert.assertTrue(sessionValid);
getTestingClientForStartedNodeInDc(1).testing("test").removeExpired("test");
// Assert statistics - nothing was updated. No refresh happened and nothing was cleared during "removeExpired"
assertStatistics("After checking valid at time 1960", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats, sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2, lsrDc1, lsrDc2, false, false, false, false);
// 35 minutes after "100". Session not valid and will be expired by the cleaner
setTimeOffset(2200);
sessionValid = getTestingClientForStartedNodeInDc(1).server("test").fetch((KeycloakSession session) -> {
RealmModel realm = session.realms().getRealmByName("test");
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
return AuthenticationManager.isSessionValid(realm, userSession);
}, Boolean.class);
Assert.assertFalse(sessionValid);
// 2000 seconds after the previous. This should ensure that session would be expired from the cache due the invalid maxIdle.
// Previous read at time 2200 "refreshed" the maxIdle in the infinispan cache. This shouldn't happen in reality as an attempt to call refreshToken request on invalid session does backchannelLogout
setTimeOffset(4200);
getTestingClientForStartedNodeInDc(1).testing("test").removeExpired("test");
// Session should be removed on both DCs
try {
getTestingClientForStartedNodeInDc(0).testing("test").getLastSessionRefresh("test", sessionId, false);
Assert.fail("It wasn't expected to find the session " + sessionId);
} catch (NotFoundException nfe) {
// Expected
}
try {
getTestingClientForStartedNodeInDc(1).testing("test").getLastSessionRefresh("test", sessionId, false);
Assert.fail("It wasn't expected to find the session " + sessionId);
} catch (NotFoundException nfe) {
// Expected
}
} finally {
// Revert time service
revertInfinispanTestTimeServiceOnAllStartedAuthServers();
}
}
Aggregations