1 /* 2 * Copyright (C) 2020 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.permission.persistence; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ApexEnvironment; 22 import android.content.pm.PackageManager; 23 import android.os.UserHandle; 24 import android.util.ArrayMap; 25 import android.util.AtomicFile; 26 import android.util.Log; 27 import android.util.Xml; 28 29 import org.xmlpull.v1.XmlPullParser; 30 import org.xmlpull.v1.XmlPullParserException; 31 import org.xmlpull.v1.XmlSerializer; 32 33 import java.io.File; 34 import java.io.FileInputStream; 35 import java.io.FileNotFoundException; 36 import java.io.FileOutputStream; 37 import java.io.IOException; 38 import java.nio.charset.StandardCharsets; 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.Map; 42 43 /** 44 * Persistence implementation for runtime permissions. 45 * 46 * TODO(b/147914847): Remove @hide when it becomes the default. 47 * @hide 48 */ 49 public class RuntimePermissionsPersistenceImpl implements RuntimePermissionsPersistence { 50 51 private static final String LOG_TAG = RuntimePermissionsPersistenceImpl.class.getSimpleName(); 52 53 private static final String APEX_MODULE_NAME = "com.android.permission"; 54 55 private static final String RUNTIME_PERMISSIONS_FILE_NAME = "runtime-permissions.xml"; 56 57 private static final String TAG_PACKAGE = "package"; 58 private static final String TAG_PERMISSION = "permission"; 59 private static final String TAG_RUNTIME_PERMISSIONS = "runtime-permissions"; 60 private static final String TAG_SHARED_USER = "shared-user"; 61 62 private static final String ATTRIBUTE_FINGERPRINT = "fingerprint"; 63 private static final String ATTRIBUTE_FLAGS = "flags"; 64 private static final String ATTRIBUTE_GRANTED = "granted"; 65 private static final String ATTRIBUTE_NAME = "name"; 66 private static final String ATTRIBUTE_VERSION = "version"; 67 68 @Nullable 69 @Override readForUser(@onNull UserHandle user)70 public RuntimePermissionsState readForUser(@NonNull UserHandle user) { 71 File file = getFile(user); 72 try (FileInputStream inputStream = new AtomicFile(file).openRead()) { 73 XmlPullParser parser = Xml.newPullParser(); 74 parser.setInput(inputStream, null); 75 return parseXml(parser); 76 } catch (FileNotFoundException e) { 77 Log.i(LOG_TAG, "runtime-permissions.xml not found"); 78 return null; 79 } catch (XmlPullParserException | IOException e) { 80 throw new IllegalStateException("Failed to read runtime-permissions.xml: " + file , e); 81 } 82 } 83 84 @NonNull parseXml(@onNull XmlPullParser parser)85 private static RuntimePermissionsState parseXml(@NonNull XmlPullParser parser) 86 throws IOException, XmlPullParserException { 87 int type; 88 int depth; 89 int innerDepth = parser.getDepth() + 1; 90 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 91 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { 92 if (depth > innerDepth || type != XmlPullParser.START_TAG) { 93 continue; 94 } 95 96 if (parser.getName().equals(TAG_RUNTIME_PERMISSIONS)) { 97 return parseRuntimePermissions(parser); 98 } 99 } 100 throw new IllegalStateException("Missing <" + TAG_RUNTIME_PERMISSIONS 101 + "> in runtime-permissions.xml"); 102 } 103 104 @NonNull parseRuntimePermissions(@onNull XmlPullParser parser)105 private static RuntimePermissionsState parseRuntimePermissions(@NonNull XmlPullParser parser) 106 throws IOException, XmlPullParserException { 107 String versionValue = parser.getAttributeValue(null, ATTRIBUTE_VERSION); 108 int version = versionValue != null ? Integer.parseInt(versionValue) 109 : RuntimePermissionsState.NO_VERSION; 110 String fingerprint = parser.getAttributeValue(null, ATTRIBUTE_FINGERPRINT); 111 112 Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions = 113 new ArrayMap<>(); 114 Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions = 115 new ArrayMap<>(); 116 int type; 117 int depth; 118 int innerDepth = parser.getDepth() + 1; 119 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 120 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { 121 if (depth > innerDepth || type != XmlPullParser.START_TAG) { 122 continue; 123 } 124 125 switch (parser.getName()) { 126 case TAG_PACKAGE: { 127 String packageName = parser.getAttributeValue(null, ATTRIBUTE_NAME); 128 List<RuntimePermissionsState.PermissionState> permissions = parsePermissions( 129 parser); 130 packagePermissions.put(packageName, permissions); 131 break; 132 } 133 case TAG_SHARED_USER: { 134 String sharedUserName = parser.getAttributeValue(null, ATTRIBUTE_NAME); 135 List<RuntimePermissionsState.PermissionState> permissions = parsePermissions( 136 parser); 137 sharedUserPermissions.put(sharedUserName, permissions); 138 break; 139 } 140 } 141 } 142 143 return new RuntimePermissionsState(version, fingerprint, packagePermissions, 144 sharedUserPermissions); 145 } 146 147 @NonNull parsePermissions( @onNull XmlPullParser parser)148 private static List<RuntimePermissionsState.PermissionState> parsePermissions( 149 @NonNull XmlPullParser parser) throws IOException, XmlPullParserException { 150 List<RuntimePermissionsState.PermissionState> permissions = new ArrayList<>(); 151 int type; 152 int depth; 153 int innerDepth = parser.getDepth() + 1; 154 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 155 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { 156 if (depth > innerDepth || type != XmlPullParser.START_TAG) { 157 continue; 158 } 159 160 if (parser.getName().equals(TAG_PERMISSION)) { 161 String name = parser.getAttributeValue(null, ATTRIBUTE_NAME); 162 boolean granted = Boolean.parseBoolean(parser.getAttributeValue(null, 163 ATTRIBUTE_GRANTED)); 164 int flags = Integer.parseInt(parser.getAttributeValue(null, 165 ATTRIBUTE_FLAGS), 16); 166 RuntimePermissionsState.PermissionState permission = 167 new RuntimePermissionsState.PermissionState(name, granted, flags); 168 permissions.add(permission); 169 } 170 } 171 return permissions; 172 } 173 174 @Override writeForUser(@onNull RuntimePermissionsState runtimePermissions, @NonNull UserHandle user)175 public void writeForUser(@NonNull RuntimePermissionsState runtimePermissions, 176 @NonNull UserHandle user) { 177 File file = getFile(user); 178 AtomicFile atomicFile = new AtomicFile(file); 179 FileOutputStream outputStream = null; 180 try { 181 outputStream = atomicFile.startWrite(); 182 183 XmlSerializer serializer = Xml.newSerializer(); 184 serializer.setOutput(outputStream, StandardCharsets.UTF_8.name()); 185 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 186 serializer.startDocument(null, true); 187 188 serializeRuntimePermissions(serializer, runtimePermissions); 189 190 serializer.endDocument(); 191 atomicFile.finishWrite(outputStream); 192 } catch (Exception e) { 193 Log.wtf(LOG_TAG, "Failed to write runtime-permissions.xml, restoring backup: " + file, 194 e); 195 atomicFile.failWrite(outputStream); 196 } finally { 197 IoUtils.closeQuietly(outputStream); 198 } 199 } 200 serializeRuntimePermissions(@onNull XmlSerializer serializer, @NonNull RuntimePermissionsState runtimePermissions)201 private static void serializeRuntimePermissions(@NonNull XmlSerializer serializer, 202 @NonNull RuntimePermissionsState runtimePermissions) throws IOException { 203 serializer.startTag(null, TAG_RUNTIME_PERMISSIONS); 204 205 int version = runtimePermissions.getVersion(); 206 serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version)); 207 String fingerprint = runtimePermissions.getFingerprint(); 208 if (fingerprint != null) { 209 serializer.attribute(null, ATTRIBUTE_FINGERPRINT, fingerprint); 210 } 211 212 for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry 213 : runtimePermissions.getPackagePermissions().entrySet()) { 214 String packageName = entry.getKey(); 215 List<RuntimePermissionsState.PermissionState> permissions = entry.getValue(); 216 217 serializer.startTag(null, TAG_PACKAGE); 218 serializer.attribute(null, ATTRIBUTE_NAME, packageName); 219 serializePermissions(serializer, permissions); 220 serializer.endTag(null, TAG_PACKAGE); 221 } 222 223 for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry 224 : runtimePermissions.getSharedUserPermissions().entrySet()) { 225 String sharedUserName = entry.getKey(); 226 List<RuntimePermissionsState.PermissionState> permissions = entry.getValue(); 227 228 serializer.startTag(null, TAG_SHARED_USER); 229 serializer.attribute(null, ATTRIBUTE_NAME, sharedUserName); 230 serializePermissions(serializer, permissions); 231 serializer.endTag(null, TAG_SHARED_USER); 232 } 233 234 serializer.endTag(null, TAG_RUNTIME_PERMISSIONS); 235 } 236 serializePermissions(@onNull XmlSerializer serializer, @NonNull List<RuntimePermissionsState.PermissionState> permissions)237 private static void serializePermissions(@NonNull XmlSerializer serializer, 238 @NonNull List<RuntimePermissionsState.PermissionState> permissions) throws IOException { 239 int permissionsSize = permissions.size(); 240 for (int i = 0; i < permissionsSize; i++) { 241 RuntimePermissionsState.PermissionState permissionState = permissions.get(i); 242 243 serializer.startTag(null, TAG_PERMISSION); 244 serializer.attribute(null, ATTRIBUTE_NAME, permissionState.getName()); 245 serializer.attribute(null, ATTRIBUTE_GRANTED, Boolean.toString( 246 permissionState.isGranted() && (permissionState.getFlags() 247 & PackageManager.FLAG_PERMISSION_ONE_TIME) == 0)); 248 serializer.attribute(null, ATTRIBUTE_FLAGS, Integer.toHexString( 249 permissionState.getFlags())); 250 serializer.endTag(null, TAG_PERMISSION); 251 } 252 } 253 254 @Override deleteForUser(@onNull UserHandle user)255 public void deleteForUser(@NonNull UserHandle user) { 256 getFile(user).delete(); 257 } 258 259 @NonNull getFile(@onNull UserHandle user)260 private static File getFile(@NonNull UserHandle user) { 261 ApexEnvironment apexEnvironment = ApexEnvironment.getApexEnvironment(APEX_MODULE_NAME); 262 File dataDirectory = apexEnvironment.getDeviceProtectedDataDirForUser(user); 263 return new File(dataDirectory, RUNTIME_PERMISSIONS_FILE_NAME); 264 } 265 } 266