SessionService.java revision 35ab1c5bca11317474fe12bdd8d22c17cdaf2697
/**
* 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
* 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: SessionService.java,v 1.37 2010/02/03 03:52:54 bina Exp $
*
* Portions Copyrighted 2010-2015 ForgeRock AS.
*/
/**
* This class represents a Session Service
*/
public class SessionService {
/**
* Service name for NotificationSets
*/
/**
* Constants for delegated permissions
*/
private final Debug sessionDebug;
private final SessionServiceConfig serviceConfig;
private final SessionServerConfig serverConfig;
private final SSOTokenManager ssoTokenManager;
private final DsameAdminTokenProvider dsameAdminTokenProvider;
private final MonitoringOperations monitoringOperations;
private final SessionLogging sessionLogging;
private final InternalSessionFactory internalSessionFactory;
private final HttpConnectionFactory httpConnectionFactory;
private final SessionNotificationSender sessionNotificationSender;
private final ExecutorService executorService = Executors.newCachedThreadPool(); // TODO: Inject from Guice
private final Set remoteSessionSet;
/**
* AM Session Repository for Session Persistence.
*/
/**
* The following InternalSession is for the Authentication Service to use
* It is only accessed by AuthD.initAuthSessions
*/
private final TokenIdFactory tokenIdFactory;
private final SessionAdapter tokenAdapter;
private final SessionInfoFactory sessionInfoFactory;
private final InternalSessionCache cache;
private final SessionServiceURLService sessionServiceURLService;
private final SessionCache sessionCache;
private final SessionCookies sessionCookies;
private final SessionPollerPool sessionPollerPool;
/**
* Private Singleton Session Service.
*/
private SessionService(
final SSOTokenManager ssoTokenManager,
final SessionServerConfig serverConfig,
final TokenIdFactory tokenIdFactory,
final SessionAdapter tokenAdapter,
final SessionLogging sessionLogging,
final SessionCache sessionCache,
final SessionCookies sessionCookies,
final SessionPollerPool sessionPollerPool) {
this.sessionDebug = sessionDebug;
this.ssoTokenManager = ssoTokenManager;
this.serverConfig = serverConfig;
this.serviceConfig = serviceConfig;
this.tokenIdFactory = tokenIdFactory;
this.tokenAdapter = tokenAdapter;
this.sessionInfoFactory = sessionInfoFactory;
this.sessionLogging = sessionLogging;
this.cache = internalSessionCache;
this.sessionCache = sessionCache;
this.sessionCookies = sessionCookies;
this.sessionPollerPool = sessionPollerPool;
try {
maxSessionStats = new SessionMaxStats(cache, monitoringOperations, sessionNotificationSender, stats);
} else {
}
// Currently, we are not allowing to default to Session Failover HA,
// even with a single server to enable session persistence.
// But can easily be turned on in the Session SubConfig.
if (serviceConfig.isSessionFailoverEnabled()) {
// XXX: Leaking 'this' before constructor completes
// **************************************************************************
// Now Bootstrap CoreTokenService (CTS) Implementation, if one was specified.
if (coreTokenService == null) {
// Instantiate our Session Repository Implementation.
// Allows Static Elements to Initialize.
}
} else {
clusterMonitor = new SingleServerClusterMonitor();
}
}
}
/**
* Returns the Internal Session used by the Auth Services.
*
* @param domain Authentication Domain
* @param httpSession HttpSession
*/
// TODO: Pull out into new AuthSessionFactory class? This method is only called by AuthD.initAuthSessions
// and AuthD then keeps a local copy of the returned Session. The authSession reference may
// be getting stored as a field on this object to avoid it being garbage collected? Since
// we're not using authSession anywhere else in this class and it isn't referenced by the returned
// Session, it probably wouldn't matter if it was garbage collected.
try {
if (authSession == null) {
// Create a special InternalSession for Authentication Service
}
} catch (Exception e) {
return null;
}
}
/**
* Returns the Internal Session which can be used by services
*
* @param domain Authentication Domain
* @param httpSession HttpSession
*/
// TODO: Also pull this method out into new AuthSessionFactory class
try {
// Create a special InternalSession which can be used as
// service token
// note that this session does not need failover protection
// as its scope is only this same instance
// more over creating an HTTP session by making a self-request
// results in dead-lock if called from within synchronized
// section in getSessionService()
return session;
} catch (Exception e) {
return null;
}
}
/**
* Returns the restricted token
*
* @param masterSid master session id
* @param restriction TokenRestriction Object
* @return restricted token id
* @throws SessionException
*/
public String getRestrictedTokenId(String masterSid, TokenRestriction restriction) throws SessionException {
// we need to accommodate session failover situation
// first try
if (!checkServerUp(hostServerID)) {
}
// TODO consider one retry attempt
} else {
return token;
}
}
}
}
}
/**
* This method is expected to only be called for local sessions
*/
String doGetRestrictedTokenId(SessionID masterSid, TokenRestriction restriction) throws SessionException {
// locate master session
}
}
// attempt to reuse the token if restriction is the same
if (restrictedSid == null) {
if (previousValue == null) {
} else {
}
}
return restrictedSid.toString();
}
}
/**
* Removes the Internal Session from the Internal Session table.
*
* @param sid Session ID
*/
boolean isSessionStored = false;
return null;
// Session Constraint
}
}
try {
} catch (Exception e) {
"SessionService : failed deleting session ", e);
}
} else {
}
}
return session;
}
if (serviceConfig.isSessionFailoverEnabled()) {
try {
} catch (Exception e) {
e);
}
}
}
/**
* This method checks if Internal session is already present locally
*
* @param sid
* @return a boolean
*/
return isPresent;
}
/**
* Checks whether current session should be considered local (so that local
* invocations of SessionService methods are to be used) and if local and
* Session Failover is enabled will recover the Session if the Session is
* not found locally.
*
* @return a boolean
*/
if (isSessionPresent(sid)) {
return true;
} else {
if (serviceConfig.isSessionFailoverEnabled()) {
}
return true;
}
} else {
}
}
return false;
}
/**
* Returns the Internal Session corresponding to a Session ID.
*
* @param sid Session Id
*/
return null;
}
// check if sid is actually a handle return null (in order to prevent from assuming recovery case)
if (sid.isSessionHandle()) {
return null;
}
}
/**
* Quick access to the total size of the remoteSessionSet.
*
* @return the size of the sessionTable
*/
public int getRemoteSessionCount() {
return remoteSessionSet.size();
}
/**
* Quick access to the total size of the sessionTable (internal sessions), including
* both invalid and valid tokens in the count, as well as 'hidden' sessions used by OpenAM itself.
*
* @return the size of the sessionTable
*/
public int getInternalSessionCount() {
}
/**
* Returns the Internal Session corresponding to a session handle.
*
* @param shandle Session Id
* @return Internal Session corresponding to a session handle
*/
}
/**
* As opposed to locateSession() this one accepts normal or restricted token
* This is expected to be only called once the session is detected as local
*
* @param token
* @return
*/
}
}
return sess;
}
boolean checkRestriction) throws SessionException {
return null;
}
if (checkRestriction) {
try {
}
} catch (SessionException se) {
throw se;
} catch (Exception e) {
throw new SessionException(e);
}
}
return session;
}
/**
* Get all valid Internal Sessions.
*/
synchronized (cache) {
return false;
}
return false;
}
return true;
}
});
return sessions;
}
}
/**
* Get all valid Internal Sessions matched with pattern.
*/
throws SessionException {
pattern = "*";
}
try {
if (!matchAll) {
// For application sessions, the client ID
// will not be in the DN format but just uid.
sess.getClientID();
continue;
} else {
}
continue;
}
}
break;
}
break;
}
}
} catch (Exception e) {
+ "Unable to get Session Information ", e);
throw new SessionException(e);
}
return sessions;
}
/**
* Returns true if the given pattern is contained in the string.
*
* @param string to examine
* @param pattern to match
* @return true if string matches <code>filter</code>
*/
return true;
}
if (wildCardIndex >= 0) {
return false;
}
int stringIndex = 0;
if (wildCardIndex > 0) {
}
return false;
}
}
return true;
}
}
return false;
}
/**
* Destroy a Internal Session, whose session id has been specified.
*
* @param sid
*/
sess.setIsISStored(false);
}
}
}
/**
* Logout a Internal Session, whose session id has been specified.
*
* @param sid
*/
sess.setIsISStored(false);
}
}
}
/**
* Simplifies the signalling that a Session has been removed.
* @param session Non null InternalSession.
* @param event An integrate from the SessionEvent class.
*/
}
/**
* Decrements number of active sessions
*/
public void decrementActiveSessions() {
}
/**
* Increments number of active sessions
*/
public void incrementActiveSessions() {
}
// The following methods are corresponding to the session requests
// defined in the Session DTD. Those methods are being called
// in SessionRequestHandler class
/**
* Returns the Session information.
*
* @param sid
* @param reset
* @throws SessionException
*/
throws SessionException {
if (reset) {
}
return info;
}
/**
* Gets all valid Internal Sessions, depending on the value of the user's
* preferences.
*
* @param s
* @throws SessionException
*/
throws SessionException {
throw new SessionException(SessionBundle
.getString("invalidSessionState")
}
try {
"iplanet-am-session-get-valid-sessions");
}
// top level admin gets all sessions
boolean isTopLevelAdmin = hasTopLevelAdminRole(s);
// replace session id with session handle to prevent from
// impersonation
}
}
return infos;
} catch (Exception e) {
throw new SessionException(e);
}
}
/**
* Destroy a Internal Session, depending on the value of the user's
* preferences.
*
* @param requester
* @param sid
* @throws SessionException
*/
return;
}
// let us check if the argument is a session handle
}
}
}
/**
* Checks if the requester has the necessary permission to destroy the provided session. The user has the necessary
* privileges if one of these conditions is fulfilled:
* <ul>
* <li>The requester attempts to destroy its own session.</li>
* <li>The requester has top level admin role (having read/write access to any service configuration in the top
* level realm).</li>
* <li>The session's client domain is listed in the requester's profile under the
* <code>iplanet-am-session-destroy-sessions service</code> service attribute.</li>
* </ul>
*
* @param requester The requester's session.
* @param sid The session to destroy.
* @throws SessionException If none of the conditions above is fulfilled, i.e. when the requester does not have the
* necessary permissions to destroy the session.
*/
public void checkPermissionToDestroySession(Session requester, SessionID sid) throws SessionException {
}
try {
// a session can destroy itself or super admin can destroy anyone including another super admin
return;
}
}
} catch (Exception e) {
throw new SessionException(e);
}
}
/**
* Logout the user.
*
* @param sid
* @throws SessionException
*/
}
//if the provided sid was a restricted token, resolveToken will always validate the restriction, so there is no
//need to check restrictions here.
}
/**
* Adds listener to a Internal Sessions.
*
* @param sid Session ID
* @param url
* @throws SessionException Session is null OR the Session is invalid
*/
}
throw new IllegalArgumentException("Session id mismatch");
}
}
/**
* Add a listener to all Internal Sessions.
*
* @param session
* @param url
* @throws SessionException
*/
throw new SessionException(SessionBundle.getString("invalidSessionState") + session.getID().toString());
}
return;
}
try {
}
} catch (Exception e) {
throw new SessionException(e);
}
}
public int getNotificationQueueSize() {
}
}
/**
* Sets internal property to the Internal Session.
*
* @param sid
* @param name
* @param value
* @throws SessionException
*/
throws SessionException {
}
/**
* Given a restricted token, returns the SSOTokenID of the master token
* can only be used if the requester is an app token
*
* @param s Must be an app token
* @param restrictedID The SSOTokenID of the restricted token
* @return The SSOTokenID string of the master token
* @throws SSOException If the master token cannot be dereferenced
*/
throws SessionException {
// we need to accomodate session failover situation
//first try
if (!checkServerUp(hostServerID)) {
}
//TODO consider one retry attempt
} else {
return masterID;
}
}
}
}
}
// sjf bug 6797573
try {
conn.setDoOutput(true);
return null;
}
} finally {
}
return null;
}
/**
* Sets external property in the Internal Session as long as it is not
* protected
*
* @param clientToken - Token of the client setting external property.
* @param sid
* @param name
* @param value
* @throws SessionException
*/
throws SessionException {
}
/**
* Utility helper method to obtain session repository reference
*
* @return reference to session repository
*/
// TODO: Use coreTokenService field directly since it should be set in constructor?
protected CTSPersistentStore getRepository() {
return null;
}
if (coreTokenService == null) {
}
return coreTokenService;
}
/**
* Initialize the cluster server map given the server IDs in Set.
* Invoked by NamingService whenever any global configuration changes occur.
*/
public void reinitializeClusterMemberMap() throws Exception {
}
/**
* This is a key method for "internal request routing" mode It determines
* the server id which is currently hosting session identified by sid. In
* "internal request routing" mode, this method also has a side effect of
* releasing a session which no longer "belongs locally" (e.g., due to
* primary server instance restart)
*
* @param sid session id
* @return server id for the server instance determined to be the current
* host
* @throws SessionException
*/
// if we have a local session replica, discard it as hosting server instance is not supposed to be local
// actively clean up duplicates
}
}
return serverId;
}
/**
* Actively check if server identified by serverID is up
*
* @param serverID server id
* @return true if server is up, false otherwise
*/
}
/**
* Indicates that the Site is up.
*
* @param siteId A possibly null Site Id.
* @return True if the Site is up, False if it failed to respond to a query.
*/
}
/**
* This method will execute all the globally set session timeout handlers
* with the corresponding timeout event simultaniously.
*
* @param sessionId The timed out sessions ID
* @param changeType Type of the timeout event: IDLE_TIMEOUT (1) or MAX_TIMEOUT (2)
*/
// Take snapshot of reference to ensure atomicity.
try {
public void run() {
try {
SessionTimeoutHandler.class).newInstance();
switch (changeType) {
case SessionEvent.IDLE_TIMEOUT:
break;
case SessionEvent.MAX_TIMEOUT:
break;
}
if (Thread.interrupted()
|| ex instanceof InterruptedException
|| ex instanceof InterruptedIOException) {
} else {
}
} finally {
}
}
};
}
// Wait 1000ms for all handlers to complete.
try {
} catch (InterruptedException ignored) {
// This should never happen: we can't handle it here, so propagate it.
}
// It doesn't matter really if the future completes between isDone and cancel.
}
}
} catch (SSOException ssoe) {
}
}
}
/**
* Returns the User of the Session
*
* @param session Session
* @throws SessionException
* @throws SSOException
*/
try {
} catch (IdRepoException e) {
}
return user;
}
/**
* Returns true if the user has top level admin role
*
* @param session Session.
* @throws SessionException
* @throws SSOException
*/
}
/**
* Returns true if the user has top level admin role
*
* @param tokenUsedForSearch Single Sign on token used to do the search.
* @param clientID Client ID of the login user.
* @throws SessionException
* @throws SSOException
*/
throws SessionException, SSOException {
boolean topLevelAdmin = false;
try {
} catch (DelegationException de) {
sessionDebug.error("SessionService.hasTopLevelAdminRole: failed to check the delegation permission.", de);
}
return topLevelAdmin;
}
/**
* Returns true if the user is super user
*
* @param uuid the uuid of the login user
*/
boolean isSuperUser = false;
try {
// Get the AMIdentity Object for super user
adminUserId = new AMIdentity(dsameAdminTokenProvider.getAdminToken(), adminUser, IdType.USER, "/", null);
}
//Get the AMIdentity Object for login user
//Check for the equality
} catch (SSOException ssoe) {
} catch (IdRepoException idme) {
}
if (sessionDebug.messageEnabled()) {
}
return isSuperUser;
}
/**
* This functions invalidates the http session associated with identity
* session specified by sid
*
* @param sid
* @return
*/
return true;
}
try {
} catch (ConnectException ex) {
if (sessionDebug.messageEnabled()) {
}
return true;
} finally {
}
return false;
}
/**
* Removes InternalSession from the session table so that another server
* instance can be an owner This is used to help work around persistent
* association of OpenAM session ID and HTTP session ID which
* some loadbalancers (like Weblogic) use to make routing decisions. This
* helps to deal with the case where a session is migrated while the current
* owner is still alive to avoid having redundant copies of the session.
* This is the client side of distributed invocation.
*
* @param owner URL of the server instance who previously owned the session
* @param sid session ID of the session migrated
*/
if (sessionDebug.messageEnabled()) {
sessionDebug.message("Attempting to release InternalSession " + sid + " from server instance: " + owner);
}
try {
} catch (ConnectException ex) {
if (sessionDebug.messageEnabled()) {
}
return true;
} finally {
}
return false;
}
/**
* Removes InternalSession from the session table so that another server
* instance can be an owner This is the server side of distributed
* invocation initiated by calling releaseSession()
*
* @param sid session id of the session migrated
*/
if (!serviceConfig.isSessionFailoverEnabled()) {
}
// switch to non-local mode for cached client side session image
}
} else {
if (sessionDebug.messageEnabled()) {
}
}
return HttpURLConnection.HTTP_OK;
}
/**
* If InternalSession is not present, we attempt to recover its state from
* associated HttpSession. We have to set the session tracking cookie to
* HttpID which is present in the SessionID object. This will work in the
* fail over cases. We first get the HttpSession by invoking the
* GetHttpSession Servlet on the SAME server instance this code is invoked.
* This should trigger the Web container to perform recovery of the
* associated Http session
* <p/>
* We also pass the SessionID to the servlet to double check the match
* between the session id and Http session
* <p/>
* This is the "client side" of the remote invocation. The servlet will call
* retrieveSession() to complete the work
*
* @param sid Session ID
*/
if (!serviceConfig.isSessionFailoverEnabled()) {
return null;
}
try {
return sess;
}
/**
* As a side effect of deserialising an InternalSession, we must trigger
* the InternalSession to reschedule its timing task to ensure it
* maintains the session expiry function.
*/
sess.setSessionServiceDependencies(this, serviceConfig, sessionLogging, sessionCookies, sessionDebug);
} catch (CoreTokenException e) {
}
} else {
if (sessionDebug.messageEnabled()) {
}
try {
}
} finally {
}
}
return sess;
}
/**
* This is the "server side" of the remote invocation for recoverSession()
* It is being called by GetHttpSession servlet to complete the work
* <p/>
* If recovery is possible we need to first notify existing server instance
* "owning" the session (if any) to release the session instance otherwise
* we end up with duplicates
*
* @param sid Session ID
*/
String sessionState = (String) httpSession.getAttribute(serviceConfig.getHttpSessionPropertyName());
if (sessionState == null) {
return null;
} else {
return null;
}
// tell all previously registered owners of this session
// who might still potentially have a copy to release it
// (we do not expect a session to migrate more than one
// time so typically this list will have a size of 2).
Set ownerList = (Set) httpSession.getAttribute(serviceConfig.getHttpSessionOwnerListPropertyName());
}
continue;
return null;
}
}
// add current server to the list of former owners
return sess;
}
}
return null;
}
/**
* Utility used to updated various cross-reference mapping data structures
* associated with sessions up-to-date when sessions are being recovered
* after server instance failure
*
* @param sess session object
*/
return;
if (checkIfShouldDestroy(sess))
return;
}
}
}
/**
* function to remove remote sessions when primary server is up
*/
public void cleanUpRemoteSessions() {
synchronized (remoteSessionSet) {
// getCurrentHostServer automatically releases local
// session replica if it does not belong locally
try {
}
// if session does not belong locally remove it
}
}
}
}
}
/**
* Utility method to check if session has to be destroyed and to remove it
* if so Note that contrary to the name sess.shouldDestroy() has non-trivial
* side effects of changing session state and sending notification messages!
*
* @param sess session object
* @return true if session should (and has !) been destroyed
*/
boolean shouldDestroy = false;
try {
shouldDestroy = true;
}
if (shouldDestroy) {
try {
}
}
return shouldDestroy;
}
/**
* This is used to save session state using remote method rather than
* directly using a reference to HttpSession saved in the InternalSession
* which is not guaranteed to survive across http requests. We set the
* session tracking cookie to HttpID which is present in the SessionID
* object. We first get the HttpSession by invoking the GetHttpSession
* Servlet on the SAME server instance this code is invoked. This should
* trigger the Web container to provide a valid instance of the associated
* Http session and then use it to save the session
* <p/>
* This is the "client side" of the remote invocation. The servlet will call
* handleSaveSession() to complete the work
*
* @param sid Session ID
*/
if (!serviceConfig.isSessionFailoverEnabled()) {
return false;
}
if (sessionDebug.messageEnabled()) {
}
try {
} finally {
}
return false;
}
/**
* This is the "server side" of the remote invocation for saveSession() It
* is being called by GetHttpSession servlet to complete the work
*
* @param sid master Session ID
* @param httpSession http session
*/
if (!serviceConfig.isSessionFailoverEnabled()) {
}
return HttpURLConnection.HTTP_NOT_FOUND;
}
return HttpURLConnection.HTTP_INTERNAL_ERROR;
}
return HttpURLConnection.HTTP_OK;
}
/**
* This method is used to create restricted token
*
* @param owner server instance URL
* @param masterSid SessionID
* @param restriction restriction
*/
private String getRestrictedTokenIdRemotely(URL owner, SessionID masterSid, TokenRestriction restriction) {
try {
conn.setDoOutput(true);
return null;
}
} finally {
}
return null;
}
/**
* This method is the "server side" of the getRestrictedTokenIdRemotely()
*
* @param masterSid SessionID
* @param restriction restriction
*/
try {
}
return null;
}
/**
* This method is used to update the HttpSession when InternalSession
* property changes.
*
* @param session Session Object
*/
if (!serviceConfig.isSessionFailoverEnabled()) {
return;
}
// do not save sessions which never expire
if (!session.willExpire()) {
return;
}
try {
} catch (Exception e) {
// handleReleaseSession(session.getID());
}
} else {
if (serviceConfig.isUseRemoteSaveMethod()) {
} else {
if (httpSession != null) {
}
}
}
}
/**
* Save identity session state in associated http session
*
* @param session
* @param httpSession
*/
try {
} catch (Exception e) {
}
}
/**
* This method is used to encrypt the InternalSession object before storing
* into HttpSession.
*
* @param obj Object to be encrypted
*/
try {
byteOut = new ByteArrayOutputStream();
// convert object to byte using streams
// convert byte to string
// encrypt string
} catch (Exception e) {
.message("Error in encrypting the Internal Session object");
return null;
}
return strEncrypted;
}
/**
* This method is used to decrypt the InternalSession object, after
* obtaining from HttpSession.
*
* @param strEncrypted Object to be decrypted
*/
if (strEncrypted == null)
return null;
byte byteDecrypted[] = null;
try {
// decrypt string
// convert string to byte
// convert byte to object using streams
} catch (Exception e) {
return null;
}
if (tempObject == null) {
return null;
}
return (InternalSession) tempObject;
}
public boolean isSiteEnabled() {
return serverConfig.isSiteEnabled();
}
public boolean isSessionFailoverEnabled() {
return serviceConfig.isSessionFailoverEnabled();
}
public boolean isReducedCrossTalkEnabled() {
return serviceConfig.isReducedCrossTalkEnabled();
}
public boolean getUseInternalRequestRouting() {
}
}
}
public long getReducedCrosstalkPurgeDelay() {
return serviceConfig.getReducedCrosstalkPurgeDelay();
}
public boolean hasExceededMaxSessions() {
}
public static String getAMServerID() {
try {
}
return serverid;
}
}