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 Integer.valueOf(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 /**Map holding usernames and userdata for user which are currently locked.*/ 165 protected static Map<String, Set<CmsUserData>> TEMP_DISABLED_USER; 166 167 /** The logger instance for this class. */ 168 private static final Log LOG = CmsLog.getLog(CmsLoginManager.class); 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 before login message. */ 189 private CmsLoginMessage m_beforeLoginMessage; 190 191 /** The login message, setting this may also disable logins for non-Admin users. */ 192 private CmsLoginMessage m_loginMessage; 193 194 /** The logout URI. */ 195 private String m_logoutUri; 196 197 /** Max inactivity time. */ 198 private String m_maxInactive; 199 200 /** Password change interval. */ 201 private String m_passwordChangeInterval; 202 203 /** Option which determines whether the login dialog should require an organizational unit. */ 204 private boolean m_requireOrgUnit; 205 206 /** User data check interval. */ 207 private String m_userDateCheckInterval; 208 209 /** 210 * Creates a new storage for invalid logins.<p> 211 * 212 * @param disableMinutes the minutes to disable an account if the threshold is reached 213 * @param maxBadAttempts the number of bad login attempts allowed before an account is temporarily disabled 214 * @param enableSecurity flag to determine if the security option should be enabled on the login dialog 215 * @param tokenLifetime the lifetime of authorization tokens, i.e. the time for which they are valid 216 * @param maxInactive maximum inactivity time 217 * @param passwordChangeInterval the password change interval 218 * @param userDataCheckInterval the user data check interval 219 * @param requireOrgUnit if true, should require organizational unit selection on login 220 * @param logoutUri the alternative logout handler URI 221 */ 222 public CmsLoginManager( 223 int disableMinutes, 224 int maxBadAttempts, 225 boolean enableSecurity, 226 String tokenLifetime, 227 String maxInactive, 228 String passwordChangeInterval, 229 String userDataCheckInterval, 230 boolean requireOrgUnit, 231 String logoutUri) { 232 233 m_maxBadAttempts = maxBadAttempts; 234 if (TEMP_DISABLED_USER == null) { 235 TEMP_DISABLED_USER = new Hashtable<String, Set<CmsUserData>>(); 236 } 237 if (m_maxBadAttempts >= 0) { 238 // otherwise the invalid login storage is sisabled 239 m_disableMinutes = disableMinutes; 240 m_disableMillis = disableMinutes * 60 * 1000; 241 m_storage = new Hashtable<String, CmsUserData>(); 242 243 } 244 m_enableSecurity = enableSecurity; 245 m_tokenLifetimeStr = tokenLifetime; 246 m_maxInactive = maxInactive; 247 m_passwordChangeInterval = passwordChangeInterval; 248 m_userDateCheckInterval = userDataCheckInterval; 249 m_requireOrgUnit = requireOrgUnit; 250 m_logoutUri = logoutUri; 251 } 252 253 /** 254 * Returns the key to use for looking up the user in the invalid login storage.<p> 255 * 256 * @param userName the name of the user 257 * @param remoteAddress the remore address (IP) from which the login attempt was made 258 * 259 * @return the key to use for looking up the user in the invalid login storage 260 */ 261 private static String createStorageKey(String userName, String remoteAddress) { 262 263 StringBuffer result = new StringBuffer(); 264 result.append(userName); 265 result.append(KEY_SEPARATOR); 266 result.append(remoteAddress); 267 return result.toString(); 268 } 269 270 /** 271 * Checks whether a user account can be locked because of inactivity. 272 * 273 * @param cms the CMS context 274 * @param user the user to check 275 * @return true if the user may be locked after being inactive for too long 276 */ 277 public boolean canLockBecauseOfInactivity(CmsObject cms, CmsUser user) { 278 279 return !user.isManaged() 280 && !user.isWebuser() 281 && !OpenCms.getDefaultUsers().isDefaultUser(user.getName()) 282 && !OpenCms.getRoleManager().hasRole(cms, user.getName(), CmsRole.ROOT_ADMIN); 283 } 284 285 /** 286 * Checks whether the given user has been inactive for longer than the configured limit.<p> 287 * 288 * If no max inactivity time is configured, always returns false. 289 * 290 * @param user the user to check 291 * @return true if the user has been inactive for longer than the configured limit 292 */ 293 public boolean checkInactive(CmsUser user) { 294 295 if (m_maxInactive == null) { 296 return false; 297 } 298 try { 299 if (user.getLastlogin() == 0) { 300 return false; 301 } 302 long maxInactive = CmsStringUtil.parseDuration(m_maxInactive, Long.MAX_VALUE); 303 return (System.currentTimeMillis() - user.getLastlogin()) > maxInactive; 304 } catch (Exception e) { 305 LOG.warn(e.getLocalizedMessage(), e); 306 return false; 307 } 308 } 309 310 /** 311 * Checks if the threshold for the invalid logins has been reached for the given user.<p> 312 * 313 * In case the configured threshold is reached, an Exception is thrown.<p> 314 * 315 * @param userName the name of the user 316 * @param remoteAddress the remote address (IP) from which the login attempt was made 317 * 318 * @throws CmsAuthentificationException in case the threshold of invalid login attempts has been reached 319 */ 320 public void checkInvalidLogins(String userName, String remoteAddress) throws CmsAuthentificationException { 321 322 if (m_maxBadAttempts < 0) { 323 // invalid login storage is disabled 324 return; 325 } 326 327 String key = createStorageKey(userName, remoteAddress); 328 // look up the user in the storage 329 CmsUserData userData = m_storage.get(key); 330 if ((userData != null) && (userData.isDisabled())) { 331 // threshold of invalid logins is reached 332 Set<CmsUserData> data = TEMP_DISABLED_USER.get(userName); 333 if (data == null) { 334 data = new HashSet<CmsUserData>(); 335 } 336 data.add(userData); 337 TEMP_DISABLED_USER.put(userName, data); 338 throw new CmsUserDisabledException( 339 Messages.get().container( 340 Messages.ERR_LOGIN_FAILED_TEMP_DISABLED_4, 341 new Object[] { 342 userName, 343 remoteAddress, 344 userData.getReleaseDate(), 345 userData.getInvalidLoginCount()})); 346 } 347 if (TEMP_DISABLED_USER.containsKey(userName) & (userData != null)) { 348 //User war disabled, but time is over -> remove from list 349 if (TEMP_DISABLED_USER.get(userName).contains(userData)) { 350 TEMP_DISABLED_USER.get(userName).remove(userData); 351 if (TEMP_DISABLED_USER.get(userName).isEmpty()) { 352 TEMP_DISABLED_USER.remove(userName); 353 } 354 } 355 } 356 } 357 358 /** 359 * Checks if a login is currently allowed.<p> 360 * 361 * In case no logins are allowed, an Exception is thrown.<p> 362 * 363 * @throws CmsAuthentificationException in case no logins are allowed 364 */ 365 public void checkLoginAllowed() throws CmsAuthentificationException { 366 367 if ((m_loginMessage != null) && (m_loginMessage.isLoginCurrentlyForbidden())) { 368 // login message has been set and is active 369 throw new CmsAuthentificationException( 370 Messages.get().container(Messages.ERR_LOGIN_FAILED_WITH_MESSAGE_1, m_loginMessage.getMessage())); 371 } 372 } 373 374 /** 375 * Returns the current before login message that is displayed on the login form.<p> 376 * 377 * if <code>null</code> is returned, no login message has been currently set.<p> 378 * 379 * @return the current login message that is displayed if a user logs in 380 */ 381 public CmsLoginMessage getBeforeLoginMessage() { 382 383 return m_beforeLoginMessage; 384 } 385 386 /** 387 * Returns the minutes an account gets disabled after too many failed login attempts.<p> 388 * 389 * @return the minutes an account gets disabled after too many failed login attempts 390 */ 391 public int getDisableMinutes() { 392 393 return m_disableMinutes; 394 } 395 396 /** 397 * Returns the current login message that is displayed if a user logs in.<p> 398 * 399 * if <code>null</code> is returned, no login message has been currently set.<p> 400 * 401 * @return the current login message that is displayed if a user logs in 402 */ 403 public CmsLoginMessage getLoginMessage() { 404 405 return m_loginMessage; 406 } 407 408 /** 409 * Gets the logout URI.<p> 410 * 411 * If this is not null, users will be redirected to this JSP when logging out from the workplace 412 * or page editor. The JSP is responsible for invalidating the user's session. 413 * 414 * @return the logout URI 415 */ 416 public String getLogoutUri() { 417 418 return m_logoutUri; 419 } 420 421 /** 422 * Returns the number of bad login attempts allowed before an account is temporarily disabled.<p> 423 * 424 * @return the number of bad login attempts allowed before an account is temporarily disabled 425 */ 426 public int getMaxBadAttempts() { 427 428 return m_maxBadAttempts; 429 } 430 431 /** 432 * Gets the max inactivity time.<p> 433 * 434 * @return the max inactivity time 435 */ 436 public String getMaxInactive() { 437 438 return m_maxInactive; 439 } 440 441 /** 442 * Gets the password change interval.<p> 443 * 444 * @return the password change interval 445 */ 446 public long getPasswordChangeInterval() { 447 448 if (m_passwordChangeInterval == null) { 449 return Long.MAX_VALUE; 450 } else { 451 return CmsStringUtil.parseDuration(m_passwordChangeInterval, Long.MAX_VALUE); 452 } 453 } 454 455 /** 456 * Gets the raw password change interval string.<p> 457 * 458 * @return the configured string for the password change interval 459 */ 460 public String getPasswordChangeIntervalStr() { 461 462 return m_passwordChangeInterval; 463 } 464 465 /** 466 * Gets the authorization token lifetime in milliseconds.<p> 467 * 468 * @return the authorization token lifetime in milliseconds 469 */ 470 public long getTokenLifetime() { 471 472 if (m_tokenLifetimeStr == null) { 473 return DEFAULT_TOKEN_LIFETIME; 474 } 475 return CmsStringUtil.parseDuration(m_tokenLifetimeStr, DEFAULT_TOKEN_LIFETIME); 476 } 477 478 /** 479 * Gets the configured token lifetime as a string.<p> 480 * 481 * @return the configured token lifetime as a string 482 */ 483 public String getTokenLifetimeStr() { 484 485 return m_tokenLifetimeStr; 486 } 487 488 /** 489 * Gets the user data check interval.<p> 490 * 491 * @return the user data check interval 492 */ 493 public long getUserDataCheckInterval() { 494 495 if (m_userDateCheckInterval == null) { 496 return Long.MAX_VALUE; 497 } else { 498 return CmsStringUtil.parseDuration(m_userDateCheckInterval, Long.MAX_VALUE); 499 } 500 } 501 502 /** 503 * Gets the raw user data check interval string.<p> 504 * 505 * @return the configured string for the user data check interval 506 */ 507 public String getUserDataCheckIntervalStr() { 508 509 return m_userDateCheckInterval; 510 } 511 512 /** 513 * Returns if the security option ahould be enabled on the login dialog.<p> 514 * 515 * @return <code>true</code> if the security option ahould be enabled on the login dialog, otherwise <code>false</code> 516 */ 517 public boolean isEnableSecurity() { 518 519 return m_enableSecurity; 520 } 521 522 /** 523 * Checks if the user should be excluded from password reset. 524 * 525 * @param cms the CmsObject to use 526 * @param user the user to check 527 * @return true if the user should be excluded from password reset 528 */ 529 public boolean isExcludedFromPasswordReset(CmsObject cms, CmsUser user) { 530 531 return user.isManaged() || user.isWebuser() || OpenCms.getDefaultUsers().isDefaultUser(user.getName()); 532 } 533 534 /** 535 * Returns true if organizational unit selection should be required on login. 536 * 537 * @return true if org unit selection should be required 538 */ 539 public boolean isOrgUnitRequired() { 540 541 return m_requireOrgUnit; 542 } 543 544 /** 545 * Checks if password has to be reset.<p> 546 * 547 * @param cms CmsObject 548 * @param user CmsUser 549 * @return true if password should be reset 550 */ 551 public boolean isPasswordReset(CmsObject cms, CmsUser user) { 552 553 if (isExcludedFromPasswordReset(cms, user)) { 554 return false; 555 } 556 if (user.getAdditionalInfo().get(CmsUserSettings.ADDITIONAL_INFO_PASSWORD_RESET) != null) { 557 return true; 558 } 559 return false; 560 } 561 562 /** 563 * Checks if a user is locked due to too many failed logins.<p> 564 * 565 * @param user the user to check 566 * 567 * @return true if the user is locked 568 */ 569 public boolean isUserLocked(CmsUser user) { 570 571 Set<String> keysForUser = getKeysForUser(user); 572 for (String key : keysForUser) { 573 CmsUserData data = m_storage.get(key); 574 if ((data != null) && data.isDisabled()) { 575 return true; 576 } 577 } 578 return false; 579 } 580 581 /** 582 * Checks if given user it temporarily locked.<p> 583 * 584 * @param username to check 585 * @return true if user is locked 586 */ 587 public boolean isUserTempDisabled(String username) { 588 589 Set<CmsUserData> data = TEMP_DISABLED_USER.get(username); 590 if (data == null) { 591 return false; 592 } 593 for (CmsUserData userData : data) { 594 if (!userData.isDisabled()) { 595 data.remove(userData); 596 } 597 } 598 if (data.size() > 0) { 599 TEMP_DISABLED_USER.put(username, data); 600 return true; 601 } else { 602 TEMP_DISABLED_USER.remove(username); 603 return false; 604 } 605 } 606 607 /** 608 * Removes the current login message.<p> 609 * 610 * This operation requires that the current user has role permissions of <code>{@link CmsRole#ROOT_ADMIN}</code>.<p> 611 * 612 * @param cms the current OpenCms user context 613 * 614 * @throws CmsRoleViolationException in case the current user does not have the required role permissions 615 */ 616 public void removeLoginMessage(CmsObject cms) throws CmsRoleViolationException { 617 618 OpenCms.getRoleManager().checkRole(cms, CmsRole.ROOT_ADMIN); 619 m_loginMessage = null; 620 } 621 622 /** 623 * Checks if a user is required to change his password now.<p> 624 * 625 * @param cms the current CMS context 626 * @param user the user to check 627 * 628 * @return true if the user should be asked to change his password 629 */ 630 public boolean requiresPasswordChange(CmsObject cms, CmsUser user) { 631 632 if (user.isManaged() 633 || user.isWebuser() 634 || OpenCms.getDefaultUsers().isDefaultUser(user.getName()) 635 || OpenCms.getRoleManager().hasRole(cms, user.getName(), CmsRole.ROOT_ADMIN)) { 636 return false; 637 } 638 String lastPasswordChangeStr = (String)user.getAdditionalInfo().get( 639 CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE); 640 if (lastPasswordChangeStr == null) { 641 return false; 642 } 643 long lastPasswordChange = Long.parseLong(lastPasswordChangeStr); 644 if ((System.currentTimeMillis() - lastPasswordChange) > getPasswordChangeInterval()) { 645 return true; 646 } 647 return false; 648 } 649 650 /** 651 * Checks if a user is required to change his password now.<p> 652 * 653 * @param cms the current CMS context 654 * @param user the user to check 655 * 656 * @return true if the user should be asked to change his password 657 */ 658 public boolean requiresUserDataCheck(CmsObject cms, CmsUser user) { 659 660 if (user.isManaged() 661 || user.isWebuser() 662 || OpenCms.getDefaultUsers().isDefaultUser(user.getName()) 663 || OpenCms.getRoleManager().hasRole(cms, user.getName(), CmsRole.ROOT_ADMIN)) { 664 return false; 665 } 666 667 String lastCheckStr = (String)user.getAdditionalInfo().get( 668 CmsUserSettings.ADDITIONAL_INFO_LAST_USER_DATA_CHECK); 669 if (lastCheckStr == null) { 670 return !CmsStringUtil.isEmptyOrWhitespaceOnly(getUserDataCheckIntervalStr()); 671 } 672 long lastCheck = Long.parseLong(lastCheckStr); 673 if ((System.currentTimeMillis() - lastCheck) > getUserDataCheckInterval()) { 674 return true; 675 } 676 return false; 677 } 678 679 /** 680 * Resets lock from user.<p> 681 * 682 * @param username to reset lock for 683 */ 684 public void resetUserTempDisable(String username) { 685 686 Set<CmsUserData> data = TEMP_DISABLED_USER.get(username); 687 if (data == null) { 688 return; 689 } 690 for (CmsUserData userData : data) { 691 userData.reset(); 692 } 693 TEMP_DISABLED_USER.remove(username); 694 } 695 696 /** 697 * Sets the before login message to display on the login form.<p> 698 * 699 * This operation requires that the current user has role permissions of <code>{@link CmsRole#ROOT_ADMIN}</code>.<p> 700 * 701 * @param cms the current OpenCms user context 702 * @param message the message to set 703 * 704 * @throws CmsRoleViolationException in case the current user does not have the required role permissions 705 */ 706 public void setBeforeLoginMessage(CmsObject cms, CmsLoginMessage message) throws CmsRoleViolationException { 707 708 if (OpenCms.getRunLevel() >= OpenCms.RUNLEVEL_3_SHELL_ACCESS) { 709 // during configuration phase no permission check id required 710 OpenCms.getRoleManager().checkRole(cms, CmsRole.ROOT_ADMIN); 711 } 712 m_beforeLoginMessage = message; 713 714 if (m_beforeLoginMessage != null) { 715 m_beforeLoginMessage.setFrozen(); 716 } 717 } 718 719 /** 720 * Sets the login message to display if a user logs in.<p> 721 * 722 * This operation requires that the current user has role permissions of <code>{@link CmsRole#ROOT_ADMIN}</code>.<p> 723 * 724 * @param cms the current OpenCms user context 725 * @param message the message to set 726 * 727 * @throws CmsRoleViolationException in case the current user does not have the required role permissions 728 */ 729 public void setLoginMessage(CmsObject cms, CmsLoginMessage message) throws CmsRoleViolationException { 730 731 if (OpenCms.getRunLevel() >= OpenCms.RUNLEVEL_3_SHELL_ACCESS) { 732 // during configuration phase no permission check id required 733 OpenCms.getRoleManager().checkRole(cms, CmsRole.ROOT_ADMIN); 734 } 735 m_loginMessage = message; 736 if (m_loginMessage != null) { 737 m_loginMessage.setFrozen(); 738 } 739 } 740 741 /** 742 * Unlocks a user who has exceeded his number of failed login attempts so that he can try to log in again.<p> 743 * This requires the "account manager" role. 744 * 745 * @param cms the current CMS context 746 * @param user the user to unlock 747 * 748 * @throws CmsRoleViolationException if the permission check fails 749 */ 750 public void unlockUser(CmsObject cms, CmsUser user) throws CmsRoleViolationException { 751 752 OpenCms.getRoleManager().checkRole(cms, CmsRole.ACCOUNT_MANAGER.forOrgUnit(cms.getRequestContext().getOuFqn())); 753 Set<String> keysToRemove = getKeysForUser(user); 754 for (String keyToRemove : keysToRemove) { 755 m_storage.remove(keyToRemove); 756 } 757 } 758 759 /** 760 * Adds an invalid attempt to login for the given user / IP to the storage.<p> 761 * 762 * In case the configured threshold is reached, the user is disabled for the configured time.<p> 763 * 764 * @param userName the name of the user 765 * @param remoteAddress the remore address (IP) from which the login attempt was made 766 */ 767 protected void addInvalidLogin(String userName, String remoteAddress) { 768 769 if (m_maxBadAttempts < 0) { 770 // invalid login storage is disabled 771 return; 772 } 773 774 String key = createStorageKey(userName, remoteAddress); 775 // look up the user in the storage 776 CmsUserData userData = m_storage.get(key); 777 if (userData != null) { 778 // user data already contained in storage 779 userData.increaseInvalidLoginCount(); 780 } else { 781 // create an new data object for this user 782 userData = new CmsUserData(); 783 m_storage.put(key, userData); 784 } 785 } 786 787 /** 788 * Removes all invalid attempts to login for the given user / IP.<p> 789 * 790 * @param userName the name of the user 791 * @param remoteAddress the remore address (IP) from which the login attempt was made 792 */ 793 protected void removeInvalidLogins(String userName, String remoteAddress) { 794 795 if (m_maxBadAttempts < 0) { 796 // invalid login storage is disabled 797 return; 798 } 799 800 String key = createStorageKey(userName, remoteAddress); 801 // just remove the user from the storage 802 m_storage.remove(key); 803 } 804 805 /** 806 * Helper method to get all the storage keys that match a user's name.<p> 807 * 808 * @param user the user for which to get the storage keys 809 * 810 * @return the set of storage keys 811 */ 812 private Set<String> getKeysForUser(CmsUser user) { 813 814 Set<String> keysToRemove = new HashSet<String>(); 815 for (Map.Entry<String, CmsUserData> entry : m_storage.entrySet()) { 816 String key = entry.getKey(); 817 int separatorPos = key.lastIndexOf(KEY_SEPARATOR); 818 String prefix = key.substring(0, separatorPos); 819 if (user.getName().equals(prefix)) { 820 keysToRemove.add(key); 821 } 822 } 823 return keysToRemove; 824 } 825}