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.main.CmsLog;
031import org.opencms.main.OpenCms;
032import org.opencms.security.twofactor.CmsSecondFactorInfo;
033import org.opencms.security.twofactor.CmsSecondFactorSetupInfo;
034import org.opencms.ui.A_CmsUI;
035import org.opencms.ui.CmsVaadinUtils;
036import org.opencms.ui.Messages;
037import org.opencms.ui.apps.user.CmsAccountsApp;
038import org.opencms.ui.components.CmsBasicDialog;
039import org.opencms.ui.components.CmsResourceInfo;
040import org.opencms.ui.login.CmsLoginController.LoginContext;
041import org.opencms.ui.login.CmsLoginController.LoginContinuation;
042
043import java.util.Collections;
044import java.util.HashMap;
045import java.util.Locale;
046
047import org.apache.commons.logging.Log;
048
049import com.vaadin.event.ShortcutAction.KeyCode;
050import com.vaadin.event.ShortcutListener;
051import com.vaadin.server.ExternalResource;
052import com.vaadin.server.UserError;
053import com.vaadin.ui.Button;
054import com.vaadin.ui.Image;
055import com.vaadin.ui.Label;
056import com.vaadin.ui.TextField;
057
058/**
059 * Dialog used to set up two-factor authentication for a user.
060 *
061 * <p>The dialog contains both a QR code used to share a secret code with the user that they can scan with an authenticator app,
062 * as well as an input field to enter a verification code generated using that app. Having the user enter a verification code in the
063 * setup phase proves that they have actually added the secret to their authenticator app, i.e. not just clicked OK on a dialog they do
064 * not understand.
065 */
066public class CmsSecondFactorSetupDialog extends CmsBasicDialog {
067
068    /** Logger instance for this class. */
069    private static final Log LOG = CmsLog.getLog(CmsSecondFactorSetupDialog.class);
070
071    /** Serial version id. */
072    private static final long serialVersionUID = 1L;
073
074    /** The login context object. */
075    private LoginContext m_context;
076
077    /** The handler to call when we can continue with the login process. */
078    private LoginContinuation m_continuation;
079
080    /** The description label. */
081    private Label m_description;
082
083    /** The OK button. */
084    private Button m_okButton;
085
086    /** The image used to display the QR code. */
087    private Image m_qrCodeImage;
088
089    /** The shared secret. */
090    private String m_secret;
091
092    /** The label to display the shared secret. */
093    private Label m_secretLabel;
094
095    /** The text input for entering the verification code. */
096    private TextField m_verification;
097
098    /**
099     * Creates a new instance.
100     *
101     * @param context the login context
102     * @param continuation the handler to call to proceed with the login process
103     */
104    public CmsSecondFactorSetupDialog(
105        CmsLoginController.LoginContext context,
106        CmsLoginController.LoginContinuation continuation) {
107
108        CmsVaadinUtils.readAndLocalizeDesign(this, CmsVaadinUtils.getWpMessagesForCurrentLocale(), new HashMap<>());
109        CmsResourceInfo userInfo = CmsAccountsApp.getPrincipalInfo(context.getUser());
110        userInfo.setTopLineText(context.getUser().getFullName());
111        displayResourceInfoDirectly(Collections.singletonList(userInfo));
112        setWidth("800px");
113        m_context = context;
114        m_continuation = continuation;
115        CmsSecondFactorSetupInfo info = OpenCms.getTwoFactorAuthenticationHandler().generateSetupInfo(
116            context.getUser());
117        Locale locale = A_CmsUI.get().getLocale();
118        String specialDescription = OpenCms.getTwoFactorAuthenticationHandler().getSetupMessage(locale);
119        if (specialDescription != null) {
120            m_description.setValue(specialDescription);
121        }
122
123        m_qrCodeImage.setSource(new ExternalResource(info.getQrCodeImageUrl()));
124        m_secret = info.getSecret();
125        m_secretLabel.setValue(info.getSecret());
126        m_okButton.addClickListener(event -> submit());
127        m_verification.addStyleName(CmsSecondFactorDialog.CLASS_VERIFICATION_CODE_FIELD);
128        m_verification.addShortcutListener(new ShortcutListener(null, KeyCode.ENTER, null) {
129
130            private static final long serialVersionUID = 1L;
131
132            @Override
133            public void handleAction(Object sender, Object target) {
134
135                submit();
136            }
137        });
138        addAttachListener(event -> {
139            m_verification.focus();
140            CmsSecondFactorDialog.initVerificationField();
141
142        });
143    }
144
145    /**
146     * Executed when the user clicks OK.
147     */
148    protected void submit() {
149
150        m_verification.setComponentError(null);
151        String verificationCode = m_verification.getValue().trim();
152        CmsSecondFactorInfo secondFactorInfo = new CmsSecondFactorInfo(m_secret, verificationCode);
153        if (!OpenCms.getTwoFactorAuthenticationHandler().verifySecondFactorSetup(secondFactorInfo)) {
154            String message = CmsVaadinUtils.getMessageText(Messages.GUI_LOGIN_2FA_SETUP_INVALID_CODE_0);
155            m_verification.setComponentError(new UserError(message));
156            return;
157        }
158        m_context.setSecondFactorInfo(secondFactorInfo);
159        CmsVaadinUtils.closeWindow(this);
160        try {
161            m_continuation.continueLogin(m_context);
162        } catch (Exception e) {
163            LOG.error(e.getLocalizedMessage(), e);
164        }
165
166    }
167}