/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.model;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.util.Time;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.models.UserManager;
import org.keycloak.testsuite.rule.KeycloakRule;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserSessionPersisterProviderTest {
@ClassRule
public static KeycloakRule kc = new KeycloakRule();
private KeycloakSession session;
private RealmModel realm;
private UserSessionPersisterProvider persister;
@Before
public void before() {
session = kc.startSession();
realm = session.realms().getRealm("test");
session.users().addUser(realm, "user1").setEmail("user1@localhost");
session.users().addUser(realm, "user2").setEmail("user2@localhost");
persister = session.getProvider(UserSessionPersisterProvider.class);
}
@After
public void after() {
resetSession();
session.sessions().removeUserSessions(realm);
UserModel user1 = session.users().getUserByUsername("user1", realm);
UserModel user2 = session.users().getUserByUsername("user2", realm);
UserManager um = new UserManager(session);
if (user1 != null) {
um.removeUser(realm, user1);
}
if (user2 != null) {
um.removeUser(realm, user2);
}
kc.stopSession(session, true);
}
@Test
public void testPersistenceWithLoad() {
// Create some sessions in infinispan
int started = Time.currentTime();
UserSessionModel[] origSessions = createSessions();
resetSession();
// Persist 3 created userSessions and clientSessions as offline
ClientModel testApp = realm.getClientByClientId("test-app");
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
for (UserSessionModel userSession : userSessions) {
persistUserSession(userSession, true);
}
// Persist 1 online session
UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
persistUserSession(userSession, false);
resetSession();
// Assert online session
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(false, 1, 1, 1);
UserSessionProviderTest.assertSession(loadedSessions.get(0), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
// Assert offline sessions
loadedSessions = loadPersistedSessionsPaginated(true, 2, 2, 3);
UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
}
@Test
public void testUpdateTimestamps() {
// Create some sessions in infinispan
int started = Time.currentTime();
UserSessionModel[] origSessions = createSessions();
resetSession();
// Persist 3 created userSessions and clientSessions as offline
ClientModel testApp = realm.getClientByClientId("test-app");
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
for (UserSessionModel userSession : userSessions) {
persistUserSession(userSession, true);
}
// Persist 1 online session
UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
persistUserSession(userSession, false);
resetSession();
// update timestamps
int newTime = started + 50;
persister.updateAllTimestamps(newTime);
// Assert online session
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(false, 1, 1, 1);
Assert.assertEquals(2, assertTimestampsUpdated(loadedSessions, newTime));
// Assert offline sessions
loadedSessions = loadPersistedSessionsPaginated(true, 2, 2, 3);
Assert.assertEquals(4, assertTimestampsUpdated(loadedSessions, newTime));
}
private int assertTimestampsUpdated(List<UserSessionModel> loadedSessions, int expectedTime) {
int clientSessionsCount = 0;
for (UserSessionModel loadedSession : loadedSessions) {
Assert.assertEquals(expectedTime, loadedSession.getLastSessionRefresh());
for (AuthenticatedClientSessionModel clientSession : loadedSession.getAuthenticatedClientSessions().values()) {
Assert.assertEquals(expectedTime, clientSession.getTimestamp());
clientSessionsCount++;
}
}
return clientSessionsCount;
}
@Test
public void testUpdateAndRemove() {
// Create some sessions in infinispan
int started = Time.currentTime();
UserSessionModel[] origSessions = createSessions();
resetSession();
// Persist 1 offline session
UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[1].getId());
persistUserSession(userSession, true);
resetSession();
// Load offline session
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
UserSessionModel persistedSession = loadedSessions.get(0);
UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
// Update userSession
Time.setOffset(10);
try {
persistedSession.setLastSessionRefresh(Time.currentTime());
persistedSession.setNote("foo", "bar");
persistedSession.setState(UserSessionModel.State.LOGGED_IN);
persister.updateUserSession(persistedSession, true);
// create new clientSession
AuthenticatedClientSessionModel clientSession = createClientSession(realm.getClientByClientId("third-party"), session.sessions().getUserSession(realm, persistedSession.getId()),
"http://redirect", "state", new HashSet<String>(), new HashSet<String>());
persister.createClientSession(clientSession, true);
resetSession();
// Assert session updated
loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
persistedSession = loadedSessions.get(0);
UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started+10, "test-app", "third-party");
Assert.assertEquals("bar", persistedSession.getNote("foo"));
Assert.assertEquals(UserSessionModel.State.LOGGED_IN, persistedSession.getState());
// Remove clientSession
persister.removeClientSession(userSession.getId(), realm.getClientByClientId("third-party").getId(), true);
resetSession();
// Assert clientSession removed
loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
persistedSession = loadedSessions.get(0);
UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started + 10, "test-app");
// Remove userSession
persister.removeUserSession(persistedSession.getId(), true);
resetSession();
// Assert nothing found
loadPersistedSessionsPaginated(true, 10, 0, 0);
} finally {
Time.setOffset(0);
}
}
@Test
public void testOnRealmRemoved() {
RealmModel fooRealm = session.realms().createRealm("foo", "foo");
fooRealm.addClient("foo-app");
session.users().addUser(fooRealm, "user3");
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
resetSession();
// Persist offline session
fooRealm = session.realms().getRealm("foo");
userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
persistUserSession(userSession, true);
resetSession();
// Assert session was persisted
loadPersistedSessionsPaginated(true, 10, 1, 1);
// Remove realm
RealmManager realmMgr = new RealmManager(session);
realmMgr.removeRealm(realmMgr.getRealm("foo"));
resetSession();
// Assert nothing loaded
loadPersistedSessionsPaginated(true, 10, 0, 0);
}
@Test
public void testOnClientRemoved() {
int started = Time.currentTime();
RealmModel fooRealm = session.realms().createRealm("foo", "foo");
fooRealm.addClient("foo-app");
fooRealm.addClient("bar-app");
session.users().addUser(fooRealm, "user3");
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
createClientSession(fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
resetSession();
// Persist offline session
fooRealm = session.realms().getRealm("foo");
userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
persistUserSession(userSession, true);
resetSession();
RealmManager realmMgr = new RealmManager(session);
ClientManager clientMgr = new ClientManager(realmMgr);
fooRealm = realmMgr.getRealm("foo");
// Assert session was persisted with both clientSessions
UserSessionModel persistedSession = loadPersistedSessionsPaginated(true, 10, 1, 1).get(0);
UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "foo-app", "bar-app");
// Remove foo-app client
ClientModel client = fooRealm.getClientByClientId("foo-app");
clientMgr.removeClient(fooRealm, client);
resetSession();
realmMgr = new RealmManager(session);
clientMgr = new ClientManager(realmMgr);
fooRealm = realmMgr.getRealm("foo");
// Assert just one bar-app clientSession persisted now
persistedSession = loadPersistedSessionsPaginated(true, 10, 1, 1).get(0);
UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "bar-app");
// Remove bar-app client
client = fooRealm.getClientByClientId("bar-app");
clientMgr.removeClient(fooRealm, client);
resetSession();
// Assert nothing loaded - userSession was removed as well because it was last userSession
loadPersistedSessionsPaginated(true, 10, 0, 0);
// Cleanup
realmMgr = new RealmManager(session);
realmMgr.removeRealm(realmMgr.getRealm("foo"));
}
@Test
public void testOnUserRemoved() {
// Create some sessions in infinispan
int started = Time.currentTime();
UserSessionModel[] origSessions = createSessions();
resetSession();
// Persist 2 offline sessions of 2 users
UserSessionModel userSession1 = session.sessions().getUserSession(realm, origSessions[1].getId());
UserSessionModel userSession2 = session.sessions().getUserSession(realm, origSessions[2].getId());
persistUserSession(userSession1, true);
persistUserSession(userSession2, true);
resetSession();
// Load offline sessions
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 2);
// Properly delete user and assert his offlineSession removed
UserModel user1 = session.users().getUserByUsername("user1", realm);
new UserManager(session).removeUser(realm, user1);
resetSession();
Assert.assertEquals(1, persister.getUserSessionsCount(true));
loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
UserSessionModel persistedSession = loadedSessions.get(0);
UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
// KEYCLOAK-2431 Assert that userSessionPersister is resistent even to situation, when users are deleted "directly"
UserModel user2 = session.users().getUserByUsername("user2", realm);
session.users().removeUser(realm, user2);
loadedSessions = loadPersistedSessionsPaginated(true, 10, 0, 0);
}
// KEYCLOAK-1999
@Test
public void testNoSessions() {
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
List<UserSessionModel> sessions = persister.loadUserSessions(0, 1, true);
Assert.assertEquals(0, sessions.size());
}
private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
if (userSession != null) clientSession.setUserSession(userSession);
clientSession.setRedirectUri(redirect);
if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
if (roles != null) clientSession.setRoles(roles);
if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
return clientSession;
}
private UserSessionModel[] createSessions() {
UserSessionModel[] sessions = new UserSessionModel[3];
sessions[0] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
Set<String> roles = new HashSet<String>();
roles.add("one");
roles.add("two");
Set<String> protocolMappers = new HashSet<String>();
protocolMappers.add("mapper-one");
protocolMappers.add("mapper-two");
createClientSession(realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state", roles, protocolMappers);
createClientSession(realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
sessions[1] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
createClientSession(realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
sessions[2] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
return sessions;
}
private void persistUserSession(UserSessionModel userSession, boolean offline) {
persister.createUserSession(userSession, offline);
for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
persister.createClientSession(clientSession, offline);
}
}
private void resetSession() {
kc.stopSession(session, true);
session = kc.startSession();
realm = session.realms().getRealm("test");
persister = session.getProvider(UserSessionPersisterProvider.class);
}
public static void assertSessionLoaded(List<UserSessionModel> sessions, String id, UserModel user, String ipAddress, int started, int lastRefresh, String... clients) {
for (UserSessionModel session : sessions) {
if (session.getId().equals(id)) {
UserSessionProviderTest.assertSession(session, user, ipAddress, started, lastRefresh, clients);
return;
}
}
Assert.fail("Session with ID " + id + " not found in the list");
}
private List<UserSessionModel> loadPersistedSessionsPaginated(boolean offline, int sessionsPerPage, int expectedPageCount, int expectedSessionsCount) {
int count = persister.getUserSessionsCount(offline);
int start = 0;
int pageCount = 0;
boolean next = true;
List<UserSessionModel> result = new ArrayList<>();
while (next && start < count) {
List<UserSessionModel> sess = persister.loadUserSessions(start, sessionsPerPage, offline);
if (sess.size() == 0) {
next = false;
} else {
pageCount++;
start += sess.size();
result.addAll(sess);
}
}
Assert.assertEquals(pageCount, expectedPageCount);
Assert.assertEquals(result.size(), expectedSessionsCount);
return result;
}
}