Browse Source

implement permanently deleting entries and groups.

pull/5/head
Herbert Poul 3 years ago
parent
commit
59078d6ec8
  1. 4
      CHANGELOG.md
  2. 24
      lib/src/kdbx_dao.dart
  3. 6
      lib/src/kdbx_deleted_object.dart
  4. 4
      lib/src/kdbx_format.dart
  5. 5
      lib/src/kdbx_group.dart
  6. 3
      lib/src/kdbx_object.dart
  7. 2
      pubspec.yaml
  8. 49
      test/deleted_objects_test.dart
  9. 4
      test/icon/kdbx_customicon_test.dart
  10. 39
      test/internal/test_utils.dart
  11. 17
      test/kdbx4_test.dart
  12. 35
      test/kdbx_binaries_test.dart
  13. 8
      test/kdbx_history_test.dart
  14. 15
      test/kdbx_test.dart
  15. 10
      test/kdbx_upgrade_test.dart
  16. 23
      test/merge/kdbx_merge_test.dart

4
CHANGELOG.md

@ -1,3 +1,7 @@
## 2.1.0
- Implement permanently removing entries and groups.
## 2.0.0+1 ## 2.0.0+1
- Small Null-safety improvement. - Small Null-safety improvement.

24
lib/src/kdbx_dao.dart

@ -1,3 +1,5 @@
import 'package:clock/clock.dart';
import 'package:kdbx/src/kdbx_deleted_object.dart';
import 'package:kdbx/src/kdbx_entry.dart'; import 'package:kdbx/src/kdbx_entry.dart';
import 'package:kdbx/src/kdbx_file.dart'; import 'package:kdbx/src/kdbx_file.dart';
import 'package:kdbx/src/kdbx_group.dart'; import 'package:kdbx/src/kdbx_group.dart';
@ -40,4 +42,26 @@ extension KdbxDao on KdbxFile {
toGroup.addEntry(kdbxObject); toGroup.addEntry(kdbxObject);
} }
} }
void deletePermanently(KdbxObject kdbxObject) {
final parent = kdbxObject.parent;
if (parent == null) {
throw StateError(
'Unable to delete object. Object as no parent, already deleted?');
}
final now = clock.now().toUtc();
if (kdbxObject is KdbxGroup) {
for (final object in kdbxObject.getAllGroupsAndEntries()) {
ctx.addDeletedObject(object.uuid, now);
}
parent.internalRemoveGroup(kdbxObject);
} else if (kdbxObject is KdbxEntry) {
ctx.addDeletedObject(kdbxObject.uuid, now);
parent.internalRemoveEntry(kdbxObject);
} else {
throw StateError('Invalid object type. ${kdbxObject.runtimeType}');
}
kdbxObject.times.locationChanged.set(now);
kdbxObject.internalChangeParent(null);
}
} }

6
lib/src/kdbx_deleted_object.dart

@ -1,12 +1,14 @@
import 'package:clock/clock.dart';
import 'package:kdbx/src/kdbx_format.dart'; import 'package:kdbx/src/kdbx_format.dart';
import 'package:kdbx/src/kdbx_object.dart'; import 'package:kdbx/src/kdbx_object.dart';
import 'package:kdbx/src/kdbx_xml.dart'; import 'package:kdbx/src/kdbx_xml.dart';
import 'package:xml/xml.dart'; import 'package:xml/xml.dart';
class KdbxDeletedObject extends KdbxNode implements KdbxNodeContext { class KdbxDeletedObject extends KdbxNode implements KdbxNodeContext {
KdbxDeletedObject.create(this.ctx, KdbxUuid? uuid) : super.create(NODE_NAME) { KdbxDeletedObject.create(this.ctx, KdbxUuid? uuid, [DateTime? now])
: super.create(NODE_NAME) {
_uuid.set(uuid); _uuid.set(uuid);
deletionTime.setToNow(); deletionTime.set(now ?? clock.now().toUtc());
} }
KdbxDeletedObject.read(XmlElement node, this.ctx) : super.read(node); KdbxDeletedObject.read(XmlElement node, this.ctx) : super.read(node);

4
lib/src/kdbx_format.dart

@ -138,6 +138,10 @@ class KdbxReadWriteContext {
'Tried to remove binary which is not in this file.'); 'Tried to remove binary which is not in this file.');
} }
} }
void addDeletedObject(KdbxUuid uuid, [DateTime? now]) {
_deletedObjects.add(KdbxDeletedObject.create(this, uuid));
}
} }
abstract class CredentialsPart { abstract class CredentialsPart {

5
lib/src/kdbx_group.dart

@ -57,6 +57,11 @@ class KdbxGroup extends KdbxObject {
List<KdbxEntry> getAllEntries() => List<KdbxEntry> getAllEntries() =>
getAllGroups().expand((g) => g.entries).toList(growable: false); getAllGroups().expand((g) => g.entries).toList(growable: false);
/// Returns all groups and entries. (Including the group itself).
Iterable<KdbxObject> getAllGroupsAndEntries() => <KdbxObject>[this]
.followedBy(entries)
.followedBy(groups.expand((g) => g.getAllGroupsAndEntries()));
List<KdbxGroup> get groups => List.unmodifiable(_groups); List<KdbxGroup> get groups => List.unmodifiable(_groups);
final List<KdbxGroup> _groups = []; final List<KdbxGroup> _groups = [];

3
lib/src/kdbx_object.dart

@ -235,7 +235,8 @@ abstract class KdbxObject extends KdbxNode {
return el; return el;
} }
void internalChangeParent(KdbxGroup parent) { @internal
void internalChangeParent(KdbxGroup? parent) {
modify(() => _parent = parent); modify(() => _parent = parent);
} }

2
pubspec.yaml

@ -1,6 +1,6 @@
name: kdbx name: kdbx
description: KeepassX format implementation in pure dart. (kdbx 3.x and 4.x support). description: KeepassX format implementation in pure dart. (kdbx 3.x and 4.x support).
version: 2.0.0+1 version: 2.1.0
homepage: https://github.com/authpass/kdbx.dart homepage: https://github.com/authpass/kdbx.dart
environment: environment:

49
test/deleted_objects_test.dart

@ -1,24 +1,67 @@
@Tags(['kdbx4']) @Tags(['kdbx4'])
import 'package:kdbx/kdbx.dart';
import 'package:kdbx/src/kdbx_xml.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:xml/xml.dart';
import 'internal/test_utils.dart'; import 'internal/test_utils.dart';
import 'kdbx_test.dart';
final _logger = Logger('deleted_objects_test'); final _logger = Logger('deleted_objects_test');
void main() { void main() {
TestUtil.setupLogging(); final testUtil = TestUtil();
_logger.finest('Running deleted objects tests.'); _logger.finest('Running deleted objects tests.');
group('read tombstones', () { group('read tombstones', () {
test('load/save keeps deleted objects.', () async { test('load/save keeps deleted objects.', () async {
final orig = final orig =
await TestUtil.readKdbxFile('test/test_files/tombstonetest.kdbx'); await testUtil.readKdbxFile('test/test_files/tombstonetest.kdbx');
expect(orig.body.deletedObjects, hasLength(1)); expect(orig.body.deletedObjects, hasLength(1));
final dt = orig.body.deletedObjects.first.deletionTime.get()!; final dt = orig.body.deletedObjects.first.deletionTime.get()!;
expect([dt.year, dt.month, dt.day], [2020, 8, 30]); expect([dt.year, dt.month, dt.day], [2020, 8, 30]);
final reload = await TestUtil.saveAndRead(orig); final reload = await testUtil.saveAndRead(orig);
expect(reload.body.deletedObjects, hasLength(1)); expect(reload.body.deletedObjects, hasLength(1));
}); });
}); });
group('delete permanently', () {
test('delete entry', () async {
final file = testUtil.createEmptyFile();
final g = file.body.rootGroup;
final entry = testUtil.createEntry(file, g, 'foo', 'bar');
expect(g.getAllGroupsAndEntries().length, 2);
file.deleteEntry(entry);
// moved into trash bin
expect(g.getAllGroupsAndEntries().length, 3);
// now delete from trash
file.deletePermanently(entry);
expect(g.getAllGroupsAndEntries().length, 2);
final xml = file.body.generateXml(FakeProtectedSaltGenerator());
final objects = xml.findAllElements(KdbxXml.NODE_DELETED_OBJECT);
expect(objects.length, 1);
expect(objects.first.findElements(KdbxXml.NODE_UUID).first.text,
entry.uuid.uuid);
});
test('delete group', () async {
final file = testUtil.createEmptyFile();
final rootGroup = file.body.rootGroup;
final g = file.createGroup(parent: rootGroup, name: 'group');
final objs = [
g,
testUtil.createEntry(file, g, 'foo', 'bar'),
testUtil.createEntry(file, g, 'foo2', 'bar2'),
testUtil.createEntry(file, g, 'foo3', 'bar3'),
];
expect(rootGroup.getAllGroupsAndEntries().length, 5);
file.deletePermanently(g);
expect(rootGroup.getAllGroupsAndEntries().length, 1);
final xml = file.body.generateXml(FakeProtectedSaltGenerator());
final objects = xml.findAllElements(KdbxXml.NODE_DELETED_OBJECT);
expect(objects.length, 4);
expect(objects.map((e) => e.findElements(KdbxXml.NODE_UUID).first.text),
objs.map((o) => o.uuid.uuid));
});
});
} }

4
test/icon/kdbx_customicon_test.dart

@ -3,9 +3,9 @@ import 'package:test/test.dart';
import '../internal/test_utils.dart'; import '../internal/test_utils.dart';
void main() { void main() {
TestUtil.setupLogging(); final testUtil = TestUtil();
test('load custom icons from file', () async { test('load custom icons from file', () async {
final file = await TestUtil.readKdbxFile('test/icon/icontest.kdbx'); final file = await testUtil.readKdbxFile('test/icon/icontest.kdbx');
final entry = file.body.rootGroup.entries.first; final entry = file.body.rootGroup.entries.first;
expect(entry.customIcon!.data, isNotNull); expect(entry.customIcon!.data, isNotNull);
}); });

39
test/internal/test_utils.dart

@ -10,12 +10,21 @@ import 'package:logging_appenders/logging_appenders.dart';
final _logger = Logger('test_utils'); final _logger = Logger('test_utils');
class TestUtil { class TestUtil {
factory TestUtil() => instance;
TestUtil._() {
setupLogging();
}
static late final instance = TestUtil._();
static final keyTitle = KdbxKey('Title'); static final keyTitle = KdbxKey('Title');
static void setupLogging() => static void setupLogging() =>
PrintAppender.setupLogging(stderrLevel: Level.WARNING); PrintAppender.setupLogging(stderrLevel: Level.WARNING);
static KdbxFormat kdbxFormat() { late final kdbxFormat = _kdbxFormat();
static KdbxFormat _kdbxFormat() {
Argon2.resolveLibraryForceDynamic = true; Argon2.resolveLibraryForceDynamic = true;
return KdbxFormat(Argon2FfiFlutter(resolveLibrary: (path) { return KdbxFormat(Argon2FfiFlutter(resolveLibrary: (path) {
final cwd = Directory('.').absolute.uri; final cwd = Directory('.').absolute.uri;
@ -26,42 +35,54 @@ class TestUtil {
})); }));
} }
static Future<KdbxFile> readKdbxFile( Future<KdbxFile> readKdbxFile(
String filePath, { String filePath, {
String password = 'asdf', String password = 'asdf',
}) async { }) async {
final kdbxFormat = TestUtil.kdbxFormat();
final data = await File(filePath).readAsBytes(); final data = await File(filePath).readAsBytes();
final file = await kdbxFormat.read( final file = await kdbxFormat.read(
data, Credentials(ProtectedValue.fromString(password))); data, Credentials(ProtectedValue.fromString(password)));
return file; return file;
} }
static Future<KdbxFile> readKdbxFileBytes(Uint8List data, Future<KdbxFile> readKdbxFileBytes(Uint8List data,
{String password = 'asdf', Credentials? credentials}) async { {String password = 'asdf', Credentials? credentials}) async {
final kdbxFormat = TestUtil.kdbxFormat();
final file = await kdbxFormat.read( final file = await kdbxFormat.read(
data, credentials ?? Credentials(ProtectedValue.fromString(password))); data, credentials ?? Credentials(ProtectedValue.fromString(password)));
return file; return file;
} }
static Future<KdbxFile> saveAndRead(KdbxFile file) async { Future<KdbxFile> saveAndRead(KdbxFile file) async {
return await readKdbxFileBytes(await file.save(), return await readKdbxFileBytes(await file.save(),
credentials: file.credentials); credentials: file.credentials);
} }
static Future<void> saveTestOutput(String name, KdbxFile file) async { Future<void> saveTestOutput(String name, KdbxFile file) async {
final bytes = await file.save(); final bytes = await file.save();
final outFile = File('test_output_$name.kdbx'); final outFile = File('test_output_$name.kdbx');
await outFile.writeAsBytes(bytes); await outFile.writeAsBytes(bytes);
_logger.info('Written to $outFile'); _logger.info('Written to $outFile');
} }
static KdbxFile createEmptyFile() { KdbxFile createEmptyFile() {
final file = kdbxFormat().create( final file = kdbxFormat.create(
Credentials.composite(ProtectedValue.fromString('asdf'), null), Credentials.composite(ProtectedValue.fromString('asdf'), null),
'example'); 'example');
return file; return file;
} }
KdbxEntry createEntry(
KdbxFile file,
KdbxGroup group,
String username,
String password,
) {
final entry = KdbxEntry.create(file, group);
group.addEntry(entry);
entry.setString(KdbxKeyCommon.USER_NAME, PlainValue(username));
entry.setString(
KdbxKeyCommon.PASSWORD, ProtectedValue.fromString(password));
return entry;
}
} }

17
test/kdbx4_test.dart

@ -1,11 +1,9 @@
@Tags(['kdbx4']) @Tags(['kdbx4'])
import 'dart:io'; import 'dart:io';
import 'package:kdbx/kdbx.dart'; import 'package:kdbx/kdbx.dart';
import 'package:kdbx/src/kdbx_header.dart'; import 'package:kdbx/src/kdbx_header.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:logging_appenders/logging_appenders.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'internal/test_utils.dart'; import 'internal/test_utils.dart';
@ -15,9 +13,8 @@ final _logger = Logger('kdbx4_test');
// ignore_for_file: non_constant_identifier_names // ignore_for_file: non_constant_identifier_names
void main() { void main() {
Logger.root.level = Level.ALL; final testUtil = TestUtil();
PrintAppender().attachToLogger(Logger.root); final kdbxFormat = testUtil.kdbxFormat;
final kdbxFormat = TestUtil.kdbxFormat();
group('Reading', () { group('Reading', () {
test('bubb', () async { test('bubb', () async {
final data = await File('test/keepassxcpasswords.kdbx').readAsBytes(); final data = await File('test/keepassxcpasswords.kdbx').readAsBytes();
@ -36,7 +33,7 @@ void main() {
expect(pwd, 'def'); expect(pwd, 'def');
}); });
test('Reading kdbx4_keeweb modification time', () async { test('Reading kdbx4_keeweb modification time', () async {
final file = await TestUtil.readKdbxFile('test/kdbx4_keeweb.kdbx'); final file = await testUtil.readKdbxFile('test/kdbx4_keeweb.kdbx');
final firstEntry = file.body.rootGroup.entries.first; final firstEntry = file.body.rootGroup.entries.first;
final createTime = firstEntry.times.creationTime.get(); final createTime = firstEntry.times.creationTime.get();
expect(createTime, DateTime.utc(2020, 2, 26, 13, 40, 48)); expect(createTime, DateTime.utc(2020, 2, 26, 13, 40, 48));
@ -44,13 +41,13 @@ void main() {
expect(modTime, DateTime.utc(2020, 2, 26, 13, 40, 54)); expect(modTime, DateTime.utc(2020, 2, 26, 13, 40, 54));
}); });
test('Change kdbx4 modification time', () async { test('Change kdbx4 modification time', () async {
final file = await TestUtil.readKdbxFile('test/kdbx4_keeweb.kdbx'); final file = await testUtil.readKdbxFile('test/kdbx4_keeweb.kdbx');
final firstEntry = file.body.rootGroup.entries.first; final firstEntry = file.body.rootGroup.entries.first;
final d = DateTime.utc(2020, 4, 5, 10, 0); final d = DateTime.utc(2020, 4, 5, 10, 0);
firstEntry.times.lastModificationTime.set(d); firstEntry.times.lastModificationTime.set(d);
final saved = await file.save(); final saved = await file.save();
{ {
final file2 = await TestUtil.readKdbxFileBytes(saved); final file2 = await testUtil.readKdbxFileBytes(saved);
final firstEntry = file2.body.rootGroup.entries.first; final firstEntry = file2.body.rootGroup.entries.first;
expect(firstEntry.times.lastModificationTime.get(), d); expect(firstEntry.times.lastModificationTime.get(), d);
} }
@ -116,12 +113,12 @@ void main() {
}); });
group('recycle bin test', () { group('recycle bin test', () {
test('empty recycle bin with "zero" uuid', () async { test('empty recycle bin with "zero" uuid', () async {
final file = await TestUtil.readKdbxFile('test/keepass2test.kdbx'); final file = await testUtil.readKdbxFile('test/keepass2test.kdbx');
final recycleBin = file.recycleBin; final recycleBin = file.recycleBin;
expect(recycleBin, isNull); expect(recycleBin, isNull);
}); });
test('check deleting item', () async { test('check deleting item', () async {
final file = await TestUtil.readKdbxFile('test/keepass2test.kdbx'); final file = await testUtil.readKdbxFile('test/keepass2test.kdbx');
expect(file.recycleBin, isNull); expect(file.recycleBin, isNull);
final entry = file.body.rootGroup.getAllEntries().first; final entry = file.body.rootGroup.getAllEntries().first;
file.deleteEntry(entry); file.deleteEntry(entry);

35
test/kdbx_binaries_test.dart

@ -18,7 +18,7 @@ void expectBinary(KdbxEntry entry, String key, dynamic matcher) {
Future<void> _testAddNewAttachment(String filePath) async { Future<void> _testAddNewAttachment(String filePath) async {
final saved = await (() async { final saved = await (() async {
final f = await TestUtil.readKdbxFile(filePath); final f = await TestUtil().readKdbxFile(filePath);
final entry = KdbxEntry.create(f, f.body.rootGroup); final entry = KdbxEntry.create(f, f.body.rootGroup);
entry.label = 'addattachment'; entry.label = 'addattachment';
f.body.rootGroup.addEntry(entry); f.body.rootGroup.addEntry(entry);
@ -34,7 +34,7 @@ Future<void> _testAddNewAttachment(String filePath) async {
return await f.save(); return await f.save();
})(); })();
{ {
final file = await TestUtil.readKdbxFileBytes(saved); final file = await TestUtil().readKdbxFileBytes(saved);
final entry = file.body.rootGroup.entries final entry = file.body.rootGroup.entries
.firstWhere((e) => e.label == 'addattachment'); .firstWhere((e) => e.label == 'addattachment');
final binaries = entry.binaryEntries.toList(); final binaries = entry.binaryEntries.toList();
@ -48,8 +48,7 @@ Future<void> _testAddNewAttachment(String filePath) async {
} }
void main() { void main() {
Logger.root.level = Level.ALL; final testUtil = TestUtil();
PrintAppender().attachToLogger(Logger.root);
group('kdbx3 attachment', () { group('kdbx3 attachment', () {
void expectKeepass2binariesContents(KdbxEntry entry) { void expectKeepass2binariesContents(KdbxEntry entry) {
@ -73,28 +72,28 @@ void main() {
} }
test('read binary', () async { test('read binary', () async {
final file = await TestUtil.readKdbxFile('test/keepass2binaries.kdbx'); final file = await testUtil.readKdbxFile('test/keepass2binaries.kdbx');
final entry = file.body.rootGroup.entries.first; final entry = file.body.rootGroup.entries.first;
expectKeepass2binariesContents(entry); expectKeepass2binariesContents(entry);
}); });
test('read write read', () async { test('read write read', () async {
final fileRead = final fileRead =
await TestUtil.readKdbxFile('test/keepass2binaries.kdbx'); await testUtil.readKdbxFile('test/keepass2binaries.kdbx');
final saved = await fileRead.save(); final saved = await fileRead.save();
final file = await TestUtil.readKdbxFileBytes(saved); final file = await testUtil.readKdbxFileBytes(saved);
final entry = file.body.rootGroup.entries.first; final entry = file.body.rootGroup.entries.first;
expectKeepass2binariesContents(entry); expectKeepass2binariesContents(entry);
}); });
test('modify file with binary in history', () async { test('modify file with binary in history', () async {
final fileRead = final fileRead =
await TestUtil.readKdbxFile('test/keepass2binaries.kdbx'); await testUtil.readKdbxFile('test/keepass2binaries.kdbx');
final updateEntry = (KdbxFile file) { final updateEntry = (KdbxFile file) {
final entry = fileRead.body.rootGroup.entries.first; final entry = fileRead.body.rootGroup.entries.first;
entry.setString(KdbxKeyCommon.TITLE, PlainValue('example')); entry.setString(KdbxKeyCommon.TITLE, PlainValue('example'));
}; };
updateEntry(fileRead); updateEntry(fileRead);
final saved = await fileRead.save(); final saved = await fileRead.save();
final file = await TestUtil.readKdbxFileBytes(saved); final file = await testUtil.readKdbxFileBytes(saved);
await file.save(); await file.save();
}); });
test('Add new attachment', () async { test('Add new attachment', () async {
@ -102,7 +101,7 @@ void main() {
}); });
test('Remove attachment', () async { test('Remove attachment', () async {
final saved = await (() async { final saved = await (() async {
final file = await TestUtil.readKdbxFile('test/keepass2binaries.kdbx'); final file = await testUtil.readKdbxFile('test/keepass2binaries.kdbx');
final entry = file.body.rootGroup.entries.first; final entry = file.body.rootGroup.entries.first;
expectKeepass2binariesContents(entry); expectKeepass2binariesContents(entry);
expect(file.ctx.binariesIterable, hasLength(3)); expect(file.ctx.binariesIterable, hasLength(3));
@ -110,7 +109,7 @@ void main() {
expect(file.ctx.binariesIterable, hasLength(3)); expect(file.ctx.binariesIterable, hasLength(3));
return await file.save(); return await file.save();
})(); })();
final file = await TestUtil.readKdbxFileBytes(saved); final file = await testUtil.readKdbxFileBytes(saved);
final entry = file.body.rootGroup.entries.first; final entry = file.body.rootGroup.entries.first;
expect(entry.binaryEntries, hasLength(2)); expect(entry.binaryEntries, hasLength(2));
expect(entry.binaryEntries.map((e) => (e.key.key)), expect(entry.binaryEntries.map((e) => (e.key.key)),
@ -124,8 +123,8 @@ void main() {
}); });
test('keepassxc compatibility', () async { test('keepassxc compatibility', () async {
// keepass has files in arbitrary sort order. // keepass has files in arbitrary sort order.
final file = await TestUtil.readKdbxFile( final file = await testUtil
'test/test_files/binarytest-keepassxc.kdbx'); .readKdbxFile('test/test_files/binarytest-keepassxc.kdbx');
final entry = file.body.rootGroup.entries.first; final entry = file.body.rootGroup.entries.first;
for (final name in ['a', 'b', 'c', 'd', 'e']) { for (final name in ['a', 'b', 'c', 'd', 'e']) {
expect( expect(
@ -138,7 +137,7 @@ void main() {
group('kdbx4 attachment', () { group('kdbx4 attachment', () {
test('read binary', () async { test('read binary', () async {
final file = final file =
await TestUtil.readKdbxFile('test/keepass2kdbx4binaries.kdbx'); await testUtil.readKdbxFile('test/keepass2kdbx4binaries.kdbx');
expect(file.body.rootGroup.entries, hasLength(2)); expect(file.body.rootGroup.entries, hasLength(2));
expectBinary(file.body.rootGroup.entries.first, 'example2.txt', expectBinary(file.body.rootGroup.entries.first, 'example2.txt',
@ -148,9 +147,9 @@ void main() {
}); });
test('read, write, read kdbx4', () async { test('read, write, read kdbx4', () async {
final fileRead = final fileRead =
await TestUtil.readKdbxFile('test/keepass2kdbx4binaries.kdbx'); await testUtil.readKdbxFile('test/keepass2kdbx4binaries.kdbx');
final saved = await fileRead.save(); final saved = await fileRead.save();
final file = await TestUtil.readKdbxFileBytes(saved); final file = await testUtil.readKdbxFileBytes(saved);
expect(file.body.rootGroup.entries, hasLength(2)); expect(file.body.rootGroup.entries, hasLength(2));
expectBinary(file.body.rootGroup.entries.first, 'example2.txt', expectBinary(file.body.rootGroup.entries.first, 'example2.txt',
IsUtf8String('content2 example\n\n')); IsUtf8String('content2 example\n\n'));
@ -160,7 +159,7 @@ void main() {
test('remove attachment kdbx4', () async { test('remove attachment kdbx4', () async {
final saved = await (() async { final saved = await (() async {
final file = final file =
await TestUtil.readKdbxFile('test/keepass2kdbx4binaries.kdbx'); await testUtil.readKdbxFile('test/keepass2kdbx4binaries.kdbx');
final entry = file.body.rootGroup.entries.first; final entry = file.body.rootGroup.entries.first;
expectBinary(file.body.rootGroup.entries.first, 'example2.txt', expectBinary(file.body.rootGroup.entries.first, 'example2.txt',
IsUtf8String('content2 example\n\n')); IsUtf8String('content2 example\n\n'));
@ -173,7 +172,7 @@ void main() {
expect(file.dirtyObjects, [entry]); expect(file.dirtyObjects, [entry]);
return await file.save(); return await file.save();
})(); })();
final file = await TestUtil.readKdbxFileBytes(saved); final file = await testUtil.readKdbxFileBytes(saved);
final entry = file.body.rootGroup.entries.first; final entry = file.body.rootGroup.entries.first;
expect(entry.binaryEntries, hasLength(0)); expect(entry.binaryEntries, hasLength(0));
expectBinary(file.body.rootGroup.entries.last, 'keepasslogo.jpeg', expectBinary(file.body.rootGroup.entries.last, 'keepasslogo.jpeg',

8
test/kdbx_history_test.dart

@ -46,10 +46,10 @@ class StreamExpect<T> {
} }
void main() { void main() {
TestUtil.setupLogging(); final testUtil = TestUtil();
group('test history for values', () { group('test history for values', () {
test('check history creation', () async { test('check history creation', () async {
final file = await TestUtil.readKdbxFile('test/keepass2test.kdbx'); final file = await testUtil.readKdbxFile('test/keepass2test.kdbx');
const valueOrig = 'Sample Entry'; const valueOrig = 'Sample Entry';
const value1 = 'new'; const value1 = 'new';
const value2 = 'new2'; const value2 = 'new2';
@ -64,7 +64,7 @@ void main() {
} }
expect(file.dirtyObjects, hasLength(1)); expect(file.dirtyObjects, hasLength(1));
final f2 = await dirtyExpect final f2 = await dirtyExpect
.expectNext({}, () async => TestUtil.saveAndRead(file)); .expectNext({}, () async => testUtil.saveAndRead(file));
expect(file.dirtyObjects, isEmpty); expect(file.dirtyObjects, isEmpty);
{ {
final first = f2.body.rootGroup.entries.first; final first = f2.body.rootGroup.entries.first;
@ -81,7 +81,7 @@ void main() {
() async => first.setString(TestUtil.keyTitle, PlainValue(value2))); () async => first.setString(TestUtil.keyTitle, PlainValue(value2)));
} }
final f3 = await dirtyExpect final f3 = await dirtyExpect
.expectNext({}, () async => TestUtil.saveAndRead(file)); .expectNext({}, () async => testUtil.saveAndRead(file));
expect(file.dirtyObjects, isEmpty); expect(file.dirtyObjects, isEmpty);
{ {
final first = f3.body.rootGroup.entries.first; final first = f3.body.rootGroup.entries.first;

15
test/kdbx_test.dart

@ -22,9 +22,8 @@ class FakeProtectedSaltGenerator implements ProtectedSaltGenerator {
} }
void main() { void main() {
Logger.root.level = Level.ALL; final testUtil = TestUtil();
PrintAppender().attachToLogger(Logger.root); final kdbxFormat = testUtil.kdbxFormat;
final kdbxFormat = KdbxFormat();
group('Reading', () { group('Reading', () {
setUp(() {}); setUp(() {});
@ -94,7 +93,7 @@ void main() {
group('times', () { group('times', () {
test('read mod date time', () async { test('read mod date time', () async {
final file = await TestUtil.readKdbxFile('test/keepass2test.kdbx'); final file = await testUtil.readKdbxFile('test/keepass2test.kdbx');
final first = file.body.rootGroup.entries.first; final first = file.body.rootGroup.entries.first;
expect(file.header.version.major, 3); expect(file.header.version.major, 3);
expect(first.getString(KdbxKeyCommon.TITLE)!.getText(), 'Sample Entry'); expect(first.getString(KdbxKeyCommon.TITLE)!.getText(), 'Sample Entry');
@ -103,7 +102,7 @@ void main() {
}); });
test('update mod date time', () async { test('update mod date time', () async {
final newModDate = DateTime.utc(2020, 1, 2, 3, 4, 5); final newModDate = DateTime.utc(2020, 1, 2, 3, 4, 5);
final file = await TestUtil.readKdbxFile('test/keepass2test.kdbx'); final file = await testUtil.readKdbxFile('test/keepass2test.kdbx');
{ {
final first = file.body.rootGroup.entries.first; final first = file.body.rootGroup.entries.first;
expect(file.header.version.major, 3); expect(file.header.version.major, 3);
@ -112,7 +111,7 @@ void main() {
} }
final saved = await file.save(); final saved = await file.save();
{ {
final file = await TestUtil.readKdbxFileBytes(saved); final file = await testUtil.readKdbxFileBytes(saved);
final first = file.body.rootGroup.entries.first; final first = file.body.rootGroup.entries.first;
final modTime = first.times.lastModificationTime.get(); final modTime = first.times.lastModificationTime.get();
expect(modTime, newModDate); expect(modTime, newModDate);
@ -144,7 +143,7 @@ void main() {
File('test.kdbx').writeAsBytesSync(saved); File('test.kdbx').writeAsBytesSync(saved);
}); });
test('concurrent save test', () async { test('concurrent save test', () async {
final file = await TestUtil.readKdbxFile('test/keepass2test.kdbx'); final file = await testUtil.readKdbxFile('test/keepass2test.kdbx');
final readLock = Lock(); final readLock = Lock();
Future<KdbxFile> doSave( Future<KdbxFile> doSave(
Future<Uint8List> byteFuture, String debug) async { Future<Uint8List> byteFuture, String debug) async {
@ -152,7 +151,7 @@ void main() {
final bytes = await byteFuture; final bytes = await byteFuture;
return await readLock.synchronized(() { return await readLock.synchronized(() {
try { try {
final ret = TestUtil.readKdbxFileBytes(bytes); final ret = testUtil.readKdbxFileBytes(bytes);
_logger.fine('$debug FINISHED: success'); _logger.fine('$debug FINISHED: success');
return ret; return ret;
} catch (e, stackTrace) { } catch (e, stackTrace) {

10
test/kdbx_upgrade_test.dart

@ -6,17 +6,17 @@ import 'package:test/test.dart';
import 'internal/test_utils.dart'; import 'internal/test_utils.dart';
void main() { void main() {
TestUtil.setupLogging(); final testUtil = TestUtil();
group('Test upgrade from v3 to v4', () { group('Test upgrade from v3 to v4', () {
final format = TestUtil.kdbxFormat(); final format = testUtil.kdbxFormat;
test('Read v3, write v4', () async { test('Read v3, write v4', () async {
final file = final file =
await TestUtil.readKdbxFile('test/FooBar.kdbx', password: 'FooBar'); await testUtil.readKdbxFile('test/FooBar.kdbx', password: 'FooBar');
expect(file.header.version, KdbxVersion.V3_1); expect(file.header.version, KdbxVersion.V3_1);
file.upgrade(KdbxVersion.V4.major); file.upgrade(KdbxVersion.V4.major);
final v4 = await TestUtil.saveAndRead(file); final v4 = await testUtil.saveAndRead(file);
expect(v4.header.version, KdbxVersion.V4); expect(v4.header.version, KdbxVersion.V4);
await TestUtil.saveTestOutput('kdbx4upgrade', v4); await testUtil.saveTestOutput('kdbx4upgrade', v4);
}, tags: 'kdbx3'); }, tags: 'kdbx3');
test('kdbx4 is the new default', () async { test('kdbx4 is the new default', () async {
final file = final file =

23
test/merge/kdbx_merge_test.dart

@ -9,7 +9,7 @@ import '../internal/test_utils.dart';
final _logger = Logger('kdbx_merge_test'); final _logger = Logger('kdbx_merge_test');
void main() { void main() {
TestUtil.setupLogging(); final testUtil = TestUtil();
var now = DateTime.fromMillisecondsSinceEpoch(0); var now = DateTime.fromMillisecondsSinceEpoch(0);
final fakeClock = Clock(() => now); final fakeClock = Clock(() => now);
@ -22,18 +22,18 @@ void main() {
}); });
group('Simple merges', () { group('Simple merges', () {
Future<KdbxFile> createSimpleFile() async { Future<KdbxFile> createSimpleFile() async {
final file = TestUtil.createEmptyFile(); final file = testUtil.createEmptyFile();
_createEntry(file, file.body.rootGroup, 'test1', 'test1'); _createEntry(file, file.body.rootGroup, 'test1', 'test1');
final subGroup = final subGroup =
file.createGroup(parent: file.body.rootGroup, name: 'Sub Group'); file.createGroup(parent: file.body.rootGroup, name: 'Sub Group');
_createEntry(file, subGroup, 'test2', 'test2'); _createEntry(file, subGroup, 'test2', 'test2');
proceedSeconds(10); proceedSeconds(10);
return await TestUtil.saveAndRead(file); return await testUtil.saveAndRead(file);
} }
test('Noop merge', () async { test('Noop merge', () async {
final file = await createSimpleFile(); final file = await createSimpleFile();
final file2 = await TestUtil.saveAndRead(file); final file2 = await testUtil.saveAndRead(file);
final merge = file.merge(file2); final merge = file.merge(file2);
final set = Set<KdbxUuid>.from(merge.merged.keys); final set = Set<KdbxUuid>.from(merge.merged.keys);
expect(set, hasLength(4)); expect(set, hasLength(4));
@ -43,7 +43,7 @@ void main() {
await withClock(fakeClock, () async { await withClock(fakeClock, () async {
final file = await createSimpleFile(); final file = await createSimpleFile();
final fileMod = await TestUtil.saveAndRead(file); final fileMod = await testUtil.saveAndRead(file);
fileMod.body.rootGroup.entries.first fileMod.body.rootGroup.entries.first
.setString(KdbxKeyCommon.USER_NAME, PlainValue('changed.')); .setString(KdbxKeyCommon.USER_NAME, PlainValue('changed.'));
@ -51,7 +51,7 @@ void main() {
fileMod.body.rootGroup.entries.first.times.lastModificationTime fileMod.body.rootGroup.entries.first.times.lastModificationTime
.get() .get()
.toString()); .toString());
final file2 = await TestUtil.saveAndRead(fileMod); final file2 = await testUtil.saveAndRead(fileMod);
_logger.info('\n\n\nstarting merge.\n'); _logger.info('\n\n\nstarting merge.\n');
final merge = file.merge(file2); final merge = file.merge(file2);
@ -66,10 +66,10 @@ void main() {
() async => await withClock(fakeClock, () async { () async => await withClock(fakeClock, () async {
final file = await createSimpleFile(); final file = await createSimpleFile();
final fileMod = await TestUtil.saveAndRead(file); final fileMod = await testUtil.saveAndRead(file);
fileMod.body.rootGroup.groups.first.name.set('Sub Group New Name.'); fileMod.body.rootGroup.groups.first.name.set('Sub Group New Name.');
final file2 = await TestUtil.saveAndRead(fileMod); final file2 = await testUtil.saveAndRead(fileMod);
final merge = file.merge(file2); final merge = file.merge(file2);
final set = Set<KdbxUuid>.from(merge.merged.keys); final set = Set<KdbxUuid>.from(merge.merged.keys);
expect(set, hasLength(4)); expect(set, hasLength(4));
@ -81,18 +81,19 @@ void main() {
() async => await withClock(fakeClock, () async { () async => await withClock(fakeClock, () async {
final file = await createSimpleFile(); final file = await createSimpleFile();
final fileMod = await TestUtil.saveAndRead(file); final fileMod = await testUtil.saveAndRead(file);
expect(fileMod.recycleBin, isNull); expect(fileMod.recycleBin, isNull);
fileMod.deleteEntry(fileMod.body.rootGroup.entries.first); fileMod.deleteEntry(fileMod.body.rootGroup.entries.first);
expect(fileMod.recycleBin, isNotNull); expect(fileMod.recycleBin, isNotNull);
final file2 = await TestUtil.saveAndRead(fileMod); final file2 = await testUtil.saveAndRead(fileMod);
final merge = file.merge(file2); final merge = file.merge(file2);
_logger.info('Merged file:\n' _logger.info('Merged file:\n'
'${KdbxPrintUtils().catGroupToString(file.body.rootGroup)}'); '${KdbxPrintUtils().catGroupToString(file.body.rootGroup)}');
final set = Set<KdbxUuid>.from(merge.merged.keys); final set = Set<KdbxUuid>.from(merge.merged.keys);
expect(set, hasLength(5)); expect(set, hasLength(5));
expect(Set<KdbxNode>.from(merge.changes.map<KdbxNode?>((e) => e.object)), expect(
Set<KdbxNode>.from(merge.changes.map<KdbxNode?>((e) => e.object)),
hasLength(2)); hasLength(2));
}), }),
); );

Loading…
Cancel
Save