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