chromium/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordAccessReauthenticationHelper.java

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.password_manager.settings;

import android.content.Context;
import android.view.View;

import androidx.annotation.IntDef;
import androidx.fragment.app.FragmentManager;

import org.chromium.base.Callback;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.device_reauth.ReauthResult;
import org.chromium.chrome.browser.password_manager.R;
import org.chromium.chrome.browser.password_manager.settings.ReauthenticationManager.ReauthScope;
import org.chromium.ui.widget.Toast;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * A helper to perform a user's reauthentication for a specific {@link ReauthReason}. Only a single
 * reauthentication can happen at a given time.
 */
public class PasswordAccessReauthenticationHelper {
    public static final String SETTINGS_REAUTHENTICATION_HISTOGRAM =
            "PasswordManager.ReauthToAccessPasswordInSettings";

    /**
     * The reason for the reauthentication.
     *
     * <p>TODO(crbug.com/40170183): Remove the edit reason once the password check credential editor
     * is completely replaced with the new one.
     */
    @IntDef({ReauthReason.VIEW_PASSWORD, ReauthReason.EDIT_PASSWORD, ReauthReason.COPY_PASSWORD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface ReauthReason {
        /** A reauthentication is required for viewing a password. */
        int VIEW_PASSWORD = 0;

        /** A reauthentication is required for editing a password. */
        int EDIT_PASSWORD = 1;

        /** Reauthentication is required in order to copy a password. */
        int COPY_PASSWORD = 2;
    }

    private final Context mContext;
    private final FragmentManager mFragmentManager;
    private Callback<Boolean> mCallback;

    public PasswordAccessReauthenticationHelper(Context context, FragmentManager fragmentManager) {
        mContext = context;
        mFragmentManager = fragmentManager;
    }

    public boolean canReauthenticate() {
        return ReauthenticationManager.isScreenLockSetUp(mContext);
    }

    /**
     * Asks the user to reauthenticate. Requires {@link #canReauthenticate()}.
     * @param reason The {@link ReauthReason} for the reauth.
     * @param callback A {@link Callback}. Will invoke {@link Callback#onResult} with whether the
     *         user passed or dismissed the reauth screen.
     */
    public void reauthenticate(@ReauthReason int reason, Callback<Boolean> callback) {
        assert canReauthenticate();
        assert mCallback == null;

        // Invoke the handler immediately if an authentication is still valid.
        if (ReauthenticationManager.authenticationStillValid(ReauthScope.ONE_AT_A_TIME)) {
            RecordHistogram.recordEnumeratedHistogram(
                    SETTINGS_REAUTHENTICATION_HISTOGRAM,
                    ReauthResult.SKIPPED,
                    ReauthResult.MAX_VALUE + 1);

            callback.onResult(true);
            return;
        }

        mCallback = callback;

        switch (reason) {
            case ReauthReason.VIEW_PASSWORD:
                ReauthenticationManager.displayReauthenticationFragment(
                        R.string.lockscreen_description_view,
                        View.NO_ID,
                        mFragmentManager,
                        ReauthScope.ONE_AT_A_TIME);
                break;
            case ReauthReason.EDIT_PASSWORD:
                ReauthenticationManager.displayReauthenticationFragment(
                        R.string.lockscreen_description_edit,
                        View.NO_ID,
                        mFragmentManager,
                        ReauthScope.ONE_AT_A_TIME);
                break;
            case ReauthReason.COPY_PASSWORD:
                ReauthenticationManager.displayReauthenticationFragment(
                        R.string.lockscreen_description_copy,
                        View.NO_ID,
                        mFragmentManager,
                        ReauthScope.ONE_AT_A_TIME);
                break;
        }
    }

    /**
     * Shows a toast to the user nudging them to set up a screen lock. Intended to be called in case
     * {@link #canReauthenticate()} returns false.
     */
    public void showScreenLockToast(@ReauthReason int reason) {
        if (reason == ReauthReason.COPY_PASSWORD) {
            Toast.makeText(
                            mContext,
                            R.string.password_entry_copy_set_screen_lock,
                            Toast.LENGTH_LONG)
                    .show();
            return;
        }
        Toast.makeText(mContext, R.string.password_entry_view_set_screen_lock, Toast.LENGTH_LONG)
                .show();
    }

    /**
     * Invoked when a reauthentication might have happened. Invokes {@link Callback#onResult}
     * with whether the user passed the reauthentication challenge.
     * No-op if {@link #mCallback} is null.
     */
    public void onReauthenticationMaybeHappened() {
        if (mCallback != null) {
            mCallback.onResult(
                    ReauthenticationManager.authenticationStillValid(ReauthScope.ONE_AT_A_TIME));
            mCallback = null;
        }
    }
}