diff options
author | 2024-10-06 06:23:38 +0000 | |
---|---|---|
committer | 2024-10-06 06:23:38 +0000 | |
commit | 0f4c9c19a7b80bff3ddf75d9355cdb76ee3e79a7 (patch) | |
tree | 310ffc92dd908a3711a584ec30c08b3d2b3e0e79 | |
parent | e66ba0839cf1b7201482ff7e779e3d580901f565 (diff) | |
parent | 58b790b358a9ee39645217a5ec3d90bb01df6601 (diff) |
Merge "Add ignoreDisabledSystemPackageWhenGranting for SYSTEM_SUPERVISION" into main
7 files changed, 218 insertions, 194 deletions
diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml index 1d01f8208..ba4e80cf2 100644 --- a/PermissionController/res/xml/roles.xml +++ b/PermissionController/res/xml/roles.xml @@ -1281,6 +1281,7 @@ name="android.app.role.SYSTEM_SUPERVISION" defaultHolders="config_systemSupervision" exclusive="true" + ignoreDisabledSystemPackageWhenGranting="true" minSdkVersion="33" static="true" systemOnly="true" diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java b/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java index 1c71e0c99..56c4944a0 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java @@ -23,6 +23,7 @@ import android.os.UserHandle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; import com.android.role.controller.util.PackageUtils; @@ -130,7 +131,8 @@ public class AppOp { return Permissions.setAppOpUidModeAsUser(packageName, mName, defaultMode, user, context); } - boolean isAvailableByFeatureFlagAndSdkVersion() { + @VisibleForTesting + public boolean isAvailableByFeatureFlagAndSdkVersion() { if (mFeatureFlag != null && !mFeatureFlag.get()) { return false; } diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java b/PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java index e788fdce1..6de52ca42 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java @@ -86,8 +86,9 @@ public class Permissions { * * @param packageName the package name of the application to be granted permissions to * @param permissions the list of permissions to be granted - * @param overrideDisabledSystemPackage whether to ignore the permissions of a disabled system - * package (if this package is an updated system package) + * @param ignoreDisabledSystemPackage whether to ignore the requested permissions of a disabled + * system package (if this package is an updated system + * package) * @param overrideUserSetAndFixed whether to override user set and fixed flags on the permission * @param setGrantedByRole whether the permissions will be granted as granted-by-role * @param setGrantedByDefault whether the permissions will be granted as granted-by-default @@ -101,7 +102,7 @@ public class Permissions { * PackageInfo, java.util.Set, boolean, boolean, int) */ public static boolean grantAsUser(@NonNull String packageName, - @NonNull List<String> permissions, boolean overrideDisabledSystemPackage, + @NonNull List<String> permissions, boolean ignoreDisabledSystemPackage, boolean overrideUserSetAndFixed, boolean setGrantedByRole, boolean setGrantedByDefault, boolean setSystemFixed, @NonNull UserHandle user, @NonNull Context context) { if (setGrantedByRole == setGrantedByDefault) { @@ -145,7 +146,7 @@ public class Permissions { // choice to grant this app the permissions needed to function. For all other // apps, (default grants on first boot and user creation) we don't grant default // permissions if the version on the system image does not declare them. - if (!overrideDisabledSystemPackage && isUpdatedSystemApp(packageInfo)) { + if (!ignoreDisabledSystemPackage && isUpdatedSystemApp(packageInfo)) { PackageInfo disabledSystemPackageInfo = getFactoryPackageInfoAsUser(packageName, user, context); if (disabledSystemPackageInfo != null) { diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java index 04fd615e1..942535b0f 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java @@ -41,6 +41,7 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; +import androidx.annotation.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; import com.android.role.controller.util.CollectionUtils; @@ -125,6 +126,12 @@ public class Role { private final Supplier<Boolean> mFeatureFlag; /** + * Whether this role should ignore the requested permissions of a disabled system package when + * granting. + */ + private final boolean mIgnoreDisabledSystemPackageWhenGranting; + + /** * The string resource for the label of this role. */ @StringRes @@ -241,7 +248,8 @@ public class Role { public Role(@NonNull String name, boolean allowBypassingQualification, @Nullable RoleBehavior behavior, @Nullable String defaultHoldersResourceName, @StringRes int descriptionResource, boolean exclusive, boolean fallBackToDefaultHolder, - @Nullable Supplier<Boolean> featureFlag, @StringRes int labelResource, + @Nullable Supplier<Boolean> featureFlag, + boolean ignoreDisabledSystemPackageWhenGranting, @StringRes int labelResource, int maxSdkVersion, int minSdkVersion, boolean onlyGrantWhenAdded, boolean overrideUserWhenGranting, @StringRes int requestDescriptionResource, @StringRes int requestTitleResource, boolean requestable, @@ -259,6 +267,7 @@ public class Role { mExclusive = exclusive; mFallBackToDefaultHolder = fallBackToDefaultHolder; mFeatureFlag = featureFlag; + mIgnoreDisabledSystemPackageWhenGranting = ignoreDisabledSystemPackageWhenGranting; mLabelResource = labelResource; mMaxSdkVersion = maxSdkVersion; mMinSdkVersion = minSdkVersion; @@ -305,6 +314,13 @@ public class Role { return mFeatureFlag; } + /** + * @see #mIgnoreDisabledSystemPackageWhenGranting + */ + public boolean shouldIgnoreDisabledSystemPackageWhenGranting() { + return mIgnoreDisabledSystemPackageWhenGranting; + } + @StringRes public int getLabelResource() { return mLabelResource; @@ -355,6 +371,10 @@ public class Role { return mShowNone; } + public boolean isSystemOnly() { + return mSystemOnly; + } + public boolean isVisible() { return mVisible; } @@ -424,7 +444,8 @@ public class Role { * * @return whether this role is available based on SDK version */ - boolean isAvailableByFeatureFlagAndSdkVersion() { + @VisibleForTesting + public boolean isAvailableByFeatureFlagAndSdkVersion() { if (mFeatureFlag != null && !mFeatureFlag.get()) { return false; } @@ -819,7 +840,7 @@ public class Role { boolean overrideUser, @NonNull UserHandle user, @NonNull Context context) { boolean permissionOrAppOpChanged = Permissions.grantAsUser(packageName, Permissions.filterBySdkVersionAsUser(mPermissions, user, context), - SdkLevel.isAtLeastS() ? !mSystemOnly : true, overrideUser, true, false, false, + mIgnoreDisabledSystemPackageWhenGranting, overrideUser, true, false, false, user, context); List<String> appOpPermissionsToGrant = @@ -1105,6 +1126,8 @@ public class Role { + ", mExclusive=" + mExclusive + ", mFallBackToDefaultHolder=" + mFallBackToDefaultHolder + ", mFeatureFlag=" + mFeatureFlag + + ", mIgnoreDisabledSystemPackageWhenGranting=" + + mIgnoreDisabledSystemPackageWhenGranting + ", mLabelResource=" + mLabelResource + ", mMaxSdkVersion=" + mMaxSdkVersion + ", mMinSdkVersion=" + mMinSdkVersion diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java b/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java index f1a275daf..d4e5fa2d6 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java @@ -94,6 +94,8 @@ public class RoleParser { private static final String ATTRIBUTE_EXCLUSIVE = "exclusive"; private static final String ATTRIBUTE_FALL_BACK_TO_DEFAULT_HOLDER = "fallBackToDefaultHolder"; private static final String ATTRIBUTE_FEATURE_FLAG = "featureFlag"; + private static final String ATTRIBUTE_IGNORE_DISABLED_SYSTEM_PACKAGE_WHEN_GRANTING = + "ignoreDisabledSystemPackageWhenGranting"; private static final String ATTRIBUTE_LABEL = "label"; private static final String ATTRIBUTE_MAX_SDK_VERSION = "maxSdkVersion"; private static final String ATTRIBUTE_MIN_SDK_VERSION = "minSdkVersion"; @@ -156,16 +158,16 @@ public class RoleParser { @NonNull private final Context mContext; - private final boolean mValidationEnabled; + private final boolean mThrowOnError; public RoleParser(@NonNull Context context) { this(context, false); } @VisibleForTesting - public RoleParser(@NonNull Context context, boolean validationEnabled) { + public RoleParser(@NonNull Context context, boolean throwOnError) { mContext = context; - mValidationEnabled = validationEnabled; + mThrowOnError = throwOnError; } /** @@ -175,18 +177,21 @@ public class RoleParser { */ @NonNull public ArrayMap<String, Role> parse() { + Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> xml = parseRolesXml(); + if (xml == null) { + return new ArrayMap<>(); + } + return xml.second; + } + + @Nullable + @VisibleForTesting + public Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> parseRolesXml() { try (XmlResourceParser parser = getRolesXml()) { - Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> xml = parseXml(parser); - if (xml == null) { - return new ArrayMap<>(); - } - ArrayMap<String, PermissionSet> permissionSets = xml.first; - ArrayMap<String, Role> roles = xml.second; - validateResult(permissionSets, roles); - return roles; + return parseXml(parser); } catch (XmlPullParserException | IOException e) { throwOrLogMessage("Unable to parse roles.xml", e); - return new ArrayMap<>(); + return null; } } @@ -426,6 +431,11 @@ public class RoleParser { Supplier<Boolean> featureFlag = getAttributeMethodValue(parser, ATTRIBUTE_FEATURE_FLAG, boolean.class, sFeatureFlagFallback, TAG_ROLE); + boolean systemOnly = getAttributeBooleanValue(parser, ATTRIBUTE_SYSTEM_ONLY, false); + boolean ignoreDisabledSystemPackageWhenGranting = getAttributeBooleanValue(parser, + ATTRIBUTE_IGNORE_DISABLED_SYSTEM_PACKAGE_WHEN_GRANTING, + SdkLevel.isAtLeastS() ? !systemOnly : true); + int maxSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_MAX_SDK_VERSION, Build.VERSION_CODES.CUR_DEVELOPMENT); int minSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_MIN_SDK_VERSION, @@ -484,8 +494,6 @@ public class RoleParser { return null; } - boolean systemOnly = getAttributeBooleanValue(parser, ATTRIBUTE_SYSTEM_ONLY, false); - String uiBehaviorName = getAttributeValue(parser, ATTRIBUTE_UI_BEHAVIOR); List<RequiredComponent> requiredComponents = null; @@ -567,8 +575,9 @@ public class RoleParser { preferredActivities = Collections.emptyList(); } return new Role(name, allowBypassingQualification, behavior, defaultHoldersResourceName, - descriptionResource, exclusive, fallBackToDefaultHolder, featureFlag, labelResource, - maxSdkVersion, minSdkVersion, onlyGrantWhenAdded, overrideUserWhenGranting, + descriptionResource, exclusive, fallBackToDefaultHolder, featureFlag, + ignoreDisabledSystemPackageWhenGranting, labelResource, maxSdkVersion, + minSdkVersion, onlyGrantWhenAdded, overrideUserWhenGranting, requestDescriptionResource, requestTitleResource, requestable, searchKeywordsResource, shortLabelResource, showNone, statik, systemOnly, visible, requiredComponents, permissions, appOpPermissions, appOps, preferredActivities, @@ -624,7 +633,7 @@ public class RoleParser { int queryFlags = getAttributeIntValue(parser, ATTRIBUTE_QUERY_FLAGS, 0); IntentFilterData intentFilterData = null; List<RequiredMetaData> metaData = new ArrayList<>(); - List<String> validationMetaDataNames = mValidationEnabled ? new ArrayList<>() : null; + List<String> validationMetaDataNames = mThrowOnError ? new ArrayList<>() : null; int type; int depth; @@ -651,7 +660,7 @@ public class RoleParser { if (metaDataName == null) { continue; } - if (mValidationEnabled) { + if (mThrowOnError) { validateNoDuplicateElement(metaDataName, validationMetaDataNames, "meta data"); } @@ -668,7 +677,7 @@ public class RoleParser { RequiredMetaData requiredMetaData = new RequiredMetaData(metaDataName, metaDataValue, metaDataProhibited); metaData.add(requiredMetaData); - if (mValidationEnabled) { + if (mThrowOnError) { validationMetaDataNames.add(metaDataName); } break; @@ -1204,7 +1213,7 @@ public class RoleParser { } private void throwOrLogMessage(String message) { - if (mValidationEnabled) { + if (mThrowOnError) { throw new IllegalArgumentException(message); } else { Log.wtf(LOG_TAG, message); @@ -1212,7 +1221,7 @@ public class RoleParser { } private void throwOrLogMessage(String message, Throwable cause) { - if (mValidationEnabled) { + if (mThrowOnError) { throw new IllegalArgumentException(message, cause); } else { Log.wtf(LOG_TAG, message, cause); @@ -1222,168 +1231,4 @@ public class RoleParser { private void throwOrLogForUnknownTag(@NonNull XmlResourceParser parser) { throwOrLogMessage("Unknown tag: " + parser.getName()); } - - /** - * Validates the permission names with {@code PackageManager} and ensures that all app ops with - * a permission in {@code AppOpsManager} have declared that permission in its role and ensures - * that all preferred activities are listed in the required components. - */ - private void validateResult(@NonNull ArrayMap<String, PermissionSet> permissionSets, - @NonNull ArrayMap<String, Role> roles) { - if (!mValidationEnabled) { - return; - } - - int permissionSetsSize = permissionSets.size(); - for (int permissionSetsIndex = 0; permissionSetsIndex < permissionSetsSize; - permissionSetsIndex++) { - PermissionSet permissionSet = permissionSets.valueAt(permissionSetsIndex); - - List<Permission> permissions = permissionSet.getPermissions(); - int permissionsSize = permissions.size(); - for (int permissionsIndex = 0; permissionsIndex < permissionsSize; permissionsIndex++) { - Permission permission = permissions.get(permissionsIndex); - - validatePermission(permission); - } - } - - int rolesSize = roles.size(); - for (int rolesIndex = 0; rolesIndex < rolesSize; rolesIndex++) { - Role role = roles.valueAt(rolesIndex); - - if (!role.isAvailableByFeatureFlagAndSdkVersion()) { - continue; - } - - List<RequiredComponent> requiredComponents = role.getRequiredComponents(); - int requiredComponentsSize = requiredComponents.size(); - for (int requiredComponentsIndex = 0; requiredComponentsIndex < requiredComponentsSize; - requiredComponentsIndex++) { - RequiredComponent requiredComponent = requiredComponents.get( - requiredComponentsIndex); - - String permission = requiredComponent.getPermission(); - if (permission != null) { - validatePermission(permission); - } - } - - List<Permission> permissions = role.getPermissions(); - int permissionsSize = permissions.size(); - for (int i = 0; i < permissionsSize; i++) { - Permission permission = permissions.get(i); - - validatePermission(permission); - } - - List<AppOp> appOps = role.getAppOps(); - int appOpsSize = appOps.size(); - for (int i = 0; i < appOpsSize; i++) { - AppOp appOp = appOps.get(i); - - validateAppOp(appOp); - } - - List<Permission> appOpPermissions = role.getAppOpPermissions(); - int appOpPermissionsSize = appOpPermissions.size(); - for (int i = 0; i < appOpPermissionsSize; i++) { - validateAppOpPermission(appOpPermissions.get(i)); - } - - List<PreferredActivity> preferredActivities = role.getPreferredActivities(); - int preferredActivitiesSize = preferredActivities.size(); - for (int preferredActivitiesIndex = 0; - preferredActivitiesIndex < preferredActivitiesSize; - preferredActivitiesIndex++) { - PreferredActivity preferredActivity = preferredActivities.get( - preferredActivitiesIndex); - - if (!role.getRequiredComponents().contains(preferredActivity.getActivity())) { - throw new IllegalArgumentException("<activity> of <preferred-activity> not" - + " required in <required-components>, role: " + role.getName() - + ", preferred activity: " + preferredActivity); - } - } - } - } - - private void validatePermission(@NonNull Permission permission) { - if (!permission.isAvailableAsUser(Process.myUserHandle(), mContext)) { - return; - } - validatePermission(permission.getName(), true); - } - - private void validatePermission(@NonNull String permission) { - validatePermission(permission, false); - } - - private void validatePermission(@NonNull String permission, boolean enforceIsRuntimeOrRole) { - PackageManager packageManager = mContext.getPackageManager(); - boolean isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); - // Skip validation for car permissions which may not be available on all build targets. - if (!isAutomotive && permission.startsWith("android.car")) { - return; - } - - PermissionInfo permissionInfo; - try { - permissionInfo = packageManager.getPermissionInfo(permission, 0); - } catch (PackageManager.NameNotFoundException e) { - throw new IllegalArgumentException("Unknown permission: " + permission, e); - } - - if (enforceIsRuntimeOrRole) { - if (!(permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS - || (permissionInfo.getProtectionFlags() & PermissionInfo.PROTECTION_FLAG_ROLE) - == PermissionInfo.PROTECTION_FLAG_ROLE)) { - throw new IllegalArgumentException( - "Permission is not a runtime or role permission: " + permission); - } - } - } - - private void validateAppOpPermission(@NonNull Permission appOpPermission) { - if (!appOpPermission.isAvailableAsUser(Process.myUserHandle(), mContext)) { - return; - } - validateAppOpPermission(appOpPermission.getName()); - } - - private void validateAppOpPermission(@NonNull String appOpPermission) { - PackageManager packageManager = mContext.getPackageManager(); - PermissionInfo permissionInfo; - try { - permissionInfo = packageManager.getPermissionInfo(appOpPermission, 0); - } catch (PackageManager.NameNotFoundException e) { - throw new IllegalArgumentException("Unknown app op permission: " + appOpPermission, e); - } - if ((permissionInfo.getProtectionFlags() & PermissionInfo.PROTECTION_FLAG_APPOP) - != PermissionInfo.PROTECTION_FLAG_APPOP) { - throw new IllegalArgumentException("Permission is not an app op permission: " - + appOpPermission); - } - } - - private void validateAppOp(@NonNull AppOp appOp) { - if (!appOp.isAvailableByFeatureFlagAndSdkVersion()) { - return; - } - // This throws IllegalArgumentException if app op is unknown. - String permission = AppOpsManager.opToPermission(appOp.getName()); - if (permission != null) { - PackageManager packageManager = mContext.getPackageManager(); - PermissionInfo permissionInfo; - try { - permissionInfo = packageManager.getPermissionInfo(permission, 0); - } catch (PackageManager.NameNotFoundException e) { - throw new RuntimeException(e); - } - if (permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS) { - throw new IllegalArgumentException("App op has an associated runtime permission: " - + appOp.getName()); - } - } - } } diff --git a/PermissionController/src/com/android/permissioncontroller/role/Role.md b/PermissionController/src/com/android/permissioncontroller/role/Role.md index d4a514784..255214495 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/Role.md +++ b/PermissionController/src/com/android/permissioncontroller/role/Role.md @@ -62,6 +62,13 @@ is optional and defaults to `false`. the Java method on the `Flags` class which will be invoked via reflection. Note that any new aconfig library dependency will need corresponding jarjar rules for PermissionController and the system service JAR. +- `ignoreDisabledSystemPackageWhenGranting`: Whether the role should ignore the requested +permissions of the disabled system package (if any) when granting permissions. If `false`, the +permission will need to be requested by the disabled system package as well, if there is one. This +attribute is optional and defaults to the opposite of `systemOnly` on Android S+, or `true` below +Android S. **Note:** Extra care should be taken when adding a runtime permission to a role with +this attribute explicitly set to `true`, because that may allow apps to update and silently obtain +a new runtime permission. - `label`: The string resource for the label of the role, e.g. `@string/role_sms_label`, which says "Default SMS app". For default apps, this string will appear in the default app detail page as the title. This attribute is required if the role is `visible`. diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/role/model/RoleParserTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/role/model/RoleParserTest.kt index ec6ab4eff..6a852b20b 100644 --- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/role/model/RoleParserTest.kt +++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/role/model/RoleParserTest.kt @@ -16,9 +16,17 @@ package com.android.permissioncontroller.tests.mocking.role.model +import android.app.AppOpsManager +import android.content.pm.PackageManager +import android.content.pm.PermissionInfo +import android.os.Process import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.android.permissioncontroller.role.model.RoleParserInitializer +import com.android.role.controller.model.AppOp +import com.android.role.controller.model.Permission +import com.android.role.controller.model.PermissionSet +import com.android.role.controller.model.Role import com.android.role.controller.model.RoleParser import org.junit.BeforeClass import org.junit.Test @@ -37,9 +45,10 @@ class RoleParserTest { private val instrumentation = InstrumentationRegistry.getInstrumentation() private val uiAutomation = instrumentation.uiAutomation private val targetContext = instrumentation.targetContext + private val packageManager = targetContext.packageManager @Test - fun testParseRolesWithValidation() { + fun parseRoles() { // We may need to call privileged APIs to determine availability of things. uiAutomation.adoptShellPermissionIdentity() try { @@ -48,4 +57,140 @@ class RoleParserTest { uiAutomation.dropShellPermissionIdentity() } } + + @Test + fun validateRoles() { + // We may need to call privileged APIs to determine availability of things. + uiAutomation.adoptShellPermissionIdentity() + try { + val xml = RoleParser(targetContext).parseRolesXml() + requireNotNull(xml) + validateRoles(xml.first, xml.second) + } finally { + uiAutomation.dropShellPermissionIdentity() + } + } + + fun validateRoles(permissionSets: Map<String, PermissionSet>, roles: Map<String, Role>) { + for (permissionSet in permissionSets.values) { + for (permission in permissionSet.permissions) { + validatePermission(permission, false) + } + } + + for (role in roles.values) { + if (!role.isAvailableByFeatureFlagAndSdkVersion) { + continue + } + + for (requiredComponent in role.requiredComponents) { + val permission = requiredComponent.permission + if (permission != null) { + validatePermission(permission) + } + } + + for (permission in role.permissions) { + // Prevent system-only roles that ignore disabled system packages from + // granting runtime permissions for now, since that may allow apps to update and + // silently obtain a new runtime permission. + val enforceNotRuntime = role.isSystemOnly + && role.shouldIgnoreDisabledSystemPackageWhenGranting() + validatePermission(permission, enforceNotRuntime) + } + + for (appOp in role.appOps) { + validateAppOp(appOp) + } + + for (appOpPermission in role.appOpPermissions) { + validateAppOpPermission(appOpPermission) + } + + for (preferredActivity in role.preferredActivities) { + require(preferredActivity.activity in role.requiredComponents) { + "<activity> of <preferred-activity> not required in <required-components>," + + " role: ${role.name}, preferred activity: $preferredActivity" + } + } + } + } + + private fun validatePermission(permission: Permission, enforceNotRuntime: Boolean) { + if (!permission.isAvailableAsUser(Process.myUserHandle(), targetContext)) { + return + } + validatePermission(permission.name, true, enforceNotRuntime) + } + + private fun validatePermission(permissionName: String) { + validatePermission(permissionName, false, false) + } + + private fun validatePermission( + permissionName: String, + enforceIsRuntimeOrRole: Boolean, + enforceNotRuntime: Boolean, + ) { + val isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) + // Skip validation for car permissions which may not be available on all build targets. + if (!isAutomotive && permissionName.startsWith("android.car")) { + return + } + + val permissionInfo = + try { + packageManager.getPermissionInfo(permissionName, 0) + } catch (e: PackageManager.NameNotFoundException) { + throw IllegalArgumentException("Unknown permission: $permissionName", e) + } + + if (enforceIsRuntimeOrRole) { + require( + permissionInfo.protection == PermissionInfo.PROTECTION_DANGEROUS || + permissionInfo.protectionFlags and PermissionInfo.PROTECTION_FLAG_ROLE + == PermissionInfo.PROTECTION_FLAG_ROLE + ) { "Permission is not a runtime or role permission: $permissionName" } + } + + if (enforceNotRuntime) { + require(permissionInfo.protection != PermissionInfo.PROTECTION_DANGEROUS) { + "Permission is a runtime permission: $permissionName" + } + } + } + + private fun validateAppOpPermission(appOpPermission: Permission) { + if (!appOpPermission.isAvailableAsUser(Process.myUserHandle(), targetContext)) { + return + } + validateAppOpPermission(appOpPermission.name) + } + + private fun validateAppOpPermission(permissionName: String) { + val permissionInfo = + try { + packageManager.getPermissionInfo(permissionName, 0) + } catch (e: PackageManager.NameNotFoundException) { + throw IllegalArgumentException("Unknown app op permission: $permissionName", e) + } + require( + permissionInfo.protectionFlags and PermissionInfo.PROTECTION_FLAG_APPOP + == PermissionInfo.PROTECTION_FLAG_APPOP + ) { "Permission is not an app op permission: $permissionName" } + } + + private fun validateAppOp(appOp: AppOp) { + if (!appOp.isAvailableByFeatureFlagAndSdkVersion) { + return + } + // This throws IllegalArgumentException if app op is unknown. + val permissionName = AppOpsManager.opToPermission(appOp.name) + if (permissionName != null) { + val permissionInfo = packageManager.getPermissionInfo(permissionName, 0) + require(permissionInfo.protection != PermissionInfo.PROTECTION_DANGEROUS) { + "App op has an associated runtime permission: ${appOp.name}" + } + } + } } |