blob: ef81808c08de502bb79b19800b1a87ec146a400e [file] [log] [blame]
// 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_