1 /* 2 * Copyright (C) 2021 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.tv.settings.connectivity; 18 19 import android.app.admin.DevicePolicyManager; 20 import android.content.ComponentName; 21 import android.content.ContentResolver; 22 import android.content.Context; 23 import android.content.pm.PackageManager; 24 import android.net.wifi.WifiConfiguration; 25 import android.net.wifi.WifiConfiguration.AuthAlgorithm; 26 import android.net.wifi.WifiConfiguration.KeyMgmt; 27 import android.net.wifi.WifiInfo; 28 import android.net.wifi.WifiManager; 29 import android.os.UserHandle; 30 import android.os.UserManager; 31 import android.provider.Settings; 32 import android.text.TextUtils; 33 import android.util.Log; 34 35 import com.android.tv.settings.library.network.AccessPoint; 36 import com.android.tv.settings.R; 37 import com.android.tv.settings.connectivity.util.WifiSecurityUtil; 38 import com.android.wifitrackerlib.WifiEntry; 39 40 import java.util.List; 41 import java.util.regex.Matcher; 42 import java.util.regex.Pattern; 43 44 /** 45 * Helper class that deals with Wi-fi configuration. 46 */ 47 public final class WifiConfigHelper { 48 49 private static final String TAG = "WifiConfigHelper"; 50 private static final boolean DEBUG = false; 51 52 // Allows underscore char to supports proxies that do not 53 // follow the spec 54 private static final String HC = "a-zA-Z0-9\\_"; 55 56 // Matches blank input, ips, and domain names 57 private static final String HOSTNAME_REGEXP = 58 "^$|^[" + HC + "]+(\\-[" + HC + "]+)*(\\.[" + HC + "]+(\\-[" + HC + "]+)*)*$"; 59 private static final Pattern HOSTNAME_PATTERN; 60 private static final String EXCLUSION_REGEXP = 61 "$|^(\\*)?\\.?[" + HC + "]+(\\-[" + HC + "]+)*(\\.[" + HC + "]+(\\-[" + HC + "]+)*)*$"; 62 private static final Pattern EXCLUSION_PATTERN; 63 64 private static final String BYPASS_PROXY_EXCLUDE_REGEX = 65 "[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*(\\.[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*)*"; 66 private static final String BYPASS_PROXY_EXCLUDE_LIST_REGEXP = "^$|^" 67 + BYPASS_PROXY_EXCLUDE_REGEX + "(," + BYPASS_PROXY_EXCLUDE_REGEX + ")*$"; 68 private static final Pattern BYPASS_PROXY_EXCLUSION_PATTERN; 69 70 static { 71 HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP); 72 EXCLUSION_PATTERN = Pattern.compile(EXCLUSION_REGEXP); 73 BYPASS_PROXY_EXCLUSION_PATTERN = Pattern.compile(BYPASS_PROXY_EXCLUDE_LIST_REGEXP); 74 } 75 WifiConfigHelper()76 private WifiConfigHelper() { 77 } 78 79 /** 80 * Set configuration ssid. 81 * 82 * @param config configuration 83 * @param ssid network ssid 84 */ setConfigSsid(WifiConfiguration config, String ssid)85 public static void setConfigSsid(WifiConfiguration config, String ssid) { 86 config.SSID = AccessPoint.convertToQuotedString(ssid); 87 } 88 89 /** 90 * Set configuration key managment by security. 91 */ setConfigKeyManagementBySecurity( WifiConfiguration config, int security)92 public static void setConfigKeyManagementBySecurity( 93 WifiConfiguration config, int security) { 94 // WifiInfo and WifiConfiguration constants are the same so we don't need to translate 95 // between them. 96 config.setSecurityParams(security); 97 } 98 99 /** 100 * validate syntax of hostname and port entries 101 * 102 * @param hostname host name to be used 103 * @param port port to be used 104 * @param exclList what should be accepted as input 105 * @return 0 on success, string resource ID on failure 106 */ validate(String hostname, String port, String exclList)107 public static int validate(String hostname, String port, String exclList) { 108 return validate(hostname, port, exclList, false); 109 } 110 111 /** 112 * validate syntax of hostname and port entries 113 * 114 * @param hostname host name to be used 115 * @param port port to be used 116 * @param exclList what should be accepted as input 117 * @param forProxyCheck if extra check for bypass proxy should be done 118 * @return 0 on success, string resource ID on failure 119 */ validate(String hostname, String port, String exclList, boolean forProxyCheck)120 public static int validate(String hostname, String port, String exclList, 121 boolean forProxyCheck) { 122 if (DEBUG) { 123 Log.i(TAG, "validate, hostname: " + hostname + ", for proxy=" + forProxyCheck); 124 } 125 Matcher match = HOSTNAME_PATTERN.matcher(hostname); 126 String[] exclListArray = exclList.split(","); 127 128 if (!match.matches()) return R.string.proxy_error_invalid_host; 129 130 for (String excl : exclListArray) { 131 Matcher m; 132 if (forProxyCheck) { 133 m = BYPASS_PROXY_EXCLUSION_PATTERN.matcher(excl); 134 } else { 135 m = EXCLUSION_PATTERN.matcher(excl); 136 } 137 if (!m.matches()) { 138 return R.string.proxy_error_invalid_exclusion_list; 139 } 140 } 141 142 if (hostname.length() > 0 && port.length() == 0) { 143 return R.string.proxy_error_empty_port; 144 } 145 146 if (port.length() > 0) { 147 if (hostname.length() == 0) { 148 return R.string.proxy_error_empty_host_set_port; 149 } 150 int portVal = -1; 151 try { 152 portVal = Integer.parseInt(port); 153 } catch (NumberFormatException ex) { 154 return R.string.proxy_error_invalid_port; 155 } 156 if (portVal <= 0 || portVal > 0xFFFF) { 157 return R.string.proxy_error_invalid_port; 158 } 159 } 160 return 0; 161 } 162 163 /** 164 * Get {@link WifiConfiguration} based upon the {@link WifiManager} and networkId. 165 * 166 * @param networkId the id of the network. 167 * @return the {@link WifiConfiguration} of the specified network. 168 */ getWifiConfiguration(WifiManager wifiManager, int networkId)169 public static WifiConfiguration getWifiConfiguration(WifiManager wifiManager, int networkId) { 170 List<WifiConfiguration> configuredNetworks = wifiManager.getConfiguredNetworks(); 171 if (configuredNetworks != null) { 172 for (WifiConfiguration configuredNetwork : configuredNetworks) { 173 if (configuredNetwork.networkId == networkId) { 174 return configuredNetwork; 175 } 176 } 177 } 178 return null; 179 } 180 181 /** 182 * Return the configured network that matches the ssid/security pair, or create one. 183 */ getConfiguration(String ssid, int security)184 public static WifiConfiguration getConfiguration(String ssid, int security) { 185 WifiConfiguration config = new WifiConfiguration(); 186 setConfigSsid(config, ssid); 187 setConfigKeyManagementBySecurity(config, security); 188 return config; 189 } 190 191 /** 192 * @param context Context of caller 193 * @param config The WiFi config. 194 * @return true if Settings cannot modify the config due to lockDown. 195 */ isNetworkLockedDown(Context context, WifiConfiguration config)196 public static boolean isNetworkLockedDown(Context context, WifiConfiguration config) { 197 if (config == null) { 198 return false; 199 } 200 201 final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); 202 final PackageManager pm = context.getPackageManager(); 203 final UserManager um = context.getSystemService(UserManager.class); 204 205 // Check if device has DPM capability. If it has and dpm is still null, then we 206 // treat this case with suspicion and bail out. 207 if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) { 208 return true; 209 } 210 211 boolean isConfigEligibleForLockdown = false; 212 if (dpm != null) { 213 final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser(); 214 if (deviceOwner != null) { 215 final int deviceOwnerUserId = dpm.getDeviceOwnerUserId(); 216 try { 217 final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(), 218 deviceOwnerUserId); 219 isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid; 220 } catch (PackageManager.NameNotFoundException e) { 221 // don't care 222 } 223 } else if (dpm.isOrganizationOwnedDeviceWithManagedProfile()) { 224 int profileOwnerUserId = getManagedProfileId(um, UserHandle.myUserId()); 225 final ComponentName profileOwner = dpm.getProfileOwnerAsUser(profileOwnerUserId); 226 if (profileOwner != null) { 227 try { 228 final int profileOwnerUid = pm.getPackageUidAsUser( 229 profileOwner.getPackageName(), profileOwnerUserId); 230 isConfigEligibleForLockdown = profileOwnerUid == config.creatorUid; 231 } catch (PackageManager.NameNotFoundException e) { 232 // don't care 233 } 234 } 235 } 236 } 237 if (!isConfigEligibleForLockdown) { 238 return false; 239 } 240 241 final ContentResolver resolver = context.getContentResolver(); 242 final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver, 243 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0; 244 return isLockdownFeatureEnabled; 245 } 246 247 /** 248 * Retrieves the id for the given user's profile. 249 * 250 * @return the profile id or UserHandle.USER_NULL if there is none. 251 */ getManagedProfileId(UserManager um, int parentUserId)252 private static int getManagedProfileId(UserManager um, int parentUserId) { 253 final int[] profileIds = um.getProfileIdsWithDisabled(parentUserId); 254 for (int profileId : profileIds) { 255 if (profileId != parentUserId && um.isManagedProfile(profileId)) { 256 return profileId; 257 } 258 } 259 return UserHandle.USER_NULL; 260 } 261 } 262