Do not go through a fragment for auth

This commit is contained in:
topjohnwu 2023-10-17 17:38:31 -07:00
parent 678c07fff5
commit e483d6befe
12 changed files with 60 additions and 132 deletions

View File

@ -156,7 +156,12 @@ object Config : PreferenceConfig, DBConfig {
var rootMode by dbSettings(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB) var rootMode by dbSettings(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB)
var suMntNamespaceMode by dbSettings(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER) var suMntNamespaceMode by dbSettings(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER)
var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY) var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY)
var suBiometric by dbSettings(Key.SU_BIOMETRIC, false) private var suBiometric by dbSettings(Key.SU_BIOMETRIC, false)
var userAuth
get() = Info.isDeviceSecure && suBiometric
set(value) {
suBiometric = value
}
var zygisk by dbSettings(Key.ZYGISK, false) var zygisk by dbSettings(Key.ZYGISK, false)
var denyList by BoolDBPropertyNoWrite(Key.DENYLIST, false) var denyList by BoolDBPropertyNoWrite(Key.DENYLIST, false)
var suManager by dbStrings(Key.SU_MANAGER, "", true) var suManager by dbStrings(Key.SU_MANAGER, "", true)

View File

@ -1,7 +1,9 @@
package com.topjohnwu.magisk.core package com.topjohnwu.magisk.core
import android.app.KeyguardManager
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.StubApk import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.ktx.getProperty import com.topjohnwu.magisk.core.ktx.getProperty
import com.topjohnwu.magisk.core.model.UpdateInfo import com.topjohnwu.magisk.core.model.UpdateInfo
import com.topjohnwu.magisk.core.repository.NetworkService import com.topjohnwu.magisk.core.repository.NetworkService
@ -46,6 +48,9 @@ object Info {
|| Config.suMultiuserMode == Config.Value.MULTIUSER_MODE_USER) || Config.suMultiuserMode == Config.Value.MULTIUSER_MODE_USER)
} }
val isDeviceSecure get() =
AppContext.getSystemService(KeyguardManager::class.java).isDeviceSecure
private fun loadState(): Env { private fun loadState(): Env {
val v = fastCmd("magisk -v").split(":".toRegex()) val v = fastCmd("magisk -v").split(":".toRegex())
return Env( return Env(

View File

@ -20,6 +20,7 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.isRunningAsStub import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.reflectField import com.topjohnwu.magisk.core.ktx.reflectField
import com.topjohnwu.magisk.core.ktx.toast import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.utils.RequestAuthentication
import com.topjohnwu.magisk.core.utils.RequestInstall import com.topjohnwu.magisk.core.utils.RequestInstall
import com.topjohnwu.magisk.core.wrap import com.topjohnwu.magisk.core.wrap
@ -43,6 +44,12 @@ abstract class BaseActivity : AppCompatActivity() {
installCallback = null installCallback = null
} }
var authenticateCallback: ((Boolean) -> Unit)? = null
val requestAuthenticate = registerForActivityResult(RequestAuthentication()) {
authenticateCallback?.invoke(it)
authenticateCallback = null
}
private var contentCallback: ContentResultCallback? = null private var contentCallback: ContentResultCallback? = null
private val getContent = registerForActivityResult(GetContent()) { private val getContent = registerForActivityResult(GetContent()) {
if (it != null) contentCallback?.onActivityResult(it) if (it != null) contentCallback?.onActivityResult(it)

View File

@ -12,7 +12,6 @@ import com.topjohnwu.magisk.core.data.magiskdb.StringDao
import com.topjohnwu.magisk.core.ktx.deviceProtectedContext import com.topjohnwu.magisk.core.ktx.deviceProtectedContext
import com.topjohnwu.magisk.core.repository.LogRepository import com.topjohnwu.magisk.core.repository.LogRepository
import com.topjohnwu.magisk.core.repository.NetworkService import com.topjohnwu.magisk.core.repository.NetworkService
import com.topjohnwu.magisk.core.utils.BiometricHelper
import io.noties.markwon.Markwon import io.noties.markwon.Markwon
import io.noties.markwon.utils.NoCopySpannableFactory import io.noties.markwon.utils.NoCopySpannableFactory
@ -24,7 +23,6 @@ object ServiceLocator {
lateinit var context: Context lateinit var context: Context
val deContext by lazy { context.deviceProtectedContext } val deContext by lazy { context.deviceProtectedContext }
val timeoutPrefs by lazy { deContext.getSharedPreferences("su_timeout", 0) } val timeoutPrefs by lazy { deContext.getSharedPreferences("su_timeout", 0) }
val biometrics by lazy { BiometricHelper(context) }
// Database // Database
val policyDB = PolicyDao() val policyDB = PolicyDao()

View File

@ -1,59 +0,0 @@
package com.topjohnwu.magisk.core.utils
import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.topjohnwu.magisk.core.Config
class BiometricHelper(context: Context) {
private val mgr = context.getSystemService(KeyguardManager::class.java)
val isSupported get() = mgr.isDeviceSecure
val isEnabled get() = isSupported && Config.suBiometric
fun authenticate(
activity: FragmentActivity,
onError: () -> Unit = {},
onSuccess: () -> Unit) {
val tag = BiometricFragment::class.java.name
val intent = mgr.createConfirmDeviceCredentialIntent(null, null)
val fragmentManager = activity.supportFragmentManager
var fragment = fragmentManager.findFragmentByTag(tag) as BiometricFragment?
if (fragment == null) {
fragment = BiometricFragment()
fragmentManager.beginTransaction()
.add(0, fragment, tag)
.commitNow()
}
fragment.start(intent, onError, onSuccess)
}
class BiometricFragment : Fragment() {
private val code = 1
private var onError: () -> Unit = {}
private var onSuccess: () -> Unit = {}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == code) {
if (resultCode == Activity.RESULT_OK) {
onSuccess()
} else {
onError()
}
onError = {}
onSuccess = {}
}
}
fun start(intent: Intent, onError: () -> Unit, onSuccess: () -> Unit) {
this.onError = onError
this.onSuccess = onSuccess
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK or Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
startActivityForResult(intent, code)
}
}
}

View File

@ -0,0 +1,17 @@
package com.topjohnwu.magisk.core.utils
import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract
class RequestAuthentication: ActivityResultContract<Unit, Boolean>() {
override fun createIntent(context: Context, input: Unit) =
context.getSystemService(KeyguardManager::class.java)
.createConfirmDeviceCredentialIntent(null, null)
override fun parseResult(resultCode: Int, intent: Intent?) =
resultCode == Activity.RESULT_OK
}

View File

@ -1,38 +0,0 @@
package com.topjohnwu.magisk.events
import com.topjohnwu.magisk.arch.ActivityExecutor
import com.topjohnwu.magisk.arch.UIActivity
import com.topjohnwu.magisk.arch.ViewEvent
import com.topjohnwu.magisk.core.di.ServiceLocator
class BiometricEvent(
builder: Builder.() -> Unit
) : ViewEvent(), ActivityExecutor {
private var listenerOnFailure: () -> Unit = {}
private var listenerOnSuccess: () -> Unit = {}
init {
builder(Builder())
}
override fun invoke(activity: UIActivity<*>) {
ServiceLocator.biometrics.authenticate(
activity,
onError = listenerOnFailure,
onSuccess = listenerOnSuccess
)
}
inner class Builder internal constructor() {
fun onFailure(listener: () -> Unit) {
listenerOnFailure = listener
}
fun onSuccess(listener: () -> Unit) {
listenerOnSuccess = listener
}
}
}

View File

@ -51,6 +51,16 @@ class RecreateEvent : ViewEvent(), ActivityExecutor {
} }
} }
class BiometricEvent(
private val callback: () -> Unit
) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: UIActivity<*>) {
activity.authenticateCallback = { if (it) callback() }
activity.requestAuthenticate.launch(Unit)
}
}
class GetContentEvent( class GetContentEvent(
private val type: String, private val type: String,
private val callback: ContentResultCallback private val callback: ContentResultCallback

View File

@ -12,7 +12,6 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.ktx.activity import com.topjohnwu.magisk.core.ktx.activity
import com.topjohnwu.magisk.core.tasks.HideAPK import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.MediaStoreUtils import com.topjohnwu.magisk.core.utils.MediaStoreUtils
@ -286,14 +285,10 @@ object Tapjack : BaseSettingsItem.Toggle() {
object Biometrics : BaseSettingsItem.Toggle() { object Biometrics : BaseSettingsItem.Toggle() {
override val title = R.string.settings_su_biometric_title.asText() override val title = R.string.settings_su_biometric_title.asText()
override var description = R.string.settings_su_biometric_summary.asText() override var description = R.string.settings_su_biometric_summary.asText()
override var value override var value by Config::userAuth
get() = ServiceLocator.biometrics.isEnabled
set(value) {
Config.suBiometric = value
}
override fun refresh() { override fun refresh() {
isEnabled = ServiceLocator.biometrics.isSupported isEnabled = Info.isDeviceSecure
if (!isEnabled) { if (!isEnabled) {
description = R.string.no_biometric.asText() description = R.string.no_biometric.asText()
} }

View File

@ -92,7 +92,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
when (item) { when (item) {
DownloadPath -> withExternalRW(andThen) DownloadPath -> withExternalRW(andThen)
UpdateChecker -> withPostNotificationPermission(andThen) UpdateChecker -> withPostNotificationPermission(andThen)
Biometrics -> authenticate(andThen) Biometrics -> BiometricEvent(andThen).publish()
Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate() Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate()
DenyListConfig -> SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate() DenyListConfig -> SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate()
SystemlessHosts -> createHosts() SystemlessHosts -> createHosts()
@ -119,13 +119,6 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
} }
} }
private fun authenticate(callback: () -> Unit) {
BiometricEvent {
// allow the change on success
onSuccess { callback() }
}.publish()
}
private fun createHosts() { private fun createHosts() {
Shell.cmd("add_hosts_module").submit { Shell.cmd("add_hosts_module").submit {
AppContext.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT) AppContext.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)

View File

@ -10,14 +10,18 @@ import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.AsyncLoadViewModel import com.topjohnwu.magisk.arch.AsyncLoadViewModel
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.di.AppContext import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.ktx.getLabel import com.topjohnwu.magisk.core.ktx.getLabel
import com.topjohnwu.magisk.core.model.su.SuPolicy import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.databinding.* import com.topjohnwu.magisk.databinding.MergeObservableList
import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.diffList
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.dialog.SuperuserRevokeDialog import com.topjohnwu.magisk.dialog.SuperuserRevokeDialog
import com.topjohnwu.magisk.events.BiometricEvent import com.topjohnwu.magisk.events.BiometricEvent
import com.topjohnwu.magisk.events.SnackbarEvent import com.topjohnwu.magisk.events.SnackbarEvent
@ -113,10 +117,8 @@ class SuperuserViewModel(
} }
} }
if (ServiceLocator.biometrics.isEnabled) { if (Config.userAuth) {
BiometricEvent { BiometricEvent { updateState() }.publish()
onSuccess { updateState() }
}.publish()
} else { } else {
SuperuserRevokeDialog(item.title) { updateState() }.show() SuperuserRevokeDialog(item.title) { updateState() }.show()
} }
@ -168,10 +170,8 @@ class SuperuserViewModel(
} }
} }
if (ServiceLocator.biometrics.isEnabled) { if (Config.userAuth) {
BiometricEvent { BiometricEvent { updateState() }.publish()
onSuccess { updateState() }
}.publish()
} else { } else {
updateState() updateState()
} }

View File

@ -22,7 +22,6 @@ import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.di.AppContext import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.ktx.getLabel import com.topjohnwu.magisk.core.ktx.getLabel
import com.topjohnwu.magisk.core.ktx.toast import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.ALLOW import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.ALLOW
@ -77,12 +76,8 @@ class SuRequestViewModel(
fun grantPressed() { fun grantPressed() {
cancelTimer() cancelTimer()
if (ServiceLocator.biometrics.isEnabled) { if (Config.userAuth) {
BiometricEvent { BiometricEvent { respond(ALLOW) }.publish()
onSuccess {
respond(ALLOW)
}
}.publish()
} else { } else {
respond(ALLOW) respond(ALLOW)
} }