/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* https://opensso.dev.java.net/public/CDDLv1.0.html or
* opensso/legal/CDDLv1.0.txt
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at opensso/legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* $Id: InternalSession.java,v 1.21 2009/03/20 21:05:25 weisun2 Exp $
*
* Portions Copyrighted 2011-2016 ForgeRock AS.
*/
package com.iplanet.dpro.session.service;
import static java.util.concurrent.TimeUnit.*;
import static org.forgerock.openam.session.SessionConstants.*;
import static org.forgerock.openam.utils.Time.currentTimeMillis;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.forgerock.guice.core.InjectorHolder;
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.utils.Time;
import org.forgerock.util.Reject;
import org.forgerock.util.annotations.VisibleForTesting;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.google.inject.Key;
import com.google.inject.name.Names;
import com.iplanet.am.util.SystemProperties;
import com.iplanet.dpro.session.SessionException;
import com.iplanet.dpro.session.SessionID;
import com.iplanet.dpro.session.TokenRestriction;
import com.iplanet.dpro.session.share.SessionInfo;
import com.iplanet.sso.SSOToken;
import com.sun.identity.authentication.server.AuthContextLocal;
import com.sun.identity.session.util.SessionUtilsWrapper;
import com.sun.identity.shared.Constants;
import com.sun.identity.shared.debug.Debug;
/**
* The InternalSession
class represents a Webtop internal session.
*
* A session has four states: invalid, valid, inactive, and destroyed. The initial state of a session is invalid.
*
* @see SessionState
*
*/
public class InternalSession implements Serializable, AMSession, SessionPersistenceObservable {
/**
* 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);
/*
* Session property names
*/
private static final String HOST = "Host";
private static final String HOST_NAME = "HostName";
private static final String AM_MAX_IDLE_TIME = "AMMaxIdleTime";
private static final String AM_MAX_SESSION_TIME = "AMMaxSessionTime";
private static final String UNIVERSAL_IDENTIFIER = "sun.am.UniversalIdentifier";
private static final String SESSION_TIMED_OUT = "SessionTimedOut";
private static final Set
* The result is a copy of the current restricted token set: modifications to it will not change the set of
* restricted tokens associated with the session.
*
* @return the set of restricted tokens associated with this session. Never null but can be empty.
*/
public Set
* Time value is returned in the requested unit (accurate to millisecond) and uses the
* same epoch as {@link System#currentTimeMillis()}.
*
* @param timeUnit the time unit to return the result in.
* @return the result in the given units.
*/
public long getExpirationTime(final TimeUnit timeUnit) {
long timeLeftInSeconds = Math.max(0L, MINUTES.toSeconds(getMaxIdleTime()) - getIdleTime());
return timeUnit.convert(currentTimeMillis(), MILLISECONDS)
+ Math.min(timeUnit.convert(getTimeLeft(), SECONDS),
timeUnit.convert(timeLeftInSeconds, SECONDS));
}
/**
* Returns time at which session's lifetime expires.
*
* Time value is returned in the requested unit (accurate to millisecond) and uses the
* same epoch as {@link System#currentTimeMillis()}.
*
* @see #getMaxSessionTime()
* @param timeUnit the time unit to return the result in.
* @return the result in the given units.
*/
public long getMaxSessionExpirationTime(final TimeUnit timeUnit) {
return timeUnit.convert(creationTimeInSeconds + MINUTES.toSeconds(maxSessionTimeInMinutes), SECONDS);
}
/**
* Returns time at which session's idle time expires.
*
* Time value is returned in the requested unit (accurate to millisecond) and uses the
* same epoch as {@link System#currentTimeMillis()}.
*
* @see #getMaxIdleTime()
* @param timeUnit the time unit to return the result in.
* @return the result in the given units.
*/
public long getMaxIdleExpirationTime(final TimeUnit timeUnit) {
return timeUnit.convert(latestAccessTimeInSeconds + MINUTES.toSeconds(maxIdleTimeInMinutes), SECONDS);
}
/**
* @return True if the Session has reached an invalid state.
*/
public boolean isInvalid() {
return sessionState == SessionState.INVALID;
}
@Override
public void setPersistenceManager(SessionPersistenceManager manager) {
persistenceManager = manager;
}
private void notifyPersistenceManager() {
if (persistenceManager != null) {
persistenceManager.notifyUpdate(this);
}
}
private void fireSessionEvent(SessionEventType sessionEventType) {
sessionEventBroker.onEvent(new InternalSessionEvent(this, sessionEventType, Time.currentTimeMillis()));
}
}
USER
or APPLICATION
.
*/
public SessionType getType() {
return sessionType;
}
/**
* Set the type of Internal Session. User OR Application.
*
* @param type USER
or APPLICATION
.
*/
public void setType(SessionType type) {
sessionType = type;
notifyPersistenceManager();
}
/**
* Returns Client ID, accessing this Internal Session.
*
* @return Client ID.
*/
public String getClientID() {
return clientID;
}
/**
* Sets Client ID for this Internal Session.
*
* @param id
*/
public void setClientID(String id) {
clientID = id;
notifyPersistenceManager();
}
/**
* Returns the Domain of the Client
*
* @return Client Domain
*/
public String getClientDomain() {
return clientDomain;
}
/**
* Sets the Clieant's Domain.
*
* @param domain
* Client Domain
*/
public void setClientDomain(String domain) {
clientDomain = domain;
notifyPersistenceManager();
}
/**
* Returns maximum time allowed for the Internal Session.
* @return the number of maximum minutes for the session
*/
public long getMaxSessionTime() {
return maxSessionTimeInMinutes;
}
/**
* Sets the maximum time (in minutes) allowed for the Internal Session
*
* @param maxSessionTimeInMinutes
* Maximum Session Time
*/
public void setMaxSessionTime(long maxSessionTimeInMinutes) {
if (this.maxSessionTimeInMinutes != maxSessionTimeInMinutes) {
this.maxSessionTimeInMinutes = maxSessionTimeInMinutes;
notifyPersistenceManager();
}
}
/**
* Returns the maximum idle time(in minutes) for the Internal Session.
* @return the number maximum idle minutes
*/
public long getMaxIdleTime() {
return maxIdleTimeInMinutes;
}
/**
* Sets the maximum idle time (in minutes) for the Internal Session.
*
* @param maxIdleTimeInMinutes
*/
public void setMaxIdleTime(long maxIdleTimeInMinutes) {
this.maxIdleTimeInMinutes = maxIdleTimeInMinutes;
notifyPersistenceManager();
}
/**
* Returns the maximum caching time(in minutes) allowed for the Internal
* Session.
* @return Maximum Cache Time
*/
public long getMaxCachingTime() {
return maxCachingTimeInMinutes;
}
/**
* Sets the maximum caching time(in minutes) for the Internal Session.
*
* @param t
* Maximum Caching Time
*/
public void setMaxCachingTime(long t) {
maxCachingTimeInMinutes = t;
notifyPersistenceManager();
}
/**
* Returns the time(in seconds) for which the Internal Session has not been
* accessed.
* @return session idle time
*/
public long getIdleTime() {
long currentTimeInSeconds = MILLISECONDS.toSeconds(currentTimeMillis());
return currentTimeInSeconds - latestAccessTimeInSeconds;
}
/**
* Returns the total time left(in seconds) for the Internal Session. Returns 0 if the time left is negative.
* @return Time left for the internal session to be invalid
*/
public long getTimeLeft() {
long timeLeftInMillis = getMaxSessionExpirationTime(MILLISECONDS) - currentTimeMillis();
return MILLISECONDS.toSeconds(Math.max(timeLeftInMillis, 0));
}
/**
* Returns true if the session has timed out due to idle/max timeout period.
* @return true
if the Internal session has timedout ,
* false
otherwise
*/
public boolean isTimedOut() {
return timedOutTimeInSeconds != 0;
}
/**
* Cache the cookie string. No guarantees are made as to its continued persistence.
* @param cookieString The cookie string to persist.
*/
public void cacheCookieString(String cookieString) {
this.cookieStr = cookieString;
}
/**
* Returns the cached cookie string for this InternalSession. May be null.
* @return The cached cookie string. May be null.
*/
public String getCachedCookieString() {
return cookieStr;
}
/**
* Return the SessionID object which represents this InternalSession.
* @return The session ID.
*/
public SessionID getSessionID() {
return sessionID;
}
/**
* Returns the state of the Internal Session
* @return the session state can be VALID, INVALID, INACTIVE or DESTROYED
*/
public SessionState getState() {
return sessionState;
}
/**
* Get the authentication context associated with this session.
*
* @return the AuthContextLocal associated with this session
*/
public AuthContextLocal getAuthContext() {
return authContext;
}
/**
* Gets whether this session has an associated authenticationContext.
* @return true if this session has an authentication context.
*/
public boolean hasAuthenticationContext() {
return null != authContext;
}
/**
* Sets the authentication context.
*
* @param authContext the authentication context
*/
public void setAuthContext(AuthContextLocal authContext) {
this.authContext = authContext;
}
/**
* Clears the authentication context from this session.
*/
public void clearAuthContext() {
this.authContext = null;
}
/**
* Returns the value of the specified key from the Internal Session property
* table.
*
* @param key
* Property key
* @return string value for the key from Internal Session table.
*/
public String getProperty(String key) {
return sessionProperties.getProperty(key);
}
/**
* Returns the Enumeration of property names of the Internal Session
* property table.
* @return list of properties in the Internal session table.
*/
public Enumeration getPropertyNames() {
return sessionProperties.propertyNames();
}
/**
* Helper method to check if a property is protected or not.
*
* We introduce a mechanism to protect certain "core" or "internal"
* properties from updates via remote SetProperty method of the
* SessionService. Allowing remote self-updates to session properties leads
* to a security vulnerability which allows unconstrained user impersonation
* or privilege elevation. See bug # 4814922 for more information
*
* protectedProperties contains a set of property names which can not be
* remotely updated. It is initially populated using static initializer. We
* also implemented an extra safety mechanism intended to protect from
* accidental reopening of this security hole in the future if a property
* name changes or new property is introduced without corresponding update
* of the static hardcoded list of protected properties below. This
* mechanism automatically adds any property to protectedProperties if it is
* set via local invocation of putProperty.
*
* However, some properties (such as Locale and CharSet) must be settable
* both locally and remotely. In order to make it configurable we use a
* second table called remotelyUpdateableProperties. Note that
* protectedProperties takes precedence over remotelyUpdateableProperties:
* remotelyUpdateableProperties will be consulted only if a property is not
* on the protectedProperties list already.
*
* The following tables defines the behavior of putProperty() and
* putExternalProperty() depending on whether property name is present in
* protectedProperties or remotelyUpdateableProperty list
*
* protectedProperties remotelyUpdateableProperties putProperty()
* putExternalProperty()
*
* in n/a sets value logs, does nothing
*
* out in sets value sets value
*
* out out sets value and sets value adds to protectedProperty
*
* @param key
* property name.
* @return true if property is protected else false.
*/
public static boolean isProtectedProperty(String key) {
return protectedProperties.contains(key) || key.toLowerCase().startsWith(Constants.AM_PROTECTED_PROPERTY_PREFIX);
}
private static SetSession
is in the upgrade state or not.
*
* @param value true
if it is an upgrade
* false
otherwise
*/
public void setIsSessionUpgrade(boolean value) {
isSessionUpgrade = value;
notifyPersistenceManager();
}
/**
* Gets the status of the Session
if is an upgrade state
*
* @return true
if the session is in upgrade state
* false
otherwise
*/
public boolean getIsSessionUpgrade() {
return isSessionUpgrade;
}
/**
* Returns whether the InternalSession represented has been stored. If this is true, changes to this object will
* update the stored version.
* return true
if the internal session is stored
* false
otherwise
*/
public boolean isStored() {
return persistenceManager != null;
}
/**
* Changes the state of the session to ACTIVE after creation.
* @param userDN
* @return true
if the session is successfully activated
* after creation , false
otherwise
*/
public boolean activate(String userDN) {
// check userDN was provided
if (userDN == null) {
return false;
}
// check session quota constraints
if ((serviceConfig.isSessionConstraintEnabled()) && !shouldIgnoreSessionQuotaChecking()) {
if (sessionConstraint.checkQuotaAndPerformAction(this)) {
debug.message("Session Quota exhausted!");
fireSessionEvent(SessionEventType.QUOTA_EXHAUSTED);
return false;
}
}
// safe to proceed with session activation
setLatestAccessTime();
setState(SessionState.VALID);
fireSessionEvent(SessionEventType.SESSION_CREATION);
return true;
}
/*
* The session quota checking will be bypassed if:
* (1) the login user is the super user (not including users assigned the top level admin role), or
* (2) the token is an application token (e.g. Agent)
*/
private boolean shouldIgnoreSessionQuotaChecking() {
boolean ignore = false;
if (sessionService.isSuperUser(getUUID()) || (isAppSession())) {
ignore = true;
}
return ignore;
}
/**
* Gets the User Universal ID
* @return UUID
*/
public String getUUID() {
return getProperty(UNIVERSAL_IDENTIFIER);
}
/**
* Sets the willExpireFlag. This flag specify that whether the session will
* ever expire or not.
*/
public void setNonExpiring() {
maxSessionTimeInMinutes = NON_EXPIRING_SESSION_LENGTH_MINUTES;
maxIdleTimeInMinutes = NON_EXPIRING_SESSION_LENGTH_MINUTES;
maxCachingTimeInMinutes = serviceConfig.getApplicationMaxCachingTime();
willExpireFlag = false;
}
/**
* Sets session timeout time (in millis).
*
* @param timeoutTime The timeout time (in millis).
*/
public void setTimedOutTime(long timeoutTime) {
Reject.rejectStateIfTrue(!willExpire(), "Cannot timeout non-expiring session.");
Reject.rejectStateIfTrue(isTimedOut(), "Session already timed out.");
timedOutTimeInSeconds = MILLISECONDS.toSeconds(timeoutTime);
putProperty(SESSION_TIMED_OUT, String.valueOf(timedOutTimeInSeconds));
}
public SessionInfo toSessionInfo() {
return toSessionInfo(true);
}
/**
* Transfers the info about the Internal Session to Session Info.
* @return SessionInfo
*/
public SessionInfo toSessionInfo(boolean withIds) {
SessionInfo info = new SessionInfo();
if (withIds) {
info.setSessionID(sessionID.toString());
} else {
info.setSecret(java.util.UUID.randomUUID().toString());
}
if (sessionType == SessionType.USER) {
info.setSessionType("user");
} else if (sessionType == SessionType.APPLICATION) {
info.setSessionType("application");
}
info.setClientID(clientID);
info.setClientDomain(clientDomain);
info.setMaxTime(getMaxSessionTime());
info.setMaxIdle(getMaxIdleTime());
info.setMaxCaching(getMaxCachingTime());
if (willExpireFlag) {
info.setTimeIdle(getIdleTime());
info.setTimeLeft(getTimeLeft());
} else {
// Sessions such as authentication session will never be destroyed
info.setNeverExpiring(true);
}
info.setState(sessionState.name().toLowerCase());
info.setProperties((Hashtabletrue
if this is an application session, false
otherwise.
*/
public boolean isAppSession() {
return sessionType == SessionType.APPLICATION;
}
/**
* Determine whether it is a user session.
*
* @return true
if this is a user session, false
otherwise.
*/
public boolean isUserSession() {
return sessionType == SessionType.USER;
}
/**
* Sets the creation time of the Internal Session, as the number of seconds
* since midnight January 1, 1970 GMT.
*/
private void setCreationTime() {
creationTimeInSeconds = currentTimeMillis() / 1000;
}
/**
* Add new restricted token pointing at the same session to the list.
*
* @param newRestrictedTokenId The session ID.
* @param restriction The token restriction.
* @return The restricted token id for this TokenRestriction.
*/
public SessionID addRestrictedToken(SessionID newRestrictedTokenId, TokenRestriction restriction) {
SessionID currentRestrictedTokenId = restrictedTokensByRestriction.putIfAbsent(restriction, newRestrictedTokenId);
if (currentRestrictedTokenId == null) {
restrictedTokensBySid.put(newRestrictedTokenId, restriction);
notifyPersistenceManager();
return newRestrictedTokenId;
}
return currentRestrictedTokenId;
}
/**
* Returns the TokenRestriction for the given SessionID.
*
* @param sid Possibly null SessionID.
* @return Null indicates there is no restriction on the Session.
*/
public TokenRestriction getRestrictionForToken(SessionID sid) {
return restrictedTokensBySid.get(sid);
}
/**
* Returns the SessionID of the restricted token for the provided restriction for this session.
*
* @param restriction restriction used to look up restricted token.
* @return restricted token sessionID.
*/
public SessionID getRestrictedTokenForRestriction(TokenRestriction restriction) {
return restrictedTokensByRestriction.get(restriction);
}
/**
* Returns the set (possibly empty) of restricted session IDs associated with this session. A restricted session
* ID can only be used when the associated {@link TokenRestriction} is satisfied. Typically this ties a particular
* user session to only be used via a particular agent or from a particular IP address.
*