Tests for issue #2845673: android:exported="false" is not obeyed

Add more tests for content provider access, including tests that
granting URIs is working correctly for both permissions and
private providers.

Change-Id: I6e0f7f11ad19dd3866682576ad6397287edf75ec
diff --git a/tests/appsecurity-tests/test-apps/PermissionDeclareApp/AndroidManifest.xml b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/AndroidManifest.xml
index 53ab2cf..b8b8ce6 100644
--- a/tests/appsecurity-tests/test-apps/PermissionDeclareApp/AndroidManifest.xml
+++ b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/AndroidManifest.xml
@@ -24,12 +24,42 @@
     <permission android:name="com.android.cts.permissionWithSignature"
         android:protectionLevel="signature" />
 
+    <uses-permission android:name="com.android.cts.permissionWithSignature" />
+
     <application>
+        <receiver android:name="GrantUriPermission" android:exported="true">
+        </receiver>
+
         <!-- Need a way for another app to try to access the permission. So create a content
         provider which is enforced by the permission -->
         <provider android:name="PermissionContentProvider"
                 android:authorities="ctspermissionwithsignature"
                 android:readPermission="com.android.cts.permissionWithSignature"
-                android:writePermission="com.android.cts.permissionWithSignature" />
+                android:writePermission="com.android.cts.permissionWithSignature">
+            <grant-uri-permission android:pathPattern=".*" />
+        </provider>
+
+        <!-- Need a way for another app to try to access the permission, but will
+             grant uri access. -->
+        <provider android:name="PermissionContentProviderGranting"
+                android:authorities="ctspermissionwithsignaturegranting"
+                android:readPermission="com.android.cts.permissionWithSignature"
+                android:writePermission="com.android.cts.permissionWithSignature">
+            <grant-uri-permission android:pathPattern=".*" />
+        </provider>
+
+        <!-- Nobody else should get access to this -->
+        <provider android:name="PrivateContentProvider"
+                android:authorities="ctsprivateprovider"
+                android:exported="false">
+            <grant-uri-permission android:pathPattern=".*" />
+        </provider>
+
+        <!-- Nobody else should get access to this, but we will grant uri access -->
+        <provider android:name="PrivateContentProviderGranting"
+                android:authorities="ctsprivateprovidergranting"
+                android:exported="false">
+            <grant-uri-permission android:pathPattern=".*" />
+        </provider>
     </application>
 </manifest>
diff --git a/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java
new file mode 100644
index 0000000..2403d63
--- /dev/null
+++ b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java
@@ -0,0 +1,14 @@
+package com.android.cts.permissiondeclareapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class GrantUriPermission extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Intent newIntent = (Intent)intent.getParcelableExtra("intent");
+        newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(newIntent);
+    }
+}
diff --git a/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PermissionContentProviderGranting.java b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PermissionContentProviderGranting.java
new file mode 100644
index 0000000..46b1b3e
--- /dev/null
+++ b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PermissionContentProviderGranting.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009 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.permissiondeclareapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Empty content provider, all permissions are enforced in manifest
+ */
+public class PermissionContentProviderGranting extends ContentProvider {
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        // do nothing
+        return 0;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PrivateContentProvider.java b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PrivateContentProvider.java
new file mode 100644
index 0000000..dfbdbb7
--- /dev/null
+++ b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PrivateContentProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009 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.permissiondeclareapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Empty content provider, all permissions are enforced in manifest
+ */
+public class PrivateContentProvider extends ContentProvider {
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        // do nothing
+        return 0;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PrivateContentProviderGranting.java b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PrivateContentProviderGranting.java
new file mode 100644
index 0000000..f81c27a
--- /dev/null
+++ b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PrivateContentProviderGranting.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009 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.permissiondeclareapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Empty content provider, all permissions are enforced in manifest
+ */
+public class PrivateContentProviderGranting extends ContentProvider {
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        // do nothing
+        return 0;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/AndroidManifest.xml b/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/AndroidManifest.xml
index b915ebc..57db903 100644
--- a/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/AndroidManifest.xml
+++ b/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/AndroidManifest.xml
@@ -25,6 +25,7 @@
 
     <application>
         <uses-library android:name="android.test.runner"/>
+        <activity android:name=".ReceiveUriActivity" android:exported="true" />
     </application>
 
     <instrumentation android:targetPackage="com.android.cts.usespermissiondiffcertapp"
diff --git a/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java b/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
index 510d5e4..3e8c4bb 100644
--- a/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
+++ b/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
@@ -16,21 +16,180 @@
 
 package com.android.cts.usespermissiondiffcertapp;
 
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Intent;
 import android.net.Uri;
 import android.test.AndroidTestCase;
+import android.util.Log;
 
 /**
  * Tests that signature-enforced permissions cannot be accessed by apps signed
  * with different certs than app that declares the permission.
  */
 public class AccessPermissionWithDiffSigTest extends AndroidTestCase {
+    static final ComponentName GRANT_URI_PERM_COMP
+            = new ComponentName("com.android.cts.permissiondeclareapp",
+                    "com.android.cts.permissiondeclareapp.GrantUriPermission");
+    static final Uri PERM_URI = Uri.parse("content://ctspermissionwithsignature");
+    static final Uri PERM_URI_GRANTING = Uri.parse("content://ctspermissionwithsignaturegranting");
+    static final Uri PRIV_URI = Uri.parse("content://ctsprivateprovider");
+    static final Uri PRIV_GRANTING_URI = Uri.parse("content://ctsprivateprovidergranting");
+
+    public void assertReadingContentUriNotAllowed(Uri uri, String msg) {
+        try {
+            getContext().getContentResolver().query(uri, null, null, null, null);
+            fail("expected SecurityException reading " + uri + ": " + msg);
+        } catch (SecurityException expected) {
+            assertNotNull("security exception's error message.", expected.getMessage());
+        }
+    }
+
+    public void assertWritingContentUriNotAllowed(Uri uri, String msg) {
+        try {
+            getContext().getContentResolver().insert(uri, new ContentValues());
+            fail("expected SecurityException writing " + uri + ": " + msg);
+        } catch (SecurityException expected) {
+            assertNotNull("security exception's error message.", expected.getMessage());
+        }
+    }
 
     /**
      * Test that the ctspermissionwithsignature content provider cannot be read,
      * since this app lacks the required certs
      */
     public void testReadProviderWithDiff() {
-        assertReadingContentUriRequiresPermission(Uri.parse("content://ctspermissionwithsignature"),
+        assertReadingContentUriRequiresPermission(PERM_URI,
                 "com.android.cts.permissionWithSignature");
     }
+
+    /**
+     * Test that the ctspermissionwithsignature content provider cannot be written,
+     * since this app lacks the required certs
+     */
+    public void testWriteProviderWithDiff() {
+        assertWritingContentUriRequiresPermission(PERM_URI,
+                "com.android.cts.permissionWithSignature");
+    }
+
+    /**
+     * Test that the ctsprivateprovider content provider cannot be read,
+     * since it is not exported from its app.
+     */
+    public void testReadProviderWhenPrivate() {
+        assertReadingContentUriNotAllowed(PRIV_URI,
+                "shouldn't read private provider");
+    }
+
+    /**
+     * Test that the ctsprivateprovider content provider cannot be written,
+     * since it is not exported from its app.
+     */
+    public void testWriteProviderWhenPrivate() {
+        assertWritingContentUriNotAllowed(PRIV_URI,
+                "shouldn't write private provider");
+    }
+
+    void doTestGrantUriReadPermission(Uri uri) {
+        final Uri subUri = Uri.withAppendedPath(uri, "foo");
+        final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
+
+        Intent grantIntent = new Intent();
+        grantIntent.setData(subUri);
+        grantIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        grantIntent.setClass(getContext(), ReceiveUriActivity.class);
+        Intent intent = new Intent();
+        intent.setComponent(GRANT_URI_PERM_COMP);
+        intent.putExtra("intent", grantIntent);
+
+        ReceiveUriActivity.clearStarted();
+        getContext().sendBroadcast(intent);
+        ReceiveUriActivity.waitForStart();
+
+        // See if we now have access to the provider.
+        getContext().getContentResolver().query(subUri, null, null, null, null);
+
+        // But not writing.
+        assertWritingContentUriNotAllowed(subUri, "shouldn't write from granted read");
+
+        // And not to the base path.
+        assertReadingContentUriNotAllowed(uri, "shouldn't read non-granted base URI");
+
+        // And not to a sub path.
+        assertReadingContentUriNotAllowed(subSubUri, "shouldn't read non-granted sub URI");
+
+        // Dispose of activity.
+        ReceiveUriActivity.finishCurInstanceSync();
+
+        // Ensure reading no longer allowed.
+        assertReadingContentUriNotAllowed(subUri, "shouldn't read after losing granted URI");
+
+    }
+
+    void doTestGrantUriWritePermission(Uri uri) {
+        final Uri subUri = Uri.withAppendedPath(uri, "foo");
+        final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
+
+        Intent grantIntent = new Intent();
+        grantIntent.setData(subUri);
+        grantIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        grantIntent.setClass(getContext(), ReceiveUriActivity.class);
+        Intent intent = new Intent();
+        intent.setComponent(GRANT_URI_PERM_COMP);
+        intent.putExtra("intent", grantIntent);
+
+        ReceiveUriActivity.clearStarted();
+        getContext().sendBroadcast(intent);
+        ReceiveUriActivity.waitForStart();
+
+        // See if we now have access to the provider.
+        getContext().getContentResolver().insert(subUri, new ContentValues());
+
+        // But not reading.
+        assertReadingContentUriNotAllowed(subUri, "shouldn't read from granted read");
+
+        // And not to the base path.
+        assertWritingContentUriNotAllowed(uri, "shouldn't write non-granted base URI");
+
+        // And not a sub-path.
+        assertWritingContentUriNotAllowed(subSubUri, "shouldn't write non-granted sub URI");
+
+        // Dispose of activity.
+        ReceiveUriActivity.finishCurInstanceSync();
+
+        // Ensure reading no longer allowed.
+        assertWritingContentUriNotAllowed(subUri, "shouldn't write after losing granted URI");
+    }
+
+    /**
+     * Test that the ctspermissionwithsignaturegranting content provider can grant a read
+     * permission.
+     */
+    public void testGrantReadPermissionFromStartActivity() {
+        doTestGrantUriReadPermission(PERM_URI_GRANTING);
+    }
+
+    /**
+     * Test that the ctspermissionwithsignaturegranting content provider can grant a write
+     * permission.
+     */
+    public void testGrantWritePermissionFromStartActivity() {
+        doTestGrantUriWritePermission(PERM_URI_GRANTING);
+    }
+
+    /**
+     * Test that the ctsprivateprovidergranting content provider can grant a read
+     * permission.
+     */
+    public void testGrantReadPrivateFromStartActivity() {
+        doTestGrantUriReadPermission(PRIV_GRANTING_URI);
+    }
+
+    /**
+     * Test that the ctsprivateprovidergranting content provider can grant a write
+     * permission.
+     */
+    public void testGrantWritePrivateFromStartActivity() {
+        doTestGrantUriWritePermission(PRIV_GRANTING_URI);
+    }
 }
diff --git a/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ReceiveUriActivity.java b/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ReceiveUriActivity.java
new file mode 100644
index 0000000..074d8ea
--- /dev/null
+++ b/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ReceiveUriActivity.java
@@ -0,0 +1,127 @@
+package com.android.cts.usespermissiondiffcertapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.MessageQueue.IdleHandler;
+
+public class ReceiveUriActivity extends Activity {
+    private static final Object sLock = new Object();
+    private static boolean sStarted;
+    private static boolean sNewIntent;
+    private static boolean sDestroyed;
+    private static ReceiveUriActivity sCurInstance;
+
+    Handler mHandler = new Handler();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        synchronized (sLock) {
+            if (sCurInstance != null) {
+                finishCurInstance();
+            }
+            sCurInstance = this;
+            sStarted = true;
+            sDestroyed = false;
+            sLock.notifyAll();
+        }
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+
+        synchronized (sLock) {
+            sNewIntent = true;
+            sLock.notifyAll();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        Looper.myQueue().addIdleHandler(new IdleHandler() {
+            @Override
+            public boolean queueIdle() {
+                synchronized (sLock) {
+                    sDestroyed = true;
+                    sLock.notifyAll();
+                }
+                return false;
+            }
+        });
+    }
+
+    public static void finishCurInstance() {
+        synchronized (sLock) {
+            if (sCurInstance != null) {
+                sCurInstance.finish();
+                sCurInstance = null;
+            }
+        }
+    }
+
+    public static void finishCurInstanceSync() {
+        finishCurInstance();
+
+        synchronized (sLock) {
+            final long startTime = SystemClock.uptimeMillis();
+            while (!sDestroyed) {
+                try {
+                    sLock.wait(5000);
+                } catch (InterruptedException e) {
+                }
+                if (SystemClock.uptimeMillis() >= (startTime+5000)) {
+                    throw new RuntimeException("Timeout");
+                }
+            }
+        }
+    }
+
+    public static void clearStarted() {
+        synchronized (sLock) {
+            sStarted = false;
+        }
+    }
+
+    public static void clearNewIntent() {
+        synchronized (sLock) {
+            sNewIntent = false;
+        }
+    }
+
+    public static void waitForStart() {
+        synchronized (sLock) {
+            final long startTime = SystemClock.uptimeMillis();
+            while (!sStarted) {
+                try {
+                    sLock.wait(5000);
+                } catch (InterruptedException e) {
+                }
+                if (SystemClock.uptimeMillis() >= (startTime+5000)) {
+                    throw new RuntimeException("Timeout");
+                }
+            }
+        }
+    }
+
+    public static void waitForNewIntent() {
+        synchronized (sLock) {
+            final long startTime = SystemClock.uptimeMillis();
+            while (!sNewIntent) {
+                try {
+                    sLock.wait(5000);
+                } catch (InterruptedException e) {
+                }
+                if (SystemClock.uptimeMillis() >= (startTime+5000)) {
+                    throw new RuntimeException("Timeout");
+                }
+            }
+        }
+    }
+}