summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Richard MacGregor <rmacgregor@google.com> 2024-10-14 16:11:38 -0700
committer Richard MacGregor <rmacgregor@google.com> 2024-11-08 17:23:04 +0000
commit7f27dc5ff6a4b2197218d02b680da802e73f4302 (patch)
tree1636a8d3e5351952065343816d7047a8dcb07cce
parent1554678e38c686c1df3f0324f58d1d6da9e404c0 (diff)
Add Cross-user role persistence
Add persistence for cross-user role support. LOW_COVERAGE_REASON=FLAG_NOT_ENABLED Relnote: Add cross-user role persistence Flag: com.android.permission.flags.cross_user_role_enabled Test: atest RolesPersistenceTest Bug: 373469933 Change-Id: I428f1033e8c76959fea611070a79ac1c7b3eb6ac
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Role.java2
-rw-r--r--service/java/com/android/role/RoleService.java2
-rw-r--r--service/java/com/android/role/RoleUserState.java36
-rw-r--r--service/java/com/android/role/persistence/RolesPersistenceImpl.java22
-rw-r--r--service/java/com/android/role/persistence/RolesState.java42
-rw-r--r--service/proto/role_service.proto3
-rw-r--r--tests/apex/Android.bp2
-rw-r--r--tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt48
8 files changed, 149 insertions, 8 deletions
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java
index 570ef034a..67a37bdef 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java
@@ -447,6 +447,8 @@ public class Role {
if (!isAvailableByFeatureFlagAndSdkVersion()) {
return false;
}
+ // TODO(b/376133070): ensure that cross-user role is only available if also available for
+ // the profile-group's full user
if (mBehavior != null) {
return mBehavior.isAvailableAsUser(this, user, context);
}
diff --git a/service/java/com/android/role/RoleService.java b/service/java/com/android/role/RoleService.java
index 20250b4f6..b8e3ad8b1 100644
--- a/service/java/com/android/role/RoleService.java
+++ b/service/java/com/android/role/RoleService.java
@@ -176,6 +176,7 @@ public class RoleService extends SystemService implements RoleUserState.Callback
registerUserRemovedReceiver();
}
+ // TODO(b/375029649): enforce single active user for all cross-user roles
private void registerUserRemovedReceiver() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
@@ -191,6 +192,7 @@ public class RoleService extends SystemService implements RoleUserState.Callback
}, intentFilter, null, null);
}
+ // TODO(b/375029649): enforce single active user for all cross-user roles
@Override
public void onStart() {
publishBinderService(Context.ROLE_SERVICE, new Stub());
diff --git a/service/java/com/android/role/RoleUserState.java b/service/java/com/android/role/RoleUserState.java
index 81007d65e..cda7fcfa8 100644
--- a/service/java/com/android/role/RoleUserState.java
+++ b/service/java/com/android/role/RoleUserState.java
@@ -39,6 +39,7 @@ import com.android.role.persistence.RolesState;
import com.android.server.role.RoleServicePlatformHelper;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -96,6 +97,10 @@ class RoleUserState {
private ArraySet<String> mFallbackEnabledRoles = new ArraySet<>();
@GuardedBy("mLock")
+ @NonNull
+ private ArrayMap<String, Integer> mActiveUserIds = new ArrayMap<>();
+
+ @GuardedBy("mLock")
private boolean mWriteScheduled;
@GuardedBy("mLock")
@@ -449,9 +454,15 @@ class RoleUserState {
// Force a reconciliation on next boot if we are bypassing role qualification now.
String packagesHash = mBypassingRoleQualification ? null : mPackagesHash;
- roles = new RolesState(mVersion, packagesHash,
- (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked(),
- snapshotFallbackEnabledRoles());
+ if (com.android.permission.flags.Flags.crossUserRoleEnabled()) {
+ roles = new RolesState(mVersion, packagesHash,
+ (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked(),
+ snapshotFallbackEnabledRoles(), snapshotActiveUserIds());
+ } else {
+ roles = new RolesState(mVersion, packagesHash,
+ (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked(),
+ snapshotFallbackEnabledRoles());
+ }
}
mPersistence.writeForUser(roles, UserHandle.of(mUserId));
@@ -463,14 +474,17 @@ class RoleUserState {
Map<String, Set<String>> roles;
Set<String> fallbackEnabledRoles;
+ Map<String, Integer> activeUserIds;
if (roleState != null) {
mVersion = roleState.getVersion();
mPackagesHash = roleState.getPackagesHash();
roles = roleState.getRoles();
fallbackEnabledRoles = roleState.getFallbackEnabledRoles();
+ activeUserIds = roleState.getActiveUserIds();
} else {
roles = mPlatformHelper.getLegacyRoleState(mUserId);
fallbackEnabledRoles = roles.keySet();
+ activeUserIds = Collections.emptyMap();
}
mRoles.clear();
for (Map.Entry<String, Set<String>> entry : roles.entrySet()) {
@@ -480,6 +494,10 @@ class RoleUserState {
}
mFallbackEnabledRoles.clear();
mFallbackEnabledRoles.addAll(fallbackEnabledRoles);
+ mActiveUserIds.clear();
+ if (com.android.permission.flags.Flags.crossUserRoleEnabled()) {
+ mActiveUserIds.putAll(activeUserIds);
+ }
if (roleState == null) {
scheduleWriteFileLocked();
}
@@ -496,12 +514,14 @@ class RoleUserState {
int version;
String packagesHash;
ArrayMap<String, ArraySet<String>> roles;
+ ArrayMap<String, Integer> activeUserIds;
ArraySet<String> fallbackEnabledRoles;
synchronized (mLock) {
version = mVersion;
packagesHash = mPackagesHash;
roles = snapshotRolesLocked();
fallbackEnabledRoles = snapshotFallbackEnabledRoles();
+ activeUserIds = snapshotActiveUserIds();
}
long fieldToken = dumpOutputStream.start(fieldName, fieldId);
@@ -514,10 +534,14 @@ class RoleUserState {
String roleName = roles.keyAt(rolesIndex);
ArraySet<String> roleHolders = roles.valueAt(rolesIndex);
boolean fallbackEnabled = fallbackEnabledRoles.contains(roleName);
+ Integer activeUserId = activeUserIds.get(roleName);
long rolesToken = dumpOutputStream.start("roles", RoleUserStateProto.ROLES);
dumpOutputStream.write("name", RoleProto.NAME, roleName);
dumpOutputStream.write("fallback_enabled", RoleProto.FALLBACK_ENABLED, fallbackEnabled);
+ if (activeUserId != null) {
+ dumpOutputStream.write("active_user_id", RoleProto.ACTIVE_USER_ID, activeUserId);
+ }
int roleHoldersSize = roleHolders.size();
for (int roleHoldersIndex = 0; roleHoldersIndex < roleHoldersSize; roleHoldersIndex++) {
String roleHolder = roleHolders.valueAt(roleHoldersIndex);
@@ -563,6 +587,12 @@ class RoleUserState {
return new ArraySet<>(mFallbackEnabledRoles);
}
+ @GuardedBy("mLock")
+ @NonNull
+ private ArrayMap<String, Integer> snapshotActiveUserIds() {
+ return new ArrayMap<>(mActiveUserIds);
+ }
+
/**
* Destroy this user state and delete the corresponding file. Any pending writes to the file
* will be cancelled, and any future interaction with this state will throw an exception.
diff --git a/service/java/com/android/role/persistence/RolesPersistenceImpl.java b/service/java/com/android/role/persistence/RolesPersistenceImpl.java
index 220a8440b..8382d3632 100644
--- a/service/java/com/android/role/persistence/RolesPersistenceImpl.java
+++ b/service/java/com/android/role/persistence/RolesPersistenceImpl.java
@@ -67,6 +67,7 @@ public class RolesPersistenceImpl implements RolesPersistence {
private static final String ATTRIBUTE_VERSION = "version";
private static final String ATTRIBUTE_NAME = "name";
private static final String ATTRIBUTE_FALLBACK_ENABLED = "fallbackEnabled";
+ private static final String ATTRIBUTE_ACTIVE_USER_ID = "activeUserId";
private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash";
@VisibleForTesting
@@ -144,6 +145,7 @@ public class RolesPersistenceImpl implements RolesPersistence {
Map<String, Set<String>> roles = new ArrayMap<>();
Set<String> fallbackEnabledRoles = new ArraySet<>();
+ Map<String, Integer> activeUserIds = new ArrayMap<>();
int type;
int depth;
int innerDepth = parser.getDepth() + 1;
@@ -159,12 +161,23 @@ public class RolesPersistenceImpl implements RolesPersistence {
if (Boolean.parseBoolean(fallbackEnabled)) {
fallbackEnabledRoles.add(roleName);
}
+ if (com.android.permission.flags.Flags.crossUserRoleEnabled()) {
+ String activeUserId = parser.getAttributeValue(null, ATTRIBUTE_ACTIVE_USER_ID);
+ if (activeUserId != null) {
+ activeUserIds.put(roleName, Integer.parseInt(activeUserId));
+ }
+ }
Set<String> roleHolders = parseRoleHolders(parser);
roles.put(roleName, roleHolders);
}
}
- return new RolesState(version, packagesHash, roles, fallbackEnabledRoles);
+ if (com.android.permission.flags.Flags.crossUserRoleEnabled()) {
+ return new RolesState(version, packagesHash, roles, fallbackEnabledRoles,
+ activeUserIds);
+ } else {
+ return new RolesState(version, packagesHash, roles, fallbackEnabledRoles);
+ }
}
@NonNull
@@ -244,15 +257,22 @@ public class RolesPersistenceImpl implements RolesPersistence {
}
Set<String> fallbackEnabledRoles = roles.getFallbackEnabledRoles();
+ Map<String, Integer> activeUserIds = roles.getActiveUserIds();
for (Map.Entry<String, Set<String>> entry : roles.getRoles().entrySet()) {
String roleName = entry.getKey();
Set<String> roleHolders = entry.getValue();
boolean isFallbackEnabled = fallbackEnabledRoles.contains(roleName);
+ Integer activeUserId = com.android.permission.flags.Flags.crossUserRoleEnabled()
+ ? activeUserIds.get(roleName) : null;
serializer.startTag(null, TAG_ROLE);
serializer.attribute(null, ATTRIBUTE_NAME, roleName);
serializer.attribute(null, ATTRIBUTE_FALLBACK_ENABLED,
Boolean.toString(isFallbackEnabled));
+ if (activeUserId != null) {
+ serializer.attribute(
+ null, ATTRIBUTE_ACTIVE_USER_ID, Integer.toString(activeUserId));
+ }
serializeRoleHolders(serializer, roleHolders);
serializer.endTag(null, TAG_ROLE);
}
diff --git a/service/java/com/android/role/persistence/RolesState.java b/service/java/com/android/role/persistence/RolesState.java
index a189dd4c2..f1b3d8dfa 100644
--- a/service/java/com/android/role/persistence/RolesState.java
+++ b/service/java/com/android/role/persistence/RolesState.java
@@ -23,12 +23,13 @@ import android.annotation.SystemApi;
import android.annotation.SystemApi.Client;
import android.permission.flags.Flags;
+import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
- * State of all roles.
+ * State of all roles for a user.
*
* TODO(b/147914847): Remove @hide when it becomes the default.
* @hide
@@ -59,6 +60,12 @@ public final class RolesState {
private final Set<String> mFallbackEnabledRoles;
/**
+ * The active users for cross user roles.
+ */
+ @NonNull
+ private final Map<String, Integer> mActiveUserIds;
+
+ /**
* Create a new instance of this class.
*
* @param version the version of the roles
@@ -81,10 +88,27 @@ public final class RolesState {
@FlaggedApi(Flags.FLAG_SYSTEM_SERVER_ROLE_CONTROLLER_ENABLED)
public RolesState(int version, @Nullable String packagesHash,
@NonNull Map<String, Set<String>> roles, @NonNull Set<String> fallbackEnabledRoles) {
+ this(version, packagesHash, roles, fallbackEnabledRoles, Collections.emptyMap());
+ }
+
+ /**
+ * Create a new instance of this class.
+ *
+ * @param version the version of the roles
+ * @param packagesHash the hash of all packages in the system
+ * @param roles the roles
+ * @param fallbackEnabledRoles the roles with fallback enabled
+ * @param activeUserIds the active users for cross user roles
+ * @hide
+ */
+ public RolesState(int version, @Nullable String packagesHash,
+ @NonNull Map<String, Set<String>> roles, @NonNull Set<String> fallbackEnabledRoles,
+ @NonNull Map<String, Integer> activeUserIds) {
mVersion = version;
mPackagesHash = packagesHash;
mRoles = roles;
mFallbackEnabledRoles = fallbackEnabledRoles;
+ mActiveUserIds = activeUserIds;
}
/**
@@ -127,6 +151,17 @@ public final class RolesState {
return mFallbackEnabledRoles;
}
+ /**
+ * Get the active users for cross user roles.
+ *
+ * @return active users for cross user roles
+ * @hide
+ */
+ @NonNull
+ public Map<String, Integer> getActiveUserIds() {
+ return mActiveUserIds;
+ }
+
@Override
public boolean equals(Object object) {
if (this == object) {
@@ -139,11 +174,12 @@ public final class RolesState {
return mVersion == that.mVersion
&& Objects.equals(mPackagesHash, that.mPackagesHash)
&& Objects.equals(mRoles, that.mRoles)
- && Objects.equals(mFallbackEnabledRoles, that.mFallbackEnabledRoles);
+ && Objects.equals(mFallbackEnabledRoles, that.mFallbackEnabledRoles)
+ && Objects.equals(mActiveUserIds, that.mActiveUserIds);
}
@Override
public int hashCode() {
- return Objects.hash(mVersion, mPackagesHash, mRoles, mFallbackEnabledRoles);
+ return Objects.hash(mVersion, mPackagesHash, mRoles, mFallbackEnabledRoles, mActiveUserIds);
}
}
diff --git a/service/proto/role_service.proto b/service/proto/role_service.proto
index f982ead5b..fb1866d83 100644
--- a/service/proto/role_service.proto
+++ b/service/proto/role_service.proto
@@ -56,4 +56,7 @@ message RoleProto {
// Whether fallback holders are enabled for this role.
optional bool fallback_enabled = 3;
+
+ // The active user id of this cross user role.
+ optional int32 active_user_id = 4;
}
diff --git a/tests/apex/Android.bp b/tests/apex/Android.bp
index 18f1bea75..9dfbdf589 100644
--- a/tests/apex/Android.bp
+++ b/tests/apex/Android.bp
@@ -31,6 +31,8 @@ android_test {
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.test.ext.truth",
+ "com.android.permission.flags-aconfig-java",
+ "flag-junit",
"mockito-target-extended-minus-junit4",
],
jni_libs: [
diff --git a/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt b/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt
index 6500b3926..e9c93a33a 100644
--- a/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt
+++ b/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt
@@ -20,12 +20,18 @@ import android.content.ApexEnvironment
import android.content.Context
import android.os.Process
import android.os.UserHandle
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.platform.app.InstrumentationRegistry
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.permission.flags.Flags
import com.google.common.truth.Truth.assertThat
import java.io.File
import org.junit.After
+import org.junit.Assume.assumeFalse
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -49,11 +55,22 @@ class RolesPersistenceTest {
private val persistence = RolesPersistenceImpl {}
private val defaultRoles = mapOf(ROLE_NAME to setOf(HOLDER_1, HOLDER_2))
+ private val activeUserIds = mapOf(ROLE_NAME to USER_ID)
private val stateVersionUndefined = RolesState(VERSION_UNDEFINED, PACKAGE_HASH, defaultRoles)
private val stateVersionFallbackMigrated =
RolesState(VERSION_FALLBACK_MIGRATED, PACKAGE_HASH, defaultRoles, setOf(ROLE_NAME))
+ private val stateVersionActiveUserIds =
+ RolesState(
+ VERSION_ACTIVE_USER_IDS,
+ PACKAGE_HASH,
+ defaultRoles,
+ setOf(ROLE_NAME),
+ activeUserIds,
+ )
private val user = Process.myUserHandle()
+ @get:Rule val flagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
@Before
fun setUp() {
createMockDataDirectory()
@@ -84,16 +101,41 @@ class RolesPersistenceTest {
mockitoSession.finishMocking()
}
+ @RequiresFlagsDisabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED)
@Test
fun testWriteRead() {
+ assumeFalse(stateVersion == StateVersion.VERSION_ACTIVE_USER_IDS)
persistence.writeForUser(state, user)
val persistedState = persistence.readForUser(user)
assertThat(persistedState).isEqualTo(state)
}
+ @RequiresFlagsEnabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @Test
+ fun testWriteRead_supportsActiveUser() {
+ persistence.writeForUser(state, user)
+ val persistedState = persistence.readForUser(user)
+
+ assertThat(persistedState).isEqualTo(state)
+ }
+
+ @RequiresFlagsDisabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED)
@Test
fun testWriteCorruptReadFromReserveCopy() {
+ assumeFalse(stateVersion == StateVersion.VERSION_ACTIVE_USER_IDS)
+ persistence.writeForUser(state, user)
+ // Corrupt the primary file.
+ RolesPersistenceImpl.getFile(user)
+ .writeText("<roles version=\"-1\"><role name=\"com.foo.bar\"><holder")
+ val persistedState = persistence.readForUser(user)
+
+ assertThat(persistedState).isEqualTo(state)
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @Test
+ fun testWriteCorruptReadFromReserveCopy_supportsActiveUser() {
persistence.writeForUser(state, user)
// Corrupt the primary file.
RolesPersistenceImpl.getFile(user)
@@ -116,11 +158,13 @@ class RolesPersistenceTest {
when (stateVersion) {
StateVersion.VERSION_UNDEFINED -> stateVersionUndefined
StateVersion.VERSION_FALLBACK_MIGRATED -> stateVersionFallbackMigrated
+ StateVersion.VERSION_ACTIVE_USER_IDS -> stateVersionActiveUserIds
}
enum class StateVersion {
VERSION_UNDEFINED,
- VERSION_FALLBACK_MIGRATED
+ VERSION_FALLBACK_MIGRATED,
+ VERSION_ACTIVE_USER_IDS,
}
companion object {
@@ -130,10 +174,12 @@ class RolesPersistenceTest {
private const val VERSION_UNDEFINED = -1
private const val VERSION_FALLBACK_MIGRATED = 1
+ private const val VERSION_ACTIVE_USER_IDS = 2
private const val APEX_MODULE_NAME = "com.android.permission"
private const val PACKAGE_HASH = "packagesHash"
private const val ROLE_NAME = "roleName"
private const val HOLDER_1 = "holder1"
private const val HOLDER_2 = "holder2"
+ private const val USER_ID = 10
}
}