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