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, &current_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, &current_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