/*
* Copyright (c) JForum Team. All rights reserved.
*
* The software in this package is published under the terms of the LGPL
* license a copy of which has been included with this distribution in the
* license.txt file.
*
* The JForum Project
* http://www.jforum.net
*/
package net.jforum.core;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.Cookie;
import net.jforum.core.exceptions.ForumException;
import net.jforum.entities.Session;
import net.jforum.entities.User;
import net.jforum.entities.UserSession;
import net.jforum.repository.SessionRepository;
import net.jforum.repository.UserRepository;
import net.jforum.security.RoleManager;
import net.jforum.sso.SSO;
import net.jforum.sso.SSOUtils;
import net.jforum.util.ConfigKeys;
import net.jforum.util.JForumConfig;
import net.jforum.util.MD5;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import br.com.caelum.vraptor.ioc.Component;
/**
* Manages all user sessions
* @author Rafael Steil
*/
@Component
public class SessionManager {
private static final Logger logger = Logger.getLogger(SessionManager.class);
private static Map<String, UserSession> loggedSessions = new HashMap<String, UserSession>();
private static Map<String, UserSession> anonymousSessions = new HashMap<String, UserSession>();
private UserRepository userRepository;
private SessionRepository sessionRepository;
private JForumConfig config;
private int moderatorsOnline;
public SessionManager(JForumConfig config, SessionRepository sessionRepository, UserRepository userRepository) {
this.config = config;
this.userRepository = userRepository;
this.sessionRepository = sessionRepository;
}
/**
* Registers a new {@link UserSession}.
*
* @param userSession The user session to add
*/
public synchronized void add(UserSession userSession) {
if (StringUtils.isEmpty(userSession.getSessionId())) {
throw new ForumException("An UserSession instance must have a session ID");
}
if (!userSession.isBot()) {
this.preventDuplicates(userSession);
if (userSession.getUser().getId() == this.config.getInt(ConfigKeys.ANONYMOUS_USER_ID)) {
anonymousSessions.put(userSession.getSessionId(), userSession);
}
else {
UserSession existing = this.isUserInSession(userSession.getUser().getId());
if (existing != null) {
userSession.setLastVisit(existing.getLastVisit());
this.remove(existing.getSessionId());
}
else {
Session session = this.sessionRepository.get(userSession.getUser().getId());
if (session != null && session.getLastVisit() != null) {
userSession.setLastVisit(session.getLastVisit().getTime());
}
}
this.checkIfIsModerator(userSession);
loggedSessions.put(userSession.getSessionId(), userSession);
}
}
}
private void checkIfIsModerator(UserSession userSession) {
RoleManager roleManager = new RoleManager();
roleManager.setGroups(userSession.getUser().getGroups());
if (roleManager.isModerator()) {
this.moderatorsOnline++;
}
}
public void computeAllOnlineModerators() {
this.moderatorsOnline = 0;
Collection<UserSession> sessions = loggedSessions.values();
for (UserSession session : sessions) {
this.checkIfIsModerator(session);
}
}
public boolean isModeratorOnline() {
return this.moderatorsOnline > 0;
}
/**
* Make sure we'll not add a session that was already registered
* @param us
*/
private void preventDuplicates(UserSession us) {
if (this.getUserSession(us.getSessionId()) != null) {
this.remove(us.getSessionId());
}
}
/**
* Remove an entry fro the session map
*
* @param sessionId The session id to remove
*/
public synchronized void remove(String sessionId) {
if (loggedSessions.containsKey(sessionId)) {
UserSession userSession = this.getUserSession(sessionId);
if (userSession.getRoleManager() != null
&& userSession.getRoleManager().isModerator() && this.moderatorsOnline > 0) {
this.moderatorsOnline--;
}
loggedSessions.remove(sessionId);
}
else {
anonymousSessions.remove(sessionId);
}
}
/**
* Get all registered sessions
*
* @return <code>ArrayList</code> with the sessions. Each entry is an <code>UserSession</code> object.
*/
public List<UserSession> getAllSessions() {
List<UserSession> list = new ArrayList<UserSession>(loggedSessions.values());
list.addAll(anonymousSessions.values());
return list;
}
/**
* Gets the {@link UserSession} instance of all logged users
*
* @return A list with the user sessions
*/
public Collection<UserSession> getLoggedSessions() {
return loggedSessions.values();
}
/**
* Get the number of logged users
*
* @return the number of logged users
*/
public int getTotalLoggedUsers() {
return loggedSessions.size();
}
/**
* Get the number of anonymous users
*
* @return the number of anonymous users
*/
public int getTotalAnonymousUsers() {
return anonymousSessions.size();
}
/**
* Gets an {@link UserSession} by the session id.
*
* @param sessionId the session's id
* @return the user session
*/
public UserSession getUserSession(String sessionId) {
UserSession us = anonymousSessions.get(sessionId);
return us != null ? us : loggedSessions.get(sessionId);
}
/**
* Gets the number of session elements.
*
* @return The number of session elements currently online (without bots)
*/
public int getTotalUsers() {
return anonymousSessions.size() + loggedSessions.size();
}
/**
* Check if a given user in in the session
*
* @param userId The user id to check for existance in the session
* @return The respective {@link UserSession} if the user is already registered, or <code>null</code> otherwise.
*/
public UserSession isUserInSession(int userId) {
for (UserSession us : loggedSessions.values()) {
if (us.getUser().getId() == userId) {
return us;
}
}
return null;
}
/**
* Do a refresh in the user's session. This method will update the
* last visit time for the current user, as well checking for
* authentication if the session is new or the SSO user has changed
* @throws IOException
*/
public UserSession refreshSession(UserSession userSession) {
boolean isSSOAuthentication = ConfigKeys.TYPE_SSO.equals(this.config.getValue(ConfigKeys.AUTHENTICATION_TYPE));
userSession.getRequest().setAttribute("sso", isSSOAuthentication);
userSession.getRequest().setAttribute("ssoLogout", this.config.getValue(ConfigKeys.SSO_LOGOUT));
int anonymousUserId = this.config.getInt(ConfigKeys.ANONYMOUS_USER_ID);
if (this.getUserSession(userSession.getRequest().getSession().getId()) == null) {
userSession.setSessionId(userSession.getRequest().getSession().getId());
userSession.setCreationTime(System.currentTimeMillis());
//if (!JForumExecutionContext.getForumContext().isBot()) {
if (true) {
if (isSSOAuthentication) {
this.checkSSO(userSession);
}
else {
boolean autoLoginEnabled = this.config.getBoolean(ConfigKeys.AUTO_LOGIN_ENABLED);
boolean autoLoginSuccess = autoLoginEnabled && this.checkAutoLogin(userSession);
if (!autoLoginSuccess) {
userSession.becomeAnonymous(anonymousUserId);
userSession.setUser(this.userRepository.get(anonymousUserId));
}
}
}
this.add(userSession);
logger.info("Registered new userSession: " + userSession.getSessionId());
}
else {
// FIXME: Force a reload of the user instance, because if it's kept in the usersession,
// changes made to the group (like permissions) won't be seen.
userSession.setUser(this.userRepository.get(userSession.getUser().getId()));
}
userSession.ping();
if (userSession.getUser() == null || userSession.getUser().getId() == 0) {
logger.warn("After userSession.ping() -> userSession.getUser returned null or user.id is zero. " +
"User is null? " + ( userSession.getUser() == null ) + ". user.id is: "
+ (userSession.getUser() == null ? "getUser() returned null" : userSession.getUser().getId())
+ ". As we have a problem, will force the user to become anonymous. Session ID: " + userSession.getSessionId());
userSession.becomeAnonymous(anonymousUserId);
User anonymousUser = this.userRepository.get(userSession.getUser().getId());
if (anonymousUser == null) {
logger.warn("Could not find the anonymous user in the database. Tried using id " + anonymousUserId);
}
else {
userSession.setUser(anonymousUser);
}
}
RoleManager roleManager = new RoleManager();
if (userSession.getUser() != null) {
roleManager.setGroups(userSession.getUser().getGroups());
}
else {
logger.warn("At last step userSession.getUser() still returned null. Ignoring the roles. Session ID: " + userSession.getSessionId());
}
userSession.setRoleManager(roleManager);
return userSession;
}
/**
* Persist the user session to the database
* @param sessionId the id of the session to persist
*/
public void storeSession(String sessionId) {
UserSession userSession = this.getUserSession(sessionId);
if (userSession != null && userSession.getUser().getId() != this.config.getInt(ConfigKeys.ANONYMOUS_USER_ID)) {
Session session = userSession.asSession();
session.setLastVisit(session.getLastAccessed());
this.sessionRepository.add(session);
}
}
/**
* Checks user credentials / automatic login.
*
* @param userSession The UserSession instance associated to the user's session
* @return <code>true</code> if auto login was enabled and the user was sucessfuly logged in.
*/
private boolean checkAutoLogin(UserSession userSession) {
Cookie userIdCookie = userSession.getCookie(this.config.getValue(ConfigKeys.COOKIE_USER_ID));
Cookie hashCookie = userSession.getCookie(this.config.getValue(ConfigKeys.COOKIE_USER_HASH));
Cookie autoLoginCookie = userSession.getCookie(this.config.getValue(ConfigKeys.COOKIE_AUTO_LOGIN));
if (hashCookie != null && userIdCookie != null
&& !userIdCookie.getValue().equals(this.config.getValue(ConfigKeys.ANONYMOUS_USER_ID))
&& autoLoginCookie != null && "1".equals(autoLoginCookie.getValue())) {
String userId = userIdCookie.getValue();
String uidHash = hashCookie.getValue();
User user = this.userRepository.get(Integer.parseInt(userId));
if (user == null || user.isDeleted() || StringUtils.isEmpty(user.getSecurityHash())) {
userSession.becomeAnonymous(this.config.getInt(ConfigKeys.ANONYMOUS_USER_ID));
return false;
}
String securityHash = MD5.hash(user.getSecurityHash());
if (!securityHash.equals(uidHash)) {
userSession.becomeAnonymous(this.config.getInt(ConfigKeys.ANONYMOUS_USER_ID));
return false;
}
else {
userSession.setUser(user);
this.configureUserSession(userSession, user);
return true;
}
}
return false;
}
/**
* Setup optios and values for the user's session if authentication was ok.
*
* @param userSession The UserSession instance of the user
* @param user The User instance of the authenticated user
*/
private void configureUserSession(UserSession userSession, User user) {
userSession.setUser(user);
userSession.becomeLogged();
}
/**
* Checks for user authentication using some SSO implementation
*
* @param userSession UserSession
* @param request TODO
*/
private void checkSSO(UserSession userSession) {
try {
SSO sso = (SSO)Class.forName(this.config.getValue(ConfigKeys.SSO_IMPLEMENTATION)).newInstance();
sso.setConfig(this.config);
String username = sso.authenticateUser(userSession.getRequest());
logger.info(String.format("SSO authenticated an user with username %s. Session ID %s", username, userSession.getSessionId()));
if (StringUtils.isEmpty(username)) {
logger.warn(String.format("checkSSO found an empty / null username. Going anonymous. Session ID %s", userSession.getSessionId()));
userSession.becomeAnonymous(this.config.getInt(ConfigKeys.ANONYMOUS_USER_ID));
}
else {
SSOUtils utils = new SSOUtils(this.userRepository);
boolean userExists = utils.userExists(username);
logger.info(String.format("SSO user %s exists? %s", username, userExists));
if (!userExists) {
String email = (String)userSession.getAttribute(
this.config.getValue(ConfigKeys.SSO_EMAIL_ATTRIBUTE));
String password = (String)userSession.getAttribute(
this.config.getValue(ConfigKeys.SSO_PASSWORD_ATTRIBUTE));
if (email == null) {
email = this.config.getValue(ConfigKeys.SSO_DEFAULT_EMAIL);
}
if (password == null) {
password = this.config.getValue(ConfigKeys.SSO_DEFAULT_PASSWORD);
}
utils.register(password, email);
}
User user = utils.getUser();
logger.info(String.format("g: username=%s, jforumUserId=%s",
user != null ? user.getUsername() : "returned null",
user != null ? user.getId() : "returned null"));
this.configureUserSession(userSession, user);
if (user == null || user.getId() == 0) {
logger.warn("checkSSO -> utils.getUser() returned null or user.id is zero");
}
}
}
catch (Exception e) {
e.printStackTrace();
throw new ForumException("Error while executing SSO actions: " + e, e);
}
}
protected void reinitialiseAllSessions() {
loggedSessions = new HashMap<String, UserSession>();
anonymousSessions = new HashMap<String, UserSession>();
}
}