Skip to content
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
4 changes: 2 additions & 2 deletions contrib/android/buildozer_qml.spec
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ fullscreen = False
#

# (list) Permissions
android.permissions = INTERNET, CAMERA, WRITE_EXTERNAL_STORAGE, POST_NOTIFICATIONS
android.permissions = INTERNET, CAMERA, WRITE_EXTERNAL_STORAGE, POST_NOTIFICATIONS, USE_BIOMETRIC

# (int) Android API to use (compileSdkVersion)
# note: when changing, Dockerfile also needs to be changed to install corresponding build tools
Expand Down Expand Up @@ -171,7 +171,7 @@ android.gradle_dependencies =
com.android.support:support-compat:28.0.0,
org.jetbrains.kotlin:kotlin-stdlib:1.8.22

android.add_activities = org.electrum.qr.SimpleScannerActivity
android.add_activities = org.electrum.qr.SimpleScannerActivity, org.electrum.biometry.BiometricActivity

# (list) Put these files or directories in the apk res directory.
# The option may be used in three ways, the value may contain one or zero ':'
Expand Down
13 changes: 12 additions & 1 deletion electrum/gui/qml/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,18 @@
from electrum.logging import get_logger


def auth_protect(func=None, reject=None, method='pin', message=''):
def auth_protect(func=None, reject=None, method='payment_auth', message=''):
"""
Supported methods:
* payment_auth: If the user has enabled the 'Payment authentication' config
they need to authenticate to continue. If biometrics are enabled they
can authenticate using the Android system dialog, else they will see the
wallet password dialog.
If the option is disabled they will have to confirm a dialog.
* wallet: Same as payment_auth, but not dependent on user configuration,
always requires authentication.
* wallet_password_only: No biometric/system authentication, user has to enter wallet password.
"""
if func is None:
return partial(auth_protect, reject=reject, method=method, message=message)

Expand Down
114 changes: 0 additions & 114 deletions electrum/gui/qml/components/Pin.qml

This file was deleted.

97 changes: 58 additions & 39 deletions electrum/gui/qml/components/Preferences.qml
Original file line number Diff line number Diff line change
Expand Up @@ -158,59 +158,83 @@ Pane {
}

RowLayout {
Layout.columnSpan: 2
Layout.fillWidth: true
spacing: 0
// isAvailable checks phone support and if a fingerprint is enrolled on the system
enabled: Biometrics.isAvailable && Daemon.currentWallet

Connections {
target: Biometrics
function onEnablingFailed(error) {
if (error === 'CANCELLED') {
return // don't show error popup
}
var err = app.messageDialog.createObject(app, {
text: qsTr('Failed to enable biometric authentication: ') + error
})
err.open()
}
}

Switch {
id: usePin
checked: Config.pinCode
id: useBiometrics
checked: Biometrics.isEnabled
onCheckedChanged: {
if (activeFocus) {
console.log('PIN active ' + checked)
useBiometrics.focus = false
if (checked) {
var dialog = pinSetup.createObject(preferences, {mode: 'enter'})
dialog.accepted.connect(function() {
Config.pinCode = dialog.pincode
dialog.close()
})
dialog.rejected.connect(function() {
checked = false
})
dialog.open()
if (Daemon.singlePasswordEnabled) {
Biometrics.enable(Daemon.singlePassword)
} else {
useBiometrics.checked = false
var err = app.messageDialog.createObject(app, {
title: qsTr('Unavailable'),
text: [
qsTr("Cannot activate biometric authentication because you have wallets with different passwords."),
qsTr("To use biometric authentication you first need to change all wallet passwords to the same password.")
].join("\n")
})
err.open()
}
} else {
focus = false
Config.pinCode = ''
// re-add binding, pincode still set if auth failed
checked = Qt.binding(function () { return Config.pinCode })
Biometrics.disableProtected()
}
}

}
}
Label {
Layout.fillWidth: true
text: qsTr('PIN protect payments')
text: qsTr('Biometric authentication')
wrapMode: Text.Wrap
}
}

Pane {
background: Rectangle { color: Material.dialogColor }
padding: 0
visible: Config.pinCode != ''
FlatButton {
text: qsTr('Modify')
onClicked: {
var dialog = pinSetup.createObject(preferences, {
mode: 'change',
pincode: Config.pinCode
})
dialog.accepted.connect(function() {
Config.pinCode = dialog.pincode
dialog.close()
})
dialog.open()
RowLayout {
Layout.columnSpan: 2
Layout.fillWidth: true
spacing: 0

property bool noWalletPassword: Daemon.currentWallet ? Daemon.currentWallet.verifyPassword('') : true
enabled: Daemon.currentWallet && !noWalletPassword

Switch {
id: paymentAuthentication
// showing the toggle as checked even if the wallet has no password would be misleading
checked: Config.paymentAuthentication && !(Daemon.currentWallet && parent.noWalletPassword)
onCheckedChanged: {
if (activeFocus) {
// will request authentication when checked = false
console.log('paymentAuthentication: ' + checked)
Config.paymentAuthentication = checked;
}
}
}
Label {
Layout.fillWidth: true
text: qsTr('Payment authentication')
wrapMode: Text.Wrap
}
}

RowLayout {
Expand Down Expand Up @@ -462,11 +486,6 @@ Pane {
}
}

Component {
id: pinSetup
Pin {}
}

Component.onCompleted: {
language.currentIndex = language.indexOfValue(Config.language)
baseUnit.currentIndex = _baseunits.indexOf(Config.baseUnit)
Expand Down
33 changes: 33 additions & 0 deletions electrum/gui/qml/components/WalletDetails.qml
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,15 @@ Pane {
})
dialog.accepted.connect(function() {
var success = Daemon.setPassword(dialog.password)
if (success && Biometrics.isEnabled) {
if (Biometrics.isAvailable) {
// also update the biometric authentication
Biometrics.enable(dialog.password)
Comment thread
SomberNight marked this conversation as resolved.
} else {
// disable biometric authentication as it is not available
Biometrics.disable()
}
}
var done_dialog = app.messageDialog.createObject(app, {
title: success ? qsTr('Success') : qsTr('Error'),
iconSource: success
Expand Down Expand Up @@ -546,6 +555,11 @@ Pane {
}
var error_msg = qsTr('Password change failed')
}
if (success && Biometrics.isEnabled) {
// unlikely to happen as this means the user somehow moved from
// a unified password to differing passwords
Biometrics.disable()
}
var done_dialog = app.messageDialog.createObject(app, {
title: success ? qsTr('Success') : qsTr('Error'),
iconSource: success
Expand All @@ -563,6 +577,25 @@ Pane {
}
}

Connections {
target: Biometrics
function onEnablingFailed(error) {
if (error === 'CANCELLED') {
var biometrics_disabled_dialog = app.messageDialog.createObject(app, {
title: qsTr('Biometric Authentication'),
iconSource: Qt.resolvedUrl('../../icons/warning.png'),
text: qsTr('Biometric authentication disabled. You can enable it again in the settings.')
})
biometrics_disabled_dialog.open()
return
}
var err = app.messageDialog.createObject(app, {
text: qsTr('Failed to update biometric authentication to new password: ') + error
})
err.open()
}
}

Component {
id: importAddressesKeysDialog
ImportAddressesKeysDialog {
Expand Down
Loading