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.startDocument(null, true); 186 187 serializeRuntimePermissions(serializer, runtimePermissions); 188 189 serializer.endDocument(); 190 atomicFile.finishWrite(outputStream); 191 } catch (Exception e) { 192 Log.wtf(LOG_TAG, "Failed to write runtime-permissions.xml, restoring backup: " + file, 193 e); 194 atomicFile.failWrite(outputStream); 195 } finally { 196 IoUtils.closeQuietly(outputStream); 197 } 198 } 199 serializeRuntimePermissions(@onNull XmlSerializer serializer, @NonNull RuntimePermissionsState runtimePermissions)200 private static void serializeRuntimePermissions(@NonNull XmlSerializer serializer, 201 @NonNull RuntimePermissionsState runtimePermissions) throws IOException { 202 serializer.startTag(null, TAG_RUNTIME_PERMISSIONS); 203 204 int version = runtimePermissions.getVersion(); 205 serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version)); 206 String fingerprint = runtimePermissions.getFingerprint(); 207 if (fingerprint != null) { 208 serializer.attribute(null, ATTRIBUTE_FINGERPRINT, fingerprint); 209 } 210 211 for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry 212 : runtimePermissions.getPackagePermissions().entrySet()) { 213 String packageName = entry.getKey(); 214 List<RuntimePermissionsState.PermissionState> permissions = entry.getValue(); 215 216 serializer.startTag(null, TAG_PACKAGE); 217 serializer.attribute(null, ATTRIBUTE_NAME, packageName); 218 serializePermissions(serializer, permissions); 219 serializer.endTag(null, TAG_PACKAGE); 220 } 221 222 for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry 223 : runtimePermissions.getSharedUserPermissions().entrySet()) { 224 String sharedUserName = entry.getKey(); 225 List<RuntimePermissionsState.PermissionState> permissions = entry.getValue(); 226 227 serializer.startTag(null, TAG_SHARED_USER); 228 serializer.attribute(null, ATTRIBUTE_NAME, sharedUserName); 229 serializePermissions(serializer, permissions); 230 serializer.endTag(null, TAG_SHARED_USER); 231 } 232 233 serializer.endTag(null, TAG_RUNTIME_PERMISSIONS); 234 } 235 serializePermissions(@onNull XmlSerializer serializer, @NonNull List<RuntimePermissionsState.PermissionState> permissions)236 private static void serializePermissions(@NonNull XmlSerializer serializer, 237 @NonNull List<RuntimePermissionsState.PermissionState> permissions) throws IOException { 238 int permissionsSize = permissions.size(); 239 for (int i = 0; i < permissionsSize; i++) { 240 RuntimePermissionsState.PermissionState permissionState = permissions.get(i); 241 242 serializer.startTag(null, TAG_PERMISSION); 243 serializer.attribute(null, ATTRIBUTE_NAME, permissionState.getName()); 244 serializer.attribute(null, ATTRIBUTE_GRANTED, Boolean.toString( 245 permissionState.isGranted() && (permissionState.getFlags() 246 & PackageManager.FLAG_PERMISSION_ONE_TIME) == 0)); 247 serializer.attribute(null, ATTRIBUTE_FLAGS, Integer.toHexString( 248 permissionState.getFlags())); 249 serializer.endTag(null, TAG_PERMISSION); 250 } 251 } 252 253 @Override deleteForUser(@onNull UserHandle user)254 public void deleteForUser(@NonNull UserHandle user) { 255 getFile(user).delete(); 256 } 257 258 @NonNull getFile(@onNull UserHandle user)259 private static File getFile(@NonNull UserHandle user) { 260 ApexEnvironment apexEnvironment = ApexEnvironment.getApexEnvironment(APEX_MODULE_NAME); 261 File dataDirectory = apexEnvironment.getDeviceProtectedDataDirForUser(user); 262 return new File(dataDirectory, RUNTIME_PERMISSIONS_FILE_NAME); 263 } 264 } 265