Skip to content

Commit

Permalink
Extend non-expiring sessions only for local sessions (WrenSecurity#141)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelhoral authored and harrdou committed Oct 13, 2023
1 parent 58f6cd9 commit e4e3776
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2016 ForgeRock AS.
* Portions Copyright 2023 Wren Security.
*/

package com.iplanet.dpro.session.service;
Expand Down Expand Up @@ -87,12 +88,27 @@ public InternalSession getSession(SessionID sessionID) {
* @throws IllegalStateException if session not found in the store.
*/
public void promoteSession(SessionID sessionID) {
promoteSession(sessionID, false);
}

/**
* Moves the specified session out of this store and into the persistent store.
*
* @param sessionID Non null sessionID for the session to be promoted.
* @param trackNonExpiring Whether to automatically extend validity of non-expiring sessions.
* @throws IllegalStateException if session not found in the store.
*/
public void promoteSession(SessionID sessionID, boolean trackNonExpiring) {
InternalSession session = removeSession(sessionID);
if (session == null) {
throw new IllegalStateException("Attempted to promote non existent session");
}

sessionAccessManager.persistInternalSession(session);

if (!session.willExpire() && trackNonExpiring) {
sessionAccessManager.trackNonExpiringSession(session);
}
}

private void cullExpiredSessions() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* $Id: InternalSession.java,v 1.21 2009/03/20 21:05:25 weisun2 Exp $
*
* Portions Copyrighted 2011-2016 ForgeRock AS.
* Portions Copyright 2023 Wren Security.
*/
package com.iplanet.dpro.session.service;

Expand Down Expand Up @@ -64,16 +65,10 @@
import com.sun.identity.shared.Constants;
import com.sun.identity.shared.debug.Debug;
import org.forgerock.guice.core.InjectorHolder;
import org.forgerock.openam.cts.adapters.SessionAdapter;
import org.forgerock.openam.cts.api.tokens.Token;
import org.forgerock.openam.session.AMSession;
import org.forgerock.openam.session.SessionEventType;
import org.forgerock.openam.session.service.access.SessionPersistenceManager;
import org.forgerock.openam.session.service.access.SessionPersistenceObservable;
import org.forgerock.openam.session.service.access.persistence.InternalSessionPersistenceStore;
import org.forgerock.openam.session.service.access.persistence.InternalSessionStore;
import org.forgerock.openam.tokens.CoreTokenField;
import org.forgerock.openam.tokens.TokenType;
import org.forgerock.openam.utils.Time;
import org.forgerock.util.Reject;
import org.forgerock.util.annotations.VisibleForTesting;
Expand All @@ -87,11 +82,19 @@
*
*/
public class InternalSession implements Serializable, AMSession, SessionPersistenceObservable {

private static final long serialVersionUID = 1L;

/**
* Expiry time which is long enough to make sessions functionally non expiring.
*/
public static final long NON_EXPIRING_SESSION_LENGTH_MINUTES = 42 * TimeUnit.DAYS.toMinutes(365);

/**
* Maximum non-expiring session idle time in minutes.
*/
public static final long NON_EXPIRING_SESSION_MAX_IDLE_TIME = 30;

/*
* Session property names
*/
Expand Down Expand Up @@ -770,12 +773,14 @@ public String getUUID() {
}

/**
* Sets the willExpireFlag. This flag specify that whether the session will
* ever expire or not.
* Sets the willExpireFlag. This flag specify that whether the session will ever expire or not.
* <p>
* Note, that non-expiring sessions are still subject to max idle time. Clients are required to
* refresh session to keep it active.
*/
public void setNonExpiring() {
maxSessionTimeInMinutes = NON_EXPIRING_SESSION_LENGTH_MINUTES;
maxIdleTimeInMinutes = NON_EXPIRING_SESSION_LENGTH_MINUTES;
maxIdleTimeInMinutes = NON_EXPIRING_SESSION_MAX_IDLE_TIME;
maxCachingTimeInMinutes = serviceConfig.getApplicationMaxCachingTime();
willExpireFlag = false;
}
Expand Down Expand Up @@ -932,7 +937,6 @@ private void setSessionEventURLs(ConcurrentMap<String, Set<SessionID>> sessionEv

/**
* Returns the value of willExpireFlag.
*
*/
public boolean willExpire() {
return willExpireFlag;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5254,7 +5254,7 @@ void persistSession() {
if ((stateless && !restriction.isRestricted(getUserDN()) || isNoSession())) {
return;
}
authenticationSessionStore.promoteSession(sessionReference);
authenticationSessionStore.promoteSession(sessionReference, AdminTokenAction.isActiveLogin());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ public class AdminTokenAction implements PrivilegedAction<SSOToken> {
*/
private static volatile AdminTokenAction instance;

/**
* Flag indicating that the class is currently performing admin login.
* <p>
* This flag is used by the session persistence layer to distinguish local vs remote
* administrator login.
*/
private static final ThreadLocal<Boolean> activeLogin = new ThreadLocal<Boolean>();

private final SSOTokenManager tokenManager;
private SSOToken appSSOToken;
private SSOToken internalAppSSOToken;
Expand Down Expand Up @@ -299,7 +307,12 @@ private SSOToken getSSOToken() {
}

// Obtain SSOToken using AuthN service
ssoAuthToken = new SystemAppTokenProvider(adminDN, adminPassword).getAppSSOToken();
try {
activeLogin.set(true);
ssoAuthToken = new SystemAppTokenProvider(adminDN, adminPassword).getAppSSOToken();
} finally {
activeLogin.set(false);
}

// Restore the authentication state
if (authInit && ssoAuthToken != null) {
Expand All @@ -315,4 +328,12 @@ private SSOToken getSSOToken() {
}
return ssoAuthToken;
}

/**
* This is for internal use only.
*/
public static boolean isActiveLogin() {
return Boolean.TRUE.equals(activeLogin.get());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ private InternalSession initAuthSession(String domain) throws SSOException, Sess
String id = "id=" + rdnValueFromDn(clientID) + ",ou=user," + ServiceManager.getBaseDN();
session.putProperty(UNIVERSAL_IDENTIFIER, id);

authenticationSessionStore.promoteSession(session.getSessionID());
authenticationSessionStore.promoteSession(session.getSessionID(), true);

return session;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

package org.forgerock.openam.session.service;

import static com.iplanet.dpro.session.service.InternalSession.NON_EXPIRING_SESSION_LENGTH_MINUTES;

import com.iplanet.dpro.session.SessionID;
import com.iplanet.dpro.session.service.InternalSession;
import com.sun.identity.shared.debug.Debug;
Expand All @@ -30,21 +28,19 @@
import org.forgerock.openam.shared.concurrency.ThreadMonitor;

/**
* This class tracks sessions created by this server which are not set to expire. It achieves this by periodically
* setting the max session time to a large number, and the idle time to double the refresh period. Then, every refresh,
* it updates the latest access time of the session.
* This means that if a server goes offline, the sessions created for it will eventually be removed.
* This class tracks sessions created by this server which are not set to expire and updates it to keep idle time
* bellow allowed limit. If the server goes offline, the sessions created for it will eventually be removed.
*/
class NonExpiringSessionManager {

private static final Debug DEBUG = Debug.getInstance(SessionConstants.SESSION_DEBUG);

// Internal (in minutes) to periodically refresh non-expiring session validity
/**
* Internal (in minutes) to periodically refresh non-expiring session validity
* This has to be less than {@link InternalSession#NON_EXPIRING_SESSION_MAX_IDLE_TIME}.
*/
private static final long SESSION_REFRESH_INTERVAL = 5;

// Maximum non-expiring session idle time
private static final long SESSION_MAX_IDLE_TIME = SESSION_REFRESH_INTERVAL * 5;

private final Set<SessionID> nonExpiringSessions = new CopyOnWriteArraySet<>();

private final SessionAccessManager sessionAccessManager;
Expand All @@ -71,8 +67,6 @@ void addNonExpiringSession(InternalSession session) {
if (session.willExpire()) {
throw new IllegalStateException("Tried to add session which would expire to NonExpiringSessionManager");
}
session.setMaxSessionTime(NON_EXPIRING_SESSION_LENGTH_MINUTES);
session.setMaxIdleTime(SESSION_MAX_IDLE_TIME);
updateSession(session);
DEBUG.message("Registering non-expiring session for '{}'", session.getUUID());
nonExpiringSessions.add(session.getID());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2016 ForgeRock AS.
* Portions Copyright 2023 Wren Security.
*/
package org.forgerock.openam.session.service;

Expand Down Expand Up @@ -127,16 +128,19 @@ public InternalSession getByRestrictedID(SessionID sessionID) {
* @param session The session to persist.
*/
public void persistInternalSession(InternalSession session) {

try {
internalSessionStore.store(session);
} catch (SessionPersistenceException e) {
throw new RuntimeException(e);
}
}

if (!session.willExpire()) {
nonExpiringSessionManager.addNonExpiringSession(session);
}
/**
* Track and keep alive non-expiring local session.
* @param session The session to keep alive.
*/
public void trackNonExpiringSession(InternalSession session) {
nonExpiringSessionManager.addNonExpiringSession(session);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2016 ForgeRock AS.
* Portions Copyright 2021 Wren Security.
* Portions Copyright 2021-2023 Wren Security.
*/

package org.forgerock.openam.session.service;
Expand All @@ -27,10 +27,8 @@
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.forgerock.openam.session.service.NonExpiringSessionManager;
import org.forgerock.openam.shared.concurrency.ThreadMonitor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.testng.annotations.BeforeMethod;
Expand Down Expand Up @@ -75,8 +73,6 @@ public void shouldBeThreadMonitoredAndScheduled() {
@Test
public void shouldSetUpSessionCorrectly() {
nonExpiringSessionManager.addNonExpiringSession(mockInternalSession);
verify(mockInternalSession).setMaxSessionTime(InternalSession.NON_EXPIRING_SESSION_LENGTH_MINUTES);
verify(mockInternalSession).setMaxIdleTime(25);
verify(mockInternalSession).setLatestAccessTime();
}

Expand Down

0 comments on commit e4e3776

Please sign in to comment.