diff options
author | Roshan Pius <rpius@google.com> | 2020-03-30 22:25:40 -0700 |
---|---|---|
committer | Roshan Pius <rpius@google.com> | 2020-03-31 14:30:28 -0700 |
commit | 7832f03e1a66dc144d8f33ed48a3405034d75762 (patch) | |
tree | 23daa4772708d12b5a2c078fd01ef995b3a66f83 | |
parent | bd16d74680d652c9f48fd3dafb9149e6cedda9af (diff) |
WifiShellCommand: Add cmds for approving NetworkRequest from app
This is useful for bypassing the UI for local testing. Needs rooted
shell.
Also, added a utility method for parsing boolean params.
Bug: 152299953
Test: Manual Steps to connect to a network via network request
adb root
adb shell cmd wifi network-requests-set-user-approved android yes
abd shell cmd wifi add-request <ssid> open
abd shell cmd wifi remove-request <ssid> open
abd shell cmd wifi add-request <ssid> wpa2 <passphrase>
abd shell cmd wifi remove-request <ssid> wpa2
Change-Id: Ib59fd1869d8827cc79d99058dca4dc9c8774b954
4 files changed, 162 insertions, 117 deletions
diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java index 588564ed9..8579887a7 100644 --- a/service/java/com/android/server/wifi/ClientModeImpl.java +++ b/service/java/com/android/server/wifi/ClientModeImpl.java @@ -5895,6 +5895,22 @@ public class ClientModeImpl extends StateMachine { } /** + * Approve all access points from {@link WifiNetworkFactory} for the provided package. + * Used by shell commands. + */ + public void setNetworkRequestUserApprovedApp(@NonNull String packageName, boolean approved) { + mNetworkFactory.setUserApprovedApp(packageName, approved); + } + + /** + * Whether all access points are approved for the specified app. + * Used by shell commands. + */ + public boolean hasNetworkRequestUserApprovedApp(@NonNull String packageName) { + return mNetworkFactory.hasUserApprovedApp(packageName); + } + + /** * Remove all approved access points from {@link WifiNetworkFactory} for the provided package. */ public void removeNetworkRequestUserApprovedAccessPointsForApp(@NonNull String packageName) { diff --git a/service/java/com/android/server/wifi/WifiNetworkFactory.java b/service/java/com/android/server/wifi/WifiNetworkFactory.java index 0af51f7c9..271bbefc4 100644 --- a/service/java/com/android/server/wifi/WifiNetworkFactory.java +++ b/service/java/com/android/server/wifi/WifiNetworkFactory.java @@ -128,6 +128,8 @@ public class WifiNetworkFactory extends NetworkFactory { public final Map<String, LinkedHashSet<AccessPoint>> mUserApprovedAccessPointMap; private WifiScanner mWifiScanner; private CompanionDeviceManager mCompanionDeviceManager; + // Temporary approval set by shell commands. + private String mApprovedApp = null; private int mGenericConnectionReqCount = 0; // Request that is being actively processed. All new requests start out as an "active" request @@ -1317,6 +1319,10 @@ public class WifiNetworkFactory extends NetworkFactory { if (isAccessPointApprovedInInternalApprovalList(scanResult, requestorPackageName)) { return scanResult; } + // Shell approved app + if (TextUtils.equals(mApprovedApp, requestorPackageName)) { + return scanResult; + } // no bypass approvals, show UI. return null; } @@ -1439,6 +1445,26 @@ public class WifiNetworkFactory extends NetworkFactory { } /** + * Sets all access points approved for the specified app. + * Used by shell commands. + */ + public void setUserApprovedApp(@NonNull String packageName, boolean approved) { + if (approved) { + mApprovedApp = packageName; + } else if (TextUtils.equals(packageName, mApprovedApp)) { + mApprovedApp = null; + } + } + + /** + * Whether all access points are approved for the specified app. + * Used by shell commands. + */ + public boolean hasUserApprovedApp(@NonNull String packageName) { + return TextUtils.equals(packageName, mApprovedApp); + } + + /** * Remove all user approved access points for the specified app. */ public void removeUserApprovedAccessPointsForApp(@NonNull String packageName) { @@ -1453,6 +1479,7 @@ public class WifiNetworkFactory extends NetworkFactory { */ public void clear() { mUserApprovedAccessPointMap.clear(); + mApprovedApp = null; Log.i(TAG, "Cleared all internal state"); saveToStore(); } diff --git a/service/java/com/android/server/wifi/WifiShellCommand.java b/service/java/com/android/server/wifi/WifiShellCommand.java index 5fa0490e8..7938aafca 100644 --- a/service/java/com/android/server/wifi/WifiShellCommand.java +++ b/service/java/com/android/server/wifi/WifiShellCommand.java @@ -23,6 +23,7 @@ import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; import android.content.Context; import android.content.pm.ParceledListSlice; import android.net.ConnectivityManager; +import android.net.MacAddress; import android.net.NetworkRequest; import android.net.wifi.IActionListener; import android.net.wifi.ScanResult; @@ -49,6 +50,7 @@ import com.android.server.wifi.util.ScanResultUtil; import java.io.PrintWriter; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; @@ -64,11 +66,9 @@ import java.util.concurrent.TimeUnit; * if command executed successfully. * - onHelp: add a description string. * - * If additional state objects are necessary add them to the - * constructor. - * - * Permissions: currently root permission is required for most - * commands, which is checked using {@link #checkRootPermission()}. + * Permissions: currently root permission is required for some commands. Others will + * enforce the corresponding API permissions. + * TODO (b/152875610): Add unit tests. */ public class WifiShellCommand extends BasicShellCommandHandler { private static String SHELL_PACKAGE_NAME = "com.android.shell"; @@ -140,18 +140,7 @@ public class WifiShellCommand extends BasicShellCommandHandler { try { switch (cmd) { case "set-ipreach-disconnect": { - boolean enabled; - String nextArg = getNextArgRequired(); - if ("enabled".equals(nextArg)) { - enabled = true; - } else if ("disabled".equals(nextArg)) { - enabled = false; - } else { - pw.println( - "Invalid argument to 'set-ipreach-disconnect' - must be 'enabled'" - + " or 'disabled'"); - return -1; - } + boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled"); mClientModeImpl.setIpReachabilityDisconnectEnabled(enabled); return 0; } @@ -184,36 +173,14 @@ public class WifiShellCommand extends BasicShellCommandHandler { + mClientModeImpl.getPollRssiIntervalMsecs()); return 0; case "force-hi-perf-mode": { - boolean enabled; - String nextArg = getNextArgRequired(); - if ("enabled".equals(nextArg)) { - enabled = true; - } else if ("disabled".equals(nextArg)) { - enabled = false; - } else { - pw.println( - "Invalid argument to 'force-hi-perf-mode' - must be 'enabled'" - + " or 'disabled'"); - return -1; - } + boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled"); if (!mWifiLockManager.forceHiPerfMode(enabled)) { pw.println("Command execution failed"); } return 0; } case "force-low-latency-mode": { - boolean enabled; - String nextArg = getNextArgRequired(); - if ("enabled".equals(nextArg)) { - enabled = true; - } else if ("disabled".equals(nextArg)) { - enabled = false; - } else { - pw.println( - "Invalid argument to 'force-low-latency-mode' - must be 'enabled'" - + " or 'disabled'"); - return -1; - } + boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled"); if (!mWifiLockManager.forceLowLatencyMode(enabled)) { pw.println("Command execution failed"); } @@ -221,18 +188,7 @@ public class WifiShellCommand extends BasicShellCommandHandler { } case "network-suggestions-set-user-approved": { String packageName = getNextArgRequired(); - boolean approved; - String nextArg = getNextArgRequired(); - if ("yes".equals(nextArg)) { - approved = true; - } else if ("no".equals(nextArg)) { - approved = false; - } else { - pw.println( - "Invalid argument to 'network-suggestions-set-user-approved' " - + "- must be 'yes' or 'no'"); - return -1; - } + boolean approved = getNextArgRequiredTrueOrFalse("yes", "no"); mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(approved, packageName); return 0; } @@ -247,7 +203,6 @@ public class WifiShellCommand extends BasicShellCommandHandler { String arg1 = getNextArgRequired(); String arg2 = getNextArgRequired(); int carrierId = -1; - boolean approved; try { carrierId = Integer.parseInt(arg1); } catch (NumberFormatException e) { @@ -256,16 +211,7 @@ public class WifiShellCommand extends BasicShellCommandHandler { + "- carrierId must be an Integer"); return -1; } - if ("yes".equals(arg2)) { - approved = true; - } else if ("no".equals(arg2)) { - approved = false; - } else { - pw.println("Invalid argument to " - + "'imsi-protection-exemption-set-user-approved-for-carrier' " - + "- must be 'yes' or 'no'"); - return -1; - } + boolean approved = getNextArgRequiredTrueOrFalse("yes", "no"); mWifiNetworkSuggestionsManager .setHasUserApprovedImsiPrivacyExemptionForCarrier(approved, carrierId); return 0; @@ -313,8 +259,8 @@ public class WifiShellCommand extends BasicShellCommandHandler { return sendLinkProbe(pw); } case "force-softap-channel": { - String nextArg = getNextArgRequired(); - if ("enabled".equals(nextArg)) { + boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled"); + if (enabled) { int apChannelMHz; try { apChannelMHz = Integer.parseInt(getNextArgRequired()); @@ -342,19 +288,14 @@ public class WifiShellCommand extends BasicShellCommandHandler { mHostapdHal.enableForceSoftApChannel(apChannel, band); return 0; - } else if ("disabled".equals(nextArg)) { + } else { mHostapdHal.disableForceSoftApChannel(); return 0; - } else { - pw.println( - "Invalid argument to 'force-softap-channel' - must be 'enabled'" - + " or 'disabled'"); - return -1; } } case "force-country-code": { - String nextArg = getNextArgRequired(); - if ("enabled".equals(nextArg)) { + boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled"); + if (enabled) { String countryCode = getNextArgRequired(); if (!(countryCode.length() == 2 && countryCode.chars().allMatch(Character::isLetter))) { @@ -364,14 +305,9 @@ public class WifiShellCommand extends BasicShellCommandHandler { } mWifiCountryCode.enableForceCountryCode(countryCode); return 0; - } else if ("disabled".equals(nextArg)) { + } else { mWifiCountryCode.disableForceCountryCode(); return 0; - } else { - pw.println( - "Invalid argument to 'force-country-code' - must be 'enabled'" - + " or 'disabled'"); - return -1; } } case "get-country-code": { @@ -380,18 +316,7 @@ public class WifiShellCommand extends BasicShellCommandHandler { return 0; } case "set-wifi-watchdog": { - boolean enabled; - String nextArg = getNextArgRequired(); - if ("enabled".equals(nextArg)) { - enabled = true; - } else if ("disabled".equals(nextArg)) { - enabled = false; - } else { - pw.println( - "Invalid argument to 'set-wifi-watchdog' - must be 'enabled'" - + " or 'disabled'"); - return -1; - } + boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled"); mWifiLastResortWatchdog.setWifiWatchdogFeature(enabled); return 0; } @@ -401,18 +326,7 @@ public class WifiShellCommand extends BasicShellCommandHandler { return 0; } case "set-wifi-enabled": { - boolean enabled; - String nextArg = getNextArgRequired(); - if ("enabled".equals(nextArg)) { - enabled = true; - } else if ("disabled".equals(nextArg)) { - enabled = false; - } else { - pw.println( - "Invalid argument to 'set-wifi-enabled' - must be 'enabled'" - + " or 'disabled'"); - return -1; - } + boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled"); mWifiService.setWifiEnabled(SHELL_PACKAGE_NAME, enabled); return 0; } @@ -544,21 +458,11 @@ public class WifiShellCommand extends BasicShellCommandHandler { } } break; - case "set-verbose-logging": - boolean enabled; - String nextArg = getNextArgRequired(); - if ("enabled".equals(nextArg)) { - enabled = true; - } else if ("disabled".equals(nextArg)) { - enabled = false; - } else { - pw.println( - "Invalid argument to 'set-verbose-logging' - must be 'enabled'" - + " or 'disabled'"); - return -1; - } + case "set-verbose-logging": { + boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled"); mWifiService.enableVerboseLogging(enabled ? 1 : 0); break; + } case "add-suggestion": mWifiService.addNetworkSuggestions( Arrays.asList(buildSuggestion(pw)), SHELL_PACKAGE_NAME, null); @@ -649,9 +553,24 @@ public class WifiShellCommand extends BasicShellCommandHandler { } } break; + case "network-requests-set-user-approved": { + String packageName = getNextArgRequired(); + boolean approved = getNextArgRequiredTrueOrFalse("yes", "no"); + mClientModeImpl.setNetworkRequestUserApprovedApp(packageName, approved); + return 0; + } + case "network-requests-has-user-approved": { + String packageName = getNextArgRequired(); + boolean hasUserApproved = + mClientModeImpl.hasNetworkRequestUserApprovedApp(packageName); + pw.println(hasUserApproved ? "yes" : "no"); + return 0; + } default: return handleDefaultCommands(cmd); } + } catch (IllegalArgumentException e) { + pw.println("Invalid args for " + cmd + ": " + e); } catch (Exception e) { pw.println("Exception while executing WifiShellCommand: "); e.printStackTrace(pw); @@ -659,6 +578,19 @@ public class WifiShellCommand extends BasicShellCommandHandler { return -1; } + private boolean getNextArgRequiredTrueOrFalse(String trueString, String falseString) + throws IllegalArgumentException { + String nextArg = getNextArgRequired(); + if (trueString.equals(nextArg)) { + return true; + } else if (falseString.equals(nextArg)) { + return false; + } else { + throw new IllegalArgumentException("Expected '" + trueString + "' or '" + falseString + + "' as next arg but got '" + nextArg + "'"); + } + } + private WifiNetworkSuggestion buildSuggestion(PrintWriter pw) { String ssid = getNextArgRequired(); String type = getNextArgRequired(); @@ -698,6 +630,19 @@ public class WifiShellCommand extends BasicShellCommandHandler { pw.println("Unknown network type " + type); return null; } + // Permission approval bypass is only available to requests with both ssid & bssid set. + // So, find scan result with the best rssi level to set in the request. + ScanResult matchingScanResult = + mWifiService.getScanResults(SHELL_PACKAGE_NAME, null) + .stream() + .filter(s -> s.SSID.equals(ssid)) + .max(Comparator.comparingInt(s -> s.level)) + .orElse(null); + if (matchingScanResult != null) { + specifierBuilder.setBssid(MacAddress.fromString(matchingScanResult.BSSID)); + } else { + pw.println("No matching bssid found, request will need UI approval"); + } return new NetworkRequest.Builder() .addTransportType(TRANSPORT_WIFI) .removeCapability(NET_CAPABILITY_INTERNET) @@ -858,6 +803,8 @@ public class WifiShellCommand extends BasicShellCommandHandler { pw.println(" Initiates wifi settings reset"); pw.println(" add-request <ssid> open|owe|wpa2|wpa3 [<passphrase>]"); pw.println(" Add a network request with provided params"); + pw.println(" Use 'network-requests-set-user-approved android yes'" + + " to pre-approve requests added via rooted shell (Not persisted)"); pw.println(" <ssid> - SSID of the network"); pw.println(" open|owe|wpa2|wpa3 - Security type of the network."); pw.println(" - Use 'open' or 'owe' for networks with no passphrase"); @@ -872,6 +819,13 @@ public class WifiShellCommand extends BasicShellCommandHandler { pw.println(" Removes all active requests added via shell"); pw.println(" list-requests"); pw.println(" Lists the requested networks added via shell"); + pw.println(" network-requests-set-user-approved <package name> yes|no"); + pw.println(" Sets whether network requests from the app is approved or not."); + pw.println(" Note: Only 1 such app can be approved from the shell at a time"); + pw.println(" network-requests-has-user-approved <package name>"); + pw.println(" Queries whether network requests from the app is approved or not."); + pw.println(" Note: This only returns whether the app was set via the " + + "'network-requests-set-user-approved' shell command"); } @Override diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java index afa9ffd53..684fd1449 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java @@ -2510,6 +2510,54 @@ public class WifiNetworkFactoryTest extends WifiBaseTest { /** * Verify the user approval bypass for a specific request for an access point that was already + * approved previously via shell command and the scan result is present in the cached scan + * results. + */ + @Test + public void + testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchApprovedViaShellWithCache() + throws Exception { + // Setup scan data for WPA-PSK networks. + setupScanData(SCAN_RESULT_TYPE_WPA_PSK, + TEST_SSID_1, TEST_SSID_2, TEST_SSID_3, TEST_SSID_4); + + // Choose the matching scan result. + ScanResult matchingScanResult = mTestScanDatas[0].getResults()[0]; + + // Setup shell approval for the scan result. + mWifiNetworkFactory.setUserApprovedApp(TEST_PACKAGE_NAME_1, true); + + // simulate no cache expiry + when(mClock.getElapsedSinceBootMillis()).thenReturn(0L); + // Simulate the cached results matching. + when(mWifiScanner.getSingleScanResults()) + .thenReturn(Arrays.asList(mTestScanDatas[0].getResults())); + + PatternMatcher ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL); + Pair<MacAddress, MacAddress> bssidPatternMatch = + Pair.create(MacAddress.fromString(matchingScanResult.BSSID), + MacAddress.BROADCAST_ADDRESS); + attachWifiNetworkSpecifierAndAppInfo( + ssidPatternMatch, bssidPatternMatch, + WifiConfigurationTestUtil.createPskNetwork(), TEST_UID_1, TEST_PACKAGE_NAME_1); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + // Verify we did not trigger the UI for the second request. + verify(mContext, never()).startActivityAsUser(any(), any()); + // Verify we did not trigger a scan. + verify(mWifiScanner, never()).startScan(any(), any(), any(), any()); + // Verify we did not trigger the match callback. + verify(mNetworkRequestMatchCallback, never()).onMatch(anyList()); + // Verify that we sent a connection attempt to ClientModeImpl + verify(mClientModeImpl).connect(eq(null), anyInt(), + any(Binder.class), mConnectListenerArgumentCaptor.capture(), anyInt(), anyInt()); + + verify(mWifiMetrics).incrementNetworkRequestApiNumUserApprovalBypass(); + } + + /** + * Verify the user approval bypass for a specific request for an access point that was already * approved previously and the scan result is present in the cached scan results, but the * results are stale. */ |