1 // Copyright 2015 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.
5 #include "components/password_manager/core/browser/affiliation_backend.h"
10 #include "base/bind.h"
11 #include "base/location.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/threading/thread_checker.h"
15 #include "base/time/clock.h"
16 #include "base/time/tick_clock.h"
17 #include "base/time/time.h"
18 #include "components/password_manager/core/browser/affiliation_database.h"
19 #include "components/password_manager/core/browser/affiliation_fetch_throttler.h"
20 #include "components/password_manager/core/browser/affiliation_fetcher.h"
21 #include "components/password_manager/core/browser/facet_manager.h"
22 #include "net/url_request/url_request_context_getter.h"
24 namespace password_manager
{
26 AffiliationBackend::AffiliationBackend(
27 const scoped_refptr
<net::URLRequestContextGetter
>& request_context_getter
,
28 const scoped_refptr
<base::SingleThreadTaskRunner
>& task_runner
,
29 scoped_ptr
<base::Clock
> time_source
,
30 scoped_ptr
<base::TickClock
> time_tick_source
)
31 : request_context_getter_(request_context_getter
),
32 task_runner_(task_runner
),
33 clock_(time_source
.Pass()),
34 tick_clock_(time_tick_source
.Pass()),
35 construction_time_(clock_
->Now()),
36 weak_ptr_factory_(this) {
37 DCHECK_LT(base::Time(), clock_
->Now());
40 AffiliationBackend::~AffiliationBackend() {
43 void AffiliationBackend::Initialize(const base::FilePath
& db_path
) {
44 thread_checker_
.reset(new base::ThreadChecker
);
48 new AffiliationFetchThrottler(this, task_runner_
, tick_clock_
.get()));
50 // TODO(engedy): Currently, when Init() returns false, it always poisons the
51 // DB, so subsequent operations will silently fail. Consider either fully
52 // committing to this approach and making Init() a void, or handling the
53 // return value here. See: https://crbug.com/478831.
54 cache_
.reset(new AffiliationDatabase());
55 cache_
->Init(db_path
);
58 void AffiliationBackend::GetAffiliations(
59 const FacetURI
& facet_uri
,
60 StrategyOnCacheMiss cache_miss_strategy
,
61 const AffiliationService::ResultCallback
& callback
,
62 const scoped_refptr
<base::TaskRunner
>& callback_task_runner
) {
63 DCHECK(thread_checker_
&& thread_checker_
->CalledOnValidThread());
65 FacetManager
* facet_manager
= GetOrCreateFacetManager(facet_uri
);
66 DCHECK(facet_manager
);
67 facet_manager
->GetAffiliations(cache_miss_strategy
, callback
,
68 callback_task_runner
);
70 if (facet_manager
->CanBeDiscarded())
71 facet_managers_
.erase(facet_uri
);
74 void AffiliationBackend::Prefetch(const FacetURI
& facet_uri
,
75 const base::Time
& keep_fresh_until
) {
76 DCHECK(thread_checker_
&& thread_checker_
->CalledOnValidThread());
78 FacetManager
* facet_manager
= GetOrCreateFacetManager(facet_uri
);
79 DCHECK(facet_manager
);
80 facet_manager
->Prefetch(keep_fresh_until
);
82 if (facet_manager
->CanBeDiscarded())
83 facet_managers_
.erase(facet_uri
);
86 void AffiliationBackend::CancelPrefetch(const FacetURI
& facet_uri
,
87 const base::Time
& keep_fresh_until
) {
88 DCHECK(thread_checker_
&& thread_checker_
->CalledOnValidThread());
90 FacetManager
* facet_manager
= facet_managers_
.get(facet_uri
);
93 facet_manager
->CancelPrefetch(keep_fresh_until
);
95 if (facet_manager
->CanBeDiscarded())
96 facet_managers_
.erase(facet_uri
);
99 void AffiliationBackend::TrimCache() {
100 DCHECK(thread_checker_
&& thread_checker_
->CalledOnValidThread());
102 std::vector
<AffiliatedFacetsWithUpdateTime
> all_affiliations
;
103 cache_
->GetAllAffiliations(&all_affiliations
);
104 for (const auto& affiliation
: all_affiliations
)
105 DiscardCachedDataIfNoLongerNeeded(affiliation
.facets
);
108 void AffiliationBackend::TrimCacheForFacet(const FacetURI
& facet_uri
) {
109 DCHECK(thread_checker_
&& thread_checker_
->CalledOnValidThread());
111 AffiliatedFacetsWithUpdateTime affiliation
;
112 if (cache_
->GetAffiliationsForFacet(facet_uri
, &affiliation
))
113 DiscardCachedDataIfNoLongerNeeded(affiliation
.facets
);
117 void AffiliationBackend::DeleteCache(const base::FilePath
& db_path
) {
118 AffiliationDatabase::Delete(db_path
);
121 FacetManager
* AffiliationBackend::GetOrCreateFacetManager(
122 const FacetURI
& facet_uri
) {
123 if (!facet_managers_
.contains(facet_uri
)) {
124 scoped_ptr
<FacetManager
> new_manager(
125 new FacetManager(facet_uri
, this, clock_
.get()));
126 facet_managers_
.add(facet_uri
, new_manager
.Pass());
128 return facet_managers_
.get(facet_uri
);
131 void AffiliationBackend::DiscardCachedDataIfNoLongerNeeded(
132 const AffiliatedFacets
& affiliated_facets
) {
133 DCHECK(thread_checker_
&& thread_checker_
->CalledOnValidThread());
135 // Discard the equivalence class if there is no facet in the class whose
136 // FacetManager claims that it needs to keep the data.
137 for (const auto& facet_uri
: affiliated_facets
) {
138 FacetManager
* facet_manager
= facet_managers_
.get(facet_uri
);
139 if (facet_manager
&& !facet_manager
->CanCachedDataBeDiscarded())
143 CHECK(!affiliated_facets
.empty());
144 cache_
->DeleteAffiliationsForFacet(affiliated_facets
[0]);
147 void AffiliationBackend::OnSendNotification(const FacetURI
& facet_uri
) {
148 DCHECK(thread_checker_
&& thread_checker_
->CalledOnValidThread());
150 FacetManager
* facet_manager
= facet_managers_
.get(facet_uri
);
153 facet_manager
->NotifyAtRequestedTime();
155 if (facet_manager
->CanBeDiscarded())
156 facet_managers_
.erase(facet_uri
);
159 bool AffiliationBackend::ReadAffiliationsFromDatabase(
160 const FacetURI
& facet_uri
,
161 AffiliatedFacetsWithUpdateTime
* affiliations
) {
162 return cache_
->GetAffiliationsForFacet(facet_uri
, affiliations
);
165 void AffiliationBackend::SignalNeedNetworkRequest() {
166 throttler_
->SignalNetworkRequestNeeded();
169 void AffiliationBackend::RequestNotificationAtTime(const FacetURI
& facet_uri
,
171 // TODO(engedy): Avoid spamming the task runner; only ever schedule the first
172 // callback. crbug.com/437865.
173 task_runner_
->PostDelayedTask(
174 FROM_HERE
, base::Bind(&AffiliationBackend::OnSendNotification
,
175 weak_ptr_factory_
.GetWeakPtr(), facet_uri
),
176 time
- clock_
->Now());
179 void AffiliationBackend::OnFetchSucceeded(
180 scoped_ptr
<AffiliationFetcherDelegate::Result
> result
) {
181 DCHECK(thread_checker_
&& thread_checker_
->CalledOnValidThread());
184 throttler_
->InformOfNetworkRequestComplete(true);
186 for (const AffiliatedFacets
& affiliated_facets
: *result
) {
187 AffiliatedFacetsWithUpdateTime affiliation
;
188 affiliation
.facets
= affiliated_facets
;
189 affiliation
.last_update_time
= clock_
->Now();
191 std::vector
<AffiliatedFacetsWithUpdateTime
> obsoleted_affiliations
;
192 cache_
->StoreAndRemoveConflicting(affiliation
, &obsoleted_affiliations
);
194 // Cached data in contradiction with newly stored data automatically gets
195 // removed from the DB, and will be stored into |obsoleted_affiliations|.
196 // TODO(engedy): Currently, handling this is entirely meaningless unless in
197 // the edge case when the user has credentials for two Android applications
198 // which are now being de-associated. But even in that case, nothing will
199 // explode and the only symptom will be that credentials for the Android
200 // application that is not being fetched right now, if any, will not be
201 // filled into affiliated applications until the next fetch. Still, this
202 // should be implemented at some point by letting facet managers know if
203 // data. See: https://crbug.com/478832.
205 for (const auto& facet_uri
: affiliated_facets
) {
206 if (!facet_managers_
.contains(facet_uri
))
208 FacetManager
* facet_manager
= facet_managers_
.get(facet_uri
);
209 facet_manager
->OnFetchSucceeded(affiliation
);
210 if (facet_manager
->CanBeDiscarded())
211 facet_managers_
.erase(facet_uri
);
215 // A subsequent fetch may be needed if any additional GetAffiliations()
216 // requests came in while the current fetch was in flight.
217 for (const auto& facet_manager_pair
: facet_managers_
) {
218 if (facet_manager_pair
.second
->DoesRequireFetch()) {
219 throttler_
->SignalNetworkRequestNeeded();
225 void AffiliationBackend::OnFetchFailed() {
226 DCHECK(thread_checker_
&& thread_checker_
->CalledOnValidThread());
229 throttler_
->InformOfNetworkRequestComplete(false);
231 // Trigger a retry if a fetch is still needed.
232 for (const auto& facet_manager_pair
: facet_managers_
) {
233 if (facet_manager_pair
.second
->DoesRequireFetch()) {
234 throttler_
->SignalNetworkRequestNeeded();
240 void AffiliationBackend::OnMalformedResponse() {
241 DCHECK(thread_checker_
&& thread_checker_
->CalledOnValidThread());
243 // TODO(engedy): Potentially handle this case differently. crbug.com/437865.
247 bool AffiliationBackend::OnCanSendNetworkRequest() {
249 std::vector
<FacetURI
> requested_facet_uris
;
250 for (const auto& facet_manager_pair
: facet_managers_
) {
251 if (facet_manager_pair
.second
->DoesRequireFetch())
252 requested_facet_uris
.push_back(facet_manager_pair
.first
);
255 // In case a request is no longer needed, return false to indicate this.
256 if (requested_facet_uris
.empty())
259 fetcher_
.reset(AffiliationFetcher::Create(request_context_getter_
.get(),
260 requested_facet_uris
, this));
261 fetcher_
->StartRequest();
262 ReportStatistics(requested_facet_uris
.size());
266 void AffiliationBackend::ReportStatistics(size_t requested_facet_uri_count
) {
267 UMA_HISTOGRAM_COUNTS_100("PasswordManager.AffiliationBackend.FetchSize",
268 requested_facet_uri_count
);
270 if (last_request_time_
.is_null()) {
271 base::TimeDelta delay
= clock_
->Now() - construction_time_
;
272 UMA_HISTOGRAM_CUSTOM_TIMES(
273 "PasswordManager.AffiliationBackend.FirstFetchDelay", delay
,
274 base::TimeDelta::FromSeconds(1), base::TimeDelta::FromDays(3), 50);
276 base::TimeDelta delay
= clock_
->Now() - last_request_time_
;
277 UMA_HISTOGRAM_CUSTOM_TIMES(
278 "PasswordManager.AffiliationBackend.SubsequentFetchDelay", delay
,
279 base::TimeDelta::FromSeconds(1), base::TimeDelta::FromDays(3), 50);
281 last_request_time_
= clock_
->Now();
284 void AffiliationBackend::SetThrottlerForTesting(
285 scoped_ptr
<AffiliationFetchThrottler
> throttler
) {
286 throttler_
= throttler
.Pass();
289 } // namespace password_manager