From b06b739b078ce4b00600487cfec31659647bf31f Mon Sep 17 00:00:00 2001 From: Steve Howard Date: Thu, 22 Jul 2010 11:33:50 -0700 Subject: Make DownloadProvider accessible for public API usage. This change removes the requirement that apps have the ACCESS_DOWNLOAD_MANAGER permission in order to access DownloadProvider. This enables the public API to work. Instead, DownloadProvider enforces the new permissions model for the public API: * insert() requires INTERNET permission * insert() checks that input fits within the restricted input allowed for the public API * insert() also strictly checks the file URI provided with DESTINATION_FILE_URI (and still requires WRITE_EXTERNAL_STORAGE permission if that is supplied) Note that if an app has the ACCESS_DOWNLOAD_MANAGER permission, legacy behavior is retained. I've added a test to cover this new access, and updated the existing permissions tests. I also fixed a bug in WHERE clause construction in update() and delete(), and refactored the code to eliminate duplication. Change-Id: I53a08df137b35c2788c36350276c9dff24858af1 --- tests/public_api_access/Android.mk | 14 +++ tests/public_api_access/AndroidManifest.xml | 37 ++++++ .../PublicApiAccessTest.java | 129 +++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 tests/public_api_access/Android.mk create mode 100644 tests/public_api_access/AndroidManifest.xml create mode 100644 tests/public_api_access/src/com/android/providers/downloads/public_api_access_tests/PublicApiAccessTest.java (limited to 'tests/public_api_access') diff --git a/tests/public_api_access/Android.mk b/tests/public_api_access/Android.mk new file mode 100644 index 00000000..6c6db1f4 --- /dev/null +++ b/tests/public_api_access/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests + +# Include all test java files. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_JAVA_LIBRARIES := android.test.runner +LOCAL_PACKAGE_NAME := DownloadPublicApiAccessTests + +include $(BUILD_PACKAGE) + diff --git a/tests/public_api_access/AndroidManifest.xml b/tests/public_api_access/AndroidManifest.xml new file mode 100644 index 00000000..01048460 --- /dev/null +++ b/tests/public_api_access/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + diff --git a/tests/public_api_access/src/com/android/providers/downloads/public_api_access_tests/PublicApiAccessTest.java b/tests/public_api_access/src/com/android/providers/downloads/public_api_access_tests/PublicApiAccessTest.java new file mode 100644 index 00000000..aca5791b --- /dev/null +++ b/tests/public_api_access/src/com/android/providers/downloads/public_api_access_tests/PublicApiAccessTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.providers.downloads.public_api_access_tests; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.provider.Downloads; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +/** + * DownloadProvider allows apps without permission ACCESS_DOWNLOAD_MANAGER to access it -- this is + * how the public API works. But such access is subject to strict constraints on what can be + * inserted. This test suite checks those constraints. + */ +@MediumTest +public class PublicApiAccessTest extends AndroidTestCase { + private static final String[] DISALLOWED_COLUMNS = new String[] { + Downloads.Impl.COLUMN_COOKIE_DATA, + Downloads.Impl.COLUMN_REFERER, + Downloads.Impl.COLUMN_USER_AGENT, + Downloads.Impl.COLUMN_NO_INTEGRITY, + Downloads.Impl.COLUMN_NOTIFICATION_CLASS, + Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS, + Downloads.Impl.COLUMN_OTHER_UID, + Downloads.Impl.COLUMN_APP_DATA, + Downloads.Impl.COLUMN_CONTROL, + Downloads.Impl.COLUMN_STATUS, + }; + + private ContentResolver mContentResolver; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mContentResolver = getContext().getContentResolver(); + } + + @Override + protected void tearDown() throws Exception { + if (mContentResolver != null) { + mContentResolver.delete(Downloads.CONTENT_URI, null, null); + } + super.tearDown(); + } + + public void testMinimalValidWrite() { + mContentResolver.insert(Downloads.Impl.CONTENT_URI, buildValidValues()); + } + + public void testMaximalValidWrite() { + ContentValues values = buildValidValues(); + values.put(Downloads.Impl.COLUMN_TITLE, "foo"); + values.put(Downloads.Impl.COLUMN_DESCRIPTION, "foo"); + values.put(Downloads.Impl.COLUMN_MIME_TYPE, "foo"); + values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, "foo"); + values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, 0); + values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, true); + mContentResolver.insert(Downloads.Impl.CONTENT_URI, values); + } + + private ContentValues buildValidValues() { + ContentValues values = new ContentValues(); + values.put(Downloads.Impl.COLUMN_URI, "foo"); + values.put(Downloads.Impl.COLUMN_DESTINATION, + Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE); + values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true); + return values; + } + + public void testNoPublicApi() { + ContentValues values = buildValidValues(); + values.remove(Downloads.Impl.COLUMN_IS_PUBLIC_API); + testInvalidValues(values); + } + + public void testInvalidDestination() { + ContentValues values = buildValidValues(); + values.put(Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_EXTERNAL); + testInvalidValues(values); + values.put(Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_CACHE_PARTITION); + testInvalidValues(values); + } + + public void testInvalidVisibility() { + ContentValues values = buildValidValues(); + values.put(Downloads.Impl.COLUMN_VISIBILITY, + Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + testInvalidValues(values); + } + + public void testDisallowedColumns() { + for (String column : DISALLOWED_COLUMNS) { + ContentValues values = buildValidValues(); + values.put(column, 1); + testInvalidValues(values); + } + } + + public void testFileUriWithoutExternalPermission() { + ContentValues values = buildValidValues(); + values.put(Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_FILE_URI); + values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, "file:///sdcard/foo"); + testInvalidValues(values); + } + + private void testInvalidValues(ContentValues values) { + try { + mContentResolver.insert(Downloads.Impl.CONTENT_URI, values); + fail("Didn't get SecurityException as expected"); + } catch (SecurityException exc) { + // expected + } + } +} -- cgit v1.2.3