1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.permissioncontroller.role.model; 18 19 import android.app.AppOpsManager; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.PackageManager; 23 import android.content.pm.PermissionInfo; 24 import android.content.res.XmlResourceParser; 25 import android.os.Build; 26 import android.util.ArrayMap; 27 import android.util.Log; 28 import android.util.Pair; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.Nullable; 32 import androidx.annotation.VisibleForTesting; 33 34 import com.android.permissioncontroller.R; 35 36 import org.xmlpull.v1.XmlPullParserException; 37 38 import java.io.IOException; 39 import java.util.ArrayList; 40 import java.util.Collection; 41 import java.util.Collections; 42 import java.util.List; 43 import java.util.Objects; 44 45 /** 46 * Parser for {@link Role} definitions. 47 */ 48 @VisibleForTesting 49 public class RoleParser { 50 51 private static final String LOG_TAG = RoleParser.class.getSimpleName(); 52 53 private static final String TAG_ROLES = "roles"; 54 private static final String TAG_PERMISSION_SET = "permission-set"; 55 private static final String TAG_PERMISSION = "permission"; 56 private static final String TAG_ROLE = "role"; 57 private static final String TAG_REQUIRED_COMPONENTS = "required-components"; 58 private static final String TAG_ACTIVITY = "activity"; 59 private static final String TAG_PROVIDER = "provider"; 60 private static final String TAG_RECEIVER = "receiver"; 61 private static final String TAG_SERVICE = "service"; 62 private static final String TAG_INTENT_FILTER = "intent-filter"; 63 private static final String TAG_ACTION = "action"; 64 private static final String TAG_CATEGORY = "category"; 65 private static final String TAG_DATA = "data"; 66 private static final String TAG_META_DATA = "meta-data"; 67 private static final String TAG_PERMISSIONS = "permissions"; 68 private static final String TAG_APP_OP_PERMISSIONS = "app-op-permissions"; 69 private static final String TAG_APP_OP_PERMISSION = "app-op-permission"; 70 private static final String TAG_APP_OPS = "app-ops"; 71 private static final String TAG_APP_OP = "app-op"; 72 private static final String TAG_PREFERRED_ACTIVITIES = "preferred-activities"; 73 private static final String TAG_PREFERRED_ACTIVITY = "preferred-activity"; 74 private static final String ATTRIBUTE_NAME = "name"; 75 private static final String ATTRIBUTE_ALLOW_BYPASSING_QUALIFICATION = 76 "allowBypassingQualification"; 77 private static final String ATTRIBUTE_BEHAVIOR = "behavior"; 78 private static final String ATTRIBUTE_DEFAULT_HOLDERS = "defaultHolders"; 79 private static final String ATTRIBUTE_DESCRIPTION = "description"; 80 private static final String ATTRIBUTE_EXCLUSIVE = "exclusive"; 81 private static final String ATTRIBUTE_FALL_BACK_TO_DEFAULT_HOLDER = "fallBackToDefaultHolder"; 82 private static final String ATTRIBUTE_LABEL = "label"; 83 private static final String ATTRIBUTE_MIN_SDK_VERSION = "minSdkVersion"; 84 private static final String ATTRIBUTE_OVERRIDE_USER_WHEN_GRANTING = "overrideUserWhenGranting"; 85 private static final String ATTRIBUTE_QUERY_FLAGS = "queryFlags"; 86 private static final String ATTRIBUTE_REQUEST_TITLE = "requestTitle"; 87 private static final String ATTRIBUTE_REQUEST_DESCRIPTION = "requestDescription"; 88 private static final String ATTRIBUTE_REQUESTABLE = "requestable"; 89 private static final String ATTRIBUTE_SEARCH_KEYWORDS = "searchKeywords"; 90 private static final String ATTRIBUTE_SHORT_LABEL = "shortLabel"; 91 private static final String ATTRIBUTE_SHOW_NONE = "showNone"; 92 private static final String ATTRIBUTE_STATIC = "static"; 93 private static final String ATTRIBUTE_SYSTEM_ONLY = "systemOnly"; 94 private static final String ATTRIBUTE_VISIBLE = "visible"; 95 private static final String ATTRIBUTE_MIN_TARGET_SDK_VERSION = "minTargetSdkVersion"; 96 private static final String ATTRIBUTE_PERMISSION = "permission"; 97 private static final String ATTRIBUTE_PROHIBITED = "prohibited"; 98 private static final String ATTRIBUTE_VALUE = "value"; 99 private static final String ATTRIBUTE_SCHEME = "scheme"; 100 private static final String ATTRIBUTE_MIME_TYPE = "mimeType"; 101 private static final String ATTRIBUTE_MAX_TARGET_SDK_VERSION = "maxTargetSdkVersion"; 102 private static final String ATTRIBUTE_MODE = "mode"; 103 104 private static final String MODE_NAME_ALLOWED = "allowed"; 105 private static final String MODE_NAME_IGNORED = "ignored"; 106 private static final String MODE_NAME_ERRORED = "errored"; 107 private static final String MODE_NAME_DEFAULT = "default"; 108 private static final String MODE_NAME_FOREGROUND = "foreground"; 109 private static final ArrayMap<String, Integer> sModeNameToMode = new ArrayMap<>(); 110 static { sModeNameToMode.put(MODE_NAME_ALLOWED, AppOpsManager.MODE_ALLOWED)111 sModeNameToMode.put(MODE_NAME_ALLOWED, AppOpsManager.MODE_ALLOWED); sModeNameToMode.put(MODE_NAME_IGNORED, AppOpsManager.MODE_IGNORED)112 sModeNameToMode.put(MODE_NAME_IGNORED, AppOpsManager.MODE_IGNORED); sModeNameToMode.put(MODE_NAME_ERRORED, AppOpsManager.MODE_ERRORED)113 sModeNameToMode.put(MODE_NAME_ERRORED, AppOpsManager.MODE_ERRORED); sModeNameToMode.put(MODE_NAME_DEFAULT, AppOpsManager.MODE_DEFAULT)114 sModeNameToMode.put(MODE_NAME_DEFAULT, AppOpsManager.MODE_DEFAULT); sModeNameToMode.put(MODE_NAME_FOREGROUND, AppOpsManager.MODE_FOREGROUND)115 sModeNameToMode.put(MODE_NAME_FOREGROUND, AppOpsManager.MODE_FOREGROUND); 116 } 117 118 @NonNull 119 private final Context mContext; 120 121 private final boolean mValidationEnabled; 122 RoleParser(@onNull Context context)123 public RoleParser(@NonNull Context context) { 124 this(context, false); 125 } 126 127 @VisibleForTesting RoleParser(@onNull Context context, boolean validationEnabled)128 public RoleParser(@NonNull Context context, boolean validationEnabled) { 129 mContext = context; 130 mValidationEnabled = validationEnabled; 131 } 132 133 /** 134 * Parse the roles defined in {@code roles.xml}. 135 * 136 * @return a map from role name to {@link Role} instances 137 */ 138 @NonNull parse()139 public ArrayMap<String, Role> parse() { 140 try (XmlResourceParser parser = mContext.getResources().getXml(R.xml.roles)) { 141 Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> xml = parseXml(parser); 142 if (xml == null) { 143 return new ArrayMap<>(); 144 } 145 ArrayMap<String, PermissionSet> permissionSets = xml.first; 146 ArrayMap<String, Role> roles = xml.second; 147 validateResult(permissionSets, roles); 148 return roles; 149 } catch (XmlPullParserException | IOException e) { 150 throwOrLogMessage("Unable to parse roles.xml", e); 151 return new ArrayMap<>(); 152 } 153 } 154 155 @Nullable parseXml( @onNull XmlResourceParser parser)156 private Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> parseXml( 157 @NonNull XmlResourceParser parser) throws IOException, XmlPullParserException { 158 Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> xml = null; 159 160 int type; 161 int depth; 162 int innerDepth = parser.getDepth() + 1; 163 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 164 && ((depth = parser.getDepth()) >= innerDepth 165 || type != XmlResourceParser.END_TAG)) { 166 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 167 continue; 168 } 169 170 if (parser.getName().equals(TAG_ROLES)) { 171 if (xml != null) { 172 throwOrLogMessage("Duplicate <roles>"); 173 skipCurrentTag(parser); 174 continue; 175 } 176 xml = parseRoles(parser); 177 } else { 178 throwOrLogForUnknownTag(parser); 179 skipCurrentTag(parser); 180 } 181 } 182 183 if (xml == null) { 184 throwOrLogMessage("Missing <roles>"); 185 } 186 return xml; 187 } 188 189 @NonNull parseRoles( @onNull XmlResourceParser parser)190 private Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> parseRoles( 191 @NonNull XmlResourceParser parser) throws IOException, XmlPullParserException { 192 ArrayMap<String, PermissionSet> permissionSets = new ArrayMap<>(); 193 ArrayMap<String, Role> roles = new ArrayMap<>(); 194 195 int type; 196 int depth; 197 int innerDepth = parser.getDepth() + 1; 198 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 199 && ((depth = parser.getDepth()) >= innerDepth 200 || type != XmlResourceParser.END_TAG)) { 201 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 202 continue; 203 } 204 205 switch (parser.getName()) { 206 case TAG_PERMISSION_SET: { 207 PermissionSet permissionSet = parsePermissionSet(parser); 208 if (permissionSet == null) { 209 continue; 210 } 211 validateNoDuplicateElement(permissionSet.getName(), permissionSets.keySet(), 212 "permission set"); 213 permissionSets.put(permissionSet.getName(), permissionSet); 214 break; 215 } 216 case TAG_ROLE: { 217 Role role = parseRole(parser, permissionSets); 218 if (role == null) { 219 continue; 220 } 221 validateNoDuplicateElement(role.getName(), roles.keySet(), "role"); 222 roles.put(role.getName(), role); 223 break; 224 } 225 default: 226 throwOrLogForUnknownTag(parser); 227 skipCurrentTag(parser); 228 } 229 } 230 231 return new Pair<>(permissionSets, roles); 232 } 233 234 @Nullable parsePermissionSet(@onNull XmlResourceParser parser)235 private PermissionSet parsePermissionSet(@NonNull XmlResourceParser parser) 236 throws IOException, XmlPullParserException { 237 String name = requireAttributeValue(parser, ATTRIBUTE_NAME, TAG_PERMISSION_SET); 238 if (name == null) { 239 skipCurrentTag(parser); 240 return null; 241 } 242 243 List<Permission> permissions = new ArrayList<>(); 244 245 int type; 246 int depth; 247 int innerDepth = parser.getDepth() + 1; 248 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 249 && ((depth = parser.getDepth()) >= innerDepth 250 || type != XmlResourceParser.END_TAG)) { 251 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 252 continue; 253 } 254 255 if (parser.getName().equals(TAG_PERMISSION)) { 256 Permission permission = parsePermission(parser); 257 if (permission == null) { 258 continue; 259 } 260 validateNoDuplicateElement(permission, permissions, "permission"); 261 permissions.add(permission); 262 } else { 263 throwOrLogForUnknownTag(parser); 264 skipCurrentTag(parser); 265 } 266 } 267 268 return new PermissionSet(name, permissions); 269 } 270 271 @Nullable parsePermission(@onNull XmlResourceParser parser)272 private Permission parsePermission(@NonNull XmlResourceParser parser) throws IOException, 273 XmlPullParserException { 274 String name = requireAttributeValue(parser, ATTRIBUTE_NAME, TAG_PERMISSION); 275 if (name == null) { 276 skipCurrentTag(parser); 277 return null; 278 } 279 int minSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_MIN_SDK_VERSION, 280 Build.VERSION_CODES.BASE); 281 return new Permission(name, minSdkVersion); 282 } 283 284 @Nullable parseRole(@onNull XmlResourceParser parser, @NonNull ArrayMap<String, PermissionSet> permissionSets)285 private Role parseRole(@NonNull XmlResourceParser parser, 286 @NonNull ArrayMap<String, PermissionSet> permissionSets) throws IOException, 287 XmlPullParserException { 288 String name = requireAttributeValue(parser, ATTRIBUTE_NAME, TAG_ROLE); 289 if (name == null) { 290 skipCurrentTag(parser); 291 return null; 292 } 293 294 boolean allowBypassingQualification = getAttributeBooleanValue(parser, 295 ATTRIBUTE_ALLOW_BYPASSING_QUALIFICATION, false); 296 297 String behaviorClassSimpleName = getAttributeValue(parser, ATTRIBUTE_BEHAVIOR); 298 RoleBehavior behavior; 299 if (behaviorClassSimpleName != null) { 300 String behaviorClassName = RoleParser.class.getPackage().getName() + '.' 301 + behaviorClassSimpleName; 302 try { 303 behavior = (RoleBehavior) Class.forName(behaviorClassName).newInstance(); 304 } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { 305 throwOrLogMessage("Unable to instantiate behavior: " + behaviorClassName, e); 306 skipCurrentTag(parser); 307 return null; 308 } 309 } else { 310 behavior = null; 311 } 312 313 String defaultHoldersResourceName = getAttributeValue(parser, ATTRIBUTE_DEFAULT_HOLDERS); 314 315 int descriptionResource = getAttributeResourceValue(parser, ATTRIBUTE_DESCRIPTION, 0); 316 317 boolean visible = getAttributeBooleanValue(parser, ATTRIBUTE_VISIBLE, true); 318 Integer labelResource; 319 Integer shortLabelResource; 320 if (visible) { 321 if (descriptionResource == 0) { 322 skipCurrentTag(parser); 323 return null; 324 } 325 326 labelResource = requireAttributeResourceValue(parser, ATTRIBUTE_LABEL, 0, TAG_ROLE); 327 if (labelResource == null) { 328 skipCurrentTag(parser); 329 return null; 330 } 331 332 shortLabelResource = requireAttributeResourceValue(parser, ATTRIBUTE_SHORT_LABEL, 0, 333 TAG_ROLE); 334 if (shortLabelResource == null) { 335 skipCurrentTag(parser); 336 return null; 337 } 338 } else { 339 labelResource = 0; 340 shortLabelResource = 0; 341 } 342 343 Boolean exclusive = requireAttributeBooleanValue(parser, ATTRIBUTE_EXCLUSIVE, true, 344 TAG_ROLE); 345 if (exclusive == null) { 346 skipCurrentTag(parser); 347 return null; 348 } 349 350 boolean fallBackToDefaultHolder = getAttributeBooleanValue(parser, 351 ATTRIBUTE_FALL_BACK_TO_DEFAULT_HOLDER, false); 352 353 int minSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_MIN_SDK_VERSION, 354 Build.VERSION_CODES.BASE); 355 356 boolean overrideUserWhenGranting = getAttributeBooleanValue(parser, 357 ATTRIBUTE_OVERRIDE_USER_WHEN_GRANTING, false); 358 359 boolean requestable = getAttributeBooleanValue(parser, ATTRIBUTE_REQUESTABLE, visible); 360 Integer requestDescriptionResource; 361 Integer requestTitleResource; 362 if (requestable) { 363 requestDescriptionResource = requireAttributeResourceValue(parser, 364 ATTRIBUTE_REQUEST_DESCRIPTION, 0, TAG_ROLE); 365 if (requestDescriptionResource == null) { 366 skipCurrentTag(parser); 367 return null; 368 } 369 370 requestTitleResource = requireAttributeResourceValue(parser, ATTRIBUTE_REQUEST_TITLE, 0, 371 TAG_ROLE); 372 if (requestTitleResource == null) { 373 skipCurrentTag(parser); 374 return null; 375 } 376 } else { 377 requestDescriptionResource = 0; 378 requestTitleResource = 0; 379 } 380 381 int searchKeywordsResource = getAttributeResourceValue(parser, ATTRIBUTE_SEARCH_KEYWORDS, 382 0); 383 384 boolean showNone = getAttributeBooleanValue(parser, ATTRIBUTE_SHOW_NONE, false); 385 if (showNone && !exclusive) { 386 throwOrLogMessage("showNone=\"true\" is invalid for a non-exclusive role: " + name); 387 skipCurrentTag(parser); 388 return null; 389 } 390 391 boolean statik = getAttributeBooleanValue(parser, ATTRIBUTE_STATIC, false); 392 if (statik && (visible || requestable)) { 393 throwOrLogMessage("static=\"true\" is invalid for a visible or requestable role: " 394 + name); 395 skipCurrentTag(parser); 396 return null; 397 } 398 399 boolean systemOnly = getAttributeBooleanValue(parser, ATTRIBUTE_SYSTEM_ONLY, false); 400 401 List<RequiredComponent> requiredComponents = null; 402 List<Permission> permissions = null; 403 List<String> appOpPermissions = null; 404 List<AppOp> appOps = null; 405 List<PreferredActivity> preferredActivities = null; 406 407 int type; 408 int depth; 409 int innerDepth = parser.getDepth() + 1; 410 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 411 && ((depth = parser.getDepth()) >= innerDepth 412 || type != XmlResourceParser.END_TAG)) { 413 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 414 continue; 415 } 416 417 switch (parser.getName()) { 418 case TAG_REQUIRED_COMPONENTS: 419 if (requiredComponents != null) { 420 throwOrLogMessage("Duplicate <required-components> in role: " + name); 421 skipCurrentTag(parser); 422 continue; 423 } 424 requiredComponents = parseRequiredComponents(parser); 425 break; 426 case TAG_PERMISSIONS: 427 if (permissions != null) { 428 throwOrLogMessage("Duplicate <permissions> in role: " + name); 429 skipCurrentTag(parser); 430 continue; 431 } 432 permissions = parsePermissions(parser, permissionSets); 433 break; 434 case TAG_APP_OP_PERMISSIONS: 435 if (appOpPermissions != null) { 436 throwOrLogMessage("Duplicate <app-op-permissions> in role: " + name); 437 skipCurrentTag(parser); 438 continue; 439 } 440 appOpPermissions = parseAppOpPermissions(parser); 441 break; 442 case TAG_APP_OPS: 443 if (appOps != null) { 444 throwOrLogMessage("Duplicate <app-ops> in role: " + name); 445 skipCurrentTag(parser); 446 continue; 447 } 448 appOps = parseAppOps(parser); 449 break; 450 case TAG_PREFERRED_ACTIVITIES: 451 if (preferredActivities != null) { 452 throwOrLogMessage("Duplicate <preferred-activities> in role: " + name); 453 skipCurrentTag(parser); 454 continue; 455 } 456 preferredActivities = parsePreferredActivities(parser); 457 break; 458 default: 459 throwOrLogForUnknownTag(parser); 460 skipCurrentTag(parser); 461 } 462 } 463 464 if (requiredComponents == null) { 465 requiredComponents = Collections.emptyList(); 466 } 467 if (permissions == null) { 468 permissions = Collections.emptyList(); 469 } 470 if (appOpPermissions == null) { 471 appOpPermissions = Collections.emptyList(); 472 } 473 if (appOps == null) { 474 appOps = Collections.emptyList(); 475 } 476 if (preferredActivities == null) { 477 preferredActivities = Collections.emptyList(); 478 } 479 return new Role(name, allowBypassingQualification, behavior, defaultHoldersResourceName, 480 descriptionResource, exclusive, fallBackToDefaultHolder, labelResource, 481 minSdkVersion, overrideUserWhenGranting, requestDescriptionResource, 482 requestTitleResource, requestable, searchKeywordsResource, shortLabelResource, 483 showNone, statik, systemOnly, visible, requiredComponents, permissions, 484 appOpPermissions, appOps, preferredActivities); 485 } 486 487 @NonNull parseRequiredComponents( @onNull XmlResourceParser parser)488 private List<RequiredComponent> parseRequiredComponents( 489 @NonNull XmlResourceParser parser) throws IOException, XmlPullParserException { 490 List<RequiredComponent> requiredComponents = new ArrayList<>(); 491 492 int type; 493 int depth; 494 int innerDepth = parser.getDepth() + 1; 495 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 496 && ((depth = parser.getDepth()) >= innerDepth 497 || type != XmlResourceParser.END_TAG)) { 498 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 499 continue; 500 } 501 502 String name = parser.getName(); 503 switch (name) { 504 case TAG_ACTIVITY: 505 case TAG_PROVIDER: 506 case TAG_RECEIVER: 507 case TAG_SERVICE: { 508 RequiredComponent requiredComponent = parseRequiredComponent(parser, name); 509 if (requiredComponent == null) { 510 continue; 511 } 512 validateNoDuplicateElement(requiredComponent, requiredComponents, 513 "require component"); 514 requiredComponents.add(requiredComponent); 515 break; 516 } 517 default: 518 throwOrLogForUnknownTag(parser); 519 skipCurrentTag(parser); 520 } 521 } 522 523 return requiredComponents; 524 } 525 526 @Nullable parseRequiredComponent(@onNull XmlResourceParser parser, @NonNull String name)527 private RequiredComponent parseRequiredComponent(@NonNull XmlResourceParser parser, 528 @NonNull String name) throws IOException, XmlPullParserException { 529 int minTargetSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_MIN_TARGET_SDK_VERSION, 530 Build.VERSION_CODES.BASE); 531 String permission = getAttributeValue(parser, ATTRIBUTE_PERMISSION); 532 int queryFlags = getAttributeIntValue(parser, ATTRIBUTE_QUERY_FLAGS, 0); 533 IntentFilterData intentFilterData = null; 534 List<RequiredMetaData> metaData = new ArrayList<>(); 535 List<String> validationMetaDataNames = mValidationEnabled ? new ArrayList<>() : null; 536 537 int type; 538 int depth; 539 int innerDepth = parser.getDepth() + 1; 540 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 541 && ((depth = parser.getDepth()) >= innerDepth 542 || type != XmlResourceParser.END_TAG)) { 543 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 544 continue; 545 } 546 547 switch (parser.getName()) { 548 case TAG_INTENT_FILTER: 549 if (intentFilterData != null) { 550 throwOrLogMessage("Duplicate <intent-filter> in <" + name + ">"); 551 skipCurrentTag(parser); 552 continue; 553 } 554 intentFilterData = parseIntentFilterData(parser); 555 break; 556 case TAG_META_DATA: 557 String metaDataName = requireAttributeValue(parser, ATTRIBUTE_NAME, 558 TAG_META_DATA); 559 if (metaDataName == null) { 560 continue; 561 } 562 if (mValidationEnabled) { 563 validateNoDuplicateElement(metaDataName, validationMetaDataNames, 564 "meta data"); 565 } 566 // HACK: Only support boolean for now. 567 // TODO(b/211568084): Support android:resource and other types of android:value, 568 // maybe by switching to TypedArray and styleables. 569 Boolean metaDataValue = requireAttributeBooleanValue(parser, ATTRIBUTE_VALUE, 570 false, TAG_META_DATA); 571 if (metaDataValue == null) { 572 continue; 573 } 574 boolean metaDataProhibited = getAttributeBooleanValue(parser, 575 ATTRIBUTE_PROHIBITED, false); 576 RequiredMetaData requiredMetaData = new RequiredMetaData(metaDataName, 577 metaDataValue, metaDataProhibited); 578 metaData.add(requiredMetaData); 579 if (mValidationEnabled) { 580 validationMetaDataNames.add(metaDataName); 581 } 582 break; 583 default: 584 throwOrLogForUnknownTag(parser); 585 skipCurrentTag(parser); 586 } 587 } 588 589 if (intentFilterData == null) { 590 throwOrLogMessage("Missing <intent-filter> in <" + name + ">"); 591 return null; 592 } 593 switch (name) { 594 case TAG_ACTIVITY: 595 return new RequiredActivity(intentFilterData, minTargetSdkVersion, permission, 596 queryFlags, metaData); 597 case TAG_PROVIDER: 598 return new RequiredContentProvider(intentFilterData, minTargetSdkVersion, 599 permission, queryFlags, metaData); 600 case TAG_RECEIVER: 601 return new RequiredBroadcastReceiver(intentFilterData, minTargetSdkVersion, 602 permission, queryFlags, metaData); 603 case TAG_SERVICE: 604 return new RequiredService(intentFilterData, minTargetSdkVersion, permission, 605 queryFlags, metaData); 606 default: 607 throwOrLogMessage("Unknown tag <" + name + ">"); 608 return null; 609 } 610 } 611 612 @Nullable parseIntentFilterData(@onNull XmlResourceParser parser)613 private IntentFilterData parseIntentFilterData(@NonNull XmlResourceParser parser) 614 throws IOException, XmlPullParserException { 615 String action = null; 616 List<String> categories = new ArrayList<>(); 617 boolean hasData = false; 618 String dataScheme = null; 619 String dataType = null; 620 621 int type; 622 int depth; 623 int innerDepth = parser.getDepth() + 1; 624 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 625 && ((depth = parser.getDepth()) >= innerDepth 626 || type != XmlResourceParser.END_TAG)) { 627 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 628 continue; 629 } 630 631 switch (parser.getName()) { 632 case TAG_ACTION: 633 if (action != null) { 634 throwOrLogMessage("Duplicate <action> in <intent-filter>"); 635 skipCurrentTag(parser); 636 continue; 637 } 638 action = requireAttributeValue(parser, ATTRIBUTE_NAME, TAG_ACTION); 639 break; 640 case TAG_CATEGORY: { 641 String category = requireAttributeValue(parser, ATTRIBUTE_NAME, TAG_CATEGORY); 642 if (category == null) { 643 continue; 644 } 645 validateIntentFilterCategory(category); 646 validateNoDuplicateElement(category, categories, "category"); 647 categories.add(category); 648 break; 649 } 650 case TAG_DATA: 651 if (!hasData) { 652 hasData = true; 653 } else { 654 throwOrLogMessage("Duplicate <data> in <intent-filter>"); 655 skipCurrentTag(parser); 656 continue; 657 } 658 dataScheme = getAttributeValue(parser, ATTRIBUTE_SCHEME); 659 dataType = getAttributeValue(parser, ATTRIBUTE_MIME_TYPE); 660 if (dataType != null) { 661 validateIntentFilterDataType(dataType); 662 } 663 break; 664 default: 665 throwOrLogForUnknownTag(parser); 666 skipCurrentTag(parser); 667 } 668 } 669 670 if (action == null) { 671 throwOrLogMessage("Missing <action> in <intent-filter>"); 672 return null; 673 } 674 return new IntentFilterData(action, categories, dataScheme, dataType); 675 } 676 validateIntentFilterCategory(@onNull String category)677 private void validateIntentFilterCategory(@NonNull String category) { 678 if (Objects.equals(category, Intent.CATEGORY_DEFAULT)) { 679 throwOrLogMessage("<category> should not include " + Intent.CATEGORY_DEFAULT); 680 } 681 } 682 683 /** 684 * Validates the data type with the same logic in {@link 685 * android.content.IntentFilter#addDataType(String)} to prevent the {@code 686 * MalformedMimeTypeException}. 687 */ validateIntentFilterDataType(@onNull String type)688 private void validateIntentFilterDataType(@NonNull String type) { 689 int slashIndex = type.indexOf('/'); 690 if (slashIndex <= 0 || type.length() < slashIndex + 2) { 691 throwOrLogMessage("Invalid attribute \"mimeType\" value on <data>: " + type); 692 } 693 } 694 695 @NonNull parsePermissions(@onNull XmlResourceParser parser, @NonNull ArrayMap<String, PermissionSet> permissionSets)696 private List<Permission> parsePermissions(@NonNull XmlResourceParser parser, 697 @NonNull ArrayMap<String, PermissionSet> permissionSets) throws IOException, 698 XmlPullParserException { 699 List<Permission> permissions = new ArrayList<>(); 700 701 int type; 702 int depth; 703 int innerDepth = parser.getDepth() + 1; 704 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 705 && ((depth = parser.getDepth()) >= innerDepth 706 || type != XmlResourceParser.END_TAG)) { 707 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 708 continue; 709 } 710 711 switch (parser.getName()) { 712 case TAG_PERMISSION_SET: { 713 String permissionSetName = requireAttributeValue(parser, ATTRIBUTE_NAME, 714 TAG_PERMISSION_SET); 715 if (permissionSetName == null) { 716 continue; 717 } 718 if (!permissionSets.containsKey(permissionSetName)) { 719 throwOrLogMessage("Unknown permission set:" + permissionSetName); 720 continue; 721 } 722 PermissionSet permissionSet = permissionSets.get(permissionSetName); 723 // We do allow intersection between permission sets. 724 permissions.addAll(permissionSet.getPermissions()); 725 break; 726 } 727 case TAG_PERMISSION: { 728 Permission permission = parsePermission(parser); 729 if (permission == null) { 730 continue; 731 } 732 validateNoDuplicateElement(permission, permissions, "permission"); 733 permissions.add(permission); 734 break; 735 } 736 default: 737 throwOrLogForUnknownTag(parser); 738 skipCurrentTag(parser); 739 } 740 } 741 742 return permissions; 743 } 744 745 @NonNull parseAppOpPermissions(@onNull XmlResourceParser parser)746 private List<String> parseAppOpPermissions(@NonNull XmlResourceParser parser) 747 throws IOException, XmlPullParserException { 748 List<String> appOpPermissions = new ArrayList<>(); 749 750 int type; 751 int depth; 752 int innerDepth = parser.getDepth() + 1; 753 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 754 && ((depth = parser.getDepth()) >= innerDepth 755 || type != XmlResourceParser.END_TAG)) { 756 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 757 continue; 758 } 759 760 if (parser.getName().equals(TAG_APP_OP_PERMISSION)) { 761 String appOpPermission = requireAttributeValue(parser, ATTRIBUTE_NAME, 762 TAG_APP_OP_PERMISSION); 763 if (appOpPermission == null) { 764 continue; 765 } 766 validateNoDuplicateElement(appOpPermission, appOpPermissions, "app op permission"); 767 appOpPermissions.add(appOpPermission); 768 } else { 769 throwOrLogForUnknownTag(parser); 770 skipCurrentTag(parser); 771 } 772 } 773 774 return appOpPermissions; 775 } 776 777 @NonNull parseAppOps(@onNull XmlResourceParser parser)778 private List<AppOp> parseAppOps(@NonNull XmlResourceParser parser) throws IOException, 779 XmlPullParserException { 780 List<String> appOpNames = new ArrayList<>(); 781 List<AppOp> appOps = new ArrayList<>(); 782 783 int type; 784 int depth; 785 int innerDepth = parser.getDepth() + 1; 786 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 787 && ((depth = parser.getDepth()) >= innerDepth 788 || type != XmlResourceParser.END_TAG)) { 789 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 790 continue; 791 } 792 793 if (parser.getName().equals(TAG_APP_OP)) { 794 String name = requireAttributeValue(parser, ATTRIBUTE_NAME, TAG_APP_OP); 795 if (name == null) { 796 continue; 797 } 798 validateNoDuplicateElement(name, appOpNames, "app op"); 799 appOpNames.add(name); 800 Integer maxTargetSdkVersion = getAttributeIntValue(parser, 801 ATTRIBUTE_MAX_TARGET_SDK_VERSION, Integer.MIN_VALUE); 802 if (maxTargetSdkVersion == Integer.MIN_VALUE) { 803 maxTargetSdkVersion = null; 804 } 805 if (maxTargetSdkVersion != null && maxTargetSdkVersion < Build.VERSION_CODES.BASE) { 806 throwOrLogMessage("Invalid value for \"maxTargetSdkVersion\": " 807 + maxTargetSdkVersion); 808 } 809 String modeName = requireAttributeValue(parser, ATTRIBUTE_MODE, TAG_APP_OP); 810 if (modeName == null) { 811 continue; 812 } 813 int modeIndex = sModeNameToMode.indexOfKey(modeName); 814 if (modeIndex < 0) { 815 throwOrLogMessage("Unknown value for \"mode\" on <app-op>: " + modeName); 816 continue; 817 } 818 int mode = sModeNameToMode.valueAt(modeIndex); 819 AppOp appOp = new AppOp(name, maxTargetSdkVersion, mode); 820 appOps.add(appOp); 821 } else { 822 throwOrLogForUnknownTag(parser); 823 skipCurrentTag(parser); 824 } 825 } 826 827 return appOps; 828 } 829 830 @NonNull parsePreferredActivities( @onNull XmlResourceParser parser)831 private List<PreferredActivity> parsePreferredActivities( 832 @NonNull XmlResourceParser parser) throws IOException, XmlPullParserException { 833 List<PreferredActivity> preferredActivities = new ArrayList<>(); 834 835 int type; 836 int depth; 837 int innerDepth = parser.getDepth() + 1; 838 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 839 && ((depth = parser.getDepth()) >= innerDepth 840 || type != XmlResourceParser.END_TAG)) { 841 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 842 continue; 843 } 844 845 if (parser.getName().equals(TAG_PREFERRED_ACTIVITY)) { 846 PreferredActivity preferredActivity = parsePreferredActivity(parser); 847 if (preferredActivity == null) { 848 continue; 849 } 850 validateNoDuplicateElement(preferredActivity, preferredActivities, 851 "preferred activity"); 852 preferredActivities.add(preferredActivity); 853 } else { 854 throwOrLogForUnknownTag(parser); 855 skipCurrentTag(parser); 856 } 857 } 858 859 return preferredActivities; 860 } 861 862 @Nullable parsePreferredActivity(@onNull XmlResourceParser parser)863 private PreferredActivity parsePreferredActivity(@NonNull XmlResourceParser parser) 864 throws IOException, XmlPullParserException { 865 RequiredActivity activity = null; 866 List<IntentFilterData> intentFilterDatas = new ArrayList<>(); 867 868 int type; 869 int depth; 870 int innerDepth = parser.getDepth() + 1; 871 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 872 && ((depth = parser.getDepth()) >= innerDepth 873 || type != XmlResourceParser.END_TAG)) { 874 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 875 continue; 876 } 877 878 switch (parser.getName()) { 879 case TAG_ACTIVITY: 880 if (activity != null) { 881 throwOrLogMessage("Duplicate <activity> in <preferred-activity>"); 882 skipCurrentTag(parser); 883 continue; 884 } 885 activity = (RequiredActivity) parseRequiredComponent(parser, TAG_ACTIVITY); 886 break; 887 case TAG_INTENT_FILTER: 888 IntentFilterData intentFilterData = parseIntentFilterData(parser); 889 if (intentFilterData == null) { 890 continue; 891 } 892 validateNoDuplicateElement(intentFilterData, intentFilterDatas, 893 "intent filter"); 894 if (intentFilterData.getDataType() != null) { 895 throwOrLogMessage("mimeType in <data> is not supported when setting a" 896 + " preferred activity"); 897 } 898 intentFilterDatas.add(intentFilterData); 899 break; 900 default: 901 throwOrLogForUnknownTag(parser); 902 skipCurrentTag(parser); 903 } 904 } 905 906 if (activity == null) { 907 throwOrLogMessage("Missing <activity> in <preferred-activity>"); 908 return null; 909 } 910 if (intentFilterDatas.isEmpty()) { 911 throwOrLogMessage("Missing <intent-filter> in <preferred-activity>"); 912 return null; 913 } 914 return new PreferredActivity(activity, intentFilterDatas); 915 } 916 skipCurrentTag(@onNull XmlResourceParser parser)917 private void skipCurrentTag(@NonNull XmlResourceParser parser) 918 throws XmlPullParserException, IOException { 919 int type; 920 int innerDepth = parser.getDepth() + 1; 921 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 922 && (parser.getDepth() >= innerDepth || type != XmlResourceParser.END_TAG)) { 923 // Do nothing 924 } 925 } 926 927 @Nullable getAttributeValue(@onNull XmlResourceParser parser, @NonNull String name)928 private String getAttributeValue(@NonNull XmlResourceParser parser, 929 @NonNull String name) { 930 return parser.getAttributeValue(null, name); 931 } 932 933 @Nullable requireAttributeValue(@onNull XmlResourceParser parser, @NonNull String name, @NonNull String tagName)934 private String requireAttributeValue(@NonNull XmlResourceParser parser, 935 @NonNull String name, @NonNull String tagName) { 936 String value = getAttributeValue(parser, name); 937 if (value == null) { 938 throwOrLogMessage("Missing attribute \"" + name + "\" on <" + tagName + ">"); 939 } 940 return value; 941 } 942 getAttributeBooleanValue(@onNull XmlResourceParser parser, @NonNull String name, boolean defaultValue)943 private boolean getAttributeBooleanValue(@NonNull XmlResourceParser parser, 944 @NonNull String name, boolean defaultValue) { 945 return parser.getAttributeBooleanValue(null, name, defaultValue); 946 } 947 948 @Nullable requireAttributeBooleanValue(@onNull XmlResourceParser parser, @NonNull String name, boolean defaultValue, @NonNull String tagName)949 private Boolean requireAttributeBooleanValue(@NonNull XmlResourceParser parser, 950 @NonNull String name, boolean defaultValue, @NonNull String tagName) { 951 String value = requireAttributeValue(parser, name, tagName); 952 if (value == null) { 953 return null; 954 } 955 return getAttributeBooleanValue(parser, name, defaultValue); 956 } 957 getAttributeIntValue(@onNull XmlResourceParser parser, @NonNull String name, int defaultValue)958 private int getAttributeIntValue(@NonNull XmlResourceParser parser, 959 @NonNull String name, int defaultValue) { 960 return parser.getAttributeIntValue(null, name, defaultValue); 961 } 962 getAttributeResourceValue(@onNull XmlResourceParser parser, @NonNull String name, int defaultValue)963 private int getAttributeResourceValue(@NonNull XmlResourceParser parser, 964 @NonNull String name, int defaultValue) { 965 return parser.getAttributeResourceValue(null, name, defaultValue); 966 } 967 968 @Nullable requireAttributeResourceValue(@onNull XmlResourceParser parser, @NonNull String name, int defaultValue, @NonNull String tagName)969 private Integer requireAttributeResourceValue(@NonNull XmlResourceParser parser, 970 @NonNull String name, int defaultValue, @NonNull String tagName) { 971 String value = requireAttributeValue(parser, name, tagName); 972 if (value == null) { 973 return null; 974 } 975 return getAttributeResourceValue(parser, name, defaultValue); 976 } 977 validateNoDuplicateElement(@onNull T element, @NonNull Collection<T> collection, @NonNull String name)978 private <T> void validateNoDuplicateElement(@NonNull T element, 979 @NonNull Collection<T> collection, @NonNull String name) { 980 if (collection.contains(element)) { 981 throwOrLogMessage("Duplicate " + name + ": " + element); 982 } 983 } 984 throwOrLogMessage(String message)985 private void throwOrLogMessage(String message) { 986 if (mValidationEnabled) { 987 throw new IllegalArgumentException(message); 988 } else { 989 Log.wtf(LOG_TAG, message); 990 } 991 } 992 throwOrLogMessage(String message, Throwable cause)993 private void throwOrLogMessage(String message, Throwable cause) { 994 if (mValidationEnabled) { 995 throw new IllegalArgumentException(message, cause); 996 } else { 997 Log.wtf(LOG_TAG, message, cause); 998 } 999 } 1000 throwOrLogForUnknownTag(@onNull XmlResourceParser parser)1001 private void throwOrLogForUnknownTag(@NonNull XmlResourceParser parser) { 1002 throwOrLogMessage("Unknown tag: " + parser.getName()); 1003 } 1004 1005 /** 1006 * Validates the permission names with {@code PackageManager} and ensures that all app ops with 1007 * a permission in {@code AppOpsManager} have declared that permission in its role and ensures 1008 * that all preferred activities are listed in the required components. 1009 */ validateResult(@onNull ArrayMap<String, PermissionSet> permissionSets, @NonNull ArrayMap<String, Role> roles)1010 private void validateResult(@NonNull ArrayMap<String, PermissionSet> permissionSets, 1011 @NonNull ArrayMap<String, Role> roles) { 1012 if (!mValidationEnabled) { 1013 return; 1014 } 1015 1016 int permissionSetsSize = permissionSets.size(); 1017 for (int permissionSetsIndex = 0; permissionSetsIndex < permissionSetsSize; 1018 permissionSetsIndex++) { 1019 PermissionSet permissionSet = permissionSets.valueAt(permissionSetsIndex); 1020 1021 List<Permission> permissions = permissionSet.getPermissions(); 1022 int permissionsSize = permissions.size(); 1023 for (int permissionsIndex = 0; permissionsIndex < permissionsSize; permissionsIndex++) { 1024 Permission permission = permissions.get(permissionsIndex); 1025 1026 validatePermission(permission); 1027 } 1028 } 1029 1030 int rolesSize = roles.size(); 1031 for (int rolesIndex = 0; rolesIndex < rolesSize; rolesIndex++) { 1032 Role role = roles.valueAt(rolesIndex); 1033 1034 if (!role.isAvailableBySdkVersion()) { 1035 continue; 1036 } 1037 1038 List<RequiredComponent> requiredComponents = role.getRequiredComponents(); 1039 int requiredComponentsSize = requiredComponents.size(); 1040 for (int requiredComponentsIndex = 0; requiredComponentsIndex < requiredComponentsSize; 1041 requiredComponentsIndex++) { 1042 RequiredComponent requiredComponent = requiredComponents.get( 1043 requiredComponentsIndex); 1044 1045 String permission = requiredComponent.getPermission(); 1046 if (permission != null) { 1047 validatePermission(permission); 1048 } 1049 } 1050 1051 List<Permission> permissions = role.getPermissions(); 1052 int permissionsSize = permissions.size(); 1053 for (int i = 0; i < permissionsSize; i++) { 1054 Permission permission = permissions.get(i); 1055 1056 validatePermission(permission); 1057 } 1058 1059 List<AppOp> appOps = role.getAppOps(); 1060 int appOpsSize = appOps.size(); 1061 for (int i = 0; i < appOpsSize; i++) { 1062 AppOp appOp = appOps.get(i); 1063 1064 validateAppOp(appOp); 1065 } 1066 1067 List<String> appOpPermissions = role.getAppOpPermissions(); 1068 int appOpPermissionsSize = appOpPermissions.size(); 1069 for (int i = 0; i < appOpPermissionsSize; i++) { 1070 String appOpPermission = appOpPermissions.get(i); 1071 1072 validateAppOpPermission(appOpPermission); 1073 } 1074 1075 List<PreferredActivity> preferredActivities = role.getPreferredActivities(); 1076 int preferredActivitiesSize = preferredActivities.size(); 1077 for (int preferredActivitiesIndex = 0; 1078 preferredActivitiesIndex < preferredActivitiesSize; 1079 preferredActivitiesIndex++) { 1080 PreferredActivity preferredActivity = preferredActivities.get( 1081 preferredActivitiesIndex); 1082 1083 if (!role.getRequiredComponents().contains(preferredActivity.getActivity())) { 1084 throw new IllegalArgumentException("<activity> of <preferred-activity> not" 1085 + " required in <required-components>, role: " + role.getName() 1086 + ", preferred activity: " + preferredActivity); 1087 } 1088 } 1089 } 1090 } 1091 validatePermission(@onNull Permission permission)1092 private void validatePermission(@NonNull Permission permission) { 1093 if (!permission.isAvailable()) { 1094 return; 1095 } 1096 validatePermission(permission.getName(), true); 1097 } 1098 validatePermission(@onNull String permission)1099 private void validatePermission(@NonNull String permission) { 1100 validatePermission(permission, false); 1101 } 1102 validatePermission(@onNull String permission, boolean enforceIsRuntimeOrRole)1103 private void validatePermission(@NonNull String permission, boolean enforceIsRuntimeOrRole) { 1104 PackageManager packageManager = mContext.getPackageManager(); 1105 boolean isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); 1106 // Skip validation for car permissions which may not be available on all build targets. 1107 if (!isAutomotive && permission.startsWith("android.car")) { 1108 return; 1109 } 1110 1111 PermissionInfo permissionInfo; 1112 try { 1113 permissionInfo = packageManager.getPermissionInfo(permission, 0); 1114 } catch (PackageManager.NameNotFoundException e) { 1115 throw new IllegalArgumentException("Unknown permission: " + permission, e); 1116 } 1117 1118 if (enforceIsRuntimeOrRole) { 1119 if (!(permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS 1120 || (permissionInfo.getProtectionFlags() & PermissionInfo.PROTECTION_FLAG_ROLE) 1121 == PermissionInfo.PROTECTION_FLAG_ROLE)) { 1122 throw new IllegalArgumentException( 1123 "Permission is not a runtime or role permission: " + permission); 1124 } 1125 } 1126 } 1127 validateAppOpPermission(@onNull String appOpPermission)1128 private void validateAppOpPermission(@NonNull String appOpPermission) { 1129 PackageManager packageManager = mContext.getPackageManager(); 1130 PermissionInfo permissionInfo; 1131 try { 1132 permissionInfo = packageManager.getPermissionInfo(appOpPermission, 0); 1133 } catch (PackageManager.NameNotFoundException e) { 1134 throw new IllegalArgumentException("Unknown app op permission: " + appOpPermission, e); 1135 } 1136 if ((permissionInfo.getProtectionFlags() & PermissionInfo.PROTECTION_FLAG_APPOP) 1137 != PermissionInfo.PROTECTION_FLAG_APPOP) { 1138 throw new IllegalArgumentException("Permission is not an app op permission: " 1139 + appOpPermission); 1140 } 1141 } 1142 validateAppOp(@onNull AppOp appOp)1143 private void validateAppOp(@NonNull AppOp appOp) { 1144 // This throws IllegalArgumentException if app op is unknown. 1145 String permission = AppOpsManager.opToPermission(appOp.getName()); 1146 if (permission != null) { 1147 PackageManager packageManager = mContext.getPackageManager(); 1148 PermissionInfo permissionInfo; 1149 try { 1150 permissionInfo = packageManager.getPermissionInfo(permission, 0); 1151 } catch (PackageManager.NameNotFoundException e) { 1152 throw new RuntimeException(e); 1153 } 1154 if (permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS) { 1155 throw new IllegalArgumentException("App op has an associated runtime permission: " 1156 + appOp.getName()); 1157 } 1158 } 1159 } 1160 } 1161