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.db; 029 030import org.opencms.file.CmsObject; 031import org.opencms.file.CmsUser; 032import org.opencms.main.CmsLog; 033import org.opencms.main.OpenCms; 034import org.opencms.security.CmsAuthentificationException; 035import org.opencms.security.CmsRole; 036import org.opencms.security.CmsRoleViolationException; 037import org.opencms.security.CmsUserDisabledException; 038import org.opencms.security.Messages; 039import org.opencms.util.CmsStringUtil; 040 041import java.util.Date; 042import java.util.HashSet; 043import java.util.Hashtable; 044import java.util.Map; 045import java.util.Set; 046 047import org.apache.commons.logging.Log; 048 049/** 050 * Provides functions used to check the validity of a user login.<p> 051 * 052 * Stores invalid login attempts and disables a user account temporarily in case 053 * the configured threshold of invalid logins is reached.<p> 054 * 055 * The invalid login attempt storage operates on a combination of user name, login remote IP address and 056 * user type. This means that a user can be disabled for one remote IP, but still be enabled for 057 * another remote IP.<p> 058 * 059 * Also allows to temporarily disallow logins (for example in case of maintenance work on the system).<p> 060 * 061 * @since 6.0.0 062 */ 063public class CmsLoginManager { 064 065 /** 066 * Contains the data stored for each user in the storage for invalid login attempts.<p> 067 */ 068 private class CmsUserData { 069 070 /** The start time this account was disabled. */ 071 private long m_disableTimeStart; 072 073 /** The count of the failed attempts. */ 074 private int m_invalidLoginCount; 075 076 /** 077 * Creates a new user data instance.<p> 078 */ 079 protected CmsUserData() { 080 081 // a new instance is creted only if there already was one failed attempt 082 m_invalidLoginCount = 1; 083 } 084 085 /** 086 * Returns the bad attempt count for this user.<p> 087 * 088 * @return the bad attempt count for this user 089 */ 090 protected Integer getInvalidLoginCount() { 091 092 return new Integer(m_invalidLoginCount); 093 } 094 095 /** 096 * Returns the date this disabled user is released again.<p> 097 * 098 * @return the date this disabled user is released again 099 */ 100 protected Date getReleaseDate() { 101 102 return new Date(m_disableTimeStart + m_disableMillis + 1); 103 } 104 105 /** 106 * Increases the bad attempt count, disables the data in case the 107 * configured threshold is reached.<p> 108 */ 109 protected void increaseInvalidLoginCount() { 110 111 m_invalidLoginCount++; 112 if (m_invalidLoginCount >= m_maxBadAttempts) { 113 // threshold for bad login attempts has been reached for this user 114 if (m_disableTimeStart == 0) { 115 // only disable in case this user has not already been disabled 116 m_disableTimeStart = System.currentTimeMillis(); 117 } 118 } 119 } 120 121 /** 122 * Returns <code>true</code> in case this user has been temporarily disabled.<p> 123 * 124 * @return <code>true</code> in case this user has been temporarily disabled 125 */ 126 protected boolean isDisabled() { 127 128 if (m_disableTimeStart > 0) { 129 // check if the disable time is already over 130 long currentTime = System.currentTimeMillis(); 131 if ((currentTime - m_disableTimeStart) > m_disableMillis) { 132 // disable time is over 133 m_disableTimeStart = 0; 134 } 135 } 136 return m_disableTimeStart > 0; 137 } 138 139 /** 140 * Reset disable time.<p> 141 */ 142 protected void reset() { 143 144 m_disableTimeStart = 0; 145 m_invalidLoginCount = 0; 146 } 147 } 148 149 /** Default token lifetime. */ 150 public static final long DEFAULT_TOKEN_LIFETIME = 3600 * 24 * 1000; 151 152 /** Default lock time if treshold for bad login attempts is reached. */ 153 public static final int DISABLE_MINUTES_DEFAULT = 15; 154 155 /** Default setting for the security option. */ 156 public static final boolean ENABLE_SECURITY_DEFAULT = false; 157 158 /** Separator used for storage keys. */ 159 public static final String KEY_SEPARATOR = "_"; 160 161 /** Default for bad login attempts. */ 162 public static final int MAX_BAD_ATTEMPTS_DEFAULT = 3; 163 164 /** The logger instance for this class. */ 165 private static final Log LOG = CmsLog.getLog(CmsLoginManager.class); 166 167 /**Map holding usernames and userdata for user which are currently locked.*/ 168 protected static Map<String, Set<CmsUserData>> TEMP_DISABLED_USER; 169 170 /** The milliseconds to disable an account if the threshold is reached. */ 171 protected int m_disableMillis; 172 173 /** The minutes to disable an account if the threshold is reached. */ 174 protected int m_disableMinutes; 175 176 /** The flag to determine if the security option ahould be enabled on the login dialog. */ 177 protected boolean m_enableSecurity; 178 179 /** The number of bad login attempts allowed before an account is temporarily disabled. */ 180 protected int m_maxBadAttempts; 181 182 /** The storage for the bad login attempts. */ 183 protected Map<String, CmsUserData> m_storage; 184 185 /** The token lifetime. */ 186 protected String m_tokenLifetimeStr; 187 188 /** The login message, setting this may also disable logins for non-Admin users. */ 189 private CmsLoginMessage m_loginMessage; 190 191 /** The before login message. */ 192 private CmsLoginMessage m_beforeLoginMessage; 193 194 /** Max inactivity time. */ 195 private String m_maxInactive; 196 197 /** Password change interval. */ 198 private String m_passwordChangeInterval; 199 200 /** User data check interval. */ 201 private String m_userDateCheckInterval; 202 203 /** 204 * Creates a new storage for invalid logins.<p> 205 * 206 * @param disableMinutes the minutes to disable an account if the threshold is reached 207 * @param maxBadAttempts the number of bad login attempts allowed before an account is temporarily disabled 208 * @param enableSecurity flag to determine if the security option should be enabled on the login dialog 209 * @param tokenLifetime the lifetime of authorization tokens, i.e. the time for which they are valid 210 * @param maxInactive maximum inactivity time 211 * @param passwordChangeInterval the password change interval 212 * @param userDataCheckInterval the user data check interval 213 */ 214 public CmsLoginManager( 215 int disableMinutes, 216 int maxBadAttempts, 217 boolean enableSecurity, 218 String tokenLifetime, 219 String maxInactive, 220 String passwordChangeInterval, 221 String userDataCheckInterval) { 222 223 m_maxBadAttempts = maxBadAttempts; 224 if (TEMP_DISABLED_USER == null) { 225 TEMP_DISABLED_USER = new Hashtable<String, Set<CmsUserData>>(); 226 } 227 if (m_maxBadAttempts >= 0) { 228 // otherwise the invalid login storage is sisabled 229 m_disableMinutes = disableMinutes; 230 m_disableMillis = disableMinutes * 60 * 1000; 231 m_storage = new Hashtable<String, CmsUserData>(); 232 233 } 234 m_enableSecurity = enableSecurity; 235 m_tokenLifetimeStr = tokenLifetime; 236 m_maxInactive = maxInactive; 237 m_passwordChangeInterval = passwordChangeInterval; 238 m_userDateCheckInterval = userDataCheckInterval; 239 } 240 241 /** 242 * Returns the key to use for looking up the user in the invalid login storage.<p> 243 * 244 * @param userName the name of the user 245 * @param remoteAddress the remore address (IP) from which the login attempt was made 246 * 247 * @return the key to use for looking up the user in the invalid login storage 248 */ 249 private static String createStorageKey(String userName, String remoteAddress) { 250 251 StringBuffer result = new StringBuffer(); 252 result.append(userName); 253 result.append(KEY_SEPARATOR); 254 result.append(remoteAddress); 255 return result.toString(); 256 } 257 258 /** 259 * Checks whether a user account can be locked because of inactivity. 260 * 261 * @param cms the CMS context 262 * @param user the user to check 263 * @return true if the user may be locked after being inactive for too long 264 */ 265 public boolean canLockBecauseOfInactivity(CmsObject cms, CmsUser user) { 266 267 return !user.isManaged() 268 && !user.isWebuser() 269 && !OpenCms.getDefaultUsers().isDefaultUser(user.getName()) 270 && !OpenCms.getRoleManager().hasRole(cms, user.getName(), CmsRole.ROOT_ADMIN); 271 } 272 273 /** 274 * Checks whether the given user has been inactive for longer than the configured limit.<p> 275 * 276 * If no max inactivity time is configured, always returns false. 277 * 278 * @param user the user to check 279 * @return true if the user has been inactive for longer than the configured limit 280 */ 281 public boolean checkInactive(CmsUser user) { 282 283 if (m_maxInactive == null) { 284 return false; 285 } 286 287 try { 288 long maxInactive = CmsStringUtil.parseDuration(m_maxInactive, Long.MAX_VALUE); 289 return (System.currentTimeMillis() - user.getLastlogin()) > maxInactive; 290 } catch (Exception e) { 291 LOG.warn(e.getLocalizedMessage(), e); 292 return false; 293 } 294 } 295 296 /** 297 * Checks if the threshold for the invalid logins has been reached for the given user.<p> 298 * 299 * In case the configured threshold is reached, an Exception is thrown.<p> 300 * 301 * @param userName the name of the user 302 * @param remoteAddress the remote address (IP) from which the login attempt was made 303 * 304 * @throws CmsAuthentificationException in case the threshold of invalid login attempts has been reached 305 */ 306 public void checkInvalidLogins(String userName, String remoteAddress) throws CmsAuthentificationException { 307 308 if (m_maxBadAttempts < 0) { 309 // invalid login storage is disabled 310 return; 311 } 312 313 String key = createStorageKey(userName, remoteAddress); 314 // look up the user in the storage 315 CmsUserData userData = m_storage.get(key); 316 if ((userData != null) && (userData.isDisabled())) { 317 // threshold of invalid logins is reached 318 Set<CmsUserData> data = TEMP_DISABLED_USER.get(userName); 319 if (data == null) { 320 data = new HashSet<CmsUserData>(); 321 } 322 data.add(userData); 323 TEMP_DISABLED_USER.put(userName, data); 324 throw new CmsUserDisabledException( 325 Messages.get().container( 326 Messages.ERR_LOGIN_FAILED_TEMP_DISABLED_4, 327 new Object[] { 328 userName, 329 remoteAddress, 330 userData.getReleaseDate(), 331 userData.getInvalidLoginCount()})); 332 } 333 if (TEMP_DISABLED_USER.containsKey(userName) & (userData != null)) { 334 //User war disabled, but time is over -> remove from list 335 if (TEMP_DISABLED_USER.get(userName).contains(userData)) { 336 TEMP_DISABLED_USER.get(userName).remove(userData); 337 if (TEMP_DISABLED_USER.get(userName).isEmpty()) { 338 TEMP_DISABLED_USER.remove(userName); 339 } 340 } 341 } 342 } 343 344 /** 345 * Checks if a login is currently allowed.<p> 346 * 347 * In case no logins are allowed, an Exception is thrown.<p> 348 * 349 * @throws CmsAuthentificationException in case no logins are allowed 350 */ 351 public void checkLoginAllowed() throws CmsAuthentificationException { 352 353 if ((m_loginMessage != null) && (m_loginMessage.isLoginCurrentlyForbidden())) { 354 // login message has been set and is active 355 throw new CmsAuthentificationException( 356 Messages.get().container(Messages.ERR_LOGIN_FAILED_WITH_MESSAGE_1, m_loginMessage.getMessage())); 357 } 358 } 359 360 /** 361 * Returns the current before login message that is displayed on the login form.<p> 362 * 363 * if <code>null</code> is returned, no login message has been currently set.<p> 364 * 365 * @return the current login message that is displayed if a user logs in 366 */ 367 public CmsLoginMessage getBeforeLoginMessage() { 368 369 return m_beforeLoginMessage; 370 } 371 372 /** 373 * Returns the minutes an account gets disabled after too many failed login attempts.<p> 374 * 375 * @return the minutes an account gets disabled after too many failed login attempts 376 */ 377 public int getDisableMinutes() { 378 379 return m_disableMinutes; 380 } 381 382 /** 383 * Returns the current login message that is displayed if a user logs in.<p> 384 * 385 * if <code>null</code> is returned, no login message has been currently set.<p> 386 * 387 * @return the current login message that is displayed if a user logs in 388 */ 389 public CmsLoginMessage getLoginMessage() { 390 391 return m_loginMessage; 392 } 393 394 /** 395 * Returns the number of bad login attempts allowed before an account is temporarily disabled.<p> 396 * 397 * @return the number of bad login attempts allowed before an account is temporarily disabled 398 */ 399 public int getMaxBadAttempts() { 400 401 return m_maxBadAttempts; 402 } 403 404 /** 405 * Gets the max inactivity time.<p> 406 * 407 * @return the max inactivity time 408 */ 409 public String getMaxInactive() { 410 411 return m_maxInactive; 412 } 413 414 /** 415 * Gets the password change interval.<p> 416 * 417 * @return the password change interval 418 */ 419 public long getPasswordChangeInterval() { 420 421 if (m_passwordChangeInterval == null) { 422 return Long.MAX_VALUE; 423 } else { 424 return CmsStringUtil.parseDuration(m_passwordChangeInterval, Long.MAX_VALUE); 425 } 426 } 427 428 /** 429 * Gets the raw password change interval string.<p> 430 * 431 * @return the configured string for the password change interval 432 */ 433 public String getPasswordChangeIntervalStr() { 434 435 return m_passwordChangeInterval; 436 } 437 438 /** 439 * Gets the authorization token lifetime in milliseconds.<p> 440 * 441 * @return the authorization token lifetime in milliseconds 442 */ 443 public long getTokenLifetime() { 444 445 if (m_tokenLifetimeStr == null) { 446 return DEFAULT_TOKEN_LIFETIME; 447 } 448 return CmsStringUtil.parseDuration(m_tokenLifetimeStr, DEFAULT_TOKEN_LIFETIME); 449 } 450 451 /** 452 * Gets the configured token lifetime as a string.<p> 453 * 454 * @return the configured token lifetime as a string 455 */ 456 public String getTokenLifetimeStr() { 457 458 return m_tokenLifetimeStr; 459 } 460 461 /** 462 * Gets the user data check interval.<p> 463 * 464 * @return the user data check interval 465 */ 466 public long getUserDataCheckInterval() { 467 468 if (m_userDateCheckInterval == null) { 469 return Long.MAX_VALUE; 470 } else { 471 return CmsStringUtil.parseDuration(m_userDateCheckInterval, Long.MAX_VALUE); 472 } 473 } 474 475 /** 476 * Gets the raw user data check interval string.<p> 477 * 478 * @return the configured string for the user data check interval 479 */ 480 public String getUserDataCheckIntervalStr() { 481 482 return m_userDateCheckInterval; 483 } 484 485 /** 486 * Returns if the security option ahould be enabled on the login dialog.<p> 487 * 488 * @return <code>true</code> if the security option ahould be enabled on the login dialog, otherwise <code>false</code> 489 */ 490 public boolean isEnableSecurity() { 491 492 return m_enableSecurity; 493 } 494 495 /** 496 * Checks if password has to be reset.<p> 497 * 498 * @param cms CmsObject 499 * @param user CmsUser 500 * @return true if password should be reset 501 */ 502 public boolean isPasswordReset(CmsObject cms, CmsUser user) { 503 504 if (user.isManaged() || user.isWebuser() || OpenCms.getDefaultUsers().isDefaultUser(user.getName())) { 505 return false; 506 } 507 if (user.getAdditionalInfo().get(CmsUserSettings.ADDITIONAL_INFO_PASSWORD_RESET) != null) { 508 return true; 509 } 510 return false; 511 } 512 513 /** 514 * Checks if a user is locked due to too many failed logins.<p> 515 * 516 * @param user the user to check 517 * 518 * @return true if the user is locked 519 */ 520 public boolean isUserLocked(CmsUser user) { 521 522 Set<String> keysForUser = getKeysForUser(user); 523 for (String key : keysForUser) { 524 CmsUserData data = m_storage.get(key); 525 if ((data != null) && data.isDisabled()) { 526 return true; 527 } 528 } 529 return false; 530 } 531 532 /** 533 * Checks if given user it temporarily locked.<p> 534 * 535 * @param username to check 536 * @return true if user is locked 537 */ 538 public boolean isUserTempDisabled(String username) { 539 540 Set<CmsUserData> data = TEMP_DISABLED_USER.get(username); 541 if (data == null) { 542 return false; 543 } 544 for (CmsUserData userData : data) { 545 if (!userData.isDisabled()) { 546 data.remove(userData); 547 } 548 } 549 if (data.size() > 0) { 550 TEMP_DISABLED_USER.put(username, data); 551 return true; 552 } else { 553 TEMP_DISABLED_USER.remove(username); 554 return false; 555 } 556 } 557 558 /** 559 * Removes the current login message.<p> 560 * 561 * This operation requires that the current user has role permissions of <code>{@link CmsRole#ROOT_ADMIN}</code>.<p> 562 * 563 * @param cms the current OpenCms user context 564 * 565 * @throws CmsRoleViolationException in case the current user does not have the required role permissions 566 */ 567 public void removeLoginMessage(CmsObject cms) throws CmsRoleViolationException { 568 569 OpenCms.getRoleManager().checkRole(cms, CmsRole.ROOT_ADMIN); 570 m_loginMessage = null; 571 } 572 573 /** 574 * Checks if a user is required to change his password now.<p> 575 * 576 * @param cms the current CMS context 577 * @param user the user to check 578 * 579 * @return true if the user should be asked to change his password 580 */ 581 public boolean requiresPasswordChange(CmsObject cms, CmsUser user) { 582 583 if (user.isManaged() 584 || user.isWebuser() 585 || OpenCms.getDefaultUsers().isDefaultUser(user.getName()) 586 || OpenCms.getRoleManager().hasRole(cms, user.getName(), CmsRole.ROOT_ADMIN)) { 587 return false; 588 } 589 String lastPasswordChangeStr = (String)user.getAdditionalInfo().get( 590 CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE); 591 if (lastPasswordChangeStr == null) { 592 return false; 593 } 594 long lastPasswordChange = Long.parseLong(lastPasswordChangeStr); 595 if ((System.currentTimeMillis() - lastPasswordChange) > getPasswordChangeInterval()) { 596 return true; 597 } 598 return false; 599 } 600 601 /** 602 * Checks if a user is required to change his password now.<p> 603 * 604 * @param cms the current CMS context 605 * @param user the user to check 606 * 607 * @return true if the user should be asked to change his password 608 */ 609 public boolean requiresUserDataCheck(CmsObject cms, CmsUser user) { 610 611 if (user.isManaged() 612 || user.isWebuser() 613 || OpenCms.getDefaultUsers().isDefaultUser(user.getName()) 614 || OpenCms.getRoleManager().hasRole(cms, user.getName(), CmsRole.ROOT_ADMIN)) { 615 return false; 616 } 617 String lastCheckStr = (String)user.getAdditionalInfo().get( 618 CmsUserSettings.ADDITIONAL_INFO_LAST_USER_DATA_CHECK); 619 if (lastCheckStr == null) { 620 return !CmsStringUtil.isEmptyOrWhitespaceOnly(getUserDataCheckIntervalStr()); 621 } 622 long lastCheck = Long.parseLong(lastCheckStr); 623 if ((System.currentTimeMillis() - lastCheck) > getUserDataCheckInterval()) { 624 return true; 625 } 626 return false; 627 } 628 629 /** 630 * Resets lock from user.<p> 631 * 632 * @param username to reset lock for 633 */ 634 public void resetUserTempDisable(String username) { 635 636 Set<CmsUserData> data = TEMP_DISABLED_USER.get(username); 637 if (data == null) { 638 return; 639 } 640 for (CmsUserData userData : data) { 641 userData.reset(); 642 } 643 TEMP_DISABLED_USER.remove(username); 644 } 645 646 /** 647 * Sets the before login message to display on the login form.<p> 648 * 649 * This operation requires that the current user has role permissions of <code>{@link CmsRole#ROOT_ADMIN}</code>.<p> 650 * 651 * @param cms the current OpenCms user context 652 * @param message the message to set 653 * 654 * @throws CmsRoleViolationException in case the current user does not have the required role permissions 655 */ 656 public void setBeforeLoginMessage(CmsObject cms, CmsLoginMessage message) throws CmsRoleViolationException { 657 658 if (OpenCms.getRunLevel() >= OpenCms.RUNLEVEL_3_SHELL_ACCESS) { 659 // during configuration phase no permission check id required 660 OpenCms.getRoleManager().checkRole(cms, CmsRole.ROOT_ADMIN); 661 } 662 m_beforeLoginMessage = message; 663 664 if (m_beforeLoginMessage != null) { 665 m_beforeLoginMessage.setFrozen(); 666 } 667 } 668 669 /** 670 * Sets the login message to display if a user logs in.<p> 671 * 672 * This operation requires that the current user has role permissions of <code>{@link CmsRole#ROOT_ADMIN}</code>.<p> 673 * 674 * @param cms the current OpenCms user context 675 * @param message the message to set 676 * 677 * @throws CmsRoleViolationException in case the current user does not have the required role permissions 678 */ 679 public void setLoginMessage(CmsObject cms, CmsLoginMessage message) throws CmsRoleViolationException { 680 681 if (OpenCms.getRunLevel() >= OpenCms.RUNLEVEL_3_SHELL_ACCESS) { 682 // during configuration phase no permission check id required 683 OpenCms.getRoleManager().checkRole(cms, CmsRole.ROOT_ADMIN); 684 } 685 m_loginMessage = message; 686 if (m_loginMessage != null) { 687 m_loginMessage.setFrozen(); 688 } 689 } 690 691 /** 692 * Unlocks a user who has exceeded his number of failed login attempts so that he can try to log in again.<p> 693 * This requires the "account manager" role. 694 * 695 * @param cms the current CMS context 696 * @param user the user to unlock 697 * 698 * @throws CmsRoleViolationException if the permission check fails 699 */ 700 public void unlockUser(CmsObject cms, CmsUser user) throws CmsRoleViolationException { 701 702 OpenCms.getRoleManager().checkRole(cms, CmsRole.ACCOUNT_MANAGER.forOrgUnit(cms.getRequestContext().getOuFqn())); 703 Set<String> keysToRemove = getKeysForUser(user); 704 for (String keyToRemove : keysToRemove) { 705 m_storage.remove(keyToRemove); 706 } 707 } 708 709 /** 710 * Adds an invalid attempt to login for the given user / IP to the storage.<p> 711 * 712 * In case the configured threshold is reached, the user is disabled for the configured time.<p> 713 * 714 * @param userName the name of the user 715 * @param remoteAddress the remore address (IP) from which the login attempt was made 716 */ 717 protected void addInvalidLogin(String userName, String remoteAddress) { 718 719 if (m_maxBadAttempts < 0) { 720 // invalid login storage is disabled 721 return; 722 } 723 724 String key = createStorageKey(userName, remoteAddress); 725 // look up the user in the storage 726 CmsUserData userData = m_storage.get(key); 727 if (userData != null) { 728 // user data already contained in storage 729 userData.increaseInvalidLoginCount(); 730 } else { 731 // create an new data object for this user 732 userData = new CmsUserData(); 733 m_storage.put(key, userData); 734 } 735 } 736 737 /** 738 * Removes all invalid attempts to login for the given user / IP.<p> 739 * 740 * @param userName the name of the user 741 * @param remoteAddress the remore address (IP) from which the login attempt was made 742 */ 743 protected void removeInvalidLogins(String userName, String remoteAddress) { 744 745 if (m_maxBadAttempts < 0) { 746 // invalid login storage is disabled 747 return; 748 } 749 750 String key = createStorageKey(userName, remoteAddress); 751 // just remove the user from the storage 752 m_storage.remove(key); 753 } 754 755 /** 756 * Helper method to get all the storage keys that match a user's name.<p> 757 * 758 * @param user the user for which to get the storage keys 759 * 760 * @return the set of storage keys 761 */ 762 private Set<String> getKeysForUser(CmsUser user) { 763 764 Set<String> keysToRemove = new HashSet<String>(); 765 for (Map.Entry<String, CmsUserData> entry : m_storage.entrySet()) { 766 String key = entry.getKey(); 767 int separatorPos = key.lastIndexOf(KEY_SEPARATOR); 768 String prefix = key.substring(0, separatorPos); 769 if (user.getName().equals(prefix)) { 770 keysToRemove.add(key); 771 } 772 } 773 return keysToRemove; 774 } 775}