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.file.CmsObject; 031import org.opencms.file.CmsUser; 032import org.opencms.main.CmsException; 033import org.opencms.main.CmsLog; 034import org.opencms.main.CmsRuntimeException; 035import org.opencms.main.OpenCms; 036import org.opencms.security.CmsSecurityException; 037import org.opencms.security.CmsUserLog; 038import org.opencms.security.I_CmsPasswordHandler; 039import org.opencms.security.I_CmsPasswordSecurityEvaluator; 040import org.opencms.security.I_CmsPasswordSecurityEvaluator.SecurityLevel; 041import org.opencms.security.twofactor.CmsSecondFactorInfo; 042import org.opencms.security.twofactor.CmsTwoFactorAuthenticationHandler; 043import org.opencms.ui.A_CmsUI; 044import org.opencms.ui.CmsVaadinUtils; 045import org.opencms.ui.I_CmsDialogContext; 046import org.opencms.ui.Messages; 047import org.opencms.ui.components.CmsBasicDialog; 048import org.opencms.ui.components.CmsOkCancelActionHandler; 049import org.opencms.ui.components.OpenCmsTheme; 050import org.opencms.util.CmsStringUtil; 051import org.opencms.util.CmsUUID; 052import org.opencms.workplace.CmsWorkplaceLoginHandler; 053 054import java.util.Collections; 055import java.util.Locale; 056import java.util.function.Consumer; 057 058import org.apache.commons.logging.Log; 059 060import com.vaadin.server.UserError; 061import com.vaadin.ui.Button; 062import com.vaadin.ui.Button.ClickEvent; 063import com.vaadin.ui.Button.ClickListener; 064import com.vaadin.ui.UI; 065import com.vaadin.ui.Window; 066import com.vaadin.v7.data.Property.ValueChangeEvent; 067import com.vaadin.v7.data.Property.ValueChangeListener; 068 069/** 070 * Dialog used to change the password.<p> 071 */ 072public class CmsChangePasswordDialog extends CmsBasicDialog { 073 074 /** Logger instance for this class. */ 075 private static final Log LOG = CmsLog.getLog(CmsChangePasswordDialog.class); 076 077 /** Serial version id. */ 078 private static final long serialVersionUID = 1L; 079 080 /** The password form. */ 081 protected CmsPasswordForm m_form; 082 083 /** The CMS context. */ 084 CmsObject m_cms; 085 086 /** The locale. */ 087 Locale m_locale; 088 089 /** The user. */ 090 CmsUser m_user; 091 092 /** The cancel button. */ 093 private Button m_cancelButton; 094 095 /** The dialog context. */ 096 private I_CmsDialogContext m_context; 097 098 /** The button to change the password. */ 099 private Button m_passwordChangeButton; 100 101 /** 102 * Creates a new instance.<p> 103 * 104 * @param cms the CMS context 105 * @param user the user 106 * @param locale the locale 107 */ 108 public CmsChangePasswordDialog(CmsObject cms, CmsUser user, Locale locale) { 109 110 super(); 111 m_locale = locale; 112 m_cms = cms; 113 m_user = user; 114 if (m_user.isManaged()) { 115 throw new CmsRuntimeException( 116 Messages.get().container(Messages.ERR_USER_NOT_SELF_MANAGED_1, m_user.getName())); 117 } 118 119 m_form = new CmsPasswordForm(m_locale); 120 setContent(m_form); 121 if (OpenCms.getPasswordHandler() instanceof I_CmsPasswordSecurityEvaluator) { 122 m_form.setSecurityHint( 123 ((I_CmsPasswordSecurityEvaluator)OpenCms.getPasswordHandler()).getPasswordSecurityHint(m_locale)); 124 } 125 126 m_passwordChangeButton = new Button(CmsVaadinUtils.getMessageText(Messages.GUI_CHANGE_PASSWORD_BUTTON_0)); 127 addButton(m_passwordChangeButton); 128 m_passwordChangeButton.addClickListener(new ClickListener() { 129 130 /** Serial versino id. */ 131 private static final long serialVersionUID = 1L; 132 133 public void buttonClick(ClickEvent event) { 134 135 submit(); 136 } 137 }); 138 139 m_cancelButton = new Button(CmsVaadinUtils.messageCancel()); 140 addButton(m_cancelButton); 141 m_cancelButton.addClickListener(new ClickListener() { 142 143 /** Serial versino id. */ 144 private static final long serialVersionUID = 1L; 145 146 public void buttonClick(ClickEvent event) { 147 148 close(); 149 } 150 }); 151 m_cancelButton.setVisible(false); 152 m_form.getOldPasswordField().setImmediate(true); 153 154 m_form.getOldPasswordField().addValueChangeListener(new ValueChangeListener() { 155 156 private static final long serialVersionUID = 1L; 157 158 public void valueChange(ValueChangeEvent event) { 159 160 clearOldPasswordError(); 161 } 162 }); 163 164 m_form.getPassword1Field().addValueChangeListener(event -> { 165 checkSecurity(event.getValue()); 166 }); 167 168 m_form.getPassword2Field().addValueChangeListener(event -> { 169 checkPasswordMatch(event.getValue()); 170 }); 171 } 172 173 /** 174 * Creates a new instance.<p> 175 * 176 * @param context the dialog context 177 */ 178 public CmsChangePasswordDialog(I_CmsDialogContext context) { 179 180 this(context.getCms(), context.getCms().getRequestContext().getCurrentUser(), UI.getCurrent().getLocale()); 181 m_context = context; 182 m_cancelButton.setVisible(true); 183 setActionHandler(new CmsOkCancelActionHandler() { 184 185 private static final long serialVersionUID = 1L; 186 187 @Override 188 protected void cancel() { 189 190 close(); 191 } 192 193 @Override 194 protected void ok() { 195 196 submit(); 197 } 198 }); 199 } 200 201 /** 202 * Displays some additional text.<p> 203 * 204 * @param text the text to display 205 */ 206 public void setAdditionalMessage(String text) { 207 208 m_form.setAdditionalText(text); 209 } 210 211 /** 212 * Opens the 2FA verification code dialog for the user if necessary, and passes the code obtained from the dialog 213 * to the handler object passed as a parameter. 214 * 215 * <p>If the user doesn't need 2FA, no dialog is opened and null is passed to the handler. 216 * 217 * @param handler the handler to call with the verification code data 218 */ 219 protected void maybeCheckSecondFactor(Consumer<CmsSecondFactorInfo> handler) { 220 221 CmsTwoFactorAuthenticationHandler twoFactorHandler = OpenCms.getTwoFactorAuthenticationHandler(); 222 boolean needToCheck = twoFactorHandler.needsTwoFactorAuthentication(m_user) 223 && twoFactorHandler.hasSecondFactor(m_user); 224 225 if (!needToCheck) { 226 handler.accept(null); 227 } else { 228 Window window = CmsBasicDialog.prepareWindow(DialogWidth.narrow); 229 window.setModal(true); 230 window.setResizable(false); 231 window.setCaption(CmsSecondFactorDialog.getCaption(m_user)); 232 CmsSecondFactorDialog dialog = new CmsSecondFactorDialog(m_user, code -> { 233 CmsSecondFactorInfo info = new CmsSecondFactorInfo(code); 234 handler.accept(info); 235 }); 236 A_CmsUI.get().addWindow(window); 237 window.setContent(dialog); 238 } 239 } 240 241 /** 242 * Checks whether the passwords match.<p> 243 * 244 * @param password2 the password 2 value 245 */ 246 void checkPasswordMatch(String password2) { 247 248 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(password2)) { 249 showPasswordMatchError(!password2.equals(m_form.getPassword1())); 250 } 251 } 252 253 /** 254 * Checks the security level of the given password.<p> 255 * 256 * @param password the password 257 */ 258 void checkSecurity(String password) { 259 260 I_CmsPasswordHandler handler = OpenCms.getPasswordHandler(); 261 try { 262 handler.validatePassword(password); 263 if (handler instanceof I_CmsPasswordSecurityEvaluator) { 264 SecurityLevel level = ((I_CmsPasswordSecurityEvaluator)handler).evaluatePasswordSecurity(password); 265 m_form.setErrorPassword1(null, OpenCmsTheme.SECURITY + "-" + level.name()); 266 } else { 267 m_form.setErrorPassword1(null, OpenCmsTheme.SECURITY_STRONG); 268 } 269 } catch (CmsSecurityException e) { 270 m_form.setErrorPassword1(new UserError(e.getLocalizedMessage(m_locale)), OpenCmsTheme.SECURITY_INVALID); 271 } 272 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_form.getPassword2())) { 273 showPasswordMatchError(!password.equals(m_form.getPassword2())); 274 } 275 } 276 277 /** 278 * Clears the wrong old password error.<p> 279 */ 280 void clearOldPasswordError() { 281 282 m_form.setErrorOldPassword(null, null); 283 } 284 285 /** 286 * Closes the dialog.<p> 287 */ 288 void close() { 289 290 if (m_context != null) { 291 m_context.finish(Collections.<CmsUUID> emptyList()); 292 } 293 } 294 295 /** 296 * Shows or hides the not matching passwords error.<p> 297 * 298 * @param show <code>true</code> to show the error 299 */ 300 void showPasswordMatchError(boolean show) { 301 302 if (show) { 303 m_form.setErrorPassword2( 304 new UserError(Messages.get().getBundle(m_locale).key(Messages.GUI_PWCHANGE_PASSWORD_MISMATCH_0)), 305 OpenCmsTheme.SECURITY_INVALID); 306 } else { 307 m_form.setErrorPassword2(null, OpenCmsTheme.SECURITY_STRONG); 308 } 309 } 310 311 /** 312 * Submits the password change.<p> 313 */ 314 void submit() { 315 316 String password1 = m_form.getPassword1(); 317 String password2 = m_form.getPassword2(); 318 319 if (validatePasswords(password1, password2)) { 320 String oldPassword = m_form.getOldPassword(); 321 if (oldPassword.equals(password1)) { 322 m_form.setErrorPassword1( 323 new UserError( 324 Messages.get().getBundle(m_locale).key(Messages.GUI_PWCHANGE_DIFFERENT_PASSWORD_REQUIRED_0)), 325 OpenCmsTheme.SECURITY_INVALID); 326 } else { 327 maybeCheckSecondFactor((CmsSecondFactorInfo secondFactor) -> { 328 329 try { 330 m_cms.setPassword(m_user.getName(), oldPassword, secondFactor, password1); 331 CmsUserLog.logPasswordChangeForRequestedReset(A_CmsUI.getCmsObject(), m_user.getName()); 332 closeOrRedirectToLoginForm(); 333 } catch (CmsException e) { 334 m_form.setErrorOldPassword( 335 new UserError(e.getLocalizedMessage(m_locale)), 336 OpenCmsTheme.SECURITY_INVALID); 337 LOG.debug(e.getLocalizedMessage(), e); 338 } 339 }); 340 } 341 } 342 } 343 344 /** 345 * Validates the passwords, checking if they match and fulfill the requirements of the password handler.<p> 346 * Will show the appropriate errors if necessary.<p> 347 * 348 * @param password1 password 1 349 * @param password2 password 2 350 * 351 * @return <code>true</code> if valid 352 */ 353 boolean validatePasswords(String password1, String password2) { 354 355 if (!password1.equals(password2)) { 356 showPasswordMatchError(true); 357 return false; 358 } 359 showPasswordMatchError(false); 360 try { 361 OpenCms.getPasswordHandler().validatePassword(password1); 362 m_form.getPassword1Field().setComponentError(null); 363 return true; 364 } catch (CmsException e) { 365 m_form.setErrorPassword1(new UserError(e.getLocalizedMessage(m_locale)), OpenCmsTheme.SECURITY_INVALID); 366 return false; 367 } 368 } 369 370 /** 371 * Closes the dialog, or redirects to the login form, depending on context. 372 */ 373 private void closeOrRedirectToLoginForm() { 374 375 if (m_context != null) { 376 close(); 377 } else { 378 // this will be the case for forced password changes after login 379 CmsVaadinUtils.showAlert( 380 Messages.get().getBundle(m_locale).key(Messages.GUI_PWCHANGE_SUCCESS_HEADER_0), 381 Messages.get().getBundle(m_locale).key(Messages.GUI_PWCHANGE_GUI_PWCHANGE_SUCCESS_CONTENT_0), 382 new Runnable() { 383 384 public void run() { 385 386 A_CmsUI.get().getPage().setLocation( 387 OpenCms.getLinkManager().substituteLinkForUnknownTarget( 388 CmsLoginUI.m_adminCms, 389 CmsWorkplaceLoginHandler.LOGIN_HANDLER 390 + "?ocUname=" 391 + m_user.getSimpleName() 392 + "&ocOuFqn=" 393 + m_user.getOuFqn(), 394 false)); 395 } 396 397 }); 398 } 399 } 400}