Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Biometric authentication for GUI Authentication Key #5390

Merged
merged 3 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions android/BOINC/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ dependencies {
implementation AndroidX.recyclerView
implementation AndroidX.swipeRefreshLayout
implementation AndroidX.viewPager2
implementation("androidx.biometric:biometric:1.1.0")

implementation 'javax.annotation:javax.annotation-api:_'
implementation 'com.github.bumptech.glide:glide:_'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,26 @@
*/
package edu.berkeley.boinc

import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
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.edit
import androidx.preference.CheckBoxPreference
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
Expand All @@ -33,6 +47,7 @@
import edu.berkeley.boinc.utils.Logging
import edu.berkeley.boinc.utils.setAppTheme
import java.io.File
import java.util.concurrent.Executor
import java.util.concurrent.ThreadLocalRandom
import kotlin.streams.asSequence

Expand All @@ -43,7 +58,9 @@
private val charPool : List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
private val passwordLength = 32
private var authKey = ""

private lateinit var biometricPrompt: BiometricPrompt
private lateinit var authenticationPopupView: View
private lateinit var authenticationPopupEditText: EditText

override fun onResume() {
super.onResume()
Expand Down Expand Up @@ -96,13 +113,81 @@
usedCpuCores?.max = hostInfo.noOfCPUs
}

val preference = findPreference<EditTextPreference>("authenticationKey")!!
preference.setSummaryProvider {
authenticationPopupView = LayoutInflater.from(context).inflate(R.layout.authenticationkey_preference_dialog, null)

Check warning on line 116 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L116

Added line #L116 was not covered by tests
authenticationPopupEditText = authenticationPopupView.findViewById(R.id.authentication_key_input)

val preference = findPreference<Preference>("authenticationKey")!!

Check warning on line 119 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L119

Added line #L119 was not covered by tests

val executor: Executor = ContextCompat.getMainExecutor(requireContext())

Check warning on line 121 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L121

Added line #L121 was not covered by tests

biometricPrompt = BiometricPrompt(this, executor, object : BiometricPrompt.AuthenticationCallback() {

Check warning on line 123 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L123

Added line #L123 was not covered by tests
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
preference.isEnabled = true
authenticationPopup(sharedPreferences)
}

Check warning on line 128 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L125-L128

Added lines #L125 - L128 were not covered by tests

override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
Toast.makeText(context, "Authentication Error", Toast.LENGTH_SHORT).show()
}

Check warning on line 133 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L131-L133

Added lines #L131 - L133 were not covered by tests
})

preference.summaryProvider = Preference.SummaryProvider<Preference> {

Check warning on line 136 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L136

Added line #L136 was not covered by tests
getString(R.string.prefs_remote_boinc_relaunched) + '\n' +
setAsterisks(authKey.length)
setAsterisks(authKey.length)

Check warning on line 138 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L138

Added line #L138 was not covered by tests
}

val biometricManager = BiometricManager.from(this.requireContext())

Check warning on line 141 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L141

Added line #L141 was not covered by tests

preference.setOnPreferenceClickListener {

Check warning on line 143 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L143

Added line #L143 was not covered by tests
when (biometricManager.canAuthenticate(BIOMETRIC_STRONG or BIOMETRIC_WEAK or DEVICE_CREDENTIAL)) {
BiometricManager.BIOMETRIC_SUCCESS -> {
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Authenticate")
.setSubtitle("Use biometric authentication to reveal or edit the authentication key.")
.setAllowedAuthenticators(DEVICE_CREDENTIAL or BIOMETRIC_WEAK or BIOMETRIC_STRONG)
.build()

Check warning on line 150 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L146-L150

Added lines #L146 - L150 were not covered by tests
biometricPrompt.authenticate(promptInfo)
}
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
val enrollIntent = Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply {
putExtra(Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
BIOMETRIC_WEAK or DEVICE_CREDENTIAL)
}
startActivityForResult(enrollIntent, 0)

Check warning on line 158 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L154-L158

Added lines #L154 - L158 were not covered by tests
}
else -> authenticationPopup(sharedPreferences)

Check warning on line 160 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L160

Added line #L160 was not covered by tests
}
true

Check warning on line 162 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L162

Added line #L162 was not covered by tests
}
}

private fun authenticationPopup(sharedPreferences: SharedPreferences) {
val builder = AlertDialog.Builder(requireContext())

Check warning on line 167 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L167

Added line #L167 was not covered by tests

if (authenticationPopupView.parent != null) {
(authenticationPopupView.parent as ViewGroup).removeView(authenticationPopupView)
}

builder.setView(authenticationPopupView)

val currentAuthKey = sharedPreferences.getString("authenticationKey", "")!!

Check warning on line 175 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L175

Added line #L175 was not covered by tests
authenticationPopupEditText.setText(currentAuthKey)


builder.setPositiveButton("OK") { _, _ ->

Check warning on line 179 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L179

Added line #L179 was not covered by tests
val enteredText = authenticationPopupEditText.text.toString()
sharedPreferences.edit { putString("authenticationKey", enteredText) }
}

Check warning on line 182 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L181-L182

Added lines #L181 - L182 were not covered by tests

builder.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}

Check warning on line 186 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L184-L186

Added lines #L184 - L186 were not covered by tests

builder.create().show()
}

Check warning on line 189 in android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/BOINC/app/src/main/java/edu/berkeley/boinc/SettingsFragment.kt#L188-L189

Added lines #L188 - L189 were not covered by tests

override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
when (key) {
// General
Expand Down Expand Up @@ -204,7 +289,6 @@
val currentAuthKey = sharedPreferences.getString(key, "")!!
if (currentAuthKey.isEmpty()) {
sharedPreferences.edit { putString(key, authKey) }
findPreference<EditTextPreference>(key)?.text = authKey
Toast.makeText(activity, R.string.prefs_remote_empty_password, Toast.LENGTH_SHORT).show()
} else {
authKey = currentAuthKey
Expand All @@ -216,7 +300,7 @@
"remoteEnable" -> {
val isRemote = sharedPreferences.getBoolean(key, false)
BOINCActivity.monitor!!.isRemote = isRemote
findPreference<EditTextPreference>("authenticationKey")?.isVisible = isRemote
findPreference<Preference>("authenticationKey")?.isVisible = isRemote
quitClient()
}

Expand Down Expand Up @@ -267,7 +351,7 @@
findPreference<PreferenceCategory>("debug")?.isVisible = showAdvanced
findPreference<PreferenceCategory>("remote")?.isVisible = showAdvanced
val isRemote = findPreference<CheckBoxPreference>("remoteEnable")?.isChecked
findPreference<EditTextPreference>("authenticationKey")?.isVisible = showAdvanced && isRemote == true
findPreference<Preference>("authenticationKey")?.isVisible = showAdvanced && isRemote == true
}

private fun writeClientPrefs(prefs: GlobalPreferences) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
style="@style/BackgroundDayNight"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textStyle="bold"
android:singleLine="true"
android:padding="20dp"
android:layout_marginBottom="20dp"
android:text="@string/prefs_remote_authentication_key"/>

<EditText
android:id="@+id/authentication_key_input"
android:inputType="text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:minHeight="50dp"
android:hint="@string/prefs_remote_authentication_key_input_hint"
android:minWidth="200dp"/>

</LinearLayout>
1 change: 1 addition & 0 deletions android/BOINC/app/src/main/res/values-en/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
<string name="prefs_other_store_at_least_x_days_of_work_header">Work unit buffer (days)</string>
<string name="prefs_other_store_up_to_an_additional_x_days_of_work_header">Additional work unit buffer (days)</string>
<string name="prefs_remote_authentication_key">GUI authentication key</string>
<string name="prefs_remote_authentication_key_input_hint">Authentication Key</string>
<string name="prefs_remote_empty_password">Password update failed: value cannot be empty</string>
<string name="prefs_remote_boinc_relaunched">The BOINC client will be relaunched after password change.</string>
<string name="prefs_client_log_flags_header">BOINC client log flags</string>
Expand Down
1 change: 1 addition & 0 deletions android/BOINC/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
<string name="prefs_other_store_at_least_x_days_of_work_header">Work unit buffer (days)</string>
<string name="prefs_other_store_up_to_an_additional_x_days_of_work_header">Additional work unit buffer (days)</string>
<string name="prefs_remote_authentication_key">GUI authentication key</string>
<string name="prefs_remote_authentication_key_input_hint">Authentication Key</string>
<string name="prefs_remote_empty_password">Password update failed: value cannot be empty</string>
<string name="prefs_remote_boinc_relaunched">The BOINC client will be relaunched after password change.</string>
<string name="prefs_client_log_flags_header">BOINC client log flags</string>
Expand Down
2 changes: 1 addition & 1 deletion android/BOINC/app/src/main/res/xml/root_preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@
app:summary="@string/prefs_remote_summary"
app:title="@string/prefs_remote_header" />

<EditTextPreference
<Preference
app:iconSpaceReserved="false"
app:key="authenticationKey"
app:title="@string/prefs_remote_authentication_key"
Expand Down
Loading