chromium/chrome/browser/android/browserservices/verification/java/src/org/chromium/chrome/browser/browserservices/verification/ChromeOriginVerifierTest.java

// Copyright 2015 The Chromium Authors
// 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.verification;

import android.util.Pair;

import androidx.browser.customtabs.CustomTabsService;
import androidx.test.filters.SmallTest;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.components.content_relationship_verification.OriginVerifier.OriginVerificationListener;
import org.chromium.components.embedder_support.util.Origin;
import org.chromium.components.externalauth.ExternalAuthUtils;
import org.chromium.content_public.browser.test.mock.MockWebContents;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/** Tests for ChromeOriginVerifier. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@Batch(ChromeOriginVerifierTest.TEST_BATCH_NAME)
public class ChromeOriginVerifierTest {
    public static final String TEST_BATCH_NAME = "chrome_origin_verifier";

    @Rule
    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();

    private static final long TIMEOUT_MS = 1000;

    private static final String PACKAGE_NAME =
            ContextUtils.getApplicationContext().getPackageName();

    private final ChromeOriginVerifierFactory mFactory = new ChromeOriginVerifierFactoryImpl();

    private Origin mHttpsOrigin;
    private Origin mHttpOrigin;
    private TestExternalAuthUtils mExternalAuthUtils;

    private class TestExternalAuthUtils extends ExternalAuthUtils {
        private List<Pair<String, Origin>> mAllowlist = new ArrayList<>();

        public void addToAllowlist(String packageName, Origin origin) {
            mAllowlist.add(Pair.create(packageName, origin));
        }

        @Override
        public boolean isAllowlistedForTwaVerification(String packageName, Origin origin) {
            return mAllowlist.contains(Pair.create(packageName, origin));
        }
    }

    private class TestOriginVerificationListener implements OriginVerificationListener {
        @Override
        public void onOriginVerified(
                String packageName, Origin origin, boolean verified, Boolean online) {
            mLastPackageName = packageName;
            mLastOrigin = origin;
            mLastVerified = verified;
            mVerificationResultSemaphore.release();
        }
    }

    private Semaphore mVerificationResultSemaphore;
    private ChromeOriginVerifier mUseAsOriginVerifier;
    private ChromeOriginVerifier mHandleAllUrlsVerifier;
    private volatile String mLastPackageName;
    private volatile Origin mLastOrigin;
    private volatile boolean mLastVerified;

    @Before
    public void setUp() throws Exception {
        mActivityTestRule.startMainActivityOnBlankPage();

        mHttpsOrigin = Origin.create("https://www.example.com");
        mHttpOrigin = Origin.create("http://www.android.com");

        mHandleAllUrlsVerifier =
                mFactory.create(
                        PACKAGE_NAME,
                        CustomTabsService.RELATION_HANDLE_ALL_URLS,
                        new MockWebContents(),
                        null);
        mUseAsOriginVerifier =
                mFactory.create(
                        PACKAGE_NAME,
                        CustomTabsService.RELATION_USE_AS_ORIGIN,
                        /* webContents= */ null,
                        null);
        mVerificationResultSemaphore = new Semaphore(0);

        mExternalAuthUtils = new TestExternalAuthUtils();
    }

    @Test
    @SmallTest
    public void testOnlyHttpsAllowed() throws InterruptedException {
        PostTask.postTask(
                TaskTraits.UI_DEFAULT,
                () ->
                        mHandleAllUrlsVerifier.start(
                                new TestOriginVerificationListener(), mHttpOrigin));
        Assert.assertTrue(
                mVerificationResultSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
        Assert.assertFalse(mLastVerified);
    }

    @Test
    @SmallTest
    public void testMultipleRelationships() throws Exception {
        PostTask.postTask(
                TaskTraits.UI_DEFAULT,
                () ->
                        ChromeOriginVerifier.addVerificationOverride(
                                PACKAGE_NAME,
                                mHttpsOrigin,
                                CustomTabsService.RELATION_USE_AS_ORIGIN));
        PostTask.postTask(
                TaskTraits.UI_DEFAULT,
                () ->
                        mUseAsOriginVerifier.start(
                                new TestOriginVerificationListener(), mHttpsOrigin));
        Assert.assertTrue(
                mVerificationResultSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
        Assert.assertTrue(mLastVerified);
        Assert.assertTrue(
                ThreadUtils.runOnUiThreadBlocking(
                        () ->
                                ChromeOriginVerifier.wasPreviouslyVerified(
                                        PACKAGE_NAME,
                                        mHttpsOrigin,
                                        CustomTabsService.RELATION_USE_AS_ORIGIN)));
        Assert.assertTrue(
                ThreadUtils.runOnUiThreadBlocking(
                        () -> mUseAsOriginVerifier.wasPreviouslyVerified(mHttpsOrigin)));

        Assert.assertFalse(
                ThreadUtils.runOnUiThreadBlocking(
                        () ->
                                ChromeOriginVerifier.wasPreviouslyVerified(
                                        PACKAGE_NAME,
                                        mHttpsOrigin,
                                        CustomTabsService.RELATION_HANDLE_ALL_URLS)));

        Assert.assertEquals(mLastPackageName, PACKAGE_NAME);
        Assert.assertEquals(mLastOrigin, mHttpsOrigin);
    }

    @Test
    @SmallTest
    public void testVerificationBypass() throws InterruptedException {
        ChromeOriginVerifier verifier =
                mFactory.create(
                        PACKAGE_NAME,
                        CustomTabsService.RELATION_HANDLE_ALL_URLS,
                        null,
                        mExternalAuthUtils);

        PostTask.postTask(
                TaskTraits.UI_DEFAULT,
                () -> verifier.start(new TestOriginVerificationListener(), mHttpsOrigin));
        Assert.assertTrue(
                mVerificationResultSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
        Assert.assertFalse(mLastVerified);

        // Try again, but this time allowlist the package/origin.
        mExternalAuthUtils.addToAllowlist(PACKAGE_NAME, mHttpsOrigin);
        PostTask.postTask(
                TaskTraits.UI_DEFAULT,
                () -> verifier.start(new TestOriginVerificationListener(), mHttpsOrigin));
        Assert.assertTrue(
                mVerificationResultSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));

        Assert.assertTrue(mLastVerified);
    }

    @Test
    @SmallTest
    public void testRelationToRelationship() throws InterruptedException {
        Assert.assertEquals(
                ChromeOriginVerifier.relationToRelationship(
                        CustomTabsService.RELATION_USE_AS_ORIGIN),
                "delegate_permission/common.use_as_origin");
        Assert.assertEquals(
                ChromeOriginVerifier.relationToRelationship(
                        CustomTabsService.RELATION_HANDLE_ALL_URLS),
                "delegate_permission/common.handle_all_urls");
    }

    @Test
    @SmallTest
    public void testIsAllowlisted() throws InterruptedException {
        ChromeOriginVerifier verifier =
                mFactory.create(
                        PACKAGE_NAME,
                        CustomTabsService.RELATION_HANDLE_ALL_URLS,
                        null,
                        mExternalAuthUtils);
        Assert.assertFalse(
                verifier.isAllowlisted(
                        "no.existing.package",
                        Origin.create("https://not.exist.com"),
                        "delegate_permission/common.handle_all_urls"));
        mExternalAuthUtils.addToAllowlist(PACKAGE_NAME, mHttpsOrigin);
        Assert.assertFalse(
                verifier.isAllowlisted(
                        PACKAGE_NAME, mHttpsOrigin, "delegate_permission/common.use_as_origin"));
        Assert.assertTrue(
                verifier.isAllowlisted(
                        PACKAGE_NAME, mHttpsOrigin, "delegate_permission/common.handle_all_urls"));

        ChromeOriginVerifier verifierNoAuth =
                mFactory.create(
                        PACKAGE_NAME, CustomTabsService.RELATION_HANDLE_ALL_URLS, null, null);
        Assert.assertFalse(
                verifierNoAuth.isAllowlisted(
                        PACKAGE_NAME, mHttpsOrigin, "delegate_permission/common.handle_all_urls"));
    }
}