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