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, 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.ui.login; 029 030import org.opencms.ade.configuration.CmsADEManager; 031import org.opencms.db.CmsLoginMessage; 032import org.opencms.db.CmsUserSettings; 033import org.opencms.file.CmsObject; 034import org.opencms.file.CmsProject; 035import org.opencms.file.CmsRequestContext; 036import org.opencms.file.CmsUser; 037import org.opencms.i18n.CmsMessageContainer; 038import org.opencms.i18n.CmsResourceBundleLoader; 039import org.opencms.jsp.CmsJspActionElement; 040import org.opencms.jsp.CmsJspLoginBean; 041import org.opencms.main.CmsBroadcast.ContentMode; 042import org.opencms.main.CmsException; 043import org.opencms.main.CmsLog; 044import org.opencms.main.OpenCms; 045import org.opencms.security.CmsCustomLoginException; 046import org.opencms.security.CmsRole; 047import org.opencms.security.CmsUserLog; 048import org.opencms.security.twofactor.CmsSecondFactorInfo; 049import org.opencms.security.twofactor.CmsTwoFactorAuthenticationHandler; 050import org.opencms.ui.A_CmsDialogContext; 051import org.opencms.ui.A_CmsUI; 052import org.opencms.ui.CmsVaadinUtils; 053import org.opencms.ui.I_CmsDialogContext; 054import org.opencms.ui.I_CmsDialogContext.ContextType; 055import org.opencms.ui.Messages; 056import org.opencms.ui.apps.CmsAppHierarchyConfiguration; 057import org.opencms.ui.apps.CmsAppWorkplaceUi; 058import org.opencms.ui.apps.CmsFileExplorerConfiguration; 059import org.opencms.ui.apps.CmsSitemapEditorConfiguration; 060import org.opencms.ui.components.CmsBasicDialog; 061import org.opencms.ui.components.CmsBasicDialog.DialogWidth; 062import org.opencms.ui.dialogs.CmsUserDataDialog; 063import org.opencms.ui.login.CmsLoginHelper.LoginParameters; 064import org.opencms.util.CmsStringUtil; 065import org.opencms.util.CmsUUID; 066import org.opencms.workplace.CmsLoginUserAgreement; 067import org.opencms.workplace.CmsWorkplace; 068import org.opencms.workplace.CmsWorkplaceLoginHandler; 069import org.opencms.workplace.CmsWorkplaceManager; 070import org.opencms.workplace.CmsWorkplaceSettings; 071 072import java.io.IOException; 073import java.text.SimpleDateFormat; 074import java.util.Collection; 075import java.util.Date; 076import java.util.List; 077import java.util.MissingResourceException; 078import java.util.ResourceBundle; 079 080import javax.servlet.http.HttpServletRequest; 081import javax.servlet.http.HttpServletResponse; 082import javax.servlet.http.HttpSession; 083 084import org.apache.commons.lang3.builder.ReflectionToStringBuilder; 085import org.apache.commons.lang3.builder.ToStringStyle; 086import org.apache.commons.logging.Log; 087 088import com.vaadin.server.Page; 089import com.vaadin.server.VaadinService; 090import com.vaadin.server.VaadinServletRequest; 091import com.vaadin.server.VaadinServletResponse; 092import com.vaadin.ui.Component; 093import com.vaadin.ui.UI; 094import com.vaadin.ui.Window; 095 096/** 097 * Controller class which actually handles the login dialog logic.<p> 098 */ 099public class CmsLoginController { 100 101 /** 102 * Represents the login target information.<p> 103 */ 104 public static class CmsLoginTargetInfo { 105 106 /** The password. */ 107 private String m_password; 108 109 /** The login target. */ 110 private String m_target; 111 112 /** The user. */ 113 private String m_user; 114 115 /** 116 * Creates a new instance.<p> 117 * 118 * @param target the login target 119 * @param user the user name 120 * @param password the password 121 */ 122 public CmsLoginTargetInfo(String target, String user, String password) { 123 124 super(); 125 m_target = target; 126 m_user = user; 127 m_password = password; 128 } 129 130 /** 131 * Returns the password.<p> 132 * 133 * @return the password 134 */ 135 public String getPassword() { 136 137 return m_password; 138 } 139 140 /** 141 * Returns the target.<p> 142 * 143 * @return the target 144 */ 145 public String getTarget() { 146 147 return m_target; 148 } 149 150 /** 151 * Returns the user.<p> 152 * 153 * @return the user 154 */ 155 public String getUser() { 156 157 return m_user; 158 } 159 } 160 161 /** 162 * The login context. 163 */ 164 public static class LoginContext { 165 166 /** The CMS context. */ 167 private CmsObject m_cms; 168 169 /** The second factor information. */ 170 private CmsSecondFactorInfo m_secondFactorInfo; 171 172 /** The user being logged in. */ 173 private CmsUser m_user; 174 175 /** 176 * Gets the CmsObject. 177 * 178 * @return the CmsObject 179 */ 180 public CmsObject getCms() { 181 182 return m_cms; 183 } 184 185 /** 186 * The second factor information for 2FA. 187 * 188 * @return the second factor information 189 */ 190 public CmsSecondFactorInfo getSecondFactorInfo() { 191 192 return m_secondFactorInfo; 193 } 194 195 /** 196 * Gets the user to be logged in. 197 * 198 * @return the user 199 */ 200 public CmsUser getUser() { 201 202 return m_user; 203 } 204 205 /** 206 * Sets the CMS context. 207 * 208 * @param cms the CMS context 209 */ 210 public void setCms(CmsObject cms) { 211 212 m_cms = cms; 213 } 214 215 /** 216 * Sets the second factor information for 2FA. 217 * 218 * @param secondFactorInfo the second factor information 219 */ 220 public void setSecondFactorInfo(CmsSecondFactorInfo secondFactorInfo) { 221 222 m_secondFactorInfo = secondFactorInfo; 223 } 224 225 /** 226 * Sets the user. 227 * 228 * @param user the user 229 */ 230 public void setUser(CmsUser user) { 231 232 m_user = user; 233 } 234 235 /** 236 * @see java.lang.Object#toString() 237 */ 238 @Override 239 public String toString() { 240 241 return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE); 242 } 243 }; 244 245 /** 246 * Helper subclass of CmsLoginUserAgreement which can be used without a page context.<p> 247 * 248 * This is only used for detecting whether we need to display the user agreement dialog, not for displaying the dialog itself.<p> 249 */ 250 protected static class UserAgreementHelper extends CmsLoginUserAgreement { 251 252 /** The replacement CMS context. */ 253 private CmsObject m_cms; 254 255 /** The replacemenet workplace settings. */ 256 private CmsWorkplaceSettings m_wpSettings; 257 258 /** 259 * Creates a new instance.<p> 260 * 261 * @param cms the replacement CMS context 262 * @param wpSettings the replacement workplace settings 263 */ 264 public UserAgreementHelper(CmsObject cms, CmsWorkplaceSettings wpSettings) { 265 266 super(null); 267 m_cms = cms; 268 m_wpSettings = wpSettings; 269 initAcceptData(); 270 } 271 272 /** 273 * @see org.opencms.workplace.CmsWorkplace#getCms() 274 */ 275 @Override 276 public CmsObject getCms() { 277 278 return m_cms; 279 } 280 281 /** 282 * @see org.opencms.workplace.CmsWorkplace#getSettings() 283 */ 284 @Override 285 public CmsWorkplaceSettings getSettings() { 286 287 return m_wpSettings; 288 } 289 290 /** 291 * @see org.opencms.workplace.CmsWorkplace#initWorkplaceMembers(org.opencms.jsp.CmsJspActionElement) 292 */ 293 @Override 294 protected void initWorkplaceMembers(CmsJspActionElement jsp) { 295 296 // do nothing 297 } 298 } 299 300 /** 301 * Helper interface for splitting the actual login off into its own object 302 * which can be called from multiple places. 303 */ 304 interface LoginContinuation { 305 306 /** 307 * Continues with the login process. 308 * 309 * @param data the login data 310 * @throws Exception if something goes wrong 311 */ 312 void continueLogin(LoginContext data) throws Exception; 313 } 314 315 /** Additional info key to mark accounts as locked due to inactivity. */ 316 public static final String KEY_ACCOUNT_LOCKED = "accountLocked"; 317 318 /** The logger for this class. */ 319 private static final Log LOG = CmsLog.getLog(CmsLoginController.class); 320 321 /** The two-factor authentication handler. */ 322 protected CmsTwoFactorAuthenticationHandler m_otpHandler = OpenCms.getTwoFactorAuthenticationHandler(); 323 324 /** The UI instance. */ 325 CmsLoginUI m_ui; 326 327 /** The administrator CMS context. */ 328 private CmsObject m_adminCms; 329 330 /** The parameters collected when the login app was opened. */ 331 private LoginParameters m_params; 332 333 /*** 334 * Creates a new instance.<p> 335 * 336 * @param adminCms the admin cms context 337 * @param params the parameters for the UI 338 */ 339 public CmsLoginController(CmsObject adminCms, LoginParameters params) { 340 341 m_params = params; 342 m_adminCms = adminCms; 343 } 344 345 /** 346 * Returns the link to the login form.<p> 347 * 348 * @param cms the current cms context 349 * 350 * @return the login form link 351 */ 352 public static String getFormLink(CmsObject cms) { 353 354 return OpenCms.getLinkManager().substituteLinkForUnknownTarget( 355 cms, 356 CmsWorkplaceLoginHandler.LOGIN_HANDLER, 357 false); 358 } 359 360 /** 361 * Gets the login target link.<p> 362 * 363 * @param currentCms the current CMS context 364 * @param settings the workplace settings 365 * @param requestedResource the requested resource parameter 366 * 367 * @return the login target 368 * 369 * @throws CmsException in case the user has insufficient permissions to access the login target 370 */ 371 public static String getLoginTarget(CmsObject currentCms, CmsWorkplaceSettings settings, String requestedResource) 372 throws CmsException { 373 374 String directEditPath = CmsLoginHelper.getDirectEditPath(currentCms, settings.getUserSettings(), false); 375 String target = ""; 376 boolean checkRole = false; 377 String fragment = UI.getCurrent() != null ? UI.getCurrent().getPage().getUriFragment() : ""; 378 boolean workplace2 = false; 379 380 if ((requestedResource == null) && (directEditPath != null)) { 381 target = directEditPath; 382 } else if ((requestedResource != null) && !CmsWorkplace.JSP_WORKPLACE_URI.equals(requestedResource)) { 383 target = requestedResource; 384 } else { 385 workplace2 = true; 386 target = CmsVaadinUtils.getWorkplaceLink(); 387 checkRole = true; 388 } 389 390 UserAgreementHelper userAgreementHelper = new UserAgreementHelper(currentCms, settings); 391 boolean showUserAgreement = userAgreementHelper.isShowUserAgreement(); 392 if (showUserAgreement) { 393 target = userAgreementHelper.getConfigurationVfsPath() 394 + "?" 395 + CmsLoginUserAgreement.PARAM_WPRES 396 + "=" 397 + target; 398 } 399 if (checkRole && !OpenCms.getRoleManager().hasRole(currentCms, CmsRole.WORKPLACE_USER)) { 400 workplace2 = false; 401 target = CmsLoginHelper.getDirectEditPath(currentCms, settings.getUserSettings(), true); 402 if (target == null) { 403 throw new CmsCustomLoginException( 404 org.opencms.workplace.Messages.get().container( 405 org.opencms.workplace.Messages.GUI_LOGIN_FAILED_NO_WORKPLACE_PERMISSIONS_0)); 406 } 407 } 408 if (!workplace2) { 409 target = OpenCms.getLinkManager().substituteLink(currentCms, target); 410 } 411 412 if (workplace2 && CmsStringUtil.isEmptyOrWhitespaceOnly(fragment)) { 413 if (settings.getUserSettings().getStartView().startsWith("/")) { 414 if (CmsWorkplace.VIEW_WORKPLACE.equals(settings.getUserSettings().getStartView())) { 415 fragment = CmsFileExplorerConfiguration.APP_ID; 416 } else if (CmsWorkplace.VIEW_ADMIN.equals(settings.getUserSettings().getStartView())) { 417 fragment = CmsAppHierarchyConfiguration.APP_ID; 418 } 419 } else { 420 fragment = settings.getUserSettings().getStartView(); 421 } 422 } 423 424 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(fragment)) { 425 if (CmsSitemapEditorConfiguration.APP_ID.equals(fragment)) { 426 target = OpenCms.getLinkManager().substituteLink(currentCms, CmsADEManager.PATH_SITEMAP_EDITOR_JSP) 427 + "?path=" 428 + settings.getUserSettings().getStartFolder(); 429 } else { 430 target += "#" + fragment; 431 } 432 } 433 return target; 434 } 435 436 /** 437 * Logs the current user out by invalidating the session an reloading the current URI.<p> 438 * Important: This works only within vaadin apps.<p> 439 */ 440 public static void logout() { 441 442 CmsObject cms = A_CmsUI.getCmsObject(); 443 if (UI.getCurrent() instanceof CmsAppWorkplaceUi) { 444 ((CmsAppWorkplaceUi)UI.getCurrent()).onWindowClose(); 445 } 446 String loggedInUser = cms.getRequestContext().getCurrentUser().getName(); 447 UI.getCurrent().getSession().close(); 448 String logoutUri = OpenCms.getLoginManager().getLogoutUri(); 449 if (logoutUri != null) { 450 String target = OpenCms.getLinkManager().substituteLinkForUnknownTarget(cms, logoutUri, false); 451 // open in top frame, so it still works when the Vaadin dialog is embedded 452 Page.getCurrent().open(target, "_top", false); 453 } else { 454 String loginLink = OpenCms.getLinkManager().substituteLinkForUnknownTarget( 455 cms, 456 CmsWorkplaceLoginHandler.LOGIN_HANDLER, 457 false); 458 VaadinService.getCurrentRequest().getWrappedSession().invalidate(); 459 // open in top frame, so it still works when the Vaadin dialog is embedded 460 Page.getCurrent().open(loginLink, "_top", false); 461 // logout was successful 462 if (LOG.isInfoEnabled()) { 463 LOG.info( 464 org.opencms.jsp.Messages.get().getBundle().key( 465 org.opencms.jsp.Messages.LOG_LOGOUT_SUCCESFUL_3, 466 loggedInUser, 467 "{workplace logout option}", 468 cms.getRequestContext().getRemoteAddress())); 469 } 470 CmsUserLog.logLogout(cms); 471 } 472 } 473 474 /** 475 * Logs out the current user redirecting to the login form afterwards.<p> 476 * 477 * @param cms the cms context 478 * @param request the servlet request 479 * @param response the servlet response 480 * 481 * @throws IOException if writing to the response fails 482 */ 483 public static void logout(CmsObject cms, HttpServletRequest request, HttpServletResponse response) 484 throws IOException { 485 486 CmsUserLog.logLogout(cms); 487 String loggedInUser = cms.getRequestContext().getCurrentUser().getName(); 488 HttpSession session = request.getSession(false); 489 String logoutUri = OpenCms.getLoginManager().getLogoutUri(); 490 if (logoutUri == null) { 491 if (session != null) { 492 session.invalidate(); 493 /* we need this because a new session might be created after this method, 494 but before the session info is updated in OpenCmsCore.showResource. */ 495 cms.getRequestContext().setUpdateSessionEnabled(false); 496 } 497 // logout was successful 498 if (LOG.isInfoEnabled()) { 499 LOG.info( 500 org.opencms.jsp.Messages.get().getBundle().key( 501 org.opencms.jsp.Messages.LOG_LOGOUT_SUCCESFUL_3, 502 loggedInUser, 503 cms.getRequestContext().addSiteRoot(cms.getRequestContext().getUri()), 504 cms.getRequestContext().getRemoteAddress())); 505 } 506 response.sendRedirect(getFormLink(cms)); 507 } else { 508 response.sendRedirect(OpenCms.getLinkManager().substituteLinkForUnknownTarget(cms, logoutUri, false)); 509 } 510 } 511 512 /** 513 * Gets the PC type.<p> 514 * 515 * @return the PC type 516 */ 517 public String getPcType() { 518 519 String result = m_params.getPcType(); 520 if (CmsStringUtil.isEmptyOrWhitespaceOnly(result)) { 521 result = "public"; 522 } 523 return result; 524 } 525 526 /** 527 * Returns the reset password link.<p> 528 * 529 * @return the reset password link 530 */ 531 public String getResetPasswordLink() { 532 533 String result = OpenCms.getLinkManager().substituteLinkForUnknownTarget( 534 CmsLoginUI.m_adminCms, 535 CmsWorkplaceLoginHandler.LOGIN_HANDLER, 536 false) + "?" + CmsLoginHelper.PARAM_RESET_PASSWORD; 537 String ou = m_ui.getOrgUnit(); 538 result += "&" + CmsLoginHelper.PARAM_OUFQN + "=" + ou; 539 return result; 540 } 541 542 /** 543 * Returns true if the security option should be displayed in the login dialog.<p> 544 * 545 * @return true if the security option should be displayed in the login dialog 546 */ 547 public boolean isShowSecure() { 548 549 return OpenCms.getLoginManager().isEnableSecurity(); 550 } 551 552 /** 553 * Called when the user clicks on the 'forgot password' button.<p> 554 */ 555 public void onClickForgotPassword() { 556 557 A_CmsUI.get().getPage().setLocation(getResetPasswordLink()); 558 } 559 560 /** 561 * Called when the user clicks on the login button.<p> 562 */ 563 public void onClickLogin() { 564 565 String user = m_ui.getUser(); 566 String password = m_ui.getPassword(); 567 String ou = m_ui.getOrgUnit(); 568 if (CmsLoginOuSelector.OU_NONE.equals(ou)) { 569 displayError( 570 CmsVaadinUtils.getMessageText(Messages.GUI_LOGIN_NO_OU_SELECTED_WARNING_0) + "\n\n", 571 false, 572 false); 573 return; 574 } 575 { 576 CmsMessageContainer message = CmsLoginHelper.validateUserAndPasswordNotEmpty(user, password); 577 if (message != null) { 578 String errorMessage = message.key(m_params.getLocale()); 579 displayError(errorMessage, true, false); 580 return; 581 } 582 } 583 584 String realUser = CmsStringUtil.joinPaths(ou, user); 585 String pcType = m_ui.getPcType(); 586 CmsObject currentCms = A_CmsUI.getCmsObject(); 587 CmsUser userObj = null; 588 589 try { 590 try { 591 userObj = currentCms.readUser(realUser); 592 } catch (CmsException e) { 593 LOG.warn(e.getLocalizedMessage(), e); 594 CmsMessageContainer message = org.opencms.workplace.Messages.get().container( 595 org.opencms.workplace.Messages.GUI_LOGIN_FAILED_0); 596 displayError(message.key(m_params.getLocale()), true, true); 597 CmsUserLog.logLoginFailure(currentCms, realUser); 598 return; 599 } 600 final CmsUser userNonNull = userObj; 601 if (OpenCms.getLoginManager().canLockBecauseOfInactivity(currentCms, userObj)) { 602 boolean locked = null != userObj.getAdditionalInfo().get(KEY_ACCOUNT_LOCKED); 603 if (locked) { 604 displayError(CmsInactiveUserMessages.getLockoutText(A_CmsUI.get().getLocale()), false, false); 605 CmsUserLog.logLoginFailure(currentCms, realUser); 606 return; 607 } 608 } 609 610 CmsObject cloneCms = OpenCms.initCmsObject(currentCms); 611 cloneCms.checkLoginUser(realUser, password); 612 613 String messageToChange = ""; 614 if (OpenCms.getLoginManager().isPasswordReset(currentCms, userObj)) { 615 messageToChange = CmsVaadinUtils.getMessageText(Messages.GUI_PWCHANGE_RESET_0); 616 } 617 if (OpenCms.getLoginManager().requiresPasswordChange(currentCms, userObj)) { 618 messageToChange = getPasswordChangeMessage(); 619 } 620 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(messageToChange)) { 621 CmsChangePasswordDialog passwordDialog = new CmsChangePasswordDialog( 622 currentCms, 623 userObj, 624 A_CmsUI.get().getLocale()); 625 passwordDialog.setAdditionalMessage(messageToChange); 626 A_CmsUI.get().setContentToDialog( 627 Messages.get().getBundle(A_CmsUI.get().getLocale()).key(Messages.GUI_PWCHANGE_HEADER_0) 628 + userObj.getSimpleName(), 629 passwordDialog); 630 return; 631 } 632 633 /* 634 * Login branches now into multiple paths: For the non-OTP case we can do it directly (synchronously), 635 * and for the OTP cases we need to show additional dialogs first (so it's asynchronous). But at the end, 636 * the same things happen. So we put these 'things that happen at the end' in a closure, so we can either 637 * execute it directly or pass it as a callback to the dialogs. 638 */ 639 LoginContinuation loginContinuation = (LoginContext details) -> { 640 641 // provisional login successful, now do for real 642 // we use another separate CmsObject so we can manually control when it is written to the session info 643 CmsObject loginCms = OpenCms.initCmsObject(currentCms); 644 CmsSecondFactorInfo secondFactorInfo = details.getSecondFactorInfo(); 645 loginCms.loginUser(realUser, password, secondFactorInfo); 646 CmsUserLog.logLogin(loginCms, realUser); 647 648 if (LOG.isInfoEnabled()) { 649 CmsRequestContext context = loginCms.getRequestContext(); 650 LOG.info( 651 org.opencms.jsp.Messages.get().getBundle().key( 652 org.opencms.jsp.Messages.LOG_LOGIN_SUCCESSFUL_3, 653 context.getCurrentUser().getName(), 654 "{workplace login dialog}", 655 context.getRemoteAddress())); 656 } 657 CmsWorkplaceSettings settings = CmsLoginHelper.initSiteAndProject(loginCms); 658 final String loginTarget = getLoginTarget(loginCms, settings, m_params.getRequestedResource()); 659 660 CmsLoginHelper.setCookieData( 661 pcType, 662 user, 663 ou, 664 (VaadinServletRequest)(VaadinService.getCurrentRequest()), 665 (VaadinServletResponse)(VaadinService.getCurrentResponse())); 666 VaadinService.getCurrentRequest().getWrappedSession().setAttribute( 667 CmsWorkplaceManager.SESSION_WORKPLACE_SETTINGS, 668 settings); 669 670 final boolean isPublicPC = CmsLoginForm.PC_TYPE_PUBLIC.equals(pcType); 671 if (OpenCms.getLoginManager().requiresUserDataCheck(loginCms, userNonNull)) { 672 I_CmsDialogContext context = new A_CmsDialogContext("", ContextType.appToolbar, null) { 673 674 @Override 675 public void finish(CmsProject project, String siteRoot) { 676 677 finish(null); 678 } 679 680 @Override 681 public void finish(Collection<CmsUUID> result) { 682 683 initSessionAndSendMessages(currentCms, loginCms); 684 m_ui.openLoginTarget(loginTarget, isPublicPC); 685 686 } 687 688 public void focus(CmsUUID structureId) { 689 690 // nothing to do 691 } 692 693 public List<CmsUUID> getAllStructureIdsInView() { 694 695 return null; 696 } 697 698 @Override 699 public CmsObject getCms() { 700 701 return loginCms; 702 } 703 704 @Override 705 public void start(String title, Component dialog, DialogWidth style) { 706 707 if (dialog != null) { 708 m_window = CmsBasicDialog.prepareWindow(style); 709 m_window.setCaption(title); 710 m_window.setContent(dialog); 711 UI.getCurrent().addWindow(m_window); 712 if (dialog instanceof CmsBasicDialog) { 713 ((CmsBasicDialog)dialog).initActionHandler(m_window); 714 } 715 } 716 } 717 718 public void updateUserInfo() { 719 720 // not supported 721 } 722 }; 723 CmsUser u = currentCms.readUser(userNonNull.getId()); 724 u.setAdditionalInfo( 725 CmsUserSettings.ADDITIONAL_INFO_LAST_USER_DATA_CHECK, 726 Long.toString(System.currentTimeMillis())); 727 loginCms.writeUser(u); 728 CmsUserDataDialog dialog = new CmsUserDataDialog(context, true); 729 context.start(dialog.getTitle(UI.getCurrent().getLocale()), dialog); 730 } else { 731 initSessionAndSendMessages(currentCms, loginCms); 732 m_ui.openLoginTarget(loginTarget, isPublicPC); 733 } 734 }; 735 LoginContext context = new LoginContext(); 736 context.setUser(userNonNull); 737 context.setCms(currentCms); 738 if (m_otpHandler.needsTwoFactorAuthentication(userNonNull)) { 739 m_ui.clearError(); 740 if (!m_otpHandler.hasSecondFactor(userObj)) { 741 showSecondFactorSetup(context, loginContinuation); 742 } else { 743 showSecondFactorDialog(context, loginContinuation); 744 } 745 } else { 746 loginContinuation.continueLogin(context); 747 } 748 } catch (Exception e) { 749 750 handleError(currentCms, realUser, e); 751 } 752 } 753 754 /** 755 * Called on initialization.<p> 756 */ 757 public void onInit() { 758 759 String authToken = m_params.getAuthToken(); 760 if (authToken != null) { 761 m_ui.showForgotPasswordView(authToken); 762 } else if (m_params.isReset()) { 763 m_ui.showPasswordResetDialog(m_params.getOufqn()); 764 } else { 765 boolean loggedIn = !A_CmsUI.getCmsObject().getRequestContext().getCurrentUser().isGuestUser(); 766 m_ui.setSelectableOrgUnits(CmsLoginHelper.getOrgUnitsForLoginDialog(A_CmsUI.getCmsObject(), null)); 767 if (loggedIn) { 768 if (m_params.isLogout()) { 769 logout(); 770 } else { 771 m_ui.showAlreadyLoggedIn(); 772 } 773 } else { 774 m_ui.showLoginView(m_params.getOufqn()); 775 } 776 } 777 778 } 779 780 /** 781 * Sets the login ui reference.<p> 782 * 783 * @param ui the login ui 784 */ 785 public void setUi(CmsLoginUI ui) { 786 787 m_ui = ui; 788 } 789 790 /** 791 * Returns the message to be displayed for the user data check dialog.<p> 792 * 793 * @return the message to display 794 */ 795 protected String getPasswordChangeMessage() { 796 797 ResourceBundle bundle = null; 798 try { 799 bundle = CmsResourceBundleLoader.getBundle("org.opencms.passwordchange.custom", A_CmsUI.get().getLocale()); 800 return bundle.getString("passwordchange.text"); 801 } catch (MissingResourceException e) { 802 return CmsVaadinUtils.getMessageText(Messages.GUI_PWCHANGE_INTERVAL_EXPIRED_0); 803 } 804 } 805 806 /** 807 * Handles exceptions during the login process and displays appropriate error messages. 808 * 809 * @param currentCms the CMS context 810 * @param user the user being logged in 811 * @param e the error 812 */ 813 protected void handleError(CmsObject currentCms, String user, Exception e) { 814 815 CmsMessageContainer message = null; 816 817 // there was an error during login 818 if (e instanceof CmsException) { 819 CmsMessageContainer exceptionMessage = ((CmsException)e).getMessageContainer(); 820 if (org.opencms.security.Messages.ERR_LOGIN_FAILED_DISABLED_2 == exceptionMessage.getKey()) { 821 // the user account is disabled 822 message = org.opencms.workplace.Messages.get().container( 823 org.opencms.workplace.Messages.GUI_LOGIN_FAILED_DISABLED_0); 824 } else if (org.opencms.security.Messages.ERR_LOGIN_FAILED_TEMP_DISABLED_4 == exceptionMessage.getKey()) { 825 // the user account is temporarily disabled because of too many login failures 826 message = org.opencms.workplace.Messages.get().container( 827 org.opencms.workplace.Messages.GUI_LOGIN_FAILED_TEMP_DISABLED_0); 828 } else if (org.opencms.security.Messages.ERR_LOGIN_FAILED_WITH_MESSAGE_1 == exceptionMessage.getKey()) { 829 // all logins have been disabled be the Administration 830 CmsLoginMessage loginMessage2 = OpenCms.getLoginManager().getLoginMessage(); 831 if (loginMessage2 != null) { 832 message = org.opencms.workplace.Messages.get().container( 833 org.opencms.workplace.Messages.GUI_LOGIN_FAILED_WITH_MESSAGE_1, 834 loginMessage2.getMessage().replace("\n", "")); 835 } 836 } 837 } 838 if (message == null) { 839 if (e instanceof CmsCustomLoginException) { 840 message = ((CmsCustomLoginException)e).getMessageContainer(); 841 } else { 842 // any other error - display default message 843 message = org.opencms.workplace.Messages.get().container( 844 org.opencms.workplace.Messages.GUI_LOGIN_FAILED_0); 845 LOG.warn(e.getLocalizedMessage(), e); 846 displayError(message.key(m_params.getLocale()), true, true); 847 CmsUserLog.logLoginFailure(currentCms, user); 848 return; 849 } 850 } 851 852 if (e instanceof CmsException) { 853 CmsJspLoginBean.logLoginException(currentCms.getRequestContext(), user, (CmsException)e); 854 CmsUserLog.logLoginFailure(currentCms, user); 855 } else { 856 LOG.error(e.getLocalizedMessage(), e); 857 } 858 displayError(message.key(m_params.getLocale()), false, false); 859 } 860 861 /** 862 * Switches the session to a new one with the logged in CmsObject. 863 * 864 * <p>This needs to be called in the <em>last</em> request to the Vaadin servlet in the login process, because switching the session breaks the Vaadin session state. 865 * 866 * @param currentCms the CmsObject for the current request from the Vaadin UI 867 * @param loginCms the CmsObject which was used for the actual login operation 868 */ 869 protected void initSessionAndSendMessages(CmsObject currentCms, CmsObject loginCms) { 870 871 HttpSession session = ((HttpServletRequest)VaadinService.getCurrentRequest()).getSession(false); 872 if (session != null) { 873 session.invalidate(); 874 } 875 session = ((HttpServletRequest)VaadinService.getCurrentRequest()).getSession(true); 876 877 // we don't want currentCms to be used to automatically update the session at the end of the request... 878 currentCms.getRequestContext().setUpdateSessionEnabled(false); 879 880 // ...instead we manually update the session with loginCms 881 loginCms.getRequestContext().setUpdateSessionEnabled(true); 882 OpenCms.getSessionManager().updateSessionInfo(loginCms, (HttpServletRequest)VaadinService.getCurrentRequest()); 883 884 String storedMessage = null; 885 CmsLoginMessage loginMessage = OpenCms.getLoginManager().getLoginMessage(); 886 if (loginMessage != null) { 887 // forbidden implies active 888 if (loginMessage.isLoginCurrentlyForbidden()) { 889 // we are an administrator, otherwise login would have failed 890 if (loginMessage.getTimeEnd() == CmsLoginMessage.DEFAULT_TIME_END) { 891 storedMessage = org.opencms.workplace.Messages.get().container( 892 org.opencms.workplace.Messages.GUI_LOGIN_SUCCESS_WITH_MESSAGE_WITHOUT_TIME_1, 893 loginMessage.getMessage(), 894 new Date(loginMessage.getTimeEnd())).key(A_CmsUI.get().getLocale()); 895 } else { 896 storedMessage = org.opencms.workplace.Messages.get().container( 897 org.opencms.workplace.Messages.GUI_LOGIN_SUCCESS_WITH_MESSAGE_2, 898 loginMessage.getMessage(), 899 new Date(loginMessage.getTimeEnd())).key(A_CmsUI.get().getLocale()); 900 } 901 } else if (loginMessage.isActive()) { 902 storedMessage = loginMessage.getMessage(); 903 } 904 } 905 906 if (storedMessage != null) { 907 OpenCms.getSessionManager().sendBroadcast( 908 null, 909 storedMessage, 910 loginCms.getRequestContext().getCurrentUser(), 911 ContentMode.html); 912 } 913 } 914 915 /** 916 * Gets the CMS context.<p> 917 * 918 * @return the CMS context 919 */ 920 CmsObject getCms() { 921 922 return m_adminCms; 923 } 924 925 /** 926 * Displays the given error message.<p> 927 * 928 * @param message the message 929 * @param showForgotPassword in case the forgot password link should be shown 930 * @param showTime show the time 931 */ 932 private void displayError(String message, boolean showForgotPassword, boolean showTime) { 933 934 message = message.replace("\n", "<br />"); 935 if (showForgotPassword) { 936 message += "<br /><br /><a href=\"" 937 + getResetPasswordLink() 938 + "\">" 939 + CmsVaadinUtils.getMessageText(Messages.GUI_FORGOT_PASSWORD_0) 940 + "</a>"; 941 } 942 if (showTime) { 943 SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); 944 message += "<div style=\"position:absolute;right:6px;bottom:5px;\">" 945 + CmsVaadinUtils.getMessageText(Messages.GUI_TIME_1, sdf.format(new Date())) 946 + "</div>"; 947 } 948 m_ui.showLoginError(message); 949 } 950 951 /** 952 * Shows the verification dialog for 2FA. 953 * 954 * @param context the login context 955 * @param loginContinuation the handler to which we pass the verification code to continue with the login 956 */ 957 private void showSecondFactorDialog(LoginContext context, LoginContinuation loginContinuation) { 958 959 Window window = CmsBasicDialog.prepareWindow(DialogWidth.narrow); 960 window.setClosable(false); 961 window.setResizable(false); 962 window.setCaption(CmsSecondFactorDialog.getCaption(context.getUser())); 963 CmsSecondFactorDialog dialog = new CmsSecondFactorDialog(context.getUser(), verificationCode -> { 964 context.setSecondFactorInfo(new CmsSecondFactorInfo(verificationCode)); 965 try { 966 loginContinuation.continueLogin(context); 967 } catch (Exception e) { 968 handleError(context.getCms(), context.getUser().getName(), e); 969 } 970 }); 971 A_CmsUI.get().addWindow(window); 972 window.setContent(dialog); 973 974 } 975 976 /** 977 * Shows a dialog for setting up 2FA. 978 * 979 * @param context the login context 980 * @param loginContinuation the handler we call with the secret and verification code to set up 2FA and proceed with the loin. 981 */ 982 private void showSecondFactorSetup(LoginContext context, LoginContinuation loginContinuation) { 983 984 Window window = CmsBasicDialog.prepareWindow(DialogWidth.wide); 985 window.setClosable(false); 986 window.setResizable(false); 987 CmsSecondFactorSetupDialog dialog = new CmsSecondFactorSetupDialog(context, context2 -> { 988 try { 989 loginContinuation.continueLogin(context); 990 } catch (Exception e) { 991 handleError(context.getCms(), context.getUser().getName(), e); 992 } 993 }); 994 window.setCaption(CmsVaadinUtils.getMessageText(Messages.GUI_LOGIN_2FA_SETUP_0)); 995 A_CmsUI.get().addWindow(window); 996 window.setContent(dialog); 997 998 } 999}