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.app.AppOpsManager;
21import android.content.Context;
22import android.content.pm.PackageManager;
23import android.content.pm.UserInfo;
24import android.net.ConnectivityManager;
25import android.net.NetworkScoreManager;
26import android.os.RemoteException;
27import android.os.UserManager;
28import android.provider.Settings;
29
30import com.android.server.wifi.WifiInjector;
31import com.android.server.wifi.WifiLog;
32import com.android.server.wifi.WifiSettingsStore;
33
34import java.util.List;
35
36/**
37 * A wifi permissions utility assessing permissions
38 * for getting scan results by a package.
39 */
40public class WifiPermissionsUtil {
41    private static final String TAG = "WifiPermissionsUtil";
42    private final WifiPermissionsWrapper mWifiPermissionsWrapper;
43    private final Context mContext;
44    private final AppOpsManager mAppOps;
45    private final UserManager mUserManager;
46    private final WifiSettingsStore mSettingsStore;
47    private final NetworkScoreManager mNetworkScoreManager;
48    private WifiLog mLog;
49
50    public WifiPermissionsUtil(WifiPermissionsWrapper wifiPermissionsWrapper,
51            Context context, WifiSettingsStore settingsStore, UserManager userManager,
52            NetworkScoreManager networkScoreManager, WifiInjector wifiInjector) {
53        mWifiPermissionsWrapper = wifiPermissionsWrapper;
54        mContext = context;
55        mUserManager = userManager;
56        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
57        mSettingsStore = settingsStore;
58        mLog = wifiInjector.makeLog(TAG);
59        mNetworkScoreManager = networkScoreManager;
60    }
61
62    /**
63     * Checks if the app has the permission to override Wi-Fi network configuration or not.
64     *
65     * @param uid uid of the app.
66     * @return true if the app does have the permission, false otherwise.
67     */
68    public boolean checkConfigOverridePermission(int uid) {
69        try {
70            int permission = mWifiPermissionsWrapper.getOverrideWifiConfigPermission(uid);
71            return (permission == PackageManager.PERMISSION_GRANTED);
72        } catch (RemoteException e) {
73            mLog.err("Error checking for permission: %").r(e.getMessage()).flush();
74            return false;
75        }
76    }
77
78    /**
79     * Check and enforce tether change permission.
80     *
81     * @param context Context object of the caller.
82     */
83    public void enforceTetherChangePermission(Context context) {
84        ConnectivityManager.enforceTetherChangePermission(context);
85    }
86
87    /**
88     * Check and enforce Location permission.
89     *
90     * @param pkgName PackageName of the application requesting access
91     * @param uid The uid of the package
92     */
93    public void enforceLocationPermission(String pkgName, int uid) {
94        if (!checkCallersLocationPermission(pkgName, uid)) {
95            throw new SecurityException("UID " + uid + " does not have Location permission");
96        }
97    }
98
99    /**
100     * API to determine if the caller has permissions to get
101     * scan results.
102     * @param pkgName Packagename of the application requesting access
103     * @param uid The uid of the package
104     * @param minVersion Minimum app API Version number to enforce location permission
105     * @return boolean true or false if permissions is granted
106     */
107    public boolean canAccessScanResults(String pkgName, int uid,
108                int minVersion) throws SecurityException {
109        mAppOps.checkPackage(uid, pkgName);
110        // Check if the calling Uid has CAN_READ_PEER_MAC_ADDRESS
111        // permission or is an Active Nw scorer.
112        boolean canCallingUidAccessLocation = checkCallerHasPeersMacAddressPermission(uid)
113                || isCallerActiveNwScorer(uid);
114        // LocationAccess by App: For AppVersion older than minVersion,
115        // it is sufficient to check if the App is foreground.
116        // Otherwise, Location Mode must be enabled and caller must have
117        // Coarse Location permission to have access to location information.
118        boolean canAppPackageUseLocation = isLegacyForeground(pkgName, minVersion)
119                || (isLocationModeEnabled(pkgName)
120                        && checkCallersLocationPermission(pkgName, uid));
121        // If neither caller or app has location access, there is no need to check
122        // any other permissions. Deny access to scan results.
123        if (!canCallingUidAccessLocation && !canAppPackageUseLocation) {
124            mLog.tC("Denied: no location permission");
125            return false;
126        }
127        // Check if Wifi Scan request is an operation allowed for this App.
128        if (!isScanAllowedbyApps(pkgName, uid)) {
129            mLog.tC("Denied: app wifi scan not allowed");
130            return false;
131        }
132        // If the User or profile is current, permission is granted
133        // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission.
134        if (!isCurrentProfile(uid) && !checkInteractAcrossUsersFull(uid)) {
135            mLog.tC("Denied: Profile not permitted");
136            return false;
137        }
138        return true;
139    }
140
141    /**
142     * Returns true if the caller holds PEERS_MAC_ADDRESS permission.
143     */
144    private boolean checkCallerHasPeersMacAddressPermission(int uid) {
145        return mWifiPermissionsWrapper.getUidPermission(
146                android.Manifest.permission.PEERS_MAC_ADDRESS, uid)
147                == PackageManager.PERMISSION_GRANTED;
148    }
149
150    /**
151     * Returns true if the caller is an Active Network Scorer.
152     */
153    private boolean isCallerActiveNwScorer(int uid) {
154        return mNetworkScoreManager.isCallerActiveScorer(uid);
155    }
156
157    /**
158     * Returns true if Wifi scan operation is allowed for this caller
159     * and package.
160     */
161    private boolean isScanAllowedbyApps(String pkgName, int uid) {
162        return checkAppOpAllowed(AppOpsManager.OP_WIFI_SCAN, pkgName, uid);
163    }
164
165    /**
166     * Returns true if the caller holds INTERACT_ACROSS_USERS_FULL.
167     */
168    private boolean checkInteractAcrossUsersFull(int uid) {
169        return mWifiPermissionsWrapper.getUidPermission(
170                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, uid)
171                == PackageManager.PERMISSION_GRANTED;
172    }
173
174    /**
175     * Returns true if the calling user is the current one or a profile of the
176     * current user.
177     */
178    private boolean isCurrentProfile(int uid) {
179        int currentUser = mWifiPermissionsWrapper.getCurrentUser();
180        int callingUserId = mWifiPermissionsWrapper.getCallingUserId(uid);
181        if (callingUserId == currentUser) {
182            return true;
183        } else {
184            List<UserInfo> userProfiles = mUserManager.getProfiles(currentUser);
185            for (UserInfo user: userProfiles) {
186                if (user.id == callingUserId) {
187                    return true;
188                }
189            }
190        }
191        return false;
192    }
193
194    /**
195     * Returns true if the App version is older than minVersion.
196     */
197    private boolean isLegacyVersion(String pkgName, int minVersion) {
198        try {
199            if (mContext.getPackageManager().getApplicationInfo(pkgName, 0)
200                    .targetSdkVersion < minVersion) {
201                return true;
202            }
203        } catch (PackageManager.NameNotFoundException e) {
204            // In case of exception, assume known app (more strict checking)
205            // Note: This case will never happen since checkPackage is
206            // called to verify valididity before checking App's version.
207        }
208        return false;
209    }
210
211    private boolean checkAppOpAllowed(int op, String pkgName, int uid) {
212        return mAppOps.noteOp(op, uid, pkgName) == AppOpsManager.MODE_ALLOWED;
213    }
214
215    private boolean isLegacyForeground(String pkgName, int version) {
216        return isLegacyVersion(pkgName, version) && isForegroundApp(pkgName);
217    }
218
219    private boolean isForegroundApp(String pkgName) {
220        return pkgName.equals(mWifiPermissionsWrapper.getTopPkgName());
221    }
222
223    /**
224     * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION
225     * and a corresponding app op is allowed for this package and uid.
226     */
227    private boolean checkCallersLocationPermission(String pkgName, int uid) {
228        // Coarse Permission implies Fine permission
229        if ((mWifiPermissionsWrapper.getUidPermission(
230                Manifest.permission.ACCESS_COARSE_LOCATION, uid)
231                == PackageManager.PERMISSION_GRANTED)
232                && checkAppOpAllowed(AppOpsManager.OP_COARSE_LOCATION, pkgName, uid)) {
233            return true;
234        }
235        return false;
236    }
237    private boolean isLocationModeEnabled(String pkgName) {
238        // Location mode check on applications that are later than version.
239        return (mSettingsStore.getLocationModeSetting(mContext)
240                 != Settings.Secure.LOCATION_MODE_OFF);
241    }
242}
243