YADESK-602 android and nfc exception handling

This commit is contained in:
Adam Velebil 2022-06-10 12:23:52 +02:00
parent 55dd6ba27f
commit b1425a5285
No known key found for this signature in database
GPG Key ID: AC6D6B9D715FC084
12 changed files with 150 additions and 75 deletions

View File

@ -0,0 +1,3 @@
package com.yubico.authenticator
class UserCancelledException : Exception()

View File

@ -76,7 +76,7 @@ object Log {
}
error?.let {
Log.e(TAG, "[$loggerName] ${level.name}: $error".also {
Log.e(TAG, "[$loggerName] ${level.name}(details): $error".also {
_buffer.add(it)
})
}

View File

@ -300,6 +300,7 @@ class OathManager(
override fun deleteAccount(uri: String, result: Result<Void>) {
coroutineScope.launch {
try {
useOathSession("Delete account", true) { session ->
withUnlockedSession(session) {
val credential = getOathCredential(session, uri)
@ -307,6 +308,9 @@ class OathManager(
returnSuccess(result)
}
}
} catch (cause: Throwable) {
returnError(result, cause)
}
}
}
@ -516,7 +520,9 @@ class OathManager(
dialogManager.showDialog(title) {
coroutineScope.launch(Dispatchers.Main) {
Log.d(TAG, "Cancelled Dialog $title")
provideYubiKey(com.yubico.yubikit.core.util.Result.failure(Exception("User canceled")))
provideYubiKey(com.yubico.yubikit.core.util.Result.failure(
UserCancelledException()
))
}
}
}

View File

@ -36,11 +36,18 @@ class AndroidLogger extends LogLevelNotifier {
}
void log(LogRecord record) {
final error = record.error == null
? null
: record.error is Exception
? record.error.toString()
: record.error is String
? record.error
: 'Invalid error type: ${record.error.runtimeType.toString()}';
_channel.invokeMethod('log', {
'loggerName': record.loggerName,
'level': record.level.name,
'message': record.message,
'error': record.error
'error': error
});
}
}

View File

@ -14,12 +14,11 @@ import 'package:yubico_authenticator/core/models.dart';
import 'package:yubico_authenticator/oath/state.dart';
import '../../oath/models.dart';
import '../../user_cancelled_exception.dart';
import 'command_providers.dart';
final _log = Logger('android.oath.state');
class CancelException implements Exception {}
final oathApiProvider = StateProvider((_) => OathApi());
final androidOathStateProvider = StateNotifierProvider.autoDispose
@ -159,10 +158,10 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
@override
Future<OathCode> calculate(OathCredential credential,
{bool update = true}) async {
final OathCode code;
try {
var resultJson = await _api.calculate(credential.id);
var result = jsonDecode(resultJson);
code = OathCode.fromJson(result);
final OathCode code = OathCode.fromJson(result);
_log.debug('Calculate', jsonEncode(code));
if (update && mounted) {
final creds = state!.toList();
@ -170,11 +169,18 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
state = creds..[i] = creds[i].copyWith(code: code);
}
return code;
} on PlatformException catch (pe) {
if (UserCancelledException.isCancellation(pe)) {
throw UserCancelledException();
}
rethrow;
}
}
@override
Future<OathCredential> addAccount(Uri credentialUri,
{bool requireTouch = false}) async {
try {
String resultString =
await _api.addAccount(credentialUri.toString(), requireTouch);
@ -184,7 +190,8 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
if (mounted) {
final newState = state!.toList();
final index = newState.indexWhere((e) => e.credential == addedCredential);
final index =
newState.indexWhere((e) => e.credential == addedCredential);
if (index > -1) {
newState.removeAt(index);
}
@ -197,6 +204,13 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
refresh();
return addedCredential;
} on PlatformException catch (pe) {
if (UserCancelledException.isCancellation(pe)) {
throw UserCancelledException();
}
_log.error('Failed to add account.', pe);
rethrow;
}
}
@override
@ -222,11 +236,13 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
}
return renamedCredential;
} on PlatformException catch (e) {
_log.debug('Failed to execute renameOathCredential: ${e.message}');
} on PlatformException catch (pe) {
_log.debug('Failed to execute renameOathCredential: ${pe.message}');
if (UserCancelledException.isCancellation(pe)) {
throw UserCancelledException();
}
rethrow;
}
return credential;
}
@override
@ -237,8 +253,12 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
if (mounted) {
state = state!.toList()..removeWhere((e) => e.credential == credential);
}
} catch (e) {
_log.debug('Call to delete credential failed: $e');
} on PlatformException catch (e) {
_log.debug('Received exception: $e');
if (UserCancelledException.isCancellation(e)) {
throw UserCancelledException();
}
rethrow;
}
}

View File

@ -99,12 +99,15 @@ UserInteractionController promptUserInteraction(
Widget? icon,
void Function()? onCancel,
}) {
var wasPopped = false;
final controller = _UserInteractionController(
title: title,
description: description,
icon: icon,
onClosed: () {
if (!wasPopped) {
Navigator.of(context).pop();
}
},
);
showDialog(
@ -114,6 +117,7 @@ UserInteractionController promptUserInteraction(
onWillPop: () async {
if (onCancel != null) {
onCancel();
wasPopped = true;
return true;
} else {
return false;

View File

@ -8,6 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../app/message.dart';
import '../../app/models.dart';
import '../../app/state.dart';
import '../../user_cancelled_exception.dart';
import '../../widgets/circle_timer.dart';
import '../../widgets/custom_icons.dart';
import '../models.dart';
@ -122,8 +123,12 @@ mixin AccountMixin {
text: 'Calculate',
icon: const Icon(Icons.refresh),
action: ready
? (context) {
calculateCode(context, ref);
? (context) async {
try {
await calculateCode(context, ref);
} on UserCancelledException catch (_) {
// ignored
}
}
: null,
),

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:yubico_authenticator/user_cancelled_exception.dart';
import 'package:yubico_authenticator/app/state.dart';
import '../models.dart';
@ -103,6 +104,7 @@ class AccountView extends ConsumerWidget with AccountMixin {
);
},
onLongPress: () async {
try {
if (calculateReady) {
await calculateCode(
context,
@ -114,6 +116,9 @@ class AccountView extends ConsumerWidget with AccountMixin {
copyToClipboard(context, ref);
},
);
} on UserCancelledException catch (_) {
// ignored
}
},
leading: showAvatar
? CircleAvatar(

View File

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logging/logging.dart';
import 'package:yubico_authenticator/user_cancelled_exception.dart';
import 'package:yubico_authenticator/app/logging.dart';
import '../../app/message.dart';
@ -169,6 +170,8 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
if (!mounted) return;
Navigator.of(context).pop();
showMessage(context, 'Account added');
} on UserCancelledException catch (_) {
// ignored
} catch (e) {
_log.error('Failed to add account', e);
showMessage(context, 'Failed adding account');

View File

@ -1,5 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:yubico_authenticator/user_cancelled_exception.dart';
import 'package:yubico_authenticator/android/oath/state.dart';
import '../../app/message.dart';
import '../../widgets/responsive_dialog.dart';
@ -29,6 +32,7 @@ class DeleteAccountDialog extends ConsumerWidget {
actions: [
TextButton(
onPressed: () async {
try {
await ref
.read(credentialListProvider(device.path).notifier)
.deleteAccount(credential);
@ -38,6 +42,9 @@ class DeleteAccountDialog extends ConsumerWidget {
showMessage(context, 'Account deleted');
},
);
} on UserCancelledException catch (_) {
// ignored
}
},
child: const Text('Delete'),
),

View File

@ -1,12 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../user_cancelled_exception.dart';
import '../../app/message.dart';
import '../../app/models.dart';
import '../../app/state.dart';
import '../../widgets/responsive_dialog.dart';
import '../models.dart';
import '../state.dart';
import '../../app/models.dart';
import '../../app/state.dart';
import 'utils.dart';
class RenameAccountDialog extends ConsumerStatefulWidget {
@ -59,13 +60,18 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
TextButton(
onPressed: isValid
? () async {
try {
final renamed = await ref
.read(credentialListProvider(widget.device.path).notifier)
.read(
credentialListProvider(widget.device.path).notifier)
.renameAccount(credential,
_issuer.isNotEmpty ? _issuer : null, _account);
if (!mounted) return;
Navigator.of(context).pop(renamed);
showMessage(context, 'Account renamed');
} on UserCancelledException catch (_) {
// ignored
}
}
: null,
child: const Text('Save'),

View File

@ -0,0 +1,9 @@
import 'package:flutter/services.dart';
class UserCancelledException implements Exception {
UserCancelledException();
static isCancellation(PlatformException pe) =>
pe.code == 'UserCancelledException';
}