ContentProvider で特定のアプリにのみ利用を許可する
ContentProvider の利用を制限したい事ってありますよね。あると思います。その方法について調べてみました。
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 なんて時代遅れだよ!!