blob: bf3bfc9ca30850ad708857dfaf8f6bcfdc382c7a [file] [log] [blame]
Avi Drissman4a8573c2022-09-09 19:35:541// Copyright 2016 The Chromium Authors
igorcovbb898fb2016-12-01 16:07:562// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Oleksandr Kulkov9c7cf9552022-06-02 12:25:435#include "chrome/browser/certificate_provider/pin_dialog_manager.h"
igorcovbb898fb2016-12-01 16:07:566
Andrew Rayskiy53c55d12024-02-26 18:58:547#include <vector>
8
Jan Wilken Dörrieb5a41c32020-12-09 18:55:479#include "base/containers/contains.h"
Avi Drissman02e49e582023-01-07 01:23:1810#include "base/functional/bind.h"
Maksim Ivanov73dfec32019-08-01 22:43:1211#include "base/logging.h"
igorcovbb898fb2016-12-01 16:07:5612
Oleksandr Kulkov1213dee82022-05-17 20:45:3413namespace chromeos {
igorcovbb898fb2016-12-01 16:07:5614
15// Define timeout for issued sign_request_id.
Peter Kastinge5a38ed2021-10-02 03:06:3516constexpr base::TimeDelta kSignRequestIdTimeout = base::Minutes(10);
igorcovbb898fb2016-12-01 16:07:5617
Maksim Ivanov86866b72019-07-25 02:15:0618PinDialogManager::PinDialogManager() = default;
igorcovbb898fb2016-12-01 16:07:5619
Maksim Ivanov73dfec32019-08-01 22:43:1220PinDialogManager::~PinDialogManager() = default;
igorcovbb898fb2016-12-01 16:07:5621
Maksim Ivanov2b8136f2019-08-09 14:03:5922void PinDialogManager::AddSignRequestId(
23 const std::string& extension_id,
24 int sign_request_id,
Arthur Sonzognife132ee2024-01-15 11:01:0425 const std::optional<AccountId>& authenticating_user_account_id) {
igorcovbb898fb2016-12-01 16:07:5626 ExtensionNameRequestIdPair key(extension_id, sign_request_id);
Maksim Ivanov2b8136f2019-08-09 14:03:5927 sign_requests_.insert(
28 std::make_pair(key, SignRequestState(/*begin_time=*/base::Time::Now(),
29 authenticating_user_account_id)));
igorcovbb898fb2016-12-01 16:07:5630}
31
Oleksandr Kulkovd47d7452021-11-30 18:16:5932void PinDialogManager::RemoveSignRequest(const std::string& extension_id,
33 int sign_request_id) {
Maksim Ivanov7652d0d2019-09-04 19:12:1934 if (active_dialog_state_ &&
35 active_dialog_state_->extension_id == extension_id &&
36 active_dialog_state_->sign_request_id == sign_request_id) {
37 CloseActiveDialog();
38 }
39
40 ExtensionNameRequestIdPair key(extension_id, sign_request_id);
41 sign_requests_.erase(key);
42}
43
Oleksandr Kulkov2ba63f012021-12-15 15:07:4344int PinDialogManager::StoredSignRequestsForTesting() const {
45 return sign_requests_.size();
46}
47
Maksim Ivanov86866b72019-07-25 02:15:0648PinDialogManager::RequestPinResult PinDialogManager::RequestPin(
igorcovbb898fb2016-12-01 16:07:5649 const std::string& extension_id,
50 const std::string& extension_name,
51 int sign_request_id,
Fabian Sommer195d1f32020-02-27 01:51:0252 security_token_pin::CodeType code_type,
53 security_token_pin::ErrorLabel error_label,
igorcovbb898fb2016-12-01 16:07:5654 int attempts_left,
Maksim Ivanov86866b72019-07-25 02:15:0655 RequestPinCallback callback) {
Maksim Ivanov73dfec32019-08-01 22:43:1256 DCHECK_GE(attempts_left, -1);
57 const bool accept_input = (attempts_left != 0);
58
Maksim Ivanov2b8136f2019-08-09 14:03:5959 // Check the validity of sign_request_id.
60 const SignRequestState* const sign_request_state =
61 FindSignRequestState(extension_id, sign_request_id);
62 if (!sign_request_state)
63 return RequestPinResult::kInvalidId;
64
Maksim Ivanov73dfec32019-08-01 22:43:1265 // Start from sanity checks, as the extension might have issued this call
66 // incorrectly.
67 if (active_dialog_state_) {
68 // The active dialog exists already, so we need to make sure it belongs to
69 // the same extension and the user submitted some input.
70 if (extension_id != active_dialog_state_->extension_id)
Maksim Ivanov86866b72019-07-25 02:15:0671 return RequestPinResult::kOtherFlowInProgress;
Maksim Ivanov73dfec32019-08-01 22:43:1272 if (active_dialog_state_->request_pin_callback ||
73 active_dialog_state_->stop_pin_request_callback) {
74 // Extension requests a PIN without having received any input from its
75 // previous request. Reject the new request.
Maksim Ivanov86866b72019-07-25 02:15:0676 return RequestPinResult::kDialogDisplayedAlready;
Maksim Ivanov73dfec32019-08-01 22:43:1277 }
78 } else {
Maksim Ivanov2b8136f2019-08-09 14:03:5979 // Check that the sign request hasn't timed out yet.
Maksim Ivanov73dfec32019-08-01 22:43:1280 const base::Time current_time = base::Time::Now();
Maksim Ivanov2b8136f2019-08-09 14:03:5981 if (current_time - sign_request_state->begin_time > kSignRequestIdTimeout)
Maksim Ivanov73dfec32019-08-01 22:43:1282 return RequestPinResult::kInvalidId;
igorcovbb898fb2016-12-01 16:07:5683
Maksim Ivanov73dfec32019-08-01 22:43:1284 // A new dialog will be opened, so initialize the related internal state.
Maksim Ivanov5fd579032019-08-02 01:15:1585 active_dialog_state_.emplace(GetHostForNewDialog(), extension_id,
Maksim Ivanov2b8136f2019-08-09 14:03:5986 extension_name, sign_request_id, code_type);
igorcovbb898fb2016-12-01 16:07:5687 }
88
Maksim Ivanov73dfec32019-08-01 22:43:1289 active_dialog_state_->request_pin_callback = std::move(callback);
90 active_dialog_state_->host->ShowSecurityTokenPinDialog(
91 extension_name, code_type, accept_input, error_label, attempts_left,
Maksim Ivanov2b8136f2019-08-09 14:03:5992 sign_request_state->authenticating_user_account_id,
Maksim Ivanov73dfec32019-08-01 22:43:1293 base::BindOnce(&PinDialogManager::OnPinEntered,
94 weak_factory_.GetWeakPtr()),
95 base::BindOnce(&PinDialogManager::OnPinDialogClosed,
96 weak_factory_.GetWeakPtr()));
igorcovbb898fb2016-12-01 16:07:5697
Maksim Ivanov86866b72019-07-25 02:15:0698 return RequestPinResult::kSuccess;
igorcovbb898fb2016-12-01 16:07:5699}
100
Maksim Ivanov86866b72019-07-25 02:15:06101PinDialogManager::StopPinRequestResult
Maksim Ivanovb2985b12019-08-08 15:23:58102PinDialogManager::StopPinRequestWithError(
103 const std::string& extension_id,
Fabian Sommer195d1f32020-02-27 01:51:02104 security_token_pin::ErrorLabel error_label,
Maksim Ivanovb2985b12019-08-08 15:23:58105 StopPinRequestCallback callback) {
Fabian Sommer195d1f32020-02-27 01:51:02106 DCHECK_NE(error_label, security_token_pin::ErrorLabel::kNone);
Maksim Ivanov73dfec32019-08-01 22:43:12107
108 // Perform sanity checks, as the extension might have issued this call
109 // incorrectly.
110 if (!active_dialog_state_ ||
111 active_dialog_state_->extension_id != extension_id) {
Maksim Ivanov86866b72019-07-25 02:15:06112 return StopPinRequestResult::kNoActiveDialog;
igorcovbb898fb2016-12-01 16:07:56113 }
Maksim Ivanov73dfec32019-08-01 22:43:12114 if (active_dialog_state_->request_pin_callback ||
115 active_dialog_state_->stop_pin_request_callback) {
Maksim Ivanov86866b72019-07-25 02:15:06116 return StopPinRequestResult::kNoUserInput;
Maksim Ivanov73dfec32019-08-01 22:43:12117 }
igorcovbb898fb2016-12-01 16:07:56118
Maksim Ivanov2b8136f2019-08-09 14:03:59119 const SignRequestState* const sign_request_state =
120 FindSignRequestState(extension_id, active_dialog_state_->sign_request_id);
121 if (!sign_request_state)
122 return StopPinRequestResult::kNoActiveDialog;
123
Maksim Ivanov73dfec32019-08-01 22:43:12124 active_dialog_state_->stop_pin_request_callback = std::move(callback);
125 active_dialog_state_->host->ShowSecurityTokenPinDialog(
126 active_dialog_state_->extension_name, active_dialog_state_->code_type,
127 /*enable_user_input=*/false, error_label,
Maksim Ivanov2b8136f2019-08-09 14:03:59128 /*attempts_left=*/-1, sign_request_state->authenticating_user_account_id,
Maksim Ivanov73dfec32019-08-01 22:43:12129 base::BindOnce(&PinDialogManager::OnPinEntered,
130 weak_factory_.GetWeakPtr()),
131 base::BindOnce(&PinDialogManager::OnPinDialogClosed,
132 weak_factory_.GetWeakPtr()));
133
Maksim Ivanov86866b72019-07-25 02:15:06134 return StopPinRequestResult::kSuccess;
igorcovbb898fb2016-12-01 16:07:56135}
136
Maksim Ivanov86866b72019-07-25 02:15:06137bool PinDialogManager::LastPinDialogClosed(
138 const std::string& extension_id) const {
139 auto iter = last_response_closed_.find(extension_id);
140 return iter != last_response_closed_.end() && iter->second;
igorcovbb898fb2016-12-01 16:07:56141}
142
143bool PinDialogManager::CloseDialog(const std::string& extension_id) {
Maksim Ivanov73dfec32019-08-01 22:43:12144 // Perform sanity checks, as the extension might have issued this call
145 // incorrectly.
146 if (!active_dialog_state_ ||
147 extension_id != active_dialog_state_->extension_id) {
igorcovbb898fb2016-12-01 16:07:56148 LOG(ERROR) << "StopPinRequest called by unexpected extension: "
149 << extension_id;
150 return false;
151 }
152
Maksim Ivanov5ccb96c2019-08-08 13:25:41153 CloseActiveDialog();
igorcovbb898fb2016-12-01 16:07:56154 return true;
155}
156
157void PinDialogManager::ExtensionUnloaded(const std::string& extension_id) {
Maksim Ivanov73dfec32019-08-01 22:43:12158 if (active_dialog_state_ &&
159 active_dialog_state_->extension_id == extension_id) {
Maksim Ivanov5ccb96c2019-08-08 13:25:41160 CloseActiveDialog();
Maksim Ivanov73dfec32019-08-01 22:43:12161 }
igorcovbb898fb2016-12-01 16:07:56162
163 last_response_closed_[extension_id] = false;
164
Maksim Ivanov2b8136f2019-08-09 14:03:59165 for (auto it = sign_requests_.cbegin(); it != sign_requests_.cend();) {
Maksim Ivanov86866b72019-07-25 02:15:06166 if (it->first.first == extension_id)
Maksim Ivanov2b8136f2019-08-09 14:03:59167 sign_requests_.erase(it++);
Maksim Ivanov86866b72019-07-25 02:15:06168 else
igorcovbb898fb2016-12-01 16:07:56169 ++it;
igorcovbb898fb2016-12-01 16:07:56170 }
171}
172
Maksim Ivanov5fd579032019-08-02 01:15:15173void PinDialogManager::AddPinDialogHost(
174 SecurityTokenPinDialogHost* pin_dialog_host) {
175 DCHECK(!base::Contains(added_dialog_hosts_, pin_dialog_host));
176 added_dialog_hosts_.push_back(pin_dialog_host);
177}
178
179void PinDialogManager::RemovePinDialogHost(
180 SecurityTokenPinDialogHost* pin_dialog_host) {
Maksim Ivanov5ccb96c2019-08-08 13:25:41181 if (active_dialog_state_ && active_dialog_state_->host == pin_dialog_host)
182 CloseActiveDialog();
Maksim Ivanov5fd579032019-08-02 01:15:15183 DCHECK(base::Contains(added_dialog_hosts_, pin_dialog_host));
Andrew Rayskiy53c55d12024-02-26 18:58:54184 std::erase(added_dialog_hosts_, pin_dialog_host);
Maksim Ivanov5fd579032019-08-02 01:15:15185}
186
Maksim Ivanov2b8136f2019-08-09 14:03:59187PinDialogManager::SignRequestState::SignRequestState(
188 base::Time begin_time,
Arthur Sonzognife132ee2024-01-15 11:01:04189 const std::optional<AccountId>& authenticating_user_account_id)
Maksim Ivanov2b8136f2019-08-09 14:03:59190 : begin_time(begin_time),
191 authenticating_user_account_id(authenticating_user_account_id) {}
192
193PinDialogManager::SignRequestState::SignRequestState(const SignRequestState&) =
194 default;
Oleksandr Kulkov9c7cf9552022-06-02 12:25:43195PinDialogManager::SignRequestState&
196PinDialogManager::SignRequestState::operator=(const SignRequestState&) =
197 default;
Maksim Ivanov2b8136f2019-08-09 14:03:59198
199PinDialogManager::SignRequestState::~SignRequestState() = default;
200
Maksim Ivanov73dfec32019-08-01 22:43:12201PinDialogManager::ActiveDialogState::ActiveDialogState(
202 SecurityTokenPinDialogHost* host,
203 const std::string& extension_id,
204 const std::string& extension_name,
Maksim Ivanov2b8136f2019-08-09 14:03:59205 int sign_request_id,
Fabian Sommer195d1f32020-02-27 01:51:02206 security_token_pin::CodeType code_type)
Maksim Ivanov73dfec32019-08-01 22:43:12207 : host(host),
208 extension_id(extension_id),
209 extension_name(extension_name),
Maksim Ivanov2b8136f2019-08-09 14:03:59210 sign_request_id(sign_request_id),
Maksim Ivanov73dfec32019-08-01 22:43:12211 code_type(code_type) {}
Maksim Ivanovab8e8582019-08-01 21:49:13212
Maksim Ivanov73dfec32019-08-01 22:43:12213PinDialogManager::ActiveDialogState::~ActiveDialogState() = default;
214
Maksim Ivanov2b8136f2019-08-09 14:03:59215PinDialogManager::SignRequestState* PinDialogManager::FindSignRequestState(
216 const std::string& extension_id,
217 int sign_request_id) {
218 const ExtensionNameRequestIdPair key(extension_id, sign_request_id);
219 const auto sign_request_iter = sign_requests_.find(key);
220 if (sign_request_iter == sign_requests_.end())
221 return nullptr;
222 return &sign_request_iter->second;
223}
224
Maksim Ivanov73dfec32019-08-01 22:43:12225void PinDialogManager::OnPinEntered(const std::string& user_input) {
226 DCHECK(!active_dialog_state_->stop_pin_request_callback);
227 last_response_closed_[active_dialog_state_->extension_id] = false;
228 if (active_dialog_state_->request_pin_callback)
229 std::move(active_dialog_state_->request_pin_callback).Run(user_input);
Maksim Ivanovab8e8582019-08-01 21:49:13230}
231
232void PinDialogManager::OnPinDialogClosed() {
Maksim Ivanov73dfec32019-08-01 22:43:12233 DCHECK(!active_dialog_state_->request_pin_callback ||
234 !active_dialog_state_->stop_pin_request_callback);
Maksim Ivanovab8e8582019-08-01 21:49:13235
Maksim Ivanov73dfec32019-08-01 22:43:12236 last_response_closed_[active_dialog_state_->extension_id] = true;
237 if (active_dialog_state_->request_pin_callback) {
238 std::move(active_dialog_state_->request_pin_callback)
239 .Run(/*user_input=*/std::string());
240 }
241 if (active_dialog_state_->stop_pin_request_callback)
242 std::move(active_dialog_state_->stop_pin_request_callback).Run();
243 active_dialog_state_.reset();
Maksim Ivanovab8e8582019-08-01 21:49:13244}
245
Maksim Ivanov5fd579032019-08-02 01:15:15246SecurityTokenPinDialogHost* PinDialogManager::GetHostForNewDialog() {
247 if (added_dialog_hosts_.empty())
248 return &default_dialog_host_;
249 return added_dialog_hosts_.back();
250}
251
Maksim Ivanov5ccb96c2019-08-08 13:25:41252void PinDialogManager::CloseActiveDialog() {
253 if (!active_dialog_state_)
254 return;
255
256 // Ignore any further callbacks from the host. Instead of relying on the host
257 // to call the closing callback, run OnPinDialogClosed() below explicitly.
258 weak_factory_.InvalidateWeakPtrs();
259
260 active_dialog_state_->host->CloseSecurityTokenPinDialog();
261 OnPinDialogClosed();
262 DCHECK(!active_dialog_state_);
263}
264
Oleksandr Kulkov1213dee82022-05-17 20:45:34265} // namespace chromeos