// 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.
#include "chrome/browser/certificate_provider/pin_dialog_manager.h"
#include <vector>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/logging.h"
namespace chromeos {
// Define timeout for issued sign_request_id.
constexpr base::TimeDelta kSignRequestIdTimeout = base::Minutes(10);
PinDialogManager::PinDialogManager() = default;
PinDialogManager::~PinDialogManager() = default;
void PinDialogManager::AddSignRequestId(
const std::string& extension_id,
int sign_request_id,
const std::optional<AccountId>& authenticating_user_account_id) {
ExtensionNameRequestIdPair key(extension_id, sign_request_id);
sign_requests_.insert(
std::make_pair(key, SignRequestState(/*begin_time=*/base::Time::Now(),
authenticating_user_account_id)));
}
void PinDialogManager::RemoveSignRequest(const std::string& extension_id,
int sign_request_id) {
if (active_dialog_state_ &&
active_dialog_state_->extension_id == extension_id &&
active_dialog_state_->sign_request_id == sign_request_id) {
CloseActiveDialog();
}
ExtensionNameRequestIdPair key(extension_id, sign_request_id);
sign_requests_.erase(key);
}
int PinDialogManager::StoredSignRequestsForTesting() const {
return sign_requests_.size();
}
PinDialogManager::RequestPinResult PinDialogManager::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) {
DCHECK_GE(attempts_left, -1);
const bool accept_input = (attempts_left != 0);
// Check the validity of sign_request_id.
const SignRequestState* const sign_request_state =
FindSignRequestState(extension_id, sign_request_id);
if (!sign_request_state)
return RequestPinResult::kInvalidId;
// Start from sanity checks, as the extension might have issued this call
// incorrectly.
if (active_dialog_state_) {
// The active dialog exists already, so we need to make sure it belongs to
// the same extension and the user submitted some input.
if (extension_id != active_dialog_state_->extension_id)
return RequestPinResult::kOtherFlowInProgress;
if (active_dialog_state_->request_pin_callback ||
active_dialog_state_->stop_pin_request_callback) {
// Extension requests a PIN without having received any input from its
// previous request. Reject the new request.
return RequestPinResult::kDialogDisplayedAlready;
}
} else {
// Check that the sign request hasn't timed out yet.
const base::Time current_time = base::Time::Now();
if (current_time - sign_request_state->begin_time > kSignRequestIdTimeout)
return RequestPinResult::kInvalidId;
// A new dialog will be opened, so initialize the related internal state.
active_dialog_state_.emplace(GetHostForNewDialog(), extension_id,
extension_name, sign_request_id, code_type);
}
active_dialog_state_->request_pin_callback = std::move(callback);
active_dialog_state_->host->ShowSecurityTokenPinDialog(
extension_name, code_type, accept_input, error_label, attempts_left,
sign_request_state->authenticating_user_account_id,
base::BindOnce(&PinDialogManager::OnPinEntered,
weak_factory_.GetWeakPtr()),
base::BindOnce(&PinDialogManager::OnPinDialogClosed,
weak_factory_.GetWeakPtr()));
return RequestPinResult::kSuccess;
}
PinDialogManager::StopPinRequestResult
PinDialogManager::StopPinRequestWithError(
const std::string& extension_id,
security_token_pin::ErrorLabel error_label,
StopPinRequestCallback callback) {
DCHECK_NE(error_label, security_token_pin::ErrorLabel::kNone);
// Perform sanity checks, as the extension might have issued this call
// incorrectly.
if (!active_dialog_state_ ||
active_dialog_state_->extension_id != extension_id) {
return StopPinRequestResult::kNoActiveDialog;
}
if (active_dialog_state_->request_pin_callback ||
active_dialog_state_->stop_pin_request_callback) {
return StopPinRequestResult::kNoUserInput;
}
const SignRequestState* const sign_request_state =
FindSignRequestState(extension_id, active_dialog_state_->sign_request_id);
if (!sign_request_state)
return StopPinRequestResult::kNoActiveDialog;
active_dialog_state_->stop_pin_request_callback = std::move(callback);
active_dialog_state_->host->ShowSecurityTokenPinDialog(
active_dialog_state_->extension_name, active_dialog_state_->code_type,
/*enable_user_input=*/false, error_label,
/*attempts_left=*/-1, sign_request_state->authenticating_user_account_id,
base::BindOnce(&PinDialogManager::OnPinEntered,
weak_factory_.GetWeakPtr()),
base::BindOnce(&PinDialogManager::OnPinDialogClosed,
weak_factory_.GetWeakPtr()));
return StopPinRequestResult::kSuccess;
}
bool PinDialogManager::LastPinDialogClosed(
const std::string& extension_id) const {
auto iter = last_response_closed_.find(extension_id);
return iter != last_response_closed_.end() && iter->second;
}
bool PinDialogManager::CloseDialog(const std::string& extension_id) {
// Perform sanity checks, as the extension might have issued this call
// incorrectly.
if (!active_dialog_state_ ||
extension_id != active_dialog_state_->extension_id) {
LOG(ERROR) << "StopPinRequest called by unexpected extension: "
<< extension_id;
return false;
}
CloseActiveDialog();
return true;
}
void PinDialogManager::ExtensionUnloaded(const std::string& extension_id) {
if (active_dialog_state_ &&
active_dialog_state_->extension_id == extension_id) {
CloseActiveDialog();
}
last_response_closed_[extension_id] = false;
for (auto it = sign_requests_.cbegin(); it != sign_requests_.cend();) {
if (it->first.first == extension_id)
sign_requests_.erase(it++);
else
++it;
}
}
void PinDialogManager::AddPinDialogHost(
SecurityTokenPinDialogHost* pin_dialog_host) {
DCHECK(!base::Contains(added_dialog_hosts_, pin_dialog_host));
added_dialog_hosts_.push_back(pin_dialog_host);
}
void PinDialogManager::RemovePinDialogHost(
SecurityTokenPinDialogHost* pin_dialog_host) {
if (active_dialog_state_ && active_dialog_state_->host == pin_dialog_host)
CloseActiveDialog();
DCHECK(base::Contains(added_dialog_hosts_, pin_dialog_host));
std::erase(added_dialog_hosts_, pin_dialog_host);
}
PinDialogManager::SignRequestState::SignRequestState(
base::Time begin_time,
const std::optional<AccountId>& authenticating_user_account_id)
: begin_time(begin_time),
authenticating_user_account_id(authenticating_user_account_id) {}
PinDialogManager::SignRequestState::SignRequestState(const SignRequestState&) =
default;
PinDialogManager::SignRequestState&
PinDialogManager::SignRequestState::operator=(const SignRequestState&) =
default;
PinDialogManager::SignRequestState::~SignRequestState() = default;
PinDialogManager::ActiveDialogState::ActiveDialogState(
SecurityTokenPinDialogHost* host,
const std::string& extension_id,
const std::string& extension_name,
int sign_request_id,
security_token_pin::CodeType code_type)
: host(host),
extension_id(extension_id),
extension_name(extension_name),
sign_request_id(sign_request_id),
code_type(code_type) {}
PinDialogManager::ActiveDialogState::~ActiveDialogState() = default;
PinDialogManager::SignRequestState* PinDialogManager::FindSignRequestState(
const std::string& extension_id,
int sign_request_id) {
const ExtensionNameRequestIdPair key(extension_id, sign_request_id);
const auto sign_request_iter = sign_requests_.find(key);
if (sign_request_iter == sign_requests_.end())
return nullptr;
return &sign_request_iter->second;
}
void PinDialogManager::OnPinEntered(const std::string& user_input) {
DCHECK(!active_dialog_state_->stop_pin_request_callback);
last_response_closed_[active_dialog_state_->extension_id] = false;
if (active_dialog_state_->request_pin_callback)
std::move(active_dialog_state_->request_pin_callback).Run(user_input);
}
void PinDialogManager::OnPinDialogClosed() {
DCHECK(!active_dialog_state_->request_pin_callback ||
!active_dialog_state_->stop_pin_request_callback);
last_response_closed_[active_dialog_state_->extension_id] = true;
if (active_dialog_state_->request_pin_callback) {
std::move(active_dialog_state_->request_pin_callback)
.Run(/*user_input=*/std::string());
}
if (active_dialog_state_->stop_pin_request_callback)
std::move(active_dialog_state_->stop_pin_request_callback).Run();
active_dialog_state_.reset();
}
SecurityTokenPinDialogHost* PinDialogManager::GetHostForNewDialog() {
if (added_dialog_hosts_.empty())
return &default_dialog_host_;
return added_dialog_hosts_.back();
}
void PinDialogManager::CloseActiveDialog() {
if (!active_dialog_state_)
return;
// Ignore any further callbacks from the host. Instead of relying on the host
// to call the closing callback, run OnPinDialogClosed() below explicitly.
weak_factory_.InvalidateWeakPtrs();
active_dialog_state_->host->CloseSecurityTokenPinDialog();
OnPinDialogClosed();
DCHECK(!active_dialog_state_);
}
} // namespace chromeos