From 316692adf0399d944d533a0d6bd092eb1c26599a Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 24 May 2024 10:20:51 +0200 Subject: [PATCH 1/9] read device info on initial NFC discovery --- .../com/yubico/authenticator/MainActivity.kt | 75 ++++++++++--------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt index 36a7eed1..2619196b 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -109,7 +109,7 @@ class MainActivity : FlutterFragmentActivity() { yubikit.startNfcDiscovery( nfcConfiguration.disableNfcDiscoverySound(appPreferences.silenceNfcSounds), this, - ::processYubiKey + ::launchProcessYubiKey ) hasNfc = true } catch (e: NfcNotAvailable) { @@ -131,7 +131,7 @@ class MainActivity : FlutterFragmentActivity() { logger.debug("YubiKey was disconnected, stopping usb discovery") stopUsbDiscovery() } - processYubiKey(device) + launchProcessYubiKey(device) } } @@ -214,7 +214,7 @@ class MainActivity : FlutterFragmentActivity() { val device = NfcYubiKeyDevice(tag, nfcConfiguration.timeout, executor) lifecycleScope.launch { try { - contextManager?.processYubiKey(device) + processYubiKey(device) device.remove { executor.shutdown() startNfcDiscovery() @@ -269,38 +269,43 @@ class MainActivity : FlutterFragmentActivity() { } } - private fun processYubiKey(device: YubiKeyDevice) { + private suspend fun processYubiKey(device: YubiKeyDevice) { + val deviceInfo = getDeviceInfo(device) + deviceManager.setDeviceInfo(deviceInfo) + + if (deviceInfo == null) { + logger.debug("Device info is null") + return + } + + val supportedContexts = DeviceManager.getSupportedContexts(deviceInfo) + logger.debug("Connected key supports: {}", supportedContexts) + if (!supportedContexts.contains(viewModel.appContext.value)) { + val preferredContext = DeviceManager.getPreferredContext(supportedContexts) + logger.debug( + "Current context ({}) is not supported by the key. Using preferred context {}", + viewModel.appContext.value, + preferredContext + ) + switchContext(preferredContext) + } + + if (contextManager == null) { + switchContext(DeviceManager.getPreferredContext(supportedContexts)) + } + + contextManager?.let { + try { + it.processYubiKey(device) + } catch (e: Throwable) { + logger.error("Error processing YubiKey in AppContextManager", e) + } + } + } + + private fun launchProcessYubiKey(device: YubiKeyDevice) { lifecycleScope.launch { - val deviceInfo = getDeviceInfo(device) - deviceManager.setDeviceInfo(deviceInfo) - - if (deviceInfo == null) { - return@launch - } - - val supportedContexts = DeviceManager.getSupportedContexts(deviceInfo) - logger.debug("Connected key supports: {}", supportedContexts) - if (!supportedContexts.contains(viewModel.appContext.value)) { - val preferredContext = DeviceManager.getPreferredContext(supportedContexts) - logger.debug( - "Current context ({}) is not supported by the key. Using preferred context {}", - viewModel.appContext.value, - preferredContext - ) - switchContext(preferredContext) - } - - if (contextManager == null) { - switchContext(DeviceManager.getPreferredContext(supportedContexts)) - } - - contextManager?.let { - try { - it.processYubiKey(device) - } catch (e: Throwable) { - logger.error("Error processing YubiKey in AppContextManager", e) - } - } + processYubiKey(device) } } @@ -342,7 +347,7 @@ class MainActivity : FlutterFragmentActivity() { viewModel.appContext.observe(this) { switchContext(it) - viewModel.connectedYubiKey.value?.let(::processYubiKey) + viewModel.connectedYubiKey.value?.let(::launchProcessYubiKey) } } From 2a89a193783571d17b331db1a6430c2865fa6ff3 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 24 May 2024 13:32:05 +0200 Subject: [PATCH 2/9] optimize lifecycle of Android Credential List --- lib/android/oath/state.dart | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index 5e868da4..03b0bdcf 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -207,10 +207,8 @@ final addCredentialsToAnyProvider = Provider( final androidCredentialListProvider = StateNotifierProvider.autoDispose .family?, DevicePath>( (ref, devicePath) { - var notifier = _AndroidCredentialListNotifier( - ref.watch(withContextProvider), - ref.watch(currentDeviceProvider)?.transport == Transport.usb, - ); + var notifier = + _AndroidCredentialListNotifier(ref.watch(withContextProvider), ref); return notifier; }, ); @@ -218,22 +216,15 @@ final androidCredentialListProvider = StateNotifierProvider.autoDispose class _AndroidCredentialListNotifier extends OathCredentialListNotifier { final _events = const EventChannel('android.oath.credentials'); final WithContext _withContext; - final bool _isUsbAttached; + final Ref _ref; late StreamSubscription _sub; - _AndroidCredentialListNotifier(this._withContext, this._isUsbAttached) - : super() { + _AndroidCredentialListNotifier(this._withContext, this._ref) : super() { _sub = _events.receiveBroadcastStream().listen((event) { final json = jsonDecode(event); List? newState = json != null ? List.from((json as List).map((e) => OathPair.fromJson(e)).toList()) : null; - if (state != null && newState == null) { - // If we go from non-null to null this means we should stop listening to - // avoid receiving a message for a different notifier as there is only - // one channel. - _sub.cancel(); - } state = newState; }); } @@ -249,7 +240,7 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier { // Prompt for touch if needed UserInteractionController? controller; Timer? touchTimer; - if (_isUsbAttached) { + if (_ref.read(currentDeviceProvider)?.transport == Transport.usb) { void triggerTouchPrompt() async { controller = await _withContext( (context) async { From 77d0d4a95a21329ec16b8af7c1e9dbd398310415 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Mon, 27 May 2024 09:09:38 +0200 Subject: [PATCH 3/9] Ignore NFC taps in app if USB YubiKey is connected --- .../kotlin/com/yubico/authenticator/MainActivity.kt | 10 +++++++--- .../com/yubico/authenticator/oath/OathManager.kt | 4 +++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt index 2619196b..d07a7080 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -108,9 +108,13 @@ class MainActivity : FlutterFragmentActivity() { logger.debug("Starting nfc discovery") yubikit.startNfcDiscovery( nfcConfiguration.disableNfcDiscoverySound(appPreferences.silenceNfcSounds), - this, - ::launchProcessYubiKey - ) + this + ) { nfcYubiKeyDevice -> + if (!deviceManager.isUsbKeyConnected()) { + launchProcessYubiKey(nfcYubiKeyDevice) + } + } + hasNfc = true } catch (e: NfcNotAvailable) { hasNfc = false diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathManager.kt index adc592ee..0606569d 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathManager.kt @@ -135,10 +135,12 @@ class OathManager( refreshJob = coroutineScope.launch { val delayMs = earliest - now - logger.debug("Will execute refresh in {}ms", delayMs) + logger.debug("XXX Will execute refresh in {}ms", delayMs) if (delayMs > 0) { delay(delayMs) } + logger.debug("XXX {}ms later will refresh!", delayMs) + ensureActive() val currentState = lifecycleOwner.lifecycle.currentState if (currentState.isAtLeast(Lifecycle.State.STARTED)) { requestRefresh() From 1a2e73a4ba574410aa5e46ecd84676f5e59680b2 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Tue, 28 May 2024 11:26:23 +0200 Subject: [PATCH 4/9] fix custom key color selection rendering --- lib/home/views/home_screen.dart | 34 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/lib/home/views/home_screen.dart b/lib/home/views/home_screen.dart index c92ee40f..a2526eac 100644 --- a/lib/home/views/home_screen.dart +++ b/lib/home/views/home_screen.dart @@ -54,7 +54,7 @@ class _HomeScreenState extends ConsumerState { final enabledCapabilities = widget.deviceData.info.config .enabledCapabilities[widget.deviceData.node.transport] ?? 0; - final primaryColor = ref.watch(defaultColorProvider); + final primaryColor = ref.watch(primaryColorProvider); // We need this to avoid unwanted app switch animation if (hide) { @@ -219,19 +219,14 @@ class _DeviceColor extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final l10n = AppLocalizations.of(context)!; - final theme = Theme.of(context); - final primaryColor = ref.watch(defaultColorProvider); - final defaultColor = - (isAndroid && ref.read(androidSdkVersionProvider) >= 31) - ? theme.colorScheme.onSurface - : primaryColor; + final defaultColor = ref.watch(defaultColorProvider); final customColor = initialCustomization.color; return ChoiceFilterChip( disableHover: true, value: customColor, items: const [null], - selected: customColor != null && customColor != defaultColor, + selected: customColor != null && customColor.value != defaultColor.value, itemBuilder: (e) => Wrap( alignment: WrapAlignment.center, runSpacing: 8, @@ -250,32 +245,31 @@ class _DeviceColor extends ConsumerWidget { Colors.lightGreen ].map((e) => _ColorButton( color: e, - isSelected: customColor == e, + isSelected: customColor?.value == e.value, onPressed: () { _updateColor(e, ref); Navigator.of(context).pop(); }, )), - // remove color button + // "use default color" button RawMaterialButton( onPressed: () { _updateColor(null, ref); Navigator.of(context).pop(); }, constraints: const BoxConstraints(minWidth: 26.0, minHeight: 26.0), - fillColor: (isAndroid && ref.read(androidSdkVersionProvider) >= 31) - ? theme.colorScheme.onSurface - : primaryColor, + fillColor: defaultColor, hoverColor: Colors.black12, shape: const CircleBorder(), - child: Icon( - Symbols.cancel, - size: 16, - color: customColor == null - ? theme.colorScheme.onSurface - : theme.colorScheme.surface.withOpacity(0.2), - ), + child: Icon(customColor == null ? Symbols.circle : Symbols.clear, + fill: 1, + size: 16, + weight: 700, + opticalSize: 20, + color: defaultColor.computeLuminance() > 0.7 + ? Colors.grey // for bright colors + : Colors.white), ), ], ), From 5fd93a390f9af9070f7a285747436b4f87d7da2b Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Tue, 28 May 2024 12:57:37 +0200 Subject: [PATCH 5/9] sort fingerprints --- lib/android/fido/state.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index 8cd1e41b..3c6182b0 100644 --- a/lib/android/fido/state.dart +++ b/lib/android/fido/state.dart @@ -17,6 +17,7 @@ import 'dart:async'; import 'dart:convert'; +import 'package:collection/collection.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; @@ -177,8 +178,10 @@ class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier { if (json == null) { state = const AsyncValue.loading(); } else { - List newState = List.from( - (json as List).map((e) => Fingerprint.fromJson(e)).toList()); + List newState = List.from((json as List) + .map((e) => Fingerprint.fromJson(e)) + .sortedBy((f) => f.label.toLowerCase()) + .toList()); state = AsyncValue.data(newState); } }, onError: (err, stackTrace) { From bbf0fd7f47b5db52a4082c1b0879d57769a4d6d2 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Tue, 28 May 2024 14:20:58 +0200 Subject: [PATCH 6/9] bump version to 7.0.1 --- helper/version_info.txt | 8 ++++---- lib/version.dart | 4 ++-- pubspec.yaml | 2 +- resources/win/release-win.ps1 | 2 +- resources/win/yubioath-desktop.wxs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/helper/version_info.txt b/helper/version_info.txt index b66c5954..f4ee95bb 100755 --- a/helper/version_info.txt +++ b/helper/version_info.txt @@ -6,8 +6,8 @@ VSVersionInfo( ffi=FixedFileInfo( # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) # Set not needed items to zero 0. - filevers=(7, 0, 0, 0), - prodvers=(7, 0, 0, 0), + filevers=(7, 0, 1, 0), + prodvers=(7, 0, 1, 0), # Contains a bitmask that specifies the valid bits 'flags'r mask=0x3f, # Contains a bitmask that specifies the Boolean attributes of the file. @@ -31,11 +31,11 @@ VSVersionInfo( '040904b0', [StringStruct('CompanyName', 'Yubico'), StringStruct('FileDescription', 'Yubico Authenticator Helper'), - StringStruct('FileVersion', '7.0.0'), + StringStruct('FileVersion', '7.0.1'), StringStruct('LegalCopyright', 'Copyright (c) Yubico'), StringStruct('OriginalFilename', 'authenticator-helper.exe'), StringStruct('ProductName', 'Yubico Authenticator'), - StringStruct('ProductVersion', '7.0.0')]) + StringStruct('ProductVersion', '7.0.1')]) ]), VarFileInfo([VarStruct('Translation', [1033, 1200])]) ] diff --git a/lib/version.dart b/lib/version.dart index 6dfba3e5..f334a6e6 100755 --- a/lib/version.dart +++ b/lib/version.dart @@ -1,5 +1,5 @@ // GENERATED CODE - DO NOT MODIFY BY HAND // This file is generated by running ./set-version.py -const String version = '7.0.0'; -const int build = 70000; +const String version = '7.0.1'; +const int build = 70001; diff --git a/pubspec.yaml b/pubspec.yaml index 046e7213..34aac2b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # This field is updated by running ./set-version.py # DO NOT MANUALLY EDIT THIS! -version: 7.0.0+70000 +version: 7.0.1+70001 environment: sdk: '>=3.0.0 <4.0.0' diff --git a/resources/win/release-win.ps1 b/resources/win/release-win.ps1 index 753a2b95..32eeddf0 100644 --- a/resources/win/release-win.ps1 +++ b/resources/win/release-win.ps1 @@ -1,4 +1,4 @@ -$version="7.0.0" +$version="7.0.1" echo "Clean-up of old files" rm *.msi diff --git a/resources/win/yubioath-desktop.wxs b/resources/win/yubioath-desktop.wxs index 69640f7f..79e084fe 100644 --- a/resources/win/yubioath-desktop.wxs +++ b/resources/win/yubioath-desktop.wxs @@ -1,7 +1,7 @@ - + From e23f599604a5ef7f549d5c8c589cab914d650013 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Tue, 28 May 2024 14:21:44 +0200 Subject: [PATCH 7/9] update NEWS --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 83c21e43..34c1c5c4 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,9 @@ +* Version 7.0.1 (released 2024-05-30) Android only release + ** Fix: Opening the app by NFC tap needs another tap to reveal accounts. + ** Fix: NFC devices attached to mobile phone prevent usage of USB YubiKeys. + ** Fix: Invalid colors shown in customization views for Android Dynamic color. + ** Fix: Fingerprints are shown in random order. + * Version 7.0.0 (released 2024-05-06) ** UI: Add home screen with device information, customization options, and factory reset. ** UI: Add search filtering to Passkeys and display more information. From 5e9dfbc29b775d3682c450727bb3a483c87436cd Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Wed, 29 May 2024 11:40:35 +0200 Subject: [PATCH 8/9] revert forgotten logging changes --- .../src/main/kotlin/com/yubico/authenticator/MainActivity.kt | 1 - .../main/kotlin/com/yubico/authenticator/oath/OathManager.kt | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt index d07a7080..1d26c67c 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -278,7 +278,6 @@ class MainActivity : FlutterFragmentActivity() { deviceManager.setDeviceInfo(deviceInfo) if (deviceInfo == null) { - logger.debug("Device info is null") return } diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathManager.kt index 0606569d..adc592ee 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathManager.kt @@ -135,12 +135,10 @@ class OathManager( refreshJob = coroutineScope.launch { val delayMs = earliest - now - logger.debug("XXX Will execute refresh in {}ms", delayMs) + logger.debug("Will execute refresh in {}ms", delayMs) if (delayMs > 0) { delay(delayMs) } - logger.debug("XXX {}ms later will refresh!", delayMs) - ensureActive() val currentState = lifecycleOwner.lifecycle.currentState if (currentState.isAtLeast(Lifecycle.State.STARTED)) { requestRefresh() From 3d48a057376446d409a71d349013c3e3cb08b6ac Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Thu, 30 May 2024 09:15:39 +0200 Subject: [PATCH 9/9] bump version to 7.0.2-dev.0 --- helper/version_info.txt | 8 ++++---- lib/version.dart | 4 ++-- pubspec.yaml | 2 +- resources/win/release-win.ps1 | 2 +- resources/win/yubioath-desktop.wxs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/helper/version_info.txt b/helper/version_info.txt index f4ee95bb..c4c014f7 100755 --- a/helper/version_info.txt +++ b/helper/version_info.txt @@ -6,8 +6,8 @@ VSVersionInfo( ffi=FixedFileInfo( # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) # Set not needed items to zero 0. - filevers=(7, 0, 1, 0), - prodvers=(7, 0, 1, 0), + filevers=(7, 0, 2, 0), + prodvers=(7, 0, 2, 0), # Contains a bitmask that specifies the valid bits 'flags'r mask=0x3f, # Contains a bitmask that specifies the Boolean attributes of the file. @@ -31,11 +31,11 @@ VSVersionInfo( '040904b0', [StringStruct('CompanyName', 'Yubico'), StringStruct('FileDescription', 'Yubico Authenticator Helper'), - StringStruct('FileVersion', '7.0.1'), + StringStruct('FileVersion', '7.0.2-dev.0'), StringStruct('LegalCopyright', 'Copyright (c) Yubico'), StringStruct('OriginalFilename', 'authenticator-helper.exe'), StringStruct('ProductName', 'Yubico Authenticator'), - StringStruct('ProductVersion', '7.0.1')]) + StringStruct('ProductVersion', '7.0.2-dev.0')]) ]), VarFileInfo([VarStruct('Translation', [1033, 1200])]) ] diff --git a/lib/version.dart b/lib/version.dart index f334a6e6..936a7305 100755 --- a/lib/version.dart +++ b/lib/version.dart @@ -1,5 +1,5 @@ // GENERATED CODE - DO NOT MODIFY BY HAND // This file is generated by running ./set-version.py -const String version = '7.0.1'; -const int build = 70001; +const String version = '7.0.2-dev.0'; +const int build = 70002; diff --git a/pubspec.yaml b/pubspec.yaml index 34aac2b0..4955ce8b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # This field is updated by running ./set-version.py # DO NOT MANUALLY EDIT THIS! -version: 7.0.1+70001 +version: 7.0.2-dev.0+70002 environment: sdk: '>=3.0.0 <4.0.0' diff --git a/resources/win/release-win.ps1 b/resources/win/release-win.ps1 index 32eeddf0..f9bf9bc9 100644 --- a/resources/win/release-win.ps1 +++ b/resources/win/release-win.ps1 @@ -1,4 +1,4 @@ -$version="7.0.1" +$version="7.0.2-dev.0" echo "Clean-up of old files" rm *.msi diff --git a/resources/win/yubioath-desktop.wxs b/resources/win/yubioath-desktop.wxs index 79e084fe..fc7895cc 100644 --- a/resources/win/yubioath-desktop.wxs +++ b/resources/win/yubioath-desktop.wxs @@ -1,7 +1,7 @@ - +