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.
4 
5 #include "components/password_manager/core/browser/android_affiliation/affiliated_match_helper.h"
6 
7 #include <algorithm>
8 #include <utility>
9 
10 #include "base/barrier_closure.h"
11 #include "base/bind.h"
12 #include "base/callback.h"
13 #include "base/threading/sequenced_task_runner_handle.h"
14 #include "components/password_manager/core/browser/android_affiliation/android_affiliation_service.h"
15 #include "components/password_manager/core/browser/password_form.h"
16 
17 namespace password_manager {
18 
19 namespace {
20 
21 // Returns whether or not |form| represents a credential for an Android
22 // application, and if so, returns the |facet_uri| of that application.
IsAndroidApplicationCredential(const PasswordForm & form,FacetURI * facet_uri)23 bool IsAndroidApplicationCredential(const PasswordForm& form,
24                                     FacetURI* facet_uri) {
25   DCHECK(facet_uri);
26   if (form.scheme != PasswordForm::Scheme::kHtml)
27     return false;
28 
29   *facet_uri = FacetURI::FromPotentiallyInvalidSpec(form.signon_realm);
30   return facet_uri->IsValidAndroidFacetURI();
31 }
32 
33 }  // namespace
34 
35 // static
36 constexpr base::TimeDelta AffiliatedMatchHelper::kInitializationDelayOnStartup;
37 
AffiliatedMatchHelper(PasswordStore * password_store,std::unique_ptr<AndroidAffiliationService> affiliation_service)38 AffiliatedMatchHelper::AffiliatedMatchHelper(
39     PasswordStore* password_store,
40     std::unique_ptr<AndroidAffiliationService> affiliation_service)
41     : password_store_(password_store),
42       affiliation_service_(std::move(affiliation_service)) {}
43 
~AffiliatedMatchHelper()44 AffiliatedMatchHelper::~AffiliatedMatchHelper() {
45   if (password_store_)
46     password_store_->RemoveObserver(this);
47 }
48 
Initialize()49 void AffiliatedMatchHelper::Initialize() {
50   DCHECK(password_store_);
51   DCHECK(affiliation_service_);
52   base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
53       FROM_HERE,
54       base::BindOnce(&AffiliatedMatchHelper::DoDeferredInitialization,
55                      weak_ptr_factory_.GetWeakPtr()),
56       kInitializationDelayOnStartup);
57 }
58 
GetAffiliatedAndroidRealms(const PasswordStore::FormDigest & observed_form,AffiliatedRealmsCallback result_callback)59 void AffiliatedMatchHelper::GetAffiliatedAndroidRealms(
60     const PasswordStore::FormDigest& observed_form,
61     AffiliatedRealmsCallback result_callback) {
62   if (IsValidWebCredential(observed_form)) {
63     FacetURI facet_uri(
64         FacetURI::FromPotentiallyInvalidSpec(observed_form.signon_realm));
65     affiliation_service_->GetAffiliationsAndBranding(
66         facet_uri, AndroidAffiliationService::StrategyOnCacheMiss::FAIL,
67         base::BindOnce(
68             &AffiliatedMatchHelper::CompleteGetAffiliatedAndroidRealms,
69             weak_ptr_factory_.GetWeakPtr(), facet_uri,
70             std::move(result_callback)));
71   } else {
72     std::move(result_callback).Run(std::vector<std::string>());
73   }
74 }
75 
GetAffiliatedWebRealms(const PasswordStore::FormDigest & android_form,AffiliatedRealmsCallback result_callback)76 void AffiliatedMatchHelper::GetAffiliatedWebRealms(
77     const PasswordStore::FormDigest& android_form,
78     AffiliatedRealmsCallback result_callback) {
79   if (IsValidAndroidCredential(android_form)) {
80     affiliation_service_->GetAffiliationsAndBranding(
81         FacetURI::FromPotentiallyInvalidSpec(android_form.signon_realm),
82         AndroidAffiliationService::StrategyOnCacheMiss::FETCH_OVER_NETWORK,
83         base::BindOnce(&AffiliatedMatchHelper::CompleteGetAffiliatedWebRealms,
84                        weak_ptr_factory_.GetWeakPtr(),
85                        std::move(result_callback)));
86   } else {
87     std::move(result_callback).Run(std::vector<std::string>());
88   }
89 }
90 
InjectAffiliationAndBrandingInformation(std::vector<std::unique_ptr<PasswordForm>> forms,AndroidAffiliationService::StrategyOnCacheMiss strategy_on_cache_miss,PasswordFormsCallback result_callback)91 void AffiliatedMatchHelper::InjectAffiliationAndBrandingInformation(
92     std::vector<std::unique_ptr<PasswordForm>> forms,
93     AndroidAffiliationService::StrategyOnCacheMiss strategy_on_cache_miss,
94     PasswordFormsCallback result_callback) {
95   std::vector<PasswordForm*> android_credentials;
96   for (const auto& form : forms) {
97     if (IsValidAndroidCredential(PasswordStore::FormDigest(*form)))
98       android_credentials.push_back(form.get());
99   }
100   base::OnceClosure on_get_all_realms(
101       base::BindOnce(std::move(result_callback), std::move(forms)));
102   base::RepeatingClosure barrier_closure = base::BarrierClosure(
103       android_credentials.size(), std::move(on_get_all_realms));
104   for (auto* form : android_credentials) {
105     affiliation_service_->GetAffiliationsAndBranding(
106         FacetURI::FromPotentiallyInvalidSpec(form->signon_realm),
107         strategy_on_cache_miss,
108         base::BindOnce(&AffiliatedMatchHelper::
109                            CompleteInjectAffiliationAndBrandingInformation,
110                        weak_ptr_factory_.GetWeakPtr(), base::Unretained(form),
111                        barrier_closure));
112   }
113 }
114 
CompleteInjectAffiliationAndBrandingInformation(PasswordForm * form,base::OnceClosure barrier_closure,const AffiliatedFacets & results,bool success)115 void AffiliatedMatchHelper::CompleteInjectAffiliationAndBrandingInformation(
116     PasswordForm* form,
117     base::OnceClosure barrier_closure,
118     const AffiliatedFacets& results,
119     bool success) {
120   if (!success) {
121     std::move(barrier_closure).Run();
122     return;
123   }
124 
125   const FacetURI facet_uri(
126       FacetURI::FromPotentiallyInvalidSpec(form->signon_realm));
127   DCHECK(facet_uri.IsValidAndroidFacetURI());
128 
129   // Inject branding information into the form (e.g. the Play Store name and
130   // icon URL). We expect to always find a matching facet URI in the results.
131   auto facet = std::find_if(results.begin(), results.end(),
132                             [&facet_uri](const Facet& affiliated_facet) {
133                               return affiliated_facet.uri == facet_uri;
134                             });
135   DCHECK(facet != results.end());
136   form->app_display_name = facet->branding_info.name;
137   form->app_icon_url = facet->branding_info.icon_url;
138 
139   // Inject the affiliated web realm into the form, if available. In case
140   // multiple web realms are available, this will always choose the first
141   // available web realm for injection.
142   auto affiliated_facet = std::find_if(
143       results.begin(), results.end(), [](const Facet& affiliated_facet) {
144         return affiliated_facet.uri.IsValidWebFacetURI();
145       });
146   if (affiliated_facet != results.end())
147     form->affiliated_web_realm = affiliated_facet->uri.canonical_spec() + "/";
148 
149   std::move(barrier_closure).Run();
150 }
151 
152 // static
IsValidAndroidCredential(const PasswordStore::FormDigest & form)153 bool AffiliatedMatchHelper::IsValidAndroidCredential(
154     const PasswordStore::FormDigest& form) {
155   return form.scheme == PasswordForm::Scheme::kHtml &&
156          IsValidAndroidFacetURI(form.signon_realm);
157 }
158 
159 // static
IsValidWebCredential(const PasswordStore::FormDigest & form)160 bool AffiliatedMatchHelper::IsValidWebCredential(
161     const PasswordStore::FormDigest& form) {
162   FacetURI facet_uri(FacetURI::FromPotentiallyInvalidSpec(form.signon_realm));
163   return form.scheme == PasswordForm::Scheme::kHtml &&
164          facet_uri.IsValidWebFacetURI();
165 }
166 
DoDeferredInitialization()167 void AffiliatedMatchHelper::DoDeferredInitialization() {
168   // Must start observing for changes at the same time as when the snapshot is
169   // taken to avoid inconsistencies due to any changes taking place in-between.
170   password_store_->AddObserver(this);
171   password_store_->GetAllLogins(this);
172 }
173 
CompleteGetAffiliatedAndroidRealms(const FacetURI & original_facet_uri,AffiliatedRealmsCallback result_callback,const AffiliatedFacets & results,bool success)174 void AffiliatedMatchHelper::CompleteGetAffiliatedAndroidRealms(
175     const FacetURI& original_facet_uri,
176     AffiliatedRealmsCallback result_callback,
177     const AffiliatedFacets& results,
178     bool success) {
179   std::vector<std::string> affiliated_realms;
180   if (success) {
181     for (const Facet& affiliated_facet : results) {
182       if (affiliated_facet.uri != original_facet_uri &&
183           affiliated_facet.uri.IsValidAndroidFacetURI())
184         // Facet URIs have no trailing slash, whereas realms do.
185         affiliated_realms.push_back(affiliated_facet.uri.canonical_spec() +
186                                     "/");
187     }
188   }
189   std::move(result_callback).Run(affiliated_realms);
190 }
191 
CompleteGetAffiliatedWebRealms(AffiliatedRealmsCallback result_callback,const AffiliatedFacets & results,bool success)192 void AffiliatedMatchHelper::CompleteGetAffiliatedWebRealms(
193     AffiliatedRealmsCallback result_callback,
194     const AffiliatedFacets& results,
195     bool success) {
196   std::vector<std::string> affiliated_realms;
197   if (success) {
198     for (const Facet& affiliated_facet : results) {
199       if (affiliated_facet.uri.IsValidWebFacetURI())
200         // Facet URIs have no trailing slash, whereas realms do.
201         affiliated_realms.push_back(affiliated_facet.uri.canonical_spec() +
202                                     "/");
203     }
204   }
205   std::move(result_callback).Run(affiliated_realms);
206 }
207 
OnLoginsChanged(const PasswordStoreChangeList & changes)208 void AffiliatedMatchHelper::OnLoginsChanged(
209     const PasswordStoreChangeList& changes) {
210   std::vector<FacetURI> facet_uris_to_trim;
211   for (const PasswordStoreChange& change : changes) {
212     FacetURI facet_uri;
213     if (!IsAndroidApplicationCredential(change.form(), &facet_uri))
214       continue;
215 
216     if (change.type() == PasswordStoreChange::ADD) {
217       affiliation_service_->Prefetch(facet_uri, base::Time::Max());
218     } else if (change.type() == PasswordStoreChange::REMOVE) {
219       // Stop keeping affiliation information fresh for deleted Android logins,
220       // and make a note to potentially remove any unneeded cached data later.
221       facet_uris_to_trim.push_back(facet_uri);
222       affiliation_service_->CancelPrefetch(facet_uri, base::Time::Max());
223     }
224   }
225 
226   // When the primary key for a login is updated, |changes| will contain both a
227   // REMOVE and ADD change for that login. Cached affiliation data should not be
228   // deleted in this case. A simple solution is to call TrimCacheForFacetURI()
229   // always after Prefetch() calls -- the trimming logic will detect that there
230   // is an active prefetch and not delete the corresponding data.
231   for (const FacetURI& facet_uri : facet_uris_to_trim)
232     affiliation_service_->TrimCacheForFacetURI(facet_uri);
233 }
234 
OnGetPasswordStoreResults(std::vector<std::unique_ptr<PasswordForm>> results)235 void AffiliatedMatchHelper::OnGetPasswordStoreResults(
236     std::vector<std::unique_ptr<PasswordForm>> results) {
237   for (const auto& form : results) {
238     FacetURI facet_uri;
239     if (IsAndroidApplicationCredential(*form, &facet_uri))
240       affiliation_service_->Prefetch(facet_uri, base::Time::Max());
241   }
242 }
243 
244 }  // namespace password_manager
245