WifiPermissionsUtil.java revision 1982ffd9f5a9434623e1e6229df9d23c506e7491
1/* 2 * Copyright (C) 2016 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 17package com.android.server.wifi.util; 18 19import android.Manifest; 20import android.annotation.Nullable; 21import android.app.AppOpsManager; 22import android.content.ComponentName; 23import android.content.Context; 24import android.content.pm.PackageManager; 25import android.content.pm.UserInfo; 26import android.net.ConnectivityManager; 27import android.net.NetworkScoreManager; 28import android.net.NetworkScorerAppData; 29import android.net.wifi.WifiConfiguration; 30import android.os.Binder; 31import android.os.RemoteException; 32import android.os.UserManager; 33import android.provider.Settings; 34import android.text.TextUtils; 35 36import com.android.server.wifi.FrameworkFacade; 37import com.android.server.wifi.WifiInjector; 38import com.android.server.wifi.WifiLog; 39import com.android.server.wifi.WifiSettingsStore; 40 41import java.util.List; 42 43/** 44 * A wifi permissions utility assessing permissions 45 * for getting scan results by a package. 46 */ 47public class WifiPermissionsUtil { 48 private static final String TAG = "WifiPermissionsUtil"; 49 private final WifiPermissionsWrapper mWifiPermissionsWrapper; 50 private final Context mContext; 51 private final AppOpsManager mAppOps; 52 private final UserManager mUserManager; 53 private final WifiSettingsStore mSettingsStore; 54 private final NetworkScoreManager mNetworkScoreManager; 55 private final FrameworkFacade mFrameworkFacade; 56 private WifiLog mLog; 57 58 public WifiPermissionsUtil(WifiPermissionsWrapper wifiPermissionsWrapper, 59 Context context, WifiSettingsStore settingsStore, UserManager userManager, 60 NetworkScoreManager networkScoreManager, WifiInjector wifiInjector) { 61 mWifiPermissionsWrapper = wifiPermissionsWrapper; 62 mContext = context; 63 mUserManager = userManager; 64 mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); 65 mSettingsStore = settingsStore; 66 mLog = wifiInjector.makeLog(TAG); 67 mNetworkScoreManager = networkScoreManager; 68 mFrameworkFacade = wifiInjector.getFrameworkFacade(); 69 } 70 71 /** 72 * Checks if the app has the permission to override Wi-Fi network configuration or not. 73 * 74 * @param uid uid of the app. 75 * @return true if the app does have the permission, false otherwise. 76 */ 77 public boolean checkConfigOverridePermission(int uid) { 78 try { 79 int permission = mWifiPermissionsWrapper.getOverrideWifiConfigPermission(uid); 80 return (permission == PackageManager.PERMISSION_GRANTED); 81 } catch (RemoteException e) { 82 mLog.err("Error checking for permission: %").r(e.getMessage()).flush(); 83 return false; 84 } 85 } 86 87 /** 88 * Checks if the app has the permission to change Wi-Fi network configuration or not. 89 * 90 * @param uid uid of the app. 91 * @return true if the app does have the permission, false otherwise. 92 */ 93 public boolean checkChangePermission(int uid) { 94 try { 95 int permission = mWifiPermissionsWrapper.getChangeWifiConfigPermission(uid); 96 return (permission == PackageManager.PERMISSION_GRANTED); 97 } catch (RemoteException e) { 98 mLog.err("Error checking for permission: %").r(e.getMessage()).flush(); 99 return false; 100 } 101 } 102 103 /** 104 * Check and enforce tether change permission. 105 * 106 * @param context Context object of the caller. 107 */ 108 public void enforceTetherChangePermission(Context context) { 109 String pkgName = context.getOpPackageName(); 110 ConnectivityManager.enforceTetherChangePermission(context, pkgName); 111 } 112 113 /** 114 * Check and enforce Location permission. 115 * 116 * @param pkgName PackageName of the application requesting access 117 * @param uid The uid of the package 118 */ 119 public void enforceLocationPermission(String pkgName, int uid) { 120 if (!checkCallersLocationPermission(pkgName, uid)) { 121 throw new SecurityException("UID " + uid + " does not have Location permission"); 122 } 123 } 124 125 126 /** 127 * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION 128 * and a corresponding app op is allowed for this package and uid. 129 * 130 * @param pkgName PackageName of the application requesting access 131 * @param uid The uid of the package 132 */ 133 public boolean checkCallersLocationPermission(String pkgName, int uid) { 134 // Coarse Permission implies Fine permission 135 if ((mWifiPermissionsWrapper.getUidPermission( 136 Manifest.permission.ACCESS_COARSE_LOCATION, uid) 137 == PackageManager.PERMISSION_GRANTED) 138 && checkAppOpAllowed(AppOpsManager.OP_COARSE_LOCATION, pkgName, uid)) { 139 return true; 140 } 141 return false; 142 } 143 144 /** 145 * API to determine if the caller has permissions to get 146 * scan results. 147 * @param pkgName package name of the application requesting access 148 * @param uid The uid of the package 149 * @param minVersion Minimum app API Version number to enforce location permission 150 * @return boolean true or false if permissions is granted 151 */ 152 public boolean canAccessScanResults(String pkgName, int uid, 153 int minVersion) throws SecurityException { 154 mAppOps.checkPackage(uid, pkgName); 155 // Check if the calling Uid has CAN_READ_PEER_MAC_ADDRESS 156 // permission or is an Active Nw scorer. 157 boolean canCallingUidAccessLocation = checkCallerHasPeersMacAddressPermission(uid) 158 || isCallerActiveNwScorer(uid); 159 // LocationAccess by App: For AppVersion older than minVersion, 160 // it is sufficient to check if the App is foreground. 161 // Otherwise, Location Mode must be enabled and caller must have 162 // Coarse Location permission to have access to location information. 163 boolean canAppPackageUseLocation = isLegacyForeground(pkgName, minVersion) 164 || (isLocationModeEnabled(pkgName) 165 && checkCallersLocationPermission(pkgName, uid)); 166 // If neither caller or app has location access, there is no need to check 167 // any other permissions. Deny access to scan results. 168 if (!canCallingUidAccessLocation && !canAppPackageUseLocation) { 169 mLog.tC("Denied: no location permission"); 170 return false; 171 } 172 // Check if Wifi Scan request is an operation allowed for this App. 173 if (!isScanAllowedbyApps(pkgName, uid)) { 174 mLog.tC("Denied: app wifi scan not allowed"); 175 return false; 176 } 177 // If the User or profile is current, permission is granted 178 // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. 179 if (!canAccessUserProfile(uid)) { 180 mLog.tC("Denied: Profile not permitted"); 181 return false; 182 } 183 return true; 184 } 185 186 /** 187 * API to determine if the caller has permissions to get a {@link android.net.wifi.WifiInfo} 188 * instance containing the SSID and BSSID. 189 * 190 * 191 * @param currentConfig the currently connected WiFi config 192 * @param pkgName package name of the application requesting access 193 * @param uid The uid of the package 194 * @param minVersion Minimum app API Version number to enforce location permission 195 * @return boolean true if the SSID/BSSID can be sent to the user, false if they 196 * should be hidden/removed. 197 */ 198 public boolean canAccessFullConnectionInfo(@Nullable WifiConfiguration currentConfig, 199 String pkgName, int uid, int minVersion) throws SecurityException { 200 mAppOps.checkPackage(uid, pkgName); 201 202 // The User or profile must be current or the uid must 203 // have INTERACT_ACROSS_USERS_FULL permission. 204 if (!canAccessUserProfile(uid)) { 205 mLog.tC("Denied: Profile not permitted"); 206 return false; 207 } 208 209 // If the caller has scan result access then they can also see the full connection info. 210 // Otherwise the caller must be the active use open wifi package and the current config 211 // must be for an open network. 212 return canAccessScanResults(pkgName, uid, minVersion) 213 || isUseOpenWifiPackageWithConnectionInfoAccess(currentConfig, pkgName); 214 215 } 216 217 /** 218 * Returns true if the given WiFi config is for an open network and the package is the active 219 * use open wifi app. 220 */ 221 private boolean isUseOpenWifiPackageWithConnectionInfoAccess( 222 @Nullable WifiConfiguration currentConfig, String pkgName) { 223 224 // Access is only granted for open networks. 225 if (currentConfig == null) { 226 mLog.tC("Denied: WifiConfiguration is NULL."); 227 return false; 228 } 229 230 // Access is only granted for open networks. 231 if (!currentConfig.isOpenNetwork()) { 232 mLog.tC("Denied: The current config is not for an open network."); 233 return false; 234 } 235 236 // The USE_OPEN_WIFI_PACKAGE can access the full connection info details without 237 // scan result access. 238 if (!isUseOpenWifiPackage(pkgName)) { 239 mLog.tC("Denied: caller is not the current USE_OPEN_WIFI_PACKAGE"); 240 return false; 241 } 242 243 return true; 244 } 245 246 /** 247 * Returns true if the User or profile is current or the 248 * uid has the INTERACT_ACROSS_USERS_FULL permission. 249 */ 250 private boolean canAccessUserProfile(int uid) { 251 if (!isCurrentProfile(uid) && !checkInteractAcrossUsersFull(uid)) { 252 return false; 253 } 254 return true; 255 } 256 257 /** 258 * Returns true if the caller holds PEERS_MAC_ADDRESS permission. 259 */ 260 private boolean checkCallerHasPeersMacAddressPermission(int uid) { 261 return mWifiPermissionsWrapper.getUidPermission( 262 android.Manifest.permission.PEERS_MAC_ADDRESS, uid) 263 == PackageManager.PERMISSION_GRANTED; 264 } 265 266 /** 267 * Returns true if the caller is an Active Network Scorer. 268 */ 269 private boolean isCallerActiveNwScorer(int uid) { 270 return mNetworkScoreManager.isCallerActiveScorer(uid); 271 } 272 273 /** 274 * Returns true if the given package is equal to the setting keyed by 275 * {@link Settings.Global#USE_OPEN_WIFI_PACKAGE} and the NetworkScoreManager 276 * has the package name set as the use open wifi package. 277 */ 278 private boolean isUseOpenWifiPackage(String packageName) { 279 if (TextUtils.isEmpty(packageName)) { 280 return false; 281 } 282 283 // When the setting is enabled it's set to the package name of the use open wifi app. 284 final String useOpenWifiPkg = 285 mFrameworkFacade.getStringSetting(mContext, Settings.Global.USE_OPEN_WIFI_PACKAGE); 286 if (packageName.equals(useOpenWifiPkg)) { 287 // If the package name matches the setting then also confirm that the scorer is 288 // active and the package matches the expected use open wifi package from the scorer's 289 // perspective. The scorer can be active when the use open wifi feature is off so we 290 // can't rely on this check alone. 291 // TODO(b/67278755): Refactor this into an API similar to isCallerActiveScorer() 292 final NetworkScorerAppData appData; 293 final long token = Binder.clearCallingIdentity(); 294 try { 295 appData = mNetworkScoreManager.getActiveScorer(); 296 } finally { 297 Binder.restoreCallingIdentity(token); 298 } 299 if (appData != null) { 300 final ComponentName enableUseOpenWifiActivity = 301 appData.getEnableUseOpenWifiActivity(); 302 return enableUseOpenWifiActivity != null 303 && packageName.equals(enableUseOpenWifiActivity.getPackageName()); 304 } 305 } 306 307 return false; 308 } 309 310 /** 311 * Returns true if Wifi scan operation is allowed for this caller 312 * and package. 313 */ 314 private boolean isScanAllowedbyApps(String pkgName, int uid) { 315 return checkAppOpAllowed(AppOpsManager.OP_WIFI_SCAN, pkgName, uid); 316 } 317 318 /** 319 * Returns true if the caller holds INTERACT_ACROSS_USERS_FULL. 320 */ 321 private boolean checkInteractAcrossUsersFull(int uid) { 322 return mWifiPermissionsWrapper.getUidPermission( 323 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, uid) 324 == PackageManager.PERMISSION_GRANTED; 325 } 326 327 /** 328 * Returns true if the calling user is the current one or a profile of the 329 * current user. 330 */ 331 private boolean isCurrentProfile(int uid) { 332 final long token = Binder.clearCallingIdentity(); 333 try { 334 int currentUser = mWifiPermissionsWrapper.getCurrentUser(); 335 int callingUserId = mWifiPermissionsWrapper.getCallingUserId(uid); 336 if (callingUserId == currentUser) { 337 return true; 338 } else { 339 List<UserInfo> userProfiles = mUserManager.getProfiles(currentUser); 340 for (UserInfo user : userProfiles) { 341 if (user.id == callingUserId) { 342 return true; 343 } 344 } 345 } 346 return false; 347 } finally { 348 Binder.restoreCallingIdentity(token); 349 } 350 } 351 352 /** 353 * Returns true if the App version is older than minVersion. 354 */ 355 private boolean isLegacyVersion(String pkgName, int minVersion) { 356 try { 357 if (mContext.getPackageManager().getApplicationInfo(pkgName, 0) 358 .targetSdkVersion < minVersion) { 359 return true; 360 } 361 } catch (PackageManager.NameNotFoundException e) { 362 // In case of exception, assume known app (more strict checking) 363 // Note: This case will never happen since checkPackage is 364 // called to verify valididity before checking App's version. 365 } 366 return false; 367 } 368 369 private boolean checkAppOpAllowed(int op, String pkgName, int uid) { 370 return mAppOps.noteOp(op, uid, pkgName) == AppOpsManager.MODE_ALLOWED; 371 } 372 373 private boolean isLegacyForeground(String pkgName, int version) { 374 return isLegacyVersion(pkgName, version) && isForegroundApp(pkgName); 375 } 376 377 private boolean isForegroundApp(String pkgName) { 378 return pkgName.equals(mWifiPermissionsWrapper.getTopPkgName()); 379 } 380 381 private boolean isLocationModeEnabled(String pkgName) { 382 // Location mode check on applications that are later than version. 383 return (mSettingsStore.getLocationModeSetting(mContext) 384 != Settings.Secure.LOCATION_MODE_OFF); 385 } 386 387 /** 388 * Returns true if the |uid| holds NETWORK_SETTINGS permission. 389 */ 390 public boolean checkNetworkSettingsPermission(int uid) { 391 return mWifiPermissionsWrapper.getUidPermission( 392 android.Manifest.permission.NETWORK_SETTINGS, uid) 393 == PackageManager.PERMISSION_GRANTED; 394 } 395} 396