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 */
16package com.android.managedprovisioning.parser;
17
18import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
19import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
20import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
21import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
22import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE;
23import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM;
24import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER;
25import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION;
26import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
27import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM;
28import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCALE;
29import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCAL_TIME;
30import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_MAIN_COLOR;
31import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION;
32import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TIME_ZONE;
33import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_HIDDEN;
34import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PAC_URL;
35import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD;
36import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_BYPASS;
37import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_HOST;
38import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_PORT;
39import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE;
40import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID;
41import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
42import static org.mockito.Mockito.when;
43
44import android.accounts.Account;
45import android.app.admin.DevicePolicyManager;
46import android.content.ComponentName;
47import android.content.Context;
48import android.content.Intent;
49import android.nfc.NdefMessage;
50import android.nfc.NdefRecord;
51import android.nfc.NfcAdapter;
52import android.os.PersistableBundle;
53import android.test.AndroidTestCase;
54import android.test.suitebuilder.annotation.SmallTest;
55import android.util.Base64;
56import com.android.managedprovisioning.common.IllegalProvisioningArgumentException;
57import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences;
58import com.android.managedprovisioning.common.StoreUtils;
59import com.android.managedprovisioning.common.Utils;
60import com.android.managedprovisioning.model.PackageDownloadInfo;
61import com.android.managedprovisioning.model.ProvisioningParams;
62import com.android.managedprovisioning.model.WifiInfo;
63import java.io.ByteArrayOutputStream;
64import java.util.Locale;
65import java.util.Properties;
66import org.mockito.Mock;
67import org.mockito.MockitoAnnotations;
68
69/** Tests for {@link PropertiesProvisioningDataParser} */
70@SmallTest
71public class PropertiesProvisioningDataParserTest extends AndroidTestCase {
72    private static final String TEST_PACKAGE_NAME = "com.afwsamples.testdpc";
73    private static final ComponentName TEST_COMPONENT_NAME =
74            ComponentName.unflattenFromString(
75                    "com.afwsamples.testdpc/com.afwsamples.testdpc.DeviceAdminReceiver");
76    private static final long TEST_LOCAL_TIME = 1456939524713L;
77    private static final Locale TEST_LOCALE = Locale.UK;
78    private static final String TEST_TIME_ZONE = "GMT";
79    private static final Integer TEST_MAIN_COLOR = 65280;
80    private static final boolean TEST_STARTED_BY_TRUSTED_SOURCE = true;
81    private static final boolean TEST_LEAVE_ALL_SYSTEM_APP_ENABLED = true;
82    private static final boolean TEST_SKIP_ENCRYPTION = true;
83    private static final boolean TEST_SKIP_USER_SETUP = true;
84    private static final long TEST_PROVISIONING_ID = 2000L;
85    private static final Account TEST_ACCOUNT_TO_MIGRATE =
86            new Account("user@gmail.com", "com.google");
87    private static final String INVALID_MIME_TYPE = "invalid-mime-type";
88
89    // Wifi info
90    private static final String TEST_SSID = "TestWifi";
91    private static final boolean TEST_HIDDEN = true;
92    private static final String TEST_SECURITY_TYPE = "WPA2";
93    private static final String TEST_PASSWORD = "GoogleRock";
94    private static final String TEST_PROXY_HOST = "testhost.com";
95    private static final int TEST_PROXY_PORT = 7689;
96    private static final String TEST_PROXY_BYPASS_HOSTS = "http://host1.com;https://host2.com";
97    private static final String TEST_PAC_URL = "pac.test.com";
98    private static final WifiInfo TEST_WIFI_INFO = WifiInfo.Builder.builder()
99            .setSsid(TEST_SSID)
100            .setHidden(TEST_HIDDEN)
101            .setSecurityType(TEST_SECURITY_TYPE)
102            .setPassword(TEST_PASSWORD)
103            .setProxyHost(TEST_PROXY_HOST)
104            .setProxyPort(TEST_PROXY_PORT)
105            .setProxyBypassHosts(TEST_PROXY_BYPASS_HOSTS)
106            .setPacUrl(TEST_PAC_URL)
107            .build();
108
109    // Device admin package download info
110    private static final String TEST_DOWNLOAD_LOCATION =
111            "http://example/dpc.apk";
112    private static final String TEST_COOKIE_HEADER =
113            "Set-Cookie: sessionToken=foobar; Expires=Thu, 18 Feb 2016 23:59:59 GMT";
114    private static final byte[] TEST_PACKAGE_CHECKSUM = new byte[] { '1', '2', '3', '4', '5' };
115    private static final byte[] TEST_SIGNATURE_CHECKSUM = new byte[] { '5', '4', '3', '2', '1' };
116    private static final int TEST_MIN_SUPPORT_VERSION = 17689;
117    private static final PackageDownloadInfo TEST_DOWNLOAD_INFO =
118            PackageDownloadInfo.Builder.builder()
119                    .setLocation(TEST_DOWNLOAD_LOCATION)
120                    .setCookieHeader(TEST_COOKIE_HEADER)
121                    .setPackageChecksum(TEST_PACKAGE_CHECKSUM)
122                    .setSignatureChecksum(TEST_SIGNATURE_CHECKSUM)
123                    .setMinVersion(TEST_MIN_SUPPORT_VERSION)
124                    .build();
125
126    @Mock
127    private Context mContext;
128
129    @Mock
130    private ManagedProvisioningSharedPreferences mSharedPreferences;
131
132    private PropertiesProvisioningDataParser mPropertiesProvisioningDataParser;
133
134    @Override
135    public void setUp() {
136        // this is necessary for mockito to work
137        System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
138
139        MockitoAnnotations.initMocks(this);
140
141        when(mSharedPreferences.incrementAndGetProvisioningId()).thenReturn(TEST_PROVISIONING_ID);
142        mPropertiesProvisioningDataParser = new PropertiesProvisioningDataParser(mContext,
143                new Utils(), mSharedPreferences);
144    }
145
146    public void testParse_nfcProvisioningIntent() throws Exception {
147        // GIVEN a NFC provisioning intent with all supported data.
148        Properties props = new Properties();
149        ByteArrayOutputStream stream = new ByteArrayOutputStream();
150        props.setProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, TEST_PACKAGE_NAME);
151        props.setProperty(
152                EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
153                TEST_COMPONENT_NAME.flattenToString());
154        setTestWifiInfo(props);
155        setTestTimeTimeZoneAndLocale(props);
156        setTestDeviceAdminDownload(props);
157        props.setProperty(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE, getTestAdminExtrasString());
158        props.setProperty(
159                EXTRA_PROVISIONING_SKIP_ENCRYPTION,
160                Boolean.toString(TEST_SKIP_ENCRYPTION));
161        // GIVEN main color is supplied even though it is not supported in NFC provisioning.
162        props.setProperty(EXTRA_PROVISIONING_MAIN_COLOR, Integer.toString(TEST_MAIN_COLOR));
163
164        props.store(stream, "NFC provisioning intent" /* data description */);
165
166        NdefRecord record = NdefRecord.createMime(
167                DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC,
168                stream.toByteArray());
169        NdefMessage ndfMsg = new NdefMessage(new NdefRecord[]{record});
170
171        Intent intent = new Intent(NfcAdapter.ACTION_NDEF_DISCOVERED)
172                .setType(MIME_TYPE_PROVISIONING_NFC)
173                .putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[]{ndfMsg});
174
175        // WHEN the intent is parsed by the parser.
176        ProvisioningParams params = mPropertiesProvisioningDataParser.parse(intent);
177
178
179        // THEN ProvisionParams is constructed as expected.
180        assertEquals(
181                ProvisioningParams.Builder.builder()
182                        // THEN NFC provisioning is translated to ACTION_PROVISION_MANAGED_DEVICE
183                        // action.
184                        .setProvisioningAction(ACTION_PROVISION_MANAGED_DEVICE)
185                        .setDeviceAdminComponentName(TEST_COMPONENT_NAME)
186                        .setDeviceAdminPackageName(TEST_PACKAGE_NAME)
187                        .setProvisioningId(TEST_PROVISIONING_ID)
188                        .setDeviceAdminDownloadInfo(
189                                PackageDownloadInfo.Builder.builder()
190                                        .setLocation(TEST_DOWNLOAD_LOCATION)
191                                        .setCookieHeader(TEST_COOKIE_HEADER)
192                                        .setPackageChecksum(TEST_PACKAGE_CHECKSUM)
193                                        .setSignatureChecksum(TEST_SIGNATURE_CHECKSUM)
194                                        .setMinVersion(TEST_MIN_SUPPORT_VERSION)
195                                        // THEN it supports SHA1 package checksum.
196                                        .setPackageChecksumSupportsSha1(true)
197                                        .build())
198                        .setLocalTime(TEST_LOCAL_TIME)
199                        .setLocale(TEST_LOCALE)
200                        .setTimeZone(TEST_TIME_ZONE)
201                        // THEN main color is not supported in NFC intent.
202                        .setMainColor(null)
203                        .setSkipEncryption(TEST_SKIP_ENCRYPTION)
204                        .setWifiInfo(TEST_WIFI_INFO)
205                        .setAdminExtrasBundle(getTestAdminExtrasPersistableBundle())
206                        .setStartedByTrustedSource(true)
207                        .setIsNfc(true)
208                        .build(),
209                params);
210    }
211
212    public void testParse_OtherIntentsThrowsException() {
213        // GIVEN a managed device provisioning intent and some extras.
214        Intent intent = new Intent(ACTION_PROVISION_MANAGED_DEVICE)
215                .putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, TEST_PACKAGE_NAME)
216                .putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, TEST_COMPONENT_NAME)
217                .putExtra(EXTRA_PROVISIONING_SKIP_ENCRYPTION, TEST_SKIP_ENCRYPTION)
218                .putExtra(EXTRA_PROVISIONING_MAIN_COLOR, TEST_MAIN_COLOR)
219                .putExtra(EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE, TEST_ACCOUNT_TO_MIGRATE);
220
221        try {
222            // WHEN the intent is parsed by the parser.
223            ProvisioningParams params = mPropertiesProvisioningDataParser.parse(intent);
224            fail("PropertiesProvisioningDataParser doesn't support intent other than NFC. "
225                    + "IllegalProvisioningArgumentException should be thrown");
226        } catch (IllegalProvisioningArgumentException e) {
227            // THEN IllegalProvisioningArgumentException is thrown.
228        }
229    }
230
231    public void testGetFirstNdefRecord_nullNdefMessages() {
232        // GIVEN nfc intent with no ndef messages
233        Intent nfcIntent = new Intent(NfcAdapter.ACTION_NDEF_DISCOVERED);
234        // WHEN getting first ndef record
235        // THEN it should return null
236        assertNull(PropertiesProvisioningDataParser.getFirstNdefRecord(nfcIntent));
237    }
238
239    public void testGetFirstNdefRecord_noNfcMimeType() {
240        // GIVEN nfc intent with no ndf message with a record with a valid mime type.
241        ByteArrayOutputStream stream = new ByteArrayOutputStream();
242        NdefRecord record = NdefRecord.createMime(INVALID_MIME_TYPE, stream.toByteArray());
243        NdefMessage ndfMsg = new NdefMessage(new NdefRecord[]{record});
244        Intent nfcIntent = new Intent(NfcAdapter.ACTION_NDEF_DISCOVERED)
245                .setType(MIME_TYPE_PROVISIONING_NFC)
246                .putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[]{ndfMsg});
247        // WHEN getting first ndef record
248        // THEN it should return null
249        assertNull(PropertiesProvisioningDataParser.getFirstNdefRecord(nfcIntent));
250    }
251
252    public void testGetFirstNdefRecord() {
253        // GIVEN nfc intent with valid ndf message with a record with mime type nfc.
254        ByteArrayOutputStream stream = new ByteArrayOutputStream();
255        NdefRecord record = NdefRecord.createMime(
256                DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC,
257                stream.toByteArray());
258        NdefMessage ndfMsg = new NdefMessage(new NdefRecord[]{record});
259        Intent nfcIntent = new Intent(NfcAdapter.ACTION_NDEF_DISCOVERED)
260                .setType(MIME_TYPE_PROVISIONING_NFC)
261                .putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[]{ndfMsg});
262        // WHEN getting first ndef record
263        // THEN valid record should be returned
264        assertEquals(PropertiesProvisioningDataParser.getFirstNdefRecord(nfcIntent), record);
265    }
266
267    private static Properties setTestWifiInfo(Properties props) {
268        props.setProperty(EXTRA_PROVISIONING_WIFI_SSID, TEST_SSID);
269        props.setProperty(EXTRA_PROVISIONING_WIFI_SECURITY_TYPE, TEST_SECURITY_TYPE);
270        props.setProperty(EXTRA_PROVISIONING_WIFI_PASSWORD, TEST_PASSWORD);
271        props.setProperty(EXTRA_PROVISIONING_WIFI_PROXY_HOST, TEST_PROXY_HOST);
272        props.setProperty(EXTRA_PROVISIONING_WIFI_PROXY_BYPASS, TEST_PROXY_BYPASS_HOSTS);
273        props.setProperty(EXTRA_PROVISIONING_WIFI_PAC_URL, TEST_PAC_URL);
274        props.setProperty(EXTRA_PROVISIONING_WIFI_PROXY_PORT, Integer.toString(TEST_PROXY_PORT));
275        props.setProperty(EXTRA_PROVISIONING_WIFI_HIDDEN, Boolean.toString(TEST_HIDDEN));
276        return props;
277    }
278
279    private static Properties setTestTimeTimeZoneAndLocale(Properties props) {
280        props.setProperty(EXTRA_PROVISIONING_LOCAL_TIME, Long.toString(TEST_LOCAL_TIME));
281        props.setProperty(EXTRA_PROVISIONING_TIME_ZONE, TEST_TIME_ZONE);
282        props.setProperty(EXTRA_PROVISIONING_LOCALE, StoreUtils.localeToString(TEST_LOCALE));
283        return props;
284    }
285
286    private static Properties setTestDeviceAdminDownload(Properties props) {
287        props.setProperty(
288                EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE,
289                Integer.toString(TEST_MIN_SUPPORT_VERSION));
290        props.setProperty(
291                EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION,
292                TEST_DOWNLOAD_LOCATION);
293        props.setProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER,
294                TEST_COOKIE_HEADER);
295        props.setProperty(
296                EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM,
297                Base64.encodeToString(TEST_PACKAGE_CHECKSUM,
298                        Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));
299        props.setProperty(
300                EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM,
301                Base64.encodeToString(TEST_SIGNATURE_CHECKSUM,
302                        Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));
303        return props;
304    }
305
306    private static String getTestAdminExtrasString() throws Exception {
307        Properties props = new Properties();
308        ByteArrayOutputStream stream = new ByteArrayOutputStream();
309
310        PersistableBundle bundle = getTestAdminExtrasPersistableBundle();
311        for (String key : bundle.keySet()) {
312            props.setProperty(key, bundle.getString(key));
313        }
314        props.store(stream, "ADMIN_EXTRAS_BUNDLE" /* data description */);
315
316        return stream.toString();
317    }
318
319    private static PersistableBundle getTestAdminExtrasPersistableBundle() {
320        PersistableBundle bundle = new PersistableBundle();
321        bundle.putString("key1", "val1");
322        bundle.putString("key2", "val2");
323        bundle.putString("key3", "val3");
324        return bundle;
325    }
326}
327