Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9e98d6d8 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes from topic "catalyst" into main

* changes:
  [Catalyst] Use Permissions for getRead/WritePermissions
  [Catalyst] Add protobuf support for Permissions
  [Catalyst] Support AND/OR combination for permissions
parents 46ff5dd8 02a3ca08
Loading
Loading
Loading
Loading
+162 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.settingslib.datastore

import android.content.Context
import android.content.pm.PackageManager.PERMISSION_GRANTED

/**
 * Class to manage permissions, which supports a combination of AND / OR.
 *
 * Samples:
 * - `Permissions.EMPTY`: no permission is required
 * - `Permissions.allOf(p1, p2) or p3 or Permissions.allOf(p4, p5)`
 * - `Permissions.anyOf(p1, p2) and p3 and Permissions.anyOf(p4, p5)`
 * - `Permissions.allOf(p1, p2) or (Permissions.allOf(p3, p4) and p5)`: ALWAYS add `()` explicitly
 *   when and/or operators are used at the same time.
 */
sealed class Permissions(vararg permissions: Any) {
    internal val permissions = mutableSetOf(*permissions)

    val size: Int
        get() = permissions.size

    override fun hashCode() = permissions.hashCode()

    override fun equals(other: Any?) =
        other is Permissions &&
            permissions == other.permissions &&
            (permissions.size == 1 || javaClass == other.javaClass)

    abstract fun check(context: Context, pid: Int, uid: Int): Boolean

    internal fun addForAnd(permission: Any): Permissions =
        when {
            // ensure empty permissions will never been modified
            permissions.isEmpty() -> (permission as? Permissions) ?: AllOfPermissions(permission)
            permission is Permissions && permission.permissions.isEmpty() -> this
            this is AllOfPermissions -> apply { and(permission) }
            permission is AllOfPermissions -> permission.also { it.and(this) }
            // anyOf(p1) and p2 => allOf(p1, p2)
            permissions.size == 1 && this is AnyOfPermissions && permission is String ->
                AllOfPermissions(permissions.first(), permission)
            // anyOf(p1) and anyOf(p2) => allOf(p1, p2)
            permissions.size == 1 &&
                permission is AnyOfPermissions &&
                permission.permissions.size == 1 ->
                AllOfPermissions(permissions.first(), permission.permissions.first())
            else -> AllOfPermissions(this, permission)
        }

    internal fun addForOr(permission: Any): Permissions =
        when {
            // ensure empty permissions will never been modified
            permissions.isEmpty() -> (permission as? Permissions) ?: AnyOfPermissions(permission)
            permission is Permissions && permission.permissions.isEmpty() -> this
            this is AnyOfPermissions -> apply { or(permission) }
            permission is AnyOfPermissions -> permission.also { it.or(this) }
            // allOf(p1) or p2 => anyOf(p1, p2)
            permissions.size == 1 && this is AllOfPermissions && permission is String ->
                AnyOfPermissions(permissions.first(), permission)
            // allOf(p1) or allOf(p2) => anyOf(p1, p2)
            permissions.size == 1 &&
                permission is AllOfPermissions &&
                permission.permissions.size == 1 ->
                AnyOfPermissions(permissions.first(), permission.permissions.first())
            else -> AnyOfPermissions(this, permission)
        }

    protected fun Any.check(context: Context, pid: Int, uid: Int) =
        when (this) {
            is String -> context.checkPermission(this, pid, uid) == PERMISSION_GRANTED
            else -> (this as Permissions).check(context, pid, uid)
        }

    fun forEach(action: (Any) -> Unit) {
        for (permission in permissions) action(permission)
    }

    companion object {
        /** Returns [Permissions] that requires all of the permissions. */
        fun allOf(vararg permissions: String): Permissions =
            if (permissions.isEmpty()) EMPTY else AllOfPermissions(*permissions)

        /** Returns [Permissions] that requires any of the permissions. */
        fun anyOf(vararg permissions: String): Permissions =
            if (permissions.isEmpty()) EMPTY else AnyOfPermissions(*permissions)

        /** No permission required. */
        val EMPTY: Permissions = AllOfPermissions()
    }
}

class AllOfPermissions internal constructor(vararg permissions: Any) : Permissions(*permissions) {

    override fun toString() = permissions.joinToString(prefix = "allOf(", postfix = ")")

    override fun check(context: Context, pid: Int, uid: Int): Boolean {
        // use for-loop explicitly instead of "all" extension for empty permissions
        for (permission in permissions) {
            if (!permission.check(context, pid, uid)) return false
        }
        return true
    }

    internal fun and(permission: Any) {
        when {
            // in-place merge to reduce the hierarchy
            permission is AllOfPermissions -> permissions.addAll(permission.permissions)
            // allOf(...) and anyOf(p) => allOf(..., p)
            permission is AnyOfPermissions && permission.permissions.size == 1 ->
                permissions.add(permission.permissions.first())

            else -> permissions.add(permission)
        }
    }
}

class AnyOfPermissions internal constructor(vararg permissions: Any) : Permissions(*permissions) {

    override fun toString() = permissions.joinToString(prefix = "anyOf(", postfix = ")")

    override fun check(context: Context, pid: Int, uid: Int): Boolean {
        // use for-loop explicitly instead of "any" extension for empty permissions
        for (permission in permissions) {
            if (permission.check(context, pid, uid)) return true
        }
        return permissions.isEmpty()
    }

    internal fun or(permission: Any) {
        when {
            // in-place merge to reduce the hierarchy
            permission is AnyOfPermissions -> permissions.addAll(permission.permissions)
            // anyOf(...) or allOf(p) => anyOf(..., p)
            permission is AllOfPermissions && permission.permissions.size == 1 ->
                permissions.add(permission.permissions.first())
            else -> permissions.add(permission)
        }
    }
}

infix fun Permissions.and(permission: String): Permissions = addForAnd(permission)

infix fun Permissions.and(permissions: Permissions): Permissions = addForAnd(permissions)

infix fun Permissions.or(permission: String): Permissions = addForOr(permission)

infix fun Permissions.or(permissions: Permissions): Permissions = addForOr(permissions)
+2 −2
Original line number Diff line number Diff line
@@ -85,9 +85,9 @@ class SettingsGlobalStore private constructor(contentResolver: ContentResolver)
                }

        /** Returns the required permissions to read [Global] settings. */
        fun getReadPermissions() = arrayOf<String>()
        fun getReadPermissions() = Permissions.EMPTY

        /** Returns the required permissions to write [Global] settings. */
        fun getWritePermissions() = arrayOf(Manifest.permission.WRITE_SECURE_SETTINGS)
        fun getWritePermissions() = Permissions.allOf(Manifest.permission.WRITE_SECURE_SETTINGS)
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -85,9 +85,9 @@ class SettingsSecureStore private constructor(contentResolver: ContentResolver)
                }

        /** Returns the required permissions to read [Secure] settings. */
        fun getReadPermissions() = arrayOf<String>()
        fun getReadPermissions() = Permissions.EMPTY

        /** Returns the required permissions to write [Secure] settings. */
        fun getWritePermissions() = arrayOf(Manifest.permission.WRITE_SECURE_SETTINGS)
        fun getWritePermissions() = Permissions.allOf(Manifest.permission.WRITE_SECURE_SETTINGS)
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -85,9 +85,9 @@ class SettingsSystemStore private constructor(contentResolver: ContentResolver)
                }

        /** Returns the required permissions to read [System] settings. */
        fun getReadPermissions() = arrayOf<String>()
        fun getReadPermissions() = Permissions.EMPTY

        /** Returns the required permissions to write [System] settings. */
        fun getWritePermissions() = arrayOf(Manifest.permission.WRITE_SETTINGS)
        fun getWritePermissions() = Permissions.allOf(Manifest.permission.WRITE_SETTINGS)
    }
}
+327 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.settingslib.datastore

import android.content.Context
import android.content.ContextWrapper
import android.content.pm.PackageManager.PERMISSION_DENIED
import android.content.pm.PackageManager.PERMISSION_GRANTED
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.datastore.Permissions.Companion.EMPTY
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class PermissionsTest {
    private val context: Context = ApplicationProvider.getApplicationContext()

    @Test
    fun empty() {
        assertThat(Permissions.allOf()).isSameInstanceAs(EMPTY)
        assertThat(Permissions.anyOf()).isSameInstanceAs(EMPTY)
        assertThat(EMPTY.check(context, 0, 0)).isTrue()
        assertThat((EMPTY and "a").permissions).containsExactly("a")
        assertThat((EMPTY or "a").permissions).containsExactly("a")
    }

    @Test
    fun allOf_op_empty() {
        val allOf = Permissions.allOf("a")
        assertThat(allOf and EMPTY).isSameInstanceAs(allOf)
        assertThat(EMPTY and allOf).isSameInstanceAs(allOf)
        assertThat(EMPTY or allOf).isSameInstanceAs(allOf)
        assertThat(allOf or EMPTY).isSameInstanceAs(allOf)
    }

    @Test
    fun anyOf_op_empty() {
        val anyOf = Permissions.anyOf("a")
        assertThat(anyOf and EMPTY).isSameInstanceAs(anyOf)
        assertThat(EMPTY and anyOf).isSameInstanceAs(anyOf)
        assertThat(EMPTY or anyOf).isSameInstanceAs(anyOf)
        assertThat(anyOf or EMPTY).isSameInstanceAs(anyOf)
    }

    @Test
    fun allOf1_and_allOf1() {
        val allOf = Permissions.allOf("a")
        assertThat(allOf and Permissions.allOf("b")).isSameInstanceAs(allOf)
        assertThat(allOf.permissions).containsExactly("a", "b")
    }

    @Test
    fun allOf1_or_allOf1() {
        val merged = Permissions.allOf("a") or Permissions.allOf("b")
        assertThat(merged.permissions).containsExactly("a", "b")
        assertThat(merged).isInstanceOf(AnyOfPermissions::class.java)
    }

    @Test
    fun allOf1_and_anyOf1() {
        val allOf = Permissions.allOf("a")
        val anyOf = Permissions.anyOf("b")
        assertThat(allOf and anyOf).isSameInstanceAs(allOf)
        assertThat(allOf.permissions).containsExactly("a", "b")
    }

    @Test
    fun allOf1_or_anyOf1() {
        val allOf = Permissions.allOf("a")
        val anyOf = Permissions.anyOf("b")
        assertThat(allOf or anyOf).isSameInstanceAs(anyOf)
        assertThat(anyOf.permissions).containsExactly("a", "b")
    }

    @Test
    fun anyOf1_and_allOf1() {
        val anyOf = Permissions.anyOf("a")
        val allOf = Permissions.allOf("b")
        assertThat(anyOf and allOf).isSameInstanceAs(allOf)
        assertThat(allOf.permissions).containsExactly("a", "b")
    }

    @Test
    fun anyOf1_or_allOf1() {
        val anyOf = Permissions.anyOf("a")
        val allOf = Permissions.allOf("b")
        assertThat(anyOf or allOf).isSameInstanceAs(anyOf)
        assertThat(anyOf.permissions).containsExactly("a", "b")
    }

    @Test
    fun anyOf1_and_anyOf1() {
        val merged = Permissions.anyOf("a") and Permissions.anyOf("b")
        assertThat(merged.permissions).containsExactly("a", "b")
        assertThat(merged).isInstanceOf(AllOfPermissions::class.java)
    }

    @Test
    fun anyOf1_or_anyOf1() {
        val anyOf = Permissions.anyOf("a")
        assertThat(anyOf or Permissions.anyOf("b")).isSameInstanceAs(anyOf)
        assertThat(anyOf.permissions).containsExactly("a", "b")
    }

    @Test
    fun allOf1_and_anyOf2() {
        val allOf = Permissions.allOf("a")
        val anyOf = Permissions.anyOf("a", "b")
        assertThat(allOf and anyOf).isSameInstanceAs(allOf)
        assertThat(allOf.permissions).containsExactly("a", anyOf)
    }

    @Test
    fun allOf1_or_anyOf2() {
        val allOf = Permissions.allOf("a")
        val anyOf = Permissions.anyOf("a", "b")
        assertThat(allOf and anyOf).isSameInstanceAs(allOf)
        assertThat(allOf.permissions).containsExactly("a", anyOf)
    }

    @Test
    fun allOf2_and_anyOf1() {
        val allOf = Permissions.allOf("a", "b")
        val anyOf = Permissions.anyOf("c")
        assertThat(allOf and anyOf).isSameInstanceAs(allOf)
        assertThat(allOf.permissions).containsExactly("a", "b", "c")
    }

    @Test
    fun allOf2_or_anyOf1() {
        val allOf = Permissions.allOf("a", "b")
        val anyOf = Permissions.anyOf("b")
        assertThat(allOf or anyOf).isSameInstanceAs(anyOf)
        assertThat(anyOf.permissions).containsExactly("b", allOf)
    }

    @Test
    fun anyOf1_and_allOf2() {
        val anyOf = Permissions.anyOf("c")
        val allOf = Permissions.allOf("a", "b")
        assertThat(anyOf and allOf).isSameInstanceAs(allOf)
        assertThat(allOf.permissions).containsExactly("a", "b", "c")
    }

    @Test
    fun anyOf1_or_allOf2() {
        val anyOf = Permissions.anyOf("a")
        val allOf = Permissions.allOf("a", "b")
        assertThat(anyOf or allOf).isSameInstanceAs(anyOf)
        assertThat(anyOf.permissions).containsExactly("a", allOf)
    }

    @Test
    fun anyOf2_and_allOf1() {
        val anyOf = Permissions.anyOf("a", "b")
        val allOf = Permissions.allOf("a")
        assertThat(anyOf and allOf).isSameInstanceAs(allOf)
        assertThat(allOf.permissions).containsExactly("a", anyOf)
    }

    @Test
    fun anyOf2_or_allOf1() {
        val anyOf = Permissions.anyOf("a", "b")
        val allOf = Permissions.allOf("c")
        assertThat(anyOf or allOf).isSameInstanceAs(anyOf)
        assertThat(anyOf.permissions).containsExactly("a", "b", "c")
    }

    @Test
    fun allOf2_and_allOf2() {
        val allOf = Permissions.allOf("a", "b")
        assertThat(allOf and Permissions.allOf("a", "c")).isSameInstanceAs(allOf)
        assertThat(allOf.permissions).containsExactly("a", "b", "c")
    }

    @Test
    fun allOf2_or_allOf2() {
        val allOf1 = Permissions.allOf("a", "b")
        val allOf2 = Permissions.allOf("a", "c")
        assertThat((allOf1 or allOf2).permissions).containsExactly(allOf1, allOf2)
    }

    @Test
    fun allOf2_and_anyOf2() {
        val allOf = Permissions.allOf("a", "b")
        val anyOf = Permissions.anyOf("a", "c")
        assertThat(allOf and anyOf).isSameInstanceAs(allOf)
        assertThat(allOf.permissions).containsExactly("a", "b", anyOf)
    }

    @Test
    fun allOf2_or_anyOf2() {
        val allOf = Permissions.allOf("a", "b")
        val anyOf = Permissions.anyOf("a", "c")
        assertThat(allOf or anyOf).isSameInstanceAs(anyOf)
        assertThat(anyOf.permissions).containsExactly("a", "c", allOf)
    }

    @Test
    fun anyOf2_and_allOf2() {
        val anyOf = Permissions.anyOf("a", "b")
        val allOf = Permissions.allOf("a", "c")
        assertThat(anyOf and allOf).isSameInstanceAs(allOf)
        assertThat(allOf.permissions).containsExactly("a", "c", anyOf)
    }

    @Test
    fun anyOf2_or_allOf2() {
        val anyOf = Permissions.anyOf("a", "b")
        val allOf = Permissions.allOf("a", "c")
        assertThat(anyOf or allOf).isSameInstanceAs(anyOf)
        assertThat(anyOf.permissions).containsExactly("a", "b", allOf)
    }

    @Test
    fun anyOf2_and_anyOf2() {
        val anyOf1 = Permissions.anyOf("a", "b")
        val anyOf2 = Permissions.anyOf("a", "c")
        assertThat((anyOf1 and anyOf2).permissions).containsExactly(anyOf1, anyOf2)
    }

    @Test
    fun anyOf2_or_anyOf2() {
        val anyOf = Permissions.anyOf("a", "b")
        assertThat(anyOf or Permissions.anyOf("a", "c")).isSameInstanceAs(anyOf)
        assertThat(anyOf.permissions).containsExactly("a", "b", "c")
    }

    @Test
    fun check_allOf() {
        val permissions = Permissions.allOf("a", "b")
        assertThat(permissions.check(PermissionsContext(setOf()), 0, 0)).isFalse()
        assertThat(permissions.check(PermissionsContext(setOf("a")), 0, 0)).isFalse()
        assertThat(permissions.check(PermissionsContext(setOf("b")), 0, 0)).isFalse()
        assertThat(permissions.check(PermissionsContext(setOf("a", "b")), 0, 0)).isTrue()
    }

    @Test
    fun check_allOf_mixed() {
        val permissions = Permissions.allOf("a", "b") or "c"
        assertThat(permissions.permissions).hasSize(2)
        assertThat(permissions.check(PermissionsContext(setOf()), 0, 0)).isFalse()
        assertThat(permissions.check(PermissionsContext(setOf("a")), 0, 0)).isFalse()
        assertThat(permissions.check(PermissionsContext(setOf("b")), 0, 0)).isFalse()
        assertThat(permissions.check(PermissionsContext(setOf("a", "b")), 0, 0)).isTrue()
        assertThat(permissions.check(PermissionsContext(setOf("c")), 0, 0)).isTrue()
        assertThat(permissions.check(PermissionsContext(setOf("a", "c")), 0, 0)).isTrue()
        assertThat(permissions.check(PermissionsContext(setOf("b", "c")), 0, 0)).isTrue()
        assertThat(permissions.check(PermissionsContext(setOf("a", "b", "c")), 0, 0)).isTrue()
    }

    @Test
    fun check_anyOf() {
        val permissions = Permissions.anyOf("a", "b")
        assertThat(permissions.check(PermissionsContext(setOf()), 0, 0)).isFalse()
        assertThat(permissions.check(PermissionsContext(setOf("a")), 0, 0)).isTrue()
        assertThat(permissions.check(PermissionsContext(setOf("b")), 0, 0)).isTrue()
        assertThat(permissions.check(PermissionsContext(setOf("a", "b")), 0, 0)).isTrue()
    }

    @Test
    fun check_anyOf_mixed() {
        val permissions = Permissions.anyOf("a", "b") and "c"
        assertThat(permissions.permissions).hasSize(2)
        assertThat(permissions.check(PermissionsContext(setOf()), 0, 0)).isFalse()
        assertThat(permissions.check(PermissionsContext(setOf("a")), 0, 0)).isFalse()
        assertThat(permissions.check(PermissionsContext(setOf("b")), 0, 0)).isFalse()
        assertThat(permissions.check(PermissionsContext(setOf("a", "b")), 0, 0)).isFalse()
        assertThat(permissions.check(PermissionsContext(setOf("c")), 0, 0)).isFalse()
        assertThat(permissions.check(PermissionsContext(setOf("a", "c")), 0, 0)).isTrue()
        assertThat(permissions.check(PermissionsContext(setOf("b", "c")), 0, 0)).isTrue()
        assertThat(permissions.check(PermissionsContext(setOf("a", "b", "c")), 0, 0)).isTrue()
    }

    @Test
    fun equals() {
        assertThat(Permissions.allOf("a")).isEqualTo(Permissions.allOf("a"))
        assertThat(Permissions.allOf("a")).isEqualTo(Permissions.anyOf("a"))
        assertThat(Permissions.anyOf("a")).isEqualTo(Permissions.allOf("a"))
        assertThat(Permissions.anyOf("a")).isEqualTo(Permissions.anyOf("a"))

        assertThat(Permissions.anyOf("a") and "b").isEqualTo(Permissions.allOf("a", "b"))
        assertThat(Permissions.allOf("a") or "b").isEqualTo(Permissions.anyOf("a", "b"))
        assertThat(Permissions.allOf("a") and "a").isEqualTo(Permissions.allOf("a"))
        assertThat(Permissions.anyOf("a") or "a").isEqualTo(Permissions.anyOf("a"))

        assertThat(Permissions.allOf("a", "c") and Permissions.allOf("c", "b"))
            .isEqualTo(Permissions.allOf("a", "b", "c"))
        assertThat(Permissions.anyOf("a", "c") or Permissions.anyOf("c", "b"))
            .isEqualTo(Permissions.anyOf("a", "b", "c"))

        assertThat(Permissions.allOf("a", "c") and Permissions.allOf("c", "b"))
            .isEqualTo(Permissions.allOf("b", "c") and Permissions.allOf("c", "a"))
        assertThat(Permissions.anyOf("a", "c") or Permissions.anyOf("c", "b"))
            .isEqualTo(Permissions.anyOf("b", "c") or Permissions.anyOf("c", "a"))
    }

    @Test
    fun notEquals() {
        assertThat(Permissions.allOf("a")).isNotEqualTo(Permissions.allOf("a", "b"))
        assertThat(Permissions.anyOf("a")).isNotEqualTo(Permissions.anyOf("a", "b"))
        assertThat(Permissions.allOf("a", "b")).isNotEqualTo(Permissions.anyOf("a", "b"))
    }
}

private class PermissionsContext(private val granted: Set<String>) :
    ContextWrapper(ApplicationProvider.getApplicationContext()) {

    override fun checkPermission(permission: String, pid: Int, uid: Int) =
        if (permission in granted) PERMISSION_GRANTED else PERMISSION_DENIED
}
Loading