1 // Copyright 2019 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/chromeos/android_sms/android_sms_app_setup_controller_impl.h"
6 
7 #include <string>
8 #include <vector>
9 
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/containers/flat_map.h"
13 #include "base/feature_list.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/optional.h"
16 #include "base/threading/thread_task_runner_handle.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/web_applications/components/external_install_options.h"
19 #include "chrome/browser/web_applications/components/install_finalizer.h"
20 #include "chrome/browser/web_applications/components/pending_app_manager.h"
21 #include "chrome/browser/web_applications/components/web_app_constants.h"
22 #include "chrome/browser/web_applications/components/web_app_helpers.h"
23 #include "chrome/browser/web_applications/components/web_app_provider_base.h"
24 #include "chromeos/components/multidevice/logging/logging.h"
25 #include "components/content_settings/core/browser/host_content_settings_map.h"
26 #include "content/public/browser/browser_context.h"
27 #include "content/public/browser/storage_partition.h"
28 #include "net/base/url_util.h"
29 #include "net/cookies/cookie_util.h"
30 #include "services/network/public/mojom/cookie_manager.mojom.h"
31 #include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
32 #include "url/gurl.h"
33 
34 namespace chromeos {
35 
36 namespace android_sms {
37 
38 namespace {
39 
40 const char kDefaultToPersistCookieName[] = "default_to_persist";
41 const char kMigrationCookieName[] = "cros_migrated_to";
42 const char kDefaultToPersistCookieValue[] = "true";
43 
44 }  // namespace
45 
46 // static
47 const base::TimeDelta AndroidSmsAppSetupControllerImpl::kInstallRetryDelay =
48     base::TimeDelta::FromSeconds(5);
49 const size_t AndroidSmsAppSetupControllerImpl::kMaxInstallRetryCount = 7u;
50 
51 AndroidSmsAppSetupControllerImpl::PwaDelegate::PwaDelegate() = default;
52 
53 AndroidSmsAppSetupControllerImpl::PwaDelegate::~PwaDelegate() = default;
54 
55 base::Optional<web_app::AppId>
GetPwaForUrl(const GURL & install_url,Profile * profile)56 AndroidSmsAppSetupControllerImpl::PwaDelegate::GetPwaForUrl(
57     const GURL& install_url,
58     Profile* profile) {
59   return web_app::FindInstalledAppWithUrlInScope(profile, install_url);
60 }
61 
62 network::mojom::CookieManager*
GetCookieManager(const GURL & app_url,Profile * profile)63 AndroidSmsAppSetupControllerImpl::PwaDelegate::GetCookieManager(
64     const GURL& app_url,
65     Profile* profile) {
66   return content::BrowserContext::GetStoragePartitionForSite(profile, app_url)
67       ->GetCookieManagerForBrowserProcess();
68 }
69 
RemovePwa(const web_app::AppId & app_id,Profile * profile,SuccessCallback callback)70 void AndroidSmsAppSetupControllerImpl::PwaDelegate::RemovePwa(
71     const web_app::AppId& app_id,
72     Profile* profile,
73     SuccessCallback callback) {
74   auto* provider = web_app::WebAppProviderBase::GetProviderBase(profile);
75   if (!provider) {
76     std::move(callback).Run(false);
77     return;
78   }
79 
80   provider->install_finalizer().UninstallExternalWebApp(
81       app_id, web_app::ExternalInstallSource::kInternalDefault,
82       std::move(callback));
83 }
84 
AndroidSmsAppSetupControllerImpl(Profile * profile,web_app::PendingAppManager * pending_app_manager,HostContentSettingsMap * host_content_settings_map)85 AndroidSmsAppSetupControllerImpl::AndroidSmsAppSetupControllerImpl(
86     Profile* profile,
87     web_app::PendingAppManager* pending_app_manager,
88     HostContentSettingsMap* host_content_settings_map)
89     : profile_(profile),
90       pending_app_manager_(pending_app_manager),
91       host_content_settings_map_(host_content_settings_map),
92       pwa_delegate_(std::make_unique<PwaDelegate>()) {}
93 
94 AndroidSmsAppSetupControllerImpl::~AndroidSmsAppSetupControllerImpl() = default;
95 
SetUpApp(const GURL & app_url,const GURL & install_url,SuccessCallback callback)96 void AndroidSmsAppSetupControllerImpl::SetUpApp(const GURL& app_url,
97                                                 const GURL& install_url,
98                                                 SuccessCallback callback) {
99   PA_LOG(VERBOSE) << "AndroidSmsAppSetupControllerImpl::SetUpApp(): Setting "
100                   << "DefaultToPersist cookie at " << app_url << " before PWA "
101                   << "installation.";
102   net::CookieOptions options;
103   options.set_same_site_cookie_context(
104       net::CookieOptions::SameSiteCookieContext::MakeInclusive());
105   net::CanonicalCookie cookie = *net::CanonicalCookie::CreateSanitizedCookie(
106       app_url, kDefaultToPersistCookieName, kDefaultToPersistCookieValue,
107       std::string() /* domain */, std::string() /* path */,
108       base::Time::Now() /* creation_time */, base::Time() /* expiration_time */,
109       base::Time::Now() /* last_access_time */,
110       !net::IsLocalhost(app_url) /* secure */, false /* http_only */,
111       net::CookieSameSite::STRICT_MODE, net::COOKIE_PRIORITY_DEFAULT,
112       false /* same_party */);
113   // TODO(crbug.com/1069974): The cookie source url must be faked here because
114   // otherwise, this would fail to set a secure cookie if |app_url| is insecure.
115   // Consider instead to use url::Replacements to force the scheme to be https.
116   pwa_delegate_->GetCookieManager(app_url, profile_)
117       ->SetCanonicalCookie(
118           cookie, net::cookie_util::SimulatedCookieSource(cookie, "https"),
119           options,
120           base::BindOnce(&AndroidSmsAppSetupControllerImpl::
121                              OnSetRememberDeviceByDefaultCookieResult,
122                          weak_ptr_factory_.GetWeakPtr(), app_url, install_url,
123                          std::move(callback)));
124 }
125 
GetPwa(const GURL & install_url)126 base::Optional<web_app::AppId> AndroidSmsAppSetupControllerImpl::GetPwa(
127     const GURL& install_url) {
128   return pwa_delegate_->GetPwaForUrl(install_url, profile_);
129 }
130 
DeleteRememberDeviceByDefaultCookie(const GURL & app_url,SuccessCallback callback)131 void AndroidSmsAppSetupControllerImpl::DeleteRememberDeviceByDefaultCookie(
132     const GURL& app_url,
133     SuccessCallback callback) {
134   PA_LOG(INFO) << "AndroidSmsAppSetupControllerImpl::"
135                << "DeleteRememberDeviceByDefaultCookie(): Deleting "
136                << "DefaultToPersist cookie at " << app_url << ".";
137   network::mojom::CookieDeletionFilterPtr filter(
138       network::mojom::CookieDeletionFilter::New());
139   filter->url = app_url;
140   filter->cookie_name = kDefaultToPersistCookieName;
141   pwa_delegate_->GetCookieManager(app_url, profile_)
142       ->DeleteCookies(
143           std::move(filter),
144           base::BindOnce(&AndroidSmsAppSetupControllerImpl::
145                              OnDeleteRememberDeviceByDefaultCookieResult,
146                          weak_ptr_factory_.GetWeakPtr(), app_url,
147                          std::move(callback)));
148 }
149 
RemoveApp(const GURL & app_url,const GURL & install_url,const GURL & migrated_to_app_url,SuccessCallback callback)150 void AndroidSmsAppSetupControllerImpl::RemoveApp(
151     const GURL& app_url,
152     const GURL& install_url,
153     const GURL& migrated_to_app_url,
154     SuccessCallback callback) {
155   base::Optional<web_app::AppId> app_id =
156       pwa_delegate_->GetPwaForUrl(install_url, profile_);
157 
158   // If there is no app installed at |url|, there is nothing more to do.
159   if (!app_id) {
160     PA_LOG(VERBOSE) << "AndroidSmsAppSetupControllerImpl::RemoveApp(): No app "
161                     << "is installed at " << install_url
162                     << "; skipping removal process.";
163     std::move(callback).Run(true /* success */);
164     return;
165   }
166 
167   PA_LOG(INFO) << "AndroidSmsAppSetupControllerImpl::RemoveApp(): "
168                << "Uninstalling app at " << install_url << ".";
169 
170   pwa_delegate_->RemovePwa(
171       *app_id, profile_,
172       base::BindOnce(&AndroidSmsAppSetupControllerImpl::OnAppRemoved,
173                      weak_ptr_factory_.GetWeakPtr(), std::move(callback),
174                      app_url, install_url, migrated_to_app_url));
175 }
176 
OnAppRemoved(SuccessCallback callback,const GURL & app_url,const GURL & install_url,const GURL & migrated_to_app_url,bool uninstalled)177 void AndroidSmsAppSetupControllerImpl::OnAppRemoved(
178     SuccessCallback callback,
179     const GURL& app_url,
180     const GURL& install_url,
181     const GURL& migrated_to_app_url,
182     bool uninstalled) {
183   UMA_HISTOGRAM_BOOLEAN("AndroidSms.PWAUninstallationResult", uninstalled);
184 
185   if (!uninstalled) {
186     PA_LOG(ERROR) << "AndroidSmsAppSetupControllerImpl::RemoveApp(): "
187                   << "PWA for " << install_url << " failed to uninstall.";
188     std::move(callback).Run(false /* success */);
189     return;
190   }
191 
192   SetMigrationCookie(app_url, migrated_to_app_url, std::move(callback));
193 }
194 
OnSetRememberDeviceByDefaultCookieResult(const GURL & app_url,const GURL & install_url,SuccessCallback callback,net::CookieAccessResult result)195 void AndroidSmsAppSetupControllerImpl::OnSetRememberDeviceByDefaultCookieResult(
196     const GURL& app_url,
197     const GURL& install_url,
198     SuccessCallback callback,
199     net::CookieAccessResult result) {
200   if (!result.status.IsInclude()) {
201     PA_LOG(WARNING)
202         << "AndroidSmsAppSetupControllerImpl::"
203         << "OnSetRememberDeviceByDefaultCookieResult(): Failed to set "
204         << "DefaultToPersist cookie at " << app_url << ". Proceeding "
205         << "to remove migration cookie.";
206   }
207 
208   // Delete migration cookie in case it was set by a previous RemoveApp call.
209   network::mojom::CookieDeletionFilterPtr filter(
210       network::mojom::CookieDeletionFilter::New());
211   filter->url = app_url;
212   filter->cookie_name = kMigrationCookieName;
213   pwa_delegate_->GetCookieManager(app_url, profile_)
214       ->DeleteCookies(
215           std::move(filter),
216           base::BindOnce(
217               &AndroidSmsAppSetupControllerImpl::OnDeleteMigrationCookieResult,
218               weak_ptr_factory_.GetWeakPtr(), app_url, install_url,
219               std::move(callback)));
220 }
221 
OnDeleteMigrationCookieResult(const GURL & app_url,const GURL & install_url,SuccessCallback callback,uint32_t num_deleted)222 void AndroidSmsAppSetupControllerImpl::OnDeleteMigrationCookieResult(
223     const GURL& app_url,
224     const GURL& install_url,
225     SuccessCallback callback,
226     uint32_t num_deleted) {
227   // If the app is already installed at |url|, there is nothing more to do.
228   if (pwa_delegate_->GetPwaForUrl(install_url, profile_)) {
229     PA_LOG(VERBOSE) << "AndroidSmsAppSetupControllerImpl::"
230                     << "OnDeleteMigrationCookieResult():"
231                     << "App is already installed at " << install_url
232                     << "; skipping setup process.";
233     std::move(callback).Run(true /* success */);
234     return;
235   }
236 
237   TryInstallApp(install_url, app_url, 0 /* num_attempts_so_far */,
238                 std::move(callback));
239 }
240 
TryInstallApp(const GURL & install_url,const GURL & app_url,size_t num_attempts_so_far,SuccessCallback callback)241 void AndroidSmsAppSetupControllerImpl::TryInstallApp(const GURL& install_url,
242                                                      const GURL& app_url,
243                                                      size_t num_attempts_so_far,
244                                                      SuccessCallback callback) {
245   PA_LOG(VERBOSE) << "AndroidSmsAppSetupControllerImpl::TryInstallApp(): "
246                   << "Trying to install PWA for " << install_url
247                   << ". Num attempts so far # " << num_attempts_so_far;
248   web_app::ExternalInstallOptions options(
249       install_url, blink::mojom::DisplayMode::kStandalone,
250       web_app::ExternalInstallSource::kInternalDefault);
251   options.override_previous_user_uninstall = true;
252   // The ServiceWorker does not load in time for the installability check, so
253   // bypass it as a workaround.
254   options.bypass_service_worker_check = true;
255   options.require_manifest = true;
256   pending_app_manager_->Install(
257       std::move(options),
258       base::BindOnce(&AndroidSmsAppSetupControllerImpl::OnAppInstallResult,
259                      weak_ptr_factory_.GetWeakPtr(), std::move(callback),
260                      num_attempts_so_far, app_url));
261 }
262 
OnAppInstallResult(SuccessCallback callback,size_t num_attempts_so_far,const GURL & app_url,const GURL & install_url,web_app::InstallResultCode code)263 void AndroidSmsAppSetupControllerImpl::OnAppInstallResult(
264     SuccessCallback callback,
265     size_t num_attempts_so_far,
266     const GURL& app_url,
267     const GURL& install_url,
268     web_app::InstallResultCode code) {
269   UMA_HISTOGRAM_ENUMERATION("AndroidSms.PWAInstallationResult", code);
270   const bool install_succeeded = web_app::IsSuccess(code);
271 
272   if (!install_succeeded && num_attempts_so_far < kMaxInstallRetryCount) {
273     base::TimeDelta retry_delay =
274         kInstallRetryDelay * (1 << num_attempts_so_far);
275     PA_LOG(VERBOSE)
276         << "AndroidSmsAppSetupControllerImpl::OnAppInstallResult(): "
277         << "PWA for " << install_url << " failed to install."
278         << "InstallResultCode: " << static_cast<int>(code)
279         << " Retrying again in " << retry_delay;
280     base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
281         FROM_HERE,
282         base::BindOnce(&AndroidSmsAppSetupControllerImpl::TryInstallApp,
283                        weak_ptr_factory_.GetWeakPtr(), install_url, app_url,
284                        num_attempts_so_far + 1, std::move(callback)),
285         retry_delay);
286     return;
287   }
288   UMA_HISTOGRAM_BOOLEAN("AndroidSms.EffectivePWAInstallationSuccess",
289                         install_succeeded);
290 
291   if (!install_succeeded) {
292     PA_LOG(WARNING)
293         << "AndroidSmsAppSetupControllerImpl::OnAppInstallResult(): "
294         << "PWA for " << install_url << " failed to install. "
295         << "InstallResultCode: " << static_cast<int>(code);
296     std::move(callback).Run(false /* success */);
297     return;
298   }
299   PA_LOG(INFO) << "AndroidSmsAppSetupControllerImpl::OnAppInstallResult(): "
300                << "PWA for " << install_url << " was installed successfully.";
301 
302   UMA_HISTOGRAM_EXACT_LINEAR("AndroidSms.NumAttemptsForSuccessfulInstallation",
303                              num_attempts_so_far + 1, kMaxInstallRetryCount);
304 
305   // Grant notification permission for the PWA.
306   host_content_settings_map_->SetWebsiteSettingDefaultScope(
307       app_url, GURL() /* top_level_url */, ContentSettingsType::NOTIFICATIONS,
308       std::make_unique<base::Value>(ContentSetting::CONTENT_SETTING_ALLOW));
309 
310   std::move(callback).Run(true /* success */);
311 }
312 
SetMigrationCookie(const GURL & app_url,const GURL & migrated_to_app_url,SuccessCallback callback)313 void AndroidSmsAppSetupControllerImpl::SetMigrationCookie(
314     const GURL& app_url,
315     const GURL& migrated_to_app_url,
316     SuccessCallback callback) {
317   // Set migration cookie on the client for which the PWA was just uninstalled.
318   // The client checks for this cookie to redirect users to the new domain. This
319   // prevents unwanted connection stealing between old and new clients should
320   // the user try to open old client.
321   net::CookieOptions options;
322   options.set_same_site_cookie_context(
323       net::CookieOptions::SameSiteCookieContext::MakeInclusive());
324   net::CanonicalCookie cookie = *net::CanonicalCookie::CreateSanitizedCookie(
325       app_url, kMigrationCookieName, migrated_to_app_url.GetContent(),
326       std::string() /* domain */, std::string() /* path */,
327       base::Time::Now() /* creation_time */, base::Time() /* expiration_time */,
328       base::Time::Now() /* last_access_time */,
329       !net::IsLocalhost(app_url) /* secure */, false /* http_only */,
330       net::CookieSameSite::STRICT_MODE, net::COOKIE_PRIORITY_DEFAULT,
331       false /* same_party */);
332   // TODO(crbug.com/1069974): The cookie source url must be faked here because
333   // otherwise, this would fail to set a secure cookie if |app_url| is insecure.
334   // Consider instead to use url::Replacements to force the scheme to be https.
335   pwa_delegate_->GetCookieManager(app_url, profile_)
336       ->SetCanonicalCookie(
337           cookie, net::cookie_util::SimulatedCookieSource(cookie, "https"),
338           options,
339           base::BindOnce(
340               &AndroidSmsAppSetupControllerImpl::OnSetMigrationCookieResult,
341               weak_ptr_factory_.GetWeakPtr(), app_url, std::move(callback)));
342 }
343 
OnSetMigrationCookieResult(const GURL & app_url,SuccessCallback callback,net::CookieAccessResult result)344 void AndroidSmsAppSetupControllerImpl::OnSetMigrationCookieResult(
345     const GURL& app_url,
346     SuccessCallback callback,
347     net::CookieAccessResult result) {
348   if (!result.status.IsInclude()) {
349     PA_LOG(ERROR)
350         << "AndroidSmsAppSetupControllerImpl::OnSetMigrationCookieResult(): "
351         << "Failed to set migration cookie for " << app_url << ". Proceeding "
352         << "to remove DefaultToPersist cookie.";
353   }
354 
355   DeleteRememberDeviceByDefaultCookie(app_url, std::move(callback));
356 }
357 
358 void AndroidSmsAppSetupControllerImpl::
OnDeleteRememberDeviceByDefaultCookieResult(const GURL & app_url,SuccessCallback callback,uint32_t num_deleted)359     OnDeleteRememberDeviceByDefaultCookieResult(const GURL& app_url,
360                                                 SuccessCallback callback,
361                                                 uint32_t num_deleted) {
362   if (num_deleted != 1u) {
363     PA_LOG(WARNING) << "AndroidSmsAppSetupControllerImpl::"
364                     << "OnDeleteRememberDeviceByDefaultCookieResult(): "
365                     << "Tried to delete a single cookie at " << app_url
366                     << ", but " << num_deleted << " cookies were deleted.";
367   }
368 
369   // Even if an unexpected number of cookies was deleted, consider this a
370   // success. If SetUpApp() failed to install a cookie earlier, the setup
371   // process is still considered a success, so failing to delete a cookie should
372   // also be considered a success.
373   std::move(callback).Run(true /* success */);
374 }
375 
SetPwaDelegateForTesting(std::unique_ptr<PwaDelegate> test_pwa_delegate)376 void AndroidSmsAppSetupControllerImpl::SetPwaDelegateForTesting(
377     std::unique_ptr<PwaDelegate> test_pwa_delegate) {
378   pwa_delegate_ = std::move(test_pwa_delegate);
379 }
380 
381 }  // namespace android_sms
382 
383 }  // namespace chromeos
384