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.ACTION_PROVISION_MANAGED_DEVICE;
19 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
20 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
21 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
22 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE;
23 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM;
24 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER;
25 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION;
26 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
27 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM;
28 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCALE;
29 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCAL_TIME;
30 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_MAIN_COLOR;
31 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION;
32 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TIME_ZONE;
33 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_HIDDEN;
34 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PAC_URL;
35 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD;
36 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_BYPASS;
37 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_HOST;
38 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_PORT;
39 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE;
40 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID;
41 import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
42 import static org.mockito.Mockito.when;
43 
44 import android.accounts.Account;
45 import android.app.admin.DevicePolicyManager;
46 import android.content.ComponentName;
47 import android.content.Context;
48 import android.content.Intent;
49 import android.nfc.NdefMessage;
50 import android.nfc.NdefRecord;
51 import android.nfc.NfcAdapter;
52 import android.os.PersistableBundle;
53 import android.test.AndroidTestCase;
54 import android.test.suitebuilder.annotation.SmallTest;
55 import android.util.Base64;
56 import com.android.managedprovisioning.common.IllegalProvisioningArgumentException;
57 import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences;
58 import com.android.managedprovisioning.common.StoreUtils;
59 import com.android.managedprovisioning.common.Utils;
60 import com.android.managedprovisioning.model.PackageDownloadInfo;
61 import com.android.managedprovisioning.model.ProvisioningParams;
62 import com.android.managedprovisioning.model.WifiInfo;
63 import java.io.ByteArrayOutputStream;
64 import java.util.Locale;
65 import java.util.Properties;
66 import org.mockito.Mock;
67 import org.mockito.MockitoAnnotations;
68 
69 /** Tests for {@link PropertiesProvisioningDataParser} */
70 @SmallTest
71 public 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
setUp()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 
testParse_nfcProvisioningIntent()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                         .build(),
208                 params);
209     }
210 
testParse_OtherIntentsThrowsException()211     public void testParse_OtherIntentsThrowsException() {
212         // GIVEN a managed device provisioning intent and some extras.
213         Intent intent = new Intent(ACTION_PROVISION_MANAGED_DEVICE)
214                 .putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, TEST_PACKAGE_NAME)
215                 .putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, TEST_COMPONENT_NAME)
216                 .putExtra(EXTRA_PROVISIONING_SKIP_ENCRYPTION, TEST_SKIP_ENCRYPTION)
217                 .putExtra(EXTRA_PROVISIONING_MAIN_COLOR, TEST_MAIN_COLOR)
218                 .putExtra(EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE, TEST_ACCOUNT_TO_MIGRATE);
219 
220         try {
221             // WHEN the intent is parsed by the parser.
222             ProvisioningParams params = mPropertiesProvisioningDataParser.parse(intent);
223             fail("PropertiesProvisioningDataParser doesn't support intent other than NFC. "
224                     + "IllegalProvisioningArgumentException should be thrown");
225         } catch (IllegalProvisioningArgumentException e) {
226             // THEN IllegalProvisioningArgumentException is thrown.
227         }
228     }
229 
testGetFirstNdefRecord_nullNdefMessages()230     public void testGetFirstNdefRecord_nullNdefMessages() {
231         // GIVEN nfc intent with no ndef messages
232         Intent nfcIntent = new Intent(NfcAdapter.ACTION_NDEF_DISCOVERED);
233         // WHEN getting first ndef record
234         // THEN it should return null
235         assertNull(PropertiesProvisioningDataParser.getFirstNdefRecord(nfcIntent));
236     }
237 
testGetFirstNdefRecord_noNfcMimeType()238     public void testGetFirstNdefRecord_noNfcMimeType() {
239         // GIVEN nfc intent with no ndf message with a record with a valid mime type.
240         ByteArrayOutputStream stream = new ByteArrayOutputStream();
241         NdefRecord record = NdefRecord.createMime(INVALID_MIME_TYPE, stream.toByteArray());
242         NdefMessage ndfMsg = new NdefMessage(new NdefRecord[]{record});
243         Intent nfcIntent = new Intent(NfcAdapter.ACTION_NDEF_DISCOVERED)
244                 .setType(MIME_TYPE_PROVISIONING_NFC)
245                 .putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[]{ndfMsg});
246         // WHEN getting first ndef record
247         // THEN it should return null
248         assertNull(PropertiesProvisioningDataParser.getFirstNdefRecord(nfcIntent));
249     }
250 
testGetFirstNdefRecord()251     public void testGetFirstNdefRecord() {
252         // GIVEN nfc intent with valid ndf message with a record with mime type nfc.
253         ByteArrayOutputStream stream = new ByteArrayOutputStream();
254         NdefRecord record = NdefRecord.createMime(
255                 DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC,
256                 stream.toByteArray());
257         NdefMessage ndfMsg = new NdefMessage(new NdefRecord[]{record});
258         Intent nfcIntent = new Intent(NfcAdapter.ACTION_NDEF_DISCOVERED)
259                 .setType(MIME_TYPE_PROVISIONING_NFC)
260                 .putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[]{ndfMsg});
261         // WHEN getting first ndef record
262         // THEN valid record should be returned
263         assertEquals(PropertiesProvisioningDataParser.getFirstNdefRecord(nfcIntent), record);
264     }
265 
setTestWifiInfo(Properties props)266     private static Properties setTestWifiInfo(Properties props) {
267         props.setProperty(EXTRA_PROVISIONING_WIFI_SSID, TEST_SSID);
268         props.setProperty(EXTRA_PROVISIONING_WIFI_SECURITY_TYPE, TEST_SECURITY_TYPE);
269         props.setProperty(EXTRA_PROVISIONING_WIFI_PASSWORD, TEST_PASSWORD);
270         props.setProperty(EXTRA_PROVISIONING_WIFI_PROXY_HOST, TEST_PROXY_HOST);
271         props.setProperty(EXTRA_PROVISIONING_WIFI_PROXY_BYPASS, TEST_PROXY_BYPASS_HOSTS);
272         props.setProperty(EXTRA_PROVISIONING_WIFI_PAC_URL, TEST_PAC_URL);
273         props.setProperty(EXTRA_PROVISIONING_WIFI_PROXY_PORT, Integer.toString(TEST_PROXY_PORT));
274         props.setProperty(EXTRA_PROVISIONING_WIFI_HIDDEN, Boolean.toString(TEST_HIDDEN));
275         return props;
276     }
277 
setTestTimeTimeZoneAndLocale(Properties props)278     private static Properties setTestTimeTimeZoneAndLocale(Properties props) {
279         props.setProperty(EXTRA_PROVISIONING_LOCAL_TIME, Long.toString(TEST_LOCAL_TIME));
280         props.setProperty(EXTRA_PROVISIONING_TIME_ZONE, TEST_TIME_ZONE);
281         props.setProperty(EXTRA_PROVISIONING_LOCALE, StoreUtils.localeToString(TEST_LOCALE));
282         return props;
283     }
284 
setTestDeviceAdminDownload(Properties props)285     private static Properties setTestDeviceAdminDownload(Properties props) {
286         props.setProperty(
287                 EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE,
288                 Integer.toString(TEST_MIN_SUPPORT_VERSION));
289         props.setProperty(
290                 EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION,
291                 TEST_DOWNLOAD_LOCATION);
292         props.setProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER,
293                 TEST_COOKIE_HEADER);
294         props.setProperty(
295                 EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM,
296                 Base64.encodeToString(TEST_PACKAGE_CHECKSUM,
297                         Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));
298         props.setProperty(
299                 EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM,
300                 Base64.encodeToString(TEST_SIGNATURE_CHECKSUM,
301                         Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));
302         return props;
303     }
304 
getTestAdminExtrasString()305     private static String getTestAdminExtrasString() throws Exception {
306         Properties props = new Properties();
307         ByteArrayOutputStream stream = new ByteArrayOutputStream();
308 
309         PersistableBundle bundle = getTestAdminExtrasPersistableBundle();
310         for (String key : bundle.keySet()) {
311             props.setProperty(key, bundle.getString(key));
312         }
313         props.store(stream, "ADMIN_EXTRAS_BUNDLE" /* data description */);
314 
315         return stream.toString();
316     }
317 
getTestAdminExtrasPersistableBundle()318     private static PersistableBundle getTestAdminExtrasPersistableBundle() {
319         PersistableBundle bundle = new PersistableBundle();
320         bundle.putString("key1", "val1");
321         bundle.putString("key2", "val2");
322         bundle.putString("key3", "val3");
323         return bundle;
324     }
325 }
326