Adds test for session referrer uri masking

This change ensures that only the owner of an install session may view
its referrer URI.

Test: atest SessionReferrerUriTest
Bug: 142125338
Merged-In: I0500e198f5e53aac5b90534c8e438b847d78191c
Change-Id: I0500e198f5e53aac5b90534c8e438b847d78191c
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SessionReferrerUriTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SessionReferrerUriTest.java
new file mode 100644
index 0000000..7477721
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SessionReferrerUriTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appsecurity.cts;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class SessionReferrerUriTest extends BaseAppSecurityTest {
+
+    private static final String SESSION_INSPECTOR_A_APK = "CtsSessionInspectorAppA.apk";
+    private static final String SESSION_INSPECTOR_B_APK = "CtsSessionInspectorAppB.apk";
+    private static final String SESSION_INSPECTOR_PKG_A = "com.android.cts.sessioninspector.a";
+    private static final String SESSION_INSPECTOR_PKG_B = "com.android.cts.sessioninspector.b";
+
+    @Before
+    public void setup() throws Exception {
+        new InstallMultiple().addApk(SESSION_INSPECTOR_A_APK).run();
+        new InstallMultiple().addApk(SESSION_INSPECTOR_B_APK).run();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        getDevice().uninstallPackage(SESSION_INSPECTOR_PKG_A);
+        getDevice().uninstallPackage(SESSION_INSPECTOR_PKG_B);
+    }
+
+    @Test
+    @AppModeFull(reason = "Only full apps may install")
+    public void testSessionReferrerUriVisibleToOwner() throws DeviceNotAvailableException {
+        Utils.runDeviceTests(getDevice(), SESSION_INSPECTOR_PKG_A,
+                "com.android.cts.sessioninspector.SessionInspectorTest", "testOnlyOwnerCanSee");
+        Utils.runDeviceTests(getDevice(), SESSION_INSPECTOR_PKG_B,
+                "com.android.cts.sessioninspector.SessionInspectorTest", "testOnlyOwnerCanSee");
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SessionInspector/Android.bp b/hostsidetests/appsecurity/test-apps/SessionInspector/Android.bp
new file mode 100644
index 0000000..421c9de
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SessionInspector/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsSessionInspectorAppA",
+    defaults: ["cts_support_defaults"],
+    manifest: "AndroidManifestA.xml",
+    sdk_version: "test_current",
+    srcs: ["src/**/*.java"],
+    static_libs: ["androidx.test.rules"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
+android_test_helper_app {
+    name: "CtsSessionInspectorAppB",
+    defaults: ["cts_support_defaults"],
+    manifest: "AndroidManifestB.xml",
+    sdk_version: "test_current",
+    srcs: ["src/**/*.java"],
+    static_libs: ["androidx.test.rules"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SessionInspector/AndroidManifestA.xml b/hostsidetests/appsecurity/test-apps/SessionInspector/AndroidManifestA.xml
new file mode 100644
index 0000000..34aba10
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SessionInspector/AndroidManifestA.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+       package="com.android.cts.sessioninspector.a">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.sessioninspector.a" />
+
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="com.android.cts.sessioninspector.SessionInspectorActivity"
+                  android:exported="true"/>
+    </application>
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SessionInspector/AndroidManifestB.xml b/hostsidetests/appsecurity/test-apps/SessionInspector/AndroidManifestB.xml
new file mode 100644
index 0000000..324aa4e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SessionInspector/AndroidManifestB.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+       package="com.android.cts.sessioninspector.b">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.sessioninspector.b" />
+
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="com.android.cts.sessioninspector.SessionInspectorActivity"
+                  android:exported="true"/>
+    </application>
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SessionInspector/OWNERS b/hostsidetests/appsecurity/test-apps/SessionInspector/OWNERS
new file mode 100644
index 0000000..9d4a924
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SessionInspector/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 533114
+patb@google.com
+toddke@google.com
+chiuwinson@google.com
diff --git a/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/Constants.java b/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/Constants.java
new file mode 100644
index 0000000..6957448
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/Constants.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.sessioninspector;
+
+import android.net.Uri;
+
+public class Constants {
+    private static final String PKG_BASE = "com.android.cts.sessioninspector.";
+
+    public static final String ACTION_CREATE_SESSION = PKG_BASE + "CREATE_SESSION";
+    public static final String ACTION_GET_SESSION = PKG_BASE + "GET_SESSION";
+    public static final String ACTION_ABANDON_SESSION = PKG_BASE + "ABANDON_SESSION";
+
+    public static final String PACKAGE_A = PKG_BASE + "a";
+    public static final String PACKAGE_B = PKG_BASE + "b";
+
+    public static final String ACTIVITY_NAME = PKG_BASE + "SessionInspectorActivity";
+
+    public static final Uri REFERRER_URI = Uri.parse("https://user-sensitive-domain.com/");
+}
diff --git a/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/SessionInspectorActivity.java b/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/SessionInspectorActivity.java
new file mode 100644
index 0000000..edf90f2
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/SessionInspectorActivity.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.sessioninspector;
+
+import static android.content.Intent.EXTRA_RESULT_RECEIVER;
+import static android.content.pm.PackageInstaller.EXTRA_SESSION;
+import static android.content.pm.PackageInstaller.EXTRA_SESSION_ID;
+import static android.content.pm.PackageInstaller.SessionInfo;
+import static android.content.pm.PackageInstaller.SessionParams;
+import static android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL;
+
+import static com.android.cts.sessioninspector.Constants.ACTION_ABANDON_SESSION;
+import static com.android.cts.sessioninspector.Constants.ACTION_CREATE_SESSION;
+import static com.android.cts.sessioninspector.Constants.ACTION_GET_SESSION;
+import static com.android.cts.sessioninspector.Constants.REFERRER_URI;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+
+public class SessionInspectorActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        RemoteCallback remoteCallback = getIntent().getParcelableExtra(EXTRA_RESULT_RECEIVER);
+        final Bundle result = new Bundle();
+        String action = getIntent().getAction();
+        try {
+            switch (action) {
+                case ACTION_CREATE_SESSION:
+                    SessionParams params = new SessionParams(MODE_FULL_INSTALL);
+                    params.setReferrerUri(REFERRER_URI);
+                    final int session =
+                            getPackageManager().getPackageInstaller().createSession(params);
+                    result.putInt(EXTRA_SESSION_ID, session);
+                    break;
+                case ACTION_GET_SESSION: {
+                    final int sessionId = getIntent().getIntExtra(EXTRA_SESSION_ID, 0);
+                    final SessionInfo sessionInfo =
+                            getPackageManager().getPackageInstaller().getSessionInfo(sessionId);
+                    result.putParcelable(EXTRA_SESSION, sessionInfo);
+                    break;
+                }
+                case ACTION_ABANDON_SESSION: {
+                    final int sessionId = getIntent().getIntExtra(EXTRA_SESSION_ID, 0);
+                    getPackageManager().getPackageInstaller().abandonSession(sessionId);
+                    break;
+                }
+                default:
+                    throw new IllegalArgumentException("Unrecognized action: " + action);
+            }
+        } catch (Exception e) {
+            result.putSerializable("error", e);
+        }
+        remoteCallback.sendResult(result);
+        finish();
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/SessionInspectorTest.java b/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/SessionInspectorTest.java
new file mode 100644
index 0000000..8afcdef
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/SessionInspectorTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.sessioninspector;
+
+import static com.android.cts.sessioninspector.Constants.ACTION_ABANDON_SESSION;
+import static com.android.cts.sessioninspector.Constants.ACTION_CREATE_SESSION;
+import static com.android.cts.sessioninspector.Constants.ACTION_GET_SESSION;
+import static com.android.cts.sessioninspector.Constants.ACTIVITY_NAME;
+import static com.android.cts.sessioninspector.Constants.REFERRER_URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInstaller;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.RemoteCallback;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public class SessionInspectorTest {
+
+    @Test
+    public void testOnlyOwnerCanSee() throws Exception {
+        String myPackage = Constants.PACKAGE_A.equals(getContext().getPackageName())
+                ? Constants.PACKAGE_A : Constants.PACKAGE_B;
+        String otherPackage = Constants.PACKAGE_A.equals(myPackage) ? Constants.PACKAGE_B
+                : Constants.PACKAGE_A;
+
+        int sessionId = createSession(myPackage);
+
+        final PackageInstaller.SessionInfo sessionToMe = getSessionInfo(myPackage, sessionId);
+        final PackageInstaller.SessionInfo sessionToOther = getSessionInfo(otherPackage, sessionId);
+
+        abandonSession(myPackage, sessionId);
+
+        assertEquals(REFERRER_URI, sessionToMe.getReferrerUri());
+        assertNull(sessionToOther.getReferrerUri());
+    }
+
+    private Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+
+    private int createSession(String packageName) throws Exception {
+        Bundle result = sendCommand(new Intent(ACTION_CREATE_SESSION).setComponent(
+                new ComponentName(packageName, ACTIVITY_NAME)));
+        return result.getInt(PackageInstaller.EXTRA_SESSION_ID, 0);
+    }
+
+    private PackageInstaller.SessionInfo getSessionInfo(String packageName, int session)
+            throws Exception {
+        Bundle result = sendCommand(new Intent(ACTION_GET_SESSION).putExtra(
+                PackageInstaller.EXTRA_SESSION_ID, session).setComponent(
+                new ComponentName(packageName, ACTIVITY_NAME)));
+        return result.getParcelable(PackageInstaller.EXTRA_SESSION);
+    }
+
+    private void abandonSession(String packageName, int sessionId) throws Exception {
+        sendCommand(new Intent(ACTION_ABANDON_SESSION).putExtra(PackageInstaller.EXTRA_SESSION_ID,
+                sessionId).setComponent(new ComponentName(packageName, ACTIVITY_NAME)));
+    }
+
+    private Bundle sendCommand(Intent intent) throws Exception {
+        ConditionVariable condition = new ConditionVariable();
+        final Bundle[] resultHolder = new Bundle[1];
+        RemoteCallback callback = new RemoteCallback(result -> {
+            resultHolder[0] = result;
+            condition.open();
+        });
+        intent.putExtra(Intent.EXTRA_RESULT_RECEIVER, callback);
+        intent.setData(Uri.parse("https://" + UUID.randomUUID()));
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getContext().startActivity(intent);
+        condition.block(TimeUnit.SECONDS.toMillis(10));
+        Bundle result = resultHolder[0];
+        if (result.containsKey("error")) {
+            throw (Exception) result.getSerializable("error");
+        }
+        return result;
+    }
+}