mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-22 00:12:09 +03:00
Merge PR #1643
This commit is contained in:
commit
9b1b2cc15a
@ -42,6 +42,7 @@ import com.yubico.authenticator.oath.keystore.ClearingMemProvider
|
||||
import com.yubico.authenticator.oath.keystore.KeyProvider
|
||||
import com.yubico.authenticator.oath.keystore.KeyStoreProvider
|
||||
import com.yubico.authenticator.oath.keystore.SharedPrefProvider
|
||||
import com.yubico.authenticator.yubikit.getDeviceInfo
|
||||
import com.yubico.authenticator.yubikit.withConnection
|
||||
import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyDevice
|
||||
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice
|
||||
@ -105,6 +106,7 @@ class OathManager(
|
||||
private var pendingAction: OathAction? = null
|
||||
private var refreshJob: Job? = null
|
||||
private var addToAny = false
|
||||
private val updateDeviceInfo = AtomicBoolean(false)
|
||||
|
||||
override fun onPause() {
|
||||
// cancel any pending actions, except for addToAny
|
||||
@ -284,6 +286,10 @@ class OathManager(
|
||||
logger.debug(
|
||||
"Successfully read Oath session info (and credentials if unlocked) from connected key"
|
||||
)
|
||||
|
||||
if (updateDeviceInfo.getAndSet(false)) {
|
||||
deviceManager.setDeviceInfo(getDeviceInfo(device))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// OATH not enabled/supported, try to get DeviceInfo over other USB interfaces
|
||||
logger.error("Failed to connect to CCID: ", e)
|
||||
@ -362,7 +368,7 @@ class OathManager(
|
||||
}
|
||||
|
||||
private suspend fun reset(): String =
|
||||
useOathSession(OathActionDescription.Reset) {
|
||||
useOathSession(OathActionDescription.Reset, updateDeviceInfo = true) {
|
||||
// note, it is ok to reset locked session
|
||||
it.reset()
|
||||
keyManager.removeKey(it.deviceId)
|
||||
@ -396,7 +402,11 @@ class OathManager(
|
||||
currentPassword: String?,
|
||||
newPassword: String,
|
||||
): String =
|
||||
useOathSession(OathActionDescription.SetPassword, unlock = false) { session ->
|
||||
useOathSession(
|
||||
OathActionDescription.SetPassword,
|
||||
unlock = false,
|
||||
updateDeviceInfo = true
|
||||
) { session ->
|
||||
if (session.isAccessKeySet) {
|
||||
if (currentPassword == null) {
|
||||
throw Exception("Must provide current password to be able to change it")
|
||||
@ -648,22 +658,30 @@ class OathManager(
|
||||
private suspend fun <T> useOathSession(
|
||||
oathActionDescription: OathActionDescription,
|
||||
unlock: Boolean = true,
|
||||
updateDeviceInfo: Boolean = false,
|
||||
action: (YubiKitOathSession) -> T
|
||||
): T {
|
||||
|
||||
// callers can decide whether the session should be unlocked first
|
||||
unlockOnConnect.set(unlock)
|
||||
// callers can request whether device info should be updated after session operation
|
||||
this@OathManager.updateDeviceInfo.set(updateDeviceInfo)
|
||||
return deviceManager.withKey(
|
||||
onUsb = { useOathSessionUsb(it, action) },
|
||||
onUsb = { useOathSessionUsb(it, updateDeviceInfo, action) },
|
||||
onNfc = { useOathSessionNfc(oathActionDescription, action) }
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun <T> useOathSessionUsb(
|
||||
device: UsbYubiKeyDevice,
|
||||
updateDeviceInfo: Boolean = false,
|
||||
block: (YubiKitOathSession) -> T
|
||||
): T = device.withConnection<SmartCardConnection, T> {
|
||||
block(getOathSession(it))
|
||||
}.also {
|
||||
if (updateDeviceInfo) {
|
||||
deviceManager.setDeviceInfo(getDeviceInfo(device))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T> useOathSessionNfc(
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Yubico.
|
||||
* Copyright (C) 2023,2024 Yubico.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -28,9 +28,17 @@ import '../features.dart' as features;
|
||||
import '../icon_provider/icon_pack_dialog.dart';
|
||||
import '../keys.dart' as keys;
|
||||
import '../models.dart';
|
||||
import 'manage_password_dialog.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
bool oathShowActionNotifier(DeviceInfo? info) {
|
||||
if (info == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final (fipsCapable, fipsApproved) = info.getFipsStatus(Capability.oath);
|
||||
return fipsCapable && !fipsApproved;
|
||||
}
|
||||
|
||||
Widget oathBuildActions(
|
||||
BuildContext context,
|
||||
DevicePath devicePath,
|
||||
@ -63,6 +71,10 @@ Widget oathBuildActions(
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
final colors = Theme.of(context).buttonTheme.colorScheme ??
|
||||
Theme.of(context).colorScheme;
|
||||
final alertIcon = Icon(Symbols.warning_amber, color: colors.tertiary);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
ActionListSection(l10n.s_setup, children: [
|
||||
@ -103,13 +115,10 @@ Widget oathBuildActions(
|
||||
oathState.hasKey ? l10n.s_manage_password : l10n.s_set_password,
|
||||
subtitle: l10n.l_password_protection,
|
||||
icon: const Icon(Symbols.password),
|
||||
trailing: fipsCapable && !fipsApproved ? alertIcon : null,
|
||||
onTap: (context) {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
showBlurDialog(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
ManagePasswordDialog(devicePath, oathState),
|
||||
);
|
||||
managePassword(context, ref, devicePath, oathState);
|
||||
}),
|
||||
]),
|
||||
],
|
||||
|
@ -167,7 +167,8 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
|
||||
final hasFeature = ref.watch(featureProvider);
|
||||
final hasActions = hasFeature(features.actions);
|
||||
final searchText = searchController.text;
|
||||
|
||||
final deviceInfo =
|
||||
ref.watch(currentDeviceDataProvider.select((s) => s.valueOrNull?.info));
|
||||
Future<void> onFileDropped(File file) async {
|
||||
final qrScanner = ref.read(qrScannerProvider);
|
||||
if (qrScanner != null) {
|
||||
@ -186,21 +187,36 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
|
||||
|
||||
if (numCreds == 0) {
|
||||
return MessagePage(
|
||||
actionsBuilder: (context, expanded) => [
|
||||
if (!expanded)
|
||||
ActionChip(
|
||||
label: Text(l10n.s_add_account),
|
||||
onPressed: () async {
|
||||
await addOathAccount(
|
||||
context,
|
||||
ref,
|
||||
widget.devicePath,
|
||||
widget.oathState,
|
||||
);
|
||||
},
|
||||
avatar: const Icon(Symbols.person_add_alt),
|
||||
)
|
||||
],
|
||||
keyActionsBadge: oathShowActionNotifier(deviceInfo),
|
||||
actionsBuilder: (context, expanded) {
|
||||
final (fipsCapable, fipsApproved) =
|
||||
deviceInfo?.getFipsStatus(Capability.oath) ?? (false, false);
|
||||
|
||||
return [
|
||||
if (!expanded && (!fipsCapable || (fipsCapable && fipsApproved)))
|
||||
ActionChip(
|
||||
label: Text(l10n.s_add_account),
|
||||
onPressed: () async {
|
||||
await addOathAccount(
|
||||
context,
|
||||
ref,
|
||||
widget.devicePath,
|
||||
widget.oathState,
|
||||
);
|
||||
},
|
||||
avatar: const Icon(Symbols.person_add_alt),
|
||||
),
|
||||
if (!expanded && fipsCapable && !fipsApproved)
|
||||
ActionChip(
|
||||
label: Text(l10n.s_set_password),
|
||||
onPressed: () async {
|
||||
await managePassword(
|
||||
context, ref, widget.devicePath, widget.oathState);
|
||||
},
|
||||
avatar: const Icon(Symbols.person_add_alt),
|
||||
)
|
||||
];
|
||||
},
|
||||
title: l10n.s_accounts,
|
||||
capabilities: const [Capability.oath],
|
||||
key: keys.noAccountsView,
|
||||
@ -225,6 +241,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
|
||||
capabilities: const [Capability.oath],
|
||||
centered: true,
|
||||
delayedContent: true,
|
||||
keyActionsBadge: oathShowActionNotifier(deviceInfo),
|
||||
builder: (context, _) => const CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
@ -290,6 +307,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
|
||||
alternativeTitle:
|
||||
searchText != '' ? l10n.l_results_for(searchText) : null,
|
||||
capabilities: const [Capability.oath],
|
||||
keyActionsBadge: oathShowActionNotifier(deviceInfo),
|
||||
keyActionsBuilder: hasActions
|
||||
? (context) => oathBuildActions(
|
||||
context,
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Yubico.
|
||||
* Copyright (C) 2022-2024 Yubico.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -35,6 +35,7 @@ import '../models.dart';
|
||||
import 'add_account_dialog.dart';
|
||||
import 'add_account_page.dart';
|
||||
import 'add_multi_account_page.dart';
|
||||
import 'manage_password_dialog.dart';
|
||||
|
||||
/// Calculates the available space for issuer and account name.
|
||||
///
|
||||
@ -178,3 +179,11 @@ Future<void> addOathAccount(BuildContext context, WidgetRef ref,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> managePassword(BuildContext context, WidgetRef ref,
|
||||
DevicePath devicePath, OathState oathState) async {
|
||||
await showBlurDialog(
|
||||
context: context,
|
||||
builder: (context) => ManagePasswordDialog(devicePath, oathState),
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user