4.3.1.2 创建/使用公共内容供应器

公共内容供应器是应该由未指定的大量应用使用的内容供应器。 需要注意的是,由于它不指定客户端,它可能会受到恶意软件的攻击和篡改。 例如,可以通过select()获取保存的数据,可以通过update()更改数据,或者可以通过insert()/ delete()插入/删除假数据。

另外,在使用 Android OS 未提供的自定义公共内容供应器时,需要注意的是,恶意软件可能会接收到请求参数,伪装成自定义公共内容供应器,并且也可能发送攻击数据。 Android OS 提供的联系人和 MediaStore 也是公共内容提供商,但恶意软件不能伪装成它们。

实现公共内容供应器的样例代码展示在下面:

要点(创建内容供应器):

  1. 将导出的属性显式设置为true

  2. 仔细安全地处理收到的请求数据。

  3. 返回结果时,请勿包含敏感信息。

AndroidManifest.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="org.jssec.android.provider.publicprovider">
  4. <application
  5. android:icon="@drawable/ic_launcher"
  6. android:label="@string/app_name" >
  7. <!-- *** POINT 1 *** Explicitly set the exported attribute to true. -->
  8. <provider
  9. android:name=".PublicProvider"
  10. android:authorities="org.jssec.android.provider.publicprovider"
  11. android:exported="true" />
  12. </application>
  13. </manifest>

PublicProvider.java

  1. package org.jssec.android.provider.publicprovider;
  2. import android.content.ContentProvider;
  3. import android.content.ContentUris;
  4. import android.content.ContentValues;
  5. import android.content.UriMatcher;
  6. import android.database.Cursor;
  7. import android.database.MatrixCursor;
  8. import android.net.Uri;
  9. public class PublicProvider extends ContentProvider {
  10. public static final String AUTHORITY = "org.jssec.android.provider.publicprovider";
  11. public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.org.jssec.contenttype";
  12. public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.jssec.contenttype";
  13. // Expose the interface that the Content Provider provides.
  14. public interface Download {
  15. public static final String PATH = "downloads";
  16. public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);
  17. }
  18. public interface Address {
  19. public static final String PATH = "addresses";
  20. public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);
  21. }
  22. // UriMatcher
  23. private static final int DOWNLOADS_CODE = 1;
  24. private static final int DOWNLOADS_ID_CODE = 2;
  25. private static final int ADDRESSES_CODE = 3;
  26. private static final int ADDRESSES_ID_CODE = 4;
  27. private static UriMatcher sUriMatcher;
  28. static {
  29. sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
  30. sUriMatcher.addURI(AUTHORITY, Download.PATH, DOWNLOADS_CODE);
  31. sUriMatcher.addURI(AUTHORITY, Download.PATH + "/#", DOWNLOADS_ID_CODE);
  32. sUriMatcher.addURI(AUTHORITY, Address.PATH, ADDRESSES_CODE);
  33. sUriMatcher.addURI(AUTHORITY, Address.PATH + "/#", ADDRESSES_ID_CODE);
  34. }
  35. // Since this is a sample program,
  36. // query method returns the following fixed result always without using database.
  37. private static MatrixCursor sAddressCursor = new MatrixCursor(new String[] { "_id", "city" });
  38. static {
  39. sAddressCursor.addRow(new String[] { "1", "New York" });
  40. sAddressCursor.addRow(new String[] { "2", "London" });
  41. sAddressCursor.addRow(new String[] { "3", "Paris" });
  42. }
  43. private static MatrixCursor sDownloadCursor = new MatrixCursor(new String[] { "_id", "path" });
  44. static {
  45. sDownloadCursor.addRow(new String[] { "1", "/sdcard/downloads/sample.jpg" });
  46. sDownloadCursor.addRow(new String[] { "2", "/sdcard/downloads/sample.txt" });
  47. }
  48. @Override
  49. public boolean onCreate() {
  50. return true;
  51. }
  52. @Override
  53. public String getType(Uri uri) {
  54. switch (sUriMatcher.match(uri)) {
  55. case DOWNLOADS_CODE:
  56. case ADDRESSES_CODE:
  57. return CONTENT_TYPE;
  58. case DOWNLOADS_ID_CODE:
  59. case ADDRESSES_ID_CODE:
  60. return CONTENT_ITEM_TYPE;
  61. default:
  62. throw new IllegalArgumentException("Invalid URI:" + uri);
  63. }
  64. }
  65. @Override
  66. public Cursor query(Uri uri, String[] projection, String selection,
  67. String[] selectionArgs, String sortOrder) {
  68. // *** POINT 2 *** Handle the received request data carefully and securely.
  69. // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
  70. // Checking for other parameters are omitted here, due to sample.
  71. // Refer to "3.2 Handle Input Data Carefully and Securely."
  72. // *** POINT 3 *** When returning a result, do not include sensitive information.
  73. // It depends on application whether the query result has sensitive meaning or not.
  74. // If no problem when the information is taken by malware, it can be returned as result.
  75. switch (sUriMatcher.match(uri)) {
  76. case DOWNLOADS_CODE:
  77. case DOWNLOADS_ID_CODE:
  78. return sDownloadCursor;
  79. case ADDRESSES_CODE:
  80. case ADDRESSES_ID_CODE:
  81. return sAddressCursor;
  82. default:
  83. throw new IllegalArgumentException("Invalid URI:" + uri);
  84. }
  85. }
  86. @Override
  87. public Uri insert(Uri uri, ContentValues values) {
  88. // *** POINT 2 *** Handle the received request data carefully and securely.
  89. // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
  90. // Checking for other parameters are omitted here, due to sample.
  91. // Refer to "3.2 Handle Input Data Carefully and Securely."
  92. // *** POINT 3 *** When returning a result, do not include sensitive information.
  93. // It depends on application whether the issued ID has sensitive meaning or not.
  94. // If no problem when the information is taken by malware, it can be returned as result.
  95. switch (sUriMatcher.match(uri)) {
  96. case DOWNLOADS_CODE:
  97. return ContentUris.withAppendedId(Download.CONTENT_URI, 3);
  98. case ADDRESSES_CODE:
  99. return ContentUris.withAppendedId(Address.CONTENT_URI, 4);
  100. default:
  101. throw new IllegalArgumentException("Invalid URI:" + uri);
  102. }
  103. }
  104. @Override
  105. public int update(Uri uri, ContentValues values, String selection,
  106. String[] selectionArgs) {
  107. // *** POINT 2 *** Handle the received request data carefully and securely.
  108. // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
  109. // Checking for other parameters are omitted here, due to sample.
  110. // Refer to "3.2 Handle Input Data Carefully and Securely."
  111. // *** POINT 3 *** When returning a result, do not include sensitive information.
  112. // It depends on application whether the number of updated records has sensitive meaning or not.
  113. // If no problem when the information is taken by malware, it can be returned as result.
  114. switch (sUriMatcher.match(uri)) {
  115. case DOWNLOADS_CODE:
  116. return 5; // Return number of updated records
  117. case DOWNLOADS_ID_CODE:
  118. return 1;
  119. case ADDRESSES_CODE:
  120. return 15;
  121. case ADDRESSES_ID_CODE:
  122. return 1;
  123. default:
  124. throw new IllegalArgumentException("Invalid URI:" + uri);
  125. }
  126. }
  127. @Override
  128. public int delete(Uri uri, String selection, String[] selectionArgs) {
  129. // *** POINT 2 *** Handle the received request data carefully and securely.
  130. // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
  131. // Checking for other parameters are omitted here, due to sample.
  132. // Refer to "3.2 Handle Input Data Carefully and Securely."
  133. // *** POINT 3 *** When returning a result, do not include sensitive information.
  134. // It depends on application whether the number of deleted records has sensitive meaning or not.
  135. // If no problem when the information is taken by malware, it can be returned as result.
  136. switch (sUriMatcher.match(uri)) {
  137. case DOWNLOADS_CODE:
  138. return 10; // Return number of deleted records
  139. case DOWNLOADS_ID_CODE:
  140. return 1;
  141. case ADDRESSES_CODE:
  142. return 20;
  143. case ADDRESSES_ID_CODE:
  144. return 1;
  145. default:
  146. throw new IllegalArgumentException("Invalid URI:" + uri);
  147. }
  148. }
  149. }

下面是使用公共内容供应器的活动示例。

要点(使用内容供应器):

  1. 不要发送敏感信息

  2. 收到结果时,小心和安全地处理结果数据

PublicUserActivity.java

  1. package org.jssec.android.provider.publicuser;
  2. import android.app.Activity;
  3. import android.content.ContentValues;
  4. import android.content.pm.ProviderInfo;
  5. import android.database.Cursor;
  6. import android.net.Uri;
  7. import android.os.Bundle;
  8. import android.view.View;
  9. import android.widget.TextView;
  10. public class PublicUserActivity extends Activity {
  11. // Target Content Provider Information
  12. private static final String AUTHORITY = "org.jssec.android.provider.publicprovider";
  13. private interface Address {
  14. public static final String PATH = "addresses";
  15. public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);
  16. }
  17. public void onQueryClick(View view) {
  18. logLine("[Query]");
  19. if (!providerExists(Address.CONTENT_URI)) {
  20. logLine(" Content Provider doesn't exist.");
  21. return;
  22. }
  23. Cursor cursor = null;
  24. try {
  25. // *** POINT 4 *** Do not send sensitive information.
  26. // since the target Content Provider may be malware.
  27. // If no problem when the information is taken by malware, it can be included in the request.
  28. cursor = getContentResolver().query(Address.CONTENT_URI, null, null, null, null);
  29. // *** POINT 5 *** When receiving a result, handle the result data carefully and securely.
  30. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
  31. if (cursor == null) {
  32. logLine(" null cursor");
  33. } else {
  34. boolean moved = cursor.moveToFirst();
  35. while (moved) {
  36. logLine(String.format(" %d, %s", cursor.getInt(0), cursor.getString(1)));
  37. moved = cursor.moveToNext();
  38. }
  39. }
  40. }
  41. finally {
  42. if (cursor != null) cursor.close();
  43. }
  44. }
  45. public void onInsertClick(View view) {
  46. logLine("[Insert]");
  47. if (!providerExists(Address.CONTENT_URI)) {
  48. logLine(" Content Provider doesn't exist.");
  49. return;
  50. }
  51. // *** POINT 4 *** Do not send sensitive information.
  52. // since the target Content Provider may be malware.
  53. // If no problem when the information is taken by malware, it can be included in the request.
  54. ContentValues values = new ContentValues();
  55. values.put("city", "Tokyo");
  56. Uri uri = getContentResolver().insert(Address.CONTENT_URI, values);
  57. // *** POINT 5 *** When receiving a result, handle the result data carefully and securely.
  58. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
  59. logLine(" uri:" + uri);
  60. }
  61. public void onUpdateClick(View view) {
  62. logLine("[Update]");
  63. if (!providerExists(Address.CONTENT_URI)) {
  64. logLine(" Content Provider doesn't exist.");
  65. return;
  66. }
  67. // *** POINT 4 *** Do not send sensitive information.
  68. // since the target Content Provider may be malware.
  69. // If no problem when the information is taken by malware, it can be included in the request.
  70. ContentValues values = new ContentValues();
  71. values.put("city", "Tokyo");
  72. String where = "_id = ?";
  73. String[] args = { "4" };
  74. int count = getContentResolver().update(Address.CONTENT_URI, values, where, args);
  75. // *** POINT 5 *** When receiving a result, handle the result data carefully and securely.
  76. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
  77. logLine(String.format(" %s records updated", count));
  78. }
  79. public void onDeleteClick(View view) {
  80. logLine("[Delete]");
  81. if (!providerExists(Address.CONTENT_URI)) {
  82. logLine(" Content Provider doesn't exist.");
  83. return;
  84. }
  85. // *** POINT 4 *** Do not send sensitive information.
  86. // since the target Content Provider may be malware.
  87. // If no problem when the information is taken by malware, it can be included in the request.
  88. int count = getContentResolver().delete(Address.CONTENT_URI, null, null);
  89. // *** POINT 5 *** When receiving a result, handle the result data carefully and securely.
  90. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
  91. logLine(String.format(" %s records deleted", count));
  92. }
  93. private boolean providerExists(Uri uri) {
  94. ProviderInfo pi = getPackageManager().resolveContentProvider(uri.getAuthority(), 0);
  95. return (pi != null);
  96. }
  97. private TextView mLogView;
  98. @Override
  99. public void onCreate(Bundle savedInstanceState) {
  100. super.onCreate(savedInstanceState);
  101. setContentView(R.layout.main);
  102. mLogView = (TextView)findViewById(R.id.logview);
  103. }
  104. private void logLine(String line) {
  105. mLogView.append(line);
  106. mLogView.append("¥n");
  107. }
  108. }