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