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 * &lt;jsp:useBean id="cmslogin" class="org.opencms.jsp.CmsJspLoginBean"&gt;
059 * &lt% cmslogin.init(pageContext, request, response); %&gt;
060 * &lt;/jsp:useBean&gt;
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}