/*
* 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.models.jpa.session;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.session.PersistentAuthenticatedClientSessionAdapter;
import org.keycloak.models.session.PersistentClientSessionModel;
import org.keycloak.models.session.PersistentUserSessionAdapter;
import org.keycloak.models.session.PersistentUserSessionModel;
import org.keycloak.models.session.UserSessionPersisterProvider;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JpaUserSessionPersisterProvider implements UserSessionPersisterProvider {
private final KeycloakSession session;
private final EntityManager em;
public JpaUserSessionPersisterProvider(KeycloakSession session, EntityManager em) {
this.session = session;
this.em = em;
}
@Override
public void createUserSession(UserSessionModel userSession, boolean offline) {
PersistentUserSessionAdapter adapter = new PersistentUserSessionAdapter(userSession);
PersistentUserSessionModel model = adapter.getUpdatedModel();
PersistentUserSessionEntity entity = new PersistentUserSessionEntity();
entity.setUserSessionId(model.getUserSessionId());
entity.setRealmId(adapter.getRealm().getId());
entity.setUserId(adapter.getUser().getId());
String offlineStr = offlineToString(offline);
entity.setOffline(offlineStr);
entity.setLastSessionRefresh(model.getLastSessionRefresh());
entity.setData(model.getData());
em.persist(entity);
em.flush();
}
@Override
public void createClientSession(AuthenticatedClientSessionModel clientSession, boolean offline) {
PersistentAuthenticatedClientSessionAdapter adapter = new PersistentAuthenticatedClientSessionAdapter(clientSession);
PersistentClientSessionModel model = adapter.getUpdatedModel();
PersistentClientSessionEntity entity = new PersistentClientSessionEntity();
entity.setClientId(clientSession.getClient().getId());
entity.setTimestamp(clientSession.getTimestamp());
String offlineStr = offlineToString(offline);
entity.setOffline(offlineStr);
entity.setUserSessionId(clientSession.getUserSession().getId());
entity.setData(model.getData());
em.persist(entity);
em.flush();
}
@Override
public void updateUserSession(UserSessionModel userSession, boolean offline) {
PersistentUserSessionAdapter adapter;
if (userSession instanceof PersistentUserSessionAdapter) {
adapter = (PersistentUserSessionAdapter) userSession;
} else {
adapter = new PersistentUserSessionAdapter(userSession);
}
PersistentUserSessionModel model = adapter.getUpdatedModel();
String offlineStr = offlineToString(offline);
PersistentUserSessionEntity entity = em.find(PersistentUserSessionEntity.class, new PersistentUserSessionEntity.Key(userSession.getId(), offlineStr));
if (entity == null) {
throw new ModelException("UserSession with ID " + userSession.getId() + ", offline: " + offline + " not found");
}
entity.setLastSessionRefresh(model.getLastSessionRefresh());
entity.setData(model.getData());
}
@Override
public void removeUserSession(String userSessionId, boolean offline) {
String offlineStr = offlineToString(offline);
em.createNamedQuery("deleteClientSessionsByUserSession")
.setParameter("userSessionId", userSessionId)
.setParameter("offline", offlineStr)
.executeUpdate();
PersistentUserSessionEntity sessionEntity = em.find(PersistentUserSessionEntity.class, new PersistentUserSessionEntity.Key(userSessionId, offlineStr));
if (sessionEntity != null) {
em.remove(sessionEntity);
em.flush();
}
}
@Override
public void removeClientSession(String userSessionId, String clientUUID, boolean offline) {
String offlineStr = offlineToString(offline);
PersistentClientSessionEntity sessionEntity = em.find(PersistentClientSessionEntity.class, new PersistentClientSessionEntity.Key(userSessionId, clientUUID, offlineStr));
if (sessionEntity != null) {
em.remove(sessionEntity);
// Remove userSession if it was last clientSession
List<PersistentClientSessionEntity> clientSessions = getClientSessionsByUserSession(sessionEntity.getUserSessionId(), offline);
if (clientSessions.size() == 0) {
offlineStr = offlineToString(offline);
PersistentUserSessionEntity userSessionEntity = em.find(PersistentUserSessionEntity.class, new PersistentUserSessionEntity.Key(sessionEntity.getUserSessionId(), offlineStr));
if (userSessionEntity != null) {
em.remove(userSessionEntity);
}
}
em.flush();
}
}
private List<PersistentClientSessionEntity> getClientSessionsByUserSession(String userSessionId, boolean offline) {
String offlineStr = offlineToString(offline);
TypedQuery<PersistentClientSessionEntity> query = em.createNamedQuery("findClientSessionsByUserSession", PersistentClientSessionEntity.class);
query.setParameter("userSessionId", userSessionId);
query.setParameter("offline", offlineStr);
return query.getResultList();
}
@Override
public void onRealmRemoved(RealmModel realm) {
int num = em.createNamedQuery("deleteClientSessionsByRealm").setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteUserSessionsByRealm").setParameter("realmId", realm.getId()).executeUpdate();
}
@Override
public void onClientRemoved(RealmModel realm, ClientModel client) {
int num = em.createNamedQuery("deleteClientSessionsByClient").setParameter("clientId", client.getId()).executeUpdate();
num = em.createNamedQuery("deleteDetachedUserSessions").executeUpdate();
}
@Override
public void onUserRemoved(RealmModel realm, UserModel user) {
onUserRemoved(realm, user.getId());
}
private void onUserRemoved(RealmModel realm, String userId) {
int num = em.createNamedQuery("deleteClientSessionsByUser").setParameter("userId", userId).executeUpdate();
num = em.createNamedQuery("deleteUserSessionsByUser").setParameter("userId", userId).executeUpdate();
}
@Override
public void clearDetachedUserSessions() {
int num = em.createNamedQuery("deleteDetachedClientSessions").executeUpdate();
num = em.createNamedQuery("deleteDetachedUserSessions").executeUpdate();
}
@Override
public void updateAllTimestamps(int time) {
int num = em.createNamedQuery("updateClientSessionsTimestamps").setParameter("timestamp", time).executeUpdate();
num = em.createNamedQuery("updateUserSessionsTimestamps").setParameter("lastSessionRefresh", time).executeUpdate();
}
@Override
public List<UserSessionModel> loadUserSessions(int firstResult, int maxResults, boolean offline) {
String offlineStr = offlineToString(offline);
TypedQuery<PersistentUserSessionEntity> query = em.createNamedQuery("findUserSessions", PersistentUserSessionEntity.class);
query.setParameter("offline", offlineStr);
if (firstResult != -1) {
query.setFirstResult(firstResult);
}
if (maxResults != -1) {
query.setMaxResults(maxResults);
}
List<PersistentUserSessionEntity> results = query.getResultList();
List<UserSessionModel> result = new ArrayList<>();
List<String> userSessionIds = new ArrayList<>();
for (PersistentUserSessionEntity entity : results) {
RealmModel realm = session.realms().getRealm(entity.getRealmId());
UserModel user = session.users().getUserById(entity.getUserId(), realm);
// Case when user was deleted in the meantime
if (user == null) {
onUserRemoved(realm, entity.getUserId());
return loadUserSessions(firstResult, maxResults, offline);
}
result.add(toAdapter(realm, user, entity));
userSessionIds.add(entity.getUserSessionId());
}
if (!userSessionIds.isEmpty()) {
TypedQuery<PersistentClientSessionEntity> query2 = em.createNamedQuery("findClientSessionsByUserSessions", PersistentClientSessionEntity.class);
query2.setParameter("userSessionIds", userSessionIds);
query2.setParameter("offline", offlineStr);
List<PersistentClientSessionEntity> clientSessions = query2.getResultList();
// Assume both userSessions and clientSessions ordered by userSessionId
int j = 0;
for (UserSessionModel ss : result) {
PersistentUserSessionAdapter userSession = (PersistentUserSessionAdapter) ss;
Map<String, AuthenticatedClientSessionModel> currentClientSessions = userSession.getAuthenticatedClientSessions(); // This is empty now and we want to fill it
boolean next = true;
while (next && j < clientSessions.size()) {
PersistentClientSessionEntity clientSession = clientSessions.get(j);
if (clientSession.getUserSessionId().equals(userSession.getId())) {
PersistentAuthenticatedClientSessionAdapter clientSessAdapter = toAdapter(userSession.getRealm(), userSession, clientSession);
currentClientSessions.put(clientSession.getClientId(), clientSessAdapter);
j++;
} else {
next = false;
}
}
}
}
return result;
}
private PersistentUserSessionAdapter toAdapter(RealmModel realm, UserModel user, PersistentUserSessionEntity entity) {
PersistentUserSessionModel model = new PersistentUserSessionModel();
model.setUserSessionId(entity.getUserSessionId());
model.setLastSessionRefresh(entity.getLastSessionRefresh());
model.setData(entity.getData());
Map<String, AuthenticatedClientSessionModel> clientSessions = new HashMap<>();
return new PersistentUserSessionAdapter(model, realm, user, clientSessions);
}
private PersistentAuthenticatedClientSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionAdapter userSession, PersistentClientSessionEntity entity) {
ClientModel client = realm.getClientById(entity.getClientId());
PersistentClientSessionModel model = new PersistentClientSessionModel();
model.setClientId(entity.getClientId());
model.setUserSessionId(userSession.getId());
model.setUserId(userSession.getUser().getId());
model.setTimestamp(entity.getTimestamp());
model.setData(entity.getData());
return new PersistentAuthenticatedClientSessionAdapter(model, realm, client, userSession);
}
@Override
public int getUserSessionsCount(boolean offline) {
String offlineStr = offlineToString(offline);
Query query = em.createNamedQuery("findUserSessionsCount");
query.setParameter("offline", offlineStr);
Number n = (Number) query.getSingleResult();
return n.intValue();
}
@Override
public void close() {
}
private String offlineToString(boolean offline) {
return offline ? "1" : "0";
}
}