Home | History | Annotate | Download | only in devicepolicy
      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 
     70     TransferOwnershipMetadataManager() {
     71         this(new Injector());
     72     }
     73 
     74     @VisibleForTesting
     75     TransferOwnershipMetadataManager(Injector injector) {
     76         mInjector = injector;
     77     }
     78 
     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 
    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
    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 
    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 
    169     void deleteMetadataFile() {
    170         new File(mInjector.getOwnerTransferMetadataDir(), OWNER_TRANSFER_METADATA_XML).delete();
    171     }
    172 
    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 
    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 
    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 
    201         private static ComponentName unflattenComponentUnchecked(String flatComponent) {
    202             Preconditions.checkNotNull(flatComponent);
    203             return ComponentName.unflattenFromString(flatComponent);
    204         }
    205 
    206         @Override
    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
    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 {
    232         public File getOwnerTransferMetadataDir() {
    233             return Environment.getDataSystemDirectory();
    234         }
    235     }
    236 }
    237