Trust extension by repo (#570)

This commit is contained in:
AntsyLich 2024-05-04 16:26:45 +06:00 committed by GitHub
parent 21145144cd
commit 70cd688ac2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 35 additions and 27 deletions

View file

@ -179,7 +179,7 @@ class DomainModule : InjektModule {
addFactory { ToggleLanguage(get()) } addFactory { ToggleLanguage(get()) }
addFactory { ToggleSource(get()) } addFactory { ToggleSource(get()) }
addFactory { ToggleSourcePin(get()) } addFactory { ToggleSourcePin(get()) }
addFactory { TrustExtension(get()) } addFactory { TrustExtension(get(), get()) }
addSingletonFactory<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) } addSingletonFactory<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) }
addFactory { ExtensionRepoService(get(), get()) } addFactory { ExtensionRepoService(get(), get()) }

View file

@ -3,15 +3,18 @@ package eu.kanade.domain.extension.interactor
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import androidx.core.content.pm.PackageInfoCompat import androidx.core.content.pm.PackageInfoCompat
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
import tachiyomi.core.common.preference.getAndSet import tachiyomi.core.common.preference.getAndSet
class TrustExtension( class TrustExtension(
private val extensionRepoRepository: ExtensionRepoRepository,
private val preferences: SourcePreferences, private val preferences: SourcePreferences,
) { ) {
fun isTrusted(pkgInfo: PackageInfo, signatureHash: String): Boolean { suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List<String>): Boolean {
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:$signatureHash" val trustedFingerprints = extensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet()
return key in preferences.trustedExtensions().get() val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:${fingerprints.last()}"
return trustedFingerprints.any { fingerprints.contains(it) } || key in preferences.trustedExtensions().get()
} }
fun trust(pkgName: String, versionCode: Long, signatureHash: String) { fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
@ -19,9 +22,7 @@ class TrustExtension(
// Remove previously trusted versions // Remove previously trusted versions
val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet() val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet()
removed.also { removed.also { it += "$pkgName:$versionCode:$signatureHash" }
it += "$pkgName:$versionCode:$signatureHash"
}
} }
} }

View file

@ -264,7 +264,7 @@ class ExtensionManager(
* *
* @param extension the extension to trust * @param extension the extension to trust
*/ */
fun trust(extension: Extension.Untrusted) { suspend fun trust(extension: Extension.Untrusted) {
_untrustedExtensionsMapFlow.value[extension.pkgName] ?: return _untrustedExtensionsMapFlow.value[extension.pkgName] ?: return
trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash) trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash)

View file

@ -9,6 +9,9 @@ import androidx.core.content.ContextCompat
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.LoadResult import eu.kanade.tachiyomi.extension.model.LoadResult
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
@ -20,16 +23,12 @@ import tachiyomi.core.common.util.system.logcat
*/ */
internal class ExtensionInstallReceiver(private val listener: Listener) : BroadcastReceiver() { internal class ExtensionInstallReceiver(private val listener: Listener) : BroadcastReceiver() {
/** val scope = CoroutineScope(SupervisorJob())
* Registers this broadcast receiver
*/
fun register(context: Context) { fun register(context: Context) {
ContextCompat.registerReceiver(context, this, filter, ContextCompat.RECEIVER_NOT_EXPORTED) ContextCompat.registerReceiver(context, this, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
} }
/**
* Returns the intent filter this receiver should subscribe to.
*/
private val filter = IntentFilter().apply { private val filter = IntentFilter().apply {
addAction(Intent.ACTION_PACKAGE_ADDED) addAction(Intent.ACTION_PACKAGE_ADDED)
addAction(Intent.ACTION_PACKAGE_REPLACED) addAction(Intent.ACTION_PACKAGE_REPLACED)
@ -51,19 +50,23 @@ internal class ExtensionInstallReceiver(private val listener: Listener) : Broadc
Intent.ACTION_PACKAGE_ADDED, ACTION_EXTENSION_ADDED -> { Intent.ACTION_PACKAGE_ADDED, ACTION_EXTENSION_ADDED -> {
if (isReplacing(intent)) return if (isReplacing(intent)) return
scope.launch {
when (val result = getExtensionFromIntent(context, intent)) { when (val result = getExtensionFromIntent(context, intent)) {
is LoadResult.Success -> listener.onExtensionInstalled(result.extension) is LoadResult.Success -> listener.onExtensionInstalled(result.extension)
is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension)
else -> {} else -> {}
} }
} }
}
Intent.ACTION_PACKAGE_REPLACED, ACTION_EXTENSION_REPLACED -> { Intent.ACTION_PACKAGE_REPLACED, ACTION_EXTENSION_REPLACED -> {
scope.launch {
when (val result = getExtensionFromIntent(context, intent)) { when (val result = getExtensionFromIntent(context, intent)) {
is LoadResult.Success -> listener.onExtensionUpdated(result.extension) is LoadResult.Success -> listener.onExtensionUpdated(result.extension)
is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension)
else -> {} else -> {}
} }
} }
}
Intent.ACTION_PACKAGE_REMOVED, ACTION_EXTENSION_REMOVED -> { Intent.ACTION_PACKAGE_REMOVED, ACTION_EXTENSION_REMOVED -> {
if (isReplacing(intent)) return if (isReplacing(intent)) return
@ -90,7 +93,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) : Broadc
* @param context The application context. * @param context The application context.
* @param intent The intent containing the package name of the extension. * @param intent The intent containing the package name of the extension.
*/ */
private fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult { private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult {
val pkgName = getPackageNameFromIntent(intent) val pkgName = getPackageNameFromIntent(intent)
if (pkgName == null) { if (pkgName == null) {
logcat(LogPriority.WARN) { "Package name not found" } logcat(LogPriority.WARN) { "Package name not found" }

View file

@ -172,7 +172,7 @@ internal object ExtensionLoader {
* Attempts to load an extension from the given package name. It checks if the extension * Attempts to load an extension from the given package name. It checks if the extension
* contains the required feature flag before trying to load it. * contains the required feature flag before trying to load it.
*/ */
fun loadExtensionFromPkgName(context: Context, pkgName: String): LoadResult { suspend fun loadExtensionFromPkgName(context: Context, pkgName: String): LoadResult {
val extensionPackage = getExtensionInfoFromPkgName(context, pkgName) val extensionPackage = getExtensionInfoFromPkgName(context, pkgName)
if (extensionPackage == null) { if (extensionPackage == null) {
logcat(LogPriority.ERROR) { "Extension package is not found ($pkgName)" } logcat(LogPriority.ERROR) { "Extension package is not found ($pkgName)" }
@ -223,7 +223,8 @@ internal object ExtensionLoader {
* @param context The application context. * @param context The application context.
* @param extensionInfo The extension to load. * @param extensionInfo The extension to load.
*/ */
private fun loadExtension(context: Context, extensionInfo: ExtensionInfo): LoadResult { @Suppress("LongMethod", "CyclomaticComplexMethod", "ReturnCount")
private suspend fun loadExtension(context: Context, extensionInfo: ExtensionInfo): LoadResult {
val pkgManager = context.packageManager val pkgManager = context.packageManager
val pkgInfo = extensionInfo.packageInfo val pkgInfo = extensionInfo.packageInfo
val appInfo = pkgInfo.applicationInfo val appInfo = pkgInfo.applicationInfo
@ -252,7 +253,7 @@ internal object ExtensionLoader {
if (signatures.isNullOrEmpty()) { if (signatures.isNullOrEmpty()) {
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" } logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
return LoadResult.Error return LoadResult.Error
} else if (!trustExtension.isTrusted(pkgInfo, signatures.last())) { } else if (!trustExtension.isTrusted(pkgInfo, signatures)) {
val extension = Extension.Untrusted( val extension = Extension.Untrusted(
extName, extName,
pkgName, pkgName,

View file

@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -196,8 +197,10 @@ class ExtensionsScreenModel(
} }
fun trustExtension(extension: Extension.Untrusted) { fun trustExtension(extension: Extension.Untrusted) {
screenModelScope.launch {
extensionManager.trust(extension) extensionManager.trust(extension)
} }
}
@Immutable @Immutable
data class State( data class State(