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 
17 package com.android.managedprovisioning.parser;
18 
19 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
20 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE;
21 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
22 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE;
23 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
24 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
25 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
26 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
27 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE;
28 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM;
29 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER;
30 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION;
31 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
32 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM;
33 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED;
34 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCALE;
35 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCAL_TIME;
36 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_MAIN_COLOR;
37 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION;
38 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_USER_SETUP;
39 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TIME_ZONE;
40 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_HIDDEN;
41 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PAC_URL;
42 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD;
43 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_BYPASS;
44 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_HOST;
45 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_PORT;
46 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE;
47 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID;
48 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOGO_URI;
49 import static com.android.internal.util.Preconditions.checkNotNull;
50 import static com.android.managedprovisioning.common.Globals.ACTION_RESUME_PROVISIONING;
51 import static com.android.managedprovisioning.parser.MessageParser.EXTRA_PROVISIONING_ACTION;
52 import static com.android.managedprovisioning.parser.MessageParser.EXTRA_PROVISIONING_DEVICE_ADMIN_SUPPORT_SHA1_PACKAGE_CHECKSUM;
53 import static com.android.managedprovisioning.parser.MessageParser.EXTRA_PROVISIONING_STARTED_BY_TRUSTED_SOURCE;
54 
55 import android.accounts.Account;
56 import android.content.ComponentName;
57 import android.content.Context;
58 import android.content.Intent;
59 import android.net.Uri;
60 import android.graphics.Color;
61 import android.os.Bundle;
62 import android.os.PersistableBundle;
63 import android.support.annotation.Nullable;
64 import android.support.annotation.VisibleForTesting;
65 
66 import com.android.managedprovisioning.LogoUtils;
67 import com.android.managedprovisioning.ProvisionLogger;
68 import com.android.managedprovisioning.common.IllegalProvisioningArgumentException;
69 import com.android.managedprovisioning.common.Utils;
70 import com.android.managedprovisioning.model.PackageDownloadInfo;
71 import com.android.managedprovisioning.model.ProvisioningParams;
72 import com.android.managedprovisioning.model.WifiInfo;
73 
74 import java.util.Arrays;
75 import java.util.HashSet;
76 import java.util.IllformedLocaleException;
77 import java.util.Locale;
78 import java.util.Properties;
79 import java.util.Set;
80 
81 /**
82  * A parser which parses provisioning data from intent which stores in {@link Bundle} extras.
83  */
84 
85 @VisibleForTesting
86 public class ExtrasProvisioningDataParser implements ProvisioningDataParser {
87     private static final Set<String> PROVISIONING_ACTIONS_SUPPORT_ALL_PROVISIONING_DATA =
88             new HashSet(Arrays.asList(
89                     ACTION_RESUME_PROVISIONING,
90                     ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE));
91 
92     private static final Set<String> PROVISIONING_ACTIONS_SUPPORT_MIN_PROVISIONING_DATA =
93             new HashSet(Arrays.asList(
94                     ACTION_PROVISION_MANAGED_DEVICE,
95                     ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
96                     ACTION_PROVISION_MANAGED_USER,
97                     ACTION_PROVISION_MANAGED_PROFILE));
98 
99     private final Utils mUtils;
100 
ExtrasProvisioningDataParser(Utils utils)101     ExtrasProvisioningDataParser(Utils utils) {
102         mUtils = checkNotNull(utils);
103     }
104 
105     @Override
parse(Intent provisioningIntent, Context context)106     public ProvisioningParams parse(Intent provisioningIntent, Context context)
107             throws IllegalProvisioningArgumentException{
108         String provisioningAction = provisioningIntent.getAction();
109 
110         if (PROVISIONING_ACTIONS_SUPPORT_MIN_PROVISIONING_DATA.contains(provisioningAction)) {
111             ProvisionLogger.logi("Processing mininalist extras intent.");
112             return parseMinimalistSupportedProvisioningDataInternal(provisioningIntent, context)
113                     .build();
114         } else if (PROVISIONING_ACTIONS_SUPPORT_ALL_PROVISIONING_DATA.contains(
115                 provisioningAction)) {
116             return parseAllSupportedProvisioningData(provisioningIntent, context);
117         } else {
118             throw new IllegalProvisioningArgumentException("Unsupported provisioning action: "
119                     + provisioningAction);
120         }
121     }
122 
123     /**
124      * Parses minimal supported set of parameters from bundle extras of a provisioning intent.
125      *
126      * <p>Here is the list of supported parameters.
127      * <ul>
128      *     <li>{@link EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
129      *     <li>
130      *         {@link EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} only in
131      *         {@link ACTION_PROVISION_MANAGED_PROFILE}.
132      *     </li>
133      *     <li>{@link EXTRA_PROVISIONING_LOGO_URI}</li>
134      *     <li>{@link EXTRA_PROVISIONING_MAIN_COLOR}</li>
135      *     <li>
136      *         {@link EXTRA_PROVISIONING_SKIP_USER_SETUP} only in
137      *         {@link ACTION_PROVISION_MANAGED_USER} and {@link ACTION_PROVISION_MANAGED_DEVICE}.
138      *     </li>
139      *     <li>{@link EXTRA_PROVISIONING_SKIP_ENCRYPTION}</li>
140      *     <li>{@link EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}</li>
141      *     <li>{@link EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE}</li>
142      *     <li>{@link EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}</li>
143      * </ul>
144      */
parseMinimalistSupportedProvisioningDataInternal( Intent intent, Context context)145     private ProvisioningParams.Builder parseMinimalistSupportedProvisioningDataInternal(
146             Intent intent, Context context)
147             throws IllegalProvisioningArgumentException {
148         boolean isProvisionManagedDeviceFromTrustedSourceIntent =
149                 ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE.equals(intent.getAction());
150         try {
151             String provisioningAction = isResumeProvisioningIntent(intent)
152                     ? intent.getStringExtra(EXTRA_PROVISIONING_ACTION)
153                     : mUtils.mapIntentToDpmAction(intent);
154 
155             // Parse device admin package name and component name.
156             ComponentName deviceAdminComponentName = (ComponentName) intent.getParcelableExtra(
157                     EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME);
158             // Device admin package name is deprecated. It is only supported in Profile Owner
159             // provisioning and when resuming NFC provisioning.
160             String deviceAdminPackageName = null;
161             if (ACTION_PROVISION_MANAGED_PROFILE.equals(provisioningAction)) {
162                 // In L, we only support package name. This means some DPC may still send us the
163                 // device admin package name only. Attempts to obtain the package name from extras.
164                 deviceAdminPackageName = intent.getStringExtra(
165                         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
166                 // For profile owner, the device admin package should be installed. Verify the
167                 // device admin package.
168                 deviceAdminComponentName = mUtils.findDeviceAdmin(
169                         deviceAdminPackageName, deviceAdminComponentName, context);
170                 // Since the device admin package must be installed at this point and its component
171                 // name has been obtained, it should be safe to set the deprecated package name
172                 // value to null.
173                 deviceAdminPackageName = null;
174             } else if (isResumeProvisioningIntent(intent)) {
175                 deviceAdminPackageName = intent.getStringExtra(
176                         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
177             }
178 
179             // Parse skip user setup in ACTION_PROVISION_MANAGED_USER and
180             // ACTION_PROVISION_MANAGED_DEVICE (sync auth) only. This extra is not supported if
181             // provisioning was started by trusted source, as it is not clear where SUW should
182             // continue from.
183             boolean skipUserSetup = ProvisioningParams.DEFAULT_SKIP_USER_SETUP;
184             if (!isProvisionManagedDeviceFromTrustedSourceIntent
185                     && (provisioningAction.equals(ACTION_PROVISION_MANAGED_USER)
186                             || provisioningAction.equals(ACTION_PROVISION_MANAGED_DEVICE))) {
187                 skipUserSetup = intent.getBooleanExtra(EXTRA_PROVISIONING_SKIP_USER_SETUP,
188                         ProvisioningParams.DEFAULT_SKIP_USER_SETUP);
189             }
190 
191             // Parse main color and organization's logo. This is not supported in managed device
192             // from trusted source provisioning because, currently, there is no way to send
193             // organization logo to the device at this stage.
194             Integer mainColor = ProvisioningParams.DEFAULT_MAIN_COLOR;
195             if (!isProvisionManagedDeviceFromTrustedSourceIntent) {
196                 if (intent.hasExtra(EXTRA_PROVISIONING_MAIN_COLOR)) {
197                     mainColor = intent.getIntExtra(EXTRA_PROVISIONING_MAIN_COLOR, 0 /* not used */);
198                 }
199                 parseOrganizationLogoUrlFromExtras(context, intent);
200             }
201 
202             return ProvisioningParams.Builder.builder()
203                     .setProvisioningAction(provisioningAction)
204                     .setDeviceAdminComponentName(deviceAdminComponentName)
205                     .setDeviceAdminPackageName(deviceAdminPackageName)
206                     .setSkipEncryption(intent.getBooleanExtra(EXTRA_PROVISIONING_SKIP_ENCRYPTION,
207                             ProvisioningParams.DEFAULT_EXTRA_PROVISIONING_SKIP_ENCRYPTION))
208                     .setLeaveAllSystemAppsEnabled(intent.getBooleanExtra(
209                             EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED,
210                             ProvisioningParams.DEFAULT_LEAVE_ALL_SYSTEM_APPS_ENABLED))
211                     .setAdminExtrasBundle((PersistableBundle) intent.getParcelableExtra(
212                             EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE))
213                     .setMainColor(mainColor)
214                     .setSkipUserSetup(skipUserSetup)
215                     .setAccountToMigrate((Account) intent.getParcelableExtra(
216                             EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE));
217         } catch (ClassCastException e) {
218             throw new IllegalProvisioningArgumentException("Extra "
219                     + EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
220                     + " must be of type PersistableBundle.", e);
221         } catch (IllegalArgumentException e) {
222             throw new IllegalProvisioningArgumentException("Invalid parameter found!", e);
223         } catch (NullPointerException e) {
224             throw new IllegalProvisioningArgumentException("Compulsory parameter not found!", e);
225         }
226     }
227 
228     /**
229      * Parses an intent and return a corresponding {@link ProvisioningParams} object.
230      *
231      * @param intent intent to be parsed.
232      * @param context a context
233      */
parseAllSupportedProvisioningData(Intent intent, Context context)234     private ProvisioningParams parseAllSupportedProvisioningData(Intent intent, Context context)
235             throws IllegalProvisioningArgumentException {
236         try {
237             ProvisionLogger.logi("Processing all supported extras intent: " + intent.getAction());
238             return parseMinimalistSupportedProvisioningDataInternal(intent, context)
239                     // Parse time zone, local time and locale.
240                     .setTimeZone(intent.getStringExtra(EXTRA_PROVISIONING_TIME_ZONE))
241                     .setLocalTime(intent.getLongExtra(EXTRA_PROVISIONING_LOCAL_TIME,
242                             ProvisioningParams.DEFAULT_LOCAL_TIME))
243                     .setLocale(MessageParser.stringToLocale(
244                             intent.getStringExtra(EXTRA_PROVISIONING_LOCALE)))
245                     // Parse WiFi configuration.
246                     .setWifiInfo(parseWifiInfoFromExtras(intent))
247                     // Parse device admin package download info.
248                     .setDeviceAdminDownloadInfo(parsePackageDownloadInfoFromExtras(intent))
249                     // Cases where startedByTrustedSource can be true are
250                     // 1. We are reloading a stored provisioning intent, either Nfc bump or
251                     //    PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE, after encryption reboot,
252                     //    which is a self-originated intent.
253                     // 2. the intent is from a trusted source, for example QR provisioning.
254                     .setStartedByTrustedSource(isResumeProvisioningIntent(intent)
255                             ? intent.getBooleanExtra(EXTRA_PROVISIONING_STARTED_BY_TRUSTED_SOURCE,
256                                     ProvisioningParams.DEFAULT_STARTED_BY_TRUSTED_SOURCE)
257                             : ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE.equals(
258                                     intent.getAction()))
259                     .build();
260         }  catch (IllegalArgumentException e) {
261             throw new IllegalProvisioningArgumentException("Invalid parameter found!", e);
262         } catch (NullPointerException e) {
263             throw new IllegalProvisioningArgumentException("Compulsory parameter not found!", e);
264         }
265     }
266 
267     /**
268      * Parses Wifi configuration from an Intent and returns the result in {@link WifiInfo}.
269      */
270     @Nullable
parseWifiInfoFromExtras(Intent intent)271     private WifiInfo parseWifiInfoFromExtras(Intent intent) {
272         if (intent.getStringExtra(EXTRA_PROVISIONING_WIFI_SSID) == null) {
273             return null;
274         }
275         return WifiInfo.Builder.builder()
276                 .setSsid(intent.getStringExtra(EXTRA_PROVISIONING_WIFI_SSID))
277                 .setSecurityType(intent.getStringExtra(EXTRA_PROVISIONING_WIFI_SECURITY_TYPE))
278                 .setPassword(intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PASSWORD))
279                 .setProxyHost(intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PROXY_HOST))
280                 .setProxyBypassHosts(intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PROXY_BYPASS))
281                 .setPacUrl(intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PAC_URL))
282                 .setProxyPort(intent.getIntExtra(EXTRA_PROVISIONING_WIFI_PROXY_PORT,
283                         WifiInfo.DEFAULT_WIFI_PROXY_PORT))
284                 .setHidden(intent.getBooleanExtra(EXTRA_PROVISIONING_WIFI_HIDDEN,
285                         WifiInfo.DEFAULT_WIFI_HIDDEN))
286                 .build();
287     }
288 
289     /**
290      * Parses device admin package download info configuration from an Intent and returns the result
291      * in {@link PackageDownloadInfo}.
292      */
293     @Nullable
parsePackageDownloadInfoFromExtras(Intent intent)294     private PackageDownloadInfo parsePackageDownloadInfoFromExtras(Intent intent) {
295         if (intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION)
296                 == null) {
297             return null;
298         }
299         PackageDownloadInfo.Builder downloadInfoBuilder = PackageDownloadInfo.Builder.builder()
300                 .setMinVersion(intent.getIntExtra(
301                         EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE,
302                         PackageDownloadInfo.DEFAULT_MINIMUM_VERSION))
303                 .setLocation(intent.getStringExtra(
304                         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION))
305                 .setCookieHeader(intent.getStringExtra(
306                         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER));
307         String packageHash =
308                 intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM);
309         if (packageHash != null) {
310             downloadInfoBuilder.setPackageChecksum(mUtils.stringToByteArray(packageHash));
311             if (isResumeProvisioningIntent(intent)) {
312                 // PackageChecksumSupportsSha1 is only supported in NFC provisioning. But if the
313                 // device is rebooted after encryption as part of the NFC provisioning flow, the
314                 // value should be restored.
315                 downloadInfoBuilder.setPackageChecksumSupportsSha1(
316                         intent.getBooleanExtra(
317                                 EXTRA_PROVISIONING_DEVICE_ADMIN_SUPPORT_SHA1_PACKAGE_CHECKSUM,
318                                 false));
319             }
320         }
321         String sigHash = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM);
322         if (sigHash != null) {
323             downloadInfoBuilder.setSignatureChecksum(mUtils.stringToByteArray(sigHash));
324         }
325         return downloadInfoBuilder.build();
326     }
327 
328     /**
329      * Parses the organization logo url from intent.
330      */
parseOrganizationLogoUrlFromExtras(Context context, Intent intent)331     private void parseOrganizationLogoUrlFromExtras(Context context, Intent intent) {
332         Uri logoUri = intent.getParcelableExtra(EXTRA_PROVISIONING_LOGO_URI);
333         if (logoUri != null) {
334             // If we go through encryption, and if the uri is a content uri:
335             // We'll lose the grant to this uri. So we need to save it to a local file.
336             LogoUtils.saveOrganisationLogo(context, logoUri);
337         } else if (!isResumeProvisioningIntent(intent)) {
338             // If the intent is not from managed provisioning app, there is a slight possibility
339             // that the logo is still kept on the file system from a previous provisioning. In
340             // this case, remove it.
341             LogoUtils.cleanUp(context);
342         }
343     }
344 
isResumeProvisioningIntent(Intent intent)345     private boolean isResumeProvisioningIntent(Intent intent) {
346         return ACTION_RESUME_PROVISIONING.equals(intent.getAction());
347     }
348 }
349