// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_CERTIFICATE_PROVIDER_PIN_DIALOG_MANAGER_H_
#define CHROME_BROWSER_CERTIFICATE_PROVIDER_PIN_DIALOG_MANAGER_H_
#include <map>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "chrome/browser/certificate_provider/security_token_pin_dialog_host.h"
#include "chrome/browser/certificate_provider/security_token_pin_dialog_host_popup_impl.h"
#include "chromeos/components/security_token_pin/constants.h"
#include "components/account_id/account_id.h"
namespace chromeos {
// Manages the state of the dialog that requests the PIN from user. Used by the
// extensions that need to request the PIN. Implemented as requirement for
// crbug.com/612886
class PinDialogManager final {
public:
enum class RequestPinResult {
kSuccess,
kInvalidId,
kOtherFlowInProgress,
kDialogDisplayedAlready,
};
enum class StopPinRequestResult {
kSuccess,
kNoActiveDialog,
kNoUserInput,
};
using RequestPinCallback =
base::OnceCallback<void(const std::string& user_input)>;
using StopPinRequestCallback = base::OnceClosure;
PinDialogManager();
PinDialogManager(const PinDialogManager&) = delete;
PinDialogManager& operator=(const PinDialogManager&) = delete;
~PinDialogManager();
// Stores internally the |signRequestId| along with current timestamp.
void AddSignRequestId(
const std::string& extension_id,
int sign_request_id,
const std::optional<AccountId>& authenticating_user_account_id);
// Removes the specified sign request, aborting both the current and the
// future PIN dialogs related to it.
void RemoveSignRequest(const std::string& extension_id, int sign_request_id);
// Returns the number of pending sign requests stored in sign_requests_
int StoredSignRequestsForTesting() const;
// Creates and displays a new PIN dialog, or reuses the old dialog with just
// updating the parameters if active one exists.
// |extension_id| - the ID of the extension requesting the dialog.
// |extension_name| - the name of the extension requesting the dialog.
// |sign_request_id| - the ID given by Chrome when the extension was asked to
// sign the data. It should be a valid, not expired ID at the time the
// extension is requesting PIN the first time.
// |code_type| - the type of input requested: either "PIN" or "PUK".
// |error_label| - the error template to be displayed inside the dialog. If
// |kNone|, no error is displayed.
// |attempts_left| - the number of attempts the user has to try the code. It
// is informational only, and enforced on Chrome side only in case it's
// zero. In that case the textfield is disabled and the user can't provide
// any input to extension. If -1 the textfield from the dialog is enabled
// but no information about the attepts left is not given to the user.
// |callback| - used to notify about the user input in the text_field from the
// dialog.
// Returns |kSuccess| if the dialog is displayed and extension owns it.
// Otherwise the specific error is returned.
RequestPinResult RequestPin(const std::string& extension_id,
const std::string& extension_name,
int sign_request_id,
security_token_pin::CodeType code_type,
security_token_pin::ErrorLabel error_label,
int attempts_left,
RequestPinCallback callback);
// Updates the existing dialog with the error message. Returns whether the
// provided |extension_id| matches the extension owning the active dialog.
// When it is, the |callback| will be executed once the UI is completed (e.g.,
// the dialog with the error message is closed by the user).
StopPinRequestResult StopPinRequestWithError(
const std::string& extension_id,
security_token_pin::ErrorLabel error_label,
StopPinRequestCallback callback);
// Returns whether the last PIN dialog from this extension was closed by the
// user.
bool LastPinDialogClosed(const std::string& extension_id) const;
// Called when extension calls the stopPinRequest method. The active dialog is
// closed if the |extension_id| matches the |active_dialog_extension_id_|.
// Returns whether the dialog was closed.
bool CloseDialog(const std::string& extension_id);
// Resets the manager data related to the extension.
void ExtensionUnloaded(const std::string& extension_id);
// Dynamically adds the dialog host that can be used by this instance for
// showing new dialogs. There may be multiple hosts added, in which case the
// most recently added is used. Before any hosts have been added, the default
// (popup-based) host is used.
void AddPinDialogHost(SecurityTokenPinDialogHost* pin_dialog_host);
// Removes the previously added dialog host. If a dialog is still opened in
// this host, closes it beforehand.
void RemovePinDialogHost(SecurityTokenPinDialogHost* pin_dialog_host);
SecurityTokenPinDialogHostPopupImpl* default_dialog_host_for_testing() {
return &default_dialog_host_;
}
private:
struct SignRequestState {
SignRequestState(
base::Time begin_time,
const std::optional<AccountId>& authenticating_user_account_id);
SignRequestState(const SignRequestState&);
SignRequestState& operator=(const SignRequestState&);
~SignRequestState();
base::Time begin_time;
std::optional<AccountId> authenticating_user_account_id;
};
// Holds information related to the currently opened PIN dialog.
struct ActiveDialogState {
ActiveDialogState(SecurityTokenPinDialogHost* host,
const std::string& extension_id,
const std::string& extension_name,
int sign_request_id,
security_token_pin::CodeType code_type);
~ActiveDialogState();
// Remember the host that was used to open the active dialog, as new hosts
// could have been added since the dialog was opened, but we want to
// continue calling the same host when dealing with the same active dialog.
const raw_ptr<SecurityTokenPinDialogHost> host;
const std::string extension_id;
const std::string extension_name;
const int sign_request_id;
const security_token_pin::CodeType code_type;
RequestPinCallback request_pin_callback;
StopPinRequestCallback stop_pin_request_callback;
};
using ExtensionNameRequestIdPair = std::pair<std::string, int>;
// Returns the sign request state for the given key, or null if not found.
SignRequestState* FindSignRequestState(const std::string& extension_id,
int sign_request_id);
// The callback that gets invoked once the user sends some input into the PIN
// dialog.
void OnPinEntered(const std::string& user_input);
// The callback that gets invoked once the PIN dialog gets closed.
void OnPinDialogClosed();
// Returns the dialog host that should own the new dialog. Currently returns
// the most recently added dialog host (falling back to the default one when
// no host has been added).
SecurityTokenPinDialogHost* GetHostForNewDialog();
// Closes the active dialog, if there's any, and runs the necessary callbacks.
void CloseActiveDialog();
// Tells whether user closed the last request PIN dialog issued by an
// extension. The extension_id is the key and value is true if user closed the
// dialog. Used to determine if the limit of dialogs rejected by the user has
// been exceeded.
std::unordered_map<std::string, bool> last_response_closed_;
// The map from extension_id and an active sign request id to the state of the
// request.
std::map<ExtensionNameRequestIdPair, SignRequestState> sign_requests_;
SecurityTokenPinDialogHostPopupImpl default_dialog_host_;
// The list of dynamically added dialog hosts, in the same order as they were
// added.
std::vector<raw_ptr<SecurityTokenPinDialogHost, VectorExperimental>>
added_dialog_hosts_;
// There can be only one active dialog to request the PIN at any point of
// time.
std::optional<ActiveDialogState> active_dialog_state_;
base::WeakPtrFactory<PinDialogManager> weak_factory_{this};
};
} // namespace chromeos
#endif // CHROME_BROWSER_CERTIFICATE_PROVIDER_PIN_DIALOG_MANAGER_H_