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

Commit 4e10f847 authored by Kholoud Mohamed's avatar Kholoud Mohamed
Browse files

Add boiler plate for the policy engine

Also added policy definitions for setAutoTimeZone and
setPermissionGrantState

Bug: 232918480
Test: manual
Change-Id: I6e2a7a3ff1bda3866142892ec2169ce37cc4f156
parent 9caeec5d
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -3914,6 +3914,23 @@ public class DevicePolicyManager {
        throw new SecurityException("not implemented");
    }
    // TODO: Expose this as SystemAPI once we add the query API
    /**
     * @hide
     */
    public static final String AUTO_TIMEZONE_POLICY = "autoTimezone";
    // TODO: Expose this as SystemAPI once we add the query API
    /**
     * @hide
     */
    public static String PERMISSION_GRANT_POLICY(
            @NonNull String packageName, @NonNull String permission) {
        Objects.requireNonNull(packageName);
        Objects.requireNonNull(permission);
        return "permissionGrant_" + packageName + "_" + permission;
    }
    /**
     * This object is a single place to tack on invalidation and disable calls.  All
     * binder caches in this class derive from this Config, so all can be invalidated or
+1 −0
Original line number Diff line number Diff line
@@ -22,5 +22,6 @@ java_library_static {
    libs: [
        "services.core",
        "app-compat-annotations",
        "service-permission.stubs.system_server",
    ],
}
+248 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.devicepolicy;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.UserHandle;
import android.util.SparseArray;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * Class responsible for setting, resolving, and enforcing policies set by multiple management
 * admins on the device.
 */
final class DevicePolicyEngine {
    private static final String TAG = "DevicePolicyEngine";

    private final Context mContext;
    private final Object mLock = new Object();

    /**
     * Map of <userId, Map<policyKey, policyState>>
     */
    private final SparseArray<Map<String, PolicyState<?>>> mUserPolicies;

    /**
     * Map of <policyKey, policyState>
     */
    private final Map<String, PolicyState<?>> mGlobalPolicies;

    DevicePolicyEngine(@NonNull Context context) {
        mContext = Objects.requireNonNull(context);
        mUserPolicies = new SparseArray<>();
        mGlobalPolicies = new HashMap<>();
    }

    // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
    /**
     * Set the policy for the provided {@code policyDefinition}
     * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}.
     * Returns {@code true} if the enforced policy has been changed.
     *
     */
    <V> boolean setLocalPolicy(
            @NonNull PolicyDefinition<V> policyDefinition,
            @NonNull EnforcingAdmin enforcingAdmin,
            @NonNull V value,
            int userId) {

        Objects.requireNonNull(policyDefinition);
        Objects.requireNonNull(enforcingAdmin);
        Objects.requireNonNull(value);

        synchronized (mLock) {
            PolicyState<V> policyState = getLocalPolicyStateLocked(policyDefinition, userId);

            boolean policyChanged = policyState.setPolicy(enforcingAdmin, value);

            if (policyChanged) {
                enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
            }
            return policyChanged;
        }
    }

    // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
    /**
     * Set the policy for the provided {@code policyDefinition}
     * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}.
     * Returns {@code true} if the enforced policy has been changed.
     *
     */
    <V> boolean setGlobalPolicy(
            @NonNull PolicyDefinition<V> policyDefinition,
            @NonNull EnforcingAdmin enforcingAdmin,
            @NonNull V value) {

        Objects.requireNonNull(policyDefinition);
        Objects.requireNonNull(enforcingAdmin);
        Objects.requireNonNull(value);

        synchronized (mLock) {
            PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);

            boolean policyChanged = policyState.setPolicy(enforcingAdmin, value);

            if (policyChanged) {
                enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
                        UserHandle.USER_ALL);
            }
            return policyChanged;
        }
    }


    // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
    /**
     * Removes any previously set policy for the provided {@code policyDefinition}
     * (see {@link PolicyDefinition}) and {@code enforcingAdmin}.
     * Returns {@code true} if the enforced policy has been changed.
     *
     */
    <V> boolean removeLocalPolicy(
            @NonNull PolicyDefinition<V> policyDefinition,
            @NonNull EnforcingAdmin enforcingAdmin,
            int userId) {

        Objects.requireNonNull(policyDefinition);
        Objects.requireNonNull(enforcingAdmin);

        synchronized (mLock) {
            PolicyState<V> policyState = getLocalPolicyStateLocked(policyDefinition, userId);
            boolean policyChanged = policyState.removePolicy(enforcingAdmin);

            if (policyChanged) {
                enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
            }
            return policyChanged;
        }
    }

    // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
    /**
     * Removes any previously set policy for the provided {@code policyDefinition}
     * (see {@link PolicyDefinition}) and {@code enforcingAdmin}.
     * Returns {@code true} if the enforced policy has been changed.
     *
     */
    <V> boolean removeGlobalPolicy(
            @NonNull PolicyDefinition<V> policyDefinition,
            @NonNull EnforcingAdmin enforcingAdmin) {

        Objects.requireNonNull(policyDefinition);
        Objects.requireNonNull(enforcingAdmin);

        synchronized (mLock) {
            PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
            boolean policyChanged = policyState.removePolicy(enforcingAdmin);

            if (policyChanged) {
                enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
                        UserHandle.USER_ALL);
            }
            return policyChanged;
        }
    }

    /**
     * Retrieves policies set by all admins for the provided {@code policyDefinition}.
     *
     */
    <V> PolicyState<V> getLocalPolicy(@NonNull PolicyDefinition<V> policyDefinition, int userId) {
        Objects.requireNonNull(policyDefinition);

        synchronized (mLock) {
            return getLocalPolicyStateLocked(policyDefinition, userId);
        }
    }

    /**
     * Retrieves policies set by all admins for the provided {@code policyDefinition}.
     *
     */
    <V> PolicyState<V> getGlobalPolicy(@NonNull PolicyDefinition<V> policyDefinition) {
        Objects.requireNonNull(policyDefinition);

        synchronized (mLock) {
            return getGlobalPolicyStateLocked(policyDefinition);
        }
    }

    @NonNull
    private <V> PolicyState<V> getLocalPolicyStateLocked(
            PolicyDefinition<V> policyDefinition, int userId) {

        if (policyDefinition.isGlobalOnlyPolicy()) {
            throw new IllegalArgumentException("Can't set global policy "
                    + policyDefinition.getKey() + " locally.");
        }

        if (!mUserPolicies.contains(userId)) {
            mUserPolicies.put(userId, new HashMap<>());
        }
        if (!mUserPolicies.get(userId).containsKey(policyDefinition.getKey())) {
            mUserPolicies.get(userId).put(
                    policyDefinition.getKey(), new PolicyState<>(policyDefinition));
        }
        return getPolicyState(mUserPolicies.get(userId), policyDefinition);
    }

    @NonNull
    private <V> PolicyState<V> getGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) {

        if (policyDefinition.isLocalOnlyPolicy()) {
            throw new IllegalArgumentException("Can't set local policy "
                    + policyDefinition.getKey() + " globally.");
        }

        if (!mGlobalPolicies.containsKey(policyDefinition.getKey())) {
            mGlobalPolicies.put(
                    policyDefinition.getKey(), new PolicyState<>(policyDefinition));
        }
        return getPolicyState(mGlobalPolicies, policyDefinition);
    }

    private static <V> PolicyState<V> getPolicyState(
            Map<String, PolicyState<?>> policies, PolicyDefinition<V> policyDefinition) {
        try {
            // This will not throw an exception because policyDefinition is of type V, so unless
            // we've created two policies with the same key but different types - we can only have
            // stored a PolicyState of the right type.
            PolicyState<V> policyState = (PolicyState<V>) policies.get(
                    policyDefinition.getKey());
            return policyState;
        } catch (ClassCastException exception) {
            // TODO: handle exception properly
            throw new IllegalArgumentException();
        }
    }

    private <V> void enforcePolicy(
            PolicyDefinition<V> policyDefinition, @Nullable V policyValue, int userId) {
        // TODO: null policyValue means remove any enforced policies, ensure callbacks handle this
        //  properly
        policyDefinition.enforcePolicy(policyValue, mContext, userId);
        // TODO: send broadcast or call callback to notify admins of policy change
        // TODO: notify calling admin of result (e.g. success, runtime failure, policy set by
        //  a different admin)
    }
}
+136 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.devicepolicy;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;

import com.android.role.RoleManagerLocal;
import com.android.server.LocalManagerRegistry;

import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

final class EnforcingAdmin {
    static final String ROLE_AUTHORITY_PREFIX = "role:";
    static final String DPC_AUTHORITY = "enterprise";
    static final String DEVICE_ADMIN_AUTHORITY = "device_admin";
    static final String DEFAULT_AUTHORITY = "default";

    private final String mPackageName;
    // This is needed for DPCs and device admins
    @Nullable private final ComponentName mComponentName;
    // TODO: implement lazy loading for authorities
    private final Set<String> mAuthorities;

    static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId) {
        Objects.requireNonNull(packageName);
        return new EnforcingAdmin(packageName, getRoleAuthoritiesOrDefault(packageName, userId));
    }

    static EnforcingAdmin createEnterpriseEnforcingAdmin(ComponentName componentName) {
        Objects.requireNonNull(componentName);
        return new EnforcingAdmin(componentName, Set.of(DPC_AUTHORITY));
    }

    static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName) {
        Objects.requireNonNull(componentName);
        return new EnforcingAdmin(componentName, Set.of(DEVICE_ADMIN_AUTHORITY));
    }

    private EnforcingAdmin(String packageName, Set<String> authorities) {
        mPackageName = packageName;
        mComponentName = null;
        mAuthorities = new HashSet<>(authorities);
    }

    private EnforcingAdmin(ComponentName componentName, Set<String> authorities) {
        mPackageName = componentName.getPackageName();
        mComponentName = componentName;
        mAuthorities = new HashSet<>(authorities);
    }

    private static Set<String> getRoleAuthoritiesOrDefault(String packageName, int userId) {
        Set<String> roles = getRoles(packageName, userId);
        Set<String> authorities = new HashSet<>();
        for (String role : roles) {
            authorities.add(ROLE_AUTHORITY_PREFIX + role);
        }
        return authorities.isEmpty() ? Set.of(DEFAULT_AUTHORITY) : authorities;
    }

    // TODO(b/259042794): move this logic to RoleManagerLocal
    private static Set<String> getRoles(String packageName, int userId) {
        RoleManagerLocal roleManagerLocal = LocalManagerRegistry.getManager(
                RoleManagerLocal.class);
        Set<String> roles = new HashSet<>();
        Map<String, Set<String>> rolesAndHolders = roleManagerLocal.getRolesAndHolders(userId);
        for (String role : rolesAndHolders.keySet()) {
            if (rolesAndHolders.get(role).contains(packageName)) {
                roles.add(role);
            }
        }
        return roles;
    }

    boolean hasAuthority(String authority) {
        return mAuthorities.contains(authority);
    }

    /**
     * For two EnforcingAdmins to be equal they must:
     *
     * <ul>
     *     <li> have the same package names and component names and either
     *     <li> have exactly the same authorities ({@link #DPC_AUTHORITY} or
     *     {@link #DEVICE_ADMIN_AUTHORITY}), or have any role or default authorities.
     * </ul>
     *
     * <p>EnforcingAdmins are considered equal if they have any role authority as they can have
     * roles granted/revoked between calls.
     */
    @Override
    public boolean equals(@Nullable Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        EnforcingAdmin other = (EnforcingAdmin) o;
        return Objects.equals(mPackageName, other.mPackageName)
                && Objects.equals(mComponentName, other.mComponentName)
                && hasMatchingAuthorities(this, other);
    }

    private static boolean hasMatchingAuthorities(EnforcingAdmin admin1, EnforcingAdmin admin2) {
        if (admin1.mAuthorities.equals(admin2.mAuthorities)) {
            return true;
        }
        return !admin1.hasAuthority(DPC_AUTHORITY) && !admin1.hasAuthority(DEVICE_ADMIN_AUTHORITY)
                && !admin2.hasAuthority(DPC_AUTHORITY) && !admin2.hasAuthority(
                DEVICE_ADMIN_AUTHORITY);
    }

    @Override
    public int hashCode() {
        if (mAuthorities.contains(DPC_AUTHORITY) || mAuthorities.contains(DEVICE_ADMIN_AUTHORITY)) {
            return Objects.hash(mComponentName, mAuthorities);
        } else {
            return Objects.hash(mPackageName);
        }
    }
}
+39 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.devicepolicy;

import android.annotation.NonNull;

import java.util.LinkedHashMap;
import java.util.Objects;

final class IntegerUnion extends ResolutionMechanism<Integer> {

    @Override
    Integer resolve(@NonNull LinkedHashMap<EnforcingAdmin, Integer> adminPolicies) {
        Objects.requireNonNull(adminPolicies);
        if (adminPolicies.isEmpty()) {
            return null;
        }

        Integer unionOfPolicies = 0;
        for (Integer policy : adminPolicies.values()) {
            unionOfPolicies |= policy;
        }
        return unionOfPolicies;
    }
}
Loading