first stab at testing OATH passwords from password management screen

This commit is contained in:
Joakim Troëng 2022-05-11 21:42:14 +02:00
parent 4efa031910
commit 2d231bca99
No known key found for this signature in database
GPG Key ID: BE887BDFCD88A558
2 changed files with 195 additions and 13 deletions

View File

@ -0,0 +1,186 @@
import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:yubico_authenticator/android/init.dart' as android;
import 'package:yubico_authenticator/app/views/no_device_screen.dart';
import 'package:yubico_authenticator/core/state.dart';
import 'package:yubico_authenticator/desktop/init.dart' as desktop;
import 'package:yubico_authenticator/oath/views/oath_screen.dart';
Future<void> addDelay(int ms) async {
await Future<void>.delayed(Duration(milliseconds: ms));
}
int randomNum(int max) {
var r = Random.secure();
return r.nextInt(max);
}
String randomPadded() {
return randomNum(999).toString().padLeft(3, '0');
}
String generateRandomIssuer() {
return 'i' + randomPadded();
}
String generateRandomName() {
return 'n' + randomPadded();
}
String generateRandomSecret() {
final random = Random.secure();
return base64Encode(List.generate(10, (_) => random.nextInt(256)));
}
void main() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
group('OATH tests', () {
/// For these tests there are defined Keys in manage_password_dialog.dart
testWidgets('set password', (WidgetTester tester) async {
final Widget initializedApp;
if (isDesktop) {
initializedApp = await desktop.initialize([]);
} else if (isAndroid) {
initializedApp = await android.initialize();
} else {
throw UnimplementedError('Platform not supported');
}
await tester.pumpWidget(initializedApp);
await tester.pump(const Duration(milliseconds: 500));
expect(find.byType(NoDeviceScreen), findsNothing, reason: 'No YubiKey connected');
expect(find.byType(OathScreen), findsOneWidget);
await tester.tap(find.byType(FloatingActionButton));
await tester.pump(const Duration(milliseconds: 300));
await tester.tap(find.text('Set password'));
await tester.pump(const Duration(milliseconds: 300));
var first_password = 'aaa111';
/// TODO: I don't understand why these Keys don't work as intended
await tester.enterText(find.byKey(const Key('new oath password')), first_password);
await tester.enterText(find.byKey(const Key('confirm oath password')), first_password);
await tester.pump();
await tester.tap(find.text('Save'));
await tester.pump(const Duration(milliseconds: 300));
/// TODO: verification of state here: restarting app and entering password
await tester.pump(const Duration(seconds: 3));
});
testWidgets('change password', (WidgetTester tester) async {
final Widget initializedApp;
if (isDesktop) {
initializedApp = await desktop.initialize([]);
} else if (isAndroid) {
initializedApp = await android.initialize();
} else {
throw UnimplementedError('Platform not supported');
}
await tester.pumpWidget(initializedApp);
await tester.pump(const Duration(milliseconds: 500));
expect(find.byType(NoDeviceScreen), findsNothing, reason: 'No YubiKey connected');
expect(find.byType(OathScreen), findsOneWidget);
await tester.tap(find.byType(FloatingActionButton));
await tester.pump(const Duration(milliseconds: 300));
await tester.tap(find.text('Manage password'));
await tester.pump(const Duration(milliseconds: 300));
var current_password = 'aaa111';
var second_password = 'bbb222';
/// TODO: I don't understand why these Keys don't work as intended
await tester.enterText(find.byKey(const Key('current oath password')), current_password);
await tester.enterText(find.byKey(const Key('new oath password')), second_password);
await tester.enterText(find.byKey(const Key('confirm oath password')), second_password);
await tester.pump();
await tester.tap(find.text('Save'));
await tester.pump(const Duration(milliseconds: 300));
/// TODO: verification of state here: restarting app and entering password
await tester.pump(const Duration(seconds: 3));
});
testWidgets('remove password', (WidgetTester tester) async {
final Widget initializedApp;
if (isDesktop) {
initializedApp = await desktop.initialize([]);
} else if (isAndroid) {
initializedApp = await android.initialize();
} else {
throw UnimplementedError('Platform not supported');
}
await tester.pumpWidget(initializedApp);
await tester.pump(const Duration(milliseconds: 500));
expect(find.byType(NoDeviceScreen), findsNothing, reason: 'No YubiKey connected');
expect(find.byType(OathScreen), findsOneWidget);
await tester.tap(find.byType(FloatingActionButton));
await tester.pump(const Duration(milliseconds: 300));
await tester.tap(find.text('Manage password'));
await tester.pump(const Duration(milliseconds: 300));
var second_password = 'bbb222';
await tester.enterText(find.byKey(const Key('current oath password')), second_password);
await tester.pump();
await tester.tap(find.text('Remove password'));
await tester.pump(const Duration(milliseconds: 300));
/// TODO: verification of state here: restarting app and entering password
await tester.pump(const Duration(seconds: 3));
});
});
group('TOTP tests', () {
testWidgets('first TOTP test', (WidgetTester tester) async {
final Widget initializedApp;
if (isDesktop) {
initializedApp = await desktop.initialize([]);
} else if (isAndroid) {
initializedApp = await android.initialize();
} else {
throw UnimplementedError('Platform not supported');
}
await tester.pumpWidget(initializedApp);
await tester.pump(const Duration(milliseconds: 500));
expect(find.byType(NoDeviceScreen), findsNothing, reason: 'No YubiKey connected');
expect(find.byType(OathScreen), findsOneWidget);
});
});
group('HOTP tests', () {
testWidgets('first HOTP test', (WidgetTester tester) async {
final Widget initializedApp;
if (isDesktop) {
initializedApp = await desktop.initialize([]);
} else if (isAndroid) {
initializedApp = await android.initialize();
} else {
throw UnimplementedError('Platform not supported');
}
await tester.pumpWidget(initializedApp);
await tester.pump(const Duration(milliseconds: 500));
expect(find.byType(NoDeviceScreen), findsNothing, reason: 'No YubiKey connected');
expect(find.byType(OathScreen), findsOneWidget);
});
});
}

View File

@ -11,12 +11,10 @@ import '../state.dart';
class ManagePasswordDialog extends ConsumerStatefulWidget { class ManagePasswordDialog extends ConsumerStatefulWidget {
final DevicePath path; final DevicePath path;
final OathState state; final OathState state;
const ManagePasswordDialog(this.path, this.state, {Key? key}) const ManagePasswordDialog(this.path, this.state, {Key? key}) : super(key: key);
: super(key: key);
@override @override
ConsumerState<ConsumerStatefulWidget> createState() => ConsumerState<ConsumerStatefulWidget> createState() => _ManagePasswordDialogState();
_ManagePasswordDialogState();
} }
class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> { class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
@ -26,9 +24,7 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
bool _currentIsWrong = false; bool _currentIsWrong = false;
_submit() async { _submit() async {
final result = await ref final result = await ref.read(oathStateProvider(widget.path).notifier).setPassword(_currentPassword, _newPassword);
.read(oathStateProvider(widget.path).notifier)
.setPassword(_currentPassword, _newPassword);
if (result) { if (result) {
Navigator.of(context).pop(); Navigator.of(context).pop();
showMessage(context, 'Password set'); showMessage(context, 'Password set');
@ -61,6 +57,7 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
style: Theme.of(context).textTheme.headline6, style: Theme.of(context).textTheme.headline6,
), ),
TextField( TextField(
key: const Key('current oath password'),
autofocus: true, autofocus: true,
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
@ -81,9 +78,8 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
child: const Text('Remove password'), child: const Text('Remove password'),
onPressed: _currentPassword.isNotEmpty onPressed: _currentPassword.isNotEmpty
? () async { ? () async {
final result = await ref final result =
.read(oathStateProvider(widget.path).notifier) await ref.read(oathStateProvider(widget.path).notifier).unsetPassword(_currentPassword);
.unsetPassword(_currentPassword);
if (result) { if (result) {
Navigator.of(context).pop(); Navigator.of(context).pop();
showMessage(context, 'Password removed'); showMessage(context, 'Password removed');
@ -99,9 +95,7 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
OutlinedButton( OutlinedButton(
child: const Text('Clear saved password'), child: const Text('Clear saved password'),
onPressed: () async { onPressed: () async {
await ref await ref.read(oathStateProvider(widget.path).notifier).forgetPassword();
.read(oathStateProvider(widget.path).notifier)
.forgetPassword();
Navigator.of(context).pop(); Navigator.of(context).pop();
showMessage(context, 'Password forgotten'); showMessage(context, 'Password forgotten');
}, },
@ -115,6 +109,7 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
style: Theme.of(context).textTheme.headline6, style: Theme.of(context).textTheme.headline6,
), ),
TextField( TextField(
key: const Key('new oath password'),
autofocus: !widget.state.hasKey, autofocus: !widget.state.hasKey,
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
@ -129,6 +124,7 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
}, },
), ),
TextField( TextField(
key: const Key('confirm oath password'),
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),