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.devicepolicy;
18 
19 import android.os.UserHandle;
20 import android.util.Slog;
21 import android.util.SparseArray;
22 
23 import com.android.internal.util.JournaledFile;
24 
25 import java.io.File;
26 import java.io.IOException;
27 import java.nio.charset.Charset;
28 import java.nio.file.Files;
29 
30 /**
31  * Class for dealing with Device Policy Manager Service version upgrades.
32  * Initially, this class is responsible for upgrading the "device_policies.xml" file upon
33  * platform version upgrade.
34  *
35  * It is useful for policies which have a different default for an upgrading device than a
36  * newly-configured device (for example, the admin can grant sensors-related permissions by
37  * default on existing fully-managed devices that upgrade to Android S, but on devices set up
38  * with Android S the value of the policy is set explicitly during set-up).
39  *
40  * Practically, it's useful for changes to the data model of the {@code DevicePolicyData} and
41  * {@code ActiveAdmin} classes.
42  *
43  * To add a new upgrade step:
44  * (1) Increase the {@code DPMS_VERSION} constant in {@code DevicePolicyManagerService} by one.
45  * (2) Add an if statement in {@code upgradePolicy} comparing the version, performing the upgrade
46  *     step and setting the value of {@code currentVersion} to the newly-incremented version.
47  * (3) Add a test in {@code PolicyVersionUpgraderTest}.
48  */
49 public class PolicyVersionUpgrader {
50     private static final String LOG_TAG = "DevicePolicyManager";
51     private static final boolean VERBOSE_LOG = DevicePolicyManagerService.VERBOSE_LOG;
52     private final PolicyUpgraderDataProvider mProvider;
53 
PolicyVersionUpgrader(PolicyUpgraderDataProvider provider)54     public PolicyVersionUpgrader(PolicyUpgraderDataProvider provider) {
55         mProvider = provider;
56     }
57 
58     /**
59      * Performs the upgrade steps for all users on the system.
60      *
61      * @param allUsers List of all user IDs on the system, including disabled users, as well as
62      *                 managed profile user IDs.
63      * @param dpmsVersion The version to upgrade to.
64      */
upgradePolicy(int dpmsVersion)65     public void upgradePolicy(int dpmsVersion) {
66         int oldVersion = readVersion();
67         if (oldVersion >= dpmsVersion) {
68             Slog.i(LOG_TAG, String.format("Current version %d, latest version %d, not upgrading.",
69                     oldVersion, dpmsVersion));
70             return;
71         }
72 
73         final int[] allUsers = mProvider.getUsersForUpgrade();
74 
75         //NOTE: The current version is provided in case the XML file format changes in a
76         // non-backwards-compatible way, so that DeviceAdminData could load it with
77         // old tags, for example.
78         final SparseArray<DevicePolicyData> allUsersData = loadAllUsersData(allUsers, oldVersion);
79 
80         int currentVersion = oldVersion;
81         if (currentVersion == 0) {
82             Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion));
83             // The first upgrade (from no version to version 1) is to overwrite
84             // the "active-password" tag in case it was left around.
85             currentVersion = 1;
86         }
87 
88         if (currentVersion == 1) {
89             Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion));
90             // This upgrade step is for Device Owner scenario only: For devices upgrading to S,
91             // if there is a device owner, it retains the ability to control sensors-related
92             // permission grants.
93             for (int userId : allUsers) {
94                 DevicePolicyData userData = allUsersData.get(userId);
95                 if (userData == null) {
96                     continue;
97                 }
98                 for (ActiveAdmin admin : userData.mAdminList) {
99                     if (mProvider.isDeviceOwner(userId, admin.info.getComponent())) {
100                         Slog.i(LOG_TAG, String.format(
101                                 "Marking Device Owner in user %d for permission grant ", userId));
102                         admin.mAdminCanGrantSensorsPermissions = true;
103                     }
104                 }
105             }
106             currentVersion = 2;
107         }
108 
109         writePoliciesAndVersion(allUsers, allUsersData, currentVersion);
110     }
111 
writePoliciesAndVersion(int[] allUsers, SparseArray<DevicePolicyData> allUsersData, int currentVersion)112     private void writePoliciesAndVersion(int[] allUsers, SparseArray<DevicePolicyData> allUsersData,
113             int currentVersion) {
114         boolean allWritesSuccessful = true;
115         for (int user : allUsers) {
116             allWritesSuccessful = allWritesSuccessful && writeDataForUser(user,
117                     allUsersData.get(user));
118         }
119 
120         if (allWritesSuccessful) {
121             writeVersion(currentVersion);
122         } else {
123             Slog.e(LOG_TAG, String.format("Error: Failed upgrading policies to version %d",
124                     currentVersion));
125         }
126     }
127 
loadAllUsersData(int[] allUsers, int loadVersion)128     private SparseArray<DevicePolicyData> loadAllUsersData(int[] allUsers, int loadVersion) {
129         final SparseArray<DevicePolicyData> allUsersData = new SparseArray<>();
130         for (int user: allUsers) {
131             allUsersData.append(user, loadDataForUser(user, loadVersion));
132         }
133         return allUsersData;
134     }
135 
loadDataForUser(int userId, int loadVersion)136     private DevicePolicyData loadDataForUser(int userId, int loadVersion) {
137         DevicePolicyData policy = new DevicePolicyData(userId);
138         DevicePolicyData.load(policy,
139                 !mProvider.storageManagerIsFileBasedEncryptionEnabled(),
140                 mProvider.makeDevicePoliciesJournaledFile(userId),
141                 mProvider.getAdminInfoSupplier(userId),
142                 mProvider.getOwnerComponent(userId));
143         return policy;
144     }
145 
writeDataForUser(int userId, DevicePolicyData policy)146     private boolean writeDataForUser(int userId, DevicePolicyData policy) {
147         return DevicePolicyData.store(
148                 policy,
149                 mProvider.makeDevicePoliciesJournaledFile(userId),
150                 !mProvider.storageManagerIsFileBasedEncryptionEnabled());
151     }
152 
getVersionFile()153     private JournaledFile getVersionFile() {
154         return mProvider.makePoliciesVersionJournaledFile(UserHandle.USER_SYSTEM);
155     }
156 
readVersion()157     private int readVersion() {
158         JournaledFile versionFile = getVersionFile();
159 
160         File file = versionFile.chooseForRead();
161         if (VERBOSE_LOG) {
162             Slog.v(LOG_TAG, "Loading version from " + file);
163         }
164         try {
165             String versionString = Files.readAllLines(
166                     file.toPath(), Charset.defaultCharset()).get(0);
167             return Integer.parseInt(versionString);
168         } catch (IOException | NumberFormatException | IndexOutOfBoundsException e) {
169             Slog.e(LOG_TAG, "Error reading version", e);
170             return 0;
171         }
172     }
173 
writeVersion(int version)174     private void writeVersion(int version) {
175         JournaledFile versionFile = getVersionFile();
176 
177         File file = versionFile.chooseForWrite();
178         if (VERBOSE_LOG) {
179             Slog.v(LOG_TAG, String.format("Writing new version to: %s", file));
180         }
181 
182         try {
183             byte[] versionBytes = String.format("%d", version).getBytes();
184             Files.write(file.toPath(), versionBytes);
185             versionFile.commit();
186         } catch (IOException e) {
187             Slog.e(LOG_TAG, String.format("Writing version %d failed: %s", version), e);
188             versionFile.rollback();
189         }
190     }
191 }
192