aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt6
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/FrostParser.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Security.kt50
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/BiometricUtils.kt110
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt2
9 files changed, 177 insertions, 3 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt
index 18ae4b0b..cf8acdd3 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt
@@ -42,6 +42,7 @@ import com.pitchedapps.frost.db.save
import com.pitchedapps.frost.db.saveTabs
import com.pitchedapps.frost.db.selectAll
import com.pitchedapps.frost.facebook.FbCookie
+import com.pitchedapps.frost.utils.BiometricUtils
import com.pitchedapps.frost.utils.EXTRA_COOKIES
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
@@ -79,6 +80,7 @@ class StartActivity : KauBaseActivity() {
}
launch {
+ val authDefer = BiometricUtils.authenticate(this@StartActivity)
try {
migrate()
FbCookie.switchBackUser()
@@ -86,6 +88,7 @@ class StartActivity : KauBaseActivity() {
L.i { "Cookies loaded at time ${System.currentTimeMillis()}" }
L._d { "Cookies: ${cookies.joinToString("\t", transform = CookieEntity::toSensitiveString)}" }
loadAssets()
+ authDefer.await()
when {
cookies.isEmpty() -> launchNewTask<LoginActivity>()
// Has cookies but no selected account
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt
index c3089c7a..bc20aa2d 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt
@@ -45,6 +45,7 @@ import com.pitchedapps.frost.settings.getDebugPrefs
import com.pitchedapps.frost.settings.getExperimentalPrefs
import com.pitchedapps.frost.settings.getFeedPrefs
import com.pitchedapps.frost.settings.getNotificationPrefs
+import com.pitchedapps.frost.settings.getSecurityPrefs
import com.pitchedapps.frost.settings.sendDebug
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
@@ -146,6 +147,11 @@ class SettingsActivity : KPrefActivity() {
iicon = GoogleMaterial.Icon.gmd_notifications
}
+ subItems(R.string.security, getSecurityPrefs()) {
+ descRes = R.string.security_desc
+ iicon = GoogleMaterial.Icon.gmd_lock
+ }
+
// subItems(R.string.network, getNetworkPrefs()) {
// descRes = R.string.network_desc
// iicon = GoogleMaterial.Icon.gmd_network_cell
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt
index f3226e2f..ec4ff9dd 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt
@@ -64,6 +64,7 @@ import com.pitchedapps.frost.kotlin.subscribeDuringJob
import com.pitchedapps.frost.services.FrostRunnable
import com.pitchedapps.frost.utils.ARG_URL
import com.pitchedapps.frost.utils.ARG_USER_ID
+import com.pitchedapps.frost.utils.BiometricUtils
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.Showcase
@@ -214,8 +215,10 @@ open class WebOverlayActivityBase(private val forceDesktopAgent: Boolean) : Base
userAgentString = USER_AGENT_DESKTOP
Prefs.prevId = Prefs.userId
launch {
+ val authDefer = BiometricUtils.authenticate(this@WebOverlayActivityBase)
if (userId != Prefs.userId)
FbCookie.switchUser(userId)
+ authDefer.await()
reloadBase(true)
if (Showcase.firstWebOverlay) {
coordinator.frostSnackbar(R.string.web_overlay_swipe_hint) {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/FrostParser.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/FrostParser.kt
index 24c39e28..6af21259 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/FrostParser.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/parsers/FrostParser.kt
@@ -76,7 +76,7 @@ const val FALLBACK_TIME_MOD = 1000000
data class FrostLink(val text: String, val href: String)
-data class ParseResponse<out T: ParseData>(val cookie: String, val data: T) {
+data class ParseResponse<out T : ParseData>(val cookie: String, val data: T) {
override fun toString() = "ParseResponse\ncookie: $cookie\ndata:\n$data"
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt
index e0ae6de5..1ee06464 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt
@@ -52,7 +52,7 @@ import java.io.File
*/
fun SettingsActivity.getDebugPrefs(): KPrefAdapterBuilder.() -> Unit = {
- plainText(R.string.experimental_disclaimer) {
+ plainText(R.string.disclaimer) {
descRes = R.string.debug_disclaimer_info
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt
index e0d314a8..41a60594 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt
@@ -30,7 +30,7 @@ import com.pitchedapps.frost.utils.Showcase
*/
fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = {
- plainText(R.string.experimental_disclaimer) {
+ plainText(R.string.disclaimer) {
descRes = R.string.experimental_disclaimer_info
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Security.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Security.kt
new file mode 100644
index 00000000..754e19de
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Security.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.settings
+
+import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.activities.SettingsActivity
+import com.pitchedapps.frost.utils.BiometricUtils
+import com.pitchedapps.frost.utils.Prefs
+import kotlinx.coroutines.launch
+
+/**
+ * Created by Allan Wang on 20179-05-01.
+ */
+fun SettingsActivity.getSecurityPrefs(): KPrefAdapterBuilder.() -> Unit = {
+
+ plainText(R.string.disclaimer) {
+ descRes = R.string.security_disclaimer_info
+ }
+
+ checkbox(R.string.enable_biometrics, Prefs::biometricsEnabled, {
+ launch {
+ /*
+ * For security, we should request authentication when:
+ * - enabling to ensure that it is supported
+ * - disabling to ensure that it is permitted
+ */
+ BiometricUtils.authenticate(this@getSecurityPrefs, force = true).await()
+ Prefs.biometricsEnabled = it
+ reloadByTitle(R.string.enable_biometrics)
+ }
+ }) {
+ descRes = R.string.enable_biometrics_desc
+ enabler = { BiometricUtils.isSupported(this@getSecurityPrefs) }
+ }
+}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/BiometricUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/BiometricUtils.kt
new file mode 100644
index 00000000..db901073
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/BiometricUtils.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2019 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.utils
+
+import android.content.Context
+import android.hardware.fingerprint.FingerprintManager
+import android.os.Build
+import androidx.biometric.BiometricPrompt
+import androidx.fragment.app.FragmentActivity
+import ca.allanwang.kau.utils.string
+import com.pitchedapps.frost.R
+import kotlinx.coroutines.CompletableDeferred
+import java.util.concurrent.Executor
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+
+typealias BiometricDeferred = CompletableDeferred<BiometricPrompt.CryptoObject?>
+
+/**
+ * Container for [BiometricPrompt]
+ * Inspired by coroutine's CommonPool
+ */
+object BiometricUtils {
+
+ private val executor: Executor
+ get() = pool ?: getOrCreatePoolSync()
+
+ @Volatile
+ private var pool: ExecutorService? = null
+
+ private var lastUnlockTime = -1L
+
+ private const val UNLOCK_TIME_INTERVAL = 15 * 60 * 1000
+
+ /**
+ * Checks if biometric authentication is possible
+ * Currently, this means checking for enrolled fingerprints
+ */
+ @Suppress("DEPRECATION")
+ fun isSupported(context: Context): Boolean {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false
+ val fingerprintManager = context.getSystemService(FingerprintManager::class.java) ?: return false
+ return fingerprintManager.isHardwareDetected && fingerprintManager.hasEnrolledFingerprints()
+ }
+
+ private fun getOrCreatePoolSync(): Executor =
+ pool ?: Executors.newSingleThreadExecutor().also { pool = it }
+
+ private fun shouldPrompt(context: Context): Boolean {
+ return Prefs.biometricsEnabled && System.currentTimeMillis() - lastUnlockTime > UNLOCK_TIME_INTERVAL
+ }
+
+ fun authenticate(activity: FragmentActivity, force: Boolean = false): BiometricDeferred {
+ val deferred: BiometricDeferred = CompletableDeferred()
+ if (!force && !shouldPrompt(activity)) {
+ deferred.complete(null)
+ return deferred
+ }
+ val info = BiometricPrompt.PromptInfo.Builder()
+ .setTitle(activity.string(R.string.biometrics_prompt_title))
+ .setNegativeButtonText(activity.string(R.string.kau_cancel))
+ .build()
+ BiometricPrompt(activity, executor, Callback(activity, deferred)).authenticate(info)
+ return deferred
+ }
+
+ private class Callback(val activity: FragmentActivity, val deferred: BiometricDeferred) :
+ BiometricPrompt.AuthenticationCallback() {
+ override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
+ deferred.cancel()
+ activity.finish()
+ }
+
+ override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
+ lastUnlockTime = System.currentTimeMillis()
+ deferred.complete(result.cryptoObject)
+ }
+
+ override fun onAuthenticationFailed() {
+ deferred.cancel()
+ activity.finish()
+ }
+ }
+
+ /**
+ * For completeness we provide a shutdown function.
+ * In practice, we initialize the executor only when it is first used,
+ * and keep it alive throughout the app lifecycle, as it will be used an arbitrary number of times,
+ * with unknown frequency
+ */
+ @Synchronized
+ fun shutdown() {
+ pool?.shutdown()
+ pool = null
+ }
+}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
index 391d422a..7656a081 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
@@ -156,6 +156,8 @@ object Prefs : KPref() {
var analytics: Boolean by kpref("analytics", true)
+ var biometricsEnabled: Boolean by kpref("biometrics_enabled", false)
+
var overlayEnabled: Boolean by kpref("overlay_enabled", true)
var overlayFullScreenSwipe: Boolean by kpref("overlay_full_screen_swipe", true)