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.jsp; 029 030import org.opencms.db.CmsLoginMessage; 031import org.opencms.file.CmsRequestContext; 032import org.opencms.file.CmsUser; 033import org.opencms.i18n.CmsMessageContainer; 034import org.opencms.main.CmsBroadcast.ContentMode; 035import org.opencms.main.CmsException; 036import org.opencms.main.CmsLog; 037import org.opencms.main.OpenCms; 038import org.opencms.security.CmsAuthentificationException; 039import org.opencms.security.CmsUserLog; 040import org.opencms.security.twofactor.CmsSecondFactorInfo; 041 042import java.io.IOException; 043import java.net.URI; 044import java.util.Date; 045 046import javax.servlet.http.HttpServletRequest; 047import javax.servlet.http.HttpServletResponse; 048import javax.servlet.http.HttpSession; 049import javax.servlet.jsp.PageContext; 050 051import org.apache.commons.logging.Log; 052 053/** 054 * Provides convenient wrappers useful to create user login pages.<p> 055 * 056 * Initialize this bean at the beginning of your JSP like this: 057 * <pre> 058 * <jsp:useBean id="cmslogin" class="org.opencms.jsp.CmsJspLoginBean"> 059 * <% cmslogin.init(pageContext, request, response); %> 060 * </jsp:useBean> 061 * </pre> 062 * <p> 063 * 064 * @since 6.0.0 065 */ 066public class CmsJspLoginBean extends CmsJspActionElement { 067 068 /** The log object for this class. */ 069 private static final Log LOG = CmsLog.getLog(CmsJspLoginBean.class); 070 071 /** Flag to indicate if a login was successful. */ 072 private CmsException m_loginException; 073 074 /** The logout redirect target. If null, the current form URI is used. */ 075 private String m_logoutTarget; 076 077 /** The verification code for 2FA. */ 078 private String m_verificationCode; 079 080 /** 081 * Empty constructor, required for every JavaBean.<p> 082 */ 083 public CmsJspLoginBean() { 084 085 // noop, you must call the init() method after you create an instance 086 } 087 088 /** 089 * Constructor, with parameters.<p> 090 * 091 * @param context the JSP page context object 092 * @param req the JSP request 093 * @param res the JSP response 094 */ 095 public CmsJspLoginBean(PageContext context, HttpServletRequest req, HttpServletResponse res) { 096 097 super(); 098 init(context, req, res); 099 } 100 101 /** 102 * Logs any login exception.<p> 103 * 104 * @param requestContext the request context 105 * @param userName the user name 106 * @param currentLoginException the exception to log 107 */ 108 public static void logLoginException( 109 CmsRequestContext requestContext, 110 String userName, 111 CmsException currentLoginException) { 112 113 if (currentLoginException instanceof CmsAuthentificationException) { 114 115 // the authentication of the user failed 116 if (org.opencms.security.Messages.ERR_LOGIN_FAILED_DISABLED_2 == currentLoginException.getMessageContainer().getKey()) { 117 118 // the user has been disabled 119 LOG.warn( 120 Messages.get().getBundle().key( 121 Messages.LOG_LOGIN_FAILED_DISABLED_3, 122 userName, 123 requestContext.addSiteRoot(requestContext.getUri()), 124 requestContext.getRemoteAddress())); 125 126 } else if (org.opencms.security.Messages.ERR_LOGIN_FAILED_TEMP_DISABLED_4 == currentLoginException.getMessageContainer().getKey()) { 127 128 // the user has been disabled 129 LOG.warn( 130 Messages.get().getBundle().key( 131 Messages.LOG_LOGIN_FAILED_TEMP_DISABLED_5, 132 new Object[] { 133 userName, 134 requestContext.addSiteRoot(requestContext.getUri()), 135 requestContext.getRemoteAddress(), 136 currentLoginException.getMessageContainer().getArgs()[2], 137 currentLoginException.getMessageContainer().getArgs()[3]})); 138 139 } else if (org.opencms.security.Messages.ERR_LOGIN_FAILED_NO_USER_2 == currentLoginException.getMessageContainer().getKey()) { 140 141 // the requested user does not exist in the database 142 LOG.warn( 143 Messages.get().getBundle().key( 144 Messages.LOG_LOGIN_FAILED_NO_USER_3, 145 userName, 146 requestContext.addSiteRoot(requestContext.getUri()), 147 requestContext.getRemoteAddress())); 148 149 } else if (org.opencms.security.Messages.ERR_LOGIN_FAILED_WITH_MESSAGE_1 == currentLoginException.getMessageContainer().getKey()) { 150 151 // logins have been disabled by the administration 152 long endTime = CmsLoginMessage.DEFAULT_TIME_END; 153 if (OpenCms.getLoginManager().getLoginMessage() != null) { 154 endTime = OpenCms.getLoginManager().getLoginMessage().getTimeEnd(); 155 } 156 LOG.info( 157 Messages.get().getBundle().key( 158 Messages.LOG_LOGIN_FAILED_WITH_MESSAGE_4, 159 new Object[] { 160 userName, 161 requestContext.addSiteRoot(requestContext.getUri()), 162 requestContext.getRemoteAddress(), 163 new Date(endTime)})); 164 165 } else { 166 167 // the user exists, so the password must have been wrong 168 CmsMessageContainer message = Messages.get().container( 169 Messages.LOG_LOGIN_FAILED_3, 170 userName, 171 requestContext.addSiteRoot(requestContext.getUri()), 172 requestContext.getRemoteAddress()); 173 if (OpenCms.getDefaultUsers().isUserAdmin(userName)) { 174 // someone tried to log in as "Admin", log this in a higher channel 175 LOG.error(message.key()); 176 } else { 177 LOG.warn(message.key()); 178 } 179 } 180 } else { 181 // the error was database related, there may be an issue with the setup 182 // write the exception to the log as well 183 LOG.error( 184 Messages.get().getBundle().key( 185 Messages.LOG_LOGIN_FAILED_DB_REASON_3, 186 userName, 187 requestContext.addSiteRoot(requestContext.getUri()), 188 requestContext.getRemoteAddress()), 189 currentLoginException); 190 } 191 } 192 193 /** 194 * Returns the link to the form that contains the login element.<p> 195 * 196 * @return the link to the form that contains the login element 197 */ 198 public String getFormLink() { 199 200 return link(getRequestContext().getUri()); 201 } 202 203 /** 204 * Returns the exception that was thrown after login, 205 * or null if no Exception was thrown (i.e. login was successful 206 * or not attempted).<p> 207 * 208 * @return the exception thrown after login 209 */ 210 public CmsException getLoginException() { 211 212 return m_loginException; 213 } 214 215 /** 216 * Gets the currently set logout target. 217 * 218 * <p>If this is null, the current login form URI is used as the logout target. 219 * 220 * @return 221 */ 222 public String getLogoutTarget() { 223 224 return m_logoutTarget; 225 } 226 227 /** 228 * Returns the currently logged in user.<p> 229 * 230 * @return the currently logged in user 231 */ 232 public CmsUser getUser() { 233 234 return getRequestContext().getCurrentUser(); 235 } 236 237 /** 238 * Returns the user name of the currently logged in user.<p> 239 * 240 * @return the user name of the currently logged in user 241 */ 242 public String getUserName() { 243 244 return getRequestContext().getCurrentUser().getName(); 245 } 246 247 /** 248 * Returns true if the current user is not the guest user, 249 * i.e. if he already has logged in with some other user account.<p> 250 * 251 * @return true if the current user is already logged in 252 */ 253 public boolean isLoggedIn() { 254 255 return !getCmsObject().getRequestContext().getCurrentUser().isGuestUser(); 256 } 257 258 /** 259 * Indicates if a login was successful or not.<p> 260 * 261 * @return true if the login was successful 262 */ 263 public boolean isLoginSuccess() { 264 265 return (m_loginException == null); 266 } 267 268 /** 269 * Logs a system user in to OpenCms.<p> 270 * 271 * @param userName the users name 272 * @param password the password 273 */ 274 public void login(String userName, String password) { 275 276 login(userName, password, null); 277 } 278 279 /** 280 * Logs a system user into OpenCms.<p> 281 * 282 * Note that if a login project name is provided, this project must exist, 283 * otherwise the login is regarded as a failure even if the user data was correct.<p> 284 * 285 * @param userName the users name 286 * @param password the password 287 * @param projectName the project to switch to after login (if null project is not switched) 288 */ 289 public void login(String userName, String password, String projectName) { 290 291 HttpSession session = null; 292 m_loginException = null; 293 try { 294 295 // login the user and create a new session 296 CmsUser user = getCmsObject().readUser(userName); 297 OpenCms.getSessionManager().checkCreateSessionForUser(user); 298 CmsSecondFactorInfo secondFactorInfo = null; 299 if (m_verificationCode != null) { 300 secondFactorInfo = new CmsSecondFactorInfo(m_verificationCode); 301 } 302 getCmsObject().loginUser(userName, password, secondFactorInfo, getRequestContext().getRemoteAddress()); 303 304 // make sure we have a new session after login for security reasons 305 session = getRequest().getSession(false); 306 if (session != null) { 307 session.invalidate(); 308 } 309 session = getRequest().getSession(true); 310 if (projectName != null) { 311 // if this fails, the login is regarded as a failure as well 312 getCmsObject().getRequestContext().setCurrentProject(getCmsObject().readProject(projectName)); 313 } 314 if (!getCmsObject().getRequestContext().getCurrentProject().isOnlineProject()) { 315 // in case the user is logged into an offline project, send any available login message 316 CmsLoginMessage loginMessage = OpenCms.getLoginManager().getLoginMessage(); 317 if ((loginMessage != null) && loginMessage.isActive()) { 318 OpenCms.getSessionManager().updateSessionInfo(getCmsObject(), getRequest()); 319 OpenCms.getSessionManager().sendBroadcast(null, loginMessage.getMessage(), user, ContentMode.html); 320 } 321 } 322 323 } catch (CmsException e) { 324 // the login has failed 325 m_loginException = e; 326 } 327 if (m_loginException == null) { 328 // login was successful 329 if (LOG.isInfoEnabled()) { 330 LOG.info( 331 Messages.get().getBundle().key( 332 Messages.LOG_LOGIN_SUCCESSFUL_3, 333 userName, 334 getRequestContext().addSiteRoot(getRequestContext().getUri()), 335 getRequestContext().getRemoteAddress())); 336 } 337 CmsUserLog.logLogin(getCmsObject(), userName); 338 } else { 339 // login was not successful 340 if (session != null) { 341 session.invalidate(); 342 } 343 CmsException currentLoginException = m_loginException; 344 logLoginException(getRequestContext(), userName, currentLoginException); 345 CmsUserLog.logLoginFailure(getCmsObject(), userName); 346 } 347 } 348 349 /** 350 * Logs a system user in to OpenCms.<p> 351 * 352 * Note that if a login project name is provided, this project must exist, 353 * otherwise the login is regarded as a failure even if the user data was correct.<p> 354 * 355 * @param userName the users name 356 * @param password the password 357 * @param projectName the project to switch to after login (if null project is not switched) 358 * @param redirectUri the URI to redirect to after login (if null the current URI is used) 359 * 360 * @throws IOException in case redirect after login was not successful 361 */ 362 public void login(String userName, String password, String projectName, String redirectUri) throws IOException { 363 364 login(userName, password, projectName); 365 if (m_loginException == null) { 366 try { 367 URI uriObj = new URI(redirectUri); 368 if (uriObj.getScheme() != null) { 369 LOG.warn("Absolute URL not allowed as redirect URI: " + redirectUri); 370 return; 371 } 372 } catch (Exception e) { 373 LOG.warn("Invalid redirect URI " + redirectUri + " in login bean: " + e.getLocalizedMessage(), e); 374 return; 375 } 376 if (redirectUri != null) { 377 getResponse().sendRedirect( 378 OpenCms.getLinkManager().substituteLink(getCmsObject(), redirectUri, null, true)); 379 } else { 380 getResponse().sendRedirect(getFormLink()); 381 } 382 } 383 } 384 385 /** 386 * Logs a user out, i.e. destroys the current users session, 387 * after that the current page will be redirected to itself one time to ensure that 388 * the users session is truly destroyed.<p> 389 * 390 * @throws IOException if redirect after logout fails 391 */ 392 public void logout() throws IOException { 393 394 String loggedInUserName = getRequestContext().getCurrentUser().getName(); 395 HttpSession session = getRequest().getSession(false); 396 String logoutTarget = m_logoutTarget != null ? m_logoutTarget : getFormLink(); 397 String redirectUri = OpenCms.getAuthorizationHandler().getLogoutRedirectUri( 398 getCmsObject(), 399 getRequest(), 400 logoutTarget); 401 if (session != null) { 402 session.invalidate(); 403 /* we need this because a new session might be created after this method, 404 but before the session info is updated in OpenCmsCore.showResource. */ 405 getCmsObject().getRequestContext().setUpdateSessionEnabled(false); 406 } 407 // logout was successful 408 if (LOG.isInfoEnabled()) { 409 LOG.info( 410 Messages.get().getBundle().key( 411 Messages.LOG_LOGOUT_SUCCESFUL_3, 412 loggedInUserName, 413 getRequestContext().addSiteRoot(getRequestContext().getUri()), 414 getRequestContext().getRemoteAddress())); 415 } 416 CmsUserLog.logLogout(getCmsObject()); 417 getResponse().sendRedirect(redirectUri); 418 419 } 420 421 /** 422 * Manually sets a URI that should be redirected to after logout. 423 * 424 * @param logoutTarget the logout target 425 */ 426 public void setLogoutTarget(String logoutTarget) { 427 428 m_logoutTarget = logoutTarget; 429 430 } 431 432 /** 433 * Sets the verification code for two-factor authentication. 434 * 435 * @param code the verification code 436 */ 437 public void setVerificationCode(String code) { 438 439 m_verificationCode = code; 440 } 441}