/* * 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.Rule; 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.services.managers.UserSessionManager; import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.LoggingRule; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> */ public class UserSessionProviderOfflineTest { @ClassRule public static KeycloakRule kc = new KeycloakRule(); @Rule public LoggingRule loggingRule = new LoggingRule(this); private KeycloakSession session; private RealmModel realm; private UserSessionManager sessionManager; 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"); sessionManager = new UserSessionManager(session); 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); um.removeUser(realm, user1); um.removeUser(realm, user2); kc.stopSession(session, true); } @Test public void testOfflineSessionsCrud() { // Create some online sessions in infinispan int started = Time.currentTime(); UserSessionModel[] origSessions = createSessions(); resetSession(); // Key is userSession ID, values are client UUIDS Map<String, Set<String>> offlineSessions = new HashMap<>(); // 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) { offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(userSession)); } resetSession(); // Assert all previously saved offline sessions found for (Map.Entry<String, Set<String>> entry : offlineSessions.entrySet()) { UserSessionModel offlineSession = sessionManager.findOfflineUserSession(realm, entry.getKey()); Assert.assertNotNull(offlineSession); Assert.assertEquals(offlineSession.getAuthenticatedClientSessions().keySet(), entry.getValue()); } // Find clients with offline token UserModel user1 = session.users().getUserByUsername("user1", realm); Set<ClientModel> clients = sessionManager.findClientsWithOfflineToken(realm, user1); Assert.assertEquals(clients.size(), 2); for (ClientModel client : clients) { Assert.assertTrue(client.getClientId().equals("test-app") || client.getClientId().equals("third-party")); } UserModel user2 = session.users().getUserByUsername("user2", realm); clients = sessionManager.findClientsWithOfflineToken(realm, user2); Assert.assertEquals(clients.size(), 1); Assert.assertTrue(clients.iterator().next().getClientId().equals("test-app")); // Test count testApp = realm.getClientByClientId("test-app"); ClientModel thirdparty = realm.getClientByClientId("third-party"); Assert.assertEquals(3, session.sessions().getOfflineSessionsCount(realm, testApp)); Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty)); // Revoke "test-app" for user1 sessionManager.revokeOfflineToken(user1, testApp); resetSession(); // Assert userSession revoked testApp = realm.getClientByClientId("test-app"); thirdparty = realm.getClientByClientId("third-party"); Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, testApp)); Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty)); List<UserSessionModel> testAppSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10); List<UserSessionModel> thirdpartySessions = session.sessions().getOfflineUserSessions(realm, thirdparty, 0, 10); Assert.assertEquals(1, testAppSessions.size()); Assert.assertEquals("127.0.0.3", testAppSessions.get(0).getIpAddress()); Assert.assertEquals("user2", testAppSessions.get(0).getUser().getUsername()); Assert.assertEquals(1, thirdpartySessions.size()); Assert.assertEquals("127.0.0.1", thirdpartySessions.get(0).getIpAddress()); Assert.assertEquals("user1", thirdpartySessions.get(0).getUser().getUsername()); user1 = session.users().getUserByUsername("user1", realm); user2 = session.users().getUserByUsername("user2", realm); clients = sessionManager.findClientsWithOfflineToken(realm, user1); Assert.assertEquals(1, clients.size()); Assert.assertEquals("third-party", clients.iterator().next().getClientId()); clients = sessionManager.findClientsWithOfflineToken(realm, user2); Assert.assertEquals(1, clients.size()); Assert.assertEquals("test-app", clients.iterator().next().getClientId()); } @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); AuthenticatedClientSessionModel clientSession = 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()); createOfflineSessionIncludeClientSessions(userSession); resetSession(); UserSessionModel offlineUserSession = sessionManager.findOfflineUserSession(fooRealm, userSession.getId()); Assert.assertEquals(offlineUserSession.getAuthenticatedClientSessions().size(), 1); AuthenticatedClientSessionModel offlineClientSession = offlineUserSession.getAuthenticatedClientSessions().values().iterator().next(); Assert.assertEquals("foo-app", offlineClientSession.getClient().getClientId()); Assert.assertEquals("user3", offlineClientSession.getUserSession().getUser().getUsername()); // Remove realm RealmManager realmMgr = new RealmManager(session); realmMgr.removeRealm(realmMgr.getRealm("foo")); resetSession(); fooRealm = session.realms().createRealm("foo", "foo"); fooRealm.addClient("foo-app"); session.users().addUser(fooRealm, "user3"); resetSession(); // Assert nothing loaded fooRealm = session.realms().getRealm("foo"); Assert.assertNull(sessionManager.findOfflineUserSession(fooRealm, userSession.getId())); Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(fooRealm, fooRealm.getClientByClientId("foo-app"))); // Cleanup realmMgr = new RealmManager(session); realmMgr.removeRealm(realmMgr.getRealm("foo")); } @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(); // Create offline session fooRealm = session.realms().getRealm("foo"); userSession = session.sessions().getUserSession(fooRealm, userSession.getId()); createOfflineSessionIncludeClientSessions(userSession); resetSession(); RealmManager realmMgr = new RealmManager(session); ClientManager clientMgr = new ClientManager(realmMgr); fooRealm = realmMgr.getRealm("foo"); // Assert session was persisted with both clientSessions UserSessionModel offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId()); UserSessionProviderTest.assertSession(offlineSession, 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 offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId()); Assert.assertEquals(1, offlineSession.getAuthenticatedClientSessions().size()); Assert.assertEquals("bar-app", offlineSession.getAuthenticatedClientSessions().values().iterator().next().getClient().getClientId()); // 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 realmMgr = new RealmManager(session); fooRealm = realmMgr.getRealm("foo"); offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId()); Assert.assertEquals(0, offlineSession.getAuthenticatedClientSessions().size()); // Cleanup realmMgr = new RealmManager(session); realmMgr.removeRealm(realmMgr.getRealm("foo")); } @Test public void testOnUserRemoved() { int started = Time.currentTime(); 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); AuthenticatedClientSessionModel clientSession = createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>()); resetSession(); // Create offline session fooRealm = session.realms().getRealm("foo"); userSession = session.sessions().getUserSession(fooRealm, userSession.getId()); createOfflineSessionIncludeClientSessions(userSession); resetSession(); RealmManager realmMgr = new RealmManager(session); fooRealm = realmMgr.getRealm("foo"); UserModel user3 = session.users().getUserByUsername("user3", fooRealm); // Assert session was persisted with both clientSessions UserSessionModel offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId()); UserSessionProviderTest.assertSession(offlineSession, user3, "127.0.0.1", started, started, "foo-app"); // Remove user3 new UserManager(session).removeUser(fooRealm, user3); resetSession(); // Assert userSession removed as well Assert.assertNull(session.sessions().getOfflineUserSession(fooRealm, userSession.getId())); // Cleanup realmMgr = new RealmManager(session); realmMgr.removeRealm(realmMgr.getRealm("foo")); } @Test public void testExpired() { // Create some online sessions in infinispan int started = Time.currentTime(); UserSessionModel[] origSessions = createSessions(); resetSession(); // Key is userSessionId, value is set of client UUIDS Map<String, Set<String>> offlineSessions = new HashMap<>(); // 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) { offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(userSession)); } resetSession(); // Assert all previously saved offline sessions found for (Map.Entry<String, Set<String>> entry : offlineSessions.entrySet()) { UserSessionModel foundSession = sessionManager.findOfflineUserSession(realm, entry.getKey()); Assert.assertEquals(foundSession.getAuthenticatedClientSessions().keySet(), entry.getValue()); } UserSessionModel session0 = session.sessions().getOfflineUserSession(realm, origSessions[0].getId()); Assert.assertNotNull(session0); // sessions are in persister too Assert.assertEquals(3, persister.getUserSessionsCount(true)); // Set lastSessionRefresh to session[0] to 0 session0.setLastSessionRefresh(0); resetSession(); session.sessions().removeExpired(realm); resetSession(); // assert session0 not found now Assert.assertNull(session.sessions().getOfflineUserSession(realm, origSessions[0].getId())); Assert.assertEquals(2, persister.getUserSessionsCount(true)); // Expire everything and assert nothing found Time.setOffset(3000000); try { session.sessions().removeExpired(realm); resetSession(); for (String userSessionId : offlineSessions.keySet()) { Assert.assertNull(sessionManager.findOfflineUserSession(realm, userSessionId)); } Assert.assertEquals(0, persister.getUserSessionsCount(true)); } finally { Time.setOffset(0); } } private Set<String> createOfflineSessionIncludeClientSessions(UserSessionModel userSession) { Set<String> offlineSessions = new HashSet<>(); for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) { sessionManager.createOrUpdateOfflineSession(clientSession, userSession); offlineSessions.add(clientSession.getClient().getId()); } return offlineSessions; } private void resetSession() { kc.stopSession(session, true); session = kc.startSession(); realm = session.realms().getRealm("test"); sessionManager = new UserSessionManager(session); persister = session.getProvider(UserSessionPersisterProvider.class); } private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) { AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(client.getRealm(), 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; } }