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