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 android.content.pm.parsing.component;
18 
19 import android.annotation.NonNull;
20 import android.content.pm.PermissionInfo;
21 import android.content.pm.parsing.ParsingPackage;
22 import android.content.pm.parsing.ParsingUtils;
23 import android.content.pm.parsing.result.ParseInput;
24 import android.content.pm.parsing.result.ParseResult;
25 import android.content.res.Resources;
26 import android.content.res.TypedArray;
27 import android.content.res.XmlResourceParser;
28 import android.util.Slog;
29 
30 import com.android.internal.R;
31 
32 import org.xmlpull.v1.XmlPullParserException;
33 
34 import java.io.IOException;
35 
36 /** @hide */
37 public class ParsedPermissionUtils {
38 
39     private static final String TAG = ParsingUtils.TAG;
40 
41     @NonNull
parsePermission(ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input)42     public static ParseResult<ParsedPermission> parsePermission(ParsingPackage pkg, Resources res,
43             XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
44             throws IOException, XmlPullParserException {
45         String packageName = pkg.getPackageName();
46         ParsedPermission
47                 permission = new ParsedPermission();
48         String tag = "<" + parser.getName() + ">";
49         final ParseResult<ParsedPermission> result;
50 
51         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermission);
52         try {
53             result = ParsedComponentUtils.parseComponent(
54                     permission, tag, pkg, sa, useRoundIcon, input,
55                     R.styleable.AndroidManifestPermission_banner,
56                     R.styleable.AndroidManifestPermission_description,
57                     R.styleable.AndroidManifestPermission_icon,
58                     R.styleable.AndroidManifestPermission_label,
59                     R.styleable.AndroidManifestPermission_logo,
60                     R.styleable.AndroidManifestPermission_name,
61                     R.styleable.AndroidManifestPermission_roundIcon);
62             if (result.isError()) {
63                 return result;
64             }
65 
66             if (sa.hasValue(
67                     R.styleable.AndroidManifestPermission_backgroundPermission)) {
68                 if ("android".equals(packageName)) {
69                     permission.backgroundPermission = sa.getNonResourceString(
70                             R.styleable
71                                     .AndroidManifestPermission_backgroundPermission);
72                 } else {
73                     Slog.w(TAG, packageName + " defines a background permission. Only the "
74                             + "'android' package can do that.");
75                 }
76             }
77 
78             // Note: don't allow this value to be a reference to a resource
79             // that may change.
80             permission.setGroup(sa.getNonResourceString(
81                     R.styleable.AndroidManifestPermission_permissionGroup));
82 
83             permission.requestRes = sa.getResourceId(
84                     R.styleable.AndroidManifestPermission_request, 0);
85 
86             permission.protectionLevel = sa.getInt(
87                     R.styleable.AndroidManifestPermission_protectionLevel,
88                     PermissionInfo.PROTECTION_NORMAL);
89 
90             permission.flags = sa.getInt(
91                     R.styleable.AndroidManifestPermission_permissionFlags, 0);
92 
93             final int knownCertsResource = sa.getResourceId(
94                     R.styleable.AndroidManifestPermission_knownCerts, 0);
95             if (knownCertsResource != 0) {
96                 // The knownCerts attribute supports both a string array resource as well as a
97                 // string resource for the case where the permission should only be granted to a
98                 // single known signer.
99                 final String resourceType = res.getResourceTypeName(knownCertsResource);
100                 if (resourceType.equals("array")) {
101                     final String[] knownCerts = res.getStringArray(knownCertsResource);
102                     if (knownCerts != null) {
103                         permission.setKnownCerts(knownCerts);
104                     }
105                 } else {
106                     final String knownCert = res.getString(knownCertsResource);
107                     if (knownCert != null) {
108                         permission.setKnownCert(knownCert);
109                     }
110                 }
111                 if (permission.knownCerts == null) {
112                     Slog.w(TAG, packageName + " defines a knownSigner permission but"
113                             + " the provided knownCerts resource is null");
114                 }
115             } else {
116                 // If the knownCerts resource ID is null check if the app specified a string
117                 // value for the attribute representing a single trusted signer.
118                 final String knownCert = sa.getString(
119                         R.styleable.AndroidManifestPermission_knownCerts);
120                 if (knownCert != null) {
121                     permission.setKnownCert(knownCert);
122                 }
123             }
124 
125             // For now only platform runtime permissions can be restricted
126             if (!permission.isRuntime() || !"android".equals(permission.getPackageName())) {
127                 permission.flags &= ~PermissionInfo.FLAG_HARD_RESTRICTED;
128                 permission.flags &= ~PermissionInfo.FLAG_SOFT_RESTRICTED;
129             } else {
130                 // The platform does not get to specify conflicting permissions
131                 if ((permission.flags & PermissionInfo.FLAG_HARD_RESTRICTED) != 0
132                         && (permission.flags & PermissionInfo.FLAG_SOFT_RESTRICTED) != 0) {
133                     throw new IllegalStateException("Permission cannot be both soft and hard"
134                             + " restricted: " + permission.getName());
135                 }
136             }
137         } finally {
138             sa.recycle();
139         }
140 
141         permission.protectionLevel = PermissionInfo.fixProtectionLevel(permission.protectionLevel);
142 
143         final int otherProtectionFlags = permission.getProtectionFlags()
144                 & ~(PermissionInfo.PROTECTION_FLAG_APPOP | PermissionInfo.PROTECTION_FLAG_INSTANT
145                 | PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY);
146         if (otherProtectionFlags != 0
147                 && permission.getProtection() != PermissionInfo.PROTECTION_SIGNATURE
148                 && permission.getProtection() != PermissionInfo.PROTECTION_INTERNAL) {
149             return input.error("<permission> protectionLevel specifies a non-instant, non-appop,"
150                     + " non-runtimeOnly flag but is not based on signature or internal type");
151         }
152 
153         return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission, input);
154     }
155 
156     @NonNull
parsePermissionTree(ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input)157     public static ParseResult<ParsedPermission> parsePermissionTree(ParsingPackage pkg, Resources res,
158             XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
159             throws IOException, XmlPullParserException {
160         ParsedPermission permission = new ParsedPermission();
161         String tag = "<" + parser.getName() + ">";
162         final ParseResult<ParsedPermission> result;
163 
164         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermissionTree);
165         try {
166             result = ParsedComponentUtils.parseComponent(
167                     permission, tag, pkg, sa, useRoundIcon, input,
168                     R.styleable.AndroidManifestPermissionTree_banner,
169                     null /*descriptionAttr*/,
170                     R.styleable.AndroidManifestPermissionTree_icon,
171                     R.styleable.AndroidManifestPermissionTree_label,
172                     R.styleable.AndroidManifestPermissionTree_logo,
173                     R.styleable.AndroidManifestPermissionTree_name,
174                     R.styleable.AndroidManifestPermissionTree_roundIcon);
175             if (result.isError()) {
176                 return result;
177             }
178         } finally {
179             sa.recycle();
180         }
181 
182         int index = permission.getName().indexOf('.');
183         if (index > 0) {
184             index = permission.getName().indexOf('.', index + 1);
185         }
186         if (index < 0) {
187             return input.error("<permission-tree> name has less than three segments: "
188                     + permission.getName());
189         }
190 
191         permission.protectionLevel = PermissionInfo.PROTECTION_NORMAL;
192         permission.tree = true;
193 
194         return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission,
195                 input);
196     }
197 
198     @NonNull
parsePermissionGroup(ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input)199     public static ParseResult<ParsedPermissionGroup> parsePermissionGroup(ParsingPackage pkg,
200             Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
201             throws IOException, XmlPullParserException {
202         ParsedPermissionGroup
203                 permissionGroup = new ParsedPermissionGroup();
204         String tag = "<" + parser.getName() + ">";
205 
206         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermissionGroup);
207         try {
208             ParseResult<ParsedPermissionGroup> result = ParsedComponentUtils.parseComponent(
209                     permissionGroup, tag, pkg, sa, useRoundIcon, input,
210                     R.styleable.AndroidManifestPermissionGroup_banner,
211                     R.styleable.AndroidManifestPermissionGroup_description,
212                     R.styleable.AndroidManifestPermissionGroup_icon,
213                     R.styleable.AndroidManifestPermissionGroup_label,
214                     R.styleable.AndroidManifestPermissionGroup_logo,
215                     R.styleable.AndroidManifestPermissionGroup_name,
216                     R.styleable.AndroidManifestPermissionGroup_roundIcon);
217             if (result.isError()) {
218                 return result;
219             }
220 
221             // @formatter:off
222             permissionGroup.requestDetailResourceId = sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_requestDetail, 0);
223             permissionGroup.backgroundRequestResourceId = sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequest, 0);
224             permissionGroup.backgroundRequestDetailResourceId = sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequestDetail, 0);
225             permissionGroup.requestRes = sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_request, 0);
226             permissionGroup.flags = sa.getInt(R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags,0);
227             permissionGroup.priority = sa.getInt(R.styleable.AndroidManifestPermissionGroup_priority, 0);
228             // @formatter:on
229         } finally {
230             sa.recycle();
231         }
232 
233         return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permissionGroup,
234                 input);
235     }
236 }
237