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