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