blob: 215052da67b20a5fdde4db0b43871bbc49e343bd [file] [log] [blame]
/*
* Copyright (C) 2021 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.access
import android.util.Log
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
import com.android.permission.access.appop.PackageAppOpPolicy
import com.android.permission.access.appop.UidAppOpPolicy
import com.android.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.permission.access.external.PackageState
import com.android.permission.access.permission.UidPermissionPolicy
import com.android.permission.access.util.forEachTag
import com.android.permission.access.util.tag
import com.android.permission.access.util.tagName
class AccessPolicy private constructor(
private val schemePolicies: IndexedMap<String, IndexedMap<String, SchemePolicy>>
) {
constructor() : this(
IndexedMap<String, IndexedMap<String, SchemePolicy>>().apply {
fun addPolicy(policy: SchemePolicy) =
getOrPut(policy.subjectScheme) { IndexedMap() }.put(policy.objectScheme, policy)
addPolicy(UidPermissionPolicy())
addPolicy(UidAppOpPolicy())
addPolicy(PackageAppOpPolicy())
}
)
fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int =
getSchemePolicy(subject, `object`).getDecision(subject, `object`, state)
fun setDecision(
subject: AccessUri,
`object`: AccessUri,
decision: Int,
oldState: AccessState,
newState: AccessState
) {
getSchemePolicy(subject, `object`)
.setDecision(subject, `object`, decision, oldState, newState)
}
private fun getSchemePolicy(subject: AccessUri, `object`: AccessUri): SchemePolicy =
checkNotNull(schemePolicies[subject.scheme]?.get(`object`.scheme)) {
"Scheme policy for subject=$subject object=$`object` does not exist"
}
fun onUserAdded(userId: Int, oldState: AccessState, newState: AccessState) {
newState.systemState.userIds += userId
newState.userStates[userId] = UserState()
forEachSchemePolicy { it.onUserAdded(userId, oldState, newState) }
}
fun onUserRemoved(userId: Int, oldState: AccessState, newState: AccessState) {
newState.systemState.userIds -= userId
newState.userStates -= userId
forEachSchemePolicy { it.onUserRemoved(userId, oldState, newState) }
}
fun onPackageAdded(packageState: PackageState, oldState: AccessState, newState: AccessState) {
var isAppIdAdded = false
newState.systemState.apply {
packageStates[packageState.packageName] = packageState
appIds.getOrPut(packageState.appId) {
isAppIdAdded = true
IndexedListSet()
}.add(packageState.packageName)
}
if (isAppIdAdded) {
forEachSchemePolicy { it.onAppIdAdded(packageState.appId, oldState, newState) }
}
forEachSchemePolicy { it.onPackageAdded(packageState, oldState, newState) }
}
fun onPackageRemoved(packageState: PackageState, oldState: AccessState, newState: AccessState) {
var isAppIdRemoved = false
newState.systemState.apply {
packageStates -= packageState.packageName
appIds.apply appIds@{
this[packageState.appId]?.apply {
this -= packageState.packageName
if (isEmpty()) {
this@appIds -= packageState.appId
isAppIdRemoved = true
}
}
}
}
forEachSchemePolicy { it.onPackageRemoved(packageState, oldState, newState) }
if (isAppIdRemoved) {
forEachSchemePolicy { it.onAppIdRemoved(packageState.appId, oldState, newState) }
}
}
fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {
forEachTag {
when (tagName) {
TAG_ACCESS -> {
forEachTag {
forEachSchemePolicy {
with(it) { this@parseSystemState.parseSystemState(systemState) }
}
}
}
else -> Log.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state")
}
}
}
fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) {
tag(TAG_ACCESS) {
forEachSchemePolicy {
with(it) { this@serializeSystemState.serializeSystemState(systemState) }
}
}
}
fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {
forEachTag {
when (tagName) {
TAG_ACCESS -> {
forEachTag {
forEachSchemePolicy {
with(it) { this@parseUserState.parseUserState(userId, userState) }
}
}
}
else -> {
Log.w(
LOG_TAG,
"Ignoring unknown tag $tagName when parsing user state for user $userId"
)
}
}
}
}
fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {
tag(TAG_ACCESS) {
forEachSchemePolicy {
with(it) { this@serializeUserState.serializeUserState(userId, userState) }
}
}
}
private inline fun forEachSchemePolicy(action: (SchemePolicy) -> Unit) {
schemePolicies.forEachValueIndexed { _, objectSchemePolicies ->
objectSchemePolicies.forEachValueIndexed { _, schemePolicy ->
action(schemePolicy)
}
}
}
companion object {
private val LOG_TAG = AccessPolicy::class.java.simpleName
private const val TAG_ACCESS = "access"
}
}
abstract class SchemePolicy {
@Volatile
private var onDecisionChangedListeners = IndexedListSet<OnDecisionChangedListener>()
private val onDecisionChangedListenersLock = Any()
abstract val subjectScheme: String
abstract val objectScheme: String
abstract fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int
abstract fun setDecision(
subject: AccessUri,
`object`: AccessUri,
decision: Int,
oldState: AccessState,
newState: AccessState
)
fun addOnDecisionChangedListener(listener: OnDecisionChangedListener) {
synchronized(onDecisionChangedListenersLock) {
onDecisionChangedListeners = onDecisionChangedListeners + listener
}
}
fun removeOnDecisionChangedListener(listener: OnDecisionChangedListener) {
synchronized(onDecisionChangedListenersLock) {
onDecisionChangedListeners = onDecisionChangedListeners - listener
}
}
protected fun notifyOnDecisionChangedListeners(
subject: AccessUri,
`object`: AccessUri,
oldDecision: Int,
newDecision: Int
) {
val listeners = onDecisionChangedListeners
listeners.forEachIndexed { _, it ->
it.onDecisionChanged(subject, `object`, oldDecision, newDecision)
}
}
open fun onUserAdded(userId: Int, oldState: AccessState, newState: AccessState) {}
open fun onUserRemoved(userId: Int, oldState: AccessState, newState: AccessState) {}
open fun onAppIdAdded(appId: Int, oldState: AccessState, newState: AccessState) {}
open fun onAppIdRemoved(appId: Int, oldState: AccessState, newState: AccessState) {}
open fun onPackageAdded(
packageState: PackageState,
oldState: AccessState,
newState: AccessState
) {}
open fun onPackageRemoved(
packageState: PackageState,
oldState: AccessState,
newState: AccessState
) {}
open fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {}
open fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) {}
open fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {}
open fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {}
fun interface OnDecisionChangedListener {
fun onDecisionChanged(
subject: AccessUri,
`object`: AccessUri,
oldDecision: Int,
newDecision: Int
)
}
}