Skip to content

Commit

Permalink
feat(ui): authenticate first when enabling security settings (#991)
Browse files Browse the repository at this point in the history
  • Loading branch information
IndusAryan committed Mar 25, 2024
1 parent 35e38a5 commit 2293742
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 51 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ repositories {
dependencies {
// Testing
testImplementation("junit:junit:4.13.2")
testImplementation("org.json:json:20231013")
testImplementation("org.json:json:20240303")
androidTestImplementation("androidx.test:core")
implementation("androidx.test.ext:junit-ktx:1.1.5")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
Expand Down
14 changes: 10 additions & 4 deletions app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@ import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.BackupUtils.backup
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
import com.lagradost.cloudstream3.utils.BiometricAuthenticator
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isAuthEnabled
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.promptInfo
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
Expand Down Expand Up @@ -1231,18 +1234,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener,
changeStatusBarState(isLayout(EMULATOR))

/** Biometric stuff for users without accounts **/
val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_key), false)
val noAccounts = settingsManager.getBoolean(
getString(R.string.skip_startup_account_select_key),
false
) || accounts.count() <= 1

if (isLayout(PHONE) && authEnabled && noAccounts) {
if (isLayout(PHONE) && isAuthEnabled(this) && noAccounts) {
if (deviceHasPasswordPinLock(this)) {
startBiometricAuthentication(this, R.string.biometric_authentication_title, false)

BiometricAuthenticator.promptInfo?.let { promt ->
BiometricAuthenticator.biometricPrompt?.authenticate(promt)
promptInfo?.let { prompt ->
biometricPrompt?.authenticate(prompt)
}

// hide background while authenticating, Sorry moms & dads 🙏
Expand Down Expand Up @@ -1825,6 +1827,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener,
binding?.navHostFragment?.isInvisible = false
}

override fun onAuthenticationError() {
finish()
}

private var backPressedCallback: OnBackPressedCallback? = null

private fun attachBackPressedCallback() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.BiometricAuthenticator
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isAuthEnabled
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.promptInfo
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
import com.lagradost.cloudstream3.utils.DataStoreHelper.accounts
import com.lagradost.cloudstream3.utils.DataStoreHelper.selectedKeyIndex
Expand All @@ -48,24 +51,23 @@ class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.Biomet
)

val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_key), false)
val skipStartup = settingsManager.getBoolean(getString(R.string.skip_startup_account_select_key), false
) || accounts.count() <= 1

viewModel = ViewModelProvider(this)[AccountViewModel::class.java]

fun askBiometricAuth() {

if (isLayout(PHONE) && authEnabled) {
if (isLayout(PHONE) && isAuthEnabled(this)) {
if (deviceHasPasswordPinLock(this)) {
startBiometricAuthentication(
this,
R.string.biometric_authentication_title,
false
)

BiometricAuthenticator.promptInfo?.let { promt ->
BiometricAuthenticator.biometricPrompt?.authenticate(promt)
promptInfo?.let { prompt ->
biometricPrompt?.authenticate(prompt)
}
}
}
Expand Down Expand Up @@ -189,4 +191,8 @@ class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.Biomet
override fun onAuthenticationSuccess() {
Log.i(BiometricAuthenticator.TAG,"Authentication successful in AccountSelectActivity")
}

override fun onAuthenticationError() {
finish()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import androidx.preference.SwitchPreferenceCompat
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
Expand All @@ -30,6 +31,7 @@ import com.lagradost.cloudstream3.syncproviders.AuthAPI
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI
import com.lagradost.cloudstream3.syncproviders.OAuth2API
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
Expand All @@ -38,13 +40,20 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setTool
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.BackupUtils
import com.lagradost.cloudstream3.utils.BiometricAuthenticator
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.authCallback
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isAuthEnabled
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.promptInfo
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogText
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.UIHelper.setImage

class SettingsAccount : PreferenceFragmentCompat() {
class SettingsAccount : PreferenceFragmentCompat(), BiometricAuthenticator.BiometricAuthCallback {
companion object {
/** Used by nginx plugin too */
fun showLoginInfo(
Expand Down Expand Up @@ -252,6 +261,31 @@ class SettingsAccount : PreferenceFragmentCompat() {
}
}

private fun updateAuthPreference(enabled: Boolean) {
val biometricKey = getString(R.string.biometric_key)

PreferenceManager.getDefaultSharedPreferences(context ?: return).edit()
.putBoolean(biometricKey, enabled).apply()
findPreference<SwitchPreferenceCompat>(biometricKey)?.isChecked = enabled
}

override fun onAuthenticationError() {
updateAuthPreference(!isAuthEnabled(context ?: return))
}

override fun onAuthenticationSuccess() {
if (isAuthEnabled(context?: return)) {
updateAuthPreference(true)
BackupUtils.backup(activity)
activity?.showBottomDialogText(
getString(R.string.biometric_setting),
getString(R.string.biometric_warning).html()
) { onDialogDismissedEvent }
} else {
updateAuthPreference(false)
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setUpToolbar(R.string.category_account)
Expand All @@ -263,22 +297,25 @@ class SettingsAccount : PreferenceFragmentCompat() {
hideKeyboard()
setPreferencesFromResource(R.xml.settings_account, rootKey)

// hide preference on tvs and emulators
getPref(R.string.biometric_key)?.isEnabled = isLayout(PHONE)

getPref(R.string.biometric_key)?.setOnPreferenceClickListener {
val authEnabled = PreferenceManager.getDefaultSharedPreferences(
context ?: return@setOnPreferenceClickListener false
)
.getBoolean(getString(R.string.biometric_key), false)

if (authEnabled) {
BackupUtils.backup(activity)
val title = activity?.getString(R.string.biometric_setting)
val warning = activity?.getString(R.string.biometric_warning)
activity?.showBottomDialogText(
title as String,
warning.html()
) { onDialogDismissedEvent }
val ctx = context ?: return@setOnPreferenceClickListener false

if (deviceHasPasswordPinLock(ctx)) {
startBiometricAuthentication(
activity?: return@setOnPreferenceClickListener false,
R.string.biometric_authentication_title,
false
)
promptInfo?.let {
authCallback = this
biometricPrompt?.authenticate(it)
}
}
true

false
}

val syncApis =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getString
import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R

object BiometricAuthenticator {

const val TAG = "cs3Auth"
private const val MAX_FAILED_ATTEMPTS = 3
private var failedAttempts = 0
const val TAG = "cs3Auth"

private var biometricManager: BiometricManager? = null
var biometricPrompt: BiometricPrompt? = null
var promptInfo: BiometricPrompt.PromptInfo? = null

var authCallback: BiometricAuthCallback? = null // listen to authentication success

private fun initializeBiometrics(activity: Activity) {
Expand All @@ -37,20 +37,12 @@ object BiometricAuthenticator {
activity as FragmentActivity,
executor,
object : BiometricPrompt.AuthenticationCallback() {

override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
showToast("$errString")
Log.e(TAG, "$errorCode")
failedAttempts++

if (failedAttempts >= MAX_FAILED_ATTEMPTS) {
failedAttempts = 0
activity.finish()
} else {
failedAttempts = 0
activity.finish()
}
authCallback?.onAuthenticationError()
//activity.finish()
}

override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
Expand Down Expand Up @@ -89,7 +81,6 @@ object BiometricAuthenticator {
.setDescription(description)
.setAllowedAuthenticators(authFlag)
.build()

} else {
// for apis < 30
promptInfo = BiometricPrompt.PromptInfo.Builder()
Expand All @@ -98,7 +89,6 @@ object BiometricAuthenticator {
.setDeviceCredentialAllowed(true)
.build()
}

} else {
// fallback for A12+ when both fingerprint & Face unlock is absent but PIN is set
promptInfo = BiometricPrompt.PromptInfo.Builder()
Expand All @@ -114,7 +104,6 @@ object BiometricAuthenticator {
var result = false

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {

when (biometricManager?.canAuthenticate(
DEVICE_CREDENTIAL or BIOMETRIC_STRONG or BIOMETRIC_WEAK
)) {
Expand All @@ -126,7 +115,6 @@ object BiometricAuthenticator {
BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true
BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false
}

} else {
@Suppress("DEPRECATION")
when (biometricManager?.canAuthenticate()) {
Expand All @@ -153,12 +141,11 @@ object BiometricAuthenticator {
// function to start authentication in any fragment or activity
fun startBiometricAuthentication(activity: Activity, title: Int, setDeviceCred: Boolean) {
initializeBiometrics(activity)

authCallback = activity as? BiometricAuthCallback
if (isBiometricHardWareAvailable()) {
authCallback = activity as? BiometricAuthCallback
authenticationDialog(activity, title, setDeviceCred)
promptInfo?.let { biometricPrompt?.authenticate(it) }

} else {
if (deviceHasPasswordPinLock(activity)) {
authCallback = activity as? BiometricAuthCallback
Expand All @@ -171,7 +158,15 @@ object BiometricAuthenticator {
}
}

fun isAuthEnabled(ctx: Context):Boolean {
return ctx.let {
PreferenceManager.getDefaultSharedPreferences(ctx)
.getBoolean(getString(ctx, R.string.biometric_key), false)
}
}

interface BiometricAuthCallback {
fun onAuthenticationSuccess()
fun onAuthenticationError()
}
}
4 changes: 2 additions & 2 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@
<string name="search">Search</string>
<string name="library">Library</string>
<string name="category_account">Accounts and Security</string>
<string name="category_updates">Updates and backup</string>
<string name="category_updates">Updates and Backup</string>
<string name="settings_info">Info</string>
<string name="advanced_search">Advanced Search</string>
<string name="advanced_search_des">Gives you the search results separated by provider</string>
Expand Down Expand Up @@ -611,7 +611,7 @@
<string name="tracks">Tracks</string>
<string name="audio_tracks">Audio tracks</string>
<string name="video_tracks">Video tracks</string>
<string name="apply_on_restart">Apply on Restart</string>
<string name="apply_on_restart">Restart the app to see changes.</string>
<string name="restart">Restart</string>
<string name="stop">Stop</string>
<string name="safe_mode_title">Safe mode on</string>
Expand Down
8 changes: 2 additions & 6 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}

dependencies {
classpath("com.android.tools.build:gradle:8.2.1")
classpath("com.android.tools.build:gradle:8.2.2")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.9.10")

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle.kts files
}
}

Expand All @@ -23,7 +19,7 @@ allprojects {
}

plugins {
id("com.google.devtools.ksp") version "1.9.22-1.0.16" apply false
id("com.google.devtools.ksp") version "1.9.22-1.0.17" apply false
}

tasks.register<Delete>("clean") {
Expand Down

0 comments on commit 2293742

Please sign in to comment.