diff options
author | 2020-01-16 01:42:58 -0800 | |
---|---|---|
committer | 2020-01-17 19:05:48 -0800 | |
commit | 9b60ce7a237e12680fcf112974f96a07adfd81ef (patch) | |
tree | aec1c0b0f69e1d2e550d734814f14396a25e7362 | |
parent | 36fd640496e7d2b8578d52d654668b56bd84041c (diff) |
Move runtime permissions persistence into APEX.
Bug: 136503238
Test: presubmit
Change-Id: Id016d8c111ceadd27dc318c256b2f32ff0380f60
-rw-r--r-- | framework/Android.bp | 8 | ||||
-rw-r--r-- | service/Android.bp | 15 | ||||
-rw-r--r-- | service/java/com/android/permission/persistence/IoUtils.java (renamed from service/java/com/android/server/permission/RuntimePermissionPersistence.java) | 22 | ||||
-rw-r--r-- | service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java | 72 | ||||
-rw-r--r-- | service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java | 261 | ||||
-rw-r--r-- | service/java/com/android/permission/persistence/RuntimePermissionsState.java | 131 |
6 files changed, 503 insertions, 6 deletions
diff --git a/framework/Android.bp b/framework/Android.bp index 8b03da3a9..09571a1cd 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -26,7 +26,13 @@ java_library { srcs: [ ":framework-permission-sources", ], - sdk_version: "system_current", + // TODO(b/146758669): Use "system_current" after nullability annotations are system APIs. + sdk_version: "core_current", + libs: [ + "framework-annotations-lib", + // TODO(b/146758669): Remove this line after nullability annotations are system APIs. + "android_system_stubs_current", + ], apex_available: [ "com.android.permission", "test_com.android.permission", diff --git a/service/Android.bp b/service/Android.bp index 972b36250..4172e95bc 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -12,13 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. +filegroup { + name: "service-permission-sources", + srcs: [ + "java/**/*.java", + ], +} + java_library { name: "service-permission", srcs: [ - "java/**/*.java", + ":service-permission-sources", ], - sdk_version: "system_current", + // TODO(b/146758669): Use "system_current" after nullability annotations are system APIs. + sdk_version: "core_current", libs: [ + "framework-annotations-lib", + // TODO(b/146758669): Remove this line after nullability annotations are system APIs. + "android_system_stubs_current", "framework-permission", ], apex_available: [ diff --git a/service/java/com/android/server/permission/RuntimePermissionPersistence.java b/service/java/com/android/permission/persistence/IoUtils.java index a534e22c0..0ae446035 100644 --- a/service/java/com/android/server/permission/RuntimePermissionPersistence.java +++ b/service/java/com/android/permission/persistence/IoUtils.java @@ -14,9 +14,25 @@ * limitations under the License. */ -package com.android.server.permission; +package com.android.permission.persistence; + +import android.annotation.NonNull; /** - * Persistence for runtime permissions. + * Utility class for IO. */ -public class RuntimePermissionPersistence {} +public class IoUtils { + + private IoUtils() {} + + /** + * Close 'closeable' ignoring any exceptions. + */ + public static void closeQuietly(@NonNull AutoCloseable closeable) { + try { + closeable.close(); + } catch (Exception ignored) { + // Ignored. + } + } +} diff --git a/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java b/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java new file mode 100644 index 000000000..5f2d94441 --- /dev/null +++ b/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 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.permission.persistence; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.UserHandle; + +/** + * Persistence for runtime permissions. + * + * TODO(b/147914847): Remove @hide when it becomes the default. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER) +public interface RuntimePermissionsPersistence { + + /** + * Read the runtime permissions from persistence. + * + * This will perform I/O operations synchronously. + * + * @param user the user to read for + * @return the runtime permissions read + */ + @Nullable + RuntimePermissionsState read(@NonNull UserHandle user); + + /** + * Write the runtime permissions to persistence. + * + * This will perform I/O operations synchronously. + * + * @param runtimePermissions the runtime permissions to write + * @param user the user to write for + */ + void write(@NonNull RuntimePermissionsState runtimePermissions, @NonNull UserHandle user); + + /** + * Delete the runtime permissions from persistence. + * + * This will perform I/O operations synchronously. + * + * @param user the user to delete for + */ + void delete(@NonNull UserHandle user); + + /** + * Create a new instance of {@link RuntimePermissionsPersistence} implementation. + * + * @return the new instance. + */ + @NonNull + static RuntimePermissionsPersistence createInstance() { + return new RuntimePermissionsPersistenceImpl(); + } +} diff --git a/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java b/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java new file mode 100644 index 000000000..51b911a94 --- /dev/null +++ b/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2020 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.permission.persistence; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.Log; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Persistence implementation for runtime permissions. + * + * TODO(b/147914847): Remove @hide when it becomes the default. + * @hide + */ +public class RuntimePermissionsPersistenceImpl implements RuntimePermissionsPersistence { + + private static final String LOG_TAG = RuntimePermissionsPersistenceImpl.class.getSimpleName(); + + private static final String RUNTIME_PERMISSIONS_FILE_NAME = "runtime-permissions.xml"; + + private static final String TAG_PACKAGE = "package"; + private static final String TAG_PERMISSION = "permission"; + private static final String TAG_RUNTIME_PERMISSIONS = "runtime-permissions"; + private static final String TAG_SHARED_USER = "shared-user"; + + private static final String ATTRIBUTE_FINGERPRINT = "fingerprint"; + private static final String ATTRIBUTE_FLAGS = "flags"; + private static final String ATTRIBUTE_GRANTED = "granted"; + private static final String ATTRIBUTE_NAME = "name"; + private static final String ATTRIBUTE_VERSION = "version"; + + @Nullable + @Override + public RuntimePermissionsState read(@NonNull UserHandle user) { + File file = getFile(user); + try (FileInputStream inputStream = new AtomicFile(file).openRead()) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(inputStream, null); + return parseXml(parser); + } catch (FileNotFoundException e) { + Log.i(LOG_TAG, "runtime-permissions.xml not found"); + return null; + } catch (XmlPullParserException | IOException e) { + throw new IllegalStateException("Failed to read runtime-permissions.xml: " + file , e); + } + } + + @NonNull + private static RuntimePermissionsState parseXml(@NonNull XmlPullParser parser) + throws IOException, XmlPullParserException { + int type; + int depth; + int innerDepth = parser.getDepth() + 1; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { + if (depth > innerDepth || type != XmlPullParser.START_TAG) { + continue; + } + + if (parser.getName().equals(TAG_RUNTIME_PERMISSIONS)) { + return parseRuntimePermissions(parser); + } + } + throw new IllegalStateException("Missing <" + TAG_RUNTIME_PERMISSIONS + + "> in runtime-permissions.xml"); + } + + @NonNull + private static RuntimePermissionsState parseRuntimePermissions(@NonNull XmlPullParser parser) + throws IOException, XmlPullParserException { + String versionValue = parser.getAttributeValue(null, ATTRIBUTE_VERSION); + int version = versionValue != null ? Integer.parseInt(versionValue) + : RuntimePermissionsState.NO_VERSION; + String fingerprint = parser.getAttributeValue(null, ATTRIBUTE_FINGERPRINT); + + Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions = + new ArrayMap<>(); + Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions = + new ArrayMap<>(); + int type; + int depth; + int innerDepth = parser.getDepth() + 1; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { + if (depth > innerDepth || type != XmlPullParser.START_TAG) { + continue; + } + + switch (parser.getName()) { + case TAG_PACKAGE: { + String packageName = parser.getAttributeValue(null, ATTRIBUTE_NAME); + List<RuntimePermissionsState.PermissionState> permissions = parsePermissions( + parser); + packagePermissions.put(packageName, permissions); + break; + } + case TAG_SHARED_USER: { + String sharedUserName = parser.getAttributeValue(null, ATTRIBUTE_NAME); + List<RuntimePermissionsState.PermissionState> permissions = parsePermissions( + parser); + sharedUserPermissions.put(sharedUserName, permissions); + break; + } + } + } + + return new RuntimePermissionsState(version, fingerprint, packagePermissions, + sharedUserPermissions); + } + + @NonNull + private static List<RuntimePermissionsState.PermissionState> parsePermissions( + @NonNull XmlPullParser parser) throws IOException, XmlPullParserException { + List<RuntimePermissionsState.PermissionState> permissions = new ArrayList<>(); + int type; + int depth; + int innerDepth = parser.getDepth() + 1; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { + if (depth > innerDepth || type != XmlPullParser.START_TAG) { + continue; + } + + if (parser.getName().equals(TAG_PERMISSION)) { + String name = parser.getAttributeValue(null, ATTRIBUTE_NAME); + boolean granted = Boolean.parseBoolean(parser.getAttributeValue(null, + ATTRIBUTE_GRANTED)); + int flags = Integer.parseInt(parser.getAttributeValue(null, + ATTRIBUTE_FLAGS), 16); + RuntimePermissionsState.PermissionState permission = + new RuntimePermissionsState.PermissionState(name, granted, flags); + permissions.add(permission); + } + } + return permissions; + } + + @Override + public void write(@NonNull RuntimePermissionsState runtimePermissions, + @NonNull UserHandle user) { + File file = getFile(user); + AtomicFile atomicFile = new AtomicFile(file); + FileOutputStream outputStream = null; + try { + outputStream = atomicFile.startWrite(); + + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(outputStream, StandardCharsets.UTF_8.name()); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + serializer.startDocument(null, true); + + serializeRuntimePermissions(serializer, runtimePermissions); + + serializer.endDocument(); + atomicFile.finishWrite(outputStream); + } catch (Exception e) { + Log.wtf(LOG_TAG, "Failed to write runtime-permissions.xml, restoring backup: " + file, + e); + atomicFile.failWrite(outputStream); + } finally { + IoUtils.closeQuietly(outputStream); + } + } + + private static void serializeRuntimePermissions(@NonNull XmlSerializer serializer, + @NonNull RuntimePermissionsState runtimePermissions) throws IOException { + serializer.startTag(null, TAG_RUNTIME_PERMISSIONS); + + int version = runtimePermissions.getVersion(); + serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version)); + String fingerprint = runtimePermissions.getFingerprint(); + if (fingerprint != null) { + serializer.attribute(null, ATTRIBUTE_FINGERPRINT, fingerprint); + } + + for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry + : runtimePermissions.getPackagePermissions().entrySet()) { + String packageName = entry.getKey(); + List<RuntimePermissionsState.PermissionState> permissions = entry.getValue(); + + serializer.startTag(null, TAG_PACKAGE); + serializer.attribute(null, ATTRIBUTE_NAME, packageName); + serializePermissions(serializer, permissions); + serializer.endTag(null, TAG_PACKAGE); + } + + for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry + : runtimePermissions.getSharedUserPermissions().entrySet()) { + String sharedUserName = entry.getKey(); + List<RuntimePermissionsState.PermissionState> permissions = entry.getValue(); + + serializer.startTag(null, TAG_SHARED_USER); + serializer.attribute(null, ATTRIBUTE_NAME, sharedUserName); + serializePermissions(serializer, permissions); + serializer.endTag(null, TAG_SHARED_USER); + } + + serializer.endTag(null, TAG_RUNTIME_PERMISSIONS); + } + + private static void serializePermissions(@NonNull XmlSerializer serializer, + @NonNull List<RuntimePermissionsState.PermissionState> permissions) throws IOException { + int permissionsSize = permissions.size(); + for (int i = 0; i < permissionsSize; i++) { + RuntimePermissionsState.PermissionState permissionState = permissions.get(i); + + serializer.startTag(null, TAG_PERMISSION); + serializer.attribute(null, ATTRIBUTE_NAME, permissionState.getName()); + serializer.attribute(null, ATTRIBUTE_GRANTED, Boolean.toString( + permissionState.isGranted())); + serializer.attribute(null, ATTRIBUTE_FLAGS, Integer.toHexString( + permissionState.getFlags())); + serializer.endTag(null, TAG_PERMISSION); + } + } + + @Override + public void delete(@NonNull UserHandle user) { + getFile(user).delete(); + } + + @NonNull + private static File getFile(@NonNull UserHandle user) { + // TODO: Use an API for this. + File dataDirectory = new File("/data/misc_de/" + user.getIdentifier() + + "/apexdata/com.android.permission"); + return new File(dataDirectory, RUNTIME_PERMISSIONS_FILE_NAME); + } +} diff --git a/service/java/com/android/permission/persistence/RuntimePermissionsState.java b/service/java/com/android/permission/persistence/RuntimePermissionsState.java new file mode 100644 index 000000000..2a939e51b --- /dev/null +++ b/service/java/com/android/permission/persistence/RuntimePermissionsState.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2020 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.permission.persistence; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; + +import java.util.List; +import java.util.Map; + +/** + * State of all runtime permissions. + * + * TODO(b/147914847): Remove @hide when it becomes the default. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER) +public final class RuntimePermissionsState { + + /** + * Special value for {@link #mVersion} to indicate that no version was read. + */ + public static final int NO_VERSION = -1; + + /** + * The version of the runtime permissions. + */ + private final int mVersion; + + /** + * The fingerprint of the runtime permissions. + */ + @Nullable + private final String mFingerprint; + + /** + * The runtime permissions by packages. + */ + @NonNull + private final Map<String, List<PermissionState>> mPackagePermissions; + + /** + * The runtime permissions by shared users. + */ + @NonNull + private final Map<String, List<PermissionState>> mSharedUserPermissions; + + public RuntimePermissionsState(int version, @Nullable String fingerprint, + @NonNull Map<String, List<PermissionState>> packagePermissions, + @NonNull Map<String, List<PermissionState>> sharedUserPermissions) { + mVersion = version; + mFingerprint = fingerprint; + mPackagePermissions = packagePermissions; + mSharedUserPermissions = sharedUserPermissions; + } + + public int getVersion() { + return mVersion; + } + + @Nullable + public String getFingerprint() { + return mFingerprint; + } + + @NonNull + public Map<String, List<PermissionState>> getPackagePermissions() { + return mPackagePermissions; + } + + @NonNull + public Map<String, List<PermissionState>> getSharedUserPermissions() { + return mSharedUserPermissions; + } + + /** + * State of a single permission. + */ + public static class PermissionState { + + /** + * Name of the permission. + */ + @NonNull + private final String mName; + + /** + * Whether the permission is granted. + */ + private final boolean mGranted; + + /** + * Flags of the permission. + */ + private final int mFlags; + + public PermissionState(@NonNull String name, boolean granted, int flags) { + mName = name; + mGranted = granted; + mFlags = flags; + } + + @NonNull + public String getName() { + return mName; + } + + public boolean isGranted() { + return mGranted; + } + + public int getFlags() { + return mFlags; + } + } +} |