Commit 38ae8d49 authored by Peter E Conn's avatar Peter E Conn Committed by Commit Bot

📎 Split TWA specific code from TrustedWebActivityVerifier.

In this first step we split TrustedWebActivityVerifier up into:
- TrustedWebActivityVerifier, which contains the logic for checking
  whether the current url is verified at the appropriate times (eg
  page load, tab switching) and notifying the outside world.
- TwaVerifiedDelegate, which contains the logic specific to TWAs,
  primarily gathering the origins the client app claims it is verified
  for and running Digital Asset Link verification.
- TwaRegistrar, which essentially contains the logic from
  TrustedWebActivtyVerifier#registerClientAppForOrigin

The next steps are:
- Rename TrustedWebActivityVerifier to just Verifier (I didn't do this
  in this CL to make the code review easier).
- Make Verifier agnostic to whether we verify for origins or scopes.
- Moving the TwaRegistrar dependency from Verifier to the
  TWACoordinator.
- Removing the knowledge about the client package name from the
  Verifier - the Verifier shouldn't be the source of truth for this.

Bug: 1017114
Change-Id: If6e508ed99130e0fbbc0c5fd148f65f1a0cae66d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1875750Reviewed-by: default avatarPeter Beverloo <peter@chromium.org>
Reviewed-by: default avatarPeter Kotwicz <pkotwicz@chromium.org>
Commit-Queue: Peter Conn <peconn@chromium.org>
Cr-Commit-Position: refs/heads/master@{#709096}
parent cbd845c9
......@@ -169,6 +169,9 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityDisclosureController.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityOpenTimeRecorder.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityVerifier.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TwaRegistrar.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TwaVerifierDelegate.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/VerifierDelegate.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/sharing/TwaSharingController.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/splashscreen/SplashImageHolder.java",
"java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/splashscreen/TwaSplashController.java",
......
......@@ -30,6 +30,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/browserservices/permissiondelegation/NotificationChannelPreserverTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/permissiondelegation/NotificationPermissionUpdaterTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/ClientAppDataRecorderTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TestVerifierDelegate.java",
"junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityDisclosureControllerTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityOpenTimeRecorderTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityVerifierTest.java",
......
......@@ -4,37 +4,23 @@
package org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller;
import android.os.Bundle;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsService;
import org.chromium.base.ContextUtils;
import org.chromium.base.Callback;
import org.chromium.base.ObserverList;
import org.chromium.base.Promise;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.browserservices.Origin;
import org.chromium.chrome.browser.browserservices.OriginVerifier;
import org.chromium.chrome.browser.browserservices.TrustedWebActivityClient;
import org.chromium.chrome.browser.browserservices.permissiondelegation.NotificationPermissionUpdater;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.TrustedWebActivityModel;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
import org.chromium.chrome.browser.customtabs.content.CustomTabActivityTabProvider;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.Destroyable;
import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
import org.chromium.chrome.browser.lifecycle.SaveInstanceStateObserver;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tab.TabObserverRegistrar;
import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.content_public.browser.WebContents;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -44,34 +30,27 @@ import java.util.Set;
import javax.inject.Inject;
import dagger.Lazy;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Checks whether the currently seen web page belongs to a verified origin and updates the
* {@link TrustedWebActivityModel} accordingly.
*
* TODO(peconn): Make this class work with both Origins and Scopes (for WebAPK unificiation).
*/
@ActivityScope
public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyable,
SaveInstanceStateObserver {
/** The Digital Asset Link relationship used for Trusted Web Activities. */
private static final int RELATIONSHIP = CustomTabsService.RELATION_HANDLE_ALL_URLS;
/** Used in activity instance state */
private static final String KEY_CLIENT_PACKAGE = "twaClientPackageName";
private final Lazy<ClientAppDataRecorder> mClientAppDataRecorder;
private final CustomTabsConnection mCustomTabsConnection;
private final CustomTabIntentDataProvider mIntentDataProvider;
public class TrustedWebActivityVerifier implements NativeInitObserver {
private final CustomTabActivityTabProvider mTabProvider;
private final TabObserverRegistrar mTabObserverRegistrar;
private final String mClientPackageName;
private final OriginVerifier mOriginVerifier;
private final NotificationPermissionUpdater mNotificationPermissionUpdater;
private final BrowserServicesIntentDataProvider mIntentDataProvider;
private final VerifierDelegate mDelegate;
// TODO(peconn): Move this up into the coordinator.
private final TwaRegistrar mTwaRegistrar;
// These origins need to be verified via OriginVerifier#start, bypassing cache.
private final Set<Origin> mOriginsToVerify = new HashSet<>();
// These origins have already been registered so we don't need to do so again.
private final Set<Origin> mRegisteredOrigins = new HashSet<>();
@Nullable private VerificationState mState;
......@@ -122,34 +101,20 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab
};
@Inject
public TrustedWebActivityVerifier(Lazy<ClientAppDataRecorder> clientAppDataRecorder,
CustomTabIntentDataProvider intentDataProvider,
CustomTabsConnection customTabsConnection,
public TrustedWebActivityVerifier(
ActivityLifecycleDispatcher lifecycleDispatcher,
TabObserverRegistrar tabObserverRegistrar,
OriginVerifier.Factory originVerifierFactory,
CustomTabActivityTabProvider tabProvider,
ChromeActivity activity,
NotificationPermissionUpdater permissionUpdater) {
mClientAppDataRecorder = clientAppDataRecorder;
mCustomTabsConnection = customTabsConnection;
mIntentDataProvider = intentDataProvider;
CustomTabIntentDataProvider intentDataProvider,
VerifierDelegate delegate,
TwaRegistrar twaRegistrar) {
// TODO(peconn): Change the CustomTabIntentDataProvider to a BrowserServices... once
// https://chromium-review.googlesource.com/c/chromium/src/+/1877600 has landed.
mTabProvider = tabProvider;
mTabObserverRegistrar = tabObserverRegistrar;
mNotificationPermissionUpdater = permissionUpdater;
Bundle savedInstanceState = activity.getSavedInstanceState();
if (savedInstanceState != null) {
mClientPackageName = savedInstanceState.getString(KEY_CLIENT_PACKAGE);
} else {
mClientPackageName = customTabsConnection.getClientPackageNameForSession(
intentDataProvider.getSession());
}
assert mClientPackageName != null;
WebContents webContents =
tabProvider.getTab() != null ? tabProvider.getTab().getWebContents() : null;
mOriginVerifier =
originVerifierFactory.create(mClientPackageName, RELATIONSHIP, webContents);
mIntentDataProvider = intentDataProvider;
mDelegate = delegate;
mTwaRegistrar = twaRegistrar;
tabObserverRegistrar.registerTabObserver(mVerifyOnPageLoadObserver);
tabProvider.addObserver(mVerifyOnTabSwitchObserver);
......@@ -160,12 +125,14 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab
* @return package name of the client app hosting this Trusted Web Activity.
*/
public String getClientPackageName() {
return mClientPackageName;
// TODO(peconn): Remove this method.
return mDelegate.getClientPackageName();
}
/**
* @return the {@link VerificationState} of the origin we are currently in.
* Since resolving the origin requires native, returns null before native is loaded.
* TODO(peconn): Is there any reason to distinguish between null and PENDING?
*/
@Nullable
public VerificationState getState() {
......@@ -191,13 +158,6 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab
collectTrustedOrigins(initialOrigin);
verifyVisitedOrigin(initialOrigin);
// This doesn't belong here, but doesn't deserve a separate class. Do extract it if more
// PostMessage-related code appears.
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.TRUSTED_WEB_ACTIVITY_POST_MESSAGE)) {
mCustomTabsConnection.resetPostMessageHandlerForSession(
mIntentDataProvider.getSession(), null);
}
}
private void collectTrustedOrigins(Origin initialOrigin) {
......@@ -216,7 +176,7 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab
* verified for.
*/
public boolean isPageOnVerifiedOrigin(String url) {
return mOriginVerifier.wasPreviouslyVerified(new Origin(url));
return mDelegate.wasPreviouslyVerified(new Origin(url));
}
/**
......@@ -224,13 +184,11 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab
* Returns a {@link Promise<Boolean>} with boolean telling whether verification succeeded.
*/
public Promise<Boolean> verifyOrigin(String url) {
if (mOriginVerifier.wasPreviouslyVerified(new Origin(url))) {
Origin origin = new Origin(url);
if (mDelegate.wasPreviouslyVerified(origin)) {
return Promise.fulfilled(true);
}
Promise<Boolean> promise = new Promise<>();
mOriginVerifier.start((packageName, origin, verified, online) -> promise.fulfill(verified),
new Origin(url));
return promise;
return mDelegate.verify(origin);
}
/**
......@@ -240,11 +198,11 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab
if (mOriginsToVerify.contains(origin)) {
// Do verification bypassing the cache.
updateState(origin, VerificationStatus.PENDING);
mOriginVerifier.start((packageName2, origin2, verified, online) ->
onVerificationResult(origin, verified), origin);
mDelegate.verify(origin).then(
(Callback<Boolean>) verified -> onVerificationResult(origin, verified));
} else {
// Look into cache only
boolean verified = mOriginVerifier.wasPreviouslyVerified(origin);
boolean verified = mDelegate.wasPreviouslyVerified(origin);
updateState(origin, verified ? VerificationStatus.SUCCESS : VerificationStatus.FAILURE);
}
}
......@@ -265,7 +223,7 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab
private void updateState(Origin origin, @VerificationStatus int status) {
if (status == VerificationStatus.SUCCESS) {
registerClientAppForOrigin(origin);
mTwaRegistrar.registerClient(mDelegate.getClientPackageName(), origin);
}
mState = new VerificationState(origin, status);
......@@ -273,49 +231,4 @@ public class TrustedWebActivityVerifier implements NativeInitObserver, Destroyab
observer.run();
}
}
@Override
public void destroy() {
// Verification may finish after activity is destroyed.
mOriginVerifier.removeListener();
}
/**
* Registers to various stores that the client app is linked with the origin.
*
* We do this here, when the Trusted Web Activity UI is shown instead of in OriginVerifier when
* verification completes because when an origin is being verified, we don't know whether it is
* for the purposes of Trusted Web Activities or for Post Message (where this behaviour is not
* required).
*
* Additionally we do it on every page navigation because an app can be verified for more than
* one Origin, eg:
* 1) App verifies with https://www.myfirsttwa.com/.
* 2) App verifies with https://www.mysecondtwa.com/.
* 3) App launches a TWA to https://www.myfirsttwa.com/.
* 4) App navigates to https://www.mysecondtwa.com/.
*
* At step 2, we don't know why the app is verifying with that origin (it could be for TWAs or
* for PostMessage). Only at step 4 do we know that Chrome should associate the browsing data
* for that origin with that app.
*/
private void registerClientAppForOrigin(Origin origin) {
if (mRegisteredOrigins.contains(origin)) return;
// Register that we should wipe data for this origin when the client app is uninstalled.
mClientAppDataRecorder.get().register(mClientPackageName, origin);
// Register that we trust the client app to forward notifications from this origin to.
TrustedWebActivityClient.registerClient(
ContextUtils.getApplicationContext(), origin, mClientPackageName);
// Update Chrome's notification permission for the website to that of the client app.
mNotificationPermissionUpdater.onOriginVerified(origin, mClientPackageName);
mRegisteredOrigins.add(origin);
}
@Override
public void onSaveInstanceState(Bundle outState) {
// TODO(pshmakov): address this problem in a more general way, http://crbug.com/952221
outState.putString(KEY_CLIENT_PACKAGE, mClientPackageName);
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller;
import static org.chromium.chrome.browser.dependency_injection.ChromeCommonQualifiers.APP_CONTEXT;
import android.content.Context;
import org.chromium.chrome.browser.browserservices.Origin;
import org.chromium.chrome.browser.browserservices.TrustedWebActivityClient;
import org.chromium.chrome.browser.browserservices.permissiondelegation.NotificationPermissionUpdater;
import java.util.HashSet;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import dagger.Lazy;
/**
* Records in all the appropriate places that a TWA has successfully been verified.
*/
public class TwaRegistrar {
private final Context mAppContext;
private final NotificationPermissionUpdater mNotificationPermissionUpdater;
private final Lazy<ClientAppDataRecorder> mClientAppDataRecorder;
// These origins have already been registered so we don't need to do so again.
private final Set<Origin> mRegisteredOrigins = new HashSet<>();
@Inject
public TwaRegistrar(@Named(APP_CONTEXT) Context appContext,
NotificationPermissionUpdater permissionUpdater,
Lazy<ClientAppDataRecorder> clientAppDataRecorder) {
mAppContext = appContext;
mNotificationPermissionUpdater = permissionUpdater;
mClientAppDataRecorder = clientAppDataRecorder;
}
/**
* Registers to various stores that the client app is linked with the origin.
*
* We do this here, when the Trusted Web Activity UI is shown instead of in OriginVerifier when
* verification completes because when an origin is being verified, we don't know whether it is
* for the purposes of Trusted Web Activities or for Post Message (where this behaviour is not
* required).
*
* Additionally we do it on every page navigation because an app can be verified for more than
* one Origin, eg:
* 1) App verifies with https://www.myfirsttwa.com/.
* 2) App verifies with https://www.mysecondtwa.com/.
* 3) App launches a TWA to https://www.myfirsttwa.com/.
* 4) App navigates to https://www.mysecondtwa.com/.
*
* At step 2, we don't know why the app is verifying with that origin (it could be for TWAs or
* for PostMessage). Only at step 4 do we know that Chrome should associate the browsing data
* for that origin with that app.
*/
void registerClient(String packageName, Origin origin) {
if (mRegisteredOrigins.contains(origin)) return;
// Register that we should wipe data for this origin when the client app is uninstalled.
mClientAppDataRecorder.get().register(packageName, origin);
// Register that we trust the client app to forward notifications from this origin to.
TrustedWebActivityClient.registerClient(mAppContext, origin, packageName);
// Update Chrome's notification permission for the website to that of the client app.
mNotificationPermissionUpdater.onOriginVerified(origin, packageName);
mRegisteredOrigins.add(origin);
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller;
import android.os.Bundle;
import org.chromium.base.Promise;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.browserservices.Origin;
import org.chromium.chrome.browser.browserservices.OriginVerifier;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
import org.chromium.chrome.browser.customtabs.content.CustomTabActivityTabProvider;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.Destroyable;
import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
import org.chromium.chrome.browser.lifecycle.SaveInstanceStateObserver;
import org.chromium.content_public.browser.WebContents;
import javax.inject.Inject;
import androidx.browser.customtabs.CustomTabsService;
/**
* Provides Trusted Web Activity specific behaviour for the {@link TrustedWebActivityVerifier}.
*/
public class TwaVerifierDelegate implements VerifierDelegate, Destroyable, NativeInitObserver,
SaveInstanceStateObserver {
/** The Digital Asset Link relationship used for Trusted Web Activities. */
private static final int RELATIONSHIP = CustomTabsService.RELATION_HANDLE_ALL_URLS;
/** Used in activity instance state */
private static final String KEY_CLIENT_PACKAGE = "twaClientPackageName";
private final CustomTabsConnection mCustomTabsConnection;
private final CustomTabIntentDataProvider mIntentDataProvider;
private final OriginVerifier mOriginVerifier;
private final String mClientPackageName;
@Inject
public TwaVerifierDelegate(
ChromeActivity activity,
ActivityLifecycleDispatcher lifecycleDispatcher,
CustomTabIntentDataProvider intentDataProvider,
CustomTabsConnection customTabsConnection,
OriginVerifier.Factory originVerifierFactory,
CustomTabActivityTabProvider tabProvider) {
mCustomTabsConnection = customTabsConnection;
mIntentDataProvider = intentDataProvider;
Bundle savedInstanceState = activity.getSavedInstanceState();
if (savedInstanceState != null) {
mClientPackageName = savedInstanceState.getString(KEY_CLIENT_PACKAGE);
} else {
mClientPackageName = customTabsConnection.getClientPackageNameForSession(
intentDataProvider.getSession());
}
assert mClientPackageName != null;
// TODO(peconn): See if we can get rid of the dependency on Web Contents.
WebContents webContents =
tabProvider.getTab() != null ? tabProvider.getTab().getWebContents() : null;
mOriginVerifier =
originVerifierFactory.create(mClientPackageName, RELATIONSHIP, webContents);
lifecycleDispatcher.register(this);
}
@Override
public String getClientPackageName() {
return mClientPackageName;
}
@Override
public boolean wasPreviouslyVerified(Origin origin) {
return mOriginVerifier.wasPreviouslyVerified(origin);
}
@Override
public Promise<Boolean> verify(Origin origin) {
Promise<Boolean> promise = new Promise<>();
mOriginVerifier.start((packageName, unused, verified, online) -> promise.fulfill(verified),
origin);
return promise;
}
@Override
public void destroy() {
// Verification may finish after activity is destroyed.
mOriginVerifier.removeListener();
}
@Override
public void onSaveInstanceState(Bundle outState) {
// TODO(pshmakov): address this problem in a more general way, http://crbug.com/952221
outState.putString(KEY_CLIENT_PACKAGE, mClientPackageName);
}
@Override
public void onFinishNativeInitialization() {
// This doesn't belong here, but doesn't deserve a separate class. Do extract it if more
// PostMessage-related code appears.
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.TRUSTED_WEB_ACTIVITY_POST_MESSAGE)) {
mCustomTabsConnection.resetPostMessageHandlerForSession(
mIntentDataProvider.getSession(), null);
}
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller;
import org.chromium.base.Promise;
import org.chromium.chrome.browser.browserservices.Origin;
/**
* A Delegate for the {@link TrustedWebActivityVerifier} that provides implementation specific to
* Trusted Web Activities, WebAPKs or A2HS as appropriate.
*/
public interface VerifierDelegate {
// TODO(peconn): Make distinction between verify and wasPreviouslyVerified more clear.
/** Asynchronously checks whether the given Origin is verified. */
Promise<Boolean> verify(Origin origin);
/** Synchronously checks whether verification was successful for the given Origin. */
boolean wasPreviouslyVerified(Origin origin);
// TODO(peconn): Get rid of this method.
/** Gets the package name of the connected app. */
String getClientPackageName();
}
......@@ -6,6 +6,8 @@ package org.chromium.chrome.browser.customtabs.dependency_injection;
import org.chromium.chrome.browser.browserservices.ClientAppDataRegister;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.TwaIntentHandlingStrategy;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.TwaVerifierDelegate;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.VerifierDelegate;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.customtabs.CustomTabNightModeStateController;
import org.chromium.chrome.browser.customtabs.content.CustomTabIntentHandler.IntentIgnoringCriterion;
......@@ -61,6 +63,12 @@ public class CustomTabActivityModule {
return mIntentDataProvider.isTrustedWebActivity() ? twaHandler.get() : defaultHandler.get();
}
@Provides
public VerifierDelegate provideVerifierDelegate(Lazy<TwaVerifierDelegate> twaVerifierDelegate) {
// TODO(peconn): Add handing of WebAPK/A2HS delegate.
return twaVerifierDelegate.get();
}
@Provides
public IntentIgnoringCriterion provideIntentIgnoringCriterion() {
return mIntentIgnoringCriterion;
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller;
import org.chromium.base.Promise;
import org.chromium.chrome.browser.browserservices.Origin;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A {@link VerifierDelegate} for testing.
*/
class TestVerifierDelegate implements VerifierDelegate {
private final Set<Origin> mPreviouslyVerifiedOrigins = new HashSet<>();
private final Map<Origin, Promise<Boolean>> mPendingVerifications = new HashMap<>();
private final String mPackageName;
TestVerifierDelegate(String packageName) {
mPackageName = packageName;
}
@Override
public Promise<Boolean> verify(Origin origin) {
Promise<Boolean> promise = new Promise<>();
mPendingVerifications.put(origin, promise);
return promise;
}
@Override
public boolean wasPreviouslyVerified(Origin origin) {
return mPreviouslyVerifiedOrigins.contains(origin);
}
@Override
public String getClientPackageName() {
return mPackageName;
}
public void passVerification(Origin origin) {
completeVerification(origin, true);
}
public void failVerification(Origin origin) {
completeVerification(origin, false);
}
private void completeVerification(Origin origin, boolean success) {
if (mPendingVerifications.get(origin) == null) return;
mPendingVerifications.get(origin).fulfill(success);
mPendingVerifications.remove(origin);
if (success) mPreviouslyVerifiedOrigins.add(origin);
}
public boolean hasPendingVerification(Origin origin) {
return mPendingVerifications.containsKey(origin);
}
public boolean hasAnyPendingVerifications() {
return mPendingVerifications.size() != 0;
}
}
......@@ -5,15 +5,9 @@
package org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Before;
......@@ -28,15 +22,10 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.browserservices.Origin;
import org.chromium.chrome.browser.browserservices.OriginVerifier;
import org.chromium.chrome.browser.browserservices.OriginVerifier.OriginVerificationListener;
import org.chromium.chrome.browser.browserservices.permissiondelegation.NotificationPermissionUpdater;
import org.chromium.chrome.browser.browserservices.trustedwebactivityui.controller.TrustedWebActivityVerifier.VerificationStatus;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
import org.chromium.chrome.browser.customtabs.content.CustomTabActivityTabProvider;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.tab.Tab;
......@@ -47,12 +36,10 @@ import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.content_public.browser.NavigationHandle;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.Collections;
/**
* Tests for {@link TrustedWebActivityVerifier}.
* Tests for {@link Verifier}.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
......@@ -73,36 +60,27 @@ public class TrustedWebActivityVerifierTest {
@Rule
public TestRule mFeaturesProcessor = new Features.JUnitProcessor();
@Mock ClientAppDataRecorder mClientAppDataRecorder;
@Mock CustomTabsConnection mCustomTabsConnection;
@Mock CustomTabIntentDataProvider mIntentDataProvider;
@Mock TabObserverRegistrar mTabObserverRegistrar;
@Mock ActivityLifecycleDispatcher mLifecycleDispatcher;
@Mock OriginVerifier.Factory mOriginVerifierFactory;
@Mock CustomTabActivityTabProvider mTabProvider;
@Mock CustomTabIntentDataProvider mIntentDataProvider;
@Mock Tab mTab;
@Mock NotificationPermissionUpdater mNotificationPermissionUpdater;
@Mock ChromeActivity mChromeActivity;
@Mock TwaRegistrar mTwaRegistrar;
@Captor ArgumentCaptor<TabObserver> mTabObserverCaptor;
private final FakeOriginVerifier mOriginVerifier = new FakeOriginVerifier();
TestVerifierDelegate mVerifierDelegate = new TestVerifierDelegate(PACKAGE_NAME);
private TrustedWebActivityVerifier mVerifier;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mCustomTabsConnection.getClientPackageNameForSession(any())).thenReturn(PACKAGE_NAME);
when(mOriginVerifierFactory.create(any(), anyInt(), any()))
.thenReturn(mOriginVerifier.mock);
when(mTabProvider.getTab()).thenReturn(mTab);
when(mIntentDataProvider.getTrustedWebActivityAdditionalOrigins()).thenReturn(
Arrays.asList("https://www.origin2.com/"));
doNothing().when(mTabObserverRegistrar).registerTabObserver(mTabObserverCaptor.capture());
mVerifier = new TrustedWebActivityVerifier(() -> mClientAppDataRecorder,
mIntentDataProvider, mCustomTabsConnection, mLifecycleDispatcher,
mTabObserverRegistrar, mOriginVerifierFactory,
mTabProvider, mChromeActivity, mNotificationPermissionUpdater);
when(mIntentDataProvider.getTrustedWebActivityAdditionalOrigins())
.thenReturn(Collections.singletonList("https://www.origin2.com/"));
mVerifier = new TrustedWebActivityVerifier(mLifecycleDispatcher, mTabObserverRegistrar,
mTabProvider, mIntentDataProvider, mVerifierDelegate, mTwaRegistrar);
// TODO(peconn): Add check on permission updated being updated.
}
......@@ -124,7 +102,7 @@ public class TrustedWebActivityVerifierTest {
public void statusIsSuccess_WhenVerificationSucceeds() {
setInitialUrl(TRUSTED_ORIGIN_PAGE1);
mVerifier.onFinishNativeInitialization();
mOriginVerifier.finishCurrentVerification();
mVerifierDelegate.passVerification(new Origin(TRUSTED_ORIGIN_PAGE1));
assertStatus(VerificationStatus.SUCCESS);
}
......@@ -132,7 +110,7 @@ public class TrustedWebActivityVerifierTest {
public void statusIsFail_WhenVerificationFails() {
setInitialUrl(UNTRUSTED_PAGE);
mVerifier.onFinishNativeInitialization();
mOriginVerifier.finishCurrentVerification();
mVerifierDelegate.failVerification(new Origin(UNTRUSTED_PAGE));
assertStatus(VerificationStatus.FAILURE);
}
......@@ -140,9 +118,8 @@ public class TrustedWebActivityVerifierTest {
public void usesCache_whenNavigatingWithinPreviouslyVerifiedOrigin() {
setInitialUrl(TRUSTED_ORIGIN_PAGE1);
mVerifier.onFinishNativeInitialization();
mOriginVerifier.finishCurrentVerification();
mVerifierDelegate.passVerification(new Origin(TRUSTED_ORIGIN_PAGE1));
clearInvocations(mOriginVerifier.mock);
navigateToUrl(TRUSTED_ORIGIN_PAGE2);
verifyUsesCache();
}
......@@ -151,9 +128,8 @@ public class TrustedWebActivityVerifierTest {
public void verifies_WhenNavigatingToOtherTrustedOrigin() {
setInitialUrl(TRUSTED_ORIGIN_PAGE1);
mVerifier.onFinishNativeInitialization();
mOriginVerifier.finishCurrentVerification();
mVerifierDelegate.passVerification(new Origin(TRUSTED_ORIGIN_PAGE1));
clearInvocations(mOriginVerifier.mock);
navigateToUrl(OTHER_TRUSTED_ORIGIN_PAGE1);
verifyStartsVerification(OTHER_TRUSTED_ORIGIN_PAGE1);
}
......@@ -162,11 +138,10 @@ public class TrustedWebActivityVerifierTest {
public void usesCache_WhenReturningBackToFirstVerifiedOrigin() {
setInitialUrl(TRUSTED_ORIGIN_PAGE1);
mVerifier.onFinishNativeInitialization();
mOriginVerifier.finishCurrentVerification();
mVerifierDelegate.passVerification(new Origin(TRUSTED_ORIGIN_PAGE1));
navigateToUrl(OTHER_TRUSTED_ORIGIN_PAGE1);
mOriginVerifier.finishCurrentVerification();
mVerifierDelegate.passVerification(new Origin(OTHER_TRUSTED_ORIGIN_PAGE1));
clearInvocations(mOriginVerifier.mock);
navigateToUrl(TRUSTED_ORIGIN_PAGE2);
verifyUsesCache();
}
......@@ -175,12 +150,11 @@ public class TrustedWebActivityVerifierTest {
public void usesCache_WhenReturningBackToSecondVerifiedOrigin() {
setInitialUrl(TRUSTED_ORIGIN_PAGE1);
mVerifier.onFinishNativeInitialization();
mOriginVerifier.finishCurrentVerification();
mVerifierDelegate.passVerification(new Origin(TRUSTED_ORIGIN_PAGE1));
navigateToUrl(OTHER_TRUSTED_ORIGIN_PAGE1);
mOriginVerifier.finishCurrentVerification();
mVerifierDelegate.passVerification(new Origin(OTHER_TRUSTED_ORIGIN_PAGE1));
navigateToUrl(TRUSTED_ORIGIN_PAGE1);
clearInvocations(mOriginVerifier.mock);
navigateToUrl(OTHER_TRUSTED_ORIGIN_PAGE2);
verifyUsesCache();
}
......@@ -189,9 +163,8 @@ public class TrustedWebActivityVerifierTest {
public void usesCache_WhenNavigatesToUntrustedOrigin() {
setInitialUrl(TRUSTED_ORIGIN_PAGE1);
mVerifier.onFinishNativeInitialization();
mOriginVerifier.finishCurrentVerification();
mVerifierDelegate.passVerification(new Origin(TRUSTED_ORIGIN_PAGE1));
clearInvocations(mOriginVerifier.mock);
navigateToUrl(UNTRUSTED_PAGE);
verifyUsesCache();
}
......@@ -201,7 +174,8 @@ public class TrustedWebActivityVerifierTest {
setInitialUrl(TRUSTED_ORIGIN_PAGE1);
mVerifier.onFinishNativeInitialization();
navigateToUrl(UNTRUSTED_PAGE);
mOriginVerifier.finishCurrentVerification();
mVerifierDelegate.failVerification(new Origin(UNTRUSTED_PAGE));
assertStatus(VerificationStatus.FAILURE);
}
......@@ -210,9 +184,9 @@ public class TrustedWebActivityVerifierTest {
setInitialUrl(TRUSTED_ORIGIN_PAGE1);
mVerifier.onFinishNativeInitialization();
navigateToUrl(OTHER_TRUSTED_ORIGIN_PAGE1);
mOriginVerifier.finishCurrentVerification();
mVerifierDelegate.passVerification(new Origin(OTHER_TRUSTED_ORIGIN_PAGE1));
navigateToUrl(TRUSTED_ORIGIN_PAGE1);
mOriginVerifier.finishCurrentVerification();
mVerifierDelegate.passVerification(new Origin(TRUSTED_ORIGIN_PAGE1));
assertStatus(VerificationStatus.SUCCESS);
}
......@@ -221,12 +195,11 @@ public class TrustedWebActivityVerifierTest {
}
private void verifyUsesCache() {
verify(mOriginVerifier.mock).wasPreviouslyVerified(any());
verify(mOriginVerifier.mock, never()).start(any(), any());
assertFalse(mVerifierDelegate.hasAnyPendingVerifications());
}
private void verifyStartsVerification(String urlToVerify) {
verify(mOriginVerifier.mock).start(any(), argThat(new Origin(urlToVerify)::equals));
private void verifyStartsVerification(String url) {
assertTrue(mVerifierDelegate.hasPendingVerification(new Origin(url)));
}
private void setInitialUrl(String url) {
......@@ -251,46 +224,4 @@ public class TrustedWebActivityVerifierTest {
tabObserver.onDidFinishNavigation(mTab, navigation);
}
}
private static class FakeOriginVerifier {
public final OriginVerifier mock = mock(OriginVerifier.class);
private final Set<Origin> mPreviouslyVerifiedOrigins = new HashSet<>();
private OriginVerificationListener mCurrentListener;
private Origin mCurrentOrigin;
public FakeOriginVerifier() {
doAnswer(invocation -> {
start(invocation.getArgument(0), invocation.getArgument(1));
return null;
}).when(mock).start(any(), any());
doAnswer(invocation -> wasPreviouslyVerified(invocation.getArgument(0)))
.when(mock).wasPreviouslyVerified(any());
}
private void start(OriginVerificationListener listener, Origin origin) {
mCurrentListener = listener;
mCurrentOrigin = origin;
}
private boolean wasPreviouslyVerified(Origin origin) {
return mPreviouslyVerifiedOrigins.contains(origin);
}
public void finishCurrentVerification() {
if (mCurrentListener == null) {
return;
}
boolean verified = mCurrentOrigin.equals(TRUSTED_ORIGIN)
|| mCurrentOrigin.equals(OTHER_TRUSTED_ORIGIN);
if (verified) {
mPreviouslyVerifiedOrigins.add(mCurrentOrigin);
}
mCurrentListener.onOriginVerified(PACKAGE_NAME, mCurrentOrigin, verified, true);
mCurrentListener = null;
mCurrentOrigin = null;
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment