1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/safe_browsing/chrome_password_protection_service.h"
6
7 #include <memory>
8 #include <string>
9
10 #include "base/bind.h"
11 #include "base/metrics/histogram_macros.h"
12 #include "base/metrics/user_metrics.h"
13 #include "base/metrics/user_metrics_action.h"
14 #include "base/rand_util.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_piece.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "chrome/browser/browser_process.h"
22 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
23 #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h"
24 #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.h"
25 #include "chrome/browser/history/history_service_factory.h"
26 #include "chrome/browser/password_manager/account_password_store_factory.h"
27 #include "chrome/browser/policy/chrome_browser_policy_connector.h"
28 #include "chrome/browser/profiles/profile.h"
29 #include "chrome/browser/safe_browsing/advanced_protection_status_manager.h"
30 #include "chrome/browser/safe_browsing/advanced_protection_status_manager_factory.h"
31 #include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h"
32 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
33 #include "chrome/browser/safe_browsing/ui_manager.h"
34 #include "chrome/browser/safe_browsing/verdict_cache_manager_factory.h"
35 #include "chrome/browser/signin/identity_manager_factory.h"
36 #include "chrome/browser/sync/profile_sync_service_factory.h"
37 #include "chrome/browser/sync/user_event_service_factory.h"
38 #include "chrome/common/pref_names.h"
39 #include "chrome/common/url_constants.h"
40 #include "components/content_settings/core/browser/host_content_settings_map.h"
41 #include "components/google/core/common/google_util.h"
42 #include "components/omnibox/common/omnibox_features.h"
43 #include "components/password_manager/core/browser/compromised_credentials_table.h"
44 #include "components/password_manager/core/browser/form_parsing/form_parser.h"
45 #include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
46 #include "components/password_manager/core/browser/ui/password_check_referrer.h"
47 #include "components/password_manager/core/common/password_manager_features.h"
48 #include "components/prefs/pref_change_registrar.h"
49 #include "components/prefs/pref_service.h"
50 #include "components/prefs/scoped_user_pref_update.h"
51 #include "components/safe_browsing/content/password_protection/password_protection_navigation_throttle.h"
52 #include "components/safe_browsing/content/password_protection/password_protection_request.h"
53 #include "components/safe_browsing/content/web_ui/safe_browsing_ui.h"
54 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
55 #include "components/safe_browsing/core/common/safebrowsing_constants.h"
56 #include "components/safe_browsing/core/common/utils.h"
57 #include "components/safe_browsing/core/db/database_manager.h"
58 #include "components/safe_browsing/core/features.h"
59 #include "components/safe_browsing/core/proto/csd.pb.h"
60 #include "components/safe_browsing/core/triggers/trigger_throttler.h"
61 #include "components/safe_browsing/core/verdict_cache_manager.h"
62 #include "components/security_interstitials/content/unsafe_resource_util.h"
63 #include "components/security_interstitials/core/unsafe_resource.h"
64 #include "components/signin/public/identity_manager/account_info.h"
65 #include "components/signin/public/identity_manager/consent_level.h"
66 #include "components/signin/public/identity_manager/identity_manager.h"
67 #include "components/strings/grit/components_strings.h"
68 #include "components/sync/driver/sync_service.h"
69 #include "components/sync/protocol/user_event_specifics.pb.h"
70 #include "components/sync_user_events/user_event_service.h"
71 #include "components/unified_consent/pref_names.h"
72 #include "components/variations/service/variations_service.h"
73 #include "content/public/browser/browser_thread.h"
74 #include "content/public/browser/navigation_entry.h"
75 #include "content/public/browser/navigation_handle.h"
76 #include "content/public/browser/render_frame_host.h"
77 #include "content/public/browser/render_process_host.h"
78 #include "content/public/browser/storage_partition.h"
79 #include "content/public/browser/web_contents.h"
80 #include "google_apis/gaia/gaia_auth_util.h"
81 #include "ui/base/l10n/l10n_util.h"
82 #include "ui/gfx/geometry/size.h"
83 #include "url/gurl.h"
84 #include "url/url_util.h"
85
86 #if BUILDFLAG(FULL_SAFE_BROWSING)
87 #include "chrome/browser/ui/browser_finder.h"
88 #include "chrome/browser/ui/chrome_pages.h"
89 #include "chrome/browser/ui/views/frame/browser_view.h"
90 #endif
91
92 #if defined(OS_ANDROID)
93 #include "chrome/browser/safe_browsing/android/password_reuse_controller_android.h"
94 #else
95 #include "chrome/browser/ui/browser_list.h"
96 #endif
97
98 using base::RecordAction;
99 using base::UserMetricsAction;
100 using content::BrowserThread;
101 using sync_pb::GaiaPasswordReuse;
102 using sync_pb::UserEventSpecifics;
103 using GaiaPasswordCaptured = UserEventSpecifics::GaiaPasswordCaptured;
104 using PasswordReuseDialogInteraction =
105 GaiaPasswordReuse::PasswordReuseDialogInteraction;
106 using PasswordReuseLookup = GaiaPasswordReuse::PasswordReuseLookup;
107 using PasswordReuseEvent =
108 safe_browsing::LoginReputationClientRequest::PasswordReuseEvent;
109 using SafeBrowsingStatus =
110 GaiaPasswordReuse::PasswordReuseDetected::SafeBrowsingStatus;
111
112 namespace safe_browsing {
113
114 using ReusedPasswordAccountType =
115 LoginReputationClientRequest::PasswordReuseEvent::ReusedPasswordAccountType;
116
117 namespace {
118
119 // The number of user gestures we trace back for login event attribution.
120 const int kPasswordEventAttributionUserGestureLimit = 2;
121
122 // Probability for sending password protection reports for domains on the
123 // allowlist for users opted into extended reporting, from non-incognito window.
124 const float kProbabilityForSendingReportsFromSafeURLs = 0.01;
125
126 #if defined(PASSWORD_REUSE_WARNING_ENABLED)
127 // If user specifically mark a site as legitimate, we will keep this decision
128 // for 2 days.
129 const int kOverrideVerdictCacheDurationSec = 2 * 24 * 60 * 60;
130
131 // Frequency to log PasswordCapture event log. Random 24-28 days.
132 const int kPasswordCaptureEventLogFreqDaysMin = 24;
133 const int kPasswordCaptureEventLogFreqDaysExtra = 4;
134
GetMicrosecondsSinceWindowsEpoch(base::Time time)135 int64_t GetMicrosecondsSinceWindowsEpoch(base::Time time) {
136 return (time - base::Time()).InMicroseconds();
137 }
138
GetVerdictToLogFromResponse(LoginReputationClientResponse::VerdictType response_verdict)139 PasswordReuseLookup::ReputationVerdict GetVerdictToLogFromResponse(
140 LoginReputationClientResponse::VerdictType response_verdict) {
141 switch (response_verdict) {
142 case LoginReputationClientResponse::SAFE:
143 return PasswordReuseLookup::SAFE;
144 case LoginReputationClientResponse::LOW_REPUTATION:
145 return PasswordReuseLookup::LOW_REPUTATION;
146 case LoginReputationClientResponse::PHISHING:
147 return PasswordReuseLookup::PHISHING;
148 case LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED:
149 return PasswordReuseLookup::VERDICT_UNSPECIFIED;
150 }
151 NOTREACHED() << "Unexpected response_verdict: " << response_verdict;
152 return PasswordReuseLookup::VERDICT_UNSPECIFIED;
153 }
154
155 // Given a |web_contents|, returns the navigation id of its last committed
156 // navigation.
GetLastCommittedNavigationID(content::WebContents * web_contents)157 int64_t GetLastCommittedNavigationID(content::WebContents* web_contents) {
158 if (!web_contents)
159 return 0;
160 content::NavigationEntry* navigation =
161 web_contents->GetController().GetLastCommittedEntry();
162 return navigation
163 ? GetMicrosecondsSinceWindowsEpoch(navigation->GetTimestamp())
164 : 0;
165 }
166
167 // Opens a |url| from |current_web_contents| with |referrer|. |in_new_tab|
168 // indicates if opening in a new foreground tab or in current tab.
OpenUrl(content::WebContents * current_web_contents,const GURL & url,const content::Referrer & referrer,bool in_new_tab)169 void OpenUrl(content::WebContents* current_web_contents,
170 const GURL& url,
171 const content::Referrer& referrer,
172 bool in_new_tab) {
173 content::OpenURLParams params(url, referrer,
174 in_new_tab
175 ? WindowOpenDisposition::NEW_FOREGROUND_TAB
176 : WindowOpenDisposition::CURRENT_TAB,
177 ui::PAGE_TRANSITION_LINK,
178 /*is_renderer_initiated=*/false);
179 current_web_contents->OpenURL(params);
180 }
181
GetNavigationIDFromPrefsByOrigin(PrefService * prefs,const Origin & origin)182 int64_t GetNavigationIDFromPrefsByOrigin(PrefService* prefs,
183 const Origin& origin) {
184 const base::DictionaryValue* unhandled_sync_password_reuses =
185 prefs->GetDictionary(prefs::kSafeBrowsingUnhandledGaiaPasswordReuses);
186 if (!unhandled_sync_password_reuses)
187 return 0;
188
189 const base::Value* navigation_id_value =
190 unhandled_sync_password_reuses->FindKey(origin.Serialize());
191
192 int64_t navigation_id;
193 return navigation_id_value &&
194 base::StringToInt64(navigation_id_value->GetString(),
195 &navigation_id)
196 ? navigation_id
197 : 0;
198 }
199
200 // Return a new UserEventSpecifics w/o the navigation_id populated
GetNewUserEventSpecifics()201 std::unique_ptr<UserEventSpecifics> GetNewUserEventSpecifics() {
202 auto specifics = std::make_unique<UserEventSpecifics>();
203 specifics->set_event_time_usec(
204 GetMicrosecondsSinceWindowsEpoch(base::Time::Now()));
205 return specifics;
206 }
207
208 // Return a new UserEventSpecifics w/ the navigation_id populated
GetUserEventSpecificsWithNavigationId(int64_t navigation_id)209 std::unique_ptr<UserEventSpecifics> GetUserEventSpecificsWithNavigationId(
210 int64_t navigation_id) {
211 if (navigation_id <= 0)
212 return nullptr;
213
214 auto specifics = GetNewUserEventSpecifics();
215 specifics->set_navigation_id(navigation_id);
216 return specifics;
217 }
218
219 // Return a new UserEventSpecifics populated from the web_contents
GetUserEventSpecifics(content::WebContents * web_contents)220 std::unique_ptr<UserEventSpecifics> GetUserEventSpecifics(
221 content::WebContents* web_contents) {
222 return GetUserEventSpecificsWithNavigationId(
223 GetLastCommittedNavigationID(web_contents));
224 }
225 #endif
226
227 } // namespace
228
ChromePasswordProtectionService(SafeBrowsingService * sb_service,Profile * profile)229 ChromePasswordProtectionService::ChromePasswordProtectionService(
230 SafeBrowsingService* sb_service,
231 Profile* profile)
232 : PasswordProtectionService(sb_service->database_manager(),
233 sb_service->GetURLLoaderFactory(profile),
234 HistoryServiceFactory::GetForProfile(
235 profile,
236 ServiceAccessType::EXPLICIT_ACCESS)),
237 ui_manager_(sb_service->ui_manager()),
238 trigger_manager_(sb_service->trigger_manager()),
239 profile_(profile),
240 navigation_observer_manager_(sb_service->navigation_observer_manager()),
241 pref_change_registrar_(new PrefChangeRegistrar),
242 cache_manager_(VerdictCacheManagerFactory::GetForProfile(profile)) {
243 pref_change_registrar_->Init(profile_->GetPrefs());
244
245 #if defined(PASSWORD_REUSE_WARNING_ENABLED)
246 scoped_refptr<password_manager::PasswordStore> password_store =
247 GetProfilePasswordStore();
248 // Password store can be null in tests.
249 if (password_store) {
250 // Subscribe to gaia hash password changes change notifications.
251 hash_password_manager_subscription_ =
252 password_store->RegisterStateCallbackOnHashPasswordManager(
253 base::BindRepeating(&ChromePasswordProtectionService::
254 CheckGaiaPasswordChangeForAllSignedInUsers,
255 base::Unretained(this)));
256 }
257 pref_change_registrar_->Add(
258 prefs::kPasswordProtectionWarningTrigger,
259 base::BindRepeating(
260 &ChromePasswordProtectionService::OnWarningTriggerChanged,
261 base::Unretained(this)));
262 pref_change_registrar_->Add(
263 prefs::kPasswordProtectionLoginURLs,
264 base::BindRepeating(
265 &ChromePasswordProtectionService::OnEnterprisePasswordUrlChanged,
266 base::Unretained(this)));
267 pref_change_registrar_->Add(
268 prefs::kPasswordProtectionChangePasswordURL,
269 base::BindRepeating(
270 &ChromePasswordProtectionService::OnEnterprisePasswordUrlChanged,
271 base::Unretained(this)));
272 #endif
273 // TODO(nparker) Move the rest of the above code into Init()
274 // without crashing unittests.
275 Init();
276 }
277
Init()278 void ChromePasswordProtectionService::Init() {
279 // The following code is disabled on Android. RefreshTokenIsAvailable cannot be
280 // used in unit tests, because it needs to interact with system accounts.
281 // Considering avoid running it during unit tests. See: crbug.com/1009957.
282 #if !defined(OS_ANDROID)
283 // This code is shared by the normal ctor and testing ctor.
284
285 sync_password_hash_ = GetSyncPasswordHashFromPrefs();
286 if (!sync_password_hash_.empty()) {
287 // Set a timer for when next to log the PasswordCapture event. The timer
288 // value is stored in a pref to carry across restarts.
289 base::TimeDelta delay =
290 GetDelayFromPref(profile_->GetPrefs(),
291 prefs::kSafeBrowsingNextPasswordCaptureEventLogTime);
292
293 // Bound it between 1 min and 28 days. Handles clock-resets. We wait
294 // 1 min to not slowdown browser-startup, and to improve the
295 // probability that the sync system is initialized.
296 base::TimeDelta min_delay = base::TimeDelta::FromMinutes(1);
297 base::TimeDelta max_delay =
298 base::TimeDelta::FromDays(kPasswordCaptureEventLogFreqDaysMin +
299 kPasswordCaptureEventLogFreqDaysExtra);
300 if (delay < min_delay)
301 delay = min_delay;
302 else if (delay > max_delay)
303 delay = max_delay;
304 SetLogPasswordCaptureTimer(delay);
305 }
306 #endif
307 }
308
~ChromePasswordProtectionService()309 ChromePasswordProtectionService::~ChromePasswordProtectionService() {
310 if (pref_change_registrar_)
311 pref_change_registrar_->RemoveAll();
312 }
313
314 // static
315 ChromePasswordProtectionService*
GetPasswordProtectionService(Profile * profile)316 ChromePasswordProtectionService::GetPasswordProtectionService(
317 Profile* profile) {
318 if (g_browser_process && g_browser_process->safe_browsing_service()) {
319 return static_cast<safe_browsing::ChromePasswordProtectionService*>(
320 g_browser_process->safe_browsing_service()
321 ->GetPasswordProtectionService(profile));
322 }
323 return nullptr;
324 }
325
326 #if defined(PASSWORD_REUSE_WARNING_ENABLED)
327 // static
ShouldShowPasswordReusePageInfoBubble(content::WebContents * web_contents,PasswordType password_type)328 bool ChromePasswordProtectionService::ShouldShowPasswordReusePageInfoBubble(
329 content::WebContents* web_contents,
330 PasswordType password_type) {
331 Profile* profile =
332 Profile::FromBrowserContext(web_contents->GetBrowserContext());
333 ChromePasswordProtectionService* service =
334 ChromePasswordProtectionService::GetPasswordProtectionService(profile);
335
336 // |service| could be null if safe browsing service is disabled.
337 if (!service)
338 return false;
339
340 if (password_type == PasswordType::ENTERPRISE_PASSWORD)
341 return service->HasUnhandledEnterprisePasswordReuse(web_contents);
342
343 bool enable_warning_for_non_sync_users = base::FeatureList::IsEnabled(
344 safe_browsing::kPasswordProtectionForSignedInUsers);
345 DCHECK(password_type == PasswordType::PRIMARY_ACCOUNT_PASSWORD ||
346 password_type == PasswordType::SAVED_PASSWORD ||
347 (enable_warning_for_non_sync_users &&
348 password_type == PasswordType::OTHER_GAIA_PASSWORD));
349 // Otherwise, checks if there's any unhandled sync password reuses matches
350 // this origin.
351 auto* unhandled_sync_password_reuses = profile->GetPrefs()->GetDictionary(
352 prefs::kSafeBrowsingUnhandledGaiaPasswordReuses);
353 return unhandled_sync_password_reuses
354 ? (unhandled_sync_password_reuses->FindKey(
355 Origin::Create(web_contents->GetLastCommittedURL())
356 .Serialize()) != nullptr)
357 : false;
358 }
359
360 safe_browsing::LoginReputationClientRequest::UrlDisplayExperiment
GetUrlDisplayExperiment() const361 ChromePasswordProtectionService::GetUrlDisplayExperiment() const {
362 safe_browsing::LoginReputationClientRequest::UrlDisplayExperiment experiment;
363 experiment.set_simplified_url_display_enabled(
364 base::FeatureList::IsEnabled(safe_browsing::kSimplifiedUrlDisplay));
365 // Delayed warnings parameters:
366 experiment.set_delayed_warnings_enabled(
367 base::FeatureList::IsEnabled(safe_browsing::kDelayedWarnings));
368 experiment.set_delayed_warnings_mouse_clicks_enabled(
369 safe_browsing::kDelayedWarningsEnableMouseClicks.Get());
370 // Actual URL display experiments:
371 experiment.set_reveal_on_hover(base::FeatureList::IsEnabled(
372 omnibox::kRevealSteadyStateUrlPathQueryAndRefOnHover));
373 experiment.set_hide_on_interaction(base::FeatureList::IsEnabled(
374 omnibox::kHideSteadyStateUrlPathQueryAndRefOnInteraction));
375 experiment.set_elide_to_registrable_domain(
376 base::FeatureList::IsEnabled(omnibox::kMaybeElideToRegistrableDomain));
377 return experiment;
378 }
379
ShowModalWarning(content::WebContents * web_contents,RequestOutcome outcome,LoginReputationClientResponse::VerdictType verdict_type,const std::string & verdict_token,ReusedPasswordAccountType password_type)380 void ChromePasswordProtectionService::ShowModalWarning(
381 content::WebContents* web_contents,
382 RequestOutcome outcome,
383 LoginReputationClientResponse::VerdictType verdict_type,
384 const std::string& verdict_token,
385 ReusedPasswordAccountType password_type) {
386 DCHECK_CURRENTLY_ON(BrowserThread::UI);
387 DCHECK(password_type.account_type() == ReusedPasswordAccountType::GMAIL ||
388 password_type.account_type() == ReusedPasswordAccountType::GSUITE ||
389 password_type.account_type() ==
390 ReusedPasswordAccountType::NON_GAIA_ENTERPRISE ||
391 (password_type.account_type() ==
392 ReusedPasswordAccountType::SAVED_PASSWORD &&
393 base::FeatureList::IsEnabled(
394 safe_browsing::kPasswordProtectionForSavedPasswords)));
395 // Don't show warning again if there is already a modal warning showing.
396 if (IsModalWarningShowingInWebContents(web_contents))
397 return;
398
399 // Exit fullscreen if this |web_contents| is showing in fullscreen mode.
400 if (web_contents->IsFullscreen())
401 web_contents->ExitFullscreen(true);
402
403 #if defined(OS_ANDROID)
404 (new PasswordReuseControllerAndroid(
405 web_contents, this, password_type,
406 base::BindOnce(&ChromePasswordProtectionService::OnUserAction,
407 base::Unretained(this), web_contents, password_type,
408 outcome, verdict_type, verdict_token,
409 WarningUIType::MODAL_DIALOG)))
410 ->ShowDialog();
411 #else // !defined(OS_ANDROID)
412 ShowPasswordReuseModalWarningDialog(
413 web_contents, this, password_type,
414 base::BindOnce(&ChromePasswordProtectionService::OnUserAction,
415 base::Unretained(this), web_contents, password_type,
416 outcome, verdict_type, verdict_token,
417 WarningUIType::MODAL_DIALOG));
418 #endif // defined(OS_ANDROID)
419
420 LogWarningAction(WarningUIType::MODAL_DIALOG, WarningAction::SHOWN,
421 password_type);
422 switch (password_type.account_type()) {
423 case ReusedPasswordAccountType::SAVED_PASSWORD:
424 OnModalWarningShownForSavedPassword(web_contents, password_type,
425 verdict_token);
426 break;
427 case ReusedPasswordAccountType::GMAIL:
428 case ReusedPasswordAccountType::GSUITE:
429 OnModalWarningShownForGaiaPassword(web_contents, password_type,
430 verdict_token);
431 break;
432 case ReusedPasswordAccountType::NON_GAIA_ENTERPRISE:
433 OnModalWarningShownForEnterprisePassword(web_contents, password_type,
434 verdict_token);
435 break;
436 default:
437 return;
438 }
439 }
440
OnModalWarningShownForSavedPassword(content::WebContents * web_contents,ReusedPasswordAccountType password_type,const std::string & verdict_token)441 void ChromePasswordProtectionService::OnModalWarningShownForSavedPassword(
442 content::WebContents* web_contents,
443 ReusedPasswordAccountType password_type,
444 const std::string& verdict_token) {
445 UpdateSecurityState(SB_THREAT_TYPE_SAVED_PASSWORD_REUSE, password_type,
446 web_contents);
447 // Starts preparing post-warning report.
448 MaybeStartThreatDetailsCollection(web_contents, verdict_token, password_type);
449 }
450
OnModalWarningShownForGaiaPassword(content::WebContents * web_contents,ReusedPasswordAccountType password_type,const std::string & verdict_token)451 void ChromePasswordProtectionService::OnModalWarningShownForGaiaPassword(
452 content::WebContents* web_contents,
453 ReusedPasswordAccountType password_type,
454 const std::string& verdict_token) {
455 if (!IsIncognito()) {
456 DictionaryPrefUpdate update(
457 profile_->GetPrefs(), prefs::kSafeBrowsingUnhandledGaiaPasswordReuses);
458 // Since base::Value doesn't support int64_t type, we convert the navigation
459 // ID to string format and store it in the preference dictionary.
460 update->SetKey(
461 Origin::Create(web_contents->GetLastCommittedURL()).Serialize(),
462 base::Value(
463 base::NumberToString(GetLastCommittedNavigationID(web_contents))));
464 }
465 SBThreatType threat_type;
466 if (password_type.is_account_syncing()) {
467 threat_type = SB_THREAT_TYPE_SIGNED_IN_SYNC_PASSWORD_REUSE;
468 } else {
469 threat_type = SB_THREAT_TYPE_SIGNED_IN_NON_SYNC_PASSWORD_REUSE;
470 }
471 UpdateSecurityState(threat_type, password_type, web_contents);
472
473 // Starts preparing post-warning report.
474 MaybeStartThreatDetailsCollection(web_contents, verdict_token, password_type);
475 }
476
OnModalWarningShownForEnterprisePassword(content::WebContents * web_contents,ReusedPasswordAccountType password_type,const std::string & verdict_token)477 void ChromePasswordProtectionService::OnModalWarningShownForEnterprisePassword(
478 content::WebContents* web_contents,
479 ReusedPasswordAccountType password_type,
480 const std::string& verdict_token) {
481 web_contents_with_unhandled_enterprise_reuses_.insert(web_contents);
482 UpdateSecurityState(SB_THREAT_TYPE_ENTERPRISE_PASSWORD_REUSE, password_type,
483 web_contents);
484 // Starts preparing post-warning report.
485 MaybeStartThreatDetailsCollection(web_contents, verdict_token, password_type);
486 }
487
ShowInterstitial(content::WebContents * web_contents,ReusedPasswordAccountType password_type)488 void ChromePasswordProtectionService::ShowInterstitial(
489 content::WebContents* web_contents,
490 ReusedPasswordAccountType password_type) {
491 DCHECK(password_type.account_type() ==
492 ReusedPasswordAccountType::NON_GAIA_ENTERPRISE ||
493 password_type.account_type() == ReusedPasswordAccountType::GSUITE);
494 // Exit fullscreen if this |web_contents| is showing in fullscreen mode.
495 if (web_contents->IsFullscreen())
496 web_contents->ExitFullscreen(/*will_cause_resize=*/true);
497
498 content::OpenURLParams params(
499 GURL(chrome::kChromeUIResetPasswordURL), content::Referrer(),
500 WindowOpenDisposition::NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_LINK,
501 /*is_renderer_initiated=*/false);
502 std::string post_data =
503 base::NumberToString(static_cast<std::underlying_type_t<PasswordType>>(
504 ConvertReusedPasswordAccountTypeToPasswordType(password_type)));
505
506 params.post_data = network::ResourceRequestBody::CreateFromBytes(
507 post_data.data(), post_data.size());
508 web_contents->OpenURL(params);
509
510 LogWarningAction(WarningUIType::INTERSTITIAL, WarningAction::SHOWN,
511 password_type);
512 }
513
OnUserAction(content::WebContents * web_contents,ReusedPasswordAccountType password_type,RequestOutcome outcome,LoginReputationClientResponse::VerdictType verdict_type,const std::string & verdict_token,WarningUIType ui_type,WarningAction action)514 void ChromePasswordProtectionService::OnUserAction(
515 content::WebContents* web_contents,
516 ReusedPasswordAccountType password_type,
517 RequestOutcome outcome,
518 LoginReputationClientResponse::VerdictType verdict_type,
519 const std::string& verdict_token,
520 WarningUIType ui_type,
521 WarningAction action) {
522 // Only log modal warning dialog action for all password types except for
523 // signed-in non-syncing type for now. We log for signed-in non-syncing type
524 // only when we are about to send the event to SecurityEventRecorder because
525 // we don't want to count non-unconsented primary accounts.
526 bool is_signed_in_non_syncing =
527 !password_type.is_account_syncing() &&
528 (password_type.account_type() == ReusedPasswordAccountType::GMAIL ||
529 password_type.account_type() == ReusedPasswordAccountType::GSUITE);
530 if (!is_signed_in_non_syncing)
531 LogWarningAction(ui_type, action, password_type);
532
533 switch (ui_type) {
534 case WarningUIType::PAGE_INFO:
535 HandleUserActionOnPageInfo(web_contents, password_type, action);
536 break;
537 case WarningUIType::MODAL_DIALOG:
538 HandleUserActionOnModalWarning(web_contents, password_type, outcome,
539 verdict_type, verdict_token, action);
540 break;
541 case WarningUIType::INTERSTITIAL:
542 DCHECK_EQ(WarningAction::CHANGE_PASSWORD, action);
543 HandleResetPasswordOnInterstitial(web_contents, action);
544 break;
545 default:
546 NOTREACHED();
547 break;
548 }
549 }
550
AddObserver(Observer * observer)551 void ChromePasswordProtectionService::AddObserver(Observer* observer) {
552 observer_list_.AddObserver(observer);
553 }
554
RemoveObserver(Observer * observer)555 void ChromePasswordProtectionService::RemoveObserver(Observer* observer) {
556 observer_list_.RemoveObserver(observer);
557 }
558
MaybeStartThreatDetailsCollection(content::WebContents * web_contents,const std::string & token,ReusedPasswordAccountType password_type)559 void ChromePasswordProtectionService::MaybeStartThreatDetailsCollection(
560 content::WebContents* web_contents,
561 const std::string& token,
562 ReusedPasswordAccountType password_type) {
563 // |trigger_manager_| can be null in test.
564 if (!trigger_manager_)
565 return;
566
567 security_interstitials::UnsafeResource resource;
568 if (password_type.account_type() ==
569 ReusedPasswordAccountType::NON_GAIA_ENTERPRISE) {
570 resource.threat_type = SB_THREAT_TYPE_ENTERPRISE_PASSWORD_REUSE;
571 } else if (password_type.account_type() ==
572 ReusedPasswordAccountType::SAVED_PASSWORD) {
573 resource.threat_type = SB_THREAT_TYPE_SAVED_PASSWORD_REUSE;
574 } else if (password_type.is_account_syncing()) {
575 resource.threat_type = SB_THREAT_TYPE_SIGNED_IN_SYNC_PASSWORD_REUSE;
576 } else {
577 resource.threat_type = SB_THREAT_TYPE_SIGNED_IN_NON_SYNC_PASSWORD_REUSE;
578 }
579 resource.url = web_contents->GetLastCommittedURL();
580 resource.web_contents_getter = security_interstitials::GetWebContentsGetter(
581 web_contents->GetMainFrame()->GetProcess()->GetID(),
582 web_contents->GetMainFrame()->GetRoutingID());
583 resource.token = token;
584 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory =
585 content::BrowserContext::GetDefaultStoragePartition(profile_)
586 ->GetURLLoaderFactoryForBrowserProcess();
587 // Ignores the return of |StartCollectingThreatDetails()| here and
588 // let TriggerManager decide whether it should start data
589 // collection.
590 trigger_manager_->StartCollectingThreatDetails(
591 safe_browsing::TriggerType::GAIA_PASSWORD_REUSE, web_contents, resource,
592 url_loader_factory, /*history_service=*/nullptr,
593 TriggerManager::GetSBErrorDisplayOptions(*profile_->GetPrefs(),
594 web_contents));
595 }
596
MaybeFinishCollectingThreatDetails(content::WebContents * web_contents,bool did_proceed)597 void ChromePasswordProtectionService::MaybeFinishCollectingThreatDetails(
598 content::WebContents* web_contents,
599 bool did_proceed) {
600 // |trigger_manager_| can be null in test.
601 if (!trigger_manager_)
602 return;
603
604 // Since we don't keep track the threat details in progress, it is safe to
605 // ignore the result of |FinishCollectingThreatDetails()|. TriggerManager will
606 // take care of whether report should be sent.
607 trigger_manager_->FinishCollectingThreatDetails(
608 safe_browsing::TriggerType::GAIA_PASSWORD_REUSE, web_contents,
609 base::TimeDelta::FromMilliseconds(0), did_proceed, /*num_visit=*/0,
610 TriggerManager::GetSBErrorDisplayOptions(*profile_->GetPrefs(),
611 web_contents));
612 }
613
MaybeLogPasswordReuseDetectedEvent(content::WebContents * web_contents)614 void ChromePasswordProtectionService::MaybeLogPasswordReuseDetectedEvent(
615 content::WebContents* web_contents) {
616 DCHECK_CURRENTLY_ON(BrowserThread::UI);
617
618 if (IsIncognito() && !WebUIInfoSingleton::HasListener())
619 return;
620
621 syncer::UserEventService* user_event_service =
622 browser_sync::UserEventServiceFactory::GetForProfile(profile_);
623 if (!user_event_service)
624 return;
625
626 std::unique_ptr<UserEventSpecifics> specifics =
627 GetUserEventSpecifics(web_contents);
628 if (!specifics)
629 return;
630
631 auto* const status = specifics->mutable_gaia_password_reuse_event()
632 ->mutable_reuse_detected()
633 ->mutable_status();
634 status->set_enabled(IsSafeBrowsingEnabled());
635
636 ExtendedReportingLevel erl = GetExtendedReportingLevel(*GetPrefs());
637 switch (erl) {
638 case SBER_LEVEL_OFF:
639 status->set_safe_browsing_reporting_population(SafeBrowsingStatus::NONE);
640 break;
641 case SBER_LEVEL_LEGACY:
642 status->set_safe_browsing_reporting_population(
643 SafeBrowsingStatus::EXTENDED_REPORTING);
644 break;
645 case SBER_LEVEL_SCOUT:
646 status->set_safe_browsing_reporting_population(SafeBrowsingStatus::SCOUT);
647 break;
648 }
649
650 WebUIInfoSingleton::GetInstance()->AddToPGEvents(*specifics);
651 user_event_service->RecordUserEvent(std::move(specifics));
652 }
653
MaybeLogPasswordReuseDialogInteraction(int64_t navigation_id,PasswordReuseDialogInteraction::InteractionResult interaction_result)654 void ChromePasswordProtectionService::MaybeLogPasswordReuseDialogInteraction(
655 int64_t navigation_id,
656 PasswordReuseDialogInteraction::InteractionResult interaction_result) {
657 DCHECK_CURRENTLY_ON(BrowserThread::UI);
658
659 if (IsIncognito() && !WebUIInfoSingleton::HasListener())
660 return;
661
662 syncer::UserEventService* user_event_service =
663 browser_sync::UserEventServiceFactory::GetForProfile(profile_);
664 if (!user_event_service)
665 return;
666
667 std::unique_ptr<UserEventSpecifics> specifics =
668 GetUserEventSpecificsWithNavigationId(navigation_id);
669 if (!specifics)
670 return;
671
672 PasswordReuseDialogInteraction* const dialog_interaction =
673 specifics->mutable_gaia_password_reuse_event()
674 ->mutable_dialog_interaction();
675 dialog_interaction->set_interaction_result(interaction_result);
676
677 WebUIInfoSingleton::GetInstance()->AddToPGEvents(*specifics);
678 user_event_service->RecordUserEvent(std::move(specifics));
679 }
680
MaybeLogPasswordReuseLookupResult(content::WebContents * web_contents,PasswordReuseLookup::LookupResult result)681 void ChromePasswordProtectionService::MaybeLogPasswordReuseLookupResult(
682 content::WebContents* web_contents,
683 PasswordReuseLookup::LookupResult result) {
684 DCHECK_CURRENTLY_ON(BrowserThread::UI);
685
686 if (IsIncognito() && !WebUIInfoSingleton::HasListener())
687 return;
688
689 syncer::UserEventService* user_event_service =
690 browser_sync::UserEventServiceFactory::GetForProfile(profile_);
691 if (!user_event_service)
692 return;
693
694 std::unique_ptr<UserEventSpecifics> specifics =
695 GetUserEventSpecifics(web_contents);
696 if (!specifics)
697 return;
698
699 auto* const reuse_lookup =
700 specifics->mutable_gaia_password_reuse_event()->mutable_reuse_lookup();
701 reuse_lookup->set_lookup_result(result);
702 WebUIInfoSingleton::GetInstance()->AddToPGEvents(*specifics);
703 user_event_service->RecordUserEvent(std::move(specifics));
704 }
705
706 void ChromePasswordProtectionService::
MaybeLogPasswordReuseLookupResultWithVerdict(content::WebContents * web_contents,PasswordType password_type,PasswordReuseLookup::LookupResult result,PasswordReuseLookup::ReputationVerdict verdict,const std::string & verdict_token)707 MaybeLogPasswordReuseLookupResultWithVerdict(
708 content::WebContents* web_contents,
709 PasswordType password_type,
710 PasswordReuseLookup::LookupResult result,
711 PasswordReuseLookup::ReputationVerdict verdict,
712 const std::string& verdict_token) {
713 DCHECK_CURRENTLY_ON(BrowserThread::UI);
714
715 if (IsIncognito() && !WebUIInfoSingleton::HasListener())
716 return;
717
718 PasswordReuseLookup reuse_lookup;
719 reuse_lookup.set_lookup_result(result);
720 reuse_lookup.set_verdict(verdict);
721 reuse_lookup.set_verdict_token(verdict_token);
722
723 // If password_type == OTHER_GAIA_PASSWORD, the account is not syncing.
724 // Therefore, we have to use the security event recorder to log events to mark
725 // the account at risk.
726 if (password_type == PasswordType::OTHER_GAIA_PASSWORD) {
727 sync_pb::GaiaPasswordReuse gaia_password_reuse_event;
728 *gaia_password_reuse_event.mutable_reuse_lookup() = reuse_lookup;
729
730 auto* identity_manager = IdentityManagerFactory::GetForProfileIfExists(
731 profile_->GetOriginalProfile());
732 if (identity_manager) {
733 CoreAccountInfo unconsented_primary_account_info =
734 identity_manager->GetPrimaryAccountInfo(
735 signin::ConsentLevel::kNotRequired);
736 // SecurityEventRecorder only supports unconsented primary accounts.
737 if (gaia::AreEmailsSame(unconsented_primary_account_info.email,
738 username_for_last_shown_warning())) {
739 // We currently only send a security event recorder ONLY when a
740 // signed-in non-syncing user clicks on "Protect Account" button.
741 LogWarningAction(WarningUIType::MODAL_DIALOG,
742 WarningAction::CHANGE_PASSWORD,
743 GetPasswordProtectionReusedPasswordAccountType(
744 password_type, username_for_last_shown_warning()));
745 WebUIInfoSingleton::GetInstance()->AddToSecurityEvents(
746 gaia_password_reuse_event);
747 SecurityEventRecorderFactory::GetForProfile(profile_)
748 ->RecordGaiaPasswordReuse(gaia_password_reuse_event);
749 }
750 }
751 } else {
752 syncer::UserEventService* user_event_service =
753 browser_sync::UserEventServiceFactory::GetForProfile(profile_);
754 if (!user_event_service)
755 return;
756
757 std::unique_ptr<UserEventSpecifics> specifics =
758 GetUserEventSpecifics(web_contents);
759 if (!specifics)
760 return;
761
762 *(specifics->mutable_gaia_password_reuse_event())->mutable_reuse_lookup() =
763 reuse_lookup;
764 WebUIInfoSingleton::GetInstance()->AddToPGEvents(*specifics);
765 user_event_service->RecordUserEvent(std::move(specifics));
766 }
767 }
768
MaybeLogPasswordReuseLookupEvent(content::WebContents * web_contents,RequestOutcome outcome,PasswordType password_type,const LoginReputationClientResponse * response)769 void ChromePasswordProtectionService::MaybeLogPasswordReuseLookupEvent(
770 content::WebContents* web_contents,
771 RequestOutcome outcome,
772 PasswordType password_type,
773 const LoginReputationClientResponse* response) {
774 switch (outcome) {
775 case RequestOutcome::MATCHED_WHITELIST:
776 MaybeLogPasswordReuseLookupResult(web_contents,
777 PasswordReuseLookup::WHITELIST_HIT);
778 break;
779 case RequestOutcome::RESPONSE_ALREADY_CACHED:
780 MaybeLogPasswordReuseLookupResultWithVerdict(
781 web_contents, password_type, PasswordReuseLookup::CACHE_HIT,
782 GetVerdictToLogFromResponse(response->verdict_type()),
783 response->verdict_token());
784 break;
785 case RequestOutcome::SUCCEEDED:
786 MaybeLogPasswordReuseLookupResultWithVerdict(
787 web_contents, password_type, PasswordReuseLookup::REQUEST_SUCCESS,
788 GetVerdictToLogFromResponse(response->verdict_type()),
789 response->verdict_token());
790 break;
791 case RequestOutcome::URL_NOT_VALID_FOR_REPUTATION_COMPUTING:
792 MaybeLogPasswordReuseLookupResult(web_contents,
793 PasswordReuseLookup::URL_UNSUPPORTED);
794 break;
795 case RequestOutcome::MATCHED_ENTERPRISE_WHITELIST:
796 case RequestOutcome::MATCHED_ENTERPRISE_LOGIN_URL:
797 case RequestOutcome::MATCHED_ENTERPRISE_CHANGE_PASSWORD_URL:
798 MaybeLogPasswordReuseLookupResult(
799 web_contents, PasswordReuseLookup::ENTERPRISE_WHITELIST_HIT);
800 break;
801 case RequestOutcome::PASSWORD_ALERT_MODE:
802 case RequestOutcome::TURNED_OFF_BY_ADMIN:
803 MaybeLogPasswordReuseLookupResult(
804 web_contents, PasswordReuseLookup::TURNED_OFF_BY_POLICY);
805 break;
806 case RequestOutcome::CANCELED:
807 case RequestOutcome::TIMEDOUT:
808 case RequestOutcome::DISABLED_DUE_TO_INCOGNITO:
809 case RequestOutcome::REQUEST_MALFORMED:
810 case RequestOutcome::FETCH_FAILED:
811 case RequestOutcome::RESPONSE_MALFORMED:
812 case RequestOutcome::SERVICE_DESTROYED:
813 case RequestOutcome::DISABLED_DUE_TO_FEATURE_DISABLED:
814 case RequestOutcome::DISABLED_DUE_TO_USER_POPULATION:
815 case RequestOutcome::SAFE_BROWSING_DISABLED:
816 case RequestOutcome::USER_NOT_SIGNED_IN:
817 case RequestOutcome::EXCLUDED_COUNTRY:
818 MaybeLogPasswordReuseLookupResult(web_contents,
819 PasswordReuseLookup::REQUEST_FAILURE);
820 break;
821 case RequestOutcome::UNKNOWN:
822 case RequestOutcome::DEPRECATED_NO_EXTENDED_REPORTING:
823 NOTREACHED() << __FUNCTION__
824 << ": outcome: " << static_cast<int>(outcome);
825 break;
826 }
827 }
828
829 void ChromePasswordProtectionService::
CheckGaiaPasswordChangeForAllSignedInUsers(const std::string & username)830 CheckGaiaPasswordChangeForAllSignedInUsers(const std::string& username) {
831 // If the sync password has changed, report the change.
832 std::string new_sync_password_hash = GetSyncPasswordHashFromPrefs();
833 if (sync_password_hash_ != new_sync_password_hash) {
834 sync_password_hash_ = new_sync_password_hash;
835 OnGaiaPasswordChanged(username, /*is_other_gaia_password=*/false);
836 return;
837 }
838
839 // For non sync password changes, we have to loop through all the password
840 // hashes and find the hash associated with the username.
841 password_manager::HashPasswordManager hash_password_manager;
842 hash_password_manager.set_prefs(profile_->GetPrefs());
843 for (const auto& hash_data :
844 hash_password_manager.RetrieveAllPasswordHashes()) {
845 if (password_manager::AreUsernamesSame(
846 hash_data.username, /*is_username1_gaia_account=*/true, username,
847 /*is_username2_gaia_account=*/true)) {
848 OnGaiaPasswordChanged(username, /*is_other_gaia_password=*/true);
849 break;
850 }
851 }
852 }
853
OnGaiaPasswordChanged(const std::string & username,bool is_other_gaia_password)854 void ChromePasswordProtectionService::OnGaiaPasswordChanged(
855 const std::string& username,
856 bool is_other_gaia_password) {
857 DictionaryPrefUpdate unhandled_gaia_password_reuses(
858 profile_->GetPrefs(), prefs::kSafeBrowsingUnhandledGaiaPasswordReuses);
859 LogNumberOfReuseBeforeSyncPasswordChange(
860 unhandled_gaia_password_reuses->size());
861 unhandled_gaia_password_reuses->Clear();
862 if (!is_other_gaia_password)
863 MaybeLogPasswordCapture(/*did_log_in=*/true);
864 for (auto& observer : observer_list_)
865 observer.OnGaiaPasswordChanged();
866
867 // Disabled on Android, because enterprise reporting extension is not supported.
868 #if !defined(OS_ANDROID)
869 // Only report if the current password changed is the primary account and it's
870 // not a Gmail account or if the current password changed is a content area
871 // account and it's not a Gmail account.
872 if ((!is_other_gaia_password && !IsPrimaryAccountGmail()) ||
873 (is_other_gaia_password && !IsOtherGaiaAccountGmail(username)))
874 ReportPasswordChanged();
875 #endif
876 }
877
GetEnterpriseChangePasswordURL() const878 GURL ChromePasswordProtectionService::GetEnterpriseChangePasswordURL() const {
879 // If change password URL is specified in preferences, returns the
880 // corresponding pref value.
881 GURL enterprise_change_password_url =
882 GetPasswordProtectionChangePasswordURLPref(*profile_->GetPrefs());
883 if (!enterprise_change_password_url.is_empty())
884 return enterprise_change_password_url;
885
886 return GetDefaultChangePasswordURL();
887 }
888
GetDefaultChangePasswordURL() const889 GURL ChromePasswordProtectionService::GetDefaultChangePasswordURL() const {
890 // Computes the default GAIA change password URL.
891 const AccountInfo account_info = GetAccountInfo();
892 std::string account_email = account_info.email;
893 // This page will prompt for re-auth and then will prompt for a new password.
894 std::string account_url =
895 "https://myaccount.google.com/signinoptions/"
896 "password?utm_source=Google&utm_campaign=PhishGuard";
897 url::RawCanonOutputT<char> percent_encoded_email;
898 url::RawCanonOutputT<char> percent_encoded_account_url;
899 url::EncodeURIComponent(account_email.c_str(), account_email.length(),
900 &percent_encoded_email);
901 url::EncodeURIComponent(account_url.c_str(), account_url.length(),
902 &percent_encoded_account_url);
903 GURL change_password_url = GURL(base::StringPrintf(
904 "https://accounts.google.com/"
905 "AccountChooser?Email=%s&continue=%s",
906 std::string(percent_encoded_email.data(), percent_encoded_email.length())
907 .c_str(),
908 std::string(percent_encoded_account_url.data(),
909 percent_encoded_account_url.length())
910 .c_str()));
911 return google_util::AppendGoogleLocaleParam(
912 change_password_url, g_browser_process->GetApplicationLocale());
913 }
914
HandleUserActionOnModalWarning(content::WebContents * web_contents,ReusedPasswordAccountType password_type,RequestOutcome outcome,LoginReputationClientResponse::VerdictType verdict_type,const std::string & verdict_token,WarningAction action)915 void ChromePasswordProtectionService::HandleUserActionOnModalWarning(
916 content::WebContents* web_contents,
917 ReusedPasswordAccountType password_type,
918 RequestOutcome outcome,
919 LoginReputationClientResponse::VerdictType verdict_type,
920 const std::string& verdict_token,
921 WarningAction action) {
922 const Origin origin = Origin::Create(web_contents->GetLastCommittedURL());
923 int64_t navigation_id =
924 GetNavigationIDFromPrefsByOrigin(profile_->GetPrefs(), origin);
925
926 if (action == WarningAction::CHANGE_PASSWORD) {
927 RecordAction(UserMetricsAction(
928 "PasswordProtection.ModalWarning.ChangePasswordButtonClicked"));
929 LogDialogMetricsOnChangePassword(web_contents, password_type, navigation_id,
930 outcome, verdict_type, verdict_token);
931 OpenChangePasswordUrl(web_contents, password_type);
932 } else if (action == WarningAction::IGNORE_WARNING &&
933 password_type.is_account_syncing()) {
934 RecordAction(UserMetricsAction(
935 "PasswordProtection.ModalWarning.IgnoreButtonClicked"));
936 // No need to change state.
937 MaybeLogPasswordReuseDialogInteraction(
938 navigation_id, PasswordReuseDialogInteraction::WARNING_ACTION_IGNORED);
939 } else if (action == WarningAction::CLOSE &&
940 password_type.is_account_syncing()) {
941 RecordAction(
942 UserMetricsAction("PasswordProtection.ModalWarning.CloseWarning"));
943 // No need to change state.
944 MaybeLogPasswordReuseDialogInteraction(
945 navigation_id, PasswordReuseDialogInteraction::WARNING_UI_IGNORED);
946 }
947 RemoveWarningRequestsByWebContents(web_contents);
948 MaybeFinishCollectingThreatDetails(
949 web_contents,
950 /*did_proceed=*/action == WarningAction::CHANGE_PASSWORD);
951 }
952
LogDialogMetricsOnChangePassword(content::WebContents * web_contents,ReusedPasswordAccountType password_type,int64_t navigation_id,RequestOutcome outcome,LoginReputationClientResponse::VerdictType verdict_type,const std::string & verdict_token)953 void ChromePasswordProtectionService::LogDialogMetricsOnChangePassword(
954 content::WebContents* web_contents,
955 ReusedPasswordAccountType password_type,
956 int64_t navigation_id,
957 RequestOutcome outcome,
958 LoginReputationClientResponse::VerdictType verdict_type,
959 const std::string& verdict_token) {
960 if (password_type.is_account_syncing() ||
961 password_type.account_type() ==
962 ReusedPasswordAccountType::SAVED_PASSWORD) {
963 MaybeLogPasswordReuseDialogInteraction(
964 navigation_id, PasswordReuseDialogInteraction::WARNING_ACTION_TAKEN);
965 } else {
966 // |outcome| is only recorded as succeeded or response_already_cached.
967 MaybeLogPasswordReuseLookupResultWithVerdict(
968 web_contents, PasswordType::OTHER_GAIA_PASSWORD,
969 outcome == RequestOutcome::SUCCEEDED
970 ? PasswordReuseLookup::REQUEST_SUCCESS
971 : PasswordReuseLookup::CACHE_HIT,
972 GetVerdictToLogFromResponse(verdict_type), verdict_token);
973 }
974 }
975
OpenChangePasswordUrl(content::WebContents * web_contents,ReusedPasswordAccountType password_type)976 void ChromePasswordProtectionService::OpenChangePasswordUrl(
977 content::WebContents* web_contents,
978 ReusedPasswordAccountType password_type) {
979 if (password_type.account_type() ==
980 ReusedPasswordAccountType::NON_GAIA_ENTERPRISE) {
981 // Directly open enterprise change password page for enterprise password
982 // reuses.
983 OpenUrl(web_contents, GetEnterpriseChangePasswordURL(), content::Referrer(),
984 /*in_new_tab=*/true);
985 web_contents_with_unhandled_enterprise_reuses_.erase(web_contents);
986 } else if (password_type.account_type() !=
987 ReusedPasswordAccountType::SAVED_PASSWORD) {
988 // Opens accounts.google.com in a new tab.
989 OpenUrl(web_contents, GetDefaultChangePasswordURL(), content::Referrer(),
990 /*in_new_tab=*/true);
991 } else if (base::FeatureList::IsEnabled(
992 password_manager::features::kPasswordCheck)) {
993 #if BUILDFLAG(FULL_SAFE_BROWSING)
994 // Opens chrome://settings/passwords/check in a new tab.
995 chrome::ShowPasswordCheck(chrome::FindBrowserWithWebContents(web_contents));
996 password_manager::LogPasswordCheckReferrer(
997 password_manager::PasswordCheckReferrer::kPhishGuardDialog);
998 #endif
999 }
1000 }
1001
HandleUserActionOnPageInfo(content::WebContents * web_contents,ReusedPasswordAccountType password_type,WarningAction action)1002 void ChromePasswordProtectionService::HandleUserActionOnPageInfo(
1003 content::WebContents* web_contents,
1004 ReusedPasswordAccountType password_type,
1005 WarningAction action) {
1006 GURL url = web_contents->GetLastCommittedURL();
1007 const Origin origin = Origin::Create(url);
1008
1009 if (action == WarningAction::CHANGE_PASSWORD) {
1010 RecordAction(UserMetricsAction(
1011 "PasswordProtection.PageInfo.ChangePasswordButtonClicked"));
1012 OpenChangePasswordUrl(web_contents, password_type);
1013 return;
1014 }
1015
1016 if (action == WarningAction::MARK_AS_LEGITIMATE) {
1017 RecordAction(
1018 UserMetricsAction("PasswordProtection.PageInfo.MarkSiteAsLegitimate"));
1019 // TODO(vakh): There's no good enum to report this dialog interaction.
1020 // This needs to be investigated.
1021 UpdateSecurityState(SB_THREAT_TYPE_SAFE, password_type, web_contents);
1022 if (password_type.account_type() ==
1023 ReusedPasswordAccountType::NON_GAIA_ENTERPRISE) {
1024 web_contents_with_unhandled_enterprise_reuses_.erase(web_contents);
1025 } else {
1026 DictionaryPrefUpdate update(
1027 profile_->GetPrefs(),
1028 prefs::kSafeBrowsingUnhandledGaiaPasswordReuses);
1029 update->RemoveKey(origin.Serialize());
1030 }
1031
1032 // If the site is marked as legitimate and the phished password is
1033 // a saved password, remove the matching saved password credentials
1034 // from the compromised credentials table as the user has considered
1035 // the site safe.
1036 if (password_type.account_type() ==
1037 ReusedPasswordAccountType::SAVED_PASSWORD) {
1038 RemovePhishedSavedPasswordCredential(
1039 saved_passwords_matching_reused_credentials());
1040 }
1041 for (auto& observer : observer_list_)
1042 observer.OnMarkingSiteAsLegitimate(url);
1043 return;
1044 }
1045
1046 NOTREACHED();
1047 }
1048
HandleResetPasswordOnInterstitial(content::WebContents * web_contents,WarningAction action)1049 void ChromePasswordProtectionService::HandleResetPasswordOnInterstitial(
1050 content::WebContents* web_contents,
1051 WarningAction action) {
1052 RecordAction(
1053 UserMetricsAction("PasswordProtection.Interstitial.ResetPassword"));
1054 // Opens enterprise change password page in current tab for user to change
1055 // password.
1056 OpenUrl(web_contents, GetEnterpriseChangePasswordURL(),
1057 content::Referrer(web_contents->GetLastCommittedURL(),
1058 network::mojom::ReferrerPolicy::kDefault),
1059 /*in_new_tab=*/false);
1060 }
1061
GetWarningDetailText(ReusedPasswordAccountType password_type,std::vector<size_t> * placeholder_offsets) const1062 base::string16 ChromePasswordProtectionService::GetWarningDetailText(
1063 ReusedPasswordAccountType password_type,
1064 std::vector<size_t>* placeholder_offsets) const {
1065 DCHECK(password_type.account_type() == ReusedPasswordAccountType::GSUITE ||
1066 password_type.account_type() == ReusedPasswordAccountType::GMAIL ||
1067 password_type.account_type() ==
1068 ReusedPasswordAccountType::NON_GAIA_ENTERPRISE ||
1069 (password_type.account_type() ==
1070 ReusedPasswordAccountType::SAVED_PASSWORD &&
1071 base::FeatureList::IsEnabled(
1072 safe_browsing::kPasswordProtectionForSavedPasswords)));
1073 if (password_type.account_type() ==
1074 ReusedPasswordAccountType::NON_GAIA_ENTERPRISE) {
1075 return l10n_util::GetStringUTF16(
1076 IDS_PAGE_INFO_CHANGE_PASSWORD_DETAILS_ENTERPRISE);
1077 }
1078
1079 if (password_type.account_type() ==
1080 ReusedPasswordAccountType::SAVED_PASSWORD &&
1081 base::FeatureList::IsEnabled(
1082 safe_browsing::kPasswordProtectionForSavedPasswords)) {
1083 return GetWarningDetailTextForSavedPasswords(placeholder_offsets);
1084 }
1085
1086 bool enable_warning_for_non_sync_users = base::FeatureList::IsEnabled(
1087 safe_browsing::kPasswordProtectionForSignedInUsers);
1088 if (enable_warning_for_non_sync_users &&
1089 !password_type.is_account_syncing()) {
1090 return l10n_util::GetStringUTF16(
1091 IDS_PAGE_INFO_CHANGE_PASSWORD_DETAILS_SIGNED_IN_NON_SYNC);
1092 }
1093 if (password_type.account_type() != ReusedPasswordAccountType::GSUITE) {
1094 return l10n_util::GetStringUTF16(
1095 IDS_PAGE_INFO_CHANGE_PASSWORD_DETAILS_SYNC);
1096 }
1097
1098 std::string org_name = GetOrganizationName(password_type);
1099 if (!org_name.empty()) {
1100 return l10n_util::GetStringFUTF16(
1101 IDS_PAGE_INFO_CHANGE_PASSWORD_DETAILS_ENTERPRISE_WITH_ORG_NAME,
1102 base::UTF8ToUTF16(org_name));
1103 }
1104 return l10n_util::GetStringUTF16(
1105 IDS_PAGE_INFO_CHANGE_PASSWORD_DETAILS_ENTERPRISE);
1106 }
1107
1108 std::vector<base::string16>
GetPlaceholdersForSavedPasswordWarningText() const1109 ChromePasswordProtectionService::GetPlaceholdersForSavedPasswordWarningText()
1110 const {
1111 const std::vector<std::string>& matching_domains =
1112 saved_passwords_matching_domains();
1113 const std::list<std::string>& spoofed_domains = common_spoofed_domains();
1114
1115 // Show most commonly spoofed domains first.
1116 // This looks through the top priority spoofed domains and then checks to see
1117 // if it's in the matching domains.
1118 std::vector<base::string16> placeholders;
1119 for (auto priority_domain_iter = spoofed_domains.begin();
1120 priority_domain_iter != spoofed_domains.end(); ++priority_domain_iter) {
1121 std::string matching_domain;
1122
1123 // Check if any of the matching domains is equal or a suffix to the current
1124 // priority domain.
1125 if (std::find_if(matching_domains.begin(), matching_domains.end(),
1126 [priority_domain_iter,
1127 &matching_domain](const std::string& domain) {
1128 // Assigns the matching_domain to add into the priority
1129 // placeholders. This value is only used if the return
1130 // value of this function is true.
1131 matching_domain = domain;
1132 const base::StringPiece domainStringPiece(domain);
1133 // Checks for two cases:
1134 // 1. if the matching domain is equal to the current
1135 // priority domain or
1136 // 2. if "," + the current priority is a suffix of the
1137 // matching domain The second case covers eTLD+1.
1138 return (domain == *priority_domain_iter) ||
1139 base::EndsWith(domainStringPiece,
1140 "." + *priority_domain_iter);
1141 }) != matching_domains.end()) {
1142 placeholders.push_back(base::UTF8ToUTF16(matching_domain));
1143 }
1144 }
1145
1146 // If there are less than 3 saved default domains, check the saved
1147 // password domains to see if there are more that can be added to the
1148 // warning text.
1149 int domains_idx = placeholders.size();
1150 for (size_t idx = 0; idx < matching_domains.size() && domains_idx < 3;
1151 idx++) {
1152 // Do not add duplicate domains if it was already in the default domains.
1153 if (std::find(placeholders.begin(), placeholders.end(),
1154 base::UTF8ToUTF16(matching_domains[idx])) !=
1155 placeholders.end()) {
1156 continue;
1157 }
1158 placeholders.push_back(base::UTF8ToUTF16(matching_domains[idx]));
1159 domains_idx++;
1160 }
1161 return placeholders;
1162 }
1163
1164 base::string16
GetWarningDetailTextForSavedPasswords(std::vector<size_t> * placeholder_offsets) const1165 ChromePasswordProtectionService::GetWarningDetailTextForSavedPasswords(
1166 std::vector<size_t>* placeholder_offsets) const {
1167 std::vector<base::string16> placeholders =
1168 GetPlaceholdersForSavedPasswordWarningText();
1169 // If showing the saved passwords domain experiment is not on or if there is
1170 // are no saved domains, default to original saved passwords reuse warning.
1171 if (placeholders.size() == 0) {
1172 return l10n_util::GetStringUTF16(
1173 IDS_PAGE_INFO_CHANGE_PASSWORD_DETAILS_SAVED);
1174 }
1175
1176 if (base::FeatureList::IsEnabled(
1177 password_manager::features::kPasswordCheck)) {
1178 return GetWarningDetailTextToCheckSavedPasswords(placeholder_offsets);
1179 }
1180
1181 if (placeholders.size() == 1) {
1182 return l10n_util::GetStringFUTF16(
1183 IDS_PAGE_INFO_CHANGE_PASSWORD_DETAILS_SAVED_1_DOMAIN, placeholders,
1184 placeholder_offsets);
1185 } else if (placeholders.size() == 2) {
1186 return l10n_util::GetStringFUTF16(
1187 IDS_PAGE_INFO_CHANGE_PASSWORD_DETAILS_SAVED_2_DOMAINS, placeholders,
1188 placeholder_offsets);
1189 } else {
1190 return l10n_util::GetStringFUTF16(
1191 IDS_PAGE_INFO_CHANGE_PASSWORD_DETAILS_SAVED_3_DOMAINS, placeholders,
1192 placeholder_offsets);
1193 }
1194 }
1195
1196 base::string16
GetWarningDetailTextToCheckSavedPasswords(std::vector<size_t> * placeholder_offsets) const1197 ChromePasswordProtectionService::GetWarningDetailTextToCheckSavedPasswords(
1198 std::vector<size_t>* placeholder_offsets) const {
1199 std::vector<base::string16> placeholders =
1200 GetPlaceholdersForSavedPasswordWarningText();
1201 if (placeholders.size() == 1) {
1202 return l10n_util::GetStringFUTF16(
1203 IDS_PAGE_INFO_CHECK_PASSWORD_DETAILS_SAVED_1_DOMAIN, placeholders,
1204 placeholder_offsets);
1205 } else if (placeholders.size() == 2) {
1206 return l10n_util::GetStringFUTF16(
1207 IDS_PAGE_INFO_CHECK_PASSWORD_DETAILS_SAVED_2_DOMAIN, placeholders,
1208 placeholder_offsets);
1209 } else {
1210 return l10n_util::GetStringFUTF16(
1211 IDS_PAGE_INFO_CHECK_PASSWORD_DETAILS_SAVED_3_DOMAIN, placeholders,
1212 placeholder_offsets);
1213 }
1214 }
1215
GetOrganizationName(ReusedPasswordAccountType password_type) const1216 std::string ChromePasswordProtectionService::GetOrganizationName(
1217 ReusedPasswordAccountType password_type) const {
1218 if (password_type.account_type() != ReusedPasswordAccountType::GSUITE) {
1219 return std::string();
1220 }
1221
1222 std::string email =
1223 password_type.is_account_syncing()
1224 ? GetAccountInfo().email
1225 : GetSignedInNonSyncAccount(username_for_last_shown_warning()).email;
1226 return email.empty() ? std::string() : gaia::ExtractDomainName(email);
1227 }
1228 #endif
1229
1230 // Disabled on Android, because enterprise reporting extension is not supported.
1231 #if !defined(OS_ANDROID)
MaybeReportPasswordReuseDetected(content::WebContents * web_contents,const std::string & username,PasswordType password_type,bool is_phishing_url)1232 void ChromePasswordProtectionService::MaybeReportPasswordReuseDetected(
1233 content::WebContents* web_contents,
1234 const std::string& username,
1235 PasswordType password_type,
1236 bool is_phishing_url) {
1237 auto reused_password_account_type =
1238 GetPasswordProtectionReusedPasswordAccountType(password_type, username);
1239 if (reused_password_account_type.account_type() ==
1240 ReusedPasswordAccountType::UNKNOWN) {
1241 return;
1242 }
1243
1244 // When a PasswordFieldFocus event is sent, a PasswordProtectionRequest is
1245 // sent which means the password reuse type is unknown. We do not want to
1246 // report these events as PasswordReuse events. Also do not send reports for
1247 // Gmail accounts.
1248 bool can_log_password_reuse_event =
1249 (password_type == PasswordType::ENTERPRISE_PASSWORD ||
1250 reused_password_account_type.account_type() ==
1251 ReusedPasswordAccountType::GSUITE) &&
1252 (password_type != PasswordType::PASSWORD_TYPE_UNKNOWN);
1253 if (!IsIncognito() && can_log_password_reuse_event) {
1254 // User name should only be empty when MaybeStartPasswordFieldOnFocusRequest
1255 // is called.
1256 std::string username_or_email =
1257 username.empty() ? GetAccountInfo().email : username;
1258 extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile_)
1259 ->OnPolicySpecifiedPasswordReuseDetected(
1260 web_contents->GetLastCommittedURL(), username_or_email,
1261 is_phishing_url);
1262 }
1263 }
1264
ReportPasswordChanged()1265 void ChromePasswordProtectionService::ReportPasswordChanged() {
1266 if (!IsIncognito()) {
1267 extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile_)
1268 ->OnPolicySpecifiedPasswordChanged(GetAccountInfo().email);
1269 }
1270 }
1271 #endif
1272
1273 #if defined(PASSWORD_REUSE_WARNING_ENABLED)
HasUnhandledEnterprisePasswordReuse(content::WebContents * web_contents) const1274 bool ChromePasswordProtectionService::HasUnhandledEnterprisePasswordReuse(
1275 content::WebContents* web_contents) const {
1276 return web_contents_with_unhandled_enterprise_reuses_.find(web_contents) !=
1277 web_contents_with_unhandled_enterprise_reuses_.end();
1278 }
1279
OnWarningTriggerChanged()1280 void ChromePasswordProtectionService::OnWarningTriggerChanged() {
1281 const base::Value* pref_value = pref_change_registrar_->prefs()->Get(
1282 prefs::kPasswordProtectionWarningTrigger);
1283 // If password protection is not turned off, do nothing.
1284 if (static_cast<PasswordProtectionTrigger>(pref_value->GetInt()) !=
1285 PASSWORD_PROTECTION_OFF) {
1286 return;
1287 }
1288
1289 // Clears captured enterprise password hashes or GSuite sync password hashes.
1290 scoped_refptr<password_manager::PasswordStore> password_store =
1291 GetProfilePasswordStore();
1292
1293 password_store->ClearAllNonGmailPasswordHash();
1294 password_store->ClearAllEnterprisePasswordHash();
1295 }
1296
OnEnterprisePasswordUrlChanged()1297 void ChromePasswordProtectionService::OnEnterprisePasswordUrlChanged() {
1298 GetProfilePasswordStore()->ScheduleEnterprisePasswordURLUpdate();
1299 }
1300
CanShowInterstitial(ReusedPasswordAccountType password_type,const GURL & main_frame_url)1301 bool ChromePasswordProtectionService::CanShowInterstitial(
1302 ReusedPasswordAccountType password_type,
1303 const GURL& main_frame_url) {
1304 bool is_supported_password_type =
1305 password_type.account_type() == ReusedPasswordAccountType::GSUITE ||
1306 password_type.account_type() ==
1307 ReusedPasswordAccountType::NON_GAIA_ENTERPRISE;
1308 return IsInPasswordAlertMode(password_type) && is_supported_password_type &&
1309 !IsURLWhitelistedForPasswordEntry(main_frame_url);
1310 }
1311
SetLogPasswordCaptureTimer(const base::TimeDelta & delay)1312 void ChromePasswordProtectionService::SetLogPasswordCaptureTimer(
1313 const base::TimeDelta& delay) {
1314 // This will replace any pending timer.
1315 log_password_capture_timer_.Start(
1316 FROM_HERE, delay,
1317 base::BindOnce(&ChromePasswordProtectionService::MaybeLogPasswordCapture,
1318 base::Unretained(this), false));
1319 }
1320
MaybeLogPasswordCapture(bool did_log_in)1321 void ChromePasswordProtectionService::MaybeLogPasswordCapture(bool did_log_in) {
1322 DCHECK_CURRENTLY_ON(BrowserThread::UI);
1323 // We skip this event and not set a timer if the profile is in incognito. When
1324 // the user logs in in the future, MaybeLogPasswordCapture() will be called
1325 // immediately then and will restart the timer.
1326 if (IsIncognito() || sync_password_hash_.empty())
1327 return;
1328
1329 syncer::UserEventService* user_event_service =
1330 browser_sync::UserEventServiceFactory::GetForProfile(profile_);
1331 if (!user_event_service)
1332 return;
1333
1334 std::unique_ptr<UserEventSpecifics> specifics = GetNewUserEventSpecifics();
1335 auto* const password_captured =
1336 specifics->mutable_gaia_password_captured_event();
1337 password_captured->set_event_trigger(
1338 did_log_in ? GaiaPasswordCaptured::USER_LOGGED_IN
1339 : GaiaPasswordCaptured::EXPIRED_28D_TIMER);
1340
1341 WebUIInfoSingleton::GetInstance()->AddToPGEvents(*specifics);
1342 user_event_service->RecordUserEvent(std::move(specifics));
1343
1344 // Set a timer to log it again in 24-28 days. Spread it to avoid hammering the
1345 // backend with fixed cycle after this code lands in Stable.
1346 base::TimeDelta delay = base::TimeDelta::FromDays(
1347 (kPasswordCaptureEventLogFreqDaysMin +
1348 base::RandInt(0, kPasswordCaptureEventLogFreqDaysExtra)));
1349 SetLogPasswordCaptureTimer(delay);
1350
1351 // Write the deadline to a pref to carry over restarts.
1352 SetDelayInPref(profile_->GetPrefs(),
1353 prefs::kSafeBrowsingNextPasswordCaptureEventLogTime, delay);
1354 }
1355
UpdateSecurityState(SBThreatType threat_type,ReusedPasswordAccountType password_type,content::WebContents * web_contents)1356 void ChromePasswordProtectionService::UpdateSecurityState(
1357 SBThreatType threat_type,
1358 ReusedPasswordAccountType password_type,
1359 content::WebContents* web_contents) {
1360 DCHECK_CURRENTLY_ON(BrowserThread::UI);
1361 const GURL url = web_contents->GetLastCommittedURL();
1362 if (!url.is_valid())
1363 return;
1364
1365 const GURL url_with_empty_path = url.GetWithEmptyPath();
1366 if (threat_type == SB_THREAT_TYPE_SAFE) {
1367 ui_manager_->RemoveWhitelistUrlSet(url_with_empty_path, web_contents,
1368 /*from_pending_only=*/false);
1369 // Overrides cached verdicts.
1370 LoginReputationClientResponse verdict;
1371 GetCachedVerdict(url, LoginReputationClientRequest::PASSWORD_REUSE_EVENT,
1372 password_type, &verdict);
1373 verdict.set_verdict_type(LoginReputationClientResponse::SAFE);
1374 verdict.set_cache_duration_sec(kOverrideVerdictCacheDurationSec);
1375 CacheVerdict(url, LoginReputationClientRequest::PASSWORD_REUSE_EVENT,
1376 password_type, verdict, base::Time::Now());
1377 return;
1378 }
1379
1380 SBThreatType current_threat_type = SB_THREAT_TYPE_UNUSED;
1381 // If user already click-through interstitial warning, or if there's already
1382 // a dangerous security state showing, we'll override it.
1383 if (ui_manager_->IsUrlWhitelistedOrPendingForWebContents(
1384 url_with_empty_path, /*is_subresource=*/false,
1385 web_contents->GetController().GetLastCommittedEntry(), web_contents,
1386 /*whitelist_only=*/false, ¤t_threat_type)) {
1387 DCHECK_NE(SB_THREAT_TYPE_UNUSED, current_threat_type);
1388 if (current_threat_type == threat_type)
1389 return;
1390 // Resets previous threat type.
1391 ui_manager_->RemoveWhitelistUrlSet(url_with_empty_path, web_contents,
1392 /*from_pending_only=*/false);
1393 }
1394 ui_manager_->AddToWhitelistUrlSet(url_with_empty_path, web_contents,
1395 /*is_pending=*/true, threat_type);
1396 }
1397 #endif
1398
1399 const policy::BrowserPolicyConnector*
GetBrowserPolicyConnector() const1400 ChromePasswordProtectionService::GetBrowserPolicyConnector() const {
1401 return g_browser_process->browser_policy_connector();
1402 }
1403
FillReferrerChain(const GURL & event_url,SessionID event_tab_id,LoginReputationClientRequest::Frame * frame)1404 void ChromePasswordProtectionService::FillReferrerChain(
1405 const GURL& event_url,
1406 SessionID event_tab_id,
1407 LoginReputationClientRequest::Frame* frame) {
1408 DCHECK_CURRENTLY_ON(BrowserThread::UI);
1409 SafeBrowsingNavigationObserverManager::AttributionResult result =
1410 navigation_observer_manager_->IdentifyReferrerChainByEventURL(
1411 event_url, event_tab_id, kPasswordEventAttributionUserGestureLimit,
1412 frame->mutable_referrer_chain());
1413 size_t referrer_chain_length = frame->referrer_chain().size();
1414 UMA_HISTOGRAM_COUNTS_100(
1415 "SafeBrowsing.ReferrerURLChainSize.PasswordEventAttribution",
1416 referrer_chain_length);
1417 UMA_HISTOGRAM_ENUMERATION(
1418 "SafeBrowsing.ReferrerAttributionResult.PasswordEventAttribution", result,
1419 SafeBrowsingNavigationObserverManager::ATTRIBUTION_FAILURE_TYPE_MAX);
1420
1421 // Determines how many recent navigation events to append to referrer chain.
1422 size_t recent_navigations_to_collect =
1423 profile_ ? SafeBrowsingNavigationObserverManager::
1424 CountOfRecentNavigationsToAppend(*profile_, result)
1425 : 0u;
1426 navigation_observer_manager_->AppendRecentNavigations(
1427 recent_navigations_to_collect, frame->mutable_referrer_chain());
1428 }
1429
GetSyncPasswordHashFromPrefs()1430 std::string ChromePasswordProtectionService::GetSyncPasswordHashFromPrefs() {
1431 if (!sync_password_hash_provider_for_testing_.is_null())
1432 return sync_password_hash_provider_for_testing_.Run();
1433
1434 password_manager::HashPasswordManager hash_password_manager;
1435 hash_password_manager.set_prefs(profile_->GetPrefs());
1436 base::Optional<password_manager::PasswordHashData> sync_hash_data =
1437 hash_password_manager.RetrievePasswordHash(GetAccountInfo().email,
1438 /*is_gaia_password=*/true);
1439 return sync_hash_data ? base::NumberToString(sync_hash_data->hash)
1440 : std::string();
1441 }
1442
GetPrefs()1443 PrefService* ChromePasswordProtectionService::GetPrefs() {
1444 return profile_->GetPrefs();
1445 }
1446
IsSafeBrowsingEnabled()1447 bool ChromePasswordProtectionService::IsSafeBrowsingEnabled() {
1448 return ::safe_browsing::IsSafeBrowsingEnabled(*GetPrefs());
1449 }
1450
IsExtendedReporting()1451 bool ChromePasswordProtectionService::IsExtendedReporting() {
1452 return IsExtendedReportingEnabled(*GetPrefs());
1453 }
1454
IsEnhancedProtection()1455 bool ChromePasswordProtectionService::IsEnhancedProtection() {
1456 return IsEnhancedProtectionEnabled(*GetPrefs());
1457 }
1458
IsIncognito()1459 bool ChromePasswordProtectionService::IsIncognito() {
1460 return profile_->IsOffTheRecord();
1461 }
1462
IsUserMBBOptedIn()1463 bool ChromePasswordProtectionService::IsUserMBBOptedIn() {
1464 return GetPrefs()->GetBoolean(
1465 unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled);
1466 }
1467
IsInPasswordAlertMode(ReusedPasswordAccountType password_type)1468 bool ChromePasswordProtectionService::IsInPasswordAlertMode(
1469 ReusedPasswordAccountType password_type) {
1470 return GetPasswordProtectionWarningTriggerPref(password_type) ==
1471 PASSWORD_REUSE;
1472 }
1473
IsPingingEnabled(LoginReputationClientRequest::TriggerType trigger_type,ReusedPasswordAccountType password_type)1474 bool ChromePasswordProtectionService::IsPingingEnabled(
1475 LoginReputationClientRequest::TriggerType trigger_type,
1476 ReusedPasswordAccountType password_type) {
1477 if (!IsSafeBrowsingEnabled()) {
1478 return false;
1479 }
1480 bool extended_reporting_enabled = IsExtendedReporting();
1481 if (trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT) {
1482 if (password_type.account_type() ==
1483 ReusedPasswordAccountType::SAVED_PASSWORD) {
1484 return true;
1485 }
1486
1487 // Only override policy if password protection is off for Gmail users.
1488 if (GetPasswordProtectionWarningTriggerPref(password_type) ==
1489 PASSWORD_PROTECTION_OFF) {
1490 return false;
1491 }
1492 // If the account type is UNKNOWN (i.e. AccountInfo fields could not be
1493 // retrieved from server), pings should be gated by SBER.
1494 if (password_type.account_type() == ReusedPasswordAccountType::UNKNOWN) {
1495 return extended_reporting_enabled;
1496 }
1497
1498 // Only saved password reuse warnings are shown on Android, so other types of
1499 // password reuse events should be gated by extended reporting.
1500 #if defined(OS_ANDROID)
1501 return extended_reporting_enabled;
1502 #else
1503 return true;
1504 #endif
1505 }
1506
1507 return !IsIncognito() && extended_reporting_enabled;
1508 }
1509
GetPingNotSentReason(LoginReputationClientRequest::TriggerType trigger_type,const GURL & url,ReusedPasswordAccountType password_type)1510 RequestOutcome ChromePasswordProtectionService::GetPingNotSentReason(
1511 LoginReputationClientRequest::TriggerType trigger_type,
1512 const GURL& url,
1513 ReusedPasswordAccountType password_type) {
1514 DCHECK(!CanSendPing(trigger_type, url, password_type));
1515 if (IsInExcludedCountry()) {
1516 return RequestOutcome::EXCLUDED_COUNTRY;
1517 }
1518 if (!IsSafeBrowsingEnabled()) {
1519 return RequestOutcome::SAFE_BROWSING_DISABLED;
1520 }
1521 if (IsIncognito()) {
1522 return RequestOutcome::DISABLED_DUE_TO_INCOGNITO;
1523 }
1524 if (trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT &&
1525 password_type.account_type() !=
1526 ReusedPasswordAccountType::SAVED_PASSWORD &&
1527 GetPasswordProtectionWarningTriggerPref(password_type) ==
1528 PASSWORD_PROTECTION_OFF) {
1529 return RequestOutcome::TURNED_OFF_BY_ADMIN;
1530 }
1531 PrefService* prefs = profile_->GetPrefs();
1532 if (IsURLWhitelistedByPolicy(url, *prefs)) {
1533 return RequestOutcome::MATCHED_ENTERPRISE_WHITELIST;
1534 }
1535 if (MatchesPasswordProtectionChangePasswordURL(url, *prefs)) {
1536 return RequestOutcome::MATCHED_ENTERPRISE_CHANGE_PASSWORD_URL;
1537 }
1538 if (MatchesPasswordProtectionLoginURL(url, *prefs)) {
1539 return RequestOutcome::MATCHED_ENTERPRISE_LOGIN_URL;
1540 }
1541 if (IsInPasswordAlertMode(password_type)) {
1542 return RequestOutcome::PASSWORD_ALERT_MODE;
1543 }
1544 return RequestOutcome::DISABLED_DUE_TO_USER_POPULATION;
1545 }
1546
IsHistorySyncEnabled()1547 bool ChromePasswordProtectionService::IsHistorySyncEnabled() {
1548 syncer::SyncService* sync =
1549 ProfileSyncServiceFactory::GetForProfile(profile_);
1550 return sync && sync->IsSyncFeatureActive() && !sync->IsLocalSyncEnabled() &&
1551 sync->GetActiveDataTypes().Has(syncer::HISTORY_DELETE_DIRECTIVES);
1552 }
1553
IsPrimaryAccountSyncing() const1554 bool ChromePasswordProtectionService::IsPrimaryAccountSyncing() const {
1555 syncer::SyncService* sync =
1556 ProfileSyncServiceFactory::GetForProfile(profile_);
1557 return sync && sync->IsSyncFeatureActive() && !sync->IsLocalSyncEnabled();
1558 }
1559
IsPrimaryAccountSignedIn() const1560 bool ChromePasswordProtectionService::IsPrimaryAccountSignedIn() const {
1561 return !GetAccountInfo().account_id.empty() &&
1562 !GetAccountInfo().hosted_domain.empty();
1563 }
1564
1565 // TODO(bdea): Combine the next two methods.
IsPrimaryAccountGmail() const1566 bool ChromePasswordProtectionService::IsPrimaryAccountGmail() const {
1567 return GetAccountInfo().hosted_domain == kNoHostedDomainFound;
1568 }
1569
IsOtherGaiaAccountGmail(const std::string & username) const1570 bool ChromePasswordProtectionService::IsOtherGaiaAccountGmail(
1571 const std::string& username) const {
1572 return GetSignedInNonSyncAccount(username).hosted_domain ==
1573 kNoHostedDomainFound;
1574 }
1575
GetSignedInNonSyncAccount(const std::string & username) const1576 AccountInfo ChromePasswordProtectionService::GetSignedInNonSyncAccount(
1577 const std::string& username) const {
1578 auto* identity_manager = IdentityManagerFactory::GetForProfileIfExists(
1579 profile_->GetOriginalProfile());
1580
1581 if (!identity_manager)
1582 return AccountInfo();
1583
1584 std::vector<CoreAccountInfo> signed_in_accounts =
1585 identity_manager->GetAccountsWithRefreshTokens();
1586 auto account_iterator =
1587 std::find_if(signed_in_accounts.begin(), signed_in_accounts.end(),
1588 [username](const auto& account) {
1589 return password_manager::AreUsernamesSame(
1590 account.email,
1591 /*is_username1_gaia_account=*/true, username,
1592 /*is_username2_gaia_account=*/true);
1593 });
1594 if (account_iterator == signed_in_accounts.end())
1595 return AccountInfo();
1596
1597 return identity_manager
1598 ->FindExtendedAccountInfoForAccountWithRefreshToken(*account_iterator)
1599 .value_or(AccountInfo());
1600 }
1601
IsInExcludedCountry()1602 bool ChromePasswordProtectionService::IsInExcludedCountry() {
1603 variations::VariationsService* variations_service =
1604 g_browser_process->variations_service();
1605 if (!variations_service)
1606 return false;
1607 return base::Contains(GetExcludedCountries(),
1608 variations_service->GetStoredPermanentCountry());
1609 }
1610
1611 PasswordReuseEvent::SyncAccountType
GetSyncAccountType() const1612 ChromePasswordProtectionService::GetSyncAccountType() const {
1613 const AccountInfo account_info = GetAccountInfo();
1614 if (!IsPrimaryAccountSignedIn()) {
1615 return PasswordReuseEvent::NOT_SIGNED_IN;
1616 }
1617
1618 // For gmail or googlemail account, the hosted_domain will always be
1619 // kNoHostedDomainFound.
1620 return account_info.hosted_domain == kNoHostedDomainFound
1621 ? PasswordReuseEvent::GMAIL
1622 : PasswordReuseEvent::GSUITE;
1623 }
1624
1625 void ChromePasswordProtectionService::
RemoveUnhandledSyncPasswordReuseOnURLsDeleted(bool all_history,const history::URLRows & deleted_rows)1626 RemoveUnhandledSyncPasswordReuseOnURLsDeleted(
1627 bool all_history,
1628 const history::URLRows& deleted_rows) {
1629 DCHECK_CURRENTLY_ON(BrowserThread::UI);
1630
1631 DictionaryPrefUpdate unhandled_sync_password_reuses(
1632 profile_->GetPrefs(), prefs::kSafeBrowsingUnhandledGaiaPasswordReuses);
1633 if (all_history) {
1634 unhandled_sync_password_reuses->Clear();
1635 return;
1636 }
1637
1638 for (const history::URLRow& row : deleted_rows) {
1639 if (!row.url().SchemeIsHTTPOrHTTPS())
1640 continue;
1641 unhandled_sync_password_reuses->RemoveKey(
1642 Origin::Create(row.url()).Serialize());
1643 }
1644 }
1645
UserClickedThroughSBInterstitial(content::WebContents * web_contents)1646 bool ChromePasswordProtectionService::UserClickedThroughSBInterstitial(
1647 content::WebContents* web_contents) {
1648 SBThreatType current_threat_type;
1649 if (!ui_manager_->IsUrlWhitelistedOrPendingForWebContents(
1650 web_contents->GetLastCommittedURL().GetWithEmptyPath(),
1651 /*is_subresource=*/false,
1652 web_contents->GetController().GetLastCommittedEntry(), web_contents,
1653 /*whitelist_only=*/true, ¤t_threat_type)) {
1654 return false;
1655 }
1656 return current_threat_type == SB_THREAT_TYPE_URL_PHISHING ||
1657 current_threat_type == SB_THREAT_TYPE_URL_MALWARE ||
1658 current_threat_type == SB_THREAT_TYPE_URL_UNWANTED ||
1659 current_threat_type == SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING ||
1660 current_threat_type == SB_THREAT_TYPE_URL_CLIENT_SIDE_MALWARE;
1661 }
1662
GetAccountInfo() const1663 AccountInfo ChromePasswordProtectionService::GetAccountInfo() const {
1664 auto* identity_manager = IdentityManagerFactory::GetForProfileIfExists(
1665 profile_->GetOriginalProfile());
1666 if (!identity_manager)
1667 return AccountInfo();
1668
1669 base::Optional<AccountInfo> primary_account_info =
1670 identity_manager->FindExtendedAccountInfoForAccountWithRefreshToken(
1671 identity_manager->GetPrimaryAccountInfo());
1672
1673 return primary_account_info.value_or(AccountInfo());
1674 }
1675
ChromePasswordProtectionService(Profile * profile,scoped_refptr<SafeBrowsingUIManager> ui_manager,StringProvider sync_password_hash_provider,VerdictCacheManager * cache_manager)1676 ChromePasswordProtectionService::ChromePasswordProtectionService(
1677 Profile* profile,
1678 scoped_refptr<SafeBrowsingUIManager> ui_manager,
1679 StringProvider sync_password_hash_provider,
1680 VerdictCacheManager* cache_manager)
1681 : PasswordProtectionService(nullptr, nullptr, nullptr),
1682 ui_manager_(ui_manager),
1683 trigger_manager_(nullptr),
1684 profile_(profile),
1685 cache_manager_(cache_manager),
1686 sync_password_hash_provider_for_testing_(sync_password_hash_provider) {
1687 Init();
1688 }
1689
1690 std::unique_ptr<PasswordProtectionNavigationThrottle>
MaybeCreateNavigationThrottle(content::NavigationHandle * navigation_handle)1691 MaybeCreateNavigationThrottle(content::NavigationHandle* navigation_handle) {
1692 Profile* profile = Profile::FromBrowserContext(
1693 navigation_handle->GetWebContents()->GetBrowserContext());
1694 ChromePasswordProtectionService* service =
1695 ChromePasswordProtectionService::GetPasswordProtectionService(profile);
1696 // |service| can be null in tests.
1697 return service ? service->MaybeCreateNavigationThrottle(navigation_handle)
1698 : nullptr;
1699 }
1700
1701 PasswordProtectionTrigger
GetPasswordProtectionWarningTriggerPref(ReusedPasswordAccountType password_type) const1702 ChromePasswordProtectionService::GetPasswordProtectionWarningTriggerPref(
1703 ReusedPasswordAccountType password_type) const {
1704 if (password_type.account_type() == ReusedPasswordAccountType::GMAIL ||
1705 (password_type.account_type() ==
1706 ReusedPasswordAccountType::SAVED_PASSWORD &&
1707 base::FeatureList::IsEnabled(
1708 safe_browsing::kPasswordProtectionForSavedPasswords)))
1709 return PHISHING_REUSE;
1710
1711 bool is_policy_managed = profile_->GetPrefs()->HasPrefPath(
1712 prefs::kPasswordProtectionWarningTrigger);
1713 PasswordProtectionTrigger trigger_level =
1714 static_cast<PasswordProtectionTrigger>(profile_->GetPrefs()->GetInteger(
1715 prefs::kPasswordProtectionWarningTrigger));
1716 return is_policy_managed ? trigger_level : PHISHING_REUSE;
1717 }
1718
IsURLWhitelistedForPasswordEntry(const GURL & url) const1719 bool ChromePasswordProtectionService::IsURLWhitelistedForPasswordEntry(
1720 const GURL& url) const {
1721 if (!profile_)
1722 return false;
1723
1724 PrefService* prefs = profile_->GetPrefs();
1725 return IsURLWhitelistedByPolicy(url, *prefs) ||
1726 MatchesPasswordProtectionChangePasswordURL(url, *prefs) ||
1727 MatchesPasswordProtectionLoginURL(url, *prefs);
1728 }
1729
PersistPhishedSavedPasswordCredential(const std::vector<password_manager::MatchingReusedCredential> & matching_reused_credentials)1730 void ChromePasswordProtectionService::PersistPhishedSavedPasswordCredential(
1731 const std::vector<password_manager::MatchingReusedCredential>&
1732 matching_reused_credentials) {
1733 if (!profile_)
1734 return;
1735
1736 for (const auto& credential : matching_reused_credentials) {
1737 password_manager::PasswordStore* password_store =
1738 GetStoreForReusedCredential(credential);
1739 // Password store can be null in tests.
1740 if (!password_store) {
1741 continue;
1742 }
1743 password_store->AddCompromisedCredentials(
1744 {credential.signon_realm, credential.username, base::Time::Now(),
1745 password_manager::CompromiseType::kPhished});
1746 }
1747 }
1748
RemovePhishedSavedPasswordCredential(const std::vector<password_manager::MatchingReusedCredential> & matching_reused_credentials)1749 void ChromePasswordProtectionService::RemovePhishedSavedPasswordCredential(
1750 const std::vector<password_manager::MatchingReusedCredential>&
1751 matching_reused_credentials) {
1752 if (!profile_)
1753 return;
1754
1755 for (const auto& credential : matching_reused_credentials) {
1756 password_manager::PasswordStore* password_store =
1757 GetStoreForReusedCredential(credential);
1758 // Password store can be null in tests.
1759 if (!password_store) {
1760 continue;
1761 }
1762 password_store->RemoveCompromisedCredentials(
1763 credential.signon_realm, credential.username,
1764 password_manager::RemoveCompromisedCredentialsReason::
1765 kMarkSiteAsLegitimate);
1766 }
1767 }
1768
1769 password_manager::PasswordStore*
GetProfilePasswordStore() const1770 ChromePasswordProtectionService::GetProfilePasswordStore() const {
1771 // Always use EXPLICIT_ACCESS as the password manager checks IsIncognito
1772 // itself when it shouldn't access the PasswordStore.
1773 return PasswordStoreFactory::GetForProfile(profile_,
1774 ServiceAccessType::EXPLICIT_ACCESS)
1775 .get();
1776 }
1777
1778 password_manager::PasswordStore*
GetAccountPasswordStore() const1779 ChromePasswordProtectionService::GetAccountPasswordStore() const {
1780 // Always use EXPLICIT_ACCESS as the password manager checks IsIncognito
1781 // itself when it shouldn't access the PasswordStore.
1782 return AccountPasswordStoreFactory::GetForProfile(
1783 profile_, ServiceAccessType::EXPLICIT_ACCESS)
1784 .get();
1785 }
1786
SanitizeReferrerChain(ReferrerChain * referrer_chain)1787 void ChromePasswordProtectionService::SanitizeReferrerChain(
1788 ReferrerChain* referrer_chain) {
1789 SafeBrowsingNavigationObserverManager::SanitizeReferrerChain(referrer_chain);
1790 }
1791
CanSendSamplePing()1792 bool ChromePasswordProtectionService::CanSendSamplePing() {
1793 // Send a sample ping only 1% of the time.
1794 return IsExtendedReporting() && !IsIncognito() &&
1795 (bypass_probability_for_tests_ ||
1796 base::RandDouble() <= kProbabilityForSendingReportsFromSafeURLs);
1797 }
1798
1799 // Stores |verdict| in |settings| based on its |trigger_type|, |url|,
1800 // reused |password_type|, |verdict| and |receive_time|.
CacheVerdict(const GURL & url,LoginReputationClientRequest::TriggerType trigger_type,ReusedPasswordAccountType password_type,const LoginReputationClientResponse & verdict,const base::Time & receive_time)1801 void ChromePasswordProtectionService::CacheVerdict(
1802 const GURL& url,
1803 LoginReputationClientRequest::TriggerType trigger_type,
1804 ReusedPasswordAccountType password_type,
1805 const LoginReputationClientResponse& verdict,
1806 const base::Time& receive_time) {
1807 if (!CanGetReputationOfURL(url) || IsIncognito())
1808 return;
1809 cache_manager_->CachePhishGuardVerdict(trigger_type, password_type, verdict,
1810 receive_time);
1811 }
1812
1813 // Looks up |settings| to find the cached verdict response. If verdict is not
1814 // available or is expired, return VERDICT_TYPE_UNSPECIFIED. Can be called on
1815 // any thread.
1816 LoginReputationClientResponse::VerdictType
GetCachedVerdict(const GURL & url,LoginReputationClientRequest::TriggerType trigger_type,ReusedPasswordAccountType password_type,LoginReputationClientResponse * out_response)1817 ChromePasswordProtectionService::GetCachedVerdict(
1818 const GURL& url,
1819 LoginReputationClientRequest::TriggerType trigger_type,
1820 ReusedPasswordAccountType password_type,
1821 LoginReputationClientResponse* out_response) {
1822 if (!url.is_valid() || !CanGetReputationOfURL(url))
1823 return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED;
1824 return cache_manager_->GetCachedPhishGuardVerdict(
1825 url, trigger_type, password_type, out_response);
1826 }
1827
GetStoredVerdictCount(LoginReputationClientRequest::TriggerType trigger_type)1828 int ChromePasswordProtectionService::GetStoredVerdictCount(
1829 LoginReputationClientRequest::TriggerType trigger_type) {
1830 return cache_manager_->GetStoredPhishGuardVerdictCount(trigger_type);
1831 }
1832
1833 #if BUILDFLAG(FULL_SAFE_BROWSING)
IsUnderAdvancedProtection()1834 bool ChromePasswordProtectionService::IsUnderAdvancedProtection() {
1835 return AdvancedProtectionStatusManagerFactory::GetForProfile(profile_)
1836 ->IsUnderAdvancedProtection();
1837 }
1838
GetCurrentContentAreaSize() const1839 gfx::Size ChromePasswordProtectionService::GetCurrentContentAreaSize() const {
1840 return BrowserView::GetBrowserViewForBrowser(
1841 BrowserList::GetInstance()->GetLastActive())
1842 ->GetContentsSize();
1843 }
1844 #endif // FULL_SAFE_BROWSING
1845
1846 password_manager::PasswordStore*
GetStoreForReusedCredential(const password_manager::MatchingReusedCredential & reused_credential)1847 ChromePasswordProtectionService::GetStoreForReusedCredential(
1848 const password_manager::MatchingReusedCredential& reused_credential) {
1849 if (!profile_)
1850 return nullptr;
1851 return reused_credential.in_store ==
1852 password_manager::PasswordForm::Store::kAccountStore
1853 ? GetAccountPasswordStore()
1854 : GetProfilePasswordStore();
1855 }
1856
1857 } // namespace safe_browsing
1858