| // 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 |