blob: fd72e7783806e465c5cbcade02e5686fb92685cb [file] [log] [blame]
/*
* 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 android.permission.cts;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
import android.app.UiAutomation;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.UserHandle;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.AppModeInstant;
import android.provider.CallLog;
import android.provider.Contacts;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.provider.Telephony;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Tests Permissions related to reading from and writing to providers
*/
@MediumTest
public class ProviderPermissionTest extends AndroidTestCase {
private static final String TAG = ProviderPermissionTest.class.getSimpleName();
private static final List<Uri> CONTACT_URIS = new ArrayList<Uri>() {{
add(Contacts.People.CONTENT_URI); // Deprecated.
add(ContactsContract.Contacts.CONTENT_FILTER_URI);
add(ContactsContract.Contacts.CONTENT_GROUP_URI);
add(ContactsContract.Contacts.CONTENT_LOOKUP_URI);
add(ContactsContract.CommonDataKinds.Email.CONTENT_URI);
add(ContactsContract.CommonDataKinds.Email.CONTENT_FILTER_URI);
add(ContactsContract.Directory.CONTENT_URI);
add(ContactsContract.Directory.ENTERPRISE_CONTENT_URI);
add(ContactsContract.Profile.CONTENT_URI);
}};
/**
* Verify that reading contacts requires permissions.
* <p>Tests Permission:
* {@link android.Manifest.permission#READ_CONTACTS}
*/
public void testReadContacts() {
for (Uri uri : CONTACT_URIS) {
Log.d(TAG, "Checking contacts URI " + uri);
assertReadingContentUriRequiresPermission(uri,
android.Manifest.permission.READ_CONTACTS);
}
}
/**
* Verify that writing contacts requires permissions.
* <p>Tests Permission:
* {@link android.Manifest.permission#WRITE_CONTACTS}
*/
public void testWriteContacts() {
assertWritingContentUriRequiresPermission(Contacts.People.CONTENT_URI,
android.Manifest.permission.WRITE_CONTACTS);
}
/**
* Verify that reading call logs requires permissions.
* <p>Tests Permission:
* {@link android.Manifest.permission#READ_CALL_LOG}
*/
@AppModeFull
public void testReadCallLog() {
assertReadingContentUriRequiresPermission(CallLog.CONTENT_URI,
android.Manifest.permission.READ_CALL_LOG);
}
/**
* Verify that writing call logs requires permissions.
* <p>Tests Permission:
* {@link android.Manifest.permission#WRITE_CALL_LOG}
*/
@AppModeFull
public void testWriteCallLog() {
assertWritingContentUriRequiresPermission(CallLog.CONTENT_URI,
android.Manifest.permission.WRITE_CALL_LOG);
}
/**
* Verify that reading from call-log (a content provider that is not accessible to instant apps)
* returns null
*/
@AppModeInstant
public void testReadCallLogInstant() {
assertNull(getContext().getContentResolver().query(CallLog.CONTENT_URI, null, null, null,
null));
}
/**
* Verify that writing to call-log (a content provider that is not accessible to instant apps)
* yields an IAE.
*/
@AppModeInstant
public void testWriteCallLogInstant() {
try {
getContext().getContentResolver().insert(CallLog.CONTENT_URI, new ContentValues());
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
/**
* Verify that reading already received SMS messages requires permissions.
* <p>Tests Permission:
* {@link android.Manifest.permission#READ_SMS}
*
* <p>Note: The WRITE_SMS permission has been removed.
*/
@AppModeFull
public void testReadSms() {
if (!mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TELEPHONY)) {
return;
}
assertReadingContentUriRequiresPermission(Telephony.Sms.CONTENT_URI,
android.Manifest.permission.READ_SMS);
}
/**
* Verify that reading from 'sms' (a content provider that is not accessible to instant apps)
* returns null
*/
@AppModeInstant
public void testReadSmsInstant() {
if (!mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TELEPHONY)) {
return;
}
assertNull(getContext().getContentResolver().query(Telephony.Sms.CONTENT_URI, null, null,
null, null));
}
/**
* Verify that write to settings requires permissions.
* <p>Tests Permission:
* {@link android.Manifest.permission#WRITE_SETTINGS}
*/
public void testWriteSettings() {
final String permission = android.Manifest.permission.WRITE_SETTINGS;
ContentValues value = new ContentValues();
value.put(Settings.System.NAME, "name");
value.put(Settings.System.VALUE, "value_insert");
try {
getContext().getContentResolver().insert(Settings.System.CONTENT_URI, value);
fail("expected SecurityException requiring " + permission);
} catch (SecurityException expected) {
assertNotNull("security exception's error message.", expected.getMessage());
assertTrue("error message should contain \"" + permission + "\". Got: \""
+ expected.getMessage() + "\".",
expected.getMessage().contains(permission));
}
}
/**
* Verify that the {@link android.Manifest.permission#MANAGE_DOCUMENTS}
* permission is only held by exactly one package: whoever handles the
* {@link android.content.Intent#ACTION_OPEN_DOCUMENT} intent.
* <p>
* No other apps should <em>ever</em> attempt to acquire this permission,
* since it would give those apps extremely broad access to all storage
* providers on the device without user involvement in the arbitration
* process. Apps should instead always rely on Uri permission grants for
* access, using
* {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} and related
* APIs.
*/
public void testManageDocuments() {
final PackageManager pm = getContext().getPackageManager();
final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
final ResolveInfo ri = pm.resolveActivity(intent, 0);
final String validPkg = ri.activityInfo.packageName;
final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
android.Manifest.permission.MANAGE_DOCUMENTS
}, PackageManager.MATCH_UNINSTALLED_PACKAGES);
for (PackageInfo pi : holding) {
if (!Objects.equals(pi.packageName, validPkg)) {
fail("Exactly one package (must be " + validPkg
+ ") can request the MANAGE_DOCUMENTS permission; found package "
+ pi.packageName + " which must be revoked for security reasons");
}
}
}
/**
* The {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission is
* a very powerful permission that grants raw storage access to all devices,
* and as such it's only appropriate to be granted to the media stack.
* <p>
* CDD now requires that all apps requesting this permission also hold the
* "Storage" runtime permission, to give users visibility into the
* capabilities of each app, and control over those capabilities.
* <p>
* If the end user revokes the "Storage" permission from an app, but that
* app still has raw access to storage via {@code WRITE_MEDIA_STORAGE}, that
* would be a CDD violation and a privacy incident.
*/
public void testWriteMediaStorage() throws Exception {
final UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
final PackageManager pm = getContext().getPackageManager();
final List<PackageInfo> pkgs = pm.getInstalledPackages(
PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_PERMISSIONS);
for (PackageInfo pkg : pkgs) {
final boolean isSystem = pkg.applicationInfo.uid == android.os.Process.SYSTEM_UID;
final boolean hasFrontDoor = pm.getLaunchIntentForPackage(pkg.packageName) != null;
final boolean grantedMedia = pm.checkPermission(WRITE_MEDIA_STORAGE,
pkg.packageName) == PackageManager.PERMISSION_GRANTED;
if (!isSystem && hasFrontDoor && grantedMedia) {
final boolean requestsStorage = contains(pkg.requestedPermissions,
WRITE_EXTERNAL_STORAGE);
if (!requestsStorage) {
fail("Found " + pkg.packageName + " holding WRITE_MEDIA_STORAGE permission "
+ "without also requesting WRITE_EXTERNAL_STORAGE; these permissions "
+ "must be requested together");
}
final boolean grantedStorage = pm.checkPermission(WRITE_EXTERNAL_STORAGE,
pkg.packageName) == PackageManager.PERMISSION_GRANTED;
if (grantedStorage) {
final int flags;
ui.adoptShellPermissionIdentity("android.permission.GET_RUNTIME_PERMISSIONS");
try {
flags = pm.getPermissionFlags(WRITE_EXTERNAL_STORAGE, pkg.packageName,
android.os.Process.myUserHandle());
} finally {
ui.dropShellPermissionIdentity();
}
final boolean isFixed = (flags & (PackageManager.FLAG_PERMISSION_USER_FIXED
| PackageManager.FLAG_PERMISSION_POLICY_FIXED
| PackageManager.FLAG_PERMISSION_SYSTEM_FIXED)) != 0;
if (isFixed) {
fail("Found " + pkg.packageName + " holding WRITE_EXTERNAL_STORAGE in a "
+ "fixed state; this permission must be revokable by the user");
}
}
}
}
}
private static boolean contains(String[] haystack, String needle) {
if (haystack != null) {
for (String test : haystack) {
if (Objects.equals(test, needle)) {
return true;
}
}
}
return false;
}
}