ContentProvider で特定のアプリにのみ利用を許可する
ContentProvider の利用を制限したい事ってありますよね。あると思います。その方法について調べてみました。
Permission
この方法だと、同一署名からの許可のみしか行なえない。もっと柔軟に制限したい。
grantUriPermission
grantUriPermission を使えば、パッケージ名単位で Permission を切り替えることが出来る。
- 9. セキュリティと許可 - ソフトウェア技術ドキュメントを勝手に翻訳
- provider - ソフトウェア技術ドキュメントを勝手に翻訳
- grant-uri-permission - ソフトウェア技術ドキュメントを勝手に翻訳
- Context | Android Developers
ただ、 grantUriPermission には大きな問題があって、 URI が完全一致しない場合にしか効力を発揮しない。
たとえば、 ContentProvider でアクセス出来うる URI が大量にあったり、 URI 内に id を持っていると、全ての URI に対して grantUriPermission を呼び出す必要がある。
また、許可を取り消したいときに使う revokeUriPermission ではパッケージ名を指定しないので、複数パッケージからの許可をしていた場合に、再登録する必要がある。
その辺を考慮した使い方が出来ればとても有用。
使い方
Manifest.xml の Provider タグで、 grantUriPermission 使うよ!と宣言します。
<provider android:name=".MyProvider"
android:authorities="com.example.sampleprovider.myprovider"
android:grantUriPermissions="true" />
アプリ起動後、どこかのタイミングで特定のパッケージからのアクセスを許可する。
フラグは query なら READ 、 update/delete/insert なら READ と WRITE を指定します。
grantUriPermission("com.example.sampleresolver",
Uri.parse("content://com.example.sampleprovider/myprovider/"),
Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 以後、 com.example.sampleresolver が content://com.example.sampleprovider/myprovider/ に
// アクセスした場合は読み込み (query) のみ許可される。
// 許可をやめたい時は revokeUriPermission を使う。
revokeUriPermission(Uri.parse("content://com.example.sampleprovider/myprovider/"),
Intent.FLAG_GRANT_READ_URI_PERMISSION);
Binder.getCallingUri
ようは ContentProvider 呼び出し時に、パッケージ名を調べて許可されていないなら SecurityException を投げれば良いだけなんです。
Permission とか、 grantUriPermission とか、深く考えすぎていました。
@Override
public Cursor query(Uri uri, String projection, String selection, String selectionArgs, String sortOrder) {
enforcePermission(Binder.getCallingPid());
// do something ...
}
private String getPackageNameFromPid(int pid) {
ActivityManager am = (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
for (RunningAppProcessInfo info : am.getRunningAppProcesses()) {
if (info.pid == pid) {
return info.processName;
}
}
return null;
}
private void enforcePermission(int pid) {
String packageName = getPackageNameFromPid(pid);
if (!packageName.equals("com.example.sampleresolver")) {
throw new SecurityException();
}
}
これで OK
IPC の時は Binder.getCallingUid() とかを使って柔軟に接続を制限しよう。 Android の Permission なんて時代遅れだよ!!