From 812bc06ef69c687f0a440449273ad78d46da15e0 Mon Sep 17 00:00:00 2001 From: Roshan Pius Date: Wed, 16 Jan 2019 10:57:13 -0800 Subject: WifiNetworkFactory: Allow user approval bypass Whenever user approves a network for a request from an app, store the exact SSID + BSSID that the user approved. If the same app makes a network request for a specific SSID & BSSID which has been previously approved, then the device will immediately trigger connection bypassing the user approval phase. Also, added a method to remove all of the approvals for the app when the app is uninstalled & via shell commands (for ACTS testing). Bug: 122658039 Test: ./frameworks/opt/net/wifi/tests/wifitests/runtests.sh Test: act.py -c wifi_manager.config -tb dut-name -tc WifiNetworkRequestTest Test: act.py -c wifi_manager.config -tb dut-name -tc WifiNetworkRequestTest:test_connect_to_wpa_psk_2g_which_is_already_approved Change-Id: I85b8c872f53d682d47477d067df347352d5aa2d9 --- .../com/android/server/wifi/ClientModeImpl.java | 8 + .../android/server/wifi/WifiNetworkFactory.java | 205 ++++++++++++++- .../com/android/server/wifi/WifiServiceImpl.java | 1 + .../com/android/server/wifi/WifiShellCommand.java | 7 + .../server/wifi/WifiNetworkFactoryTest.java | 276 ++++++++++++++++++++- .../android/server/wifi/WifiServiceImplTest.java | 5 + 6 files changed, 492 insertions(+), 10 deletions(-) diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java index 7906a7576..afd9b127a 100644 --- a/service/java/com/android/server/wifi/ClientModeImpl.java +++ b/service/java/com/android/server/wifi/ClientModeImpl.java @@ -22,6 +22,7 @@ import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING; import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; @@ -6029,6 +6030,13 @@ public class ClientModeImpl extends StateMachine { mNetworkFactory.removeCallback(callbackIdentifier); } + /** + * Remove all approved access points from {@link WifiNetworkFactory} for the provided package. + */ + public void removeNetworkRequestUserApprovedAccessPointsForApp(@NonNull String packageName) { + mNetworkFactory.removeUserApprovedAccessPointsForApp(packageName); + } + /** * Gets the factory MAC address of wlan0 (station interface). * @return String representation of the factory MAC address. diff --git a/service/java/com/android/server/wifi/WifiNetworkFactory.java b/service/java/com/android/server/wifi/WifiNetworkFactory.java index 57d1635db..0552d2c90 100644 --- a/service/java/com/android/server/wifi/WifiNetworkFactory.java +++ b/service/java/com/android/server/wifi/WifiNetworkFactory.java @@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.server.wifi.util.NativeUtil.addEnclosingQuotes; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AlarmManager; import android.content.Context; @@ -41,6 +42,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Messenger; +import android.os.PatternMatcher; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; @@ -49,14 +51,21 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.wifi.ScanResultMatchInfo.NetworkType; import com.android.server.wifi.util.ExternalCallbackTracker; +import com.android.server.wifi.util.ScanResultUtil; import com.android.server.wifi.util.WifiPermissionsUtil; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; /** @@ -96,6 +105,9 @@ public class WifiNetworkFactory extends NetworkFactory { private final ConnectionTimeoutAlarmListener mConnectionTimeoutAlarmListener; private final ExternalCallbackTracker mRegisteredCallbacks; private final Messenger mSrcMessenger; + // Store all user approved access points for apps. + // TODO(b/122658039): Persist this. + private final Map> mUserApprovedAccessPointMap = new HashMap<>(); private WifiScanner mWifiScanner; private int mGenericConnectionReqCount = 0; @@ -117,6 +129,51 @@ public class WifiNetworkFactory extends NetworkFactory { private boolean mIsPeriodicScanPaused = false; private boolean mWifiEnabled = false; + /** + * Helper class to store an access point that the user previously approved for a specific app. + */ + private static class AccessPoint { + public final String ssid; + public final MacAddress bssid; + public final @NetworkType int networkType; + + AccessPoint(@NonNull String ssid, @NonNull MacAddress bssid, @NetworkType int networkType) { + this.ssid = ssid; + this.bssid = bssid; + this.networkType = networkType; + } + + @Override + public int hashCode() { + return Objects.hash(ssid, bssid, networkType); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AccessPoint)) { + return false; + } + AccessPoint other = (AccessPoint) obj; + return TextUtils.equals(this.ssid, other.ssid) + && Objects.equals(this.bssid, other.bssid) + && this.networkType == other.networkType; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("AccessPoint: "); + return sb.append(ssid) + .append(", ") + .append(bssid) + .append(", ") + .append(networkType) + .toString(); + } + } + // Scan listener for scan requests. private class NetworkFactoryScanListener implements WifiScanner.ScanListener { @Override @@ -152,11 +209,28 @@ public class WifiNetworkFactory extends NetworkFactory { } List matchedScanResults = getNetworksMatchingActiveNetworkRequest(scanResults); - sendNetworkRequestMatchCallbacksForActiveRequest(matchedScanResults); mActiveMatchedScanResults = matchedScanResults; - // Didn't find a match, schedule the next scan. - scheduleNextPeriodicScan(); + ScanResult approvedScanResult = null; + if (isActiveRequestForSingleAccessPoint()) { + approvedScanResult = + findUserApprovedAccessPointForActiveRequestFromActiveMatchedScanResults(); + } + if (approvedScanResult == null) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "No approved access points found in matching scan results. " + + "Sending match callback"); + } + sendNetworkRequestMatchCallbacksForActiveRequest(matchedScanResults); + // Didn't find an approved match, schedule the next scan. + scheduleNextPeriodicScan(); + } else { + Log.v(TAG, "Approved access point found in matching scan results. " + + "Triggering connect " + approvedScanResult); + handleConnectToNetworkUserSelectionInternal( + ScanResultUtil.createNetworkFromScanResult(approvedScanResult)); + // TODO (b/122658039): Post notification. + } } @Override @@ -545,12 +619,7 @@ public class WifiNetworkFactory extends NetworkFactory { scheduleConnectionTimeout(); } - private void handleConnectToNetworkUserSelection(WifiConfiguration network) { - Log.d(TAG, "User initiated connect to network: " + network.SSID); - - // Cancel the ongoing scans after user selection. - cancelPeriodicScans(); - + private void handleConnectToNetworkUserSelectionInternal(WifiConfiguration network) { // Disable Auto-join so that NetworkFactory can take control of the network connection. mWifiConnectivityManager.setSpecificNetworkRequestInProgress(true); @@ -563,6 +632,19 @@ public class WifiNetworkFactory extends NetworkFactory { connectToNetwork(network); } + private void handleConnectToNetworkUserSelection(WifiConfiguration network) { + Log.d(TAG, "User initiated connect to network: " + network.SSID); + + // Cancel the ongoing scans after user selection. + cancelPeriodicScans(); + + // Trigger connection attempts. + handleConnectToNetworkUserSelectionInternal(network); + + // Add the network to the approved access point map for the app. + addNetworkToUserApprovedAccessPointMap(network); + } + private void handleRejectUserSelection() { Log.w(TAG, "User dismissed notification, cancelling " + mActiveSpecificNetworkRequest); teardownForActiveRequest(); @@ -697,6 +779,7 @@ public class WifiNetworkFactory extends NetworkFactory { mUserSelectedNetwork = null; mUserSelectedNetworkConnectRetryCount = 0; mIsPeriodicScanPaused = false; + mActiveMatchedScanResults = null; // Cancel periodic scan, connection timeout alarm. cancelPeriodicScans(); cancelConnectionTimeout(); @@ -915,5 +998,109 @@ public class WifiNetworkFactory extends NetworkFactory { mContext.startActivityAsUser(intent, UserHandle.getUserHandleForUid(requestorUid)); } + + // Helper method to determine if the specifier does not contain any patterns and matches + // a single access point. + private boolean isActiveRequestForSingleAccessPoint() { + if (mActiveSpecificNetworkRequestSpecifier == null) return false; + + if (mActiveSpecificNetworkRequestSpecifier.ssidPatternMatcher.getType() + != PatternMatcher.PATTERN_LITERAL) { + return false; + } + if (!Objects.equals( + mActiveSpecificNetworkRequestSpecifier.bssidPatternMatcher.second, + MacAddress.BROADCAST_ADDRESS)) { + return false; + } + return true; + } + + private @Nullable ScanResult + findUserApprovedAccessPointForActiveRequestFromActiveMatchedScanResults() { + if (mActiveSpecificNetworkRequestSpecifier == null + || mActiveMatchedScanResults == null) return null; + String requestorPackageName = mContext.getPackageManager().getNameForUid( + mActiveSpecificNetworkRequestSpecifier.requestorUid); + Set approvedAccessPoints = + mUserApprovedAccessPointMap.get(requestorPackageName); + if (approvedAccessPoints == null) return null; + for (ScanResult scanResult : mActiveMatchedScanResults) { + ScanResultMatchInfo fromScanResult = ScanResultMatchInfo.fromScanResult(scanResult); + AccessPoint accessPoint = + new AccessPoint(scanResult.SSID, + MacAddress.fromString(scanResult.BSSID), fromScanResult.networkType); + if (approvedAccessPoints.contains(accessPoint)) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Found " + accessPoint + + " in user approved access point for " + requestorPackageName); + } + return scanResult; + } + } + return null; + } + + // Helper method to store the all the BSSIDs matching the network from the matched scan results + private void addNetworkToUserApprovedAccessPointMap(@NonNull WifiConfiguration network) { + if (mActiveSpecificNetworkRequestSpecifier == null + || mActiveMatchedScanResults == null) return; + // Note: This hopefully is a list of size 1, because we want to store a 1:1 mapping + // from user selection and the AP that was approved. But, since we get a WifiConfiguration + // object representing an entire network from UI, we need to ensure that all the visible + // BSSIDs matching the original request and the selected network are stored. + Set newUserApprovedAccessPoints = new HashSet<>(); + for (ScanResult scanResult : mActiveMatchedScanResults) { + ScanResultMatchInfo fromScanResult = ScanResultMatchInfo.fromScanResult(scanResult); + ScanResultMatchInfo fromWifiConfiguration = + ScanResultMatchInfo.fromWifiConfiguration(network); + if (fromScanResult.equals(fromWifiConfiguration)) { + AccessPoint approvedAccessPoint = + new AccessPoint(scanResult.SSID, MacAddress.fromString(scanResult.BSSID), + fromScanResult.networkType); + newUserApprovedAccessPoints.add(approvedAccessPoint); + } + } + String requestorPackageName = mContext.getPackageManager().getNameForUid( + mActiveSpecificNetworkRequestSpecifier.requestorUid); + Set approvedAccessPoints = + mUserApprovedAccessPointMap.get(requestorPackageName); + if (approvedAccessPoints == null) { + approvedAccessPoints = new HashSet<>(); + mUserApprovedAccessPointMap.put(requestorPackageName, approvedAccessPoints); + } + approvedAccessPoints.addAll(newUserApprovedAccessPoints); + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Adding " + newUserApprovedAccessPoints + + " to user approved access point for " + requestorPackageName); + } + } + + private String getSimplePackageName(@NonNull String origPackageName) { + // TODO (b/122658039): We could alternatively plumb the package name in the network + // specifier itself. getNameForUid is kind of messy for shared UIDs. + // getNameForUid (Stored in packageName) returns a concatenation of name + // and uid for shared UIDs ("name:uid"). + if (!origPackageName.contains(":")) { + return origPackageName; // regular app not using shared UID. + } + // Separate the package name from the string for app using shared UID. + return origPackageName.substring(0, origPackageName.indexOf(":")); + } + + /** + * Remove all user approved access points for the specified app. + */ + public void removeUserApprovedAccessPointsForApp(@NonNull String packageName) { + Iterator>> iter = + mUserApprovedAccessPointMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry> entry = iter.next(); + if (packageName.equals(getSimplePackageName(entry.getKey()))) { + Log.i(TAG, "Removing all approved access points for " + packageName); + iter.remove(); + } + } + } } diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java index 9fe6b41f1..a92eff9f5 100644 --- a/service/java/com/android/server/wifi/WifiServiceImpl.java +++ b/service/java/com/android/server/wifi/WifiServiceImpl.java @@ -2628,6 +2628,7 @@ public class WifiServiceImpl extends BaseWifiService { mScanRequestProxy.clearScanRequestTimestampsForApp(pkgName, uid); // Remove all suggestions from the package. mWifiNetworkSuggestionsManager.removeApp(pkgName); + mClientModeImpl.removeNetworkRequestUserApprovedAccessPointsForApp(pkgName); }); } } diff --git a/service/java/com/android/server/wifi/WifiShellCommand.java b/service/java/com/android/server/wifi/WifiShellCommand.java index 76682ff91..4e0f5b2d2 100644 --- a/service/java/com/android/server/wifi/WifiShellCommand.java +++ b/service/java/com/android/server/wifi/WifiShellCommand.java @@ -163,6 +163,11 @@ public class WifiShellCommand extends ShellCommand { pw.println(hasUserApproved ? "yes" : "no"); return 0; } + case "network-requests-remove-user-approved-access-points": { + String packageName = getNextArgRequired(); + mClientModeImpl.removeNetworkRequestUserApprovedAccessPointsForApp(packageName); + return 0; + } default: return handleDefaultCommands(cmd); } @@ -204,6 +209,8 @@ public class WifiShellCommand extends ShellCommand { pw.println(" Sets whether network suggestions from the app is approved or not."); pw.println(" network-suggestions-has-user-approved "); pw.println(" Queries whether network suggestions from the app is approved or not."); + pw.println(" network-requests-remove-user-approved-access-points "); + pw.println(" Removes all user approved network requests for the app."); pw.println(); } } diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java index 12c70533c..df0282c48 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java @@ -59,6 +59,7 @@ import android.os.test.TestLooper; import android.test.suitebuilder.annotation.SmallTest; import android.util.Pair; +import com.android.server.wifi.util.ScanResultUtil; import com.android.server.wifi.util.WifiPermissionsUtil; import org.junit.After; @@ -161,6 +162,7 @@ public class WifiNetworkFactoryTest { // Setup with wifi on. mWifiNetworkFactory.setWifiState(true); + mWifiNetworkFactory.enableVerboseLogging(1); } /** @@ -1646,6 +1648,278 @@ public class WifiNetworkFactoryTest { verify(mWifiScanner).startScan(any(), any(), any()); } + /** + * Verify the user approval bypass for a specific request for an access point that was already + * approved previously. + */ + @Test + public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchPreviouslyApproved() + throws Exception { + setupScanData(SCAN_RESULT_TYPE_WPA_PSK, + TEST_SSID_1, TEST_SSID_2, TEST_SSID_3, TEST_SSID_4); + + // 1. First request (no user approval bypass) + ScanResult matchingScanResult1 = mTestScanDatas[0].getResults()[0]; + PatternMatcher ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL); + Pair bssidPatternMatch = + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration( + mNetworkRequestUserSelectionCallback.capture()); + verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); + ArgumentCaptor> matchedScanResultsCaptor = + ArgumentCaptor.forClass(List.class); + verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture()); + assertNotNull(matchedScanResultsCaptor.getValue()); + validateScanResults(matchedScanResultsCaptor.getValue(), matchingScanResult1); + // Now trigger user selection to the network. + mSelectedNetwork = ScanResultUtil.createNetworkFromScanResult(matchingScanResult1); + INetworkRequestUserSelectionCallback networkRequestUserSelectionCallback = + mNetworkRequestUserSelectionCallback.getValue(); + assertNotNull(networkRequestUserSelectionCallback); + networkRequestUserSelectionCallback.select(mSelectedNetwork); + mLooper.dispatchAll(); + + mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER); + reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl); + + // 2. Second request for the same access point (user approval bypass). + ScanResult matchingScanResult2 = matchingScanResult1; + ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL); + bssidPatternMatch = + Pair.create(MacAddress.fromString(matchingScanResult2.BSSID), + MacAddress.BROADCAST_ADDRESS); + specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + // Trigger scan results & ensure we triggered a connect. + verify(mWifiScanner).startScan(any(), mScanListenerArgumentCaptor.capture(), any()); + ScanListener scanListener = mScanListenerArgumentCaptor.getValue(); + assertNotNull(scanListener); + scanListener.onResults(mTestScanDatas); + + // Verify we did not trigger the match callback. + verify(mNetworkRequestMatchCallback, never()).onMatch(anyList()); + // Verify that we sent a connection attempt to ClientModeImpl + verify(mClientModeImpl).sendMessage(any()); + } + + /** + * Verify that we don't bypass user approval for a specific request for an access point that was + * not approved previously. + */ + @Test + public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchNotPreviouslyApproved() + throws Exception { + setupScanData(SCAN_RESULT_TYPE_WPA_PSK, + TEST_SSID_1, TEST_SSID_2, TEST_SSID_3, TEST_SSID_4); + + // 1. First request (no user approval bypass) + ScanResult matchingScanResult1 = mTestScanDatas[0].getResults()[0]; + PatternMatcher ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL); + Pair bssidPatternMatch = + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration( + mNetworkRequestUserSelectionCallback.capture()); + verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); + ArgumentCaptor> matchedScanResultsCaptor = + ArgumentCaptor.forClass(List.class); + verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture()); + assertNotNull(matchedScanResultsCaptor.getValue()); + validateScanResults(matchedScanResultsCaptor.getValue(), matchingScanResult1); + // Now trigger user selection to the network. + mSelectedNetwork = ScanResultUtil.createNetworkFromScanResult(matchingScanResult1); + INetworkRequestUserSelectionCallback networkRequestUserSelectionCallback = + mNetworkRequestUserSelectionCallback.getValue(); + assertNotNull(networkRequestUserSelectionCallback); + networkRequestUserSelectionCallback.select(mSelectedNetwork); + mLooper.dispatchAll(); + + mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER); + reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl); + + // 2. Second request for a different access point (but same network). + setupScanData(SCAN_RESULT_TYPE_WPA_PSK, + TEST_SSID_1, TEST_SSID_1, TEST_SSID_3, TEST_SSID_4); + ScanResult matchingScanResult2 = mTestScanDatas[0].getResults()[1]; + ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL); + bssidPatternMatch = + Pair.create(MacAddress.fromString(matchingScanResult2.BSSID), + MacAddress.BROADCAST_ADDRESS); + specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); + // Verify we triggered the match callback. + matchedScanResultsCaptor = ArgumentCaptor.forClass(List.class); + verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture()); + assertNotNull(matchedScanResultsCaptor.getValue()); + validateScanResults(matchedScanResultsCaptor.getValue(), matchingScanResult2); + // Verify that we did not send a connection attempt to ClientModeImpl. + verify(mClientModeImpl, never()).sendMessage(any()); + } + + /** + * Verify that we don't bypass user approval for a specific request for a network + * (not access point) that was approved previously. + */ + @Test + public void testNetworkSpecifierMatchSuccessUsingLiteralSsidMatchPreviouslyApproved() + throws Exception { + setupScanData(SCAN_RESULT_TYPE_WPA_PSK, + TEST_SSID_1, TEST_SSID_2, TEST_SSID_3, TEST_SSID_4); + + // 1. First request (no user approval bypass) + ScanResult matchingScanResult1 = mTestScanDatas[0].getResults()[0]; + PatternMatcher ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL); + Pair bssidPatternMatch = + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration( + mNetworkRequestUserSelectionCallback.capture()); + verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); + ArgumentCaptor> matchedScanResultsCaptor = + ArgumentCaptor.forClass(List.class); + verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture()); + assertNotNull(matchedScanResultsCaptor.getValue()); + validateScanResults(matchedScanResultsCaptor.getValue(), matchingScanResult1); + // Now trigger user selection to the network. + mSelectedNetwork = ScanResultUtil.createNetworkFromScanResult(matchingScanResult1); + INetworkRequestUserSelectionCallback networkRequestUserSelectionCallback = + mNetworkRequestUserSelectionCallback.getValue(); + assertNotNull(networkRequestUserSelectionCallback); + networkRequestUserSelectionCallback.select(mSelectedNetwork); + mLooper.dispatchAll(); + + mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER); + reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl); + + // 2. Second request for the same network (but not specific access point) + ScanResult matchingScanResult2 = matchingScanResult1; + ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL); + // match-all. + bssidPatternMatch = + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); + // Verify we triggered the match callback. + matchedScanResultsCaptor = ArgumentCaptor.forClass(List.class); + verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture()); + assertNotNull(matchedScanResultsCaptor.getValue()); + validateScanResults(matchedScanResultsCaptor.getValue(), matchingScanResult2); + // Verify that we did not send a connection attempt to ClientModeImpl. + verify(mClientModeImpl, never()).sendMessage(any()); + } + + /** + * Verify the we don't bypass user approval for a specific request for an access point that was + * already approved previously, but was then removed (app uninstalled, user deleted it from + * notification, from tests, etc). + */ + @Test + public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchAfterApprovalsRemove() + throws Exception { + setupScanData(SCAN_RESULT_TYPE_WPA_PSK, + TEST_SSID_1, TEST_SSID_2, TEST_SSID_3, TEST_SSID_4); + + // 1. First request (no user approval bypass) + ScanResult matchingScanResult1 = mTestScanDatas[0].getResults()[0]; + PatternMatcher ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL); + Pair bssidPatternMatch = + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration( + mNetworkRequestUserSelectionCallback.capture()); + verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); + ArgumentCaptor> matchedScanResultsCaptor = + ArgumentCaptor.forClass(List.class); + verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture()); + assertNotNull(matchedScanResultsCaptor.getValue()); + validateScanResults(matchedScanResultsCaptor.getValue(), matchingScanResult1); + // Now trigger user selection to the network. + mSelectedNetwork = ScanResultUtil.createNetworkFromScanResult(matchingScanResult1); + INetworkRequestUserSelectionCallback networkRequestUserSelectionCallback = + mNetworkRequestUserSelectionCallback.getValue(); + assertNotNull(networkRequestUserSelectionCallback); + networkRequestUserSelectionCallback.select(mSelectedNetwork); + mLooper.dispatchAll(); + + mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER); + reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl); + + // 2. Remove all approvals for the app. + mWifiNetworkFactory.removeUserApprovedAccessPointsForApp(TEST_PACKAGE_NAME_1); + + // 3. Second request for the same access point + ScanResult matchingScanResult2 = matchingScanResult1; + ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL); + bssidPatternMatch = + Pair.create(MacAddress.fromString(matchingScanResult2.BSSID), + MacAddress.ALL_ZEROS_ADDRESS); + specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); + // Verify we triggered the match callback. + matchedScanResultsCaptor = ArgumentCaptor.forClass(List.class); + verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture()); + assertNotNull(matchedScanResultsCaptor.getValue()); + validateScanResults(matchedScanResultsCaptor.getValue(), matchingScanResult2); + // Verify that we did not send a connection attempt to ClientModeImpl. + verify(mClientModeImpl, never()).sendMessage(any()); + } + private Messenger sendNetworkRequestAndSetupForConnectionStatus() throws RemoteException { return sendNetworkRequestAndSetupForConnectionStatus(TEST_SSID_1); } @@ -1756,7 +2030,7 @@ public class WifiNetworkFactoryTest { assertNotNull(alarmListener); } - verifyNoMoreInteractions(mWifiScanner, mAlarmManager); + mInOrder.verifyNoMoreInteractions(); } private WifiNetworkSpecifier createWifiNetworkSpecifier(int uid, boolean isHidden) { diff --git a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java index 0a683fed2..e73d2219e 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java @@ -2734,6 +2734,7 @@ public class WifiServiceImplTest { mLooper.dispatchAll(); verify(mScanRequestProxy).clearScanRequestTimestampsForApp(packageName, uid); verify(mWifiNetworkSuggestionsManager).removeApp(packageName); + verify(mClientModeImpl).removeNetworkRequestUserApprovedAccessPointsForApp(packageName); } @Test @@ -2755,6 +2756,8 @@ public class WifiServiceImplTest { mLooper.dispatchAll(); verify(mScanRequestProxy, never()).clearScanRequestTimestampsForApp(anyString(), anyInt()); verify(mWifiNetworkSuggestionsManager, never()).removeApp(anyString()); + verify(mClientModeImpl, never()).removeNetworkRequestUserApprovedAccessPointsForApp( + packageName); } @Test @@ -2776,6 +2779,8 @@ public class WifiServiceImplTest { mLooper.dispatchAll(); verify(mScanRequestProxy, never()).clearScanRequestTimestampsForApp(anyString(), anyInt()); verify(mWifiNetworkSuggestionsManager, never()).removeApp(anyString()); + verify(mClientModeImpl, never()).removeNetworkRequestUserApprovedAccessPointsForApp( + anyString()); } @Test -- cgit v1.2.3