1 /*
2  * Copyright (C) 2018 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.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.admin.DevicePolicyManager;
22 import android.content.ComponentName;
23 import android.os.Environment;
24 import android.text.TextUtils;
25 import android.util.AtomicFile;
26 import android.util.Slog;
27 import android.util.Xml;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 import com.android.internal.util.FastXmlSerializer;
31 import com.android.internal.util.Preconditions;
32 
33 import org.xmlpull.v1.XmlPullParser;
34 import org.xmlpull.v1.XmlPullParserException;
35 import org.xmlpull.v1.XmlSerializer;
36 
37 import java.io.File;
38 import java.io.FileInputStream;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.nio.charset.StandardCharsets;
42 
43 /**
44  * Handles reading and writing of the owner transfer metadata file.
45  *
46  * Before we perform a device or profile owner transfer, we save this xml file with information
47  * about the current admin, target admin, user id and admin type (device owner or profile owner).
48  * After {@link DevicePolicyManager#transferOwnership} completes, we delete the file. If after
49  * device boot the file is still there, this indicates that the transfer was interrupted by a
50  * reboot.
51  *
52  * Note that this class is not thread safe.
53  */
54 class TransferOwnershipMetadataManager {
55     final static String ADMIN_TYPE_DEVICE_OWNER = "device-owner";
56     final static String ADMIN_TYPE_PROFILE_OWNER = "profile-owner";
57     @VisibleForTesting
58     final static String TAG_USER_ID = "user-id";
59     @VisibleForTesting
60     final static String TAG_SOURCE_COMPONENT = "source-component";
61     @VisibleForTesting
62     final static String TAG_TARGET_COMPONENT = "target-component";
63     @VisibleForTesting
64     final static String TAG_ADMIN_TYPE = "admin-type";
65     private final static String TAG = TransferOwnershipMetadataManager.class.getName();
66     public static final String OWNER_TRANSFER_METADATA_XML = "owner-transfer-metadata.xml";
67 
68     private final Injector mInjector;
69 
TransferOwnershipMetadataManager()70     TransferOwnershipMetadataManager() {
71         this(new Injector());
72     }
73 
74     @VisibleForTesting
TransferOwnershipMetadataManager(Injector injector)75     TransferOwnershipMetadataManager(Injector injector) {
76         mInjector = injector;
77     }
78 
saveMetadataFile(Metadata params)79     boolean saveMetadataFile(Metadata params) {
80         final File transferOwnershipMetadataFile = new File(mInjector.getOwnerTransferMetadataDir(),
81                 OWNER_TRANSFER_METADATA_XML);
82         final AtomicFile atomicFile = new AtomicFile(transferOwnershipMetadataFile);
83         FileOutputStream stream = null;
84         try {
85             stream = atomicFile.startWrite();
86             final XmlSerializer serializer = new FastXmlSerializer();
87             serializer.setOutput(stream, StandardCharsets.UTF_8.name());
88             serializer.startDocument(null, true);
89             insertSimpleTag(serializer, TAG_USER_ID, Integer.toString(params.userId));
90             insertSimpleTag(serializer,
91                     TAG_SOURCE_COMPONENT, params.sourceComponent.flattenToString());
92             insertSimpleTag(serializer,
93                     TAG_TARGET_COMPONENT, params.targetComponent.flattenToString());
94             insertSimpleTag(serializer, TAG_ADMIN_TYPE, params.adminType);
95             serializer.endDocument();
96             atomicFile.finishWrite(stream);
97             return true;
98         } catch (IOException e) {
99             Slog.e(TAG, "Caught exception while trying to save Owner Transfer "
100                     + "Params to file " + transferOwnershipMetadataFile, e);
101             transferOwnershipMetadataFile.delete();
102             atomicFile.failWrite(stream);
103         }
104         return false;
105     }
106 
insertSimpleTag(XmlSerializer serializer, String tagName, String value)107     private void insertSimpleTag(XmlSerializer serializer, String tagName, String value)
108             throws IOException {
109         serializer.startTag(null, tagName);
110         serializer.text(value);
111         serializer.endTag(null, tagName);
112     }
113 
114     @Nullable
loadMetadataFile()115     Metadata loadMetadataFile() {
116         final File transferOwnershipMetadataFile =
117                 new File(mInjector.getOwnerTransferMetadataDir(), OWNER_TRANSFER_METADATA_XML);
118         if (!transferOwnershipMetadataFile.exists()) {
119             return null;
120         }
121         Slog.d(TAG, "Loading TransferOwnershipMetadataManager from "
122                 + transferOwnershipMetadataFile);
123         try (FileInputStream stream = new FileInputStream(transferOwnershipMetadataFile)) {
124             final XmlPullParser parser = Xml.newPullParser();
125             parser.setInput(stream, null);
126             return parseMetadataFile(parser);
127         } catch (IOException | XmlPullParserException | IllegalArgumentException e) {
128             Slog.e(TAG, "Caught exception while trying to load the "
129                     + "owner transfer params from file " + transferOwnershipMetadataFile, e);
130         }
131         return null;
132     }
133 
parseMetadataFile(XmlPullParser parser)134     private Metadata parseMetadataFile(XmlPullParser parser)
135             throws XmlPullParserException, IOException {
136         int type;
137         final int outerDepth = parser.getDepth();
138         int userId = 0;
139         String adminComponent = null;
140         String targetComponent = null;
141         String adminType = null;
142         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
143                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
144             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
145                 continue;
146             }
147             switch (parser.getName()) {
148                 case TAG_USER_ID:
149                     parser.next();
150                     userId = Integer.parseInt(parser.getText());
151                     break;
152                 case TAG_TARGET_COMPONENT:
153                     parser.next();
154                     targetComponent = parser.getText();
155                     break;
156                 case TAG_SOURCE_COMPONENT:
157                     parser.next();
158                     adminComponent = parser.getText();
159                     break;
160                 case TAG_ADMIN_TYPE:
161                     parser.next();
162                     adminType = parser.getText();
163                     break;
164             }
165         }
166         return new Metadata(adminComponent, targetComponent, userId, adminType);
167     }
168 
deleteMetadataFile()169     void deleteMetadataFile() {
170         new File(mInjector.getOwnerTransferMetadataDir(), OWNER_TRANSFER_METADATA_XML).delete();
171     }
172 
metadataFileExists()173     boolean metadataFileExists() {
174         return new File(mInjector.getOwnerTransferMetadataDir(),
175                 OWNER_TRANSFER_METADATA_XML).exists();
176     }
177 
178     static class Metadata {
179         final int userId;
180         final ComponentName sourceComponent;
181         final ComponentName targetComponent;
182         final String adminType;
183 
Metadata(@onNull ComponentName sourceComponent, @NonNull ComponentName targetComponent, @NonNull int userId, @NonNull String adminType)184         Metadata(@NonNull ComponentName sourceComponent, @NonNull ComponentName targetComponent,
185                 @NonNull int userId, @NonNull String adminType) {
186             this.sourceComponent = sourceComponent;
187             this.targetComponent = targetComponent;
188             Preconditions.checkNotNull(sourceComponent);
189             Preconditions.checkNotNull(targetComponent);
190             Preconditions.checkStringNotEmpty(adminType);
191             this.userId = userId;
192             this.adminType = adminType;
193         }
194 
Metadata(@onNull String flatSourceComponent, @NonNull String flatTargetComponent, @NonNull int userId, @NonNull String adminType)195         Metadata(@NonNull String flatSourceComponent, @NonNull String flatTargetComponent,
196                 @NonNull int userId, @NonNull String adminType) {
197             this(unflattenComponentUnchecked(flatSourceComponent),
198                     unflattenComponentUnchecked(flatTargetComponent), userId, adminType);
199         }
200 
unflattenComponentUnchecked(String flatComponent)201         private static ComponentName unflattenComponentUnchecked(String flatComponent) {
202             Preconditions.checkNotNull(flatComponent);
203             return ComponentName.unflattenFromString(flatComponent);
204         }
205 
206         @Override
equals(Object obj)207         public boolean equals(Object obj) {
208             if (!(obj instanceof Metadata)) {
209                 return false;
210             }
211             Metadata params = (Metadata) obj;
212 
213             return userId == params.userId
214                     && sourceComponent.equals(params.sourceComponent)
215                     && targetComponent.equals(params.targetComponent)
216                     && TextUtils.equals(adminType, params.adminType);
217         }
218 
219         @Override
hashCode()220         public int hashCode() {
221             int hashCode = 1;
222             hashCode = 31 * hashCode + userId;
223             hashCode = 31 * hashCode + sourceComponent.hashCode();
224             hashCode = 31 * hashCode + targetComponent.hashCode();
225             hashCode = 31 * hashCode + adminType.hashCode();
226             return hashCode;
227         }
228     }
229 
230     @VisibleForTesting
231     static class Injector {
getOwnerTransferMetadataDir()232         public File getOwnerTransferMetadataDir() {
233             return Environment.getDataSystemDirectory();
234         }
235     }
236 }
237