1 // Copyright 2020 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 "chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl.h"
6
7 #include <algorithm>
8
9 #include "base/memory/ptr_util.h"
10 #include "base/stl_util.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/time/time.h"
13 #include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
14 #include "chrome/browser/nearby_sharing/contacts/nearby_share_contact_downloader.h"
15 #include "chrome/browser/nearby_sharing/contacts/nearby_share_contact_downloader_impl.h"
16 #include "chrome/browser/nearby_sharing/local_device_data/nearby_share_local_device_data_manager.h"
17 #include "chrome/browser/nearby_sharing/logging/logging.h"
18 #include "chrome/browser/nearby_sharing/proto/device_rpc.pb.h"
19 #include "chrome/browser/nearby_sharing/proto/rpc_resources.pb.h"
20 #include "chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler.h"
21 #include "chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler_factory.h"
22 #include "chrome/browser/ui/webui/nearby_share/public/mojom/nearby_share_settings.mojom-shared.h"
23 #include "chrome/browser/ui/webui/nearby_share/public/mojom/nearby_share_settings.mojom.h"
24 #include "components/prefs/pref_service.h"
25 #include "crypto/secure_hash.h"
26
27 namespace {
28
29 constexpr base::TimeDelta kContactUploadPeriod = base::TimeDelta::FromHours(24);
30 constexpr base::TimeDelta kContactDownloadPeriod =
31 base::TimeDelta::FromHours(12);
32 constexpr base::TimeDelta kContactDownloadRpcTimeout =
33 base::TimeDelta::FromSeconds(60);
34
35 // Removes contact IDs from the allowlist if they are not in |contacts|.
RemoveNonexistentContactsFromAllowlist(const std::set<std::string> & allowed_contact_ids,const std::vector<nearbyshare::proto::ContactRecord> & contacts)36 std::set<std::string> RemoveNonexistentContactsFromAllowlist(
37 const std::set<std::string>& allowed_contact_ids,
38 const std::vector<nearbyshare::proto::ContactRecord>& contacts) {
39 std::set<std::string> new_allowed_contact_ids;
40 for (const nearbyshare::proto::ContactRecord& contact : contacts) {
41 if (base::Contains(allowed_contact_ids, contact.id()))
42 new_allowed_contact_ids.insert(contact.id());
43 }
44 return new_allowed_contact_ids;
45 }
46
47 // Converts a list of ContactRecord protos, along with the allowlist, into a
48 // list of Contact protos.
ContactRecordsToContacts(const std::set<std::string> & allowed_contact_ids,const std::vector<nearbyshare::proto::ContactRecord> & contact_records)49 std::vector<nearbyshare::proto::Contact> ContactRecordsToContacts(
50 const std::set<std::string>& allowed_contact_ids,
51 const std::vector<nearbyshare::proto::ContactRecord>& contact_records) {
52 std::vector<nearbyshare::proto::Contact> contacts;
53 for (const auto& contact_record : contact_records) {
54 bool is_selected = base::Contains(allowed_contact_ids, contact_record.id());
55 for (const auto& identifier : contact_record.identifiers()) {
56 nearbyshare::proto::Contact contact;
57 contact.mutable_identifier()->CopyFrom(identifier);
58 contact.set_is_selected(is_selected);
59 contacts.push_back(contact);
60 }
61 }
62 return contacts;
63 }
64
CreateLocalContact(const std::string & profile_user_name)65 nearbyshare::proto::Contact CreateLocalContact(
66 const std::string& profile_user_name) {
67 nearbyshare::proto::Contact contact;
68 contact.mutable_identifier()->set_account_name(profile_user_name);
69 // Always consider your own account a selected contact.
70 contact.set_is_selected(true);
71 return contact;
72 }
73
74 // Creates a hex-encoded hash of the contact data, implicitly including the
75 // allowlist, to be sent to the Nearby Share server. This hash is persisted and
76 // used to detect any changes to the user's contact list or allowlist since the
77 // last successful upload to the server.
ComputeHash(const std::vector<nearbyshare::proto::Contact> & contacts)78 std::string ComputeHash(
79 const std::vector<nearbyshare::proto::Contact>& contacts) {
80 std::unique_ptr<crypto::SecureHash> hasher =
81 crypto::SecureHash::Create(crypto::SecureHash::Algorithm::SHA256);
82
83 for (const nearbyshare::proto::Contact& contact : contacts) {
84 std::string serialized = contact.SerializeAsString();
85 hasher->Update(serialized.data(), serialized.size());
86 }
87
88 std::vector<uint8_t> hash(hasher->GetHashLength());
89 hasher->Finish(hash.data(), hash.size());
90
91 return base::HexEncode(hash);
92 }
93
ProtoToMojo(const nearbyshare::proto::Contact_Identifier & identifier)94 nearby_share::mojom::ContactIdentifierPtr ProtoToMojo(
95 const nearbyshare::proto::Contact_Identifier& identifier) {
96 nearby_share::mojom::ContactIdentifierPtr identifier_ptr =
97 nearby_share::mojom::ContactIdentifier::New();
98 switch (identifier.identifier_case()) {
99 case nearbyshare::proto::Contact_Identifier::IdentifierCase::kAccountName:
100 identifier_ptr->set_account_name(identifier.account_name());
101 break;
102 case nearbyshare::proto::Contact_Identifier::IdentifierCase::
103 kObfuscatedGaia:
104 identifier_ptr->set_obfuscated_gaia(identifier.obfuscated_gaia());
105 break;
106 case nearbyshare::proto::Contact_Identifier::IdentifierCase::kPhoneNumber:
107 identifier_ptr->set_phone_number(identifier.phone_number());
108 break;
109 case nearbyshare::proto::Contact_Identifier::IdentifierCase::
110 IDENTIFIER_NOT_SET:
111 NOTREACHED();
112 break;
113 }
114 return identifier_ptr;
115 }
116
ProtoToMojo(const nearbyshare::proto::ContactRecord & contact_record)117 nearby_share::mojom::ContactRecordPtr ProtoToMojo(
118 const nearbyshare::proto::ContactRecord& contact_record) {
119 nearby_share::mojom::ContactRecordPtr contact_record_ptr =
120 nearby_share::mojom::ContactRecord::New();
121 contact_record_ptr->id = contact_record.id();
122 contact_record_ptr->person_name = contact_record.person_name();
123 contact_record_ptr->image_url = GURL(contact_record.image_url());
124 for (const auto& identifier : contact_record.identifiers()) {
125 contact_record_ptr->identifiers.push_back(ProtoToMojo(identifier));
126 }
127 return contact_record_ptr;
128 }
129
ProtoToMojo(const std::vector<nearbyshare::proto::ContactRecord> & contacts)130 std::vector<nearby_share::mojom::ContactRecordPtr> ProtoToMojo(
131 const std::vector<nearbyshare::proto::ContactRecord>& contacts) {
132 std::vector<nearby_share::mojom::ContactRecordPtr> mojo_contacts;
133 mojo_contacts.reserve(contacts.size());
134 for (const auto& contact_record : contacts) {
135 mojo_contacts.push_back(ProtoToMojo(contact_record));
136 }
137 return mojo_contacts;
138 }
139
140 } // namespace
141
142 // static
143 NearbyShareContactManagerImpl::Factory*
144 NearbyShareContactManagerImpl::Factory::test_factory_ = nullptr;
145
146 // static
147 std::unique_ptr<NearbyShareContactManager>
Create(PrefService * pref_service,NearbyShareClientFactory * http_client_factory,NearbyShareLocalDeviceDataManager * local_device_data_manager,const std::string & profile_user_name)148 NearbyShareContactManagerImpl::Factory::Create(
149 PrefService* pref_service,
150 NearbyShareClientFactory* http_client_factory,
151 NearbyShareLocalDeviceDataManager* local_device_data_manager,
152 const std::string& profile_user_name) {
153 if (test_factory_) {
154 return test_factory_->CreateInstance(pref_service, http_client_factory,
155 local_device_data_manager,
156 profile_user_name);
157 }
158 return base::WrapUnique(new NearbyShareContactManagerImpl(
159 pref_service, http_client_factory, local_device_data_manager,
160 profile_user_name));
161 }
162
163 // static
SetFactoryForTesting(Factory * test_factory)164 void NearbyShareContactManagerImpl::Factory::SetFactoryForTesting(
165 Factory* test_factory) {
166 test_factory_ = test_factory;
167 }
168
169 NearbyShareContactManagerImpl::Factory::~Factory() = default;
170
NearbyShareContactManagerImpl(PrefService * pref_service,NearbyShareClientFactory * http_client_factory,NearbyShareLocalDeviceDataManager * local_device_data_manager,const std::string & profile_user_name)171 NearbyShareContactManagerImpl::NearbyShareContactManagerImpl(
172 PrefService* pref_service,
173 NearbyShareClientFactory* http_client_factory,
174 NearbyShareLocalDeviceDataManager* local_device_data_manager,
175 const std::string& profile_user_name)
176 : pref_service_(pref_service),
177 http_client_factory_(http_client_factory),
178 local_device_data_manager_(local_device_data_manager),
179 profile_user_name_(profile_user_name),
180 periodic_contact_upload_scheduler_(
181 NearbyShareSchedulerFactory::CreatePeriodicScheduler(
182 kContactUploadPeriod,
183 /*retry_failures=*/false,
184 /*require_connectivity=*/true,
185 prefs::kNearbySharingSchedulerPeriodicContactUploadPrefName,
186 pref_service_,
187 base::BindRepeating(&NearbyShareContactManagerImpl::
188 OnPeriodicContactsUploadRequested,
189 base::Unretained(this)))),
190 contact_download_and_upload_scheduler_(
191 NearbyShareSchedulerFactory::CreatePeriodicScheduler(
192 kContactDownloadPeriod,
193 /*retry_failures=*/true,
194 /*require_connectivity=*/true,
195 prefs::kNearbySharingSchedulerContactDownloadAndUploadPrefName,
196 pref_service_,
197 base::BindRepeating(
198 &NearbyShareContactManagerImpl::OnContactsDownloadRequested,
199 base::Unretained(this)))) {}
200
201 NearbyShareContactManagerImpl::~NearbyShareContactManagerImpl() = default;
202
DownloadContacts()203 void NearbyShareContactManagerImpl::DownloadContacts() {
204 // Make sure the scheduler is running so we can retrieve contacts while
205 // onboarding.
206 Start();
207
208 contact_download_and_upload_scheduler_->MakeImmediateRequest();
209 }
210
SetAllowedContacts(const std::set<std::string> & allowed_contact_ids)211 void NearbyShareContactManagerImpl::SetAllowedContacts(
212 const std::set<std::string>& allowed_contact_ids) {
213 // If the allowlist changed, re-upload contacts to Nearby server.
214 if (SetAllowlist(allowed_contact_ids))
215 contact_download_and_upload_scheduler_->MakeImmediateRequest();
216 }
217
OnStart()218 void NearbyShareContactManagerImpl::OnStart() {
219 periodic_contact_upload_scheduler_->Start();
220 contact_download_and_upload_scheduler_->Start();
221 }
222
OnStop()223 void NearbyShareContactManagerImpl::OnStop() {
224 periodic_contact_upload_scheduler_->Stop();
225 contact_download_and_upload_scheduler_->Stop();
226 }
227
Bind(mojo::PendingReceiver<nearby_share::mojom::ContactManager> receiver)228 void NearbyShareContactManagerImpl::Bind(
229 mojo::PendingReceiver<nearby_share::mojom::ContactManager> receiver) {
230 receiver_set_.Add(this, std::move(receiver));
231 }
232
AddDownloadContactsObserver(::mojo::PendingRemote<nearby_share::mojom::DownloadContactsObserver> observer)233 void NearbyShareContactManagerImpl::AddDownloadContactsObserver(
234 ::mojo::PendingRemote<nearby_share::mojom::DownloadContactsObserver>
235 observer) {
236 observers_set_.Add(std::move(observer));
237 }
238
GetAllowedContacts() const239 std::set<std::string> NearbyShareContactManagerImpl::GetAllowedContacts()
240 const {
241 std::set<std::string> allowlist;
242 for (const base::Value& id :
243 pref_service_->Get(prefs::kNearbySharingAllowedContactsPrefName)
244 ->GetList()) {
245 allowlist.insert(id.GetString());
246 }
247 return allowlist;
248 }
249
OnPeriodicContactsUploadRequested()250 void NearbyShareContactManagerImpl::OnPeriodicContactsUploadRequested() {
251 NS_LOG(VERBOSE) << __func__
252 << ": Periodic Nearby Share contacts upload requested. "
253 << "Upload will occur after next contacts download.";
254 }
255
OnContactsDownloadRequested()256 void NearbyShareContactManagerImpl::OnContactsDownloadRequested() {
257 NS_LOG(VERBOSE) << __func__ << ": Nearby Share contacts download requested.";
258
259 DCHECK(!contact_downloader_);
260 contact_downloader_ = NearbyShareContactDownloaderImpl::Factory::Create(
261 local_device_data_manager_->GetId(), kContactDownloadRpcTimeout,
262 http_client_factory_,
263 base::BindOnce(&NearbyShareContactManagerImpl::OnContactsDownloadSuccess,
264 base::Unretained(this)),
265 base::BindOnce(&NearbyShareContactManagerImpl::OnContactsDownloadFailure,
266 base::Unretained(this)));
267 contact_downloader_->Run();
268 }
269
OnContactsDownloadSuccess(std::vector<nearbyshare::proto::ContactRecord> contacts,uint32_t num_unreachable_contacts_filtered_out)270 void NearbyShareContactManagerImpl::OnContactsDownloadSuccess(
271 std::vector<nearbyshare::proto::ContactRecord> contacts,
272 uint32_t num_unreachable_contacts_filtered_out) {
273 contact_downloader_.reset();
274
275 NS_LOG(VERBOSE) << __func__ << ": Nearby Share download of "
276 << contacts.size() << " contacts succeeded.";
277
278 // Remove contacts from the allowlist that are not in the contact list.
279 SetAllowlist(
280 RemoveNonexistentContactsFromAllowlist(GetAllowedContacts(), contacts));
281
282 // Notify observers that the contact list was downloaded.
283 std::set<std::string> allowed_contact_ids = GetAllowedContacts();
284 NotifyContactsDownloaded(allowed_contact_ids, contacts,
285 num_unreachable_contacts_filtered_out);
286 NotifyMojoObserverContactsDownloaded(allowed_contact_ids, contacts,
287 num_unreachable_contacts_filtered_out);
288
289 std::vector<nearbyshare::proto::Contact> contacts_to_upload =
290 ContactRecordsToContacts(GetAllowedContacts(), contacts);
291
292 // Enable cross-device self-share by adding your account to the list of
293 // contacts. It is also marked as a selected contact.
294 if (profile_user_name_.empty()) {
295 NS_LOG(WARNING) << __func__ << ": Profile user name is empty; could not "
296 << "add self to list of contacts to upload.";
297 } else {
298 contacts_to_upload.push_back(CreateLocalContact(profile_user_name_));
299 }
300
301 std::string contact_upload_hash = ComputeHash(contacts_to_upload);
302 bool did_contacts_change_since_last_upload =
303 contact_upload_hash !=
304 pref_service_->GetString(prefs::kNearbySharingContactUploadHashPrefName);
305 if (did_contacts_change_since_last_upload) {
306 NS_LOG(VERBOSE) << __func__
307 << ": Contact list or allowlist changed since last "
308 << "successful upload to the Nearby Share server.";
309 }
310
311 // Request a contacts upload if the contact list or allowlist has changed
312 // since the last successful upload. Also request an upload periodically.
313 if (did_contacts_change_since_last_upload ||
314 periodic_contact_upload_scheduler_->IsWaitingForResult()) {
315 local_device_data_manager_->UploadContacts(
316 std::move(contacts_to_upload),
317 base::BindOnce(&NearbyShareContactManagerImpl::OnContactsUploadFinished,
318 weak_ptr_factory_.GetWeakPtr(),
319 did_contacts_change_since_last_upload,
320 contact_upload_hash));
321 return;
322 }
323
324 // No upload is needed.
325 contact_download_and_upload_scheduler_->HandleResult(/*success=*/true);
326 }
327
OnContactsDownloadFailure()328 void NearbyShareContactManagerImpl::OnContactsDownloadFailure() {
329 contact_downloader_.reset();
330
331 NS_LOG(WARNING) << __func__ << ": Nearby Share contacts download failed.";
332
333 // Notify mojo remotes.
334 for (auto& remote : observers_set_) {
335 remote->OnContactsDownloadFailed();
336 }
337
338 contact_download_and_upload_scheduler_->HandleResult(/*success=*/false);
339 }
340
OnContactsUploadFinished(bool did_contacts_change_since_last_upload,const std::string & contact_upload_hash,bool success)341 void NearbyShareContactManagerImpl::OnContactsUploadFinished(
342 bool did_contacts_change_since_last_upload,
343 const std::string& contact_upload_hash,
344 bool success) {
345 NS_LOG(VERBOSE) << __func__ << ": Upload of contacts to Nearby Share server "
346 << (success ? "succeeded." : "failed.")
347 << " Contact upload hash: " << contact_upload_hash;
348 if (success) {
349 // Only resolve the periodic upload request on success; let the
350 // download-and-upload scheduler handle any failure retries. The periodic
351 // upload scheduler will remember that it has an outstanding request even
352 // after reboot.
353 if (periodic_contact_upload_scheduler_->IsWaitingForResult()) {
354 periodic_contact_upload_scheduler_->HandleResult(success);
355 }
356
357 pref_service_->SetString(prefs::kNearbySharingContactUploadHashPrefName,
358 contact_upload_hash);
359 NotifyContactsUploaded(did_contacts_change_since_last_upload);
360 }
361
362 contact_download_and_upload_scheduler_->HandleResult(success);
363 }
364
SetAllowlist(const std::set<std::string> & new_allowlist)365 bool NearbyShareContactManagerImpl::SetAllowlist(
366 const std::set<std::string>& new_allowlist) {
367 if (new_allowlist == GetAllowedContacts())
368 return false;
369
370 base::Value allowlist_value(base::Value::Type::LIST);
371 for (const std::string& id : new_allowlist) {
372 allowlist_value.Append(id);
373 }
374 pref_service_->Set(prefs::kNearbySharingAllowedContactsPrefName,
375 std::move(allowlist_value));
376
377 return true;
378 }
379
NotifyMojoObserverContactsDownloaded(const std::set<std::string> & allowed_contact_ids,const std::vector<nearbyshare::proto::ContactRecord> & contacts,uint32_t num_unreachable_contacts_filtered_out)380 void NearbyShareContactManagerImpl::NotifyMojoObserverContactsDownloaded(
381 const std::set<std::string>& allowed_contact_ids,
382 const std::vector<nearbyshare::proto::ContactRecord>& contacts,
383 uint32_t num_unreachable_contacts_filtered_out) {
384 if (observers_set_.empty()) {
385 return;
386 }
387
388 // Mojo doesn't have sets, so we have to copy to an array.
389 std::vector<std::string> allowed_contact_ids_vector(
390 allowed_contact_ids.begin(), allowed_contact_ids.end());
391
392 // Notify mojo remotes.
393 for (auto& remote : observers_set_) {
394 remote->OnContactsDownloaded(allowed_contact_ids_vector,
395 ProtoToMojo(contacts),
396 num_unreachable_contacts_filtered_out);
397 }
398 }
399