Skip to content

Commit

Permalink
Enhanced Session Management in Apache Roller (#148)
Browse files Browse the repository at this point in the history
* Roller session improvements.

* Add a test and fixes for problems revealed.

* Restore listener methods.

* Session manager only manages logged-in user sessions.

* Use default methods rather than adapter, also add test for session manager (a wip).
  • Loading branch information
snoopdave authored Feb 1, 2025
1 parent faafe37 commit cd5139a
Show file tree
Hide file tree
Showing 6 changed files with 396 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.apache.roller.weblogger.ui.core;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.roller.weblogger.pojos.User;
import org.apache.roller.weblogger.util.cache.Cache;
import org.apache.roller.weblogger.util.cache.CacheHandler;
import org.apache.roller.weblogger.util.cache.CacheManager;

import java.util.HashMap;
import java.util.Map;

public class RollerLoginSessionManager {
private static final Log log = LogFactory.getLog(RollerLoginSessionManager.class);
private static final String CACHE_ID = "roller.session.cache";
private final Cache sessionCache;

public static RollerLoginSessionManager getInstance() {
return RollerLoginSessionManager.SingletonHolder.INSTANCE;
}

private static class SingletonHolder {
private static final RollerLoginSessionManager INSTANCE = new RollerLoginSessionManager();
}

class SessionCacheHandler implements CacheHandler {
@Override
public void invalidate(User user) {
if (user != null && user.getUserName() != null) {
sessionCache.remove(user.getUserName());
}
}
}

/** Testing purpose only */
RollerLoginSessionManager(Cache cache) {
this.sessionCache = cache;
CacheManager.registerHandler(new SessionCacheHandler());
}

private RollerLoginSessionManager() {
Map<String, String> cacheProps = new HashMap<>();
cacheProps.put("id", CACHE_ID);
cacheProps.put("size", "1000"); // Cache up to 1000 sessions
cacheProps.put("timeout", "3600"); // Session timeout in seconds (1 hour)
this.sessionCache = CacheManager.constructCache(null, cacheProps);
CacheManager.registerHandler(new SessionCacheHandler());
}

public void register(String userName, RollerSession session) {
if (userName != null && session != null) {
this.sessionCache.put(userName, session);
log.debug("Registered session for user: " + userName);
}
}

public RollerSession get(String userName) {
if (userName != null) {
return (RollerSession) this.sessionCache.get(userName);
}
return null;
}

public void invalidate(String userName) {
if (userName != null) {
this.sessionCache.remove(userName);
log.debug("Invalidated session for user: " + userName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,19 @@

/**
* Roller session handles session startup and shutdown.
*
* @web.listener
*/
public class RollerSession
implements HttpSessionListener, HttpSessionActivationListener, Serializable {

static final long serialVersionUID = 5890132909166913727L;
private static final long serialVersionUID = 5890132909166913727L;

// the id of the user represented by this session
private String userName = null;

private static final Log log;

public static final String ROLLER_SESSION = "org.apache.roller.weblogger.rollersession";
public static final String ERROR_MESSAGE = "rollererror_message";
public static final String STATUS_MESSAGE = "rollerstatus_message";


static{
WebloggerConfig.init(); // must be called before calls to logging APIs
log = LogFactory.getLog(RollerSession.class);
Expand All @@ -68,14 +64,20 @@ public static RollerSession getRollerSession(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
rollerSession = (RollerSession)session.getAttribute(ROLLER_SESSION);

if (rollerSession == null) {
// HttpSession with no RollerSession?
// Must be a session that was de-serialized from a previous run.
// Create new session if none exists
rollerSession = new RollerSession();
session.setAttribute(ROLLER_SESSION, rollerSession);
} else if (rollerSession.getAuthenticatedUser() != null) {
// Check if session is still valid in cache
RollerLoginSessionManager manager = RollerLoginSessionManager.getInstance();
String username = rollerSession.getAuthenticatedUser().getUserName();
if (manager.get(username) == null) {
rollerSession = new RollerSession();
session.setAttribute(ROLLER_SESSION, rollerSession);
}
}

Principal principal = request.getUserPrincipal();

// If we've got a principal but no user object, then attempt to get
Expand Down Expand Up @@ -124,8 +126,7 @@ public static RollerSession getRollerSession(HttpServletRequest request) {

return rollerSession;
}



/** Create session's Roller instance */
@Override
public void sessionCreated(HttpSessionEvent se) {
Expand All @@ -138,15 +139,8 @@ public void sessionCreated(HttpSessionEvent se) {
public void sessionDestroyed(HttpSessionEvent se) {
clearSession(se);
}


/** Init session as if it was new */
@Override
public void sessionDidActivate(HttpSessionEvent se) {
}


/**

/**
* Purge session before passivation. Because Roller currently does not
* support session recovery, failover, migration, or whatever you want
* to call it when sessions are saved and then restored at some later
Expand All @@ -156,15 +150,14 @@ public void sessionDidActivate(HttpSessionEvent se) {
public void sessionWillPassivate(HttpSessionEvent se) {
clearSession(se);
}



/**
* Authenticated user associated with this session.
*/
public User getAuthenticatedUser() {

User authenticUser = null;
if(userName != null) {
if (userName != null) {
try {
UserManager mgr = WebloggerFactory.getWeblogger().getUserManager();
authenticUser = mgr.getUserByUserName(userName);
Expand All @@ -175,16 +168,16 @@ public User getAuthenticatedUser() {

return authenticUser;
}



/**
* Authenticated user associated with this session.
*/
public void setAuthenticatedUser(User authenticatedUser) {
this.userName = authenticatedUser.getUserName();
RollerLoginSessionManager sessionManager = RollerLoginSessionManager.getInstance();
sessionManager.register(authenticatedUser.getUserName(), this);
}



private void clearSession(HttpSessionEvent se) {
HttpSession session = se.getSession();
try {
Expand All @@ -196,5 +189,4 @@ private void clearSession(HttpSessionEvent se) {
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.apache.roller.weblogger.pojos.GlobalPermission;
import org.apache.roller.weblogger.pojos.User;
import org.apache.roller.weblogger.pojos.WeblogPermission;
import org.apache.roller.weblogger.ui.core.RollerLoginSessionManager;
import org.apache.roller.weblogger.ui.struts2.core.Register;
import org.apache.roller.weblogger.ui.struts2.util.UIAction;
import org.apache.struts2.interceptor.validation.SkipValidation;
Expand Down Expand Up @@ -165,6 +166,18 @@ public String save() {
// reset password if set
if (!StringUtils.isEmpty(getBean().getPassword())) {
user.resetPassword(getBean().getPassword());

// invalidate user's session if it's not user executing this action
if (!getAuthenticatedUser().getUserName().equals(user.getUserName())) {
RollerLoginSessionManager sessionManager = RollerLoginSessionManager.getInstance();
sessionManager.invalidate(user.getUserName());
}
}

// if user is disabled and not the same as the user executing this action, then invalidate their session
if (!user.getEnabled() && !getAuthenticatedUser().getUserName().equals(user.getUserName())) {
RollerLoginSessionManager sessionManager = RollerLoginSessionManager.getInstance();
sessionManager.invalidate(user.getUserName());
}

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,20 @@
*/
public interface CacheHandler {

void invalidate(WeblogEntry entry);

void invalidate(Weblog website);

void invalidate(WeblogBookmark bookmark);

void invalidate(WeblogBookmarkFolder folder);
default void invalidate(WeblogEntry entry) {}

void invalidate(WeblogEntryComment comment);
default void invalidate(Weblog website) {}

void invalidate(User user);
default void invalidate(WeblogBookmark bookmark) {}

void invalidate(WeblogCategory category);
default void invalidate(WeblogBookmarkFolder folder) {}

default void invalidate(WeblogEntryComment comment) {}

default void invalidate(User user) {}

default void invalidate(WeblogCategory category) {}

default void invalidate(WeblogTemplate template) {}

void invalidate(WeblogTemplate template);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. The ASF licenses this file to You
* 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. For additional information regarding
* copyright in this work, please see the NOTICE file in the top level
* directory of this distribution.
*/

package org.apache.roller.weblogger.ui.core;

import org.apache.roller.weblogger.pojos.User;
import org.apache.roller.weblogger.util.cache.Cache;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

class RollerLoginSessionManagerTest {
private RollerLoginSessionManager sessionManager;
private Cache mockCache;

@BeforeEach
void setUp() {
mockCache = mock(Cache.class);
sessionManager = new RollerLoginSessionManager(mockCache);
}

@Test
void testRegisterSession() {
RollerSession mockSession = mock(RollerSession.class);
String userName = "testUser";

sessionManager.register(userName, mockSession);

verify(mockCache).put(userName, mockSession);
}

@Test
void testGetSession() {
RollerSession mockSession = mock(RollerSession.class);
String userName = "testUser";
when(mockCache.get(userName)).thenReturn(mockSession);

RollerSession result = sessionManager.get(userName);

assertEquals(mockSession, result);
verify(mockCache).get(userName);
}

@Test
void testInvalidateSession() {
String userName = "testUser";

sessionManager.invalidate(userName);

verify(mockCache).remove(userName);
}

@Test
void testCacheHandlerInvalidation() {
User mockUser = mock(User.class);
String userName = "testUser";
when(mockUser.getUserName()).thenReturn(userName);

sessionManager.new SessionCacheHandler().invalidate(mockUser);

verify(mockCache).remove(userName);
}

@Test
void testNullInputHandling() {
RollerSession mockSession = mock(RollerSession.class);

sessionManager.register(null, mockSession);
sessionManager.invalidate(null);
sessionManager.get(null);

verify(mockCache, never()).put(any(), any());
verify(mockCache, never()).remove(any());
verify(mockCache, never()).get(any());
}

@Test
void testSessionTimeout() {
String userName = "testUser";
when(mockCache.get(userName)).thenReturn(null);

RollerSession result = sessionManager.get(userName);

assertNull(result);
verify(mockCache).get(userName);
}
}
Loading

0 comments on commit cd5139a

Please sign in to comment.