PermissionsState.java revision 1b7025f264fd811ab27470867f8e65eeebf092e9
1/*
2 * Copyright (C) 2015 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.pm;
18
19import android.os.UserHandle;
20import android.util.ArrayMap;
21import android.util.ArraySet;
22
23import com.android.internal.util.ArrayUtils;
24
25import java.util.Arrays;
26import java.util.Collections;
27import java.util.Set;
28
29/**
30 * This class encapsulates the permissions for a package or a shared user.
31 * <p>
32 * There are two types of permissions: install (granted at installation)
33 * and runtime (granted at runtime). Install permissions are granted to
34 * all device users while runtime permissions are granted explicitly to
35 * specific users.
36 * </p>
37 * <p>
38 * The permissions are kept on a per device user basis. For example, an
39 * application may have some runtime permissions granted under the device
40 * owner but not granted under the secondary user.
41 * <p>
42 * This class is also responsible for keeping track of the Linux gids per
43 * user for a package or a shared user. The gids are computed as a set of
44 * the gids for all granted permissions' gids on a per user basis.
45 * </p>
46 */
47public final class PermissionsState {
48
49    /** The permission operation succeeded and no gids changed. */
50    public static final int PERMISSION_OPERATION_SUCCESS = 1;
51
52    /** The permission operation succeeded and gids changed. */
53    public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 2;
54
55    /** The permission operation failed. */
56    public static final int PERMISSION_OPERATION_FAILURE = 3;
57
58    public static final int[] USERS_ALL = {UserHandle.USER_ALL};
59
60    public static final int[] USERS_NONE = {};
61
62    private static final int[] NO_GIDS = {};
63
64    private static final int FLAG_INSTALL_PERMISSIONS = 1 << 0;
65    private static final int FLAG_RUNTIME_PERMISSIONS = 1 << 1;
66
67    private ArrayMap<String, PermissionData> mPermissions;
68
69    private int[] mGlobalGids = NO_GIDS;
70
71    public PermissionsState() {
72        /* do nothing */
73    }
74
75    public PermissionsState(PermissionsState prototype) {
76        copyFrom(prototype);
77    }
78
79    /**
80     * Sets the global gids, applicable to all users.
81     *
82     * @param globalGids The global gids.
83     */
84    public void setGlobalGids(int[] globalGids) {
85        if (!ArrayUtils.isEmpty(globalGids)) {
86            mGlobalGids = Arrays.copyOf(globalGids, globalGids.length);
87        }
88    }
89
90    /**
91     * Initialized this instance from another one.
92     *
93     * @param other The other instance.
94     */
95    public void copyFrom(PermissionsState other) {
96        if (mPermissions != null) {
97            if (other.mPermissions == null) {
98                mPermissions = null;
99            } else {
100                mPermissions.clear();
101            }
102        }
103        if (other.mPermissions != null) {
104            if (mPermissions == null) {
105                mPermissions = new ArrayMap<>();
106            }
107            final int permissionCount = other.mPermissions.size();
108            for (int i = 0; i < permissionCount; i++) {
109                String name = other.mPermissions.keyAt(i);
110                PermissionData permissionData = other.mPermissions.valueAt(i);
111                mPermissions.put(name, new PermissionData(permissionData));
112            }
113        }
114
115        mGlobalGids = NO_GIDS;
116        if (other.mGlobalGids != NO_GIDS) {
117            mGlobalGids = Arrays.copyOf(other.mGlobalGids,
118                    other.mGlobalGids.length);
119        }
120    }
121
122    /**
123     * Grant an install permission.
124     *
125     * @param permission The permission to grant.
126     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
127     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
128     *     #PERMISSION_OPERATION_FAILURE}.
129     */
130    public int grantInstallPermission(BasePermission permission) {
131        return grantPermission(permission, UserHandle.USER_ALL);
132    }
133
134    /**
135     * Revoke an install permission.
136     *
137     * @param permission The permission to revoke.
138     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
139     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
140     *     #PERMISSION_OPERATION_FAILURE}.
141     */
142    public int revokeInstallPermission(BasePermission permission) {
143        return revokePermission(permission, UserHandle.USER_ALL);
144    }
145
146    /**
147     * Grant a runtime permission.
148     *
149     * @param permission The permission to grant.
150     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
151     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
152     *     #PERMISSION_OPERATION_FAILURE}.
153     */
154    public int grantRuntimePermission(BasePermission permission, int userId) {
155        if (userId == UserHandle.USER_ALL) {
156            return PERMISSION_OPERATION_FAILURE;
157        }
158        return grantPermission(permission, userId);
159    }
160
161    /**
162     * Revoke a runtime permission for a given device user.
163     *
164     * @param permission The permission to revoke.
165     * @param userId The device user id.
166     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
167     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
168     *     #PERMISSION_OPERATION_FAILURE}.
169     */
170    public int revokeRuntimePermission(BasePermission permission, int userId) {
171        if (userId == UserHandle.USER_ALL) {
172            return PERMISSION_OPERATION_FAILURE;
173        }
174        return revokePermission(permission, userId);
175    }
176
177    /**
178     * Gets whether this state has a given permission, regardless if
179     * it is install time or runtime one.
180     *
181     * @param name The permission name.
182     * @return Whether this state has the permission.
183     */
184    public boolean hasPermission(String name) {
185        return mPermissions != null && mPermissions.get(name) != null;
186    }
187
188    /**
189     * Gets whether this state has a given runtime permission for a
190     * given device user id.
191     *
192     * @param name The permission name.
193     * @param userId The device user id.
194     * @return Whether this state has the permission.
195     */
196    public boolean hasRuntimePermission(String name, int userId) {
197        return !hasInstallPermission(name) && hasPermission(name, userId);
198    }
199
200    /**
201     * Gets whether this state has a given install permission.
202     *
203     * @param name The permission name.
204     * @return Whether this state has the permission.
205     */
206    public boolean hasInstallPermission(String name) {
207        return hasPermission(name, UserHandle.USER_ALL);
208    }
209
210    /**
211     * Revokes a permission for all users regardless if it is an install or
212     * a runtime permission.
213     *
214     * @param permission The permission to revoke.
215     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
216     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
217     *     #PERMISSION_OPERATION_FAILURE}.
218     */
219    public int revokePermission(BasePermission permission) {
220        if (!hasPermission(permission.name)) {
221            return PERMISSION_OPERATION_FAILURE;
222        }
223
224        int result = PERMISSION_OPERATION_SUCCESS;
225
226        PermissionData permissionData = mPermissions.get(permission.name);
227        for (int userId : permissionData.getUserIds()) {
228            if (revokePermission(permission, userId)
229                    == PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
230                result = PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
231                break;
232            }
233        }
234
235        mPermissions.remove(permission.name);
236
237        return result;
238    }
239
240    /**
241     * Gets whether the state has a given permission for the specified
242     * user, regardless if this is an install or a runtime permission.
243     *
244     * @param name The permission name.
245     * @param userId The device user id.
246     * @return Whether the user has the permission.
247     */
248    public boolean hasPermission(String name, int userId) {
249        enforceValidUserId(userId);
250
251        if (mPermissions == null) {
252            return false;
253        }
254
255        PermissionData permissionData = mPermissions.get(name);
256        return permissionData != null && permissionData.hasUserId(userId);
257    }
258
259    /**
260     * Gets all permissions regardless if they are install or runtime.
261     *
262     * @return The permissions or an empty set.
263     */
264    public Set<String> getPermissions() {
265        if (mPermissions != null) {
266            return mPermissions.keySet();
267        }
268
269        return Collections.emptySet();
270    }
271
272    /**
273     * Gets all permissions for a given device user id regardless if they
274     * are install time or runtime permissions.
275     *
276     * @param userId The device user id.
277     * @return The permissions or an empty set.
278     */
279    public Set<String> getPermissions(int userId) {
280        return getPermissionsInternal(FLAG_INSTALL_PERMISSIONS | FLAG_RUNTIME_PERMISSIONS, userId);
281    }
282
283    /**
284     * Gets all runtime permissions.
285     *
286     * @return The permissions or an empty set.
287     */
288    public Set<String> getRuntimePermissions(int userId) {
289        return getPermissionsInternal(FLAG_RUNTIME_PERMISSIONS, userId);
290    }
291
292    /**
293     * Gets all install permissions.
294     *
295     * @return The permissions or an empty set.
296     */
297    public Set<String> getInstallPermissions() {
298        return getPermissionsInternal(FLAG_INSTALL_PERMISSIONS, UserHandle.USER_ALL);
299    }
300
301    /**
302     * Compute the Linux gids for a given device user from the permissions
303     * granted to this user. Note that these are computed to avoid additional
304     * state as they are rarely accessed.
305     *
306     * @param userId The device user id.
307     * @return The gids for the device user.
308     */
309    public int[] computeGids(int userId) {
310        enforceValidUserId(userId);
311
312        int[] gids = mGlobalGids;
313
314        if (mPermissions != null) {
315            final int permissionCount = mPermissions.size();
316            for (int i = 0; i < permissionCount; i++) {
317                String permission = mPermissions.keyAt(i);
318                if (!hasPermission(permission, userId)) {
319                    continue;
320                }
321                PermissionData permissionData = mPermissions.valueAt(i);
322                final int[] permGids = permissionData.computeGids(userId);
323                if (permGids != NO_GIDS) {
324                    gids = appendInts(gids, permGids);
325                }
326            }
327        }
328
329        return gids;
330    }
331
332    /**
333     * Compute the Linux gids for all device users from the permissions
334     * granted to these users.
335     *
336     * @return The gids for all device users.
337     */
338    public int[] computeGids() {
339        int[] gids = mGlobalGids;
340
341        for (int userId : UserManagerService.getInstance().getUserIds()) {
342            final int[] userGids = computeGids(userId);
343            gids = appendInts(gids, userGids);
344        }
345
346        return gids;
347    }
348
349    /**
350     * Resets the internal state of this object.
351     */
352    public void reset() {
353        mGlobalGids = NO_GIDS;
354        mPermissions = null;
355    }
356
357    private Set<String> getPermissionsInternal(int flags, int userId) {
358        enforceValidUserId(userId);
359
360        if (mPermissions == null) {
361            return Collections.emptySet();
362        }
363
364        if (userId == UserHandle.USER_ALL) {
365            flags = FLAG_INSTALL_PERMISSIONS;
366        }
367
368        Set<String> permissions = new ArraySet<>();
369
370        final int permissionCount = mPermissions.size();
371        for (int i = 0; i < permissionCount; i++) {
372            String permission = mPermissions.keyAt(i);
373
374            if ((flags & FLAG_INSTALL_PERMISSIONS) != 0) {
375                if (hasInstallPermission(permission)) {
376                    permissions.add(permission);
377                }
378            }
379            if ((flags & FLAG_RUNTIME_PERMISSIONS) != 0) {
380                if (hasRuntimePermission(permission, userId)) {
381                    permissions.add(permission);
382                }
383            }
384        }
385
386        return  permissions;
387    }
388
389    private int grantPermission(BasePermission permission, int userId) {
390        if (hasPermission(permission.name, userId)) {
391            return PERMISSION_OPERATION_FAILURE;
392        }
393
394        final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
395        final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
396
397        if (mPermissions == null) {
398            mPermissions = new ArrayMap<>();
399        }
400
401        PermissionData permissionData = mPermissions.get(permission.name);
402        if (permissionData == null) {
403            permissionData = new PermissionData(permission);
404            mPermissions.put(permission.name, permissionData);
405        }
406
407        if (!permissionData.addUserId(userId)) {
408            return PERMISSION_OPERATION_FAILURE;
409        }
410
411        if (hasGids) {
412            final int[] newGids = computeGids(userId);
413            if (oldGids.length != newGids.length) {
414                return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
415            }
416        }
417
418        return PERMISSION_OPERATION_SUCCESS;
419    }
420
421    private int revokePermission(BasePermission permission, int userId) {
422        if (!hasPermission(permission.name, userId)) {
423            return PERMISSION_OPERATION_FAILURE;
424        }
425
426        final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
427        final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
428
429        PermissionData permissionData = mPermissions.get(permission.name);
430
431        if (!permissionData.removeUserId(userId)) {
432            return PERMISSION_OPERATION_FAILURE;
433        }
434
435        if (permissionData.getUserIds() == USERS_NONE) {
436            mPermissions.remove(permission.name);
437        }
438
439        if (mPermissions.isEmpty()) {
440            mPermissions = null;
441        }
442
443        if (hasGids) {
444            final int[] newGids = computeGids(userId);
445            if (oldGids.length != newGids.length) {
446                return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
447            }
448        }
449
450        return PERMISSION_OPERATION_SUCCESS;
451    }
452
453    private static int[] appendInts(int[] current, int[] added) {
454        if (current != null && added != null) {
455            for (int guid : added) {
456                current = ArrayUtils.appendInt(current, guid);
457            }
458        }
459        return current;
460    }
461
462    private static void enforceValidUserId(int userId) {
463        if (userId != UserHandle.USER_ALL && userId < 0) {
464            throw new IllegalArgumentException("Invalid userId:" + userId);
465        }
466    }
467
468    private static final class PermissionData {
469        private final BasePermission mPerm;
470        private int[] mUserIds = USERS_NONE;
471
472        public PermissionData(BasePermission perm) {
473            mPerm = perm;
474        }
475
476        public PermissionData(PermissionData other) {
477            this(other.mPerm);
478
479            if (other.mUserIds == USERS_ALL || other.mUserIds == USERS_NONE) {
480                mUserIds = other.mUserIds;
481            } else {
482                mUserIds = Arrays.copyOf(other.mUserIds, other.mUserIds.length);
483            }
484        }
485
486        public int[] computeGids(int userId) {
487            return mPerm.computeGids(userId);
488        }
489
490        public int[] getUserIds() {
491            return mUserIds;
492        }
493
494        public boolean hasUserId(int userId) {
495            if (mUserIds == USERS_ALL) {
496                return true;
497            }
498
499            if (userId != UserHandle.USER_ALL) {
500                return ArrayUtils.contains(mUserIds, userId);
501            }
502
503            return false;
504        }
505
506        public boolean addUserId(int userId) {
507            if (hasUserId(userId)) {
508                return false;
509            }
510
511            if (userId == UserHandle.USER_ALL) {
512                mUserIds = USERS_ALL;
513                return true;
514            }
515
516            mUserIds = ArrayUtils.appendInt(mUserIds, userId);
517
518            return true;
519        }
520
521        public boolean removeUserId(int userId) {
522            if (!hasUserId(userId)) {
523                return false;
524            }
525
526            if (mUserIds == USERS_ALL) {
527                mUserIds = UserManagerService.getInstance().getUserIds();
528            }
529
530            mUserIds = ArrayUtils.removeInt(mUserIds, userId);
531
532            if (mUserIds.length == 0) {
533                mUserIds = USERS_NONE;
534            }
535
536            return true;
537        }
538    }
539}
540