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