• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016, 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 package com.android.managedprovisioning.parser;
17 
18 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DISCLAIMER_CONTENT;
19 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DISCLAIMER_HEADER;
20 import static com.android.managedprovisioning.common.StoreUtils.DIR_PROVISIONING_PARAMS_FILE_CACHE;
21 
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.PackageManager;
25 import android.net.Uri;
26 import android.os.Bundle;
27 import android.os.Parcelable;
28 import androidx.annotation.Nullable;
29 import android.os.Binder;
30 
31 import android.text.TextUtils;
32 import com.android.managedprovisioning.common.ProvisionLogger;
33 import com.android.managedprovisioning.common.StoreUtils;
34 import com.android.managedprovisioning.model.DisclaimersParam;
35 import com.android.managedprovisioning.model.DisclaimersParam.Disclaimer;
36 import java.io.File;
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.Objects;
40 
41 /**
42  * Parser for {@link EXTRA_PROVISIONING_DISCLAIMERS} into {@link DisclaimersParam}
43  * It also saves the disclaimer content into files
44  */
45 public class DisclaimersParserImpl implements DisclaimerParser {
46     private static final int MAX_LENGTH = 3;
47     private static final String SCHEME_ANDROID_RESOURCE = "android.resource";
48     private static final String SCHEME_CONTENT = "content";
49 
50 
51     private final Context mContext;
52     private final long mProvisioningId;
53     private final File mDisclaimerDir;
54 
DisclaimersParserImpl(Context context, long provisioningId)55     public DisclaimersParserImpl(Context context, long provisioningId) {
56         mContext = context;
57         mProvisioningId = provisioningId;
58         mDisclaimerDir =  new File(mContext.getFilesDir(), DIR_PROVISIONING_PARAMS_FILE_CACHE);
59     }
60 
61     @Nullable
parse(Parcelable[] parcelables)62     public DisclaimersParam parse(Parcelable[] parcelables) throws ClassCastException {
63         if (parcelables == null) {
64             return null;
65         }
66 
67         List<Disclaimer> disclaimers = new ArrayList<>(MAX_LENGTH);
68         for (int i = 0; i < parcelables.length; i++) {
69             // maximum 3 disclaimers are accepted in the EXTRA_PROVISIONING_DISCLAIMERS API
70             if (disclaimers.size() >= MAX_LENGTH) {
71                 break;
72             }
73             final Bundle disclaimerBundle = (Bundle) parcelables[i];
74             final String header = disclaimerBundle.getString(EXTRA_PROVISIONING_DISCLAIMER_HEADER);
75             final Uri uri = disclaimerBundle.getParcelable(EXTRA_PROVISIONING_DISCLAIMER_CONTENT);
76             if (TextUtils.isEmpty(header)) {
77                 ProvisionLogger.logw("Empty disclaimer header in " + i + " element");
78                 continue;
79             }
80 
81             if (uri == null) {
82                 ProvisionLogger.logw("Null disclaimer content uri in " + i + " element");
83                 continue;
84             }
85             try {
86                 validateUriSchemeAndPermission(uri);
87             } catch (SecurityException e) {
88                 ProvisionLogger.loge(
89                     "Skipping disclaimer in "
90                         + i
91                         + " element due to URI validation failure: "
92                         + e.getMessage(),
93                     e);
94                 continue;
95             }
96             File disclaimerFile = saveDisclaimerContentIntoFile(uri, i);
97 
98             if (disclaimerFile == null) {
99                 ProvisionLogger.logw("Failed to copy disclaimer uri in " + i + " element");
100                 continue;
101             }
102 
103             disclaimers.add(new Disclaimer(header, disclaimerFile.getPath()));
104         }
105         return disclaimers.isEmpty() ? null : new DisclaimersParam.Builder()
106                 .setDisclaimers(disclaimers.toArray(new Disclaimer[disclaimers.size()])).build();
107     }
108 
109     /**
110      * Validates a {@link Uri} extra pointing to disclaimer content.
111      *
112      * <p>It checks that the URI scheme is one of {@code content} ({@link
113      * android.content.ContentResolver#SCHEME_CONTENT}) or {@code
114      * android.resource} ({@link
115      * android.content.ContentResolver#SCHEME_ANDROID_RESOURCE}). If a {@code
116      * content:} URI is passed, it also checks that the caller has grant read
117      * permission ({@link Intent#FLAG_GRANT_READ_URI_PERMISSION}).
118      *
119      * @throws SecurityException if the URI scheme is invalid or the caller
120      * does not have permission to access the URI.
121      */
validateUriSchemeAndPermission(Uri uri)122     private void validateUriSchemeAndPermission(Uri uri) throws SecurityException {
123         ProvisionLogger.logd("validateUriSchemeAndPermission: " + uri);
124         String scheme = uri.getScheme();
125         if (!Objects.equals(scheme, SCHEME_ANDROID_RESOURCE)
126             && !Objects.equals(scheme, SCHEME_CONTENT)) {
127             String errorMessage = "Invalid URI scheme: " + scheme;
128             throw new SecurityException(errorMessage);
129         }
130         if (Objects.equals(scheme, SCHEME_CONTENT)) {
131             int permissionCheck =
132                 mContext.checkUriPermission(
133                     uri,
134                     Binder.getCallingPid(),
135                     Binder.getCallingUid(),
136                     Intent.FLAG_GRANT_READ_URI_PERMISSION);
137 
138             if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
139                 String errorMessage = "Caller does not have permission to access"
140                     + " disclaimer URI: " + uri;
141                 throw new SecurityException(errorMessage);
142             }
143         }
144     }
145 
146     /**
147      * @return {@link File} if the uri content is saved into the file successfully. Otherwise,
148      * return null.
149      */
saveDisclaimerContentIntoFile(Uri uri, int index)150     private File saveDisclaimerContentIntoFile(Uri uri, int index) {
151         if (!mDisclaimerDir.exists()) {
152             mDisclaimerDir.mkdirs();
153         }
154 
155         String filename = "disclaimer_content_" + mProvisioningId + "_" + index + ".txt";
156         File outputFile = new File(mDisclaimerDir, filename);
157 
158         boolean success = StoreUtils.copyUriIntoFile(mContext.getContentResolver(), uri,
159                 outputFile);
160         return success ? outputFile : null;
161     }
162 }
163