2012/06/07

Android:Grant Permissionでアクセスを制御する

GrantPermissionの仕組みを使えば、一時的にパーミッションを付与することが可能です。

たとえば下記のようなケースで有効です。

1. 選択モードで起動されたが、選択結果をURIで返却し1回限りのアクセスを許可したい。
2. 起動するアプリAに対して、一時的なアクセス権限を付与したい

・ケース1の例
Android標準の電話帳アプリには"選択モード"があります。
下記のIntentで起動可能です。
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setData(ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(intent, requestCode);
選択モードで起動すると電話帳リストが表示されます。
リストを選択すると、選択したデータのURIを詰めたIntentを起動元アプリに返します。
起動元アプリは、このURIを参照すれば選択されたデータの詳細を取得可能という仕組み
です。
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    Uri resultUri = data.getData();
    getContentResolver().query(resultUri, null, null, null, null);
}
ここで、返却されるURIは
content://com.android.contacts/contacts/lookup/....
の形式です。
つまり、Contactsへのアクセスとなるため、通常であればREAD_CONTACTS権限が必要です。
しかし、このケースではREAD_CONTACTS権限無しでアクセスすることが可能です。
Getting a Result from an Activity /Bonus: Read the contact dataのNote参照

これは、電話帳アプリ側で呼出元アプリに一時的なアクセス権限を設定する
Intent.FLAG_GRANT_READ_URI_PERMISSION
を付与したIntentを返却しているためです。


・ケース1の注意点
もし、あなたのアプリがIntent.FLAG_GRANT_READ_URI_PERMISSIONで権限付与されること
を期待して、他アプリを呼び出す場合は下記の点に留意する必要があります。
  • Intent.FLAG_GRANT_READ_URI_PERMISSIONの付与は呼出先アプリの実装依存
  • "選択モード"で起動してもパーミッションが付与される保証はない
選択モードのケースでも、結果に対してFLAG_GRANT_READ_URI_PERMISSIONを付与しないア
プリが存在します。
つまり、FLAG_GRANT_READ_URI_PERMISSIONが付与されることを期待する場合は、
連携先アプリの仕様について知っている必要があるのです。

Android標準の電話帳選択モードはFLAG_GRANT_READ_URI_PERMISSIONを付与しますが、
キャリアカスタマイズされた電話帳の中には付与しないものが存在します。



・ケース2の例
たとえば、他アプリから自アプリデータに対するアクセスを一時的に許可したい場合。

まず、パーミッションの付与を許可するために、<provider>要素のandroid:grantUriPermissions
属性をtrueに設定します(デフォルトfalse)。
<provider
    android:authorities="yuki.provider"
    android:name=".TestProvider"
    android:grantUriPermissions="true"
    android:permission="yuki.provider.signature" >
ContextのgrantUriPermissionメソッドで一時的にパーミッションを付与することが可能です。
grantUriPermission("opponent.package.name",
    Uri.parse("content://test.provider/allow"),
    Intent.FLAG_GRANT_READ_URI_PERMISSION);
第一引数にはアクセスを許可するパッケージを指定します。
第二引数にはアクセスを許可するコンテンツパスを指定します。
第三引数にはアクセスを許可する種別(READ/WRITE)を指定します。

ただし、許可する側が付与対象のパーミッションを持っている必要があります。
これを怠るとセキュリティ例外が発生します。
java.lang.SecurityException:
    Uid 10042 does not have permission to uri content://test.provider/allow
この"特権状態"はContextのrevokeUriPermissionで権限が破棄されるまで続きます。
revokeUriPermission(Uri.parse("content://test.provider/allow"),
    Intent.FLAG_GRANT_READ_URI_PERMISSION);
第一引数には特権状態を破棄するコンテンツパスを指定します。
第二引数には破棄する種別(READ/WRITE)を指定します。


・ケース2の注意点
revokeUriPermissionを忘れた場合、相手先の"特権状態"は継続されます。
これは両プロセスが終了した後も続き、端末再起動されるまで持続します。

また、revokeUriPermissionにはgrantUriPermissionにあった「対象パッケージ名の指定」
がありません。
revokeUriPermissionされると、関連する全てのパッケージの権限が破棄されます。
// パッケージa, b, cにパーミッション付与
grantUriPermission("opponent.package.a",
    Uri.parse("content://test.provider/allow"),
    Intent.FLAG_GRANT_READ_URI_PERMISSION);
grantUriPermission("opponent.package.b",
    Uri.parse("content://test.provider/allow"),
    Intent.FLAG_GRANT_READ_URI_PERMISSION);
grantUriPermission("opponent.package.c",
    Uri.parse("content://test.provider/allow"),
    Intent.FLAG_GRANT_READ_URI_PERMISSION);

// これでパッケージa, b, cに付与したパーミッションが破棄される
revokeUriPermission(Uri.parse("content://test.provider/allow"),
    Intent.FLAG_GRANT_READ_URI_PERMISSION);
このため「パッケージbのパーミッションのみ破棄したい」というケースでは、
一度全パッケージのパーミッションを破棄した後、再度パッケージa, cにパーミッション
付与する必要があります。

参考:http://developer.android.com/guide/topics/providers/content-provider-basics.html

以上です。