Extract OwnersData from Owners.

This change is intended to allow processing owners data during policy
version upgrade. Owners currently has a lot of side effects like
calls to other services, which doesn't allow it to be used as is.

This change extracts "pure" data object from Onwers, which just holds
the values and knows how to serialize/deserialize itself, which can
be used for upgrade.

Bug: 218639412
Test: atest com.android.server.devicepolicy
Change-Id: I7a79cafee4ff4437c55bb21d65adeb6815da2d0b
This commit is contained in:
Pavel Grafov
2022-04-04 12:22:45 +01:00
parent 21469e70d6
commit 6b0d330da0
3 changed files with 741 additions and 646 deletions

View File

@@ -0,0 +1,583 @@
/*
* 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 static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
import android.annotation.Nullable;
import android.app.admin.SystemUpdateInfo;
import android.app.admin.SystemUpdatePolicy;
import android.content.ComponentName;
import android.os.Environment;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
class OwnersData {
private static final String TAG = "DevicePolicyManagerService";
private static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE
// XML storing device owner info, system update policy and pending OTA update information.
private static final String DEVICE_OWNER_XML = "device_owner_2.xml";
private static final String PROFILE_OWNER_XML = "profile_owner.xml";
private static final String TAG_ROOT = "root";
private static final String TAG_DEVICE_OWNER = "device-owner";
private static final String TAG_SYSTEM_UPDATE_POLICY = "system-update-policy";
private static final String TAG_FREEZE_PERIOD_RECORD = "freeze-record";
private static final String TAG_PENDING_OTA_INFO = "pending-ota-info";
private static final String TAG_PROFILE_OWNER = "profile-owner";
// Holds "context" for device-owner, this must not be show up before device-owner.
private static final String TAG_DEVICE_OWNER_CONTEXT = "device-owner-context";
private static final String TAG_DEVICE_OWNER_TYPE = "device-owner-type";
private static final String TAG_DEVICE_OWNER_PROTECTED_PACKAGES =
"device-owner-protected-packages";
private static final String ATTR_NAME = "name";
private static final String ATTR_PACKAGE = "package";
private static final String ATTR_COMPONENT_NAME = "component";
private static final String ATTR_SIZE = "size";
private static final String ATTR_REMOTE_BUGREPORT_URI = "remoteBugreportUri";
private static final String ATTR_REMOTE_BUGREPORT_HASH = "remoteBugreportHash";
private static final String ATTR_USERID = "userId";
private static final String ATTR_FREEZE_RECORD_START = "start";
private static final String ATTR_FREEZE_RECORD_END = "end";
// Legacy attribute, its presence would mean the profile owner associated with it is
// managing a profile on an organization-owned device.
private static final String ATTR_CAN_ACCESS_DEVICE_IDS = "canAccessDeviceIds";
// New attribute for profile owner of organization-owned device.
private static final String ATTR_PROFILE_OWNER_OF_ORG_OWNED_DEVICE =
"isPoOrganizationOwnedDevice";
private static final String ATTR_DEVICE_OWNER_TYPE_VALUE = "value";
// Internal state for the device owner package.
OwnerInfo mDeviceOwner;
int mDeviceOwnerUserId = UserHandle.USER_NULL;
// Device owner type for a managed device.
final ArrayMap<String, Integer> mDeviceOwnerTypes = new ArrayMap<>();
final ArrayMap<String, List<String>> mDeviceOwnerProtectedPackages = new ArrayMap<>();
// Internal state for the profile owner packages.
final ArrayMap<Integer, OwnerInfo> mProfileOwners = new ArrayMap<>();
// Local system update policy controllable by device owner.
SystemUpdatePolicy mSystemUpdatePolicy;
LocalDate mSystemUpdateFreezeStart;
LocalDate mSystemUpdateFreezeEnd;
// Pending OTA info if there is one.
@Nullable
SystemUpdateInfo mSystemUpdateInfo;
private final Injector mInjector;
OwnersData(Injector injector) {
mInjector = injector;
}
void load(int[] allUsers) {
new DeviceOwnerReadWriter().readFromFileLocked();
for (int userId : allUsers) {
new ProfileOwnerReadWriter(userId).readFromFileLocked();
}
OwnerInfo profileOwner = mProfileOwners.get(mDeviceOwnerUserId);
ComponentName admin = profileOwner != null ? profileOwner.admin : null;
if (mDeviceOwner != null && admin != null) {
Slog.w(TAG, String.format("User %d has both DO and PO, which is not supported",
mDeviceOwnerUserId));
}
}
void writeDeviceOwner() {
if (DEBUG) {
Log.d(TAG, "Writing to device owner file");
}
new DeviceOwnerReadWriter().writeToFileLocked();
}
void writeProfileOwner(int userId) {
if (DEBUG) {
Log.d(TAG, "Writing to profile owner file for user " + userId);
}
new ProfileOwnerReadWriter(userId).writeToFileLocked();
}
void dump(IndentingPrintWriter pw) {
boolean needBlank = false;
if (mDeviceOwner != null) {
pw.println("Device Owner: ");
pw.increaseIndent();
mDeviceOwner.dump(pw);
pw.println("User ID: " + mDeviceOwnerUserId);
pw.decreaseIndent();
needBlank = true;
}
if (mSystemUpdatePolicy != null) {
if (needBlank) {
pw.println();
}
pw.println("System Update Policy: " + mSystemUpdatePolicy);
needBlank = true;
}
if (mProfileOwners != null) {
for (Map.Entry<Integer, OwnerInfo> entry : mProfileOwners.entrySet()) {
if (needBlank) {
pw.println();
}
pw.println("Profile Owner (User " + entry.getKey() + "): ");
pw.increaseIndent();
entry.getValue().dump(pw);
pw.decreaseIndent();
needBlank = true;
}
}
if (mSystemUpdateInfo != null) {
if (needBlank) {
pw.println();
}
pw.println("Pending System Update: " + mSystemUpdateInfo);
needBlank = true;
}
if (mSystemUpdateFreezeStart != null || mSystemUpdateFreezeEnd != null) {
if (needBlank) {
pw.println();
}
pw.println("System update freeze record: "
+ getSystemUpdateFreezePeriodRecordAsString());
needBlank = true;
}
}
String getSystemUpdateFreezePeriodRecordAsString() {
StringBuilder freezePeriodRecord = new StringBuilder();
freezePeriodRecord.append("start: ");
if (mSystemUpdateFreezeStart != null) {
freezePeriodRecord.append(mSystemUpdateFreezeStart.toString());
} else {
freezePeriodRecord.append("null");
}
freezePeriodRecord.append("; end: ");
if (mSystemUpdateFreezeEnd != null) {
freezePeriodRecord.append(mSystemUpdateFreezeEnd.toString());
} else {
freezePeriodRecord.append("null");
}
return freezePeriodRecord.toString();
}
@VisibleForTesting
File getDeviceOwnerFile() {
return new File(mInjector.environmentGetDataSystemDirectory(), DEVICE_OWNER_XML);
}
@VisibleForTesting
File getProfileOwnerFile(int userId) {
return new File(mInjector.environmentGetUserSystemDirectory(userId), PROFILE_OWNER_XML);
}
private abstract static class FileReadWriter {
private final File mFile;
protected FileReadWriter(File file) {
mFile = file;
}
abstract boolean shouldWrite();
void writeToFileLocked() {
if (!shouldWrite()) {
if (DEBUG) {
Log.d(TAG, "No need to write to " + mFile);
}
// No contents, remove the file.
if (mFile.exists()) {
if (DEBUG) {
Log.d(TAG, "Deleting existing " + mFile);
}
if (!mFile.delete()) {
Slog.e(TAG, "Failed to remove " + mFile.getPath());
}
}
return;
}
if (DEBUG) {
Log.d(TAG, "Writing to " + mFile);
}
final AtomicFile f = new AtomicFile(mFile);
FileOutputStream outputStream = null;
try {
outputStream = f.startWrite();
final TypedXmlSerializer out = Xml.resolveSerializer(outputStream);
// Root tag
out.startDocument(null, true);
out.startTag(null, TAG_ROOT);
// Actual content
writeInner(out);
// Close root
out.endTag(null, TAG_ROOT);
out.endDocument();
out.flush();
// Commit the content.
f.finishWrite(outputStream);
outputStream = null;
} catch (IOException e) {
Slog.e(TAG, "Exception when writing", e);
if (outputStream != null) {
f.failWrite(outputStream);
}
}
}
void readFromFileLocked() {
if (!mFile.exists()) {
if (DEBUG) {
Log.d(TAG, "" + mFile + " doesn't exist");
}
return;
}
if (DEBUG) {
Log.d(TAG, "Reading from " + mFile);
}
final AtomicFile f = new AtomicFile(mFile);
InputStream input = null;
try {
input = f.openRead();
final TypedXmlPullParser parser = Xml.resolvePullParser(input);
int type;
int depth = 0;
while ((type = parser.next()) != TypedXmlPullParser.END_DOCUMENT) {
switch (type) {
case TypedXmlPullParser.START_TAG:
depth++;
break;
case TypedXmlPullParser.END_TAG:
depth--;
// fallthrough
default:
continue;
}
// Check the root tag
final String tag = parser.getName();
if (depth == 1) {
if (!TAG_ROOT.equals(tag)) {
Slog.e(TAG, "Invalid root tag: " + tag);
return;
}
continue;
}
// readInner() will only see START_TAG at depth >= 2.
if (!readInner(parser, depth, tag)) {
return; // Error
}
}
} catch (XmlPullParserException | IOException e) {
Slog.e(TAG, "Error parsing owners information file", e);
} finally {
IoUtils.closeQuietly(input);
}
}
abstract void writeInner(TypedXmlSerializer out) throws IOException;
abstract boolean readInner(TypedXmlPullParser parser, int depth, String tag);
}
private class DeviceOwnerReadWriter extends FileReadWriter {
protected DeviceOwnerReadWriter() {
super(getDeviceOwnerFile());
}
@Override
boolean shouldWrite() {
return (mDeviceOwner != null) || (mSystemUpdatePolicy != null)
|| (mSystemUpdateInfo != null);
}
@Override
void writeInner(TypedXmlSerializer out) throws IOException {
if (mDeviceOwner != null) {
mDeviceOwner.writeToXml(out, TAG_DEVICE_OWNER);
out.startTag(null, TAG_DEVICE_OWNER_CONTEXT);
out.attributeInt(null, ATTR_USERID, mDeviceOwnerUserId);
out.endTag(null, TAG_DEVICE_OWNER_CONTEXT);
}
if (!mDeviceOwnerTypes.isEmpty()) {
for (ArrayMap.Entry<String, Integer> entry : mDeviceOwnerTypes.entrySet()) {
out.startTag(null, TAG_DEVICE_OWNER_TYPE);
out.attribute(null, ATTR_PACKAGE, entry.getKey());
out.attributeInt(null, ATTR_DEVICE_OWNER_TYPE_VALUE, entry.getValue());
out.endTag(null, TAG_DEVICE_OWNER_TYPE);
}
}
if (!mDeviceOwnerProtectedPackages.isEmpty()) {
for (ArrayMap.Entry<String, List<String>> entry :
mDeviceOwnerProtectedPackages.entrySet()) {
List<String> protectedPackages = entry.getValue();
out.startTag(null, TAG_DEVICE_OWNER_PROTECTED_PACKAGES);
out.attribute(null, ATTR_PACKAGE, entry.getKey());
out.attributeInt(null, ATTR_SIZE, protectedPackages.size());
for (int i = 0, size = protectedPackages.size(); i < size; i++) {
out.attribute(null, ATTR_NAME + i, protectedPackages.get(i));
}
out.endTag(null, TAG_DEVICE_OWNER_PROTECTED_PACKAGES);
}
}
if (mSystemUpdatePolicy != null) {
out.startTag(null, TAG_SYSTEM_UPDATE_POLICY);
mSystemUpdatePolicy.saveToXml(out);
out.endTag(null, TAG_SYSTEM_UPDATE_POLICY);
}
if (mSystemUpdateInfo != null) {
mSystemUpdateInfo.writeToXml(out, TAG_PENDING_OTA_INFO);
}
if (mSystemUpdateFreezeStart != null || mSystemUpdateFreezeEnd != null) {
out.startTag(null, TAG_FREEZE_PERIOD_RECORD);
if (mSystemUpdateFreezeStart != null) {
out.attribute(
null, ATTR_FREEZE_RECORD_START, mSystemUpdateFreezeStart.toString());
}
if (mSystemUpdateFreezeEnd != null) {
out.attribute(null, ATTR_FREEZE_RECORD_END, mSystemUpdateFreezeEnd.toString());
}
out.endTag(null, TAG_FREEZE_PERIOD_RECORD);
}
}
@Override
boolean readInner(TypedXmlPullParser parser, int depth, String tag) {
if (depth > 2) {
return true; // Ignore
}
switch (tag) {
case TAG_DEVICE_OWNER:
mDeviceOwner = OwnerInfo.readFromXml(parser);
mDeviceOwnerUserId = UserHandle.USER_SYSTEM; // Set default
break;
case TAG_DEVICE_OWNER_CONTEXT: {
mDeviceOwnerUserId =
parser.getAttributeInt(null, ATTR_USERID, mDeviceOwnerUserId);
break;
}
case TAG_SYSTEM_UPDATE_POLICY:
mSystemUpdatePolicy = SystemUpdatePolicy.restoreFromXml(parser);
break;
case TAG_PENDING_OTA_INFO:
mSystemUpdateInfo = SystemUpdateInfo.readFromXml(parser);
break;
case TAG_FREEZE_PERIOD_RECORD:
String startDate = parser.getAttributeValue(null, ATTR_FREEZE_RECORD_START);
String endDate = parser.getAttributeValue(null, ATTR_FREEZE_RECORD_END);
if (startDate != null && endDate != null) {
mSystemUpdateFreezeStart = LocalDate.parse(startDate);
mSystemUpdateFreezeEnd = LocalDate.parse(endDate);
if (mSystemUpdateFreezeStart.isAfter(mSystemUpdateFreezeEnd)) {
Slog.e(TAG, "Invalid system update freeze record loaded");
mSystemUpdateFreezeStart = null;
mSystemUpdateFreezeEnd = null;
}
}
break;
case TAG_DEVICE_OWNER_TYPE:
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
int deviceOwnerType = parser.getAttributeInt(
null, ATTR_DEVICE_OWNER_TYPE_VALUE, DEVICE_OWNER_TYPE_DEFAULT);
mDeviceOwnerTypes.put(packageName, deviceOwnerType);
break;
case TAG_DEVICE_OWNER_PROTECTED_PACKAGES:
packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
int protectedPackagesSize = parser.getAttributeInt(null, ATTR_SIZE, 0);
List<String> protectedPackages = new ArrayList<>();
for (int i = 0; i < protectedPackagesSize; i++) {
protectedPackages.add(parser.getAttributeValue(null, ATTR_NAME + i));
}
mDeviceOwnerProtectedPackages.put(packageName, protectedPackages);
break;
default:
Slog.e(TAG, "Unexpected tag: " + tag);
return false;
}
return true;
}
}
private class ProfileOwnerReadWriter extends FileReadWriter {
private final int mUserId;
ProfileOwnerReadWriter(int userId) {
super(getProfileOwnerFile(userId));
mUserId = userId;
}
@Override
boolean shouldWrite() {
return mProfileOwners.get(mUserId) != null;
}
@Override
void writeInner(TypedXmlSerializer out) throws IOException {
final OwnerInfo profileOwner = mProfileOwners.get(mUserId);
if (profileOwner != null) {
profileOwner.writeToXml(out, TAG_PROFILE_OWNER);
}
}
@Override
boolean readInner(TypedXmlPullParser parser, int depth, String tag) {
if (depth > 2) {
return true; // Ignore
}
switch (tag) {
case TAG_PROFILE_OWNER:
mProfileOwners.put(mUserId, OwnerInfo.readFromXml(parser));
break;
default:
Slog.e(TAG, "Unexpected tag: " + tag);
return false;
}
return true;
}
}
static class OwnerInfo {
public final String name;
public final String packageName;
public final ComponentName admin;
public String remoteBugreportUri;
public String remoteBugreportHash;
public boolean isOrganizationOwnedDevice;
OwnerInfo(String name, ComponentName admin, String remoteBugreportUri,
String remoteBugreportHash, boolean isOrganizationOwnedDevice) {
this.name = name;
this.admin = admin;
this.packageName = admin.getPackageName();
this.remoteBugreportUri = remoteBugreportUri;
this.remoteBugreportHash = remoteBugreportHash;
this.isOrganizationOwnedDevice = isOrganizationOwnedDevice;
}
public void writeToXml(TypedXmlSerializer out, String tag) throws IOException {
out.startTag(null, tag);
if (name != null) {
out.attribute(null, ATTR_NAME, name);
}
if (admin != null) {
out.attribute(null, ATTR_COMPONENT_NAME, admin.flattenToString());
}
if (remoteBugreportUri != null) {
out.attribute(null, ATTR_REMOTE_BUGREPORT_URI, remoteBugreportUri);
}
if (remoteBugreportHash != null) {
out.attribute(null, ATTR_REMOTE_BUGREPORT_HASH, remoteBugreportHash);
}
if (isOrganizationOwnedDevice) {
out.attributeBoolean(
null, ATTR_PROFILE_OWNER_OF_ORG_OWNED_DEVICE, isOrganizationOwnedDevice);
}
out.endTag(null, tag);
}
public static OwnerInfo readFromXml(TypedXmlPullParser parser) {
final String name = parser.getAttributeValue(null, ATTR_NAME);
final String componentName = parser.getAttributeValue(null, ATTR_COMPONENT_NAME);
final String remoteBugreportUri =
parser.getAttributeValue(null, ATTR_REMOTE_BUGREPORT_URI);
final String remoteBugreportHash =
parser.getAttributeValue(null, ATTR_REMOTE_BUGREPORT_HASH);
final String canAccessDeviceIdsStr =
parser.getAttributeValue(null, ATTR_CAN_ACCESS_DEVICE_IDS);
final boolean canAccessDeviceIds = "true".equals(canAccessDeviceIdsStr);
final String isOrgOwnedDeviceStr =
parser.getAttributeValue(null, ATTR_PROFILE_OWNER_OF_ORG_OWNED_DEVICE);
final boolean isOrgOwnedDevice =
"true".equals(isOrgOwnedDeviceStr) | canAccessDeviceIds;
if (componentName == null) {
Slog.e(TAG, "Owner component not found");
return null;
}
final ComponentName admin = ComponentName.unflattenFromString(componentName);
if (admin == null) {
Slog.e(TAG, "Owner component not parsable: " + componentName);
return null;
}
return new OwnerInfo(
name, admin, remoteBugreportUri, remoteBugreportHash, isOrgOwnedDevice);
}
public void dump(IndentingPrintWriter pw) {
pw.println("admin=" + admin);
pw.println("name=" + name);
pw.println("package=" + packageName);
pw.println("isOrganizationOwnedDevice=" + isOrganizationOwnedDevice);
}
}
@VisibleForTesting
static class Injector {
File environmentGetDataSystemDirectory() {
return Environment.getDataSystemDirectory();
}
File environmentGetUserSystemDirectory(int userId) {
return Environment.getUserSystemDirectory(userId);
}
}
}

View File

@@ -75,7 +75,7 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi
services.activityManagerInternal, new MockInjector(services));
}
static class MockInjector extends Injector {
static class MockInjector extends OwnersData.Injector {
private final MockSystemServices mServices;
private MockInjector(MockSystemServices services) {