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