mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-22 00:12:09 +03:00
Merge branch main into adamve/nfc_activity_widget
This commit is contained in:
commit
3873ec4514
@ -18,6 +18,7 @@ package com.yubico.authenticator.fido
|
||||
|
||||
import com.yubico.authenticator.device.DeviceManager
|
||||
import com.yubico.authenticator.fido.data.YubiKitFidoSession
|
||||
import com.yubico.authenticator.yubikit.DeviceInfoHelper.Companion.getDeviceInfo
|
||||
import com.yubico.authenticator.yubikit.withConnection
|
||||
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice
|
||||
import com.yubico.yubikit.core.fido.FidoConnection
|
||||
@ -43,9 +44,13 @@ class FidoConnectionHelper(private val deviceManager: DeviceManager) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> useSession(block: (YubiKitFidoSession) -> T): T {
|
||||
suspend fun <T> useSession(
|
||||
updateDeviceInfo: Boolean = false,
|
||||
block: (YubiKitFidoSession) -> T
|
||||
): T {
|
||||
FidoManager.updateDeviceInfo.set(updateDeviceInfo)
|
||||
return deviceManager.withKey(
|
||||
onUsb = { useSessionUsb(it, block) },
|
||||
onUsb = { useSessionUsb(it, updateDeviceInfo, block) },
|
||||
onNfc = { useSessionNfc(block) },
|
||||
onDialogCancelled = {
|
||||
pendingAction?.invoke(Result.failure(CancellationException()))
|
||||
@ -56,9 +61,14 @@ class FidoConnectionHelper(private val deviceManager: DeviceManager) {
|
||||
|
||||
suspend fun <T> useSessionUsb(
|
||||
device: UsbYubiKeyDevice,
|
||||
updateDeviceInfo: Boolean = false,
|
||||
block: (YubiKitFidoSession) -> T
|
||||
): T = device.withConnection<FidoConnection, T> {
|
||||
block(YubiKitFidoSession(it))
|
||||
}.also {
|
||||
if (updateDeviceInfo) {
|
||||
deviceManager.setDeviceInfo(getDeviceInfo(device))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> useSessionNfc(
|
||||
|
@ -31,6 +31,7 @@ import com.yubico.authenticator.fido.data.Session
|
||||
import com.yubico.authenticator.fido.data.SessionInfo
|
||||
import com.yubico.authenticator.fido.data.YubiKitFidoSession
|
||||
import com.yubico.authenticator.setHandler
|
||||
import com.yubico.authenticator.yubikit.DeviceInfoHelper.Companion.getDeviceInfo
|
||||
import com.yubico.authenticator.yubikit.withConnection
|
||||
import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyDevice
|
||||
import com.yubico.yubikit.core.YubiKeyConnection
|
||||
@ -62,6 +63,7 @@ import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.util.Arrays
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
typealias FidoAction = (Result<YubiKitFidoSession, Exception>) -> Unit
|
||||
|
||||
@ -82,6 +84,7 @@ class FidoManager(
|
||||
}
|
||||
|
||||
companion object {
|
||||
val updateDeviceInfo = AtomicBoolean(false)
|
||||
fun getPreferredPinUvAuthProtocol(infoData: InfoData): PinUvAuthProtocol {
|
||||
val pinUvAuthProtocols = infoData.pinUvAuthProtocols
|
||||
val pinSupported = infoData.options["clientPin"] != null
|
||||
@ -124,6 +127,8 @@ class FidoManager(
|
||||
pinStore
|
||||
)
|
||||
|
||||
|
||||
|
||||
init {
|
||||
pinRetries = null
|
||||
|
||||
@ -177,6 +182,7 @@ class FidoManager(
|
||||
fidoChannel.setMethodCallHandler(null)
|
||||
fidoViewModel.clearSessionState()
|
||||
fidoViewModel.updateCredentials(null)
|
||||
connectionHelper.cancelPending()
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
@ -191,6 +197,10 @@ class FidoManager(
|
||||
processYubiKey(connection, device)
|
||||
}
|
||||
}
|
||||
|
||||
if (updateDeviceInfo.getAndSet(false)) {
|
||||
deviceManager.setDeviceInfo(getDeviceInfo(device))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// something went wrong, try to get DeviceInfo from any available connection type
|
||||
logger.error("Failure when processing YubiKey: ", e)
|
||||
@ -383,7 +393,7 @@ class FidoManager(
|
||||
}
|
||||
|
||||
private suspend fun setPin(pin: CharArray?, newPin: CharArray): String =
|
||||
connectionHelper.useSession { fidoSession ->
|
||||
connectionHelper.useSession(updateDeviceInfo = true) { fidoSession ->
|
||||
try {
|
||||
val clientPin =
|
||||
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
||||
|
@ -167,8 +167,9 @@ class FidoResetHelper(
|
||||
coroutineScope.launch(Dispatchers.Main) {
|
||||
fidoViewModel.updateResetState(FidoResetState.Touch)
|
||||
logger.debug("Waiting for touch")
|
||||
deviceManager.withKey { usbYubiKeyDevice ->
|
||||
connectionHelper.useSessionUsb(usbYubiKeyDevice) { fidoSession ->
|
||||
deviceManager.withKey {
|
||||
usbYubiKeyDevice ->
|
||||
connectionHelper.useSessionUsb(usbYubiKeyDevice, updateDeviceInfo = true) { fidoSession ->
|
||||
resetCommandState = CommandState()
|
||||
try {
|
||||
if (cancelReset) {
|
||||
@ -219,6 +220,7 @@ class FidoResetHelper(
|
||||
}
|
||||
fidoViewModel.updateResetState(FidoResetState.Touch)
|
||||
try {
|
||||
FidoManager.updateDeviceInfo.set(true)
|
||||
connectionHelper.useSessionNfc { fidoSession ->
|
||||
doReset(fidoSession)
|
||||
continuation.resume(Unit)
|
||||
|
@ -209,6 +209,7 @@ class OathManager(
|
||||
oathChannel.setMethodCallHandler(null)
|
||||
oathViewModel.clearSession()
|
||||
oathViewModel.updateCredentials(mapOf())
|
||||
pendingAction?.invoke(Result.failure(Exception()))
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,9 @@ import '../../exception/no_data_exception.dart';
|
||||
import '../../exception/platform_exception_decoder.dart';
|
||||
import '../../oath/models.dart';
|
||||
import '../../oath/state.dart';
|
||||
import '../../widgets/toast.dart';
|
||||
import '../method_channel_notifier.dart';
|
||||
import '../tap_request_dialog.dart';
|
||||
|
||||
final _log = Logger('android.oath.state');
|
||||
|
||||
@ -146,27 +148,45 @@ class _AndroidOathStateNotifier extends OathStateNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
// Converts Platform exception during Add Account operation
|
||||
// Returns CancellationException for situations we don't want to show a Toast
|
||||
Exception _decodeAddAccountException(PlatformException platformException) {
|
||||
final decodedException = platformException.decode();
|
||||
Exception handlePlatformException(
|
||||
Ref ref, PlatformException platformException) {
|
||||
final decoded = platformException.decode();
|
||||
final l10n = ref.read(l10nProvider);
|
||||
final withContext = ref.read(withContextProvider);
|
||||
|
||||
// Auth required, the app will show Unlock dialog
|
||||
if (decodedException is ApduException && decodedException.sw == 0x6982) {
|
||||
_log.error('Add account failed: Auth required');
|
||||
return CancellationException();
|
||||
toast(String message, {bool popStack = false}) =>
|
||||
withContext((context) async {
|
||||
ref.read(androidDialogProvider.notifier).closeDialog();
|
||||
if (popStack) {
|
||||
Navigator.of(context).popUntil((route) {
|
||||
return route.isFirst;
|
||||
});
|
||||
}
|
||||
showToast(context, message, duration: const Duration(seconds: 4));
|
||||
});
|
||||
|
||||
switch (decoded) {
|
||||
case ApduException apduException:
|
||||
if (apduException.sw == 0x6985) {
|
||||
// pop stack to show the OATH view with "Set password"
|
||||
toast(l10n.l_add_account_password_required, popStack: true);
|
||||
return CancellationException();
|
||||
}
|
||||
if (apduException.sw == 0x6982) {
|
||||
toast(l10n.l_add_account_unlock_required);
|
||||
return CancellationException();
|
||||
}
|
||||
case PlatformException pe:
|
||||
if (pe.code == 'JobCancellationException') {
|
||||
// pop stack to show FIDO view
|
||||
toast(l10n.l_add_account_func_missing, popStack: true);
|
||||
return CancellationException();
|
||||
} else if (pe.code == 'IllegalArgumentException') {
|
||||
toast(l10n.l_add_account_already_exists);
|
||||
return CancellationException();
|
||||
}
|
||||
}
|
||||
|
||||
// Thrown in native code when the account already exists on the YubiKey
|
||||
// The entry dialog will show an error message and that is why we convert
|
||||
// this to CancellationException to avoid showing a Toast
|
||||
if (platformException.code == 'IllegalArgumentException') {
|
||||
_log.error('Add account failed: Account already exists');
|
||||
return CancellationException();
|
||||
}
|
||||
|
||||
// original exception
|
||||
return decodedException;
|
||||
return decoded;
|
||||
}
|
||||
|
||||
final addCredentialToAnyProvider =
|
||||
@ -179,7 +199,7 @@ final addCredentialToAnyProvider =
|
||||
var result = jsonDecode(resultString);
|
||||
return OathCredential.fromJson(result['credential']);
|
||||
} on PlatformException catch (pe) {
|
||||
throw _decodeAddAccountException(pe);
|
||||
throw handlePlatformException(ref, pe);
|
||||
}
|
||||
});
|
||||
|
||||
@ -290,7 +310,7 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
|
||||
var result = jsonDecode(resultString);
|
||||
return OathCredential.fromJson(result['credential']);
|
||||
} on PlatformException catch (pe) {
|
||||
throw _decodeAddAccountException(pe);
|
||||
throw handlePlatformException(_ref, pe);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ class _DialogProvider extends Notifier<int> {
|
||||
break;
|
||||
|
||||
case 'close':
|
||||
notifier.sendCommand(hideNfcView);
|
||||
closeDialog();
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -147,6 +147,10 @@ class _DialogProvider extends Notifier<int> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void closeDialog() {
|
||||
ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView);
|
||||
}
|
||||
|
||||
void cancelDialog() async {
|
||||
explicitAction = false;
|
||||
await _channel.invokeMethod('cancel');
|
||||
|
@ -639,6 +639,8 @@ class _AppPageState extends ConsumerState<AppPage> {
|
||||
},
|
||||
),
|
||||
),
|
||||
iconTheme: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
scrolledUnderElevation: 0.0,
|
||||
leadingWidth: hasRail ? 84 : null,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
@ -730,7 +732,10 @@ class _AppPageState extends ConsumerState<AppPage> {
|
||||
.read(_detailViewVisibilityProvider.notifier)
|
||||
.toggleExpanded();
|
||||
},
|
||||
icon: const Icon(Symbols.more_vert),
|
||||
icon: const Icon(
|
||||
Symbols.more_vert,
|
||||
weight: 600.0,
|
||||
),
|
||||
iconSize: 24,
|
||||
tooltip: showDetailView ? l10n.s_hide_menu : l10n.s_show_menu,
|
||||
padding: const EdgeInsets.all(12),
|
||||
|
@ -69,6 +69,7 @@ class MainPage extends ConsumerWidget {
|
||||
'oath_add_account',
|
||||
'oath_icon_pack_dialog',
|
||||
'android_qr_scanner_view',
|
||||
'android_alert_dialog'
|
||||
].contains(route.settings.name);
|
||||
});
|
||||
});
|
||||
|
@ -431,6 +431,10 @@
|
||||
"message": {}
|
||||
}
|
||||
},
|
||||
"l_add_account_password_required": null,
|
||||
"l_add_account_unlock_required": null,
|
||||
"l_add_account_already_exists": null,
|
||||
"l_add_account_func_missing": null,
|
||||
"l_account_name_required": "Ihr Konto muss einen Namen haben",
|
||||
"l_name_already_exists": "Für diesen Aussteller existiert dieser Name bereits",
|
||||
"l_account_already_exists": "Dieses Konto existiert bereits auf dem YubiKey",
|
||||
|
@ -431,6 +431,10 @@
|
||||
"message": {}
|
||||
}
|
||||
},
|
||||
"l_add_account_password_required": "Password required",
|
||||
"l_add_account_unlock_required": "Unlock required",
|
||||
"l_add_account_already_exists": "Account already exists",
|
||||
"l_add_account_func_missing": "Functionality missing or disabled",
|
||||
"l_account_name_required": "Your account must have a name",
|
||||
"l_name_already_exists": "This name already exists for the issuer",
|
||||
"l_account_already_exists": "This account already exists on the YubiKey",
|
||||
|
@ -431,6 +431,10 @@
|
||||
"message": {}
|
||||
}
|
||||
},
|
||||
"l_add_account_password_required": null,
|
||||
"l_add_account_unlock_required": null,
|
||||
"l_add_account_already_exists": null,
|
||||
"l_add_account_func_missing": null,
|
||||
"l_account_name_required": "Votre compte doit avoir un nom",
|
||||
"l_name_already_exists": "Ce nom existe déjà pour l'émetteur",
|
||||
"l_account_already_exists": "Ce compte existe déjà sur la YubiKey",
|
||||
|
@ -431,6 +431,10 @@
|
||||
"message": {}
|
||||
}
|
||||
},
|
||||
"l_add_account_password_required": null,
|
||||
"l_add_account_unlock_required": null,
|
||||
"l_add_account_already_exists": null,
|
||||
"l_add_account_func_missing": null,
|
||||
"l_account_name_required": "アカウントには名前が必要です",
|
||||
"l_name_already_exists": "この名前は発行者にすでに存在します",
|
||||
"l_account_already_exists": "このアカウントはYubiKeyにすでに存在します",
|
||||
|
@ -431,6 +431,10 @@
|
||||
"message": {}
|
||||
}
|
||||
},
|
||||
"l_add_account_password_required": null,
|
||||
"l_add_account_unlock_required": null,
|
||||
"l_add_account_already_exists": null,
|
||||
"l_add_account_func_missing": null,
|
||||
"l_account_name_required": "Twoje konto musi mieć nazwę",
|
||||
"l_name_already_exists": "Ta nazwa już istnieje dla tego wydawcy",
|
||||
"l_account_already_exists": "To konto już istnieje w YubiKey",
|
||||
|
@ -431,6 +431,10 @@
|
||||
"message": {}
|
||||
}
|
||||
},
|
||||
"l_add_account_password_required": null,
|
||||
"l_add_account_unlock_required": null,
|
||||
"l_add_account_already_exists": null,
|
||||
"l_add_account_func_missing": null,
|
||||
"l_account_name_required": "Tài khoản của bạn phải có tên",
|
||||
"l_name_already_exists": "Tên này đã tồn tại cho nhà phát hành",
|
||||
"l_account_already_exists": "Tài khoản này đã tồn tại trên YubiKey",
|
||||
|
Loading…
Reference in New Issue
Block a user