001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.main; 029 030import org.opencms.configuration.CmsSystemConfiguration.UserSessionMode; 031import org.opencms.db.CmsUserSettings; 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsProject; 034import org.opencms.file.CmsRequestContext; 035import org.opencms.file.CmsUser; 036import org.opencms.main.CmsBroadcast.ContentMode; 037import org.opencms.security.CmsCustomLoginException; 038import org.opencms.security.CmsRole; 039import org.opencms.security.CmsSecurityException; 040import org.opencms.security.CmsUserLog; 041import org.opencms.ui.login.CmsLoginHelper; 042import org.opencms.util.CmsRequestUtil; 043import org.opencms.util.CmsStringUtil; 044import org.opencms.util.CmsUUID; 045import org.opencms.workplace.CmsWorkplace; 046import org.opencms.workplace.CmsWorkplaceManager; 047import org.opencms.workplace.tools.CmsToolManager; 048 049import java.io.PrintWriter; 050import java.io.StringWriter; 051import java.util.Collections; 052import java.util.Enumeration; 053import java.util.Iterator; 054import java.util.List; 055 056import javax.servlet.http.HttpServletRequest; 057import javax.servlet.http.HttpSession; 058import javax.servlet.http.HttpSessionEvent; 059 060import org.apache.commons.collections.Buffer; 061import org.apache.commons.collections.BufferUtils; 062import org.apache.commons.collections.buffer.CircularFifoBuffer; 063import org.apache.commons.logging.Log; 064 065/** 066 * Keeps track of the sessions running on the OpenCms server and 067 * provides a session info storage which is used to get an overview 068 * about currently authenticated OpenCms users, as well as sending broadcasts between users.<p> 069 * 070 * For each authenticated OpenCms user, a {@link org.opencms.main.CmsSessionInfo} object 071 * holds the information about the users status.<p> 072 * 073 * When a user session is invalidated, the user info will be removed. 074 * This happens when a user log out, or when his session times out.<p> 075 * 076 * <b>Please Note:</b> The current implementation does not provide any permission checking, 077 * so all users can access the methods of this manager. Permission checking 078 * based on the current users OpenCms context may be added in a future OpenCms release.<p> 079 * 080 * @since 6.0.0 081 */ 082public class CmsSessionManager { 083 084 /** Header key 'true-client-ip' used by akamai proxies. */ 085 public static final String HEADER_TRUE_CLIENT_IP = "true-client-ip"; 086 087 /** Header key 'user-agent'. */ 088 public static final String HEADER_USER_AGENT = "user-agent"; 089 090 /** Request header containing the real client IP address. */ 091 public static final String HEADER_X_FORWARDED_FOR = "x-forwarded-for"; 092 093 /** Name of the logger for logging user switches. */ 094 public static final String NAME_USERSWITCH = "userswitch"; 095 096 /** Session attribute key for client token. */ 097 private static final String CLIENT_TOKEN = "client-token"; 098 099 /** The log object for this class. */ 100 private static final Log LOG = CmsLog.getLog(CmsSessionManager.class); 101 102 /** Special logger for logging user switches. */ 103 private static final Log USERSWITCH = CmsLog.getLog(NAME_USERSWITCH); 104 105 static { 106 CmsLog.makeChannelNonManageable(NAME_USERSWITCH); 107 } 108 109 /** Lock object for synchronized session count updates. */ 110 private Object m_lockSessionCount; 111 112 /** Counter for the currently active sessions. */ 113 private int m_sessionCountCurrent; 114 115 /** Counter for all sessions created so far. */ 116 private int m_sessionCountTotal; 117 118 /** Session storage provider instance. */ 119 private I_CmsSessionStorageProvider m_sessionStorageProvider; 120 121 /** The user session mode. */ 122 private UserSessionMode m_userSessionMode; 123 124 /** Admin CmsObject. */ 125 private CmsObject m_adminCms; 126 127 /** 128 * Creates a new instance of the OpenCms session manager.<p> 129 */ 130 protected CmsSessionManager() { 131 132 // create a lock object for the session counter 133 m_lockSessionCount = new Object(); 134 } 135 136 /** 137 * Checks whether a new session can be created for the user, and throws an exception if not.<p> 138 * 139 * @param user the user to check 140 * @throws CmsException if no new session for the user can't be created 141 */ 142 public void checkCreateSessionForUser(CmsUser user) throws CmsException { 143 144 if (getUserSessionMode() == UserSessionMode.single) { 145 List<CmsSessionInfo> infos = getSessionInfos(user.getId()); 146 if (!infos.isEmpty()) { 147 throw new CmsCustomLoginException( 148 org.opencms.security.Messages.get().container( 149 org.opencms.security.Messages.ERR_ALREADY_LOGGED_IN_0)); 150 } 151 } 152 153 } 154 155 /** 156 * Returns the broadcast queue for the given OpenCms session id.<p> 157 * 158 * @param sessionId the OpenCms session id to get the broadcast queue for 159 * 160 * @return the broadcast queue for the given OpenCms session id 161 */ 162 public Buffer getBroadcastQueue(String sessionId) { 163 164 CmsSessionInfo sessionInfo = getSessionInfo(getSessionUUID(sessionId)); 165 if (sessionInfo == null) { 166 // return empty message buffer if the session is gone or not available 167 return BufferUtils.synchronizedBuffer(new CircularFifoBuffer(CmsSessionInfo.QUEUE_SIZE)); 168 } 169 return sessionInfo.getBroadcastQueue(); 170 } 171 172 /** 173 * Returns the number of sessions currently authenticated in the OpenCms security system.<p> 174 * 175 * @return the number of sessions currently authenticated in the OpenCms security system 176 */ 177 public int getSessionCountAuthenticated() { 178 179 // since this method could be called from another thread 180 // we have to prevent access before initialization 181 if (m_sessionStorageProvider == null) { 182 return 0; 183 } 184 return m_sessionStorageProvider.getSize(); 185 } 186 187 /** 188 * Returns the number of current sessions, including the sessions of not authenticated guest users.<p> 189 * 190 * @return the number of current sessions, including the sessions of not authenticated guest users 191 */ 192 public int getSessionCountCurrent() { 193 194 return m_sessionCountCurrent; 195 } 196 197 /** 198 * Returns the number of total sessions generated so far, including already destroyed sessions.<p> 199 * 200 * @return the number of total sessions generated so far, including already destroyed sessions 201 */ 202 public int getSessionCountTotal() { 203 204 return m_sessionCountTotal; 205 } 206 207 /** 208 * Returns the complete user session info of a user from the session storage, 209 * or <code>null</code> if this session id has no session info attached.<p> 210 * 211 * @param sessionId the OpenCms session id to return the session info for 212 * 213 * @return the complete user session info of a user from the session storage 214 */ 215 public CmsSessionInfo getSessionInfo(CmsUUID sessionId) { 216 217 // since this method could be called from another thread 218 // we have to prevent access before initialization 219 if (m_sessionStorageProvider == null) { 220 return null; 221 } 222 return m_sessionStorageProvider.get(sessionId); 223 } 224 225 /** 226 * Returns the OpenCms user session info for the given request, 227 * or <code>null</code> if no user session is available.<p> 228 * 229 * @param req the current request 230 * 231 * @return the OpenCms user session info for the given request, or <code>null</code> if no user session is available 232 */ 233 public CmsSessionInfo getSessionInfo(HttpServletRequest req) { 234 235 HttpSession session = req.getSession(false); 236 if (session == null) { 237 // special case for accessing a session from "outside" requests (e.g. upload applet) 238 String sessionId = req.getHeader(CmsRequestUtil.HEADER_JSESSIONID); 239 return sessionId == null ? null : getSessionInfo(sessionId); 240 } 241 return getSessionInfo(session); 242 } 243 244 /** 245 * Returns the OpenCms user session info for the given http session, 246 * or <code>null</code> if no user session is available.<p> 247 * 248 * @param session the current http session 249 * 250 * @return the OpenCms user session info for the given http session, or <code>null</code> if no user session is available 251 */ 252 public CmsSessionInfo getSessionInfo(HttpSession session) { 253 254 if (session == null) { 255 return null; 256 } 257 CmsUUID sessionId = (CmsUUID)session.getAttribute(CmsSessionInfo.ATTRIBUTE_SESSION_ID); 258 return (sessionId == null) ? null : getSessionInfo(sessionId); 259 } 260 261 /** 262 * Returns the complete user session info of a user from the session storage, 263 * or <code>null</code> if this session id has no session info attached.<p> 264 * 265 * @param sessionId the OpenCms session id to return the session info for, 266 * this must be a String representation of a {@link CmsUUID} 267 * 268 * @return the complete user session info of a user from the session storage 269 * 270 * @see #getSessionInfo(CmsUUID) 271 */ 272 public CmsSessionInfo getSessionInfo(String sessionId) { 273 274 return getSessionInfo(getSessionUUID(sessionId)); 275 } 276 277 /** 278 * Returns all current session info objects.<p> 279 * 280 * @return all current session info objects 281 */ 282 public List<CmsSessionInfo> getSessionInfos() { 283 284 // since this method could be called from another thread 285 // we have to prevent access before initialization 286 if (m_sessionStorageProvider == null) { 287 return Collections.emptyList(); 288 } 289 return m_sessionStorageProvider.getAll(); 290 } 291 292 /** 293 * Returns a list of all active session info objects for the specified user.<p> 294 * 295 * An OpenCms user can have many active sessions. 296 * This is e.g. possible when two people have logged in to the system using the 297 * same username. Even one person can have multiple sessions if he 298 * is logged in to OpenCms with several browser windows at the same time.<p> 299 * 300 * @param userId the id of the user 301 * 302 * @return a list of all active session info objects for the specified user 303 */ 304 public List<CmsSessionInfo> getSessionInfos(CmsUUID userId) { 305 306 // since this method could be called from another thread 307 // we have to prevent access before initialization 308 if (m_sessionStorageProvider == null) { 309 return Collections.emptyList(); 310 } 311 return m_sessionStorageProvider.getAllOfUser(userId); 312 } 313 314 /** 315 * Gets the user session mode.<p> 316 * 317 * @return the user session mode 318 */ 319 public UserSessionMode getUserSessionMode() { 320 321 return m_userSessionMode; 322 } 323 324 /** 325 * Returns whether the current request has a valid client token.<p> 326 * Used to prevent session hijacking.<p> 327 * 328 * @param req the current request 329 * 330 * @return <code>true</code> in case the request has a valid token 331 */ 332 public boolean hasValidClientToken(HttpServletRequest req) { 333 334 String requestToken = generateClientToken(req); 335 String sessionToken = null; 336 HttpSession session = req.getSession(false); 337 if (session != null) { 338 sessionToken = (String)session.getAttribute(CLIENT_TOKEN); 339 } 340 return requestToken.equals(sessionToken); 341 } 342 343 /** 344 * Kills all sessions for the given user.<p> 345 * 346 * @param cms the current CMS context 347 * @param user the user for whom the sessions should be killed 348 * 349 * @throws CmsException if something goes wrong 350 */ 351 public void killSession(CmsObject cms, CmsUser user) throws CmsException { 352 353 OpenCms.getRoleManager().checkRole(cms, CmsRole.ACCOUNT_MANAGER); 354 List<CmsSessionInfo> infos = getSessionInfos(user.getId()); 355 for (CmsSessionInfo info : infos) { 356 m_sessionStorageProvider.remove(info.getSessionId()); 357 } 358 } 359 360 /** 361 * Destroys a session given the session id. Only allowed for users which have the "account manager" role.<p> 362 * 363 * @param cms the current CMS context 364 * @param sessionid the session id 365 * 366 * @throws CmsException if something goes wrong 367 */ 368 public void killSession(CmsObject cms, CmsUUID sessionid) throws CmsException { 369 370 OpenCms.getRoleManager().checkRole(cms, CmsRole.ACCOUNT_MANAGER); 371 m_sessionStorageProvider.remove(sessionid); 372 373 } 374 375 /** 376 * Sends a broadcast to all sessions of all currently authenticated users.<p> 377 * 378 * @param cms the OpenCms user context of the user sending the broadcast 379 * 380 * @param message the message to broadcast 381 */ 382 @Deprecated 383 public void sendBroadcast(CmsObject cms, String message) { 384 385 sendBroadcast(cms, message, ContentMode.plain); 386 } 387 388 /** 389 * Sends a broadcast to all sessions of all currently authenticated users.<p> 390 * 391 * @param cms the OpenCms user context of the user sending the broadcast 392 * 393 * @param message the message to broadcast 394 * @param repeat repeat this message 395 */ 396 @Deprecated 397 public void sendBroadcast(CmsObject cms, String message, boolean repeat) { 398 399 sendBroadcast(cms, message, repeat, ContentMode.plain); 400 401 } 402 403 /** 404 * Sends a broadcast to all sessions of all currently authenticated users.<p> 405 * 406 * @param cms the OpenCms user context of the user sending the broadcast 407 * @param message the message to broadcast 408 * @param repeat repeat this message 409 * @param mode the content mode to use 410 */ 411 public void sendBroadcast(CmsObject cms, String message, boolean repeat, ContentMode mode) { 412 413 if (CmsStringUtil.isEmptyOrWhitespaceOnly(message)) { 414 // don't broadcast empty messages 415 return; 416 } 417 // create the broadcast 418 CmsBroadcast broadcast = new CmsBroadcast(cms.getRequestContext().getCurrentUser(), message, repeat, mode); 419 // send the broadcast to all authenticated sessions 420 Iterator<CmsSessionInfo> i = m_sessionStorageProvider.getAll().iterator(); 421 while (i.hasNext()) { 422 CmsSessionInfo sessionInfo = i.next(); 423 if (m_sessionStorageProvider.get(sessionInfo.getSessionId()) != null) { 424 // double check for concurrent modification 425 sessionInfo.getBroadcastQueue().add(broadcast); 426 } 427 } 428 429 } 430 431 /** 432 * Sends a broadcast to all sessions of all currently authenticated users.<p> 433 * 434 * @param cms the OpenCms user context of the user sending the broadcast 435 * @param message the message to broadcast 436 * @param mode the content mode 437 */ 438 public void sendBroadcast(CmsObject cms, String message, ContentMode mode) { 439 440 sendBroadcast(cms, message, false, mode); 441 } 442 443 /** 444 * Sends a broadcast to the specified user session.<p> 445 * 446 * @param cms the OpenCms user context of the user sending the broadcast 447 * 448 * @param message the message to broadcast 449 * @param sessionId the OpenCms session uuid target (receiver) of the broadcast 450 */ 451 @Deprecated 452 public void sendBroadcast(CmsObject cms, String message, String sessionId) { 453 454 sendBroadcast(cms, message, sessionId, false); 455 456 } 457 458 /** 459 * Sends a broadcast to the specified user session.<p> 460 * 461 * @param cms the OpenCms user context of the user sending the broadcast 462 * 463 * @param message the message to broadcast 464 * @param sessionId the OpenCms session uuid target (receiver) of the broadcast 465 * @param repeat repeat this message 466 */ 467 @Deprecated 468 public void sendBroadcast(CmsObject cms, String message, String sessionId, boolean repeat) { 469 470 sendBroadcast(cms, message, sessionId, repeat, ContentMode.plain); 471 472 } 473 474 /** 475 * Sends a broadcast to the specified user session.<p> 476 * 477 * @param cms the OpenCms user context of the user sending the broadcast 478 * 479 * @param message the message to broadcast 480 * @param sessionId the OpenCms session uuid target (receiver) of the broadcast 481 * @param repeat repeat this message 482 * @param mode the content mode to use 483 */ 484 public void sendBroadcast(CmsObject cms, String message, String sessionId, boolean repeat, ContentMode mode) { 485 486 if (CmsStringUtil.isEmptyOrWhitespaceOnly(message)) { 487 // don't broadcast empty messages 488 return; 489 } 490 // send the broadcast only to the selected session 491 CmsSessionInfo sessionInfo = m_sessionStorageProvider.get(new CmsUUID(sessionId)); 492 if (sessionInfo != null) { 493 // double check for concurrent modification 494 sessionInfo.getBroadcastQueue().add( 495 new CmsBroadcast(cms.getRequestContext().getCurrentUser(), message, repeat, mode)); 496 } 497 } 498 499 /** 500 * Sends a broadcast to the specified user session.<p> 501 * 502 * @param cms the OpenCms user context of the user sending the broadcast 503 * 504 * @param message the message to broadcast 505 * @param sessionId the OpenCms session uuid target (receiver) of the broadcast 506 * @param mode the content mode to use 507 */ 508 public void sendBroadcast(CmsObject cms, String message, String sessionId, ContentMode mode) { 509 510 sendBroadcast(cms, message, sessionId, false, mode); 511 } 512 513 /** 514 * Sends a broadcast to all sessions of a given user.<p> 515 * 516 * The user sending the message may be a real user like 517 * <code>cms.getRequestContext().currentUser()</code> or 518 * <code>null</code> for a system message.<p> 519 * 520 * @param fromUser the user sending the broadcast 521 * @param message the message to broadcast 522 * @param toUser the target (receiver) of the broadcast 523 */ 524 @Deprecated 525 public void sendBroadcast(CmsUser fromUser, String message, CmsUser toUser) { 526 527 sendBroadcast(fromUser, message, toUser, ContentMode.plain); 528 529 } 530 531 /** 532 * Sends a broadcast to all sessions of a given user.<p> 533 * 534 * The user sending the message may be a real user like 535 * <code>cms.getRequestContext().currentUser()</code> or 536 * <code>null</code> for a system message.<p> 537 * 538 * @param fromUser the user sending the broadcast 539 * @param message the message to broadcast 540 * @param toUser the target (receiver) of the broadcast 541 * @param mode the content mode to use 542 */ 543 public void sendBroadcast(CmsUser fromUser, String message, CmsUser toUser, CmsBroadcast.ContentMode mode) { 544 545 if (CmsStringUtil.isEmptyOrWhitespaceOnly(message)) { 546 // don't broadcast empty messages 547 return; 548 } 549 // create the broadcast 550 CmsBroadcast broadcast = new CmsBroadcast(fromUser, message, mode); 551 List<CmsSessionInfo> userSessions = getSessionInfos(toUser.getId()); 552 Iterator<CmsSessionInfo> i = userSessions.iterator(); 553 // send the broadcast to all sessions of the selected user 554 while (i.hasNext()) { 555 CmsSessionInfo sessionInfo = i.next(); 556 if (m_sessionStorageProvider.get(sessionInfo.getSessionId()) != null) { 557 // double check for concurrent modification 558 sessionInfo.getBroadcastQueue().add(broadcast); 559 } 560 } 561 562 } 563 564 /** 565 * Switches the current user to the given user. The session info is rebuild as if the given user 566 * performs a login at the workplace. 567 * 568 * @param cms the current CmsObject 569 * @param req the current request 570 * @param user the user to switch to 571 * 572 * @return the direct edit target if available 573 * 574 * @throws CmsException if something goes wrong 575 */ 576 public String switchUser(CmsObject cms, HttpServletRequest req, CmsUser user) throws CmsException { 577 578 return switchUserFromSession(cms, req, user, null); 579 } 580 581 /** 582 * Switches the current user to the given user. The session info is rebuild as if the given user 583 * performs a login at the workplace. 584 * 585 * @param cms the current CmsObject 586 * @param req the current request 587 * @param user the user to switch to 588 * @param sessionInfo to switch to a currently logged in user using the same session state 589 * 590 * @return the direct edit target if available 591 * 592 * @throws CmsException if something goes wrong 593 */ 594 public String switchUserFromSession(CmsObject cms, HttpServletRequest req, CmsUser user, CmsSessionInfo sessionInfo) 595 throws CmsException { 596 597 // only user with root administrator role are allowed to switch the user 598 OpenCms.getRoleManager().checkRole(cms, CmsRole.ADMINISTRATOR.forOrgUnit(user.getOuFqn())); 599 CmsSessionInfo info = getSessionInfo(req); 600 HttpSession session = req.getSession(false); 601 if ((info == null) || (session == null)) { 602 throw new CmsException(Messages.get().container(Messages.ERR_NO_SESSIONINFO_SESSION_0)); 603 } 604 605 if (!OpenCms.getRoleManager().hasRole(cms, user.getName(), CmsRole.ELEMENT_AUTHOR)) { 606 throw new CmsSecurityException(Messages.get().container(Messages.ERR_NO_WORKPLACE_PERMISSIONS_0)); 607 } 608 String oldUser = cms.getRequestContext().getCurrentUser().getName(); 609 610 // get the user settings for the given user and set the start project and the site root 611 CmsUserSettings settings = new CmsUserSettings(user); 612 String ouFqn = user.getOuFqn(); 613 614 CmsProject userProject; 615 String userSiteRoot; 616 617 if (sessionInfo == null) { 618 userProject = cms.readProject( 619 ouFqn + OpenCms.getWorkplaceManager().getDefaultUserSettings().getStartProject()); 620 try { 621 userProject = cms.readProject(settings.getStartProject()); 622 } catch (Exception e) { 623 // ignore, use default 624 } 625 CmsObject userCms = OpenCms.initCmsObject(m_adminCms, new CmsContextInfo(user.getName())); 626 627 userSiteRoot = CmsWorkplace.getStartSiteRoot(userCms, settings); 628 } else { 629 userProject = cms.readProject(sessionInfo.getProject()); 630 userSiteRoot = sessionInfo.getSiteRoot(); 631 } 632 633 CmsRequestContext context = new CmsRequestContext( 634 user, 635 userProject, 636 null, 637 cms.getRequestContext().getRequestMatcher(), 638 userSiteRoot, 639 cms.getRequestContext().isSecureRequest(), 640 null, 641 null, 642 null, 643 0, 644 null, 645 null, 646 ouFqn, 647 false); 648 // delete the stored workplace settings, so the session has to receive them again 649 session.removeAttribute(CmsWorkplaceManager.SESSION_WORKPLACE_SETTINGS); 650 651 // create a new CmsSessionInfo and store it inside the session map 652 CmsSessionInfo newInfo = new CmsSessionInfo(context, info.getSessionId(), info.getMaxInactiveInterval()); 653 addSessionInfo(newInfo); 654 // set the site root, project and ou fqn to current cms context 655 cms.getRequestContext().setSiteRoot(userSiteRoot); 656 cms.getRequestContext().setCurrentProject(userProject); 657 cms.getRequestContext().setOuFqn(user.getOuFqn()); 658 USERSWITCH.info("User '" + oldUser + "' switched to user '" + user.getName() + "'"); 659 CmsUserLog.logSwitchUser(cms, user.getName()); 660 String directEditTarget = CmsLoginHelper.getDirectEditPath(cms, new CmsUserSettings(user), false); 661 return directEditTarget != null 662 ? OpenCms.getLinkManager().substituteLink(cms, directEditTarget, userSiteRoot) 663 : null; 664 } 665 666 /** 667 * @see java.lang.Object#toString() 668 */ 669 @Override 670 public String toString() { 671 672 StringBuffer output = new StringBuffer(); 673 Iterator<CmsSessionInfo> i = m_sessionStorageProvider.getAll().iterator(); 674 output.append("[CmsSessions]:\n"); 675 while (i.hasNext()) { 676 CmsSessionInfo sessionInfo = i.next(); 677 output.append(sessionInfo.getSessionId().toString()); 678 output.append(" : "); 679 output.append(sessionInfo.getUserId().toString()); 680 output.append('\n'); 681 } 682 return output.toString(); 683 } 684 685 /** 686 * Updates the the OpenCms session data used for quick authentication of users.<p> 687 * 688 * This is required if the user data (current group or project) was changed in 689 * the requested document.<p> 690 * 691 * The user data is only updated if the user was authenticated to the system. 692 * 693 * @param cms the current OpenCms user context 694 * @param req the current request 695 */ 696 public void updateSessionInfo(CmsObject cms, HttpServletRequest req) { 697 698 updateSessionInfo(cms, req, false); 699 } 700 701 /** 702 * Updates the the OpenCms session data used for quick authentication of users.<p> 703 * 704 * This is required if the user data (current group or project) was changed in 705 * the requested document.<p> 706 * 707 * The user data is only updated if the user was authenticated to the system. 708 * 709 * @param cms the current OpenCms user context 710 * @param req the current request 711 * @param isHeartBeatRequest in case of heart beat requests 712 */ 713 public void updateSessionInfo(CmsObject cms, HttpServletRequest req, boolean isHeartBeatRequest) { 714 715 if (!cms.getRequestContext().isUpdateSessionEnabled()) { 716 // this request must not update the user session info 717 // this is true for long running "thread" requests, e.g. during project publish 718 return; 719 } 720 721 if (cms.getRequestContext().getUri().equals(CmsToolManager.VIEW_JSPPAGE_LOCATION)) { 722 // this request must not update the user session info 723 // if not the switch user feature would not work 724 return; 725 } 726 727 if (!cms.getRequestContext().getCurrentUser().isGuestUser()) { 728 // Guest user requests don't need to update the OpenCms user session information 729 730 // get the session info object for the user 731 CmsSessionInfo sessionInfo = getSessionInfo(req); 732 if (sessionInfo != null) { 733 // update the users session information 734 sessionInfo.update(cms.getRequestContext(), isHeartBeatRequest); 735 addSessionInfo(sessionInfo); 736 } else { 737 HttpSession session = req.getSession(false); 738 // only create session info if a session is already available 739 if (session != null) { 740 // create a new session info for the user 741 sessionInfo = new CmsSessionInfo( 742 cms.getRequestContext(), 743 new CmsUUID(), 744 session.getMaxInactiveInterval()); 745 // append the session info to the http session 746 session.setAttribute(CmsSessionInfo.ATTRIBUTE_SESSION_ID, sessionInfo.getSessionId().clone()); 747 // store a client token to prevent session hijacking 748 session.setAttribute(CLIENT_TOKEN, generateClientToken(req)); 749 // update the session info user data 750 addSessionInfo(sessionInfo); 751 } 752 } 753 } 754 } 755 756 /** 757 * Updates the the OpenCms session data used for quick authentication of users.<p> 758 * 759 * This is required if the user data (current group or project) was changed in 760 * the requested document.<p> 761 * 762 * The user data is only updated if the user was authenticated to the system. 763 * 764 * @param cms the current OpenCms user context 765 * @param session the current session 766 */ 767 public void updateSessionInfo(CmsObject cms, HttpSession session) { 768 769 if (session == null) { 770 return; 771 } 772 773 if (!cms.getRequestContext().isUpdateSessionEnabled()) { 774 // this request must not update the user session info 775 // this is true for long running "thread" requests, e.g. during project publish 776 return; 777 } 778 779 if (cms.getRequestContext().getUri().equals(CmsToolManager.VIEW_JSPPAGE_LOCATION)) { 780 // this request must not update the user session info 781 // if not the switch user feature would not work 782 return; 783 } 784 785 if (!cms.getRequestContext().getCurrentUser().isGuestUser()) { 786 // Guest user requests don't need to update the OpenCms user session information 787 788 // get the session info object for the user 789 CmsSessionInfo sessionInfo = getSessionInfo(session); 790 if (sessionInfo != null) { 791 // update the users session information 792 sessionInfo.update(cms.getRequestContext()); 793 addSessionInfo(sessionInfo); 794 } else { 795 sessionInfo = new CmsSessionInfo( 796 cms.getRequestContext(), 797 new CmsUUID(), 798 session.getMaxInactiveInterval()); 799 // append the session info to the http session 800 session.setAttribute(CmsSessionInfo.ATTRIBUTE_SESSION_ID, sessionInfo.getSessionId().clone()); 801 // update the session info user data 802 addSessionInfo(sessionInfo); 803 } 804 } 805 } 806 807 /** 808 * Updates all session info objects, so that invalid projects 809 * are replaced by the Online project.<p> 810 * 811 * @param cms the cms context 812 */ 813 public void updateSessionInfos(CmsObject cms) { 814 815 // get all sessions 816 List<CmsSessionInfo> userSessions = getSessionInfos(); 817 Iterator<CmsSessionInfo> i = userSessions.iterator(); 818 while (i.hasNext()) { 819 CmsSessionInfo sessionInfo = i.next(); 820 // check is the project stored in this session is not existing anymore 821 // if so, set it to the online project 822 CmsUUID projectId = sessionInfo.getProject(); 823 try { 824 cms.readProject(projectId); 825 } catch (CmsException e) { 826 // the project does not longer exist, update the project information with the online project 827 sessionInfo.setProject(CmsProject.ONLINE_PROJECT_ID); 828 addSessionInfo(sessionInfo); 829 } 830 } 831 } 832 833 /** 834 * Adds a new session info into the session storage.<p> 835 * 836 * @param sessionInfo the session info to store for the id 837 */ 838 protected void addSessionInfo(CmsSessionInfo sessionInfo) { 839 840 if (getUserSessionMode() == UserSessionMode.standard) { 841 m_sessionStorageProvider.put(sessionInfo); 842 } else if (getUserSessionMode() == UserSessionMode.single) { 843 CmsUUID userId = sessionInfo.getUserId(); 844 List<CmsSessionInfo> infos = getSessionInfos(userId); 845 if (infos.isEmpty() 846 || ((infos.size() == 1) && infos.get(0).getSessionId().equals(sessionInfo.getSessionId()))) { 847 m_sessionStorageProvider.put(sessionInfo); 848 } else { 849 throw new RuntimeException("Can't create another session for the same user."); 850 } 851 } 852 } 853 854 /** 855 * Returns the UUID representation for the given session id String.<p> 856 * 857 * @param sessionId the session id String to return the UUID representation for 858 * 859 * @return the UUID representation for the given session id String 860 */ 861 protected CmsUUID getSessionUUID(String sessionId) { 862 863 return new CmsUUID(sessionId); 864 } 865 866 /** 867 * Sets the storage provider.<p> 868 * 869 * @param sessionStorageProvider the storage provider implementation 870 * @param adminCms 871 */ 872 protected void initialize(I_CmsSessionStorageProvider sessionStorageProvider, CmsObject adminCms) { 873 874 m_sessionStorageProvider = sessionStorageProvider; 875 m_sessionStorageProvider.initialize(); 876 m_adminCms = adminCms; 877 } 878 879 /** 880 * Called by the {@link OpenCmsListener} when a http session is created.<p> 881 * 882 * @param event the http session event 883 * 884 * @see javax.servlet.http.HttpSessionListener#sessionCreated(javax.servlet.http.HttpSessionEvent) 885 * @see OpenCmsListener#sessionCreated(HttpSessionEvent) 886 */ 887 protected void sessionCreated(HttpSessionEvent event) { 888 889 HttpServletRequest request = OpenCmsServlet.currentRequestStack.top(); 890 String tid = "[" + Thread.currentThread().getId() + "] "; 891 synchronized (m_lockSessionCount) { 892 m_sessionCountCurrent = (m_sessionCountCurrent <= 0) ? 1 : (m_sessionCountCurrent + 1); 893 m_sessionCountTotal++; 894 if (LOG.isInfoEnabled()) { 895 LOG.info( 896 tid 897 + Messages.get().getBundle().key( 898 Messages.LOG_SESSION_CREATED_2, 899 Integer.valueOf(m_sessionCountTotal), 900 Integer.valueOf(m_sessionCountCurrent))); 901 } 902 } 903 904 if (LOG.isDebugEnabled()) { 905 LOG.debug(tid + Messages.get().getBundle().key(Messages.LOG_SESSION_CREATED_1, event.getSession().getId())); 906 if (request != null) { 907 LOG.debug(tid + "Session created in request: " + request.getRequestURL()); 908 } 909 StringWriter sw = new StringWriter(); 910 new Throwable("").printStackTrace(new PrintWriter(sw)); 911 String stackTrace = sw.toString(); 912 LOG.debug(tid + "Stack = \n" + stackTrace); 913 } 914 } 915 916 /** 917 * Called by the {@link OpenCmsListener} when a http session is destroyed.<p> 918 * 919 * @param event the http session event 920 * 921 * @see javax.servlet.http.HttpSessionListener#sessionDestroyed(javax.servlet.http.HttpSessionEvent) 922 * @see OpenCmsListener#sessionDestroyed(HttpSessionEvent) 923 */ 924 protected void sessionDestroyed(HttpSessionEvent event) { 925 926 synchronized (m_lockSessionCount) { 927 m_sessionCountCurrent = (m_sessionCountCurrent <= 0) ? 0 : (m_sessionCountCurrent - 1); 928 if (LOG.isInfoEnabled()) { 929 LOG.info( 930 Messages.get().getBundle().key( 931 Messages.LOG_SESSION_DESTROYED_2, 932 Integer.valueOf(m_sessionCountTotal), 933 Integer.valueOf(m_sessionCountCurrent))); 934 } 935 } 936 937 CmsSessionInfo sessionInfo = getSessionInfo(event.getSession()); 938 CmsUUID userId = null; 939 if (sessionInfo != null) { 940 userId = sessionInfo.getUserId(); 941 m_sessionStorageProvider.remove(sessionInfo.getSessionId()); 942 } 943 944 if ((userId != null) && (getSessionInfos(userId).size() == 0)) { 945 // remove the temporary locks of this user from memory 946 OpenCmsCore.getInstance().getLockManager().removeTempLocks(userId); 947 } 948 949 HttpSession session = event.getSession(); 950 Enumeration<?> attrNames = session.getAttributeNames(); 951 while (attrNames.hasMoreElements()) { 952 String attrName = (String)attrNames.nextElement(); 953 Object attribute = session.getAttribute(attrName); 954 if (attribute instanceof I_CmsSessionDestroyHandler) { 955 try { 956 ((I_CmsSessionDestroyHandler)attribute).onSessionDestroyed(); 957 } catch (Exception e) { 958 LOG.error(e.getLocalizedMessage(), e); 959 } 960 } 961 } 962 963 if (LOG.isDebugEnabled()) { 964 LOG.debug(Messages.get().getBundle().key(Messages.LOG_SESSION_DESTROYED_1, event.getSession().getId())); 965 } 966 } 967 968 /** 969 * Sets the user session mode.<p> 970 * 971 * @param userSessionMode the user session mode 972 */ 973 protected void setUserSessionMode(UserSessionMode userSessionMode) { 974 975 m_userSessionMode = userSessionMode; 976 } 977 978 /** 979 * Removes all stored session info objects.<p> 980 * 981 * @throws Exception if something goes wrong 982 */ 983 protected void shutdown() throws Exception { 984 985 if (m_sessionStorageProvider != null) { 986 m_sessionStorageProvider.shutdown(); 987 } 988 } 989 990 /** 991 * Validates the sessions stored in this manager and removes 992 * any sessions that have become invalidated.<p> 993 */ 994 protected void validateSessionInfos() { 995 996 // since this method could be called from another thread 997 // we have to prevent access before initialization 998 if (m_sessionStorageProvider == null) { 999 return; 1000 } 1001 m_sessionStorageProvider.validate(); 1002 } 1003 1004 /** 1005 * Generates a token based on hashed client ip and user agent.<p> 1006 * Used to prevent session hijacking.<p> 1007 * 1008 * @param request the current request 1009 * 1010 * @return the client token 1011 */ 1012 private String generateClientToken(HttpServletRequest request) { 1013 1014 String ip = request.getHeader(HEADER_TRUE_CLIENT_IP); 1015 if (CmsStringUtil.isEmptyOrWhitespaceOnly(ip)) { 1016 ip = request.getHeader(HEADER_X_FORWARDED_FOR); 1017 if ((ip != null) && ip.contains(",")) { 1018 ip = ip.split(",")[0]; 1019 } 1020 } 1021 if ((ip == null) || CmsStringUtil.isEmptyOrWhitespaceOnly(ip)) { 1022 ip = request.getRemoteAddr(); 1023 } 1024 return String.valueOf(ip.hashCode()); 1025 } 1026}