Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6fb00bb2 authored by Soonil Nagarkar's avatar Soonil Nagarkar
Browse files

Remove permission info from CallerIdentity

Location permission info is 1) location specific and 2) may change over
time, and thus shouldn't be in CallerIdentity. This CL moves the
information (temporarily) into LocationRequest. Follow up CLs will
include deeper refactors that eliminate the need for this information to
be in LocationRequest as well.

Test: manual + presubmits
Change-Id: I3b4b41941e386f644efce2effe6985b9c865586c
parent c35fb3f4
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -150,6 +150,8 @@ public final class LocationRequest implements Parcelable {

    @UnsupportedAppUsage
    private String mProvider;
    // if true, client requests coarse location, if false, client requests fine location
    private boolean mCoarseLocation;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    private int mQuality;
    @UnsupportedAppUsage
@@ -255,6 +257,7 @@ public final class LocationRequest implements Parcelable {
    public LocationRequest() {
        this(
                /* provider= */ LocationManager.FUSED_PROVIDER,
                /* coarseLocation= */ false,
                /* quality= */ POWER_LOW,
                /* interval= */ DEFAULT_INTERVAL_MS,
                /* fastestInterval= */ (long) (DEFAULT_INTERVAL_MS / FASTEST_INTERVAL_FACTOR),
@@ -273,6 +276,7 @@ public final class LocationRequest implements Parcelable {
    public LocationRequest(LocationRequest src) {
        this(
                src.mProvider,
                src.mCoarseLocation,
                src.mQuality,
                src.mInterval,
                src.mFastestInterval,
@@ -289,6 +293,7 @@ public final class LocationRequest implements Parcelable {

    private LocationRequest(
            @NonNull String provider,
            boolean coarseLocation,
            int quality,
            long intervalMs,
            long fastestIntervalMs,
@@ -305,6 +310,7 @@ public final class LocationRequest implements Parcelable {
        checkQuality(quality);

        mProvider = provider;
        mCoarseLocation = coarseLocation;
        mQuality = quality;
        mInterval = intervalMs;
        mFastestInterval = fastestIntervalMs;
@@ -320,6 +326,20 @@ public final class LocationRequest implements Parcelable {
        mWorkSource = workSource;
    }

    /**
     * @hide
     */
    public boolean isCoarse() {
        return mCoarseLocation;
    }

    /**
     * @hide
     */
    public void setCoarse(boolean coarse) {
        mCoarseLocation = coarse;
    }

    /**
     * Set the quality of the request.
     *
@@ -700,6 +720,7 @@ public final class LocationRequest implements Parcelable {
                public LocationRequest createFromParcel(Parcel in) {
                    return new LocationRequest(
                            /* provider= */ in.readString(),
                            /* coarseLocation= */ in.readBoolean(),
                            /* quality= */ in.readInt(),
                            /* interval= */ in.readLong(),
                            /* fastestInterval= */ in.readLong(),
@@ -728,6 +749,7 @@ public final class LocationRequest implements Parcelable {
    @Override
    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeString(mProvider);
        parcel.writeBoolean(mCoarseLocation);
        parcel.writeInt(mQuality);
        parcel.writeLong(mInterval);
        parcel.writeLong(mFastestInterval);
+61 −143
Original line number Diff line number Diff line
@@ -16,24 +16,16 @@

package android.location.util.identity;

import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;

import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.os.Binder;
import android.os.Process;
import android.os.UserHandle;
import android.os.WorkSource;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;

/**
@@ -43,60 +35,21 @@ import java.util.Objects;
 */
public final class CallerIdentity {

    public static final int PERMISSION_NONE = 0;
    public static final int PERMISSION_COARSE = 1;
    public static final int PERMISSION_FINE = 2;

    @IntDef({PERMISSION_NONE, PERMISSION_COARSE, PERMISSION_FINE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface PermissionLevel {}

    /**
     * Converts the given permission level to the corresponding permission.
     */
    public static String asPermission(@PermissionLevel int permissionLevel) {
        switch (permissionLevel) {
            case PERMISSION_COARSE:
                return ACCESS_COARSE_LOCATION;
            case PERMISSION_FINE:
                return ACCESS_FINE_LOCATION;
            default:
                throw new IllegalArgumentException();
        }
    }

    /**
     * Converts the given permission level to the corresponding appop.
     */
    public static int asAppOp(@PermissionLevel int permissionLevel) {
        switch (permissionLevel) {
            case PERMISSION_COARSE:
                return AppOpsManager.OP_COARSE_LOCATION;
            case PERMISSION_FINE:
                return AppOpsManager.OP_FINE_LOCATION;
            default:
                throw new IllegalArgumentException();
        }
    }

    /**
     * Construct a CallerIdentity for test purposes.
     */
    @VisibleForTesting
    public static CallerIdentity forTest(int uid, int pid, String packageName,
            @Nullable String attributionTag, @PermissionLevel int permissionLevel) {

        return new CallerIdentity(uid, pid, packageName, attributionTag, null,
                permissionLevel);
            @Nullable String attributionTag) {
        return new CallerIdentity(uid, pid, packageName, attributionTag, null);
    }

    /**
     * Creates a CallerIdentity for the current process and context.
     */
    public static CallerIdentity fromContext(Context context) {
        return new CallerIdentity(Process.myUid(), Process.myPid(),
                context.getPackageName(), context.getAttributionTag(), null,
                getPermissionLevel(context, Binder.getCallingPid(), Binder.getCallingUid()));
        return new CallerIdentity(Process.myUid(), Process.myPid(), context.getPackageName(),
                context.getAttributionTag(), null);
    }

    /**
@@ -121,7 +74,7 @@ public final class CallerIdentity {
            throw new SecurityException("invalid package \"" + packageName + "\" for uid " + uid);
        }

        return fromBinderUnsafe(context, packageName, attributionTag, listenerId);
        return fromBinderUnsafe(packageName, attributionTag, listenerId);
    }

    /**
@@ -130,9 +83,9 @@ public final class CallerIdentity {
     * this method should only be used if the package will be validated by some other means, such as
     * an appops call.
     */
    public static CallerIdentity fromBinderUnsafe(Context context, String packageName,
    public static CallerIdentity fromBinderUnsafe(String packageName,
            @Nullable String attributionTag) {
        return fromBinderUnsafe(context, packageName, attributionTag, null);
        return fromBinderUnsafe(packageName, attributionTag, null);
    }

    /**
@@ -141,124 +94,89 @@ public final class CallerIdentity {
     * calling uid - this method should only be used if the package will be validated by some other
     * means, such as an appops call.
     */
    public static CallerIdentity fromBinderUnsafe(Context context, String packageName,
    public static CallerIdentity fromBinderUnsafe(String packageName,
            @Nullable String attributionTag, @Nullable String listenerId) {
        return new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(),
                packageName, attributionTag, listenerId,
                getPermissionLevel(context, Binder.getCallingPid(), Binder.getCallingUid()));
    }

    /**
     * Throws a security exception if the caller does not hold a location permission.
     */
    public static void enforceCallingOrSelfLocationPermission(Context context,
            @PermissionLevel int desiredPermissionLevel) {
        enforceLocationPermission(Binder.getCallingUid(),
                getPermissionLevel(context, Binder.getCallingPid(), Binder.getCallingUid()),
                desiredPermissionLevel);
                packageName, attributionTag, listenerId);
    }

    /**
     * Returns false if the caller does not hold a location permission, true otherwise.
     */
    public static boolean checkCallingOrSelfLocationPermission(Context context,
            @PermissionLevel int desiredPermissionLevel) {
        return checkLocationPermission(
                getPermissionLevel(context, Binder.getCallingPid(), Binder.getCallingUid()),
                desiredPermissionLevel);
    }
    private final int mUid;

    private static void enforceLocationPermission(int uid, @PermissionLevel int permissionLevel,
            @PermissionLevel int desiredPermissionLevel) {
        if (checkLocationPermission(permissionLevel, desiredPermissionLevel)) {
            return;
        }
    private final int mPid;

        if (desiredPermissionLevel == PERMISSION_COARSE) {
            throw new SecurityException("uid " + uid + " does not have " + ACCESS_COARSE_LOCATION
                    + " or " + ACCESS_FINE_LOCATION + ".");
        } else if (desiredPermissionLevel == PERMISSION_FINE) {
            throw new SecurityException("uid " + uid + " does not have " + ACCESS_FINE_LOCATION
                    + ".");
        }
    }
    private final String mPackageName;

    private static boolean checkLocationPermission(@PermissionLevel int permissionLevel,
            @PermissionLevel int desiredPermissionLevel) {
        return permissionLevel >= desiredPermissionLevel;
    }
    private final @Nullable String mAttributionTag;

    private static @PermissionLevel int getPermissionLevel(Context context, int pid, int uid) {
        if (context.checkPermission(ACCESS_FINE_LOCATION, pid, uid) == PERMISSION_GRANTED) {
            return PERMISSION_FINE;
        }
        if (context.checkPermission(ACCESS_COARSE_LOCATION, pid, uid) == PERMISSION_GRANTED) {
            return PERMISSION_COARSE;
        }
    private final @Nullable String mListenerId;

        return PERMISSION_NONE;
    private CallerIdentity(int uid, int pid, String packageName,
            @Nullable String attributionTag, @Nullable String listenerId) {
        this.mUid = uid;
        this.mPid = pid;
        this.mPackageName = Objects.requireNonNull(packageName);
        this.mAttributionTag = attributionTag;
        this.mListenerId = listenerId;
    }

    /** The calling UID. */
    public final int uid;
    public int getUid() {
        return mUid;
    }

    /** The calling PID. */
    public final int pid;
    public int getPid() {
        return mPid;
    }

    /** The calling user. */
    public final int userId;
    public int getUserId() {
        return UserHandle.getUserId(mUid);
    }

    /** The calling package name. */
    public final String packageName;
    public String getPackageName() {
        return mPackageName;
    }

    /** The calling attribution tag. */
    public final @Nullable String attributionTag;
    public String getAttributionTag() {
        return mAttributionTag;
    }

    /** The calling listener id. */
    public final @Nullable String listenerId;

    /**
     * The calling location permission level. This field should only be used for validating
     * permissions for API access. It should not be used for validating permissions for location
     * access - that must be done through appops.
     */
    public final @PermissionLevel int permissionLevel;

    private CallerIdentity(int uid, int pid, String packageName,
            @Nullable String attributionTag, @Nullable String listenerId,
            @PermissionLevel int permissionLevel) {
        this.uid = uid;
        this.pid = pid;
        this.userId = UserHandle.getUserId(uid);
        this.packageName = Objects.requireNonNull(packageName);
        this.attributionTag = attributionTag;
        this.listenerId = listenerId;
        this.permissionLevel = Preconditions.checkArgumentInRange(permissionLevel, PERMISSION_NONE,
                PERMISSION_FINE, "permissionLevel");
    public String getListenerId() {
        return mListenerId;
    }

    /**
     * Throws a security exception if the CallerIdentity does not hold a location permission.
     * Adds this identity to the worksource supplied, or if not worksource is supplied, creates a
     * new worksource representing this identity.
     */
    public void enforceLocationPermission(@PermissionLevel int desiredPermissionLevel) {
        enforceLocationPermission(uid, permissionLevel, desiredPermissionLevel);
    public WorkSource addToWorkSource(@Nullable WorkSource workSource) {
        if (workSource == null) {
            return new WorkSource(mUid, mPackageName);
        } else {
            workSource.add(mUid, mPackageName);
            return workSource;
        }
    }

    @Override
    public String toString() {
        int length = 10 + packageName.length();
        if (attributionTag != null) {
            length += attributionTag.length();
        int length = 10 + mPackageName.length();
        if (mAttributionTag != null) {
            length += mAttributionTag.length();
        }

        StringBuilder builder = new StringBuilder(length);
        builder.append(pid).append("/").append(packageName);
        if (attributionTag != null) {
        builder.append(mPid).append("/").append(mPackageName);
        if (mAttributionTag != null) {
            builder.append("[");
            if (attributionTag.startsWith(packageName)) {
                builder.append(attributionTag.substring(packageName.length()));
            if (mAttributionTag.startsWith(mPackageName)) {
                builder.append(mAttributionTag.substring(mPackageName.length()));
            } else {
                builder.append(attributionTag);
                builder.append(mAttributionTag);
            }
            builder.append("]");
        }
@@ -274,14 +192,14 @@ public final class CallerIdentity {
            return false;
        }
        CallerIdentity that = (CallerIdentity) o;
        return uid == that.uid
                && pid == that.pid
                && packageName.equals(that.packageName)
                && Objects.equals(attributionTag, that.attributionTag);
        return getUid() == that.getUid()
                && mPid == that.mPid
                && mPackageName.equals(that.mPackageName)
                && Objects.equals(mAttributionTag, that.mAttributionTag);
    }

    @Override
    public int hashCode() {
        return Objects.hash(uid, pid, packageName, attributionTag);
        return Objects.hash(mUid, mPid, mPackageName, mAttributionTag);
    }
}
+28 −24
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
import com.android.server.location.LocationPermissions.PermissionLevel;

import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -112,13 +113,15 @@ public class AppOpsHelper {

    /**
     * Checks if the given identity may have locations delivered without noting that a location is
     * being delivered. This is a looser guarantee than {@link #noteLocationAccess(CallerIdentity)},
     * and this function does not validate package arguments and so should not be used with
     * unvalidated arguments or before actually delivering locations.
     * being delivered. This is a looser guarantee than
     * {@link #noteLocationAccess(CallerIdentity, int)}, and this function does not validate package
     * arguments and so should not be used with unvalidated arguments or before actually delivering
     * locations.
     *
     * @see AppOpsManager#checkOpNoThrow(int, int, String)
     */
    public boolean checkLocationAccess(CallerIdentity callerIdentity) {
    public boolean checkLocationAccess(CallerIdentity callerIdentity,
            @PermissionLevel int permissionLevel) {
        synchronized (this) {
            Preconditions.checkState(mAppOps != null);
        }
@@ -126,9 +129,9 @@ public class AppOpsHelper {
        long identity = Binder.clearCallingIdentity();
        try {
            return mAppOps.checkOpNoThrow(
                    CallerIdentity.asAppOp(callerIdentity.permissionLevel),
                    callerIdentity.uid,
                    callerIdentity.packageName) == AppOpsManager.MODE_ALLOWED;
                    LocationPermissions.asAppOp(permissionLevel),
                    callerIdentity.getUid(),
                    callerIdentity.getPackageName()) == AppOpsManager.MODE_ALLOWED;
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
@@ -139,8 +142,9 @@ public class AppOpsHelper {
     * called right before a location is delivered, and if it returns false, the location should not
     * be delivered.
     */
    public boolean noteLocationAccess(CallerIdentity identity) {
        return noteOpNoThrow(CallerIdentity.asAppOp(identity.permissionLevel), identity);
    public boolean noteLocationAccess(CallerIdentity identity,
            @PermissionLevel int permissionLevel) {
        return noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel), identity);
    }

    /**
@@ -189,10 +193,10 @@ public class AppOpsHelper {
            // note that this is not the no throw version of noteOp, this call may throw exceptions
            return mAppOps.noteOp(
                    AppOpsManager.OP_MOCK_LOCATION,
                    callerIdentity.uid,
                    callerIdentity.packageName,
                    callerIdentity.attributionTag,
                    callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED;
                    callerIdentity.getUid(),
                    callerIdentity.getPackageName(),
                    callerIdentity.getAttributionTag(),
                    callerIdentity.getListenerId()) == AppOpsManager.MODE_ALLOWED;
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
@@ -207,11 +211,11 @@ public class AppOpsHelper {
        try {
            return mAppOps.startOpNoThrow(
                    appOp,
                    callerIdentity.uid,
                    callerIdentity.packageName,
                    callerIdentity.getUid(),
                    callerIdentity.getPackageName(),
                    false,
                    callerIdentity.attributionTag,
                    callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED;
                    callerIdentity.getAttributionTag(),
                    callerIdentity.getListenerId()) == AppOpsManager.MODE_ALLOWED;
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
@@ -226,9 +230,9 @@ public class AppOpsHelper {
        try {
            mAppOps.finishOp(
                    appOp,
                    callerIdentity.uid,
                    callerIdentity.packageName,
                    callerIdentity.attributionTag);
                    callerIdentity.getUid(),
                    callerIdentity.getPackageName(),
                    callerIdentity.getAttributionTag());
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
@@ -243,10 +247,10 @@ public class AppOpsHelper {
        try {
            return mAppOps.noteOpNoThrow(
                    appOp,
                    callerIdentity.uid,
                    callerIdentity.packageName,
                    callerIdentity.attributionTag,
                    callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED;
                    callerIdentity.getUid(),
                    callerIdentity.getPackageName(),
                    callerIdentity.getAttributionTag(),
                    callerIdentity.getListenerId()) == AppOpsManager.MODE_ALLOWED;
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
+97 −70

File changed.

Preview size limit exceeded, changes collapsed.

+176 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.location;

import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;

import android.annotation.IntDef;
import android.app.AppOpsManager;
import android.content.Context;
import android.os.Binder;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/** Utility class for dealing with location permissions. */
public final class LocationPermissions {

    /**
     * Indicates no location permissions are present, or no location permission are required.
     */
    public static final int PERMISSION_NONE = 0;

    /**
     * Indicates the coarse location permission is present, or either the coarse or fine permissions
     * are required.
     */
    public static final int PERMISSION_COARSE = 1;

    /**
     * Indicates the fine location permission is present, or the fine location permission is
     * required.
     */
    public static final int PERMISSION_FINE = 2;

    @IntDef({PERMISSION_NONE, PERMISSION_COARSE, PERMISSION_FINE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface PermissionLevel {}

    /**
     * Converts the given permission level to the corresponding permission.
     */
    public static String asPermission(@PermissionLevel int permissionLevel) {
        switch (permissionLevel) {
            case PERMISSION_COARSE:
                return ACCESS_COARSE_LOCATION;
            case PERMISSION_FINE:
                return ACCESS_FINE_LOCATION;
            default:
                throw new IllegalArgumentException();
        }
    }

    /**
     * Converts the given permission level to the corresponding appop.
     */
    public static int asAppOp(@PermissionLevel int permissionLevel) {
        switch (permissionLevel) {
            case PERMISSION_COARSE:
                return AppOpsManager.OP_COARSE_LOCATION;
            case PERMISSION_FINE:
                return AppOpsManager.OP_FINE_LOCATION;
            default:
                throw new IllegalArgumentException();
        }
    }

    /**
     * Throws a security exception if the caller does not hold the required location permissions.
     */
    public static void enforceCallingOrSelfLocationPermission(Context context,
            @PermissionLevel int requiredPermissionLevel) {
        enforceLocationPermission(Binder.getCallingUid(),
                getPermissionLevel(context, Binder.getCallingUid(), Binder.getCallingPid()),
                requiredPermissionLevel);
    }

    /**
     * Throws a security exception if the given uid/pid does not hold the required location
     * permissions.
     */
    public static void enforceLocationPermission(Context context, int uid, int pid,
            @PermissionLevel int requiredPermissionLevel) {
        enforceLocationPermission(uid,
                getPermissionLevel(context, uid, pid),
                requiredPermissionLevel);
    }

    /**
     * Throws a security exception if the given permission level does not meet the required location
     * permission level.
     */
    public static void enforceLocationPermission(int uid, @PermissionLevel int permissionLevel,
            @PermissionLevel int requiredPermissionLevel) {
        if (checkLocationPermission(permissionLevel, requiredPermissionLevel)) {
            return;
        }

        if (requiredPermissionLevel == PERMISSION_COARSE) {
            throw new SecurityException("uid " + uid + " does not have " + ACCESS_COARSE_LOCATION
                    + " or " + ACCESS_FINE_LOCATION + ".");
        } else if (requiredPermissionLevel == PERMISSION_FINE) {
            throw new SecurityException("uid " + uid + " does not have " + ACCESS_FINE_LOCATION
                    + ".");
        }
    }

    /**
     * Returns false if the caller does not hold the required location permissions.
     */
    public static boolean checkCallingOrSelfLocationPermission(Context context,
            @PermissionLevel int requiredPermissionLevel) {
        return checkLocationPermission(
                getCallingOrSelfPermissionLevel(context),
                requiredPermissionLevel);
    }

    /**
     * Returns false if the given uid/pid does not hold the required location permissions.
     */
    public static boolean checkLocationPermission(Context context, int uid, int pid,
            @PermissionLevel int requiredPermissionLevel) {
        return checkLocationPermission(
                getPermissionLevel(context, uid, pid),
                requiredPermissionLevel);
    }

    /**
     * Returns false if the given permission level does not meet the required location permission
     * level.
     */
    public static boolean checkLocationPermission(@PermissionLevel int permissionLevel,
            @PermissionLevel int requiredPermissionLevel) {
        return permissionLevel >= requiredPermissionLevel;
    }

    /**
     * Returns the permission level of the caller.
     */
    @PermissionLevel
    public static int getCallingOrSelfPermissionLevel(Context context) {
        return getPermissionLevel(context, Binder.getCallingUid(), Binder.getCallingPid());
    }

    /**
     * Returns the permission level of the given uid/pid.
     */
    @PermissionLevel
    public static int getPermissionLevel(Context context, int uid, int pid) {
        if (context.checkPermission(ACCESS_FINE_LOCATION, pid, uid) == PERMISSION_GRANTED) {
            return PERMISSION_FINE;
        }
        if (context.checkPermission(ACCESS_COARSE_LOCATION, pid, uid) == PERMISSION_GRANTED) {
            return PERMISSION_COARSE;
        }

        return PERMISSION_NONE;
    }

    private LocationPermissions() {}
}
Loading