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/autofill/core/common/password_form.h"
15 #include "components/password_manager/core/browser/android_affiliation/affiliation_service.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 autofill::PasswordForm & form,FacetURI * facet_uri)23 bool IsAndroidApplicationCredential(const autofill::PasswordForm& form,
24                                     FacetURI* facet_uri) {
25   DCHECK(facet_uri);
26   if (form.scheme != autofill::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<AffiliationService> affiliation_service)38 AffiliatedMatchHelper::AffiliatedMatchHelper(
39     PasswordStore* password_store,
40     std::unique_ptr<AffiliationService> 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, AffiliationService::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         AffiliationService::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<autofill::PasswordForm>> forms,PasswordFormsCallback result_callback)91 void AffiliatedMatchHelper::InjectAffiliationAndBrandingInformation(
92     std::vector<std::unique_ptr<autofill::PasswordForm>> forms,
93     PasswordFormsCallback result_callback) {
94   std::vector<autofill::PasswordForm*> android_credentials;
95   for (const auto& form : forms) {
96     if (IsValidAndroidCredential(PasswordStore::FormDigest(*form)))
97       android_credentials.push_back(form.get());
98   }
99   base::OnceClosure on_get_all_realms(
100       base::BindOnce(std::move(result_callback), std::move(forms)));
101   base::RepeatingClosure barrier_closure = base::BarrierClosure(
102       android_credentials.size(), std::move(on_get_all_realms));
103   for (auto* form : android_credentials) {
104     affiliation_service_->GetAffiliationsAndBranding(
105         FacetURI::FromPotentiallyInvalidSpec(form->signon_realm),
106         AffiliationService::StrategyOnCacheMiss::FAIL,
107         base::BindOnce(&AffiliatedMatchHelper::
108                            CompleteInjectAffiliationAndBrandingInformation,
109                        weak_ptr_factory_.GetWeakPtr(), base::Unretained(form),
110                        barrier_closure));
111   }
112 }
113 
CompleteInjectAffiliationAndBrandingInformation(autofill::PasswordForm * form,base::OnceClosure barrier_closure,const AffiliatedFacets & results,bool success)114 void AffiliatedMatchHelper::CompleteInjectAffiliationAndBrandingInformation(
115     autofill::PasswordForm* form,
116     base::OnceClosure barrier_closure,
117     const AffiliatedFacets& results,
118     bool success) {
119   if (!success) {
120     std::move(barrier_closure).Run();
121     return;
122   }
123 
124   const FacetURI facet_uri(
125       FacetURI::FromPotentiallyInvalidSpec(form->signon_realm));
126   DCHECK(facet_uri.IsValidAndroidFacetURI());
127 
128   // Inject branding information into the form (e.g. the Play Store name and
129   // icon URL). We expect to always find a matching facet URI in the results.
130   auto facet = std::find_if(results.begin(), results.end(),
131                             [&facet_uri](const Facet& affiliated_facet) {
132                               return affiliated_facet.uri == facet_uri;
133                             });
134   DCHECK(facet != results.end());
135   form->app_display_name = facet->branding_info.name;
136   form->app_icon_url = facet->branding_info.icon_url;
137 
138   // Inject the affiliated web realm into the form, if available. In case
139   // multiple web realms are available, this will always choose the first
140   // available web realm for injection.
141   auto affiliated_facet = std::find_if(
142       results.begin(), results.end(), [](const Facet& affiliated_facet) {
143         return affiliated_facet.uri.IsValidWebFacetURI();
144       });
145   if (affiliated_facet != results.end())
146     form->affiliated_web_realm = affiliated_facet->uri.canonical_spec() + "/";
147 
148   std::move(barrier_closure).Run();
149 }
150 
151 // static
IsValidAndroidCredential(const PasswordStore::FormDigest & form)152 bool AffiliatedMatchHelper::IsValidAndroidCredential(
153     const PasswordStore::FormDigest& form) {
154   return form.scheme == autofill::PasswordForm::Scheme::kHtml &&
155          IsValidAndroidFacetURI(form.signon_realm);
156 }
157 
158 // static
IsValidWebCredential(const PasswordStore::FormDigest & form)159 bool AffiliatedMatchHelper::IsValidWebCredential(
160     const PasswordStore::FormDigest& form) {
161   FacetURI facet_uri(FacetURI::FromPotentiallyInvalidSpec(form.signon_realm));
162   return form.scheme == autofill::PasswordForm::Scheme::kHtml &&
163          facet_uri.IsValidWebFacetURI();
164 }
165 
DoDeferredInitialization()166 void AffiliatedMatchHelper::DoDeferredInitialization() {
167   // Must start observing for changes at the same time as when the snapshot is
168   // taken to avoid inconsistencies due to any changes taking place in-between.
169   password_store_->AddObserver(this);
170   password_store_->GetAllLogins(this);
171 }
172 
CompleteGetAffiliatedAndroidRealms(const FacetURI & original_facet_uri,AffiliatedRealmsCallback result_callback,const AffiliatedFacets & results,bool success)173 void AffiliatedMatchHelper::CompleteGetAffiliatedAndroidRealms(
174     const FacetURI& original_facet_uri,
175     AffiliatedRealmsCallback result_callback,
176     const AffiliatedFacets& results,
177     bool success) {
178   std::vector<std::string> affiliated_realms;
179   if (success) {
180     for (const Facet& affiliated_facet : results) {
181       if (affiliated_facet.uri != original_facet_uri &&
182           affiliated_facet.uri.IsValidAndroidFacetURI())
183         // Facet URIs have no trailing slash, whereas realms do.
184         affiliated_realms.push_back(affiliated_facet.uri.canonical_spec() +
185                                     "/");
186     }
187   }
188   std::move(result_callback).Run(affiliated_realms);
189 }
190 
CompleteGetAffiliatedWebRealms(AffiliatedRealmsCallback result_callback,const AffiliatedFacets & results,bool success)191 void AffiliatedMatchHelper::CompleteGetAffiliatedWebRealms(
192     AffiliatedRealmsCallback result_callback,
193     const AffiliatedFacets& results,
194     bool success) {
195   std::vector<std::string> affiliated_realms;
196   if (success) {
197     for (const Facet& affiliated_facet : results) {
198       if (affiliated_facet.uri.IsValidWebFacetURI())
199         // Facet URIs have no trailing slash, whereas realms do.
200         affiliated_realms.push_back(affiliated_facet.uri.canonical_spec() +
201                                     "/");
202     }
203   }
204   std::move(result_callback).Run(affiliated_realms);
205 }
206 
OnLoginsChanged(const PasswordStoreChangeList & changes)207 void AffiliatedMatchHelper::OnLoginsChanged(
208     const PasswordStoreChangeList& changes) {
209   std::vector<FacetURI> facet_uris_to_trim;
210   for (const PasswordStoreChange& change : changes) {
211     FacetURI facet_uri;
212     if (!IsAndroidApplicationCredential(change.form(), &facet_uri))
213       continue;
214 
215     if (change.type() == PasswordStoreChange::ADD) {
216       affiliation_service_->Prefetch(facet_uri, base::Time::Max());
217     } else if (change.type() == PasswordStoreChange::REMOVE) {
218       // Stop keeping affiliation information fresh for deleted Android logins,
219       // and make a note to potentially remove any unneeded cached data later.
220       facet_uris_to_trim.push_back(facet_uri);
221       affiliation_service_->CancelPrefetch(facet_uri, base::Time::Max());
222     }
223   }
224 
225   // When the primary key for a login is updated, |changes| will contain both a
226   // REMOVE and ADD change for that login. Cached affiliation data should not be
227   // deleted in this case. A simple solution is to call TrimCacheForFacetURI()
228   // always after Prefetch() calls -- the trimming logic will detect that there
229   // is an active prefetch and not delete the corresponding data.
230   for (const FacetURI& facet_uri : facet_uris_to_trim)
231     affiliation_service_->TrimCacheForFacetURI(facet_uri);
232 }
233 
OnGetPasswordStoreResults(std::vector<std::unique_ptr<autofill::PasswordForm>> results)234 void AffiliatedMatchHelper::OnGetPasswordStoreResults(
235     std::vector<std::unique_ptr<autofill::PasswordForm>> results) {
236   for (const auto& form : results) {
237     FacetURI facet_uri;
238     if (IsAndroidApplicationCredential(*form, &facet_uri))
239       affiliation_service_->Prefetch(facet_uri, base::Time::Max());
240   }
241 }
242 
243 }  // namespace password_manager
244