Browse Source

basic support for DeletedObjects tombstones in the root group.

pull/3/head
Herbert Poul 4 years ago
parent
commit
7111cc3711
  1. 7
      example/pubspec.lock
  2. 3
      example/pubspec.yaml
  3. 4
      lib/src/internal/extension_utils.dart
  4. 5
      lib/src/kdbx_deleted_object.dart
  5. 5
      lib/src/kdbx_entry.dart
  6. 33
      lib/src/kdbx_format.dart
  7. 4
      lib/src/kdbx_group.dart
  8. 3
      lib/src/kdbx_xml.dart
  9. 24
      test/deleted_objects_test.dart
  10. BIN
      test/test_files/tombstonetest.kdbx

7
example/pubspec.lock

@ -148,6 +148,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
pedantic:
dependency: "direct dev"
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.2"
petitparser:
dependency: transitive
description:

3
example/pubspec.yaml

@ -10,3 +10,6 @@ dependencies:
kdbx:
path: ../
dev_dependencies:
pedantic: ^1.9.2

4
lib/src/internal/extension_utils.dart

@ -33,3 +33,7 @@ extension XmlElementExt on xml.XmlElement {
extension ObjectExt<T> on T {
R let<R>(R Function(T that) op) => op(this);
}
extension IterableExt<T> on Iterable<T> {
T get singleOrNull => singleWhere((element) => true, orElse: () => null);
}

5
lib/src/kdbx_deleted_object.dart

@ -3,14 +3,15 @@ import 'package:kdbx/src/kdbx_xml.dart';
import 'package:xml/xml.dart';
class KdbxDeletedObject extends KdbxNode implements KdbxNodeContext {
KdbxDeletedObject.create(this.ctx, KdbxUuid uuid)
: super.create('DeletedObject') {
KdbxDeletedObject.create(this.ctx, KdbxUuid uuid) : super.create(NODE_NAME) {
_uuid.set(uuid);
deletionTime.setToNow();
}
KdbxDeletedObject.read(XmlElement node, this.ctx) : super.read(node);
static const NODE_NAME = KdbxXml.NODE_DELETED_OBJECT;
@override
final KdbxReadWriteContext ctx;

5
lib/src/kdbx_entry.dart

@ -2,6 +2,7 @@ import 'dart:typed_data';
import 'package:kdbx/kdbx.dart';
import 'package:kdbx/src/crypto/protected_value.dart';
import 'package:kdbx/src/internal/extension_utils.dart';
import 'package:kdbx/src/kdbx_binary.dart';
import 'package:kdbx/src/kdbx_consts.dart';
import 'package:kdbx/src/kdbx_file.dart';
@ -255,7 +256,3 @@ class KdbxEntry extends KdbxObject {
return 'KdbxGroup{uuid=$uuid,name=$label}';
}
}
extension<T> on Iterable<T> {
T get singleOrNull => singleWhere((element) => true, orElse: () => null);
}

33
lib/src/kdbx_format.dart

@ -12,6 +12,8 @@ import 'package:kdbx/kdbx.dart';
import 'package:kdbx/src/crypto/key_encrypter_kdf.dart';
import 'package:kdbx/src/crypto/protected_salt_generator.dart';
import 'package:kdbx/src/crypto/protected_value.dart';
import 'package:kdbx/src/internal/extension_utils.dart';
import 'package:kdbx/src/kdbx_deleted_object.dart';
import 'package:kdbx/src/utils/byte_utils.dart';
import 'package:kdbx/src/internal/consts.dart';
import 'package:kdbx/src/internal/crypto_utils.dart';
@ -191,7 +193,9 @@ class HashCredentials implements Credentials {
}
class KdbxBody extends KdbxNode {
KdbxBody.create(this.meta, this.rootGroup) : super.create('KeePassFile') {
KdbxBody.create(this.meta, this.rootGroup)
: _deletedObjects = [],
super.create('KeePassFile') {
node.children.add(meta.node);
final rootNode = xml.XmlElement(xml.XmlName('Root'));
node.children.add(rootNode);
@ -202,11 +206,17 @@ class KdbxBody extends KdbxNode {
xml.XmlElement node,
this.meta,
this.rootGroup,
) : super.read(node);
Iterable<KdbxDeletedObject> deletedObjects,
) : _deletedObjects = List.of(deletedObjects),
super.read(node);
// final xml.XmlDocument xmlDocument;
final KdbxMeta meta;
final KdbxGroup rootGroup;
final List<KdbxDeletedObject> _deletedObjects;
@visibleForTesting
List<KdbxDeletedObject> get deletedObjects => _deletedObjects;
Future<void> writeV3(WriterHelper writer, KdbxFile kdbxFile,
ProtectedSaltGenerator saltGenerator) async {
@ -302,7 +312,13 @@ class KdbxBody extends KdbxNode {
'KeePassFile',
nest: [
meta.toXml(),
() => builder.element('Root', nest: rootGroupNode),
() => builder.element('Root', nest: [
rootGroupNode,
XmlUtils.createNode(
KdbxXml.NODE_DELETED_OBJECTS,
_deletedObjects.map((e) => e.toXml()).toList(),
),
]),
],
);
// final doc = xml.XmlDocument();
@ -637,9 +653,16 @@ class KdbxFormat {
}
final rootGroup =
KdbxGroup.read(ctx, null, root.findElements('Group').single);
KdbxGroup.read(ctx, null, root.findElements(KdbxXml.NODE_GROUP).single);
final deletedObjects = root
.findElements(KdbxXml.NODE_DELETED_OBJECTS)
.singleOrNull
?.let((el) => el
.findElements(KdbxDeletedObject.NODE_NAME)
.map((node) => KdbxDeletedObject.read(node, ctx))) ??
[];
_logger.fine('successfully read Meta.');
return KdbxBody.read(keePassFile, kdbxMeta, rootGroup);
return KdbxBody.read(keePassFile, kdbxMeta, rootGroup, deletedObjects);
}
Uint8List _decryptContent(

4
lib/src/kdbx_group.dart

@ -15,7 +15,7 @@ class KdbxGroup extends KdbxObject {
: super.create(
ctx,
parent?.file,
'Group',
KdbxXml.NODE_GROUP,
parent,
) {
this.name.set(name);
@ -26,7 +26,7 @@ class KdbxGroup extends KdbxObject {
KdbxGroup.read(KdbxReadWriteContext ctx, KdbxGroup parent, XmlElement node)
: super.read(ctx, parent, node) {
node
.findElements('Group')
.findElements(KdbxXml.NODE_GROUP)
.map((el) => KdbxGroup.read(ctx, this, el))
.forEach(_groups.add);
node

3
lib/src/kdbx_xml.dart

@ -14,6 +14,9 @@ class KdbxXml {
static const NODE_VALUE = 'Value';
static const ATTR_PROTECTED = 'Protected';
static const ATTR_COMPRESSED = 'Compressed';
static const NODE_GROUP = 'Group';
static const NODE_DELETED_OBJECT = 'DeletedObject';
static const NODE_DELETED_OBJECTS = 'DeletedObjects';
static const NODE_HISTORY = 'History';
static const NODE_BINARIES = 'Binaries';
static const ATTR_ID = 'ID';

24
test/deleted_objects_test.dart

@ -0,0 +1,24 @@
@Tags(['kdbx4'])
import 'package:logging/logging.dart';
import 'package:test/test.dart';
import 'internal/test_utils.dart';
final _logger = Logger('deleted_objects_test');
void main() {
TestUtil.setupLogging();
_logger.finest('Running deleted objects tests.');
group('read tombstones', () {
test('load/save keeps deleted objects.', () async {
final orig =
await TestUtil.readKdbxFile('test/test_files/tombstonetest.kdbx');
expect(orig.body.deletedObjects, hasLength(1));
final dt = orig.body.deletedObjects.first.deletionTime.get();
expect([dt.year, dt.month, dt.day], [2020, 8, 30]);
final reload = await TestUtil.saveAndRead(orig);
expect(reload.body.deletedObjects, hasLength(1));
});
});
}

BIN
test/test_files/tombstonetest.kdbx

Binary file not shown.
Loading…
Cancel
Save