1 /*
2  * Copyright (C) 2022 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.vpndialogs;
18 
19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
20 
21 import static junit.framework.Assert.assertFalse;
22 import static junit.framework.Assert.assertTrue;
23 
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.ArgumentMatchers.anyInt;
26 import static org.mockito.ArgumentMatchers.anyString;
27 import static org.mockito.Mockito.doReturn;
28 
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.pm.ApplicationInfo;
34 import android.content.pm.PackageManager;
35 import android.net.VpnManager;
36 
37 import androidx.test.core.app.ActivityScenario;
38 import androidx.test.ext.junit.runners.AndroidJUnit4;
39 
40 import org.junit.Before;
41 import org.junit.Test;
42 import org.junit.runner.RunWith;
43 import org.mockito.Mock;
44 import org.mockito.MockitoAnnotations;
45 
46 @RunWith(AndroidJUnit4.class)
47 public class VpnDialogTest {
48     private ActivityScenario<ConfirmDialog> mActivityScenario;
49 
50     @SuppressWarnings("StaticMockMember")
51     @Mock
52     private static PackageManager sPm;
53 
54     @SuppressWarnings("StaticMockMember")
55     @Mock
56     private static VpnManager sVm;
57 
58     @Mock
59     private ApplicationInfo mAi;
60 
61     private static final String VPN_APP_NAME = "VpnApp";
62     private static final String VPN_APP_PACKAGE_NAME = "com.android.vpndialogs.VpnDialogTest";
63     private static final String VPN_LABEL_CONTAINS_HTML_TAG =
64             "<b><a href=\"https://www.malicious.vpn.app.com\">Google Play</a>";
65     private static final String VPN_LABEL_CONTAINS_HTML_TAG_AND_VIOLATE_LENGTH_RESTRICTION =
66             "<b><a href=\"https://www.malicious.vpn.app.com\">Google Play</a></b>"
67             + " Wants to connect the network. <br></br><br></br><br></br><br></br><br></br>"
68             + " <br></br><br></br><br></br><br></br><br></br><br></br><br></br><br></br> Deny it?";
69     private static final String VPN_LABEL_VIOLATES_LENGTH_RESTRICTION = "This is a VPN label"
70             + " which violates the length restriction. The length restriction here are 150 code"
71             + " points. So the VPN label should be sanitized, and shows the package name to the"
72             + " user.";
73 
74     public static class InstrumentedConfirmDialog extends ConfirmDialog {
75         @Override
getPackageManager()76         public PackageManager getPackageManager() {
77             return sPm;
78         }
79 
80         @Override
getSystemService(@erviceName @onNull String name)81         public @Nullable Object getSystemService(@ServiceName @NonNull String name) {
82             switch (name) {
83                 case Context.VPN_MANAGEMENT_SERVICE:
84                     return sVm;
85                 default:
86                     return super.getSystemService(name);
87             }
88         }
89 
90         @Override
getCallingPackage()91         public String getCallingPackage() {
92             return VPN_APP_PACKAGE_NAME;
93         }
94     }
95 
launchActivity()96     private void launchActivity() {
97         final Context context = getInstrumentation().getContext();
98         mActivityScenario = ActivityScenario.launch(
99                 new Intent(context, InstrumentedConfirmDialog.class));
100     }
101 
102     @Test
testGetSanitizedVpnLabel_withNormalCase()103     public void testGetSanitizedVpnLabel_withNormalCase() throws Exception {
104         // Test the normal case that the VPN label showed in the VpnDialog is the app name.
105         doReturn(VPN_APP_NAME).when(mAi).loadLabel(sPm);
106         launchActivity();
107         mActivityScenario.onActivity(activity -> {
108             assertTrue(activity.getWarningText().toString().contains(VPN_APP_NAME));
109         });
110     }
111 
verifySanitizedVpnLabel(String originalLabel)112     private void verifySanitizedVpnLabel(String originalLabel) {
113         doReturn(originalLabel).when(mAi).loadLabel(sPm);
114         launchActivity();
115         mActivityScenario.onActivity(activity -> {
116             // The VPN label was sanitized because violating length restriction or having a html
117             // tag, so the warning message will contain the package name.
118             assertTrue(activity.getWarningText().toString().contains(activity.getCallingPackage()));
119             // Also, the length of sanitized VPN label shouldn't longer than MAX_VPN_LABEL_LENGTH
120             // and it shouldn't contain html tag.
121             final String sanitizedVpnLabel =
122                     activity.getSanitizedVpnLabel(originalLabel, VPN_APP_PACKAGE_NAME);
123             assertTrue(sanitizedVpnLabel.codePointCount(0, sanitizedVpnLabel.length())
124                     < ConfirmDialog.MAX_VPN_LABEL_LENGTH);
125             assertFalse(sanitizedVpnLabel.contains("<b>"));
126         });
127     }
128 
129     @Test
testGetSanitizedVpnLabel_withHtmlTag()130     public void testGetSanitizedVpnLabel_withHtmlTag() throws Exception {
131         // Test the case that the VPN label was sanitized because there is a html tag.
132         verifySanitizedVpnLabel(VPN_LABEL_CONTAINS_HTML_TAG);
133     }
134 
135     @Test
testGetSanitizedVpnLabel_withHtmlTagAndViolateLengthRestriction()136     public void testGetSanitizedVpnLabel_withHtmlTagAndViolateLengthRestriction() throws Exception {
137         // Test the case that the VPN label was sanitized because there is a html tag.
138         verifySanitizedVpnLabel(VPN_LABEL_CONTAINS_HTML_TAG_AND_VIOLATE_LENGTH_RESTRICTION);
139     }
140 
141     @Test
testGetSanitizedVpnLabel_withLengthRestriction()142     public void testGetSanitizedVpnLabel_withLengthRestriction() throws Exception {
143         // Test the case that the VPN label was sanitized because hitting the length restriction.
144         verifySanitizedVpnLabel(VPN_LABEL_VIOLATES_LENGTH_RESTRICTION);
145     }
146 
147     @Before
setUp()148     public void setUp() throws Exception {
149         MockitoAnnotations.initMocks(this);
150         doReturn(false).when(sVm).prepareVpn(anyString(), anyString(), anyInt());
151         doReturn(null).when(sPm).queryIntentServices(any(), anyInt());
152         doReturn(mAi).when(sPm).getApplicationInfo(anyString(), anyInt());
153     }
154 }
155