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