11

I am trying to query custom content provider(App A) from another app(App B).

I can do that when there is no permission protection for content provider. Specifically, I build custom content provider on App A and sent an intent containing the URI to App B. Here is intent-sending part in App A.

class InsertOnClickListener implements OnClickListener{        
        public void onClick(View v) {
            ContentValues values = new ContentValues();
            values.put(DataBaseConfiguation.TableConfiguation.USER_NAME, "Jack");
            Uri uri = getContentResolver().insert(DataBaseConfiguation.TableConfiguation.CONTENT_URI, values);
            System.out.println("uri------------------->" + uri);
            // the uri above should be like "content://com.catking.contentprovider.MyContentProvider/user"
            Uri uri2 = Uri.parse("content://com.catking.contentprovider.MyContentProvider/user");
              Cursor c = managedQuery(uri2, null, null, null, null);
              String sendvalue = null;
               if (c.moveToFirst()) {
                  do{
                     System.out.println("User name:"+c.getString(c.getColumnIndex(DataBaseConfiguation.TableConfiguation.USER_NAME)).toString());               
                     sendvalue = c.getString(c.getColumnIndex(DataBaseConfiguation.TableConfiguation.USER_NAME)).toString();
                  } while (c.moveToNext());
               }
            Intent sendIntent = new Intent();
            sendIntent.setClassName("com.android.web", "com.android.web.Provid");
            sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            sendIntent.putExtra("name", uri2.toString());
            sendIntent.setType("text/plain");
            startActivity(sendIntent);
        }
}

followed by manifest file of App A.

<application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name" >
    <activity
        android:name=".ContentProviderTestActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <provider android:authorities="com.catking.contentprovider.MyContentProvider"
        android:exported="true"
        android:grantUriPermissions="true"
        android:name="com.catking.contentprovider.MyContentProvider" 
        android:readPermission="android.permission.permRead"
        android:writePermission="android.permission.permWrite" >
    </provider>
</application>

Then App B(class Provid) get the URI and query the corresponding data in content provider(using following code).

public class Provid extends Activity {

public void onCreate(Bundle savedInstanceState) {
    Bundle extras = getIntent().getExtras(); 
    String userNameuri;
    if (extras != null) {
        userNameuri = extras.getString("name");
      Uri allTitles = Uri.parse(userNameuri);
      Cursor c = managedQuery(allTitles, null, null, null, null);
       if (c.moveToFirst()) {
          do{
             System.out.println("Name is"+c.getString(c.getColumnIndex(DataBaseConfiguation.TableConfiguation.USER_NAME)).toString());               
          } while (c.moveToNext());
       }
    }
}

}

Here's App B's manifest file.

<uses-permission android:name="android.permission.INTERNET" />
<application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name" >
    <activity
        android:name="._GetWebResoureActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.SEND" >
            </action>
            <category android:name="android.intent.category.DEFAULT" />
            <data android:mimeType="*/*" />
        </intent-filter>
    </activity>

    <receiver android:name="StaticReceiver11" >
        <intent-filter>
            <action android:name="android.intent.action.MYSEND" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </receiver>

    <activity
        android:name="Provid"
        android:label="@string/title_activity_provid" >
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:mimeType="*/*" />
        </intent-filter>

    </activity>
</application>

However, when I query content provider from App B, errors occur:

java.lang.RuntimeException: Unable to start activity ComponentInfo {com.android.web/com.android.web.Provid}: java.lang.SecurityException: Permission Denial: opening     provider com.ck.contentprovider.MyContentProvider from ProcessRecord{426c6ea8 17032:com.android.web/u0a95} (pid=17032, uid=10095) requires android.permission.permRead or android.permission.permWrite   

It seems that App B did not make use of the temporary permission to access. In other word, how to utilize FLAG_GRANT_READ_URI_PERMISSION from App B?

I have also tried directly adding Uri to intent(using setData()), instead of Uri.toString()(using putExtra()).

sendIntent.setData(uri2);

and

Uri userNameuri = getIntent().getData(); 

But the "userNameuri" got in App B is null.

I am totally confused...

updated

I tried "grantUriPermission("com.android.getCPaccess", uri2, Intent.FLAG_GRANT_READ_URI_PERMISSION)" according to a previous post
What is the correct permission handling when sending sensitive app data as email attachment?

And it works indeed. It can work without using FLAG_GRANT_READ_URI_PERMISSION. But the permission is not "temporary". It have to be ended manually by revokeUriPermission().

So, I am wondering if there is a way to grant temporary permission as introduced in FLAG_GRANT_READ_URI_PERMISSION, or it's a bug at all?

Community
  • 1
  • 1
Jin
  • 113
  • 1
  • 1
  • 6
  • Is there a for **android.permission.permRead** in your app that implements the provider & picker activity? – Jens Nov 17 '12 at 23:56
  • Hi, Jens. I have also tried that. When both of apps have in manifest file, it works fine. But this is the kind of permanent permission. I want to work out temporary access permission with FLAG_GRANT_READ_URI_PERMISSION(as described by google android developer). So the App B won't have android.permission.permRead, instead, it receive the intent with FLAG_GRANT_READ_URI_PERMISSION flag "true" from App A. But this fails. – Jin Nov 18 '12 at 02:59
  • You should only need one `uses-permission`, declared in the app that declares the picker activity that calls `#addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)`. You should probably include more of your manifest & code in the question. – Jens Nov 19 '12 at 07:48
  • Sure. I edited the question. Yes App A doesn't need uses-permission. But App B shouldn't have either, because if it has, there's no need to addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION). – Jin Nov 19 '12 at 18:02
  • No - application A, which defines the provider, _should_ also define the permission(s) you use for read/write. It **must** also declare a uses-permission for both the read (& write, if you in the future wish to grant write permission also) - you can't grant a permission you yourself do not hold. – Jens Nov 20 '12 at 08:35
  • It still cannot work. The weird thing is: if I use putExtra("name", uri2.toString()) to transfer the URI, it may fail because the system may not bundle temporary acecss grant to a string. So I use setData(uri2), but when I use getIntent().getData() on the other side, it shows uri is null. Other part of intent can all be successfully received. – Jin Nov 20 '12 at 15:42

1 Answers1

12

It seems that FLAG_GRANT_READ_URI_PERMISSION affects only Uri Intent.mData but not uris in extras.

I found similar problems when playing with ACTION_SEND, which takes an uri in EXTRA_STREAM. Providing the same uri in setData() will work, but doesn't conform to the rules and leads to unexpected behavior (e.g. Gmail recipient).

As of Jelly Bean intents can contain ClipData, which should solve the problem. For ACTION_SEND it's generated automatically from extras.

grantUriPermission will work, but requires revokeUriPermission. To do the same job as startActivity(Intent.createChooser(intent, title)) you will have to pick the target (ACTION_PICK_ACTIVITY), grant permissions to its package and revoke them when not needed anymore (onActivityResult?).

Here's some code:

Receiver app:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.handler" >

    <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" >
        …
        <activity
            android:name=".ActivityView"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <action android:name="android.intent.action.SEND"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:mimeType="*/*"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

ActivityView.java:

protected void onCreate(Bundle savedInstanceState) {
    Intent intent = getIntent();
    if ((intent != null)) {
        procUri(intent.getData());
        procUri(intent.<Uri>getParcelableExtra(Intent.EXTRA_STREAM));
    }
}

private void procUri(Uri uri) {
    if (uri != null) {
        InputStream i;
        try {
            i = getContentResolver().openInputStream(uri);
            byte[] b = new byte[i.available()];
            i.read(b);
            …
        } catch (Throwable e) {
            …
        }
    }
}

Sender app:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.sender" >

    <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" >
        …
        <activity
            android:name=".ActivitySend"
            android:label="@string/app_name" >
        </activity>
        <provider
            android:name=".MyContentProvider"
            android:authorities="com.example.sender.content"
            android:enabled="true"
            android:exported="false"
            android:grantUriPermissions="true" >
        </provider>
    </application>
</manifest>

ActivitySend.java:

// OK!
private void doTestView(Uri uri, String type) {
    startActivity(
            new Intent(Intent.ACTION_VIEW)
                .setDataAndType(uri, type)
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
        );
}

// error prior to JellyBean
// ok on JellyBean, even without explicit FLAG_GRANT_READ_URI_PERMISSION (due to generated ClipData)
private void doTestSend(Uri uri, String type, CharSequence title) {
    startActivity(
            Intent.createChooser(
                new Intent(Intent.ACTION_SEND)
                    .setType(type)
                    .putExtra(Intent.EXTRA_STREAM, uri)
                    .putExtra(Intent.EXTRA_SUBJECT, title)
                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
                    .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                ,
                title
            )
        );
}

// working but not ok, unexpected results!
private void doTestSend2(Uri uri, String type, CharSequence title) {
    startActivity(
            Intent.createChooser(
                new Intent(Intent.ACTION_SEND)
                    .setDataAndType(uri, type)
                    .putExtra(Intent.EXTRA_STREAM, uri)
                    .putExtra(Intent.EXTRA_SUBJECT, title)
                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
                    .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                ,
                title
            )
        );
}
marx
  • 196
  • 2
  • 8