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