Skip to content

Commit

Permalink
KDBX import implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
GleammerRay committed Sep 4, 2023
1 parent e194ed4 commit 555a3c3
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 13 deletions.
76 changes: 76 additions & 0 deletions lib/passy_data/loaded_account.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import 'package:crypton/crypton.dart';
import 'package:encrypt/encrypt.dart';
import 'package:kdbx/kdbx.dart';
import 'package:passy/passy_data/argon2_info.dart';
import 'package:passy/passy_data/custom_field.dart';
import 'package:passy/passy_data/json_file.dart';
import 'package:passy/passy_data/local_settings.dart';
import 'package:passy/passy_data/passy_entires_json_file.dart';
import 'package:passy/passy_data/passy_entries_file_collection.dart';
import 'package:archive/archive_io.dart';
import 'package:passy/passy_data/tfa.dart';
import 'dart:io';

import 'account_credentials.dart';
Expand Down Expand Up @@ -596,6 +598,80 @@ class LoadedAccount {
return fileName;
}

Future<void> importKDBXPasswords(List<KdbxEntry> entries) async {
String keyPrefix = '${DateTime.now().toUtc().toIso8601String()}-import';
Map<String, Password> passwords = {};
for (int i = 0; i != entries.length; i++) {
KdbxEntry entry = entries[i];
List<CustomField> customFields = [];
String? additionalInfo;
String? nickname;
String? username;
String? email;
String? password;
TFA? tfa;
String? website;
for (var e in entry.stringEntries) {
String? val = e.value?.getText();
if (val == null) continue;
switch (e.key.key) {
case 'Additional Info':
additionalInfo = val;
continue;
case KdbxKeyCommon.KEY_TITLE:
nickname = val;
continue;
case KdbxKeyCommon.KEY_USER_NAME:
username = val;
continue;
case 'Email':
email = val;
continue;
case KdbxKeyCommon.KEY_PASSWORD:
password = val;
continue;
case KdbxKeyCommon.KEY_OTP:
tfa = TFA(secret: val);
continue;
case KdbxKeyCommon.KEY_URL:
website = val;
continue;
}
customFields.add(CustomField(
title: e.key.key,
value: val,
));
}
String key = '$keyPrefix-$i';
passwords[key] = Password(
key: key,
customFields: customFields,
additionalInfo: additionalInfo ?? '',
nickname: nickname ?? '',
username: username ?? '',
email: email ?? '',
password: password ?? '',
tfa: tfa,
website: website ?? '',
);
}
await _history.reload();
for (Password password in passwords.values) {
_history.value.passwords[password.key] = EntryEvent(
password.key,
status: EntryStatus.alive,
lastModified: DateTime.now().toUtc(),
);
}
try {
_passwords.setEntries(passwords);
} catch (_) {
await _history.reload();
rethrow;
}
await _history.save();
}

Future<void> Function(PassyEntry value) setEntry(EntryType type) {
switch (type) {
case EntryType.password:
Expand Down
60 changes: 48 additions & 12 deletions lib/screens/confirm_import_screen.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:kdbx/kdbx.dart';
import 'package:passy/common/common.dart';
import 'package:passy/passy_data/loaded_account.dart';
import 'package:passy/passy_flutter/passy_flutter.dart';
import 'package:passy/screens/common.dart';
import 'package:passy/screens/login_screen.dart';
import 'package:passy/screens/main_screen.dart';
import 'package:passy/screens/splash_screen.dart';

import 'import_screen.dart';
import 'log_screen.dart';

enum ImportType {
passy,
kdbx,
}

class ConfirmImportScreenArgs {
final String path;
final ImportType importType;

ConfirmImportScreenArgs({
required this.path,
required this.importType,
});
}

class ConfirmImportScreen extends StatefulWidget {
static const String routeName = '${ImportScreen.routeName}/confirm';

Expand All @@ -20,19 +39,34 @@ class ConfirmImportScreen extends StatefulWidget {

class _ConfirmImportScreen extends State<ConfirmImportScreen> {
final LoadedAccount _account = data.loadedAccount!;
String _path = '';

Future<void> _onConfirmPressed(BuildContext context, String value) async {
Future<void> _onConfirmPressed(
BuildContext context, String value, ConfirmImportScreenArgs args) async {
Navigator.pushNamed(context, SplashScreen.routeName);
try {
await data.importAccount(_path,
encrypter:
(await data.getEncrypter(_account.username, password: value))!,
syncEncrypter: (await data.getSyncEncrypter(
username: _account.username, password: value)));
Navigator.popUntil(
context, (route) => route.settings.name == MainScreen.routeName);
Navigator.pushReplacementNamed(context, LoginScreen.routeName);
switch (args.importType) {
case ImportType.passy:
await data.importAccount(args.path,
encrypter: (await data.getEncrypter(_account.username,
password: value))!,
syncEncrypter: (await data.getSyncEncrypter(
username: _account.username, password: value)));
Navigator.popUntil(
context, (route) => route.settings.name == MainScreen.routeName);
Navigator.pushReplacementNamed(context, LoginScreen.routeName);
break;
case ImportType.kdbx:
KdbxFile file = await KdbxFormat().read(
await File(args.path).readAsBytes(),
Credentials(ProtectedValue.fromString(value)));
await _account
.importKDBXPasswords(file.body.rootGroup.getAllEntries());
Navigator.popUntil(context,
(route) => route.settings.name == ImportScreen.routeName);
break;
}
} catch (e, s) {
Navigator.pop(context);
showSnackBar(
context,
message: localizations.couldNotImportAccount,
Expand All @@ -49,7 +83,8 @@ class _ConfirmImportScreen extends State<ConfirmImportScreen> {

@override
Widget build(BuildContext context) {
_path = ModalRoute.of(context)!.settings.arguments as String;
ConfirmImportScreenArgs args =
ModalRoute.of(context)!.settings.arguments as ConfirmImportScreenArgs;
return ConfirmStringScaffold(
title: Text(localizations.passyImport),
message: PassyPadding(RichText(
Expand All @@ -72,6 +107,7 @@ class _ConfirmImportScreen extends State<ConfirmImportScreen> {
obscureText: true,
confirmIcon: const Icon(Icons.download_for_offline_outlined),
onBackPressed: (context) => Navigator.pop(context),
onConfirmPressed: _onConfirmPressed);
onConfirmPressed: (context, value) =>
_onConfirmPressed(context, value, args));
}
}
40 changes: 39 additions & 1 deletion lib/screens/import_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,36 @@ class _ImportScreen extends State<ImportScreen> {
Navigator.pushNamed(
context,
ConfirmImportScreen.routeName,
arguments: _pick.files[0].path,
arguments: ConfirmImportScreenArgs(
path: _pick.files[0].path!,
importType: ImportType.passy,
),
);
},
);
}

void _onKdbxImportPressed() {
MainScreen.shouldLockScreen = false;
FilePicker.platform
.pickFiles(
dialogTitle: localizations.importFromPassy,
type: FileType.custom,
allowedExtensions: ['kdbx'],
lockParentWindow: true,
)
.then(
(_pick) {
Future.delayed(const Duration(seconds: 2))
.then((value) => MainScreen.shouldLockScreen = true);
if (_pick == null) return;
Navigator.pushNamed(
context,
ConfirmImportScreen.routeName,
arguments: ConfirmImportScreenArgs(
path: _pick.files[0].path!,
importType: ImportType.kdbx,
),
);
},
);
Expand Down Expand Up @@ -68,6 +97,15 @@ class _ImportScreen extends State<ImportScreen> {
onPressed: () =>
Navigator.pushNamed(context, CSVImportScreen.routeName)),
),
PassyPadding(ThreeWidgetButton(
center: Text(localizations.kdbxImport),
left: const Padding(
padding: EdgeInsets.only(right: 30),
child: Icon(Icons.file_copy),
),
right: const Icon(Icons.arrow_forward_ios_rounded),
onPressed: _onKdbxImportPressed,
)),
PassyPadding(ThreeWidgetButton(
center: Text(localizations.passyImport),
left: Padding(
Expand Down
11 changes: 11 additions & 0 deletions lib/screens/setup_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:passy/passy_data/key_derivation_type.dart';
import 'package:passy/passy_data/loaded_account.dart';
import 'package:passy/passy_flutter/passy_flutter.dart';
import 'package:passy/screens/automatic_backup_screen.dart';
import 'package:passy/screens/import_screen.dart';
import 'package:passy/screens/main_screen.dart';
import 'package:passy/screens/security_screen.dart';

Expand Down Expand Up @@ -33,6 +34,16 @@ class _SetupScreen extends State<SetupScreen> {
),
body: ListView(
children: [
PassyPadding(ThreeWidgetButton(
center: Text(localizations.import),
left: const Padding(
padding: EdgeInsets.only(right: 30),
child: Icon(Icons.download_for_offline_outlined),
),
right: const Icon(Icons.arrow_forward_ios_rounded),
onPressed: () =>
Navigator.pushNamed(context, ImportScreen.routeName),
)),
PassyPadding(ThreeWidgetButton(
center: Text(localizations.security),
left: const Padding(
Expand Down

0 comments on commit 555a3c3

Please sign in to comment.