| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef COMPONENTS_SAFE_BROWSING_PASSWORD_PROTECTION_PASSWORD_PROTECTION_SERVICE_H_ |
| #define COMPONENTS_SAFE_BROWSING_PASSWORD_PROTECTION_PASSWORD_PROTECTION_SERVICE_H_ |
| |
| #include <set> |
| #include <unordered_map> |
| |
| #include "base/callback.h" |
| #include "base/feature_list.h" |
| #include "base/gtest_prod_util.h" |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/scoped_observer.h" |
| #include "base/task/cancelable_task_tracker.h" |
| #include "base/values.h" |
| #include "components/history/core/browser/history_service_observer.h" |
| #include "components/password_manager/core/browser/password_manager_metrics_util.h" |
| #include "components/safe_browsing/common/safe_browsing_prefs.h" |
| #include "components/safe_browsing/db/v4_protocol_manager_util.h" |
| #include "components/safe_browsing/proto/csd.pb.h" |
| #include "components/sessions/core/session_id.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "third_party/protobuf/src/google/protobuf/repeated_field.h" |
| |
| namespace content { |
| class WebContents; |
| class NavigationHandle; |
| } |
| |
| namespace history { |
| class HistoryService; |
| } |
| |
| namespace policy { |
| class BrowserPolicyConnector; |
| } |
| |
| class GURL; |
| class HostContentSettingsMap; |
| |
| namespace safe_browsing { |
| |
| class PasswordProtectionNavigationThrottle; |
| class PasswordProtectionRequest; |
| class SafeBrowsingDatabaseManager; |
| |
| // UMA metrics |
| extern const char kPasswordOnFocusRequestOutcomeHistogram[]; |
| extern const char kAnyPasswordEntryRequestOutcomeHistogram[]; |
| extern const char kSyncPasswordEntryRequestOutcomeHistogram[]; |
| extern const char kProtectedPasswordEntryRequestOutcomeHistogram[]; |
| extern const char kSyncPasswordWarningDialogHistogram[]; |
| extern const char kSyncPasswordPageInfoHistogram[]; |
| extern const char kSyncPasswordChromeSettingsHistogram[]; |
| extern const char kSyncPasswordInterstitialHistogram[]; |
| extern const char kEnterprisePasswordEntryRequestOutcomeHistogram[]; |
| extern const char kEnterprisePasswordWarningDialogHistogram[]; |
| extern const char kEnterprisePasswordPageInfoHistogram[]; |
| extern const char kEnterprisePasswordInterstitialHistogram[]; |
| extern const char kGSuiteSyncPasswordEntryRequestOutcomeHistogram[]; |
| extern const char kGSuiteSyncPasswordWarningDialogHistogram[]; |
| extern const char kGSuiteSyncPasswordPageInfoHistogram[]; |
| extern const char kGSuiteSyncPasswordInterstitialHistogram[]; |
| extern const char kInterstitialActionByUserNavigationHistogram[]; |
| ; |
| |
| using ReusedPasswordType = |
| LoginReputationClientRequest::PasswordReuseEvent::ReusedPasswordType; |
| |
| // Manage password protection pings and verdicts. There is one instance of this |
| // class per profile. Therefore, every PasswordProtectionService instance is |
| // associated with a unique HistoryService instance and a unique |
| // HostContentSettingsMap instance. |
| class PasswordProtectionService : public history::HistoryServiceObserver { |
| public: |
| // The outcome of the request. These values are used for UMA. |
| // DO NOT CHANGE THE ORDERING OF THESE VALUES. |
| enum RequestOutcome { |
| UNKNOWN = 0, |
| SUCCEEDED = 1, |
| CANCELED = 2, |
| TIMEDOUT = 3, |
| MATCHED_WHITELIST = 4, |
| RESPONSE_ALREADY_CACHED = 5, |
| DEPRECATED_NO_EXTENDED_REPORTING = 6, |
| DISABLED_DUE_TO_INCOGNITO = 7, |
| REQUEST_MALFORMED = 8, |
| FETCH_FAILED = 9, |
| RESPONSE_MALFORMED = 10, |
| SERVICE_DESTROYED = 11, |
| DISABLED_DUE_TO_FEATURE_DISABLED = 12, |
| DISABLED_DUE_TO_USER_POPULATION = 13, |
| URL_NOT_VALID_FOR_REPUTATION_COMPUTING = 14, |
| MATCHED_ENTERPRISE_WHITELIST = 15, |
| MATCHED_ENTERPRISE_CHANGE_PASSWORD_URL = 16, |
| MATCHED_ENTERPRISE_LOGIN_URL = 17, |
| // No request is ever sent if the admin configures password protection to |
| // warn on ALL password reuses (rather than just phishing sites). |
| PASSWORD_ALERT_MODE = 18, |
| // No request is event sent if the admin turns off password protection. |
| TURNED_OFF_BY_ADMIN = 19, |
| MAX_OUTCOME |
| }; |
| |
| // Enum values indicates if a password protection warning is shown or |
| // represents user's action on warnings. These values are used for UMA. |
| // DO NOT CHANGE THE ORDERING OF THESE VALUES. |
| enum WarningAction { |
| // Warning shows up. |
| SHOWN = 0, |
| |
| // User clicks on "Change Password" button. |
| CHANGE_PASSWORD = 1, |
| |
| // User clicks on "Ignore" button. |
| IGNORE_WARNING = 2, |
| |
| // Dialog closed in reaction to change of user state. |
| CLOSE = 3, |
| |
| // User explicitly mark the site as legitimate. |
| MARK_AS_LEGITIMATE = 4, |
| |
| MAX_ACTION |
| }; |
| |
| // Type of password protection warning UI. |
| enum WarningUIType { |
| NOT_USED = 0, |
| PAGE_INFO = 1, |
| MODAL_DIALOG = 2, |
| CHROME_SETTINGS = 3, |
| INTERSTITIAL = 4 |
| }; |
| |
| PasswordProtectionService( |
| const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| history::HistoryService* history_service, |
| HostContentSettingsMap* host_content_settings_map); |
| |
| ~PasswordProtectionService() override; |
| |
| base::WeakPtr<PasswordProtectionService> GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| // Looks up |settings| to find the cached verdict response. If verdict is not |
| // available or is expired, return VERDICT_TYPE_UNSPECIFIED. Can be called on |
| // any thread. |
| LoginReputationClientResponse::VerdictType GetCachedVerdict( |
| const GURL& url, |
| LoginReputationClientRequest::TriggerType trigger_type, |
| ReusedPasswordType password_type, |
| LoginReputationClientResponse* out_response); |
| |
| // Stores |verdict| in |settings| based on its |trigger_type|, |url|, |
| // reused |password_type|, |verdict| and |receive_time|. |
| virtual void CacheVerdict( |
| const GURL& url, |
| LoginReputationClientRequest::TriggerType trigger_type, |
| ReusedPasswordType password_type, |
| LoginReputationClientResponse* verdict, |
| const base::Time& receive_time); |
| |
| // Migrates cached password reuse verdicts such that verdicts of different |
| // reused password type are cached separately. |
| void MigrateCachedVerdicts(); |
| |
| // Removes all the expired verdicts from cache. |
| void CleanUpExpiredVerdicts(); |
| |
| // Creates an instance of PasswordProtectionRequest and call Start() on that |
| // instance. This function also insert this request object in |requests_| for |
| // record keeping. |
| void StartRequest(content::WebContents* web_contents, |
| const GURL& main_frame_url, |
| const GURL& password_form_action, |
| const GURL& password_form_frame_url, |
| ReusedPasswordType reused_password_type, |
| const std::vector<std::string>& matching_domains, |
| LoginReputationClientRequest::TriggerType trigger_type, |
| bool password_field_exists); |
| |
| virtual void MaybeStartPasswordFieldOnFocusRequest( |
| content::WebContents* web_contents, |
| const GURL& main_frame_url, |
| const GURL& password_form_action, |
| const GURL& password_form_frame_url); |
| |
| virtual void MaybeStartProtectedPasswordEntryRequest( |
| content::WebContents* web_contents, |
| const GURL& main_frame_url, |
| ReusedPasswordType reused_password_type, |
| const std::vector<std::string>& matching_domains, |
| bool password_field_exists); |
| |
| // Records a Chrome Sync event that sync password reuse was detected. |
| virtual void MaybeLogPasswordReuseDetectedEvent( |
| content::WebContents* web_contents) = 0; |
| |
| scoped_refptr<SafeBrowsingDatabaseManager> database_manager(); |
| |
| // Safe Browsing backend cannot get a reliable reputation of a URL if |
| // (1) URL is not valid |
| // (2) URL doesn't have http or https scheme |
| // (3) It maps to a local host. |
| // (4) Its hostname is an IP Address in an IANA-reserved range. |
| // (5) Its hostname is a not-yet-assigned by ICANN gTLD. |
| // (6) Its hostname is a dotless domain. |
| static bool CanGetReputationOfURL(const GURL& url); |
| |
| // Records user action on warnings to corresponding UMA histograms. |
| void RecordWarningAction(WarningUIType ui_type, |
| WarningAction action, |
| ReusedPasswordType password_type); |
| |
| // If we want to show password reuse modal warning. |
| bool ShouldShowModalWarning( |
| LoginReputationClientRequest::TriggerType trigger_type, |
| ReusedPasswordType reused_password_type, |
| LoginReputationClientResponse::VerdictType verdict_type); |
| |
| // Shows modal warning dialog on the current |web_contents| and pass the |
| // |verdict_token| to callback of this dialog. |
| virtual void ShowModalWarning(content::WebContents* web_contents, |
| const std::string& verdict_token, |
| ReusedPasswordType reused_password_type) = 0; |
| |
| // Shows chrome://reset-password interstitial. |
| virtual void ShowInterstitial(content::WebContents* web_contens, |
| ReusedPasswordType password_type) = 0; |
| |
| virtual void UpdateSecurityState(safe_browsing::SBThreatType threat_type, |
| ReusedPasswordType password_type, |
| content::WebContents* web_contents) = 0; |
| |
| // Log the |reason| to several UMA metrics, depending on the value |
| // of |password_type|. |
| void LogPasswordEntryRequestOutcome(RequestOutcome reason, |
| ReusedPasswordType password_type); |
| |
| // If user has clicked through any Safe Browsing interstitial on this given |
| // |web_contents|. |
| virtual bool UserClickedThroughSBInterstitial( |
| content::WebContents* web_contents) = 0; |
| |
| // Called when a new navigation is starting. Create throttle if there is a |
| // pending sync password reuse ping or if there is a modal warning dialog |
| // showing in the corresponding web contents. |
| std::unique_ptr<PasswordProtectionNavigationThrottle> |
| MaybeCreateNavigationThrottle(content::NavigationHandle* navigation_handle); |
| |
| // Returns if the warning UI is enabled. |
| bool IsWarningEnabled(); |
| |
| // Returns if the event logging is enabled. |
| bool IsEventLoggingEnabled(); |
| |
| // Returns the pref value of password protection warning trigger. |
| virtual PasswordProtectionTrigger GetPasswordProtectionWarningTriggerPref() |
| const = 0; |
| |
| // If |url| matches Safe Browsing whitelist domains, password protection |
| // change password URL, or password protection login URLs in the enterprise |
| // policy. |
| virtual bool IsURLWhitelistedForPasswordEntry( |
| const GURL& url, |
| RequestOutcome* reason) const = 0; |
| |
| // Called when password reuse warning or phishing reuse warning is shown. |
| // Must be called on UI thread. |
| virtual void OnPolicySpecifiedPasswordReuseDetected(const GURL& url, |
| bool is_phishing_url) = 0; |
| |
| // Called when a protected password change is detected. Must be called on |
| // UI thread. |
| virtual void OnPolicySpecifiedPasswordChanged() = 0; |
| |
| // Converts from password::metrics_util::PasswordType to |
| // LoginReputationClientRequest::PasswordReuseEvent::ReusedPasswordType. |
| static ReusedPasswordType GetPasswordProtectionReusedPasswordType( |
| password_manager::metrics_util::PasswordType password_type); |
| |
| // If we can send ping for this type of reused password. |
| bool IsSupportedPasswordTypeForPinging( |
| ReusedPasswordType reused_password_type) const; |
| |
| // If we can show modal warning for this type of reused password. |
| bool IsSupportedPasswordTypeForModalWarning( |
| ReusedPasswordType reused_password_type) const; |
| |
| protected: |
| friend class PasswordProtectionRequest; |
| |
| // Chrome can send password protection ping if it is allowed by for the |
| // |trigger_type| and if Safe Browsing can compute reputation of |
| // |main_frame_url| (e.g. Safe Browsing is not able to compute reputation of a |
| // private IP or a local host). Update |reason| if sending ping is not |
| // allowed. |password_type| is used for UMA metric recording. |
| bool CanSendPing(LoginReputationClientRequest::TriggerType trigger_type, |
| const GURL& main_frame_url, |
| ReusedPasswordType password_type, |
| RequestOutcome* reason); |
| |
| // Called by a PasswordProtectionRequest instance when it finishes to remove |
| // itself from |requests_|. |
| virtual void RequestFinished( |
| PasswordProtectionRequest* request, |
| bool already_cached, |
| std::unique_ptr<LoginReputationClientResponse> response); |
| |
| // Cancels all requests in |requests_|, empties it, and releases references to |
| // the requests. |
| void CancelPendingRequests(); |
| |
| // Gets the total number of verdicts of the specified |trigger_type| we cached |
| // for this profile. This counts both expired and active verdicts. |
| virtual int GetStoredVerdictCount( |
| LoginReputationClientRequest::TriggerType trigger_type); |
| |
| // Gets an unowned |BrowserPolicyConnector| for the current platform. |
| virtual const policy::BrowserPolicyConnector* GetBrowserPolicyConnector() |
| const = 0; |
| |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory() { |
| return url_loader_factory_; |
| } |
| |
| // Returns the URL where PasswordProtectionRequest instances send requests. |
| static GURL GetPasswordProtectionRequestUrl(); |
| |
| // Gets the request timeout in milliseconds. |
| static int GetRequestTimeoutInMS(); |
| |
| // Obtains referrer chain of |event_url| and |event_tab_id| and adds this |
| // info into |frame|. |
| virtual void FillReferrerChain( |
| const GURL& event_url, |
| SessionID |
| event_tab_id, // SessionID::InvalidValue() if tab not available. |
| LoginReputationClientRequest::Frame* frame) = 0; |
| |
| void FillUserPopulation( |
| LoginReputationClientRequest::TriggerType trigger_type, |
| LoginReputationClientRequest* request_proto); |
| |
| virtual bool IsExtendedReporting() = 0; |
| |
| virtual bool IsIncognito() = 0; |
| |
| virtual bool IsPingingEnabled( |
| LoginReputationClientRequest::TriggerType trigger_type, |
| RequestOutcome* reason) = 0; |
| |
| virtual bool IsHistorySyncEnabled() = 0; |
| |
| // Gets the type of sync account associated with current profile or |
| // |NOT_SIGNED_IN|. |
| virtual LoginReputationClientRequest::PasswordReuseEvent::SyncAccountType |
| GetSyncAccountType() const = 0; |
| |
| // Records a Chrome Sync event for the result of the URL reputation lookup |
| // if the user enters their sync password on a website. |
| virtual void MaybeLogPasswordReuseLookupEvent( |
| content::WebContents* web_contents, |
| PasswordProtectionService::RequestOutcome, |
| const LoginReputationClientResponse*) = 0; |
| |
| void CheckCsdWhitelistOnIOThread(const GURL& url, bool* check_result); |
| |
| HostContentSettingsMap* content_settings() const { return content_settings_; } |
| |
| void RemoveWarningRequestsByWebContents(content::WebContents* web_contents); |
| |
| bool IsModalWarningShowingInWebContents(content::WebContents* web_contents); |
| |
| virtual bool CanShowInterstitial(RequestOutcome reason, |
| ReusedPasswordType password_type, |
| const GURL& main_frame_url) = 0; |
| |
| void LogPasswordAlertModeOutcome(RequestOutcome reason, |
| ReusedPasswordType password_type); |
| |
| private: |
| friend class PasswordProtectionServiceTest; |
| friend class TestPasswordProtectionService; |
| friend class ChromePasswordProtectionServiceTest; |
| friend class ChromePasswordProtectionServiceBrowserTest; |
| FRIEND_TEST_ALL_PREFIXES(PasswordProtectionServiceTest, |
| TestParseInvalidVerdictEntry); |
| FRIEND_TEST_ALL_PREFIXES(PasswordProtectionServiceTest, |
| TestParseValidVerdictEntry); |
| FRIEND_TEST_ALL_PREFIXES(PasswordProtectionServiceTest, |
| TestPathVariantsMatchCacheExpression); |
| FRIEND_TEST_ALL_PREFIXES(PasswordProtectionServiceTest, |
| TestRemoveCachedVerdictOnURLsDeleted); |
| FRIEND_TEST_ALL_PREFIXES(PasswordProtectionServiceTest, |
| TestCleanUpExpiredVerdict); |
| |
| // Overridden from history::HistoryServiceObserver. |
| void OnURLsDeleted(history::HistoryService* history_service, |
| const history::DeletionInfo& deletion_info) override; |
| |
| void HistoryServiceBeingDeleted( |
| history::HistoryService* history_service) override; |
| |
| // Posted to UI thread by OnURLsDeleted(..). This function cleans up password |
| // protection content settings related to deleted URLs. |
| void RemoveContentSettingsOnURLsDeleted(bool all_history, |
| const history::URLRows& deleted_rows); |
| |
| // Posted to UI thread by OnURLsDeleted(...). This function remove the related |
| // entries in kSafeBrowsingUnhandledSyncPasswordReuses. |
| virtual void RemoveUnhandledSyncPasswordReuseOnURLsDeleted( |
| bool all_history, |
| const history::URLRows& deleted_rows) = 0; |
| |
| // Helper function called by RemoveContentSettingsOnURLsDeleted(..). It |
| // calculate the number of verdicts of |type| that associate with |url|. |
| int GetVerdictCountForURL(const GURL& url, |
| LoginReputationClientRequest::TriggerType type); |
| |
| // Remove verdict of |type| from |cache_dictionary|. Return false if no |
| // verdict removed, true otherwise. |
| bool RemoveExpiredVerdicts(LoginReputationClientRequest::TriggerType type, |
| base::DictionaryValue* cache_dictionary); |
| |
| // Helper function called by RemoveExpiredVerdicts(..). Returns the number of |
| // expired entries removed. |
| size_t RemoveExpiredEntries(base::Value* verdict_dictionary); |
| |
| static bool ParseVerdictEntry(base::Value* verdict_entry, |
| int* out_verdict_received_time, |
| LoginReputationClientResponse* out_verdict); |
| |
| static bool PathVariantsMatchCacheExpression( |
| const std::vector<std::string>& generated_paths, |
| const std::string& cache_expression_path); |
| |
| static bool IsCacheExpired(int cache_creation_time, int cache_duration); |
| |
| static void GeneratePathVariantsWithoutQuery(const GURL& url, |
| std::vector<std::string>* paths); |
| |
| static std::string GetCacheExpressionPath( |
| const std::string& cache_expression); |
| |
| static std::unique_ptr<base::DictionaryValue> CreateDictionaryFromVerdict( |
| const LoginReputationClientResponse* verdict, |
| const base::Time& receive_time); |
| |
| void RecordNoPingingReason( |
| LoginReputationClientRequest::TriggerType trigger_type, |
| RequestOutcome reason, |
| ReusedPasswordType password_type); |
| |
| // Number of verdict stored for this profile for password on focus pings. |
| int stored_verdict_count_password_on_focus_; |
| |
| // Number of verdict stored for this profile for protected password entry |
| // pings. |
| int stored_verdict_count_password_entry_; |
| |
| scoped_refptr<SafeBrowsingDatabaseManager> database_manager_; |
| |
| // The context we use to issue network requests. This request_context_getter |
| // is obtained from SafeBrowsingService so that we can use the Safe Browsing |
| // cookie store. |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_; |
| |
| // Set of pending PasswordProtectionRequests that are still waiting for |
| // verdict. |
| std::set<scoped_refptr<PasswordProtectionRequest>> pending_requests_; |
| |
| // Set of PasswordProtectionRequests that are triggering modal warnings. |
| std::set<scoped_refptr<PasswordProtectionRequest>> warning_requests_; |
| |
| ScopedObserver<history::HistoryService, history::HistoryServiceObserver> |
| history_service_observer_; |
| |
| // Content settings map associated with this instance. |
| HostContentSettingsMap* content_settings_; |
| |
| // Weakptr can only cancel task if it is posted to the same thread. Therefore, |
| // we need CancelableTaskTracker to cancel tasks posted to IO thread. |
| base::CancelableTaskTracker tracker_; |
| |
| base::WeakPtrFactory<PasswordProtectionService> weak_factory_; |
| DISALLOW_COPY_AND_ASSIGN(PasswordProtectionService); |
| }; |
| |
| } // namespace safe_browsing |
| |
| #endif // COMPONENTS_SAFE_BROWSING_PASSWORD_PROTECTION_PASSWORD_PROTECTION_SERVICE_H_ |