• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.server.companion.utils;
18 
19 import static android.Manifest.permission.BLUETOOTH_CONNECT;
20 import static android.Manifest.permission.BLUETOOTH_SCAN;
21 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
22 import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
23 import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
24 import static android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE;
25 import static android.app.AppOpsManager.MODE_ALLOWED;
26 import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
27 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
28 import static android.companion.AssociationRequest.DEVICE_PROFILE_COMPUTER;
29 import static android.companion.AssociationRequest.DEVICE_PROFILE_GLASSES;
30 import static android.companion.AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING;
31 import static android.companion.AssociationRequest.DEVICE_PROFILE_VIRTUAL_DEVICE;
32 import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
33 import static android.companion.AssociationRequest.DEVICE_PROFILE_WEARABLE_SENSING;
34 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
35 import static android.os.Binder.getCallingPid;
36 import static android.os.Binder.getCallingUid;
37 import static android.os.Process.SYSTEM_UID;
38 import static android.os.UserHandle.getCallingUserId;
39 
40 import static com.android.server.companion.utils.RolesUtils.isRoleHolder;
41 
42 import static java.util.Collections.unmodifiableMap;
43 import static java.util.Collections.unmodifiableSet;
44 
45 import android.Manifest;
46 import android.annotation.NonNull;
47 import android.annotation.Nullable;
48 import android.annotation.UserIdInt;
49 import android.companion.AssociationRequest;
50 import android.companion.CompanionDeviceManager;
51 import android.content.Context;
52 import android.os.Binder;
53 import android.os.RemoteException;
54 import android.os.ServiceManager;
55 import android.util.ArrayMap;
56 import android.util.ArraySet;
57 
58 import com.android.internal.app.IAppOpsService;
59 
60 import java.util.Map;
61 import java.util.Set;
62 
63 /**
64  * Utility methods for checking permissions required for accessing {@link CompanionDeviceManager}
65  * APIs (such as {@link Manifest.permission#REQUEST_COMPANION_PROFILE_WATCH},
66  * {@link Manifest.permission#REQUEST_COMPANION_PROFILE_APP_STREAMING},
67  * {@link Manifest.permission#REQUEST_COMPANION_SELF_MANAGED} etc.)
68  */
69 public final class PermissionsUtils {
70 
71     private static final Set<String> SYSTEM_ONLY_DEVICE_PROFILES;
72     static {
73         final Set<String> set = new ArraySet<>();
74         set.add(DEVICE_PROFILE_WEARABLE_SENSING);
75         SYSTEM_ONLY_DEVICE_PROFILES = unmodifiableSet(set);
76     }
77 
78     private static final Map<String, String> DEVICE_PROFILE_TO_PERMISSION;
79     static {
80         final Map<String, String> map = new ArrayMap<>();
map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH)81         map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH);
map.put(DEVICE_PROFILE_APP_STREAMING, Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING)82         map.put(DEVICE_PROFILE_APP_STREAMING,
83                 Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING);
map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION)84         map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION,
85                 Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION);
map.put(DEVICE_PROFILE_COMPUTER, Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER)86         map.put(DEVICE_PROFILE_COMPUTER, Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER);
map.put(DEVICE_PROFILE_GLASSES, Manifest.permission.REQUEST_COMPANION_PROFILE_GLASSES)87         map.put(DEVICE_PROFILE_GLASSES, Manifest.permission.REQUEST_COMPANION_PROFILE_GLASSES);
map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, Manifest.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING)88         map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
89                 Manifest.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING);
map.put(DEVICE_PROFILE_VIRTUAL_DEVICE, Manifest.permission.REQUEST_COMPANION_PROFILE_VIRTUAL_DEVICE)90         map.put(DEVICE_PROFILE_VIRTUAL_DEVICE,
91                 Manifest.permission.REQUEST_COMPANION_PROFILE_VIRTUAL_DEVICE);
92 
93         DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
94     }
95 
96     /**
97      * Require the app to declare necessary permission for creating association.
98      */
enforcePermissionForCreatingAssociation(@onNull Context context, @NonNull AssociationRequest request, int packageUid)99     public static void enforcePermissionForCreatingAssociation(@NonNull Context context,
100             @NonNull AssociationRequest request, int packageUid) {
101         enforcePermissionForRequestingProfile(context, request.getDeviceProfile(), packageUid);
102 
103         if (request.isSelfManaged() || request.getDeviceIcon() != null) {
104             enforcePermissionForRequestingSelfManaged(context, packageUid);
105         }
106     }
107 
108     /**
109      * Require the app to declare necessary permission for creating association with profile.
110      */
enforcePermissionForRequestingProfile( @onNull Context context, @Nullable String deviceProfile, int packageUid)111     public static void enforcePermissionForRequestingProfile(
112             @NonNull Context context, @Nullable String deviceProfile, int packageUid) {
113         // Device profile can be null.
114         if (deviceProfile == null) return;
115 
116         if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)
117                 && !SYSTEM_ONLY_DEVICE_PROFILES.contains(deviceProfile)) {
118             throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile);
119         }
120 
121         if (SYSTEM_ONLY_DEVICE_PROFILES.contains(deviceProfile) && getCallingUid() != SYSTEM_UID) {
122             throw new SecurityException("Caller must be system to associate with a device with "
123                     + deviceProfile + " profile.");
124         }
125 
126         final String permission = DEVICE_PROFILE_TO_PERMISSION.getOrDefault(deviceProfile, null);
127         if (permission != null && context.checkPermission(permission, getCallingPid(), packageUid)
128                 != PERMISSION_GRANTED) {
129             throw new SecurityException("Application must hold " + permission + " to associate "
130                     + "with a device with " + deviceProfile + " profile.");
131         }
132     }
133 
134     /**
135      * Require the app to declare necessary permission for creating self-managed association.
136      */
enforcePermissionForRequestingSelfManaged(@onNull Context context, int packageUid)137     public static void enforcePermissionForRequestingSelfManaged(@NonNull Context context,
138             int packageUid) {
139         if (context.checkPermission(REQUEST_COMPANION_SELF_MANAGED, getCallingPid(), packageUid)
140                 != PERMISSION_GRANTED) {
141             throw new SecurityException("Application does not hold "
142                     + REQUEST_COMPANION_SELF_MANAGED);
143         }
144     }
145 
146     /**
147      * Check if the caller can interact with the user.
148      */
checkCallerCanInteractWithUserId(@onNull Context context, int userId)149     public static boolean checkCallerCanInteractWithUserId(@NonNull Context context, int userId) {
150         if (getCallingUserId() == userId) return true;
151 
152         return context.checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED;
153     }
154 
155     /**
156      * Require the caller to be able to interact with the user.
157      */
enforceCallerCanInteractWithUserId(@onNull Context context, int userId)158     public static void enforceCallerCanInteractWithUserId(@NonNull Context context, int userId) {
159         if (getCallingUserId() == userId) return;
160 
161         context.enforceCallingPermission(INTERACT_ACROSS_USERS, null);
162     }
163 
164     /**
165      * Require the caller to be system UID or to be able to interact with the user.
166      */
enforceCallerIsSystemOrCanInteractWithUserId(@onNull Context context, int userId)167     public static void enforceCallerIsSystemOrCanInteractWithUserId(@NonNull Context context,
168             int userId) {
169         if (getCallingUid() == SYSTEM_UID) return;
170 
171         enforceCallerCanInteractWithUserId(context, userId);
172     }
173 
174     /**
175      * Check if the calling user id matches the userId, and if the package belongs to
176      * the calling uid.
177      */
enforceCallerIsSystemOr(@serIdInt int userId, @NonNull String packageName)178     public static void enforceCallerIsSystemOr(@UserIdInt int userId, @NonNull String packageName) {
179         final int callingUid = getCallingUid();
180         if (callingUid == SYSTEM_UID) return;
181 
182         final int callingUserId = getCallingUserId();
183         if (getCallingUserId() != userId) {
184             throw new SecurityException("Calling UserId (" + callingUserId + ") does not match "
185                     + "the expected UserId (" + userId + ")");
186         }
187 
188         if (!checkPackage(callingUid, packageName)) {
189             throw new SecurityException(packageName + " doesn't belong to calling uid ("
190                     + callingUid + ")");
191         }
192     }
193 
194     /**
195      * Require the caller to be able to manage the associations for the package.
196      */
enforceCallerCanManageAssociationsForPackage(@onNull Context context, @UserIdInt int userId, @NonNull String packageName, @Nullable String actionDescription)197     public static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context,
198             @UserIdInt int userId, @NonNull String packageName,
199             @Nullable String actionDescription) {
200         final int callingUid = getCallingUid();
201 
202         // If the caller is the system
203         if (callingUid == SYSTEM_UID) {
204             return;
205         }
206 
207         // If caller can manage the package or has the permissions to manage companion devices
208         boolean canInteractAcrossUsers = context.checkCallingPermission(INTERACT_ACROSS_USERS)
209                 == PERMISSION_GRANTED;
210         boolean canManageCompanionDevices = context.checkCallingPermission(MANAGE_COMPANION_DEVICES)
211                 == PERMISSION_GRANTED;
212         if (getCallingUserId() == userId) {
213             if (checkPackage(callingUid, packageName) || canManageCompanionDevices) {
214                 return;
215             }
216         } else if (canInteractAcrossUsers && canManageCompanionDevices) {
217             return;
218         }
219 
220         throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
221                 + "permissions to "
222                 + (actionDescription != null ? actionDescription : "manage associations")
223                 + " for u" + userId + "/" + packageName);
224     }
225 
226     /**
227      * Require the caller to hold necessary permission to observe device presence by UUID.
228      */
enforceCallerCanObserveDevicePresenceByUuid(@onNull Context context, String packageName, int userId)229     public static void enforceCallerCanObserveDevicePresenceByUuid(@NonNull Context context,
230             String packageName, int userId) {
231         if (!hasRequirePermissions(context, packageName, userId)) {
232             throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
233                     + "permissions to request observing device presence base on the UUID");
234         }
235     }
236 
checkPackage(@serIdInt int uid, @NonNull String packageName)237     private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) {
238         try {
239             return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED;
240         } catch (RemoteException e) {
241             // Can't happen: AppOpsManager is running in the same process.
242             return true;
243         }
244     }
245 
getAppOpsService()246     private static IAppOpsService getAppOpsService() {
247         if (sAppOpsService == null) {
248             synchronized (PermissionsUtils.class) {
249                 if (sAppOpsService == null) {
250                     sAppOpsService = IAppOpsService.Stub.asInterface(
251                             ServiceManager.getService(Context.APP_OPS_SERVICE));
252                 }
253             }
254         }
255         return sAppOpsService;
256     }
257 
hasRequirePermissions( @onNull Context context, String packageName, int userId)258     private static boolean hasRequirePermissions(
259             @NonNull Context context, String packageName, int userId) {
260         return context.checkCallingPermission(
261                 REQUEST_OBSERVE_DEVICE_UUID_PRESENCE) == PERMISSION_GRANTED
262                 && context.checkCallingPermission(BLUETOOTH_SCAN) == PERMISSION_GRANTED
263                 && context.checkCallingPermission(BLUETOOTH_CONNECT) == PERMISSION_GRANTED
264                 && Boolean.TRUE.equals(Binder.withCleanCallingIdentity(
265                         () -> isRoleHolder(context, userId, packageName,
266                                 DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)));
267     }
268 
269     // DO NOT USE DIRECTLY! Access via getAppOpsService().
270     private static IAppOpsService sAppOpsService = null;
271 
PermissionsUtils()272     private PermissionsUtils() {}
273 }
274