Browse Source

add support for deleting, moving groups and entries.

remove-cryptography-dependency
Herbert Poul 5 years ago
parent
commit
fbf3378afd
  1. 1
      lib/kdbx.dart
  2. 67
      lib/src/kdbx_dao.dart
  3. 15
      lib/src/kdbx_entry.dart
  4. 2
      lib/src/kdbx_format.dart
  5. 50
      lib/src/kdbx_group.dart
  6. 25
      lib/src/kdbx_object.dart
  7. 7
      lib/src/kdbx_xml.dart

1
lib/kdbx.dart

@ -5,6 +5,7 @@ export 'src/crypto/protected_value.dart'
show ProtectedValue, StringValue, PlainValue; show ProtectedValue, StringValue, PlainValue;
export 'src/kdbx_consts.dart'; export 'src/kdbx_consts.dart';
export 'src/kdbx_custom_data.dart'; export 'src/kdbx_custom_data.dart';
export 'src/kdbx_dao.dart' show KdbxDao;
export 'src/kdbx_entry.dart'; export 'src/kdbx_entry.dart';
export 'src/kdbx_format.dart'; export 'src/kdbx_format.dart';
export 'src/kdbx_group.dart'; export 'src/kdbx_group.dart';

67
lib/src/kdbx_dao.dart

@ -0,0 +1,67 @@
import 'package:kdbx/kdbx.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
final _logger = Logger('kdbx_dao');
/// Helper object for accessing and modifing data inside
/// a kdbx file.
extension KdbxDao on KdbxFile {
KdbxGroup createGroup({
@required KdbxGroup parent,
@required String name,
}) {
assert(parent != null, name != null);
final newGroup = KdbxGroup.create(parent: parent, name: name);
parent.addGroup(newGroup);
return newGroup;
}
KdbxGroup findGroupByUuid(KdbxUuid uuid) =>
body.rootGroup.getAllGroups().firstWhere((group) => group.uuid == uuid,
orElse: () =>
throw StateError('Unable to find group with uuid $uuid'));
KdbxGroup _createRecycleBin() {
body.meta.recycleBinEnabled.set(true);
final group = createGroup(parent: body.rootGroup, name: 'Trash');
group.icon.set(KdbxIcon.TrashBin);
group.enableAutoType.set(false);
group.enableSearching.set(false);
body.meta.recycleBinUUID.set(group.uuid);
return group;
}
KdbxGroup get recycleBin {
final uuid = body.meta.recycleBinUUID.get();
if (uuid == null) {
return _createRecycleBin();
}
_logger.finer(() {
final groupDebug = body.rootGroup
.getAllGroups()
.map((g) => '${g.uuid}: ${g.name}')
.join('\n');
return 'All Groups: $groupDebug';
});
return findGroupByUuid(uuid);
}
void deleteGroup(KdbxGroup group) {
move(group, recycleBin);
}
void move(KdbxObject kdbxObject, KdbxGroup toGroup) {
assert(toGroup != null);
kdbxObject.times.locationChanged.setToNow();
if (kdbxObject is KdbxGroup) {
kdbxObject.parent.internalRemoveGroup(kdbxObject);
kdbxObject.internalChangeParent(toGroup);
toGroup.addGroup(kdbxObject);
} else if (kdbxObject is KdbxEntry) {
kdbxObject.parent.internalRemoveEntry(kdbxObject);
kdbxObject.internalChangeParent(toGroup);
toGroup.addEntry(kdbxObject);
}
}
}

15
lib/src/kdbx_entry.dart

@ -30,14 +30,15 @@ class KdbxKey {
} }
class KdbxEntry extends KdbxObject { class KdbxEntry extends KdbxObject {
KdbxEntry.create(KdbxFile file, this.parent) KdbxEntry.create(KdbxFile file, KdbxGroup parent)
: isHistoryEntry = false, : isHistoryEntry = false,
super.create(file, 'Entry') { super.create(file, 'Entry', parent) {
icon.set(KdbxIcon.Key); icon.set(KdbxIcon.Key);
} }
KdbxEntry.read(this.parent, XmlElement node, {this.isHistoryEntry = false}) KdbxEntry.read(KdbxGroup parent, XmlElement node,
: super.read(node) { {this.isHistoryEntry = false})
: super.read(parent, node) {
_strings.addEntries(node.findElements(KdbxXml.NODE_STRING).map((el) { _strings.addEntries(node.findElements(KdbxXml.NODE_STRING).map((el) {
final key = KdbxKey(el.findElements(KdbxXml.NODE_KEY).single.text); final key = KdbxKey(el.findElements(KdbxXml.NODE_KEY).single.text);
final valueNode = el.findElements(KdbxXml.NODE_VALUE).single; final valueNode = el.findElements(KdbxXml.NODE_VALUE).single;
@ -111,7 +112,6 @@ class KdbxEntry extends KdbxObject {
return el; return el;
} }
KdbxGroup parent;
final Map<KdbxKey, StringValue> _strings = {}; final Map<KdbxKey, StringValue> _strings = {};
// Map<KdbxKey, StringValue> get strings => UnmodifiableMapView(_strings); // Map<KdbxKey, StringValue> get strings => UnmodifiableMapView(_strings);
@ -152,4 +152,9 @@ class KdbxEntry extends KdbxObject {
} }
String get label => _plainValue(KdbxKey('Title')); String get label => _plainValue(KdbxKey('Title'));
@override
String toString() {
return 'KdbxGroup{uuid=$uuid,name=$label}';
}
} }

2
lib/src/kdbx_format.dart

@ -5,6 +5,7 @@ import 'dart:typed_data';
import 'package:convert/convert.dart' as convert; import 'package:convert/convert.dart' as convert;
import 'package:crypto/crypto.dart' as crypto; import 'package:crypto/crypto.dart' as crypto;
import 'package:kdbx/kdbx.dart';
import 'package:kdbx/src/crypto/protected_salt_generator.dart'; import 'package:kdbx/src/crypto/protected_salt_generator.dart';
import 'package:kdbx/src/crypto/protected_value.dart'; import 'package:kdbx/src/crypto/protected_value.dart';
import 'package:kdbx/src/internal/byte_utils.dart'; import 'package:kdbx/src/internal/byte_utils.dart';
@ -18,7 +19,6 @@ import 'package:logging/logging.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:pointycastle/export.dart'; import 'package:pointycastle/export.dart';
import 'package:xml/xml.dart' as xml; import 'package:xml/xml.dart' as xml;
import 'package:kdbx/src/internal/extension_utils.dart';
final _logger = Logger('kdbx.format'); final _logger = Logger('kdbx.format');

50
lib/src/kdbx_group.dart

@ -8,18 +8,22 @@ import 'package:xml/xml.dart';
import 'kdbx_object.dart'; import 'kdbx_object.dart';
class KdbxGroup extends KdbxObject { class KdbxGroup extends KdbxObject {
KdbxGroup.create({@required this.parent, @required String name}) KdbxGroup.create({@required KdbxGroup parent, @required String name})
: super.create(parent?.file, 'Group') { : super.create(
parent?.file,
'Group',
parent,
) {
this.name.set(name); this.name.set(name);
icon.set(KdbxIcon.Folder); icon.set(KdbxIcon.Folder);
expanded.set(true); expanded.set(true);
} }
KdbxGroup.read(this.parent, XmlElement node) : super.read(node) { KdbxGroup.read(KdbxGroup parent, XmlElement node) : super.read(parent, node) {
node node
.findElements('Group') .findElements('Group')
.map((el) => KdbxGroup.read(this, el)) .map((el) => KdbxGroup.read(this, el))
.forEach(groups.add); .forEach(_groups.add);
node node
.findElements('Entry') .findElements('Entry')
.map((el) => KdbxEntry.read(this, el)) .map((el) => KdbxEntry.read(this, el))
@ -45,9 +49,8 @@ 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);
/// null if this is the root group. List<KdbxGroup> get groups => List.unmodifiable(_groups);
final KdbxGroup parent; final List<KdbxGroup> _groups = [];
final List<KdbxGroup> groups = [];
List<KdbxEntry> get entries => List.unmodifiable(_entries); List<KdbxEntry> get entries => List.unmodifiable(_entries);
final List<KdbxEntry> _entries = []; final List<KdbxEntry> _entries = [];
@ -58,7 +61,29 @@ class KdbxGroup extends KdbxObject {
'Invalid operation. Trying to add entry which is already in another group.'); 'Invalid operation. Trying to add entry which is already in another group.');
} }
_entries.add(entry); _entries.add(entry);
node.children.add(entry.node); isDirty = true;
}
void addGroup(KdbxGroup group) {
if (group.parent != this) {
throw StateError(
'Invalid operation. Trying to add group which is already in another group.');
}
_groups.add(group);
isDirty = true;
}
void internalRemoveGroup(KdbxGroup group) {
if (!_groups.remove(group)) {
throw StateError('Unable to remove $group from $this (Not found)');
}
isDirty = true;
}
void internalRemoveEntry(KdbxEntry entry) {
if (!_entries.remove(entry)) {
throw StateError('Unable to remove $entry from $this (Not found)');
}
isDirty = true; isDirty = true;
} }
@ -66,4 +91,13 @@ class KdbxGroup extends KdbxObject {
// String get name => text('Name') ?? ''; // String get name => text('Name') ?? '';
BooleanNode get expanded => BooleanNode(this, 'IsExpanded'); BooleanNode get expanded => BooleanNode(this, 'IsExpanded');
BooleanNode get enableAutoType => BooleanNode(this, 'EnableAutoType');
BooleanNode get enableSearching => BooleanNode(this, 'EnableSearching');
@override
String toString() {
return 'KdbxGroup{uuid=$uuid,name=${name.get()}}';
}
} }

25
lib/src/kdbx_object.dart

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:kdbx/src/kdbx_format.dart'; import 'package:kdbx/src/kdbx_format.dart';
import 'package:kdbx/src/kdbx_group.dart';
import 'package:kdbx/src/kdbx_times.dart'; import 'package:kdbx/src/kdbx_times.dart';
import 'package:kdbx/src/kdbx_xml.dart'; import 'package:kdbx/src/kdbx_xml.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@ -19,6 +20,11 @@ class ChangeEvent<T> {
final T object; final T object;
final bool isDirty; final bool isDirty;
@override
String toString() {
return 'ChangeEvent{object: $object, isDirty: $isDirty}';
}
} }
mixin Changeable<T> { mixin Changeable<T> {
@ -60,14 +66,16 @@ abstract class KdbxNode with Changeable<KdbxNode> {
} }
abstract class KdbxObject extends KdbxNode { abstract class KdbxObject extends KdbxNode {
KdbxObject.create(this.file, String nodeName) KdbxObject.create(this.file, String nodeName, KdbxGroup parent)
: times = KdbxTimes.create(), : times = KdbxTimes.create(),
_parent = parent,
super.create(nodeName) { super.create(nodeName) {
_uuid.set(KdbxUuid.random()); _uuid.set(KdbxUuid.random());
} }
KdbxObject.read(XmlElement node) KdbxObject.read(KdbxGroup parent, XmlElement node)
: times = KdbxTimes.read(node.findElements('Times').single), : times = KdbxTimes.read(node.findElements('Times').single),
_parent = parent,
super.read(node); super.read(node);
/// the file this object is part of. will be set AFTER loading, etc. /// the file this object is part of. will be set AFTER loading, etc.
@ -81,6 +89,10 @@ abstract class KdbxObject extends KdbxNode {
IconNode get icon => IconNode(this, 'IconID'); IconNode get icon => IconNode(this, 'IconID');
KdbxGroup get parent => _parent;
KdbxGroup _parent;
@override @override
set isDirty(bool dirty) { set isDirty(bool dirty) {
if (dirty) { if (dirty) {
@ -99,6 +111,8 @@ abstract class KdbxObject extends KdbxNode {
el.children.add(times.toXml()); el.children.add(times.toXml());
return el; return el;
} }
void internalChangeParent(KdbxGroup parent) => _parent = parent;
} }
class KdbxUuid { class KdbxUuid {
@ -117,4 +131,11 @@ class KdbxUuid {
@override @override
String toString() => uuid; String toString() => uuid;
@override
bool operator ==(Object other) =>
identical(this, other) || other is KdbxUuid && uuid == other.uuid;
@override
int get hashCode => uuid.hashCode;
} }

7
lib/src/kdbx_xml.dart

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:clock/clock.dart';
import 'package:kdbx/kdbx.dart'; import 'package:kdbx/kdbx.dart';
import 'package:kdbx/src/kdbx_consts.dart'; import 'package:kdbx/src/kdbx_consts.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
@ -130,7 +131,7 @@ class BooleanNode extends KdbxSubTextNode<bool> {
@override @override
bool decode(String value) { bool decode(String value) {
switch (value) { switch (value?.toLowerCase()) {
case 'null': case 'null':
return null; return null;
case 'true': case 'true':
@ -148,6 +149,10 @@ class BooleanNode extends KdbxSubTextNode<bool> {
class DateTimeUtcNode extends KdbxSubTextNode<DateTime> { class DateTimeUtcNode extends KdbxSubTextNode<DateTime> {
DateTimeUtcNode(KdbxNode node, String name) : super(node, name); DateTimeUtcNode(KdbxNode node, String name) : super(node, name);
void setToNow() {
set(clock.now().toUtc());
}
@override @override
DateTime decode(String value) => DateTime.parse(value); DateTime decode(String value) => DateTime.parse(value);

Loading…
Cancel
Save