mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-26 10:33:15 +03:00
first stab at testing OATH passwords from password management screen
This commit is contained in:
parent
4efa031910
commit
2d231bca99
186
integration_test/oath_test.dart
Normal file
186
integration_test/oath_test.dart
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -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(),
|
||||||
|
Loading…
Reference in New Issue
Block a user