I'm trying to make an app that access a SQLite table from another app with a custom content provider. I'm getting the following error:
java.lang.SecurityException: Permission Denial: reading com.example.carlos.gymlog.MiContentProvider uri content://mi.uri.porquesi/sesiones from pid=2375, uid=10064 requires android.permission.permRead, or grantUriPermission()
However, my "sender" app (the one with the database) already has the required permissions:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.carlos.gymlog" >
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/MiTema" >
<activity
android:name=".Principal"
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:name="MiContentProvider"
android:authorities="mi.uri.porquesi"
android:readPermission="android.permission.permRead"
android:exported="true"
android:grantUriPermissions="true"> </provider>
</application>
</manifest>
This is the code that causes the error in the "receiver" app (the one that tries to access the other):
ArrayList<Sesion> sacarDatos(Context c){
Uri cUri = Uri.parse("content://mi.uri.porquesi/sesiones");
ContentProviderClient miCP = c.getContentResolver().acquireContentProviderClient(cUri);
ArrayList<Sesion> sesiones = null;
try {
Cursor cur = miCP.query(cUri, null, null, null, null);
sesiones = new ArrayList<Sesion>();
cur.moveToFirst();
Sesion sesion = null;
do {
sesion = new Sesion(cur.getInt(0),cur.getInt(1),cur.getInt(2),cur.getInt(3));
sesiones.add(sesion);
} while (cur.moveToNext());
} catch (RemoteException e) {
e.printStackTrace();
}
return sesiones;
}
And lastly, this is my custom content provider:
public class MiContentProvider extends android.content.ContentProvider {
private static final String uri = "content://mi.uri.porquesi";
public static final Uri CONTENT_URI = Uri.parse(uri);
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
BaseDatosHelper miBD = new BaseDatosHelper(getContext(), "SESIONES", null, 1);
SQLiteDatabase db = miBD.getWritableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables("SESIONES");
Cursor cur = qb.query(db,projection,selection,selectionArgs,"","",sortOrder);
cur.setNotificationUri(getContext().getContentResolver(),uri);
return cur;
}
(the rest of the methods like insert() aren't implemented, they just return null or whatever it is, I just want to select data not insert it).
EDIT:
My app A has
<provider android:name="MiContentProvider"
android:authorities="mi.uri.porquesi"
android:exported="true"
android:permission="mi.permision"
android:grantUriPermissions="true"> </provider>
<permission
android:name="mi.permision"
/>
( just a random temporal name)
In app B, I've included
<uses-permission android:name="mi.permision"/>
However, now I get:
java.lang.SecurityException: Permission Denial: opening provider com.example.carlos.gymlog.MiContentProvider from ProcessRecord{335efe6e 3116:com.example.carlos.sample/u0a67} (pid=3116, uid=10067) requires mi.permision or mi.permision
So it recognizes that I need my custom permission, but it doesn't recognize that my app B is using it. Why??
Let's say that we have App A and App B. App A has the ContentProvider
; App B wants to use the ContentProvider
from App A. And, you want to defend the ContentProvider
, with a custom permission.
App A needs a <permission>
element in its manifest, with your custom permission name in the android:name
attribute. DO NOT use an android.permission
prefix on that custom permission name, as you are not a developer on the Android Open Source Project. Use something namespaced for your app, such as com.kace91.permission.AND_REMEMBER_THAT_CUSTOM_PERMISSIONS_HAVE_SECURITY_ISSUES
.
App A then needs an attribute, on the <provider>
manifest element, that has a value matching the name of the android:name
attribute of the <permission>
element. Either use android:permission
to defend all operations, or have two custom permissions and use android:readPermission
and android:writePermission
. DO NOT simply use android:readPermission
, as that means that any app can modify the ContentProvider
without permission, and it is rather unlikely that this is what the user wants.
App A also needs an android:exported="true"
attribute on the <provider>
element, to say that third party apps can initiate communications with that ContentProvider
.
App B can then have a <uses-permission>
manifest element, with an android:name
attribute value matching the android:name
attribute of the <permission>
element from App A. If you used two permissions, for read and write, App B would need two <uses-permission>
elements.
Then, if App A is installed before App B, and the user agrees to grant App B permission to hold the custom permission, App B will be able to perform the designated operations on App A's ContentProvider
.
And, as noted in my comment, there are security issues with custom permissions.