From 80fd9c01a80eb5f6416b9aaa7017feaef225a830 Mon Sep 17 00:00:00 2001 From: luckyrat Date: Mon, 19 Feb 2024 19:17:11 +0000 Subject: [PATCH 01/13] wip --- lib/kdbx.dart | 6 +- lib/src/kdbx_entry.dart | 310 +------------ lib/src/kdbx_meta.dart | 3 + .../browser_entry_settings.dart | 220 ++++++++++ .../browser_entry_settings_v1.dart | 408 ++++++++++++++++++ lib/src/kee_vault_model/entry_matcher.dart | 69 +++ .../kee_vault_model/entry_matcher_config.dart | 113 +++++ lib/src/kee_vault_model/enums.dart | 32 ++ lib/src/kee_vault_model/field.dart | 171 ++++++++ lib/src/kee_vault_model/field_matcher.dart | 125 ++++++ .../kee_vault_model/field_matcher_config.dart | 110 +++++ lib/src/kee_vault_model/form_field_type.dart | 8 + .../kee_vault_model.dart} | 39 +- lib/src/utils/field_type_utils.dart | 96 +++++ lib/src/utils/guid_service.dart | 12 + test/browser_entry_settings_test.dart | 86 ++++ test/internal/test_utils.dart | 3 +- 17 files changed, 1487 insertions(+), 324 deletions(-) create mode 100644 lib/src/kee_vault_model/browser_entry_settings.dart create mode 100644 lib/src/kee_vault_model/browser_entry_settings_v1.dart create mode 100644 lib/src/kee_vault_model/entry_matcher.dart create mode 100644 lib/src/kee_vault_model/entry_matcher_config.dart create mode 100644 lib/src/kee_vault_model/enums.dart create mode 100644 lib/src/kee_vault_model/field.dart create mode 100644 lib/src/kee_vault_model/field_matcher.dart create mode 100644 lib/src/kee_vault_model/field_matcher_config.dart create mode 100644 lib/src/kee_vault_model/form_field_type.dart rename lib/src/{field.dart => kee_vault_model/kee_vault_model.dart} (82%) create mode 100644 lib/src/utils/field_type_utils.dart create mode 100644 lib/src/utils/guid_service.dart create mode 100644 test/browser_entry_settings_test.dart diff --git a/lib/kdbx.dart b/lib/kdbx.dart index abddd6d..45ef7fe 100644 --- a/lib/kdbx.dart +++ b/lib/kdbx.dart @@ -1,6 +1,9 @@ /// dart library for reading keepass file format (kdbx). library kdbx; +import 'package:kdbx/src/kee_vault_model/enums.dart'; +import 'package:kdbx/src/kee_vault_model/form_field_type.dart'; + export 'src/credentials/credentials.dart' show Credentials, CredentialsPart, HashCredentials, PasswordCredentials; export 'src/credentials/keyfile.dart' show KeyFileComposite, KeyFileCredentials; @@ -8,7 +11,8 @@ export 'src/crypto/key_encrypter_kdf.dart' show KeyEncrypterKdf, KdfType, KdfField; export 'src/crypto/protected_value.dart' show ProtectedValue, StringValue, PlainValue; -export 'src/field.dart' show BrowserFieldModel, FormFieldType, FieldStorage; +export 'src/kee_vault_model/kee_vault_model.dart' + show BrowserFieldModel, FormFieldType, FieldStorage; export 'src/internal/kdf_cache.dart' show KdfCache; export 'src/kdbx_binary.dart' show KdbxBinary; export 'src/kdbx_consts.dart'; diff --git a/lib/src/kdbx_entry.dart b/lib/src/kdbx_entry.dart index 429c0c9..66f30e5 100644 --- a/lib/src/kdbx_entry.dart +++ b/lib/src/kdbx_entry.dart @@ -2,6 +2,8 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:typed_data'; import 'package:collection/collection.dart'; +import 'package:kdbx/src/kee_vault_model/browser_entry_settings_v1.dart'; +import 'package:kdbx/src/kee_vault_model/kee_vault_model.dart'; import 'package:kdbx/src/internal/extension_utils.dart'; import 'package:kdbx/src/kdbx_format.dart'; import 'package:kdbx/src/kdbx_object.dart'; @@ -72,302 +74,6 @@ class KdbxKey { } } -class BrowserEntrySettings { - BrowserEntrySettings({ - this.version = 1, - this.behaviour = BrowserAutoFillBehaviour.Default, - required this.minimumMatchAccuracy, - this.priority = 0, - this.hide = false, - this.realm = '', - List? includeUrls, - List? excludeUrls, - List? fields, - }) : includeUrls = includeUrls ?? [], - excludeUrls = excludeUrls ?? [], - fields = fields ?? []; - - factory BrowserEntrySettings.fromMap(Map? map, - {required MatchAccuracy minimumMatchAccuracy}) { - if (map == null) { - return BrowserEntrySettings(minimumMatchAccuracy: minimumMatchAccuracy); - } - - return BrowserEntrySettings( - version: map['version'] as int? ?? 1, - behaviour: getBehaviour(map), - minimumMatchAccuracy: getMam(map), - priority: map['priority'] as int? ?? 0, - hide: map['hide'] as bool? ?? false, - realm: map['hTTPRealm'] as String?, - includeUrls: getIncludeUrls(map), - excludeUrls: getExcludeUrls(map), - fields: List.from((map['formFieldList'] - as List?) - ?.cast>() - .map((x) => BrowserFieldModel.fromMap(x)) ?? - []), - ); - } - - factory BrowserEntrySettings.fromJson(String source, - {required MatchAccuracy minimumMatchAccuracy}) => - BrowserEntrySettings.fromMap(json.decode(source) as Map?, - minimumMatchAccuracy: minimumMatchAccuracy); - - int version; - // enum - BrowserAutoFillBehaviour behaviour; - // enum - MatchAccuracy minimumMatchAccuracy; - int priority; // always 0 - bool hide; - String? realm; - List includeUrls; - List excludeUrls; - List fields; - - BrowserEntrySettings copyWith({ - int? version, - BrowserAutoFillBehaviour? behaviour, - MatchAccuracy? minimumMatchAccuracy, - int? priority, - bool? hide, - String? realm, - List? includeUrls, - List? excludeUrls, - List? fields, - }) { - return BrowserEntrySettings( - version: version ?? this.version, - behaviour: behaviour ?? this.behaviour, - minimumMatchAccuracy: minimumMatchAccuracy ?? this.minimumMatchAccuracy, - priority: priority ?? this.priority, - hide: hide ?? this.hide, - realm: realm ?? this.realm, - includeUrls: includeUrls ?? this.includeUrls, - excludeUrls: excludeUrls ?? this.excludeUrls, - fields: fields ?? this.fields, - ); - } - - static BrowserAutoFillBehaviour getBehaviour(Map map) { - if (map['neverAutoFill'] as bool? ?? false) { - return BrowserAutoFillBehaviour.NeverAutoFillNeverAutoSubmit; - } else if (map['alwaysAutoSubmit'] as bool? ?? false) { - return BrowserAutoFillBehaviour.AlwaysAutoFillAlwaysAutoSubmit; - } else if ((map['alwaysAutoFill'] as bool? ?? false) && - (map['neverAutoSubmit'] as bool? ?? false)) { - return BrowserAutoFillBehaviour.AlwaysAutoFillNeverAutoSubmit; - } else if (map['neverAutoSubmit'] as bool? ?? false) { - return BrowserAutoFillBehaviour.NeverAutoSubmit; - } else if (map['alwaysAutoFill'] as bool? ?? false) { - return BrowserAutoFillBehaviour.AlwaysAutoFill; - } else { - return BrowserAutoFillBehaviour.Default; - } - } - - static MatchAccuracy getMam(Map map) { - if (map['blockHostnameOnlyMatch'] as bool? ?? false) { - return MatchAccuracy.Exact; - } else if (map['blockDomainOnlyMatch'] as bool? ?? false) { - return MatchAccuracy.Hostname; - } else { - return MatchAccuracy.Domain; - } - } - - static Map parseBehaviour(BrowserAutoFillBehaviour behaviour) { - switch (behaviour) { - case BrowserAutoFillBehaviour.AlwaysAutoFill: - return { - 'alwaysAutoFill': true, - 'alwaysAutoSubmit': false, - 'neverAutoFill': false, - 'neverAutoSubmit': false, - }; - case BrowserAutoFillBehaviour.NeverAutoSubmit: - return { - 'alwaysAutoFill': false, - 'alwaysAutoSubmit': false, - 'neverAutoFill': false, - 'neverAutoSubmit': true, - }; - case BrowserAutoFillBehaviour.AlwaysAutoFillAlwaysAutoSubmit: - return { - 'alwaysAutoFill': true, - 'alwaysAutoSubmit': true, - 'neverAutoFill': false, - 'neverAutoSubmit': false, - }; - case BrowserAutoFillBehaviour.NeverAutoFillNeverAutoSubmit: - return { - 'alwaysAutoFill': false, - 'alwaysAutoSubmit': false, - 'neverAutoFill': true, - 'neverAutoSubmit': true, - }; - case BrowserAutoFillBehaviour.AlwaysAutoFillNeverAutoSubmit: - return { - 'alwaysAutoFill': true, - 'alwaysAutoSubmit': false, - 'neverAutoFill': false, - 'neverAutoSubmit': true, - }; - case BrowserAutoFillBehaviour.Default: - return { - 'alwaysAutoFill': false, - 'alwaysAutoSubmit': false, - 'neverAutoFill': false, - 'neverAutoSubmit': false, - }; - } - } - - static Map parseMam(MatchAccuracy mam) { - switch (mam) { - case MatchAccuracy.Domain: - return { - 'blockDomainOnlyMatch': false, - 'blockHostnameOnlyMatch': false, - }; - case MatchAccuracy.Hostname: - return { - 'blockDomainOnlyMatch': true, - 'blockHostnameOnlyMatch': false, - }; - default: - return { - 'blockDomainOnlyMatch': false, - 'blockHostnameOnlyMatch': true, - }; - } - } - - static Map> parseUrls( - List includeUrls, List excludeUrls) { - final altURLs = []; - final regExURLs = []; - final blockedURLs = []; - final regExBlockedURLs = []; - for (final p in includeUrls) { - if (p is RegExp) { - regExURLs.add(p.pattern); - } else if (p is String) { - altURLs.add(p); - } - } - for (final p in excludeUrls) { - if (p is RegExp) { - regExBlockedURLs.add(p.pattern); - } else if (p is String) { - blockedURLs.add(p); - } - } - return >{ - 'altURLs': altURLs, - 'regExURLs': regExURLs, - 'blockedURLs': blockedURLs, - 'regExBlockedURLs': regExBlockedURLs, - }; - } - - static List getIncludeUrls(Map map) { - final includeUrls = []; - final altUrls = (map['altURLs'] as List?)?.cast(); - final regExURLs = (map['regExURLs'] as List?)?.cast(); - if (altUrls != null) { - altUrls.forEach(includeUrls.add); - } - if (regExURLs != null) { - for (final url in regExURLs) { - includeUrls.add(RegExp(url)); - } - } - return includeUrls; - } - - static List getExcludeUrls(Map map) { - final excludeUrls = []; - final blockedURLs = (map['blockedURLs'] as List?)?.cast(); - final regExBlockedURLs = - (map['regExBlockedURLs'] as List?)?.cast(); - if (blockedURLs != null) { - blockedURLs.forEach(excludeUrls.add); - } - if (regExBlockedURLs != null) { - for (final url in regExBlockedURLs) { - excludeUrls.add(RegExp(url)); - } - } - return excludeUrls; - } - - Map toMap() { - return { - 'version': version, - 'priority': priority, - 'hide': hide, - 'hTTPRealm': realm, - 'formFieldList': fields.map((x) => x.toMap()).toList(), - ...parseBehaviour(behaviour), - ...parseMam(minimumMatchAccuracy), - ...parseUrls(includeUrls, excludeUrls), - }; - } - - String toJson() => json.encode(toMap()); - - @override - String toString() { - return 'BrowserSettingsModel(version: $version, behaviour: $behaviour, minimumMatchAccuracy: $minimumMatchAccuracy, priority: $priority, hide: $hide, realm: $realm, includeUrls: $includeUrls, excludeUrls: $excludeUrls, fields: $fields)'; - } - - @override - // ignore: avoid_renaming_method_parameters - bool operator ==(Object o) { - if (identical(this, o)) { - return true; - } - final unOrdDeepEq = const DeepCollectionEquality.unordered().equals; - return o is BrowserEntrySettings && - o.version == version && - o.behaviour == behaviour && - o.minimumMatchAccuracy == minimumMatchAccuracy && - o.priority == priority && - o.hide == hide && - o.realm == realm && - unOrdDeepEq(o.includeUrls, includeUrls) && - unOrdDeepEq(o.excludeUrls, excludeUrls) && - unOrdDeepEq(o.fields, fields); - } - - @override - int get hashCode { - return version.hashCode ^ - behaviour.hashCode ^ - minimumMatchAccuracy.hashCode ^ - priority.hashCode ^ - hide.hashCode ^ - realm.hashCode ^ - includeUrls.hashCode ^ - excludeUrls.hashCode ^ - fields.hashCode; - } -} - -enum BrowserAutoFillBehaviour { - Default, - AlwaysAutoFill, - NeverAutoSubmit, - AlwaysAutoFillNeverAutoSubmit, - AlwaysAutoFillAlwaysAutoSubmit, - NeverAutoFillNeverAutoSubmit -} - -enum MatchAccuracy { Exact, Hostname, Domain } - extension KdbxEntryInternal on KdbxEntry { KdbxEntry cloneInto(KdbxGroup otherGroup, {bool toHistoryEntry = false, bool withNewUuid = false}) => @@ -461,7 +167,7 @@ class KdbxEntry extends KdbxObject { }) : history = [], super.create(file.ctx, file, 'Entry', parent) { icon.set(KdbxIcon.Key); - _browserSettings = BrowserEntrySettings( + _browserSettings = BrowserEntrySettingsV1( minimumMatchAccuracy: file.body.meta.browserSettings.defaultMatchAccuracy); } @@ -523,19 +229,19 @@ class KdbxEntry extends KdbxObject { customData['KeeVault.AndroidPackageNames'] = json.encode(names); } - BrowserEntrySettings? _browserSettings; - BrowserEntrySettings get browserSettings { + BrowserEntrySettingsV1? _browserSettings; + BrowserEntrySettingsV1 get browserSettings { if (_browserSettings == null) { final tempJson = stringEntries .firstWhereOrNull((s) => s.key.key == 'KPRPC JSON') ?.value; if (tempJson != null) { - _browserSettings = BrowserEntrySettings.fromJson(tempJson.getText(), + _browserSettings = BrowserEntrySettingsV1.fromJson(tempJson.getText(), minimumMatchAccuracy: file!.body.meta.browserSettings.defaultMatchAccuracy); } else { - _browserSettings = BrowserEntrySettings( + _browserSettings = BrowserEntrySettingsV1( minimumMatchAccuracy: file!.body.meta.browserSettings.defaultMatchAccuracy); } @@ -543,7 +249,7 @@ class KdbxEntry extends KdbxObject { return _browserSettings!; } - set browserSettings(BrowserEntrySettings settings) { + set browserSettings(BrowserEntrySettingsV1 settings) { setString( KdbxKey('KPRPC JSON'), ProtectedValue.fromString(settings.toJson())); _browserSettings = null; diff --git a/lib/src/kdbx_meta.dart b/lib/src/kdbx_meta.dart index a7ee9f4..0ab59ef 100644 --- a/lib/src/kdbx_meta.dart +++ b/lib/src/kdbx_meta.dart @@ -12,12 +12,15 @@ import 'package:kdbx/src/kdbx_format.dart'; import 'package:kdbx/src/kdbx_header.dart'; import 'package:kdbx/src/kdbx_object.dart'; import 'package:kdbx/src/kdbx_xml.dart'; +import 'package:kdbx/src/kee_vault_model/enums.dart'; import 'package:logging/logging.dart'; import 'package:quiver/iterables.dart'; import 'package:uuid/uuid.dart'; import 'package:xml/xml.dart' as xml; import 'package:xml/xml.dart'; +import 'kee_vault_model/kee_vault_model.dart'; + final _logger = Logger('kdbx_meta'); class KdbxMeta extends KdbxNode implements KdbxNodeContext { diff --git a/lib/src/kee_vault_model/browser_entry_settings.dart b/lib/src/kee_vault_model/browser_entry_settings.dart new file mode 100644 index 0000000..6c1ac82 --- /dev/null +++ b/lib/src/kee_vault_model/browser_entry_settings.dart @@ -0,0 +1,220 @@ +import 'dart:convert'; +import 'package:collection/collection.dart'; +import 'package:kdbx/src/kee_vault_model/browser_entry_settings_v1.dart'; +import 'package:kdbx/src/kee_vault_model/entry_matcher_config.dart'; +import 'package:kdbx/src/kee_vault_model/enums.dart'; +import 'package:kdbx/src/kee_vault_model/field.dart'; + +//TODO: EntryConfigV2 +//TODO: translate TS unit tests for migration +//TODO: implement conversion between versions +//TODO: kdbxswift too + +class BrowserEntrySettings { + BrowserEntrySettings({ + this.version = 2, + List? includeUrls, + List? excludeUrls, + this.realm = '', + List? authenticationMethods, + this.behaviour = BrowserAutoFillBehaviour.Default, + required this.matcherConfigs, + List? fields, + }) : authenticationMethods = authenticationMethods ?? [], + includeUrls = includeUrls ?? [], + excludeUrls = excludeUrls ?? [], + fields = fields ?? []; + + factory BrowserEntrySettings.fromMap(Map? map, + {required MatchAccuracy minimumMatchAccuracy}) { + if (map == null) { + return BrowserEntrySettings(matcherConfigs: [ + EntryMatcherConfig.forDefaultUrlMatchBehaviour(minimumMatchAccuracy) + ]); + } + + return BrowserEntrySettings( + version: map['version'] as int? ?? 2, + includeUrls: getIncludeUrls(map), + excludeUrls: getExcludeUrls(map), + realm: map['hTTPRealm'] as String?, + authenticationMethods: + (map['authenticationMethods'] as List?)?.cast() ?? + [], + behaviour: BrowserAutoFillBehaviour.values + .firstWhereOrNull((v) => v.name == map['behaviour']), + matcherConfigs: List.from((map['matcherConfigs'] + as List?) + ?.cast>() + .map((x) => EntryMatcherConfig.fromMap(x)) ?? + []), + fields: List.from((map['fields'] as List?) + ?.cast>() + .map((x) => Field.fromMap(x)) ?? + []), + ); + } + + factory BrowserEntrySettings.fromJson(String source, + {required MatchAccuracy minimumMatchAccuracy}) => + BrowserEntrySettings.fromMap(json.decode(source) as Map?, + minimumMatchAccuracy: minimumMatchAccuracy); + + int version; + List includeUrls; + List excludeUrls; + String? realm; + List? authenticationMethods; + // enum + BrowserAutoFillBehaviour? behaviour; + List matcherConfigs; + List? fields; + + BrowserEntrySettings copyWith({ + int? version, + List? includeUrls, + List? excludeUrls, + String? realm, + List? authenticationMethods, + BrowserAutoFillBehaviour? behaviour, + List? matcherConfigs, + List? fields, + }) { + return BrowserEntrySettings( + version: version ?? this.version, + behaviour: behaviour ?? this.behaviour, + authenticationMethods: + authenticationMethods ?? this.authenticationMethods, + realm: realm ?? this.realm, + includeUrls: includeUrls ?? this.includeUrls, + excludeUrls: excludeUrls ?? this.excludeUrls, + fields: fields ?? this.fields, + matcherConfigs: matcherConfigs ?? this.matcherConfigs, + ); + } + + BrowserEntrySettingsV1 convertToV1() { + return BrowserEntrySettingsV1( + minimumMatchAccuracy: matcherConfigs + .firstWhereOrNull( + (element) => element.matcherType == EntryMatcherType.Url) + ?.urlMatchMethod ?? + MatchAccuracy.Domain, + realm: realm ?? '', + fields: fields?.map((f) => f.convertToV1()).nonNulls.toList(), + behaviour: behaviour ?? BrowserAutoFillBehaviour.Default, + excludeUrls: excludeUrls, + includeUrls: includeUrls, + priority: 0, + hide: matcherConfigs + .any((element) => element.matcherType == EntryMatcherType.Hide)); + } + + static Map> parseUrls( + List includeUrls, List excludeUrls) { + final altURLs = []; + final regExURLs = []; + final blockedURLs = []; + final regExBlockedURLs = []; + for (final p in includeUrls) { + if (p is RegExp) { + regExURLs.add(p.pattern); + } else if (p is String) { + altURLs.add(p); + } + } + for (final p in excludeUrls) { + if (p is RegExp) { + regExBlockedURLs.add(p.pattern); + } else if (p is String) { + blockedURLs.add(p); + } + } + return >{ + if (altURLs.isNotEmpty) 'altURLs': altURLs, + if (regExURLs.isNotEmpty) 'regExURLs': regExURLs, + if (blockedURLs.isNotEmpty) 'blockedURLs': blockedURLs, + if (regExBlockedURLs.isNotEmpty) 'regExBlockedURLs': regExBlockedURLs, + }; + } + + static List getIncludeUrls(Map map) { + final includeUrls = []; + final altUrls = (map['altURLs'] as List?)?.cast(); + final regExURLs = (map['regExURLs'] as List?)?.cast(); + if (altUrls != null) { + altUrls.forEach(includeUrls.add); + } + if (regExURLs != null) { + for (final url in regExURLs) { + includeUrls.add(RegExp(url)); + } + } + return includeUrls; + } + + static List getExcludeUrls(Map map) { + final excludeUrls = []; + final blockedURLs = (map['blockedURLs'] as List?)?.cast(); + final regExBlockedURLs = + (map['regExBlockedURLs'] as List?)?.cast(); + if (blockedURLs != null) { + blockedURLs.forEach(excludeUrls.add); + } + if (regExBlockedURLs != null) { + for (final url in regExBlockedURLs) { + excludeUrls.add(RegExp(url)); + } + } + return excludeUrls; + } + + Map toMap() { + return { + 'version': version, + 'authenticationMethods': authenticationMethods, + 'hTTPRealm': realm, + if (fields != null) 'fields': fields?.map((x) => x.toMap()).toList(), + 'behaviour': behaviour?.name, + 'matcherConfigs': matcherConfigs.map((x) => x.toMap()).toList(), + ...parseUrls(includeUrls, excludeUrls), + }; + } + + String toJson() => json.encode(toMap()); + + @override + String toString() { + return 'BrowserSettingsModel(version: $version, behaviour: $behaviour, realm: $realm, includeUrls: $includeUrls, excludeUrls: $excludeUrls, fields: $fields)'; + } + + @override + // ignore: avoid_renaming_method_parameters + bool operator ==(Object o) { + if (identical(this, o)) { + return true; + } + final unOrdDeepEq = const DeepCollectionEquality.unordered().equals; + return o is BrowserEntrySettings && + o.version == version && + o.behaviour == behaviour && + o.authenticationMethods == authenticationMethods && + o.realm == realm && + unOrdDeepEq(o.matcherConfigs, matcherConfigs) && + unOrdDeepEq(o.includeUrls, includeUrls) && + unOrdDeepEq(o.excludeUrls, excludeUrls) && + unOrdDeepEq(o.fields, fields); + } + + @override + int get hashCode { + return version.hashCode ^ + behaviour.hashCode ^ + authenticationMethods.hashCode ^ + realm.hashCode ^ + matcherConfigs.hashCode ^ + includeUrls.hashCode ^ + excludeUrls.hashCode ^ + fields.hashCode; + } +} diff --git a/lib/src/kee_vault_model/browser_entry_settings_v1.dart b/lib/src/kee_vault_model/browser_entry_settings_v1.dart new file mode 100644 index 0000000..7610644 --- /dev/null +++ b/lib/src/kee_vault_model/browser_entry_settings_v1.dart @@ -0,0 +1,408 @@ +import 'dart:convert'; +import 'dart:math'; +import 'package:collection/collection.dart'; +import 'package:kdbx/src/kee_vault_model/browser_entry_settings.dart'; +import 'package:kdbx/src/kee_vault_model/entry_matcher_config.dart'; +import 'package:kdbx/src/kee_vault_model/enums.dart'; +import 'package:kdbx/src/kee_vault_model/field.dart'; +import 'package:kdbx/src/kee_vault_model/kee_vault_model.dart'; + +import '../utils/field_type_utils.dart'; +import '../utils/guid_service.dart'; +import 'field_matcher_config.dart'; +import 'form_field_type.dart'; + +class BrowserEntrySettingsV1 { + BrowserEntrySettingsV1({ + this.version = 1, + this.behaviour = BrowserAutoFillBehaviour.Default, + required this.minimumMatchAccuracy, + this.priority = 0, + this.hide = false, + this.realm = '', + List? includeUrls, + List? excludeUrls, + List? fields, + }) : includeUrls = includeUrls ?? [], + excludeUrls = excludeUrls ?? [], + fields = fields ?? []; + + factory BrowserEntrySettingsV1.fromMap(Map? map, + {required MatchAccuracy minimumMatchAccuracy}) { + if (map == null) { + return BrowserEntrySettingsV1(minimumMatchAccuracy: minimumMatchAccuracy); + } + + return BrowserEntrySettingsV1( + version: map['version'] as int? ?? 1, + behaviour: getBehaviour(map), + minimumMatchAccuracy: getMam(map), + priority: map['priority'] as int? ?? 0, + hide: map['hide'] as bool? ?? false, + realm: map['hTTPRealm'] as String?, + includeUrls: getIncludeUrls(map), + excludeUrls: getExcludeUrls(map), + fields: List.from( + (map['formFieldList'] as List?) + ?.cast>() + .map( + (x) => BrowserFieldModelV1.fromMap(x)) ?? + []), + ); + } + + factory BrowserEntrySettingsV1.fromJson(String source, + {required MatchAccuracy minimumMatchAccuracy}) => + BrowserEntrySettingsV1.fromMap( + json.decode(source) as Map?, + minimumMatchAccuracy: minimumMatchAccuracy); + + int version; + // enum + BrowserAutoFillBehaviour behaviour; + // enum + MatchAccuracy minimumMatchAccuracy; + int priority; // always 0 + bool hide; + String? realm; + List includeUrls; + List excludeUrls; + List fields; + + BrowserEntrySettingsV1 copyWith({ + int? version, + BrowserAutoFillBehaviour? behaviour, + MatchAccuracy? minimumMatchAccuracy, + int? priority, + bool? hide, + String? realm, + List? includeUrls, + List? excludeUrls, + List? fields, + }) { + return BrowserEntrySettingsV1( + version: version ?? this.version, + behaviour: behaviour ?? this.behaviour, + minimumMatchAccuracy: minimumMatchAccuracy ?? this.minimumMatchAccuracy, + priority: priority ?? this.priority, + hide: hide ?? this.hide, + realm: realm ?? this.realm, + includeUrls: includeUrls ?? this.includeUrls, + excludeUrls: excludeUrls ?? this.excludeUrls, + fields: fields ?? this.fields, + ); + } + + static BrowserAutoFillBehaviour getBehaviour(Map map) { + if (map['neverAutoFill'] as bool? ?? false) { + return BrowserAutoFillBehaviour.NeverAutoFillNeverAutoSubmit; + } else if (map['alwaysAutoSubmit'] as bool? ?? false) { + return BrowserAutoFillBehaviour.AlwaysAutoFillAlwaysAutoSubmit; + } else if ((map['alwaysAutoFill'] as bool? ?? false) && + (map['neverAutoSubmit'] as bool? ?? false)) { + return BrowserAutoFillBehaviour.AlwaysAutoFillNeverAutoSubmit; + } else if (map['neverAutoSubmit'] as bool? ?? false) { + return BrowserAutoFillBehaviour.NeverAutoSubmit; + } else if (map['alwaysAutoFill'] as bool? ?? false) { + return BrowserAutoFillBehaviour.AlwaysAutoFill; + } else { + return BrowserAutoFillBehaviour.Default; + } + } + + static MatchAccuracy getMam(Map map) { + if (map['blockHostnameOnlyMatch'] as bool? ?? false) { + return MatchAccuracy.Exact; + } else if (map['blockDomainOnlyMatch'] as bool? ?? false) { + return MatchAccuracy.Hostname; + } else { + return MatchAccuracy.Domain; + } + } + + static Map parseBehaviour(BrowserAutoFillBehaviour behaviour) { + switch (behaviour) { + case BrowserAutoFillBehaviour.AlwaysAutoFill: + return { + 'alwaysAutoFill': true, + 'alwaysAutoSubmit': false, + 'neverAutoFill': false, + 'neverAutoSubmit': false, + }; + case BrowserAutoFillBehaviour.NeverAutoSubmit: + return { + 'alwaysAutoFill': false, + 'alwaysAutoSubmit': false, + 'neverAutoFill': false, + 'neverAutoSubmit': true, + }; + case BrowserAutoFillBehaviour.AlwaysAutoFillAlwaysAutoSubmit: + return { + 'alwaysAutoFill': true, + 'alwaysAutoSubmit': true, + 'neverAutoFill': false, + 'neverAutoSubmit': false, + }; + case BrowserAutoFillBehaviour.NeverAutoFillNeverAutoSubmit: + return { + 'alwaysAutoFill': false, + 'alwaysAutoSubmit': false, + 'neverAutoFill': true, + 'neverAutoSubmit': true, + }; + case BrowserAutoFillBehaviour.AlwaysAutoFillNeverAutoSubmit: + return { + 'alwaysAutoFill': true, + 'alwaysAutoSubmit': false, + 'neverAutoFill': false, + 'neverAutoSubmit': true, + }; + case BrowserAutoFillBehaviour.Default: + return { + 'alwaysAutoFill': false, + 'alwaysAutoSubmit': false, + 'neverAutoFill': false, + 'neverAutoSubmit': false, + }; + } + } + + static Map parseMam(MatchAccuracy mam) { + switch (mam) { + case MatchAccuracy.Domain: + return { + 'blockDomainOnlyMatch': false, + 'blockHostnameOnlyMatch': false, + }; + case MatchAccuracy.Hostname: + return { + 'blockDomainOnlyMatch': true, + 'blockHostnameOnlyMatch': false, + }; + default: + return { + 'blockDomainOnlyMatch': false, + 'blockHostnameOnlyMatch': true, + }; + } + } + + static Map> parseUrls( + List includeUrls, List excludeUrls) { + final altURLs = []; + final regExURLs = []; + final blockedURLs = []; + final regExBlockedURLs = []; + for (final p in includeUrls) { + if (p is RegExp) { + regExURLs.add(p.pattern); + } else if (p is String) { + altURLs.add(p); + } + } + for (final p in excludeUrls) { + if (p is RegExp) { + regExBlockedURLs.add(p.pattern); + } else if (p is String) { + blockedURLs.add(p); + } + } + return >{ + 'altURLs': altURLs, + 'regExURLs': regExURLs, + 'blockedURLs': blockedURLs, + 'regExBlockedURLs': regExBlockedURLs, + }; + } + + static List getIncludeUrls(Map map) { + final includeUrls = []; + final altUrls = (map['altURLs'] as List?)?.cast(); + final regExURLs = (map['regExURLs'] as List?)?.cast(); + if (altUrls != null) { + altUrls.forEach(includeUrls.add); + } + if (regExURLs != null) { + for (final url in regExURLs) { + includeUrls.add(RegExp(url)); + } + } + return includeUrls; + } + + static List getExcludeUrls(Map map) { + final excludeUrls = []; + final blockedURLs = (map['blockedURLs'] as List?)?.cast(); + final regExBlockedURLs = + (map['regExBlockedURLs'] as List?)?.cast(); + if (blockedURLs != null) { + blockedURLs.forEach(excludeUrls.add); + } + if (regExBlockedURLs != null) { + for (final url in regExBlockedURLs) { + excludeUrls.add(RegExp(url)); + } + } + return excludeUrls; + } + + Map toMap() { + return { + 'version': version, + 'priority': priority, + 'hide': hide, + 'hTTPRealm': realm, + 'formFieldList': fields.map((x) => x.toMap()).toList(), + ...parseBehaviour(behaviour), + ...parseMam(minimumMatchAccuracy), + ...parseUrls(includeUrls, excludeUrls), + }; + } + + String toJson() => json.encode(toMap()); + + @override + String toString() { + return 'BrowserSettingsModelV1(version: $version, behaviour: $behaviour, minimumMatchAccuracy: $minimumMatchAccuracy, priority: $priority, hide: $hide, realm: $realm, includeUrls: $includeUrls, excludeUrls: $excludeUrls, fields: $fields)'; + } + + @override + // ignore: avoid_renaming_method_parameters + bool operator ==(Object o) { + if (identical(this, o)) { + return true; + } + final unOrdDeepEq = const DeepCollectionEquality.unordered().equals; + return o is BrowserEntrySettingsV1 && + o.version == version && + o.behaviour == behaviour && + o.minimumMatchAccuracy == minimumMatchAccuracy && + o.priority == priority && + o.hide == hide && + o.realm == realm && + unOrdDeepEq(o.includeUrls, includeUrls) && + unOrdDeepEq(o.excludeUrls, excludeUrls) && + unOrdDeepEq(o.fields, fields); + } + + @override + int get hashCode { + return version.hashCode ^ + behaviour.hashCode ^ + minimumMatchAccuracy.hashCode ^ + priority.hashCode ^ + hide.hashCode ^ + realm.hashCode ^ + includeUrls.hashCode ^ + excludeUrls.hashCode ^ + fields.hashCode; + } + + BrowserEntrySettings convertToV2(IGuidService guidService) { + final List mcList = [ + EntryMatcherConfig.forDefaultUrlMatchBehaviour(minimumMatchAccuracy), + if (hide) EntryMatcherConfig(matcherType: EntryMatcherType.Hide) + ]; + + final conf2 = BrowserEntrySettings( + behaviour: behaviour, + authenticationMethods: ['password'], + matcherConfigs: mcList, + includeUrls: includeUrls, + excludeUrls: excludeUrls, + realm: realm, + fields: convertFields(fields, guidService), + ); + + return conf2; + } + + List convertFields( + List formFieldList, IGuidService guidService) { + final List fields = []; + bool usernameFound = false; + bool passwordFound = false; + formFieldList.forEach((ff) { + if (ff.value == '{USERNAME}') { + usernameFound = true; + final mc = !((ff.fieldId?.isNotEmpty ?? false) || + (ff.name?.isNotEmpty ?? false)) + ? FieldMatcherConfig( + matcherType: FieldMatcherType.UsernameDefaultHeuristic) + : FieldMatcherConfig.forSingleClientMatch( + ff.fieldId, ff.name, FormFieldType.USERNAME); + final f = Field( + valuePath: 'UserName', + page: max(ff.page, 1), + uuid: guidService.newGuid(), + type: FieldType.Text, + matcherConfigs: [mc], + ); + if (ff.placeholderHandling != PlaceholderHandling.Default.name) { + f.placeholderHandling = PlaceholderHandling.values + .firstWhereOrNull((v) => v.name == ff.placeholderHandling); + } + fields.add(f); + } else if (ff.value == '{PASSWORD}') { + passwordFound = true; + final mc = !((ff.fieldId?.isNotEmpty ?? false) || + (ff.name?.isNotEmpty ?? false)) + ? FieldMatcherConfig( + matcherType: FieldMatcherType.PasswordDefaultHeuristic) + : FieldMatcherConfig.forSingleClientMatch( + ff.fieldId, ff.name, FormFieldType.PASSWORD); + final f = Field( + valuePath: 'Password', + page: max(ff.page, 1), + uuid: guidService.newGuid(), + type: FieldType.Password, + matcherConfigs: [mc]); + if (ff.placeholderHandling != PlaceholderHandling.Default.name) { + f.placeholderHandling = PlaceholderHandling.values + .firstWhereOrNull((v) => v.name == ff.placeholderHandling); + } + fields.add(f); + } else { + final mc = FieldMatcherConfig.forSingleClientMatch( + ff.fieldId, ff.name, ff.type ?? FormFieldType.TEXT); + final f = Field( + name: ff.displayName, + valuePath: '.', + page: max(ff.page, 1), + uuid: guidService.newGuid(), + type: Utilities.formFieldTypeToFieldType( + ff.type ?? FormFieldType.TEXT), + matcherConfigs: [mc], + value: ff.value); + if (ff.placeholderHandling != PlaceholderHandling.Default.name) { + f.placeholderHandling = PlaceholderHandling.values + .firstWhereOrNull((v) => v.name == ff.placeholderHandling); + } + fields.add(f); + } + }); + + if (!usernameFound) { + fields.add(Field( + valuePath: 'UserName', + uuid: guidService.newGuid(), + type: FieldType.Text, + matcherConfigs: [ + FieldMatcherConfig( + matcherType: FieldMatcherType.UsernameDefaultHeuristic) + ])); + } + if (!passwordFound) { + fields.add(Field( + valuePath: 'Password', + uuid: guidService.newGuid(), + type: FieldType.Password, + matcherConfigs: [ + FieldMatcherConfig( + matcherType: FieldMatcherType.PasswordDefaultHeuristic) + ])); + } + + return fields; + } +} diff --git a/lib/src/kee_vault_model/entry_matcher.dart b/lib/src/kee_vault_model/entry_matcher.dart new file mode 100644 index 0000000..6a7f5fe --- /dev/null +++ b/lib/src/kee_vault_model/entry_matcher.dart @@ -0,0 +1,69 @@ +import 'dart:convert'; +import 'package:collection/collection.dart'; +import 'package:kdbx/src/kee_vault_model/enums.dart'; + +class EntryMatcher { + EntryMatcher({ + this.matchLogic, + this.queries = const [], + this.pageTitles = const [], + }); + + EntryMatcher copyWith({ + MatcherLogic? matchLogic, + List? queries, + List? pageTitles, + }) { + return EntryMatcher( + matchLogic: matchLogic ?? this.matchLogic, + queries: queries ?? this.queries, + pageTitles: pageTitles ?? this.pageTitles, + ); + } + + Map toMap() { + return { + 'matchLogic': matchLogic?.name, + 'queries': queries, + 'pageTitles': pageTitles, + }; + } + + factory EntryMatcher.fromMap(Map? map) { + if (map == null) { + return EntryMatcher(); + } + + return EntryMatcher( + matchLogic: MatcherLogic.values + .firstWhereOrNull((v) => v.name == map['matchLogic']), + queries: (map['queries'] as List?)?.cast() ?? [], + pageTitles: (map['pageTitles'] as List?)?.cast() ?? [], + ); + } + + String toJson() => json.encode(toMap()); + + factory EntryMatcher.fromJson(String source) => + EntryMatcher.fromMap(json.decode(source) as Map?); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other is EntryMatcher && + other.matchLogic == matchLogic && + listEquals(other.queries, queries) && + listEquals(other.pageTitles, pageTitles); + } + + @override + int get hashCode { + return matchLogic.hashCode ^ queries.hashCode ^ pageTitles.hashCode; + } + + MatcherLogic? matchLogic; // default to Client initially + List queries; // HTML DOM select query + List pageTitles; // HTML Page title contains +} diff --git a/lib/src/kee_vault_model/entry_matcher_config.dart b/lib/src/kee_vault_model/entry_matcher_config.dart new file mode 100644 index 0000000..557e500 --- /dev/null +++ b/lib/src/kee_vault_model/entry_matcher_config.dart @@ -0,0 +1,113 @@ +import 'dart:convert'; + +import 'package:collection/collection.dart'; +import 'package:kdbx/src/kee_vault_model/entry_matcher.dart'; +import 'package:kdbx/src/kee_vault_model/enums.dart'; + +class EntryMatcherConfig { + EntryMatcherConfig({ + this.matcherType, + this.customMatcher, + this.urlMatchMethod, + this.weight, // 0 = client decides or ignores locator + this.actionOnMatch, + this.actionOnNoMatch, + }); + + EntryMatcherConfig.forDefaultUrlMatchBehaviour(MatchAccuracy ma) + : this( + matcherType: EntryMatcherType.Url, + urlMatchMethod: ma, + ); + + EntryMatcherConfig copyWith({ + EntryMatcherType? matcherType, + EntryMatcher? customMatcher, + MatchAccuracy? urlMatchMethod, + num? weight, + MatchAction? actionOnMatch, + MatchAction? actionOnNoMatch, + }) { + return EntryMatcherConfig( + matcherType: matcherType ?? this.matcherType, + customMatcher: customMatcher ?? this.customMatcher, + urlMatchMethod: urlMatchMethod ?? this.urlMatchMethod, + weight: weight ?? this.weight, + actionOnMatch: actionOnMatch ?? this.actionOnMatch, + actionOnNoMatch: actionOnNoMatch ?? this.actionOnNoMatch, + ); + } + + Map toMap() { + return { + 'matcherType': matcherType?.name, + 'customMatcher': customMatcher?.toMap(), + 'urlMatchMethod': urlMatchMethod?.name, + 'weight': weight, + 'actionOnMatch': actionOnMatch?.name, + 'actionOnNoMatch': actionOnNoMatch?.name, + }; + } + + factory EntryMatcherConfig.fromMap(Map? map) { + if (map == null) { + return EntryMatcherConfig(); + } + + return EntryMatcherConfig( + matcherType: EntryMatcherType.values + .firstWhereOrNull((v) => v.name == map['matcherType']), + customMatcher: map['customMatcher'] != null + ? EntryMatcher.fromMap(map['customMatcher'] as Map) + : null, + urlMatchMethod: MatchAccuracy.values + .firstWhereOrNull((v) => v.name == map['urlMatchMethod']), + weight: map['weight'] as int?, + actionOnMatch: MatchAction.values + .firstWhereOrNull((v) => v.name == map['actionOnMatch']), + actionOnNoMatch: MatchAction.values + .firstWhereOrNull((v) => v.name == map['actionOnNoMatch']), + ); + } + + String toJson() => json.encode(toMap()); + + factory EntryMatcherConfig.fromJson(String source) => + EntryMatcherConfig.fromMap(json.decode(source) as Map); + + @override + String toString() { + return 'EntryMatcherConfig(matcherType: $matcherType, customMatcher: $customMatcher, urlMatchMethod: $urlMatchMethod, weight: $weight, actionOnMatch: $actionOnMatch, actionOnNoMatch: $actionOnNoMatch)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is EntryMatcherConfig && + other.matcherType == matcherType && + other.customMatcher == customMatcher && + other.urlMatchMethod == urlMatchMethod && + other.weight == weight && + other.actionOnMatch == actionOnMatch && + other.actionOnNoMatch == actionOnNoMatch; + } + + @override + int get hashCode { + return matcherType.hashCode ^ + customMatcher.hashCode ^ + urlMatchMethod.hashCode ^ + weight.hashCode ^ + actionOnMatch.hashCode ^ + actionOnNoMatch.hashCode; + } + + EntryMatcherType? matcherType; + EntryMatcher? customMatcher; + MatchAccuracy? urlMatchMethod; + num? weight; // 0 = client decides or ignores locator + MatchAction? actionOnMatch; + MatchAction? + actionOnNoMatch; // critical to use TotalBlock here for Url match type +} diff --git a/lib/src/kee_vault_model/enums.dart b/lib/src/kee_vault_model/enums.dart new file mode 100644 index 0000000..0cb8d90 --- /dev/null +++ b/lib/src/kee_vault_model/enums.dart @@ -0,0 +1,32 @@ +enum BrowserAutoFillBehaviour { + Default, + AlwaysAutoFill, + NeverAutoSubmit, + AlwaysAutoFillNeverAutoSubmit, + AlwaysAutoFillAlwaysAutoSubmit, + NeverAutoFillNeverAutoSubmit +} + +enum MatchAccuracy { Exact, Hostname, Domain } + +enum FieldStorage { CUSTOM, JSON, BOTH } + +enum FieldType { Text, Password, Existing, Toggle, Otp, SomeChars } + +enum FieldMatcherType { + Custom, + UsernameDefaultHeuristic, + PasswordDefaultHeuristic, +} + +enum EntryMatcherType { + Custom, + Hide, + Url, // magic type that uses primary URL + the 4 URL data arrays and current urlmatchconfig to determine a match +} + +enum MatchAction { TotalMatch, TotalBlock, WeightedMatch, WeightedBlock } + +enum MatcherLogic { Client, All, Any } + +enum PlaceholderHandling { Default, Enabled, Disabled } diff --git a/lib/src/kee_vault_model/field.dart b/lib/src/kee_vault_model/field.dart new file mode 100644 index 0000000..8767af5 --- /dev/null +++ b/lib/src/kee_vault_model/field.dart @@ -0,0 +1,171 @@ +import 'dart:convert'; +import 'package:collection/collection.dart'; +import 'package:kdbx/src/kee_vault_model/enums.dart'; +import 'package:kdbx/src/kee_vault_model/field_matcher_config.dart'; +import 'package:kdbx/src/kee_vault_model/form_field_type.dart'; +import 'package:kdbx/src/kee_vault_model/kee_vault_model.dart'; +import 'package:kdbx/src/utils/field_type_utils.dart'; + +class Field { + Field({ + this.uuid, + this.name, + this.valuePath, + this.value, + this.page = 1, + this.type, + this.placeholderHandling, + this.matcherConfigs, + }); + + Field copyWith({ + String? uuid, + String? name, + String? valuePath, + String? value, + int? page, + FieldType? type, + PlaceholderHandling? placeholderHandling, + List? matcherConfigs, + }) { + return Field( + uuid: uuid ?? this.uuid, + name: name ?? this.name, + valuePath: valuePath ?? this.valuePath, + value: value ?? this.value, + page: page ?? this.page, + type: type ?? this.type, + placeholderHandling: placeholderHandling ?? this.placeholderHandling, + matcherConfigs: matcherConfigs ?? this.matcherConfigs, + ); + } + + Map toMap() { + return { + 'uuid': uuid, + 'name': name, + 'valuePath': valuePath, + 'value': value, + 'page': page, + 'type': type?.name, + 'placeholderHandling': placeholderHandling?.name, + 'matcherConfigs': matcherConfigs?.map((x) => x.toMap()).toList(), + }; + } + + factory Field.fromMap(Map? map) { + if (map == null) { + return Field(); + } + return Field( + uuid: map['uuid'] as String?, + name: map['name'] as String?, + valuePath: map['valuePath'] as String?, + value: map['value'] as String?, + page: map['page'] as int? ?? 1, + type: FieldType.values.firstWhereOrNull((v) => v.name == map['type']), + placeholderHandling: PlaceholderHandling.values + .firstWhereOrNull((v) => v.name == map['placeholderHandling']), + matcherConfigs: List.from((map['matcherConfigs'] + as List?) + ?.cast>() + .map((x) => FieldMatcherConfig.fromMap(x)) ?? + []), + ); + } + + String toJson() => json.encode(toMap()); + + factory Field.fromJson(String source) => + Field.fromMap(json.decode(source) as Map?); + + @override + String toString() { + return 'Field(uuid: $uuid, name: $name, valuePath: $valuePath, value: $value, page: $page, type: $type, placeholderHandling: $placeholderHandling, matcherConfigs: $matcherConfigs)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other is Field && + other.uuid == uuid && + other.name == name && + other.valuePath == valuePath && + other.value == value && + other.page == page && + other.type == type && + other.placeholderHandling == placeholderHandling && + listEquals(other.matcherConfigs, matcherConfigs); + } + + @override + int get hashCode { + return uuid.hashCode ^ + name.hashCode ^ + valuePath.hashCode ^ + value.hashCode ^ + page.hashCode ^ + type.hashCode ^ + placeholderHandling.hashCode ^ + matcherConfigs.hashCode; + } + + String? uuid; + String? name; + String? valuePath; + String? value; + int page = 1; + FieldType? type; + PlaceholderHandling? placeholderHandling; + List? matcherConfigs; + + BrowserFieldModelV1? convertToV1() { + var displayName = name; + var ffValue = value; + var htmlName = ''; + var htmlId = ''; + var htmlType = Utilities.fieldTypeToFormFieldType(type ?? FieldType.Text); + + // Currently we can only have one custommatcher. If that changes and someone tries + // to use this old version with a newer DB things will break so they will have to + // upgrade again to fix it. + final customMatcherConfig = + matcherConfigs?.firstWhereOrNull((mc) => mc.customMatcher != null); + if (customMatcherConfig != null) { + htmlName = customMatcherConfig.customMatcher?.names[0] ?? ''; + htmlId = customMatcherConfig.customMatcher?.ids[0] ?? ''; + + if (customMatcherConfig.customMatcher?.types != null) { + htmlType = Utilities.formFieldTypeFromHtmlTypeOrFieldType( + customMatcherConfig.customMatcher!.types[0], + type ?? FieldType.Text); + } + } + + if (type == FieldType.Password && valuePath == 'Password') { + displayName = 'KeePass password'; + htmlType = FormFieldType.PASSWORD; + ffValue = '{PASSWORD}'; + } else if (type == FieldType.Text && valuePath == 'UserName') { + displayName = 'KeePass username'; + htmlType = FormFieldType.USERNAME; + ffValue = '{USERNAME}'; + } + + if (ffValue != '') { + return BrowserFieldModelV1( + name: htmlName, + displayName: displayName, + value: ffValue, + type: htmlType, + fieldId: htmlId, + page: page, + placeholderHandling: + (placeholderHandling ?? PlaceholderHandling.Default).name, + ); + } + return null; + } +} diff --git a/lib/src/kee_vault_model/field_matcher.dart b/lib/src/kee_vault_model/field_matcher.dart new file mode 100644 index 0000000..db1f545 --- /dev/null +++ b/lib/src/kee_vault_model/field_matcher.dart @@ -0,0 +1,125 @@ +import 'dart:convert'; +import 'package:collection/collection.dart'; +import 'package:kdbx/src/kee_vault_model/enums.dart'; + +class FieldMatcher { + FieldMatcher({ + this.matchLogic, + this.ids = const [], + this.names = const [], + this.types = const [], + this.queries = const [], + this.labels = const [], + this.autocompleteValues = const [], + this.maxLength, + this.minLength, + }); + + FieldMatcher copyWith({ + MatcherLogic? matchLogic, + List? ids, + List? names, + List? types, + List? queries, + List? labels, + List? autocompleteValues, + int? maxLength, + int? minLength, + }) { + return FieldMatcher( + matchLogic: matchLogic ?? this.matchLogic, + ids: ids ?? this.ids, + names: names ?? this.names, + types: types ?? this.types, + queries: queries ?? this.queries, + labels: labels ?? this.labels, + autocompleteValues: autocompleteValues ?? this.autocompleteValues, + maxLength: maxLength ?? this.maxLength, + minLength: minLength ?? this.minLength, + ); + } + + Map toMap() { + return { + 'matchLogic': matchLogic?.name, + 'ids': ids, + 'names': names, + 'types': types, + 'queries': queries, + 'labels': labels, + 'autocompleteValues': autocompleteValues, + 'maxLength': maxLength, + 'minLength': minLength, + }; + } + + factory FieldMatcher.fromMap(Map? map) { + if (map == null) { + return FieldMatcher(); + } + + return FieldMatcher( + matchLogic: MatcherLogic.values + .firstWhereOrNull((v) => v.name == map['matchLogic']), + ids: (map['ids'] as List?)?.cast() ?? [], + names: (map['names'] as List?)?.cast() ?? [], + types: (map['types'] as List?)?.cast() ?? [], + queries: (map['queries'] as List?)?.cast() ?? [], + labels: (map['labels'] as List?)?.cast() ?? [], + autocompleteValues: + (map['autocompleteValues'] as List?)?.cast() ?? [], + maxLength: map['maxLength'] as int?, + minLength: map['minLength'] as int?, + ); + } + + String toJson() => json.encode(toMap()); + + factory FieldMatcher.fromJson(String source) => + FieldMatcher.fromMap(json.decode(source) as Map?); + + @override + String toString() { + return 'FieldMatcher(matchLogic: $matchLogic, ids: $ids, names: $names, types: $types, queries: $queries, labels: $labels, autocompleteValues: $autocompleteValues, maxLength: $maxLength, minLength: $minLength)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other is FieldMatcher && + other.matchLogic == matchLogic && + listEquals(other.ids, ids) && + listEquals(other.names, names) && + listEquals(other.types, types) && + listEquals(other.queries, queries) && + listEquals(other.labels, labels) && + listEquals(other.autocompleteValues, autocompleteValues) && + other.maxLength == maxLength && + other.minLength == minLength; + } + + @override + int get hashCode { + return matchLogic.hashCode ^ + ids.hashCode ^ + names.hashCode ^ + types.hashCode ^ + queries.hashCode ^ + labels.hashCode ^ + autocompleteValues.hashCode ^ + maxLength.hashCode ^ + minLength.hashCode; + } + + MatcherLogic? matchLogic; // default to Client initially + List ids; // HTML id attribute + List names; // HTML name attribute + List types; // HTML input type + List queries; // HTML DOM select query + List labels; // HTML Label or otherwise visible UI label + List autocompleteValues; // HTML autocomplete attribute values + int? maxLength; // max chars allowed in a candidate field for this to match + int? minLength; // min chars allowed in a candidate field for this to match +} diff --git a/lib/src/kee_vault_model/field_matcher_config.dart b/lib/src/kee_vault_model/field_matcher_config.dart new file mode 100644 index 0000000..f2bf121 --- /dev/null +++ b/lib/src/kee_vault_model/field_matcher_config.dart @@ -0,0 +1,110 @@ +import 'dart:convert'; + +import 'package:collection/collection.dart'; +import 'package:kdbx/src/kee_vault_model/enums.dart'; +import 'package:kdbx/src/kee_vault_model/field_matcher.dart'; +import 'package:kdbx/src/utils/field_type_utils.dart'; + +class FieldMatcherConfig { + FieldMatcherConfig({ + this.matcherType, + this.customMatcher, + this.weight, // 0 = client decides or ignores locator + this.actionOnMatch, + }); + + FieldMatcherConfig.forSingleClientMatch(String? id, String? name, String fft) + : this( + customMatcher: FieldMatcher( + ids: id == null ? [] : [id], + names: name == null ? [] : [name], + types: [Utilities.formFieldTypeToHtmlType(fft)], + queries: [], + ), + ); + + FieldMatcherConfig.forSingleClientMatchHtmlType( + String? id, String? name, String? htmlType, String? domSelector) + : this( + customMatcher: FieldMatcher( + ids: id == null ? [] : [id], + names: name == null ? [] : [name], + types: htmlType == null ? [] : [htmlType], + queries: domSelector == null ? [] : [domSelector], + ), + ); + + FieldMatcherConfig copyWith({ + FieldMatcherType? matcherType, + FieldMatcher? customMatcher, + num? weight, + MatchAction? actionOnMatch, + }) { + return FieldMatcherConfig( + matcherType: matcherType ?? this.matcherType, + customMatcher: customMatcher ?? this.customMatcher, + weight: weight ?? this.weight, + actionOnMatch: actionOnMatch ?? this.actionOnMatch, + ); + } + + Map toMap() { + return { + 'matcherType': matcherType?.name, + 'customMatcher': customMatcher?.toMap(), + 'weight': weight, + 'actionOnMatch': actionOnMatch?.name, + }; + } + + factory FieldMatcherConfig.fromMap(Map? map) { + if (map == null) { + return FieldMatcherConfig(); + } + + return FieldMatcherConfig( + matcherType: FieldMatcherType.values + .firstWhereOrNull((v) => v.name == map['matchLogic']), + customMatcher: map['customMatcher'] != null + ? FieldMatcher.fromMap(map['customMatcher'] as Map) + : null, + weight: map['weight'] as int?, + actionOnMatch: MatchAction.values + .firstWhereOrNull((v) => v.name == map['actionOnMatch']), + ); + } + + String toJson() => json.encode(toMap()); + + factory FieldMatcherConfig.fromJson(String source) => + FieldMatcherConfig.fromMap(json.decode(source) as Map?); + + @override + String toString() { + return 'FieldMatcherConfig(matcherType: $matcherType, customMatcher: $customMatcher, weight: $weight, actionOnMatch: $actionOnMatch)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is FieldMatcherConfig && + other.matcherType == matcherType && + other.customMatcher == customMatcher && + other.weight == weight && + other.actionOnMatch == actionOnMatch; + } + + @override + int get hashCode { + return matcherType.hashCode ^ + customMatcher.hashCode ^ + weight.hashCode ^ + actionOnMatch.hashCode; + } + + FieldMatcherType? matcherType; + FieldMatcher? customMatcher; + num? weight; // 0 = client decides or ignores locator + MatchAction? actionOnMatch; +} diff --git a/lib/src/kee_vault_model/form_field_type.dart b/lib/src/kee_vault_model/form_field_type.dart new file mode 100644 index 0000000..b1e9bc1 --- /dev/null +++ b/lib/src/kee_vault_model/form_field_type.dart @@ -0,0 +1,8 @@ +class FormFieldType { + static const String USERNAME = 'FFTusername'; + static const String PASSWORD = 'FFTpassword'; + static const String TEXT = 'FFTtext'; + static const String RADIO = 'FFTradio'; + static const String CHECKBOX = 'FFTcheckbox'; + static const String SELECT = 'FFTselect'; +} diff --git a/lib/src/field.dart b/lib/src/kee_vault_model/kee_vault_model.dart similarity index 82% rename from lib/src/field.dart rename to lib/src/kee_vault_model/kee_vault_model.dart index 9b89737..4d2f84b 100644 --- a/lib/src/field.dart +++ b/lib/src/kee_vault_model/kee_vault_model.dart @@ -1,18 +1,15 @@ import 'dart:convert'; -enum FieldStorage { CUSTOM, JSON, BOTH } - -class FormFieldType { - static const String USERNAME = 'FFTusername'; - static const String PASSWORD = 'FFTpassword'; - static const String TEXT = 'FFTtext'; - static const String RADIO = 'FFTradio'; - static const String CHECKBOX = 'FFTcheckbox'; - static const String SELECT = 'FFTselect'; -} +import 'package:collection/collection.dart'; + +import 'package:kdbx/src/kee_vault_model/entry_matcher.dart'; +import 'package:kdbx/src/kee_vault_model/enums.dart'; +import 'package:kdbx/src/kee_vault_model/form_field_type.dart'; -class BrowserFieldModel { - BrowserFieldModel({ +import 'field_matcher_config.dart'; + +class BrowserFieldModelV1 { + BrowserFieldModelV1({ this.displayName, this.name = '', this.type = FormFieldType.TEXT, @@ -22,12 +19,12 @@ class BrowserFieldModel { this.value = '', }); - factory BrowserFieldModel.fromMap(Map? map) { + factory BrowserFieldModelV1.fromMap(Map? map) { if (map == null) { - return BrowserFieldModel(); + return BrowserFieldModelV1(); } - return BrowserFieldModel( + return BrowserFieldModelV1( displayName: map['displayName'] as String?, name: map['name'] as String?, type: map['type'] as String?, @@ -39,8 +36,8 @@ class BrowserFieldModel { value: map['value'] as String?, ); } - factory BrowserFieldModel.fromJson(String source) => - BrowserFieldModel.fromMap(json.decode(source) as Map?); + factory BrowserFieldModelV1.fromJson(String source) => + BrowserFieldModelV1.fromMap(json.decode(source) as Map?); String? displayName; String? name; @@ -57,7 +54,7 @@ class BrowserFieldModel { return true; } - return o is BrowserFieldModel && + return o is BrowserFieldModelV1 && o.displayName == displayName && o.name == name && o.type == type && @@ -78,7 +75,7 @@ class BrowserFieldModel { value.hashCode; } - BrowserFieldModel copyWith({ + BrowserFieldModelV1 copyWith({ String? displayName, String? name, String? type, @@ -87,7 +84,7 @@ class BrowserFieldModel { String? placeholderHandling, String? value, }) { - return BrowserFieldModel( + return BrowserFieldModelV1( displayName: displayName ?? this.displayName, name: name ?? this.name, type: type ?? this.type, @@ -118,6 +115,8 @@ class BrowserFieldModel { } } +//TODO: delete all below when configv2 is working + // defaults... // class BrowserFieldModel( // String displayName: this.getBrowserFieldDisplayNameDefault(), diff --git a/lib/src/utils/field_type_utils.dart b/lib/src/utils/field_type_utils.dart new file mode 100644 index 0000000..a571063 --- /dev/null +++ b/lib/src/utils/field_type_utils.dart @@ -0,0 +1,96 @@ +import 'package:kdbx/src/kee_vault_model/enums.dart'; +import 'package:kdbx/src/kee_vault_model/form_field_type.dart'; +import 'package:kdbx/src/kee_vault_model/kee_vault_model.dart'; + +class Utilities { + static String formFieldTypeToHtmlType(String fft) { + if (fft == FormFieldType.PASSWORD) { + return 'password'; + } + if (fft == FormFieldType.SELECT) { + return 'select-one'; + } + if (fft == FormFieldType.RADIO) { + return 'radio'; + } + if (fft == FormFieldType.CHECKBOX) { + return 'checkbox'; + } + return 'text'; + } + + static FieldType formFieldTypeToFieldType(String fft) { + FieldType type = FieldType.Text; + if (fft == FormFieldType.PASSWORD) { + type = FieldType.Password; + } else if (fft == FormFieldType.SELECT) { + type = FieldType.Existing; + } else if (fft == FormFieldType.RADIO) { + type = FieldType.Existing; + } else if (fft == FormFieldType.USERNAME) { + type = FieldType.Text; + } else if (fft == FormFieldType.CHECKBOX) { + type = FieldType.Toggle; + } + return type; + } + + static String fieldTypeToDisplay(FieldType type, bool titleCase) { + String typeD = 'Text'; + if (type == FieldType.Password) { + typeD = 'Password'; + } else if (type == FieldType.Existing) { + typeD = 'Existing'; + } else if (type == FieldType.Text) { + typeD = 'Text'; + } else if (type == FieldType.Toggle) { + typeD = 'Toggle'; + } + if (!titleCase) { + return typeD.toLowerCase(); + } + return typeD; + } + + static String fieldTypeToHtmlType(FieldType ft) { + switch (ft) { + case FieldType.Password: + return 'password'; + case FieldType.Existing: + return 'radio'; + case FieldType.Toggle: + return 'checkbox'; + default: + return 'text'; + } + } + + static String fieldTypeToFormFieldType(FieldType ft) { + switch (ft) { + case FieldType.Password: + return FormFieldType.PASSWORD; + case FieldType.Existing: + return FormFieldType.RADIO; + case FieldType.Toggle: + return FormFieldType.CHECKBOX; + default: + return FormFieldType.TEXT; + } + } + + // Assumes funky Username type has already been determined so all textual stuff is type text by now + static String formFieldTypeFromHtmlTypeOrFieldType(String t, FieldType ft) { + switch (t) { + case 'password': + return FormFieldType.PASSWORD; + case 'radio': + return FormFieldType.RADIO; + case 'checkbox': + return FormFieldType.CHECKBOX; + case 'select-one': + return FormFieldType.SELECT; + default: + return Utilities.fieldTypeToFormFieldType(ft); + } + } +} diff --git a/lib/src/utils/guid_service.dart b/lib/src/utils/guid_service.dart new file mode 100644 index 0000000..3af3c92 --- /dev/null +++ b/lib/src/utils/guid_service.dart @@ -0,0 +1,12 @@ +import 'package:uuid/uuid.dart'; + +abstract class IGuidService { + String newGuid(); +} + +class GuidService implements IGuidService { + @override + String newGuid() { + return Uuid().v4(); + } +} diff --git a/test/browser_entry_settings_test.dart b/test/browser_entry_settings_test.dart new file mode 100644 index 0000000..dc1f7db --- /dev/null +++ b/test/browser_entry_settings_test.dart @@ -0,0 +1,86 @@ +import 'package:clock/clock.dart'; +import 'package:kdbx/src/kee_vault_model/browser_entry_settings.dart'; +import 'package:kdbx/src/kee_vault_model/browser_entry_settings_v1.dart'; +import 'package:kdbx/src/kee_vault_model/enums.dart'; +import 'package:kdbx/src/utils/guid_service.dart'; +import 'package:logging/logging.dart'; +import 'package:logging_appenders/logging_appenders.dart'; +import 'package:test/test.dart'; + +import 'internal/test_utils.dart'; + +final _logger = Logger('browser_entry_settings_test'); + +class MockGuidService implements IGuidService { + @override + String newGuid() { + return '00000000-0000-0000-0000-000000000000'; + } +} + +void main() { + Logger.root.level = Level.ALL; + PrintAppender().attachToLogger(Logger.root); + final kdbxFormat = TestUtil.kdbxFormat(); + if (!kdbxFormat.argon2.isFfi) { + throw StateError('Expected ffi!'); + } + var now = DateTime.fromMillisecondsSinceEpoch(0); + + final fakeClock = Clock(() => now); + void proceedSeconds(int seconds) { + now = now.add(Duration(seconds: seconds)); + } + + setUp(() { + now = DateTime.fromMillisecondsSinceEpoch(0); + }); + + void testCase(String persistedV2, String expectedResult) { + final bes = BrowserEntrySettings.fromJson(persistedV2, + minimumMatchAccuracy: MatchAccuracy.Domain); + final configV1 = bes.convertToV1(); + final sut = configV1.toJson(); + + expect(sut, expectedResult); + } + + void testCaseToV2(String persistedV1, String expectedResult) { + final bes = BrowserEntrySettingsV1.fromJson(persistedV1, + minimumMatchAccuracy: MatchAccuracy.Domain); + final configV2 = bes.convertToV2(MockGuidService()); + final sut = configV2.toJson(); + + expect(sut, expectedResult); + } + + group('BrowserEntrySettings', () { + test('config v2->v1', () async { + testCase( + '{"version":2,"altUrls":[],"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"},{"matcherType":"Hide"}],"fields":[{"page":-1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":-1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}]}', + '{"version":1,"priority":0,"hide":true,"hTTPRealm":"","formFieldList":[{"displayName":"KeePass password","name":"password","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default","value":"{PASSWORD}"},{"displayName":"KeePass username","name":"username","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default","value":"{USERNAME}"}],"alwaysAutoFill":false,"alwaysAutoSubmit":false,"neverAutoFill":false,"neverAutoSubmit":false,"blockDomainOnlyMatch":false,"blockHostnameOnlyMatch":false,"altURLs":[],"regExURLs":[],"blockedURLs":[],"regExBlockedURLs":[]}'); + + testCase( + '{"version":2,"altUrls":[],"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":-1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}]}', + '{"version":1,"priority":0,"hide":false,"hTTPRealm":"","formFieldList":[{"displayName":"KeePass password","name":"password","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default","value":"{PASSWORD}"},{"displayName":"KeePass username","name":"username","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default","value":"{USERNAME}"}],"alwaysAutoFill":false,"alwaysAutoSubmit":false,"neverAutoFill":false,"neverAutoSubmit":false,"blockDomainOnlyMatch":false,"blockHostnameOnlyMatch":false,"altURLs":[],"regExURLs":[],"blockedURLs":[],"regExBlockedURLs":[]}'); + + testCase( + '{"version":2,"altUrls":[],"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":-1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}]}', + '{"version":1,"priority":0,"hide":false,"hTTPRealm":"","formFieldList":[{"displayName":"KeePass password","name":"password","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default","value":"{PASSWORD}"},{"displayName":"KeePass username","name":"username","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default","value":"{USERNAME}"}],"alwaysAutoFill":false,"alwaysAutoSubmit":false,"neverAutoFill":false,"neverAutoSubmit":false,"blockDomainOnlyMatch":false,"blockHostnameOnlyMatch":false,"altURLs":[],"regExURLs":[],"blockedURLs":[],"regExBlockedURLs":[]}'); + + testCase( + '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"matcherType":"UsernameDefaultHeuristic"}]},{"page":-1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"matcherType":"PasswordDefaultHeuristic"}]}]}', + '{"version":1,"priority":0,"hide":false,"hTTPRealm":"","formFieldList":[{"displayName":"KeePass username","name":"","type":"FFTusername","id":"","page":-1,"placeholderHandling":"Default","value":"{USERNAME}"},{"displayName":"KeePass password","name":"","type":"FFTpassword","id":"","page":-1,"placeholderHandling":"Default","value":"{PASSWORD}"}],"alwaysAutoFill":false,"alwaysAutoSubmit":false,"neverAutoFill":false,"neverAutoSubmit":false,"blockDomainOnlyMatch":false,"blockHostnameOnlyMatch":false,"altURLs":[],"regExURLs":[],"blockedURLs":[],"regExBlockedURLs":[]}'); + }); + + test('config v1->v2', () async { + testCaseToV2( + '{"version":1,"hTTPRealm":"","formFieldList":[{"name":"password","displayName":"KeePass password","value":"{PASSWORD}","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default"},{"name":"username","displayName":"KeePass username","value":"{USERNAME}","type":"FFTradio","id":"username","page":-1,"placeholderHandling":"Default"}],"alwaysAutoFill":false,"neverAutoFill":false,"alwaysAutoSubmit":false,"neverAutoSubmit":false,"priority":0,"altURLs":[],"hide":true,"blockHostnameOnlyMatch":false,"blockDomainOnlyMatch":false}', + '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"},{"matcherType":"Hide"}],"fields":[{"page":1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}],"altUrls":[]}'); + + // testCase("{\"version\":1,\"hTTPRealm\":\"\",\"formFieldList\":[{\"name\":\"password\",\"displayName\":\"KeePass password\",\"value\":\"{PASSWORD}\",\"type\":\"FFTpassword\",\"id\":\"password\",\"page\":-1,\"placeholderHandling\":\"Default\"},{\"name\":\"username\",\"displayName\":\"KeePass username\",\"value\":\"{USERNAME}\",\"type\":\"FFTradio\",\"id\":\"username\",\"page\":-1,\"placeholderHandling\":\"Default\"}],\"alwaysAutoFill\":false,\"neverAutoFill\":false,\"alwaysAutoSubmit\":false,\"neverAutoSubmit\":false,\"priority\":0,\"altURLs\":[],\"hide\":true,\"blockHostnameOnlyMatch\":false,\"blockDomainOnlyMatch\":false}", + + // "{\"version\":2,\"altUrls\":[],\"authenticationMethods\":[\"password\"],\"matcherConfigs\":[{\"matcherType\":\"Url\"},{\"matcherType\":\"Hide\"}],\"fields\":[{\"page\":1,\"valuePath\":\"Password\",\"uuid\":\"00000000-0000-0000-0000-000000000000\",\"type\":\"Password\",\"matcherConfigs\":[{\"customMatcher\":{\"ids\":[\"password\"],\"names\":[\"password\"],\"types\":[\"password\"],\"queries\":[]}}]},{\"page\":1,\"valuePath\":\"UserName\",\"uuid\":\"00000000-0000-0000-0000-000000000000\",\"type\":\"Text\",\"matcherConfigs\":[{\"customMatcher\":{\"ids\":[\"username\"],\"names\":[\"username\"],\"types\":[\"text\"],\"queries\":[]}}]}]}"); + }); + }); +} diff --git a/test/internal/test_utils.dart b/test/internal/test_utils.dart index c993dba..5efd6ee 100644 --- a/test/internal/test_utils.dart +++ b/test/internal/test_utils.dart @@ -5,6 +5,7 @@ import 'dart:typed_data'; import 'package:argon2_ffi_base/argon2_ffi_base.dart'; import 'package:kdbx/kdbx.dart'; +import 'package:kdbx/src/kee_vault_model/kee_vault_model.dart'; import 'package:logging/logging.dart'; import 'package:logging_appenders/logging_appenders.dart'; @@ -105,7 +106,7 @@ class TestUtil { Function proceedSeconds) async { final file = TestUtil.createEmptyFile(); final entry = createEntry(file, file.body.rootGroup, 'test1', 'test1'); - entry.browserSettings.fields.add(BrowserFieldModel( + entry.browserSettings.fields.add(BrowserFieldModelV1( displayName: 'test name', fieldId: 'id', name: 'form field name', From 28c4b5caed58ce0163f5411e7e3f7f6ce227339f Mon Sep 17 00:00:00 2001 From: luckyrat Date: Tue, 20 Feb 2024 13:14:52 +0000 Subject: [PATCH 02/13] wip --- .../browser_entry_settings.dart | 7 ++++--- .../browser_entry_settings_v1.dart | 12 +++++++---- .../kee_vault_model/entry_matcher_config.dart | 11 +++++----- lib/src/kee_vault_model/field.dart | 12 ++++++----- lib/src/kee_vault_model/field_matcher.dart | 20 ++++++++++--------- .../kee_vault_model/field_matcher_config.dart | 9 +++++---- test/browser_entry_settings_test.dart | 13 +++++++++--- 7 files changed, 51 insertions(+), 33 deletions(-) diff --git a/lib/src/kee_vault_model/browser_entry_settings.dart b/lib/src/kee_vault_model/browser_entry_settings.dart index 6c1ac82..f8e2f0f 100644 --- a/lib/src/kee_vault_model/browser_entry_settings.dart +++ b/lib/src/kee_vault_model/browser_entry_settings.dart @@ -173,10 +173,11 @@ class BrowserEntrySettings { return { 'version': version, 'authenticationMethods': authenticationMethods, - 'hTTPRealm': realm, - if (fields != null) 'fields': fields?.map((x) => x.toMap()).toList(), - 'behaviour': behaviour?.name, + if (realm?.isNotEmpty ?? false) 'hTTPRealm': realm, 'matcherConfigs': matcherConfigs.map((x) => x.toMap()).toList(), + if (fields != null) 'fields': fields?.map((x) => x.toMap()).toList(), + if (behaviour != null && behaviour != BrowserAutoFillBehaviour.Default) + 'behaviour': behaviour?.name, ...parseUrls(includeUrls, excludeUrls), }; } diff --git a/lib/src/kee_vault_model/browser_entry_settings_v1.dart b/lib/src/kee_vault_model/browser_entry_settings_v1.dart index 7610644..052f415 100644 --- a/lib/src/kee_vault_model/browser_entry_settings_v1.dart +++ b/lib/src/kee_vault_model/browser_entry_settings_v1.dart @@ -52,10 +52,14 @@ class BrowserEntrySettingsV1 { } factory BrowserEntrySettingsV1.fromJson(String source, - {required MatchAccuracy minimumMatchAccuracy}) => - BrowserEntrySettingsV1.fromMap( - json.decode(source) as Map?, - minimumMatchAccuracy: minimumMatchAccuracy); + {required MatchAccuracy minimumMatchAccuracy}) { + if (source.isEmpty) { + return BrowserEntrySettingsV1(minimumMatchAccuracy: minimumMatchAccuracy); + } + return BrowserEntrySettingsV1.fromMap( + json.decode(source) as Map?, + minimumMatchAccuracy: minimumMatchAccuracy); + } int version; // enum diff --git a/lib/src/kee_vault_model/entry_matcher_config.dart b/lib/src/kee_vault_model/entry_matcher_config.dart index 557e500..a0b9220 100644 --- a/lib/src/kee_vault_model/entry_matcher_config.dart +++ b/lib/src/kee_vault_model/entry_matcher_config.dart @@ -41,11 +41,12 @@ class EntryMatcherConfig { Map toMap() { return { 'matcherType': matcherType?.name, - 'customMatcher': customMatcher?.toMap(), - 'urlMatchMethod': urlMatchMethod?.name, - 'weight': weight, - 'actionOnMatch': actionOnMatch?.name, - 'actionOnNoMatch': actionOnNoMatch?.name, + if (customMatcher != null) 'customMatcher': customMatcher?.toMap(), + if (urlMatchMethod != null && urlMatchMethod != MatchAccuracy.Domain) + 'urlMatchMethod': urlMatchMethod?.name, + if (weight != null) 'weight': weight, + if (actionOnMatch != null) 'actionOnMatch': actionOnMatch?.name, + if (actionOnNoMatch != null) 'actionOnNoMatch': actionOnNoMatch?.name, }; } diff --git a/lib/src/kee_vault_model/field.dart b/lib/src/kee_vault_model/field.dart index 8767af5..848f02a 100644 --- a/lib/src/kee_vault_model/field.dart +++ b/lib/src/kee_vault_model/field.dart @@ -42,14 +42,16 @@ class Field { Map toMap() { return { - 'uuid': uuid, - 'name': name, - 'valuePath': valuePath, - 'value': value, 'page': page, + 'valuePath': valuePath, + 'uuid': uuid, 'type': type?.name, - 'placeholderHandling': placeholderHandling?.name, 'matcherConfigs': matcherConfigs?.map((x) => x.toMap()).toList(), + if (name?.isNotEmpty ?? false) 'name': name, + if (value?.isNotEmpty ?? false) 'value': value, + if (placeholderHandling != null && + placeholderHandling != PlaceholderHandling.Default) + 'placeholderHandling': placeholderHandling?.name, }; } diff --git a/lib/src/kee_vault_model/field_matcher.dart b/lib/src/kee_vault_model/field_matcher.dart index db1f545..8186688 100644 --- a/lib/src/kee_vault_model/field_matcher.dart +++ b/lib/src/kee_vault_model/field_matcher.dart @@ -41,15 +41,17 @@ class FieldMatcher { Map toMap() { return { - 'matchLogic': matchLogic?.name, - 'ids': ids, - 'names': names, - 'types': types, - 'queries': queries, - 'labels': labels, - 'autocompleteValues': autocompleteValues, - 'maxLength': maxLength, - 'minLength': minLength, + if (matchLogic != null && matchLogic != MatcherLogic.Client) + 'matchLogic': matchLogic?.name, + if (ids.isNotEmpty) 'ids': ids, + if (names.isNotEmpty) 'names': names, + if (types.isNotEmpty) 'types': types, + if (queries.isNotEmpty) 'queries': queries, + if (labels.isNotEmpty) 'labels': labels, + if (autocompleteValues.isNotEmpty) + 'autocompleteValues': autocompleteValues, + if (maxLength != null) 'maxLength': maxLength, + if (minLength != null) 'minLength': minLength, }; } diff --git a/lib/src/kee_vault_model/field_matcher_config.dart b/lib/src/kee_vault_model/field_matcher_config.dart index f2bf121..3076fd4 100644 --- a/lib/src/kee_vault_model/field_matcher_config.dart +++ b/lib/src/kee_vault_model/field_matcher_config.dart @@ -50,10 +50,11 @@ class FieldMatcherConfig { Map toMap() { return { - 'matcherType': matcherType?.name, - 'customMatcher': customMatcher?.toMap(), - 'weight': weight, - 'actionOnMatch': actionOnMatch?.name, + if (matcherType != null && matcherType != FieldMatcherType.Custom) + 'matcherType': matcherType?.name, + if (customMatcher != null) 'customMatcher': customMatcher?.toMap(), + if (weight != null) 'weight': weight, + if (actionOnMatch != null) 'actionOnMatch': actionOnMatch?.name, }; } diff --git a/test/browser_entry_settings_test.dart b/test/browser_entry_settings_test.dart index dc1f7db..a5ba1e8 100644 --- a/test/browser_entry_settings_test.dart +++ b/test/browser_entry_settings_test.dart @@ -76,11 +76,18 @@ void main() { test('config v1->v2', () async { testCaseToV2( '{"version":1,"hTTPRealm":"","formFieldList":[{"name":"password","displayName":"KeePass password","value":"{PASSWORD}","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default"},{"name":"username","displayName":"KeePass username","value":"{USERNAME}","type":"FFTradio","id":"username","page":-1,"placeholderHandling":"Default"}],"alwaysAutoFill":false,"neverAutoFill":false,"alwaysAutoSubmit":false,"neverAutoSubmit":false,"priority":0,"altURLs":[],"hide":true,"blockHostnameOnlyMatch":false,"blockDomainOnlyMatch":false}', - '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"},{"matcherType":"Hide"}],"fields":[{"page":1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}],"altUrls":[]}'); + '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"},{"matcherType":"Hide"}],"fields":[{"page":1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}]}'); - // testCase("{\"version\":1,\"hTTPRealm\":\"\",\"formFieldList\":[{\"name\":\"password\",\"displayName\":\"KeePass password\",\"value\":\"{PASSWORD}\",\"type\":\"FFTpassword\",\"id\":\"password\",\"page\":-1,\"placeholderHandling\":\"Default\"},{\"name\":\"username\",\"displayName\":\"KeePass username\",\"value\":\"{USERNAME}\",\"type\":\"FFTradio\",\"id\":\"username\",\"page\":-1,\"placeholderHandling\":\"Default\"}],\"alwaysAutoFill\":false,\"neverAutoFill\":false,\"alwaysAutoSubmit\":false,\"neverAutoSubmit\":false,\"priority\":0,\"altURLs\":[],\"hide\":true,\"blockHostnameOnlyMatch\":false,\"blockDomainOnlyMatch\":false}", + testCaseToV2( + '{"version":1,"hTTPRealm":"","formFieldList":[{"name":"password","displayName":"KeePass password","value":"{PASSWORD}","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default"},{"name":"username","displayName":"KeePass username","value":"{USERNAME}","type":"FFTradio","id":"username","page":-1,"placeholderHandling":"Default"}],"alwaysAutoFill":false,"neverAutoFill":false,"alwaysAutoSubmit":false,"neverAutoSubmit":false,"priority":0,"altURLs":[],"hide":false,"blockHostnameOnlyMatch":false,"blockDomainOnlyMatch":false}', + '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}]}'); + + testCaseToV2( + '{"version":1,"hTTPRealm":"","formFieldList":[{"name":"password","displayName":"KeePass password","value":"{PASSWORD}","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default"},{"name":"username","displayName":"KeePass username","value":"{USERNAME}","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default"}],"alwaysAutoFill":false,"neverAutoFill":false,"alwaysAutoSubmit":false,"neverAutoSubmit":false,"priority":0,"altURLs":[],"hide":false,"blockHostnameOnlyMatch":false,"blockDomainOnlyMatch":false}', + '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}]}'); - // "{\"version\":2,\"altUrls\":[],\"authenticationMethods\":[\"password\"],\"matcherConfigs\":[{\"matcherType\":\"Url\"},{\"matcherType\":\"Hide\"}],\"fields\":[{\"page\":1,\"valuePath\":\"Password\",\"uuid\":\"00000000-0000-0000-0000-000000000000\",\"type\":\"Password\",\"matcherConfigs\":[{\"customMatcher\":{\"ids\":[\"password\"],\"names\":[\"password\"],\"types\":[\"password\"],\"queries\":[]}}]},{\"page\":1,\"valuePath\":\"UserName\",\"uuid\":\"00000000-0000-0000-0000-000000000000\",\"type\":\"Text\",\"matcherConfigs\":[{\"customMatcher\":{\"ids\":[\"username\"],\"names\":[\"username\"],\"types\":[\"text\"],\"queries\":[]}}]}]}"); + testCaseToV2('', + '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"matcherType":"UsernameDefaultHeuristic"}]},{"page":1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"matcherType":"PasswordDefaultHeuristic"}]}]}'); }); }); } From 7cb986e3caf98875fb5625c65478cb9b36e2da15 Mon Sep 17 00:00:00 2001 From: luckyrat Date: Tue, 20 Feb 2024 14:36:36 +0000 Subject: [PATCH 03/13] wip --- lib/src/kee_vault_model/browser_entry_settings.dart | 5 ----- pubspec.yaml | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/src/kee_vault_model/browser_entry_settings.dart b/lib/src/kee_vault_model/browser_entry_settings.dart index f8e2f0f..94137c0 100644 --- a/lib/src/kee_vault_model/browser_entry_settings.dart +++ b/lib/src/kee_vault_model/browser_entry_settings.dart @@ -5,11 +5,6 @@ import 'package:kdbx/src/kee_vault_model/entry_matcher_config.dart'; import 'package:kdbx/src/kee_vault_model/enums.dart'; import 'package:kdbx/src/kee_vault_model/field.dart'; -//TODO: EntryConfigV2 -//TODO: translate TS unit tests for migration -//TODO: implement conversion between versions -//TODO: kdbxswift too - class BrowserEntrySettings { BrowserEntrySettings({ this.version = 2, diff --git a/pubspec.yaml b/pubspec.yaml index 3a65dda..3dcdab5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: kdbx description: KeepassX format implementation in pure dart. (kdbx 3.x and 4.x support). -version: 0.6.0+0 +version: 0.6.0+1 homepage: https://github.com/kee-org/kdbx.dart publish_to: none From 585bd27c7a5bbd89bee5190ed210d48914e9ddf2 Mon Sep 17 00:00:00 2001 From: luckyrat Date: Tue, 20 Feb 2024 14:52:25 +0000 Subject: [PATCH 04/13] wip --- lib/kdbx.dart | 22 ++++++++----------- lib/src/kdbx_entry.dart | 1 - lib/src/kdbx_meta.dart | 3 --- .../browser_entry_settings_v1.dart | 2 +- ...model.dart => browser_field_model_v1.dart} | 7 ------ lib/src/kee_vault_model/field.dart | 2 +- lib/src/utils/field_type_utils.dart | 1 - pubspec.yaml | 2 +- test/internal/test_utils.dart | 2 +- 9 files changed, 13 insertions(+), 29 deletions(-) rename lib/src/kee_vault_model/{kee_vault_model.dart => browser_field_model_v1.dart} (95%) diff --git a/lib/kdbx.dart b/lib/kdbx.dart index 45ef7fe..03ecebb 100644 --- a/lib/kdbx.dart +++ b/lib/kdbx.dart @@ -1,9 +1,6 @@ /// dart library for reading keepass file format (kdbx). library kdbx; -import 'package:kdbx/src/kee_vault_model/enums.dart'; -import 'package:kdbx/src/kee_vault_model/form_field_type.dart'; - export 'src/credentials/credentials.dart' show Credentials, CredentialsPart, HashCredentials, PasswordCredentials; export 'src/credentials/keyfile.dart' show KeyFileComposite, KeyFileCredentials; @@ -11,21 +8,12 @@ export 'src/crypto/key_encrypter_kdf.dart' show KeyEncrypterKdf, KdfType, KdfField; export 'src/crypto/protected_value.dart' show ProtectedValue, StringValue, PlainValue; -export 'src/kee_vault_model/kee_vault_model.dart' - show BrowserFieldModel, FormFieldType, FieldStorage; export 'src/internal/kdf_cache.dart' show KdfCache; export 'src/kdbx_binary.dart' show KdbxBinary; export 'src/kdbx_consts.dart'; export 'src/kdbx_custom_data.dart'; export 'src/kdbx_dao.dart' show KdbxDao; -export 'src/kdbx_entry.dart' - show - KdbxEntry, - KdbxKey, - KdbxKeyCommon, - BrowserEntrySettings, - BrowserAutoFillBehaviour, - MatchAccuracy; +export 'src/kdbx_entry.dart' show KdbxEntry, KdbxKey, KdbxKeyCommon; export 'src/kdbx_exceptions.dart'; export 'src/kdbx_file.dart'; export 'src/kdbx_format.dart' show KdbxBody, MergeContext, KdbxFormat; @@ -41,4 +29,12 @@ export 'src/kdbx_object.dart' ChangeEvent, KdbxNodeContext; export 'src/kdbx_var_dictionary.dart' show VarDictionary; +export 'src/kee_vault_model/browser_entry_settings.dart' + show BrowserEntrySettings; +export 'src/kee_vault_model/entry_matcher.dart'; +export 'src/kee_vault_model/entry_matcher_config.dart'; +export 'src/kee_vault_model/enums.dart'; +export 'src/kee_vault_model/field.dart' show Field; +export 'src/kee_vault_model/field_matcher.dart' show FieldMatcher; +export 'src/kee_vault_model/field_matcher_config.dart'; export 'src/utils/byte_utils.dart' show ByteUtils; diff --git a/lib/src/kdbx_entry.dart b/lib/src/kdbx_entry.dart index 66f30e5..51fd7eb 100644 --- a/lib/src/kdbx_entry.dart +++ b/lib/src/kdbx_entry.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:kdbx/src/kee_vault_model/browser_entry_settings_v1.dart'; -import 'package:kdbx/src/kee_vault_model/kee_vault_model.dart'; import 'package:kdbx/src/internal/extension_utils.dart'; import 'package:kdbx/src/kdbx_format.dart'; import 'package:kdbx/src/kdbx_object.dart'; diff --git a/lib/src/kdbx_meta.dart b/lib/src/kdbx_meta.dart index 0ab59ef..c39123d 100644 --- a/lib/src/kdbx_meta.dart +++ b/lib/src/kdbx_meta.dart @@ -6,7 +6,6 @@ import 'package:collection/collection.dart'; import 'package:kdbx/src/internal/extension_utils.dart'; import 'package:kdbx/src/kdbx_binary.dart'; import 'package:kdbx/src/kdbx_custom_data.dart'; -import 'package:kdbx/src/kdbx_entry.dart'; import 'package:kdbx/src/kdbx_exceptions.dart'; import 'package:kdbx/src/kdbx_format.dart'; import 'package:kdbx/src/kdbx_header.dart'; @@ -19,8 +18,6 @@ import 'package:uuid/uuid.dart'; import 'package:xml/xml.dart' as xml; import 'package:xml/xml.dart'; -import 'kee_vault_model/kee_vault_model.dart'; - final _logger = Logger('kdbx_meta'); class KdbxMeta extends KdbxNode implements KdbxNodeContext { diff --git a/lib/src/kee_vault_model/browser_entry_settings_v1.dart b/lib/src/kee_vault_model/browser_entry_settings_v1.dart index 052f415..c827602 100644 --- a/lib/src/kee_vault_model/browser_entry_settings_v1.dart +++ b/lib/src/kee_vault_model/browser_entry_settings_v1.dart @@ -2,10 +2,10 @@ import 'dart:convert'; import 'dart:math'; import 'package:collection/collection.dart'; import 'package:kdbx/src/kee_vault_model/browser_entry_settings.dart'; +import 'package:kdbx/src/kee_vault_model/browser_field_model_v1.dart'; import 'package:kdbx/src/kee_vault_model/entry_matcher_config.dart'; import 'package:kdbx/src/kee_vault_model/enums.dart'; import 'package:kdbx/src/kee_vault_model/field.dart'; -import 'package:kdbx/src/kee_vault_model/kee_vault_model.dart'; import '../utils/field_type_utils.dart'; import '../utils/guid_service.dart'; diff --git a/lib/src/kee_vault_model/kee_vault_model.dart b/lib/src/kee_vault_model/browser_field_model_v1.dart similarity index 95% rename from lib/src/kee_vault_model/kee_vault_model.dart rename to lib/src/kee_vault_model/browser_field_model_v1.dart index 4d2f84b..6e28487 100644 --- a/lib/src/kee_vault_model/kee_vault_model.dart +++ b/lib/src/kee_vault_model/browser_field_model_v1.dart @@ -1,13 +1,6 @@ import 'dart:convert'; - -import 'package:collection/collection.dart'; - -import 'package:kdbx/src/kee_vault_model/entry_matcher.dart'; -import 'package:kdbx/src/kee_vault_model/enums.dart'; import 'package:kdbx/src/kee_vault_model/form_field_type.dart'; -import 'field_matcher_config.dart'; - class BrowserFieldModelV1 { BrowserFieldModelV1({ this.displayName, diff --git a/lib/src/kee_vault_model/field.dart b/lib/src/kee_vault_model/field.dart index 848f02a..c99f0c3 100644 --- a/lib/src/kee_vault_model/field.dart +++ b/lib/src/kee_vault_model/field.dart @@ -1,9 +1,9 @@ import 'dart:convert'; import 'package:collection/collection.dart'; +import 'package:kdbx/src/kee_vault_model/browser_field_model_v1.dart'; import 'package:kdbx/src/kee_vault_model/enums.dart'; import 'package:kdbx/src/kee_vault_model/field_matcher_config.dart'; import 'package:kdbx/src/kee_vault_model/form_field_type.dart'; -import 'package:kdbx/src/kee_vault_model/kee_vault_model.dart'; import 'package:kdbx/src/utils/field_type_utils.dart'; class Field { diff --git a/lib/src/utils/field_type_utils.dart b/lib/src/utils/field_type_utils.dart index a571063..637891e 100644 --- a/lib/src/utils/field_type_utils.dart +++ b/lib/src/utils/field_type_utils.dart @@ -1,6 +1,5 @@ import 'package:kdbx/src/kee_vault_model/enums.dart'; import 'package:kdbx/src/kee_vault_model/form_field_type.dart'; -import 'package:kdbx/src/kee_vault_model/kee_vault_model.dart'; class Utilities { static String formFieldTypeToHtmlType(String fft) { diff --git a/pubspec.yaml b/pubspec.yaml index 3dcdab5..ca90fc6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: kdbx description: KeepassX format implementation in pure dart. (kdbx 3.x and 4.x support). -version: 0.6.0+1 +version: 0.6.0+2 homepage: https://github.com/kee-org/kdbx.dart publish_to: none diff --git a/test/internal/test_utils.dart b/test/internal/test_utils.dart index 5efd6ee..f6e7a52 100644 --- a/test/internal/test_utils.dart +++ b/test/internal/test_utils.dart @@ -5,7 +5,7 @@ import 'dart:typed_data'; import 'package:argon2_ffi_base/argon2_ffi_base.dart'; import 'package:kdbx/kdbx.dart'; -import 'package:kdbx/src/kee_vault_model/kee_vault_model.dart'; +import 'package:kdbx/src/kee_vault_model/browser_field_model_v1.dart'; import 'package:logging/logging.dart'; import 'package:logging_appenders/logging_appenders.dart'; From 479cd78ebf30e26f2b6a928513e9604ebc3a5508 Mon Sep 17 00:00:00 2001 From: luckyrat Date: Tue, 20 Feb 2024 20:02:01 +0000 Subject: [PATCH 05/13] wip --- lib/src/kdbx_entry.dart | 61 +++++++++++++------ .../browser_entry_settings_v1.dart | 15 ++++- .../kee_vault_model/field_matcher_config.dart | 16 +++-- test/internal/test_utils.dart | 19 +++--- test/kdbx_history_test.dart | 4 +- 5 files changed, 80 insertions(+), 35 deletions(-) diff --git a/lib/src/kdbx_entry.dart b/lib/src/kdbx_entry.dart index 51fd7eb..c2a451a 100644 --- a/lib/src/kdbx_entry.dart +++ b/lib/src/kdbx_entry.dart @@ -7,6 +7,7 @@ import 'package:kdbx/src/internal/extension_utils.dart'; import 'package:kdbx/src/kdbx_format.dart'; import 'package:kdbx/src/kdbx_object.dart'; import 'package:kdbx/src/kdbx_xml.dart'; +import 'package:kdbx/src/utils/guid_service.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; import 'package:quiver/check.dart'; @@ -166,9 +167,12 @@ class KdbxEntry extends KdbxObject { }) : history = [], super.create(file.ctx, file, 'Entry', parent) { icon.set(KdbxIcon.Key); - _browserSettings = BrowserEntrySettingsV1( - minimumMatchAccuracy: - file.body.meta.browserSettings.defaultMatchAccuracy); + _browserSettings = BrowserEntrySettings( + matcherConfigs: [ + EntryMatcherConfig.forDefaultUrlMatchBehaviour( + file.body.meta.browserSettings.defaultMatchAccuracy) + ], + ); } KdbxEntry.read(KdbxReadWriteContext ctx, KdbxGroup? parent, XmlElement node, @@ -228,29 +232,48 @@ class KdbxEntry extends KdbxObject { customData['KeeVault.AndroidPackageNames'] = json.encode(names); } - BrowserEntrySettingsV1? _browserSettings; - BrowserEntrySettingsV1 get browserSettings { + BrowserEntrySettings? _browserSettings; + BrowserEntrySettings get browserSettings { if (_browserSettings == null) { - final tempJson = stringEntries - .firstWhereOrNull((s) => s.key.key == 'KPRPC JSON') - ?.value; + final cdJson = getCustomData('KPRPC JSON'); - if (tempJson != null) { - _browserSettings = BrowserEntrySettingsV1.fromJson(tempJson.getText(), + if (cdJson != null) { + _browserSettings = BrowserEntrySettings.fromJson(cdJson, minimumMatchAccuracy: file!.body.meta.browserSettings.defaultMatchAccuracy); } else { - _browserSettings = BrowserEntrySettingsV1( - minimumMatchAccuracy: - file!.body.meta.browserSettings.defaultMatchAccuracy); + final stringJson = stringEntries + .firstWhereOrNull((s) => s.key.key == 'KPRPC JSON') + ?.value + ?.getText(); + + if (stringJson != null) { + final v1 = BrowserEntrySettingsV1.fromJson(stringJson, + minimumMatchAccuracy: + file!.body.meta.browserSettings.defaultMatchAccuracy); + _browserSettings = v1.convertToV2(GuidService()); + } else { + _browserSettings = BrowserEntrySettings( + matcherConfigs: [ + EntryMatcherConfig.forDefaultUrlMatchBehaviour( + file!.body.meta.browserSettings.defaultMatchAccuracy) + ], + ); + } } } return _browserSettings!; } - set browserSettings(BrowserEntrySettingsV1 settings) { - setString( - KdbxKey('KPRPC JSON'), ProtectedValue.fromString(settings.toJson())); + set browserSettings(BrowserEntrySettings settings) { + setCustomData('KPRPC JSON', settings.toJson()); + try { + final v1 = settings.convertToV1(); + setString(KdbxKey('KPRPC JSON'), ProtectedValue.fromString(v1.toJson())); + } catch (ex) { + _logger.severe( + 'String KPRPC JSON failed to convert or write. This may indicate a newer version of Kee Vault was used to create this configuration.'); + } _browserSettings = null; } @@ -291,7 +314,8 @@ class KdbxEntry extends KdbxObject { browserSettings.includeUrls.add(newUrl); } } - browserSettings.hide = false; + browserSettings.matcherConfigs + .removeWhere((mc) => mc.matcherType == EntryMatcherType.Hide); browserSettings = browserSettings; return; } @@ -301,7 +325,8 @@ class KdbxEntry extends KdbxObject { final updatedList = androidPackageNames..add(name); androidPackageNames = updatedList; } - browserSettings.hide = false; + browserSettings.matcherConfigs + .removeWhere((mc) => mc.matcherType == EntryMatcherType.Hide); browserSettings = browserSettings; return; } diff --git a/lib/src/kee_vault_model/browser_entry_settings_v1.dart b/lib/src/kee_vault_model/browser_entry_settings_v1.dart index c827602..53451d5 100644 --- a/lib/src/kee_vault_model/browser_entry_settings_v1.dart +++ b/lib/src/kee_vault_model/browser_entry_settings_v1.dart @@ -334,7 +334,10 @@ class BrowserEntrySettingsV1 { ? FieldMatcherConfig( matcherType: FieldMatcherType.UsernameDefaultHeuristic) : FieldMatcherConfig.forSingleClientMatch( - ff.fieldId, ff.name, FormFieldType.USERNAME); + FormFieldType.USERNAME, + id: ff.fieldId, + name: ff.name, + ); final f = Field( valuePath: 'UserName', page: max(ff.page, 1), @@ -354,7 +357,10 @@ class BrowserEntrySettingsV1 { ? FieldMatcherConfig( matcherType: FieldMatcherType.PasswordDefaultHeuristic) : FieldMatcherConfig.forSingleClientMatch( - ff.fieldId, ff.name, FormFieldType.PASSWORD); + FormFieldType.PASSWORD, + id: ff.fieldId, + name: ff.name, + ); final f = Field( valuePath: 'Password', page: max(ff.page, 1), @@ -368,7 +374,10 @@ class BrowserEntrySettingsV1 { fields.add(f); } else { final mc = FieldMatcherConfig.forSingleClientMatch( - ff.fieldId, ff.name, ff.type ?? FormFieldType.TEXT); + ff.type ?? FormFieldType.TEXT, + id: ff.fieldId, + name: ff.name, + ); final f = Field( name: ff.displayName, valuePath: '.', diff --git a/lib/src/kee_vault_model/field_matcher_config.dart b/lib/src/kee_vault_model/field_matcher_config.dart index 3076fd4..ddb36cb 100644 --- a/lib/src/kee_vault_model/field_matcher_config.dart +++ b/lib/src/kee_vault_model/field_matcher_config.dart @@ -13,8 +13,11 @@ class FieldMatcherConfig { this.actionOnMatch, }); - FieldMatcherConfig.forSingleClientMatch(String? id, String? name, String fft) - : this( + FieldMatcherConfig.forSingleClientMatch( + String fft, { + String? id, + String? name, + }) : this( customMatcher: FieldMatcher( ids: id == null ? [] : [id], names: name == null ? [] : [name], @@ -23,9 +26,12 @@ class FieldMatcherConfig { ), ); - FieldMatcherConfig.forSingleClientMatchHtmlType( - String? id, String? name, String? htmlType, String? domSelector) - : this( + FieldMatcherConfig.forSingleClientMatchHtmlType({ + String? id, + String? name, + String? htmlType, + String? domSelector, + }) : this( customMatcher: FieldMatcher( ids: id == null ? [] : [id], names: name == null ? [] : [name], diff --git a/test/internal/test_utils.dart b/test/internal/test_utils.dart index f6e7a52..1291788 100644 --- a/test/internal/test_utils.dart +++ b/test/internal/test_utils.dart @@ -5,7 +5,6 @@ import 'dart:typed_data'; import 'package:argon2_ffi_base/argon2_ffi_base.dart'; import 'package:kdbx/kdbx.dart'; -import 'package:kdbx/src/kee_vault_model/browser_field_model_v1.dart'; import 'package:logging/logging.dart'; import 'package:logging_appenders/logging_appenders.dart'; @@ -106,12 +105,18 @@ class TestUtil { Function proceedSeconds) async { final file = TestUtil.createEmptyFile(); final entry = createEntry(file, file.body.rootGroup, 'test1', 'test1'); - entry.browserSettings.fields.add(BrowserFieldModelV1( - displayName: 'test name', - fieldId: 'id', - name: 'form field name', - value: 'value1')); - // Would be nice to find a way to not have to do this to persist into a custom string entry + entry.browserSettings.fields = [ + Field( + name: 'test name', + valuePath: '.', + matcherConfigs: [ + FieldMatcherConfig.forSingleClientMatchHtmlType( + id: 'id', name: 'form field name') + ], + value: 'value1', + ) + ]; + // Would be nice to find a way to not have to do this to persist into a custom data and string entry entry.browserSettings = entry.browserSettings; await TestUtil.saveAndRead(file); proceedSeconds(1); diff --git a/test/kdbx_history_test.dart b/test/kdbx_history_test.dart index 79bedba..36f46e0 100644 --- a/test/kdbx_history_test.dart +++ b/test/kdbx_history_test.dart @@ -203,7 +203,7 @@ void main() { await TestUtil.createFileWithJsonFieldHistory(proceedSeconds); proceedSeconds(10); final entry = file.body.rootGroup.entries.values.toList()[0]; - expect(entry.browserSettings.fields.length, 0); + expect(entry.browserSettings.fields?.length ?? 0, 0); entry.revertToHistoryEntry(entry.history.length - 1); final history = entry.history; proceedSeconds(10); @@ -215,7 +215,7 @@ void main() { expect( history_2.getString(KdbxKeyCommon.USER_NAME)!.getText(), 'test1'); expect(entry.getString(KdbxKeyCommon.USER_NAME)!.getText(), 'test1'); - expect(entry.browserSettings.fields.length, 1); + expect(entry.browserSettings.fields?.length ?? 0, 1); }), ); }, tags: ['kdbx3']); From b24b601a9a47005690c145511acde1e771fef7b9 Mon Sep 17 00:00:00 2001 From: luckyrat Date: Tue, 20 Feb 2024 20:04:51 +0000 Subject: [PATCH 06/13] wip --- bin/kdbx.dart | 3 +- lib/src/crypto/protected_salt_generator.dart | 2 +- lib/src/crypto/protected_value.dart | 2 +- lib/src/kdbx_custom_data.dart | 8 ++-- lib/src/kdbx_deleted_object.dart | 3 +- lib/src/kdbx_entry.dart | 4 +- lib/src/kdbx_format.dart | 4 +- lib/src/kdbx_meta.dart | 4 +- lib/src/kdbx_times.dart | 3 +- lib/src/kdbx_xml.dart | 20 ++++---- .../browser_entry_settings_v1.dart | 4 +- lib/src/kee_vault_model/entry_matcher.dart | 32 ++++++------- .../kee_vault_model/entry_matcher_config.dart | 48 +++++++++---------- lib/src/kee_vault_model/field.dart | 48 +++++++++---------- lib/src/kee_vault_model/field_matcher.dart | 46 +++++++++--------- .../kee_vault_model/field_matcher_config.dart | 40 ++++++++-------- lib/src/utils/byte_utils.dart | 4 +- lib/src/utils/guid_service.dart | 2 +- lib/src/utils/scope_functions.dart | 1 + 19 files changed, 139 insertions(+), 139 deletions(-) diff --git a/bin/kdbx.dart b/bin/kdbx.dart index 97d26d8..67fe04b 100644 --- a/bin/kdbx.dart +++ b/bin/kdbx.dart @@ -33,8 +33,7 @@ void main(List arguments) { } class KdbxCommandRunner extends CommandRunner { - KdbxCommandRunner(String executableName, String description) - : super(executableName, description) { + KdbxCommandRunner(super.executableName, super.description) { argParser.addFlag('verbose', abbr: 'v'); addCommand(CatCommand()); addCommand(DumpXmlCommand()); diff --git a/lib/src/crypto/protected_salt_generator.dart b/lib/src/crypto/protected_salt_generator.dart index d108dcf..ccea8e3 100644 --- a/lib/src/crypto/protected_salt_generator.dart +++ b/lib/src/crypto/protected_salt_generator.dart @@ -42,7 +42,7 @@ class ProtectedSaltGenerator { } class ChachaProtectedSaltGenerator extends ProtectedSaltGenerator { - ChachaProtectedSaltGenerator._(StreamCipher state) : super._(state); + ChachaProtectedSaltGenerator._(super.state) : super._(); factory ChachaProtectedSaltGenerator.create(Uint8List key) { final hash = sha512.convert(key); diff --git a/lib/src/crypto/protected_value.dart b/lib/src/crypto/protected_value.dart index dc12aba..747a2d7 100644 --- a/lib/src/crypto/protected_value.dart +++ b/lib/src/crypto/protected_value.dart @@ -30,7 +30,7 @@ class PlainValue implements StringValue { } class ProtectedValue extends PlainValue { - ProtectedValue(String text) : super(text); + ProtectedValue(super.text); factory ProtectedValue.fromString(String value) { return ProtectedValue(value); diff --git a/lib/src/kdbx_custom_data.dart b/lib/src/kdbx_custom_data.dart index eb7dd0c..7c06908 100644 --- a/lib/src/kdbx_custom_data.dart +++ b/lib/src/kdbx_custom_data.dart @@ -8,14 +8,14 @@ class KdbxObjectCustomData extends KdbxNode { : _data = {}, super.create(KdbxXml.NODE_CUSTOM_DATA); - KdbxObjectCustomData.read(xml.XmlElement node) + KdbxObjectCustomData.read(super.node) : _data = Map.fromEntries( node.findElements(KdbxXml.NODE_CUSTOM_DATA_ITEM).map((el) { final key = el.singleTextNode(KdbxXml.NODE_KEY); final value = el.singleTextNode(KdbxXml.NODE_VALUE); return MapEntry(key, value); })), - super.read(node); + super.read(); final Map _data; @@ -59,7 +59,7 @@ class KdbxMetaCustomData extends KdbxNode { : _data = {}, super.create(KdbxXml.NODE_CUSTOM_DATA); - KdbxMetaCustomData.read(xml.XmlElement node) + KdbxMetaCustomData.read(super.node) : _data = Map.fromEntries( node.findElements(KdbxXml.NODE_CUSTOM_DATA_ITEM).map((el) { final key = el.singleTextNode(KdbxXml.NODE_KEY); @@ -73,7 +73,7 @@ class KdbxMetaCustomData extends KdbxNode { : null )); })), - super.read(node); + super.read(); final Map _data; diff --git a/lib/src/kdbx_deleted_object.dart b/lib/src/kdbx_deleted_object.dart index d100f82..050d536 100644 --- a/lib/src/kdbx_deleted_object.dart +++ b/lib/src/kdbx_deleted_object.dart @@ -2,7 +2,6 @@ import 'package:clock/clock.dart'; import 'package:kdbx/src/kdbx_format.dart'; import 'package:kdbx/src/kdbx_object.dart'; import 'package:kdbx/src/kdbx_xml.dart'; -import 'package:xml/xml.dart'; class KdbxDeletedObject extends KdbxNode implements KdbxNodeContext { KdbxDeletedObject.create(this.ctx, KdbxUuid uuid, {DateTime? deletionTime}) @@ -11,7 +10,7 @@ class KdbxDeletedObject extends KdbxNode implements KdbxNodeContext { this.deletionTime.set(deletionTime ?? clock.now().toUtc()); } - KdbxDeletedObject.read(XmlElement node, this.ctx) : super.read(node); + KdbxDeletedObject.read(super.node, this.ctx) : super.read(); static const NODE_NAME = KdbxXml.NODE_DELETED_OBJECT; diff --git a/lib/src/kdbx_entry.dart b/lib/src/kdbx_entry.dart index c2a451a..c5b7bdf 100644 --- a/lib/src/kdbx_entry.dart +++ b/lib/src/kdbx_entry.dart @@ -1,17 +1,19 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:typed_data'; + import 'package:collection/collection.dart'; -import 'package:kdbx/src/kee_vault_model/browser_entry_settings_v1.dart'; import 'package:kdbx/src/internal/extension_utils.dart'; import 'package:kdbx/src/kdbx_format.dart'; import 'package:kdbx/src/kdbx_object.dart'; import 'package:kdbx/src/kdbx_xml.dart'; +import 'package:kdbx/src/kee_vault_model/browser_entry_settings_v1.dart'; import 'package:kdbx/src/utils/guid_service.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; import 'package:quiver/check.dart'; import 'package:xml/xml.dart'; + import '../kdbx.dart'; final _logger = Logger('kdbx.kdbx_entry'); diff --git a/lib/src/kdbx_format.dart b/lib/src/kdbx_format.dart index edc0c45..d9f6e5c 100644 --- a/lib/src/kdbx_format.dart +++ b/lib/src/kdbx_format.dart @@ -133,10 +133,10 @@ class KdbxBody extends KdbxNode { } KdbxBody.read( - xml.XmlElement node, + super.node, this.meta, this.rootGroup, - ) : super.read(node); + ) : super.read(); // final xml.XmlDocument xmlDocument; final KdbxMeta meta; diff --git a/lib/src/kdbx_meta.dart b/lib/src/kdbx_meta.dart index c39123d..28d2525 100644 --- a/lib/src/kdbx_meta.dart +++ b/lib/src/kdbx_meta.dart @@ -40,7 +40,7 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext { historyMaxSize.set(Consts.DefaultHistoryMaxSize); } - KdbxMeta.read(xml.XmlElement node, this.ctx) + KdbxMeta.read(super.node, this.ctx) : customData = node .singleElement(KdbxXml.NODE_CUSTOM_DATA) ?.let((e) => KdbxMetaCustomData.read(e)) ?? @@ -91,7 +91,7 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext { .map((e) => MapEntry(e.uuid, e)) .let((that) => Map.fromEntries(that)) ?? {}, - super.read(node); + super.read(); @override final KdbxReadWriteContext ctx; diff --git a/lib/src/kdbx_times.dart b/lib/src/kdbx_times.dart index b1094b1..5acb1f6 100644 --- a/lib/src/kdbx_times.dart +++ b/lib/src/kdbx_times.dart @@ -4,7 +4,6 @@ import 'package:kdbx/src/kdbx_object.dart'; import 'package:kdbx/src/kdbx_xml.dart'; import 'package:logging/logging.dart'; import 'package:quiver/iterables.dart'; -import 'package:xml/xml.dart'; final _logger = Logger('kdbx_times'); @@ -19,7 +18,7 @@ class KdbxTimes extends KdbxNode implements KdbxNodeContext { usageCount.set(0); locationChanged.set(now); } - KdbxTimes.read(XmlElement node, this.ctx) : super.read(node) { + KdbxTimes.read(super.node, this.ctx) : super.read() { // backward compatibility - there was a bug setting/reading // modification, lastAccess and expiryTime. Make sure they are defined. final checkDates = { diff --git a/lib/src/kdbx_xml.dart b/lib/src/kdbx_xml.dart index 2a6fb5f..33e5c49 100644 --- a/lib/src/kdbx_xml.dart +++ b/lib/src/kdbx_xml.dart @@ -92,7 +92,7 @@ extension on List { } abstract class KdbxSubTextNode extends KdbxSubNode { - KdbxSubTextNode(KdbxNode node, String name) : super(node, name); + KdbxSubTextNode(super.node, super.name); void Function()? _onModify; @@ -158,7 +158,7 @@ abstract class KdbxSubTextNode extends KdbxSubNode { } class IntNode extends KdbxSubTextNode { - IntNode(KdbxNode node, String name) : super(node, name); + IntNode(super.node, super.name); @override int? decode(String value) => int.tryParse(value); @@ -168,7 +168,7 @@ class IntNode extends KdbxSubTextNode { } class StringNode extends KdbxSubTextNode { - StringNode(KdbxNode node, String name) : super(node, name); + StringNode(super.node, super.name); @override String decode(String value) => value; @@ -178,7 +178,7 @@ class StringNode extends KdbxSubTextNode { } class StringListNode extends KdbxSubTextNode> { - StringListNode(KdbxNode node, String name) : super(node, name); + StringListNode(super.node, super.name); @override List decode(String value) { @@ -195,7 +195,7 @@ class StringListNode extends KdbxSubTextNode> { } class Base64Node extends KdbxSubTextNode { - Base64Node(KdbxNode node, String name) : super(node, name); + Base64Node(super.node, super.name); @override ByteBuffer decode(String value) => base64.decode(value).buffer; @@ -205,7 +205,7 @@ class Base64Node extends KdbxSubTextNode { } class UuidNode extends KdbxSubTextNode { - UuidNode(KdbxNode node, String name) : super(node, name); + UuidNode(super.node, super.name); @override KdbxUuid decode(String value) => KdbxUuid(value); @@ -215,7 +215,7 @@ class UuidNode extends KdbxSubTextNode { } class IconNode extends KdbxSubTextNode { - IconNode(KdbxNode node, String name) : super(node, name); + IconNode(super.node, super.name); @override KdbxIcon decode(String value) => KdbxIcon.values[int.tryParse(value) ?? 0]; @@ -243,7 +243,7 @@ class KdbxColor { // Tolerates 6 digit hex strings but outputs more-compatible 7 char strings with leading # class ColorNode extends KdbxSubTextNode { - ColorNode(KdbxNode node, String name) : super(node, name); + ColorNode(super.node, super.name); @override KdbxColor decode(String value) => KdbxColor.parse(value); @@ -253,7 +253,7 @@ class ColorNode extends KdbxSubTextNode { } class NullableBooleanNode extends KdbxSubTextNode { - NullableBooleanNode(KdbxNode node, String name) : super(node, name); + NullableBooleanNode(super.node, super.name); @override bool? decode(String value) { @@ -284,7 +284,7 @@ class NullableBooleanNode extends KdbxSubTextNode { } class DateTimeUtcNode extends KdbxSubTextNode { - DateTimeUtcNode(KdbxNodeContext node, String name) : super(node, name); + DateTimeUtcNode(KdbxNodeContext super.node, super.name); KdbxReadWriteContext get _ctx => (node as KdbxNodeContext).ctx; static final minDate = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); diff --git a/lib/src/kee_vault_model/browser_entry_settings_v1.dart b/lib/src/kee_vault_model/browser_entry_settings_v1.dart index 53451d5..d15be94 100644 --- a/lib/src/kee_vault_model/browser_entry_settings_v1.dart +++ b/lib/src/kee_vault_model/browser_entry_settings_v1.dart @@ -326,7 +326,7 @@ class BrowserEntrySettingsV1 { final List fields = []; bool usernameFound = false; bool passwordFound = false; - formFieldList.forEach((ff) { + for (final ff in formFieldList) { if (ff.value == '{USERNAME}') { usernameFound = true; final mc = !((ff.fieldId?.isNotEmpty ?? false) || @@ -393,7 +393,7 @@ class BrowserEntrySettingsV1 { } fields.add(f); } - }); + } if (!usernameFound) { fields.add(Field( diff --git a/lib/src/kee_vault_model/entry_matcher.dart b/lib/src/kee_vault_model/entry_matcher.dart index 6a7f5fe..1541968 100644 --- a/lib/src/kee_vault_model/entry_matcher.dart +++ b/lib/src/kee_vault_model/entry_matcher.dart @@ -9,6 +9,22 @@ class EntryMatcher { this.pageTitles = const [], }); + factory EntryMatcher.fromMap(Map? map) { + if (map == null) { + return EntryMatcher(); + } + + return EntryMatcher( + matchLogic: MatcherLogic.values + .firstWhereOrNull((v) => v.name == map['matchLogic']), + queries: (map['queries'] as List?)?.cast() ?? [], + pageTitles: (map['pageTitles'] as List?)?.cast() ?? [], + ); + } + + factory EntryMatcher.fromJson(String source) => + EntryMatcher.fromMap(json.decode(source) as Map?); + EntryMatcher copyWith({ MatcherLogic? matchLogic, List? queries, @@ -29,24 +45,8 @@ class EntryMatcher { }; } - factory EntryMatcher.fromMap(Map? map) { - if (map == null) { - return EntryMatcher(); - } - - return EntryMatcher( - matchLogic: MatcherLogic.values - .firstWhereOrNull((v) => v.name == map['matchLogic']), - queries: (map['queries'] as List?)?.cast() ?? [], - pageTitles: (map['pageTitles'] as List?)?.cast() ?? [], - ); - } - String toJson() => json.encode(toMap()); - factory EntryMatcher.fromJson(String source) => - EntryMatcher.fromMap(json.decode(source) as Map?); - @override bool operator ==(Object other) { if (identical(this, other)) return true; diff --git a/lib/src/kee_vault_model/entry_matcher_config.dart b/lib/src/kee_vault_model/entry_matcher_config.dart index a0b9220..2f75246 100644 --- a/lib/src/kee_vault_model/entry_matcher_config.dart +++ b/lib/src/kee_vault_model/entry_matcher_config.dart @@ -14,6 +14,30 @@ class EntryMatcherConfig { this.actionOnNoMatch, }); + factory EntryMatcherConfig.fromMap(Map? map) { + if (map == null) { + return EntryMatcherConfig(); + } + + return EntryMatcherConfig( + matcherType: EntryMatcherType.values + .firstWhereOrNull((v) => v.name == map['matcherType']), + customMatcher: map['customMatcher'] != null + ? EntryMatcher.fromMap(map['customMatcher'] as Map) + : null, + urlMatchMethod: MatchAccuracy.values + .firstWhereOrNull((v) => v.name == map['urlMatchMethod']), + weight: map['weight'] as int?, + actionOnMatch: MatchAction.values + .firstWhereOrNull((v) => v.name == map['actionOnMatch']), + actionOnNoMatch: MatchAction.values + .firstWhereOrNull((v) => v.name == map['actionOnNoMatch']), + ); + } + + factory EntryMatcherConfig.fromJson(String source) => + EntryMatcherConfig.fromMap(json.decode(source) as Map); + EntryMatcherConfig.forDefaultUrlMatchBehaviour(MatchAccuracy ma) : this( matcherType: EntryMatcherType.Url, @@ -50,32 +74,8 @@ class EntryMatcherConfig { }; } - factory EntryMatcherConfig.fromMap(Map? map) { - if (map == null) { - return EntryMatcherConfig(); - } - - return EntryMatcherConfig( - matcherType: EntryMatcherType.values - .firstWhereOrNull((v) => v.name == map['matcherType']), - customMatcher: map['customMatcher'] != null - ? EntryMatcher.fromMap(map['customMatcher'] as Map) - : null, - urlMatchMethod: MatchAccuracy.values - .firstWhereOrNull((v) => v.name == map['urlMatchMethod']), - weight: map['weight'] as int?, - actionOnMatch: MatchAction.values - .firstWhereOrNull((v) => v.name == map['actionOnMatch']), - actionOnNoMatch: MatchAction.values - .firstWhereOrNull((v) => v.name == map['actionOnNoMatch']), - ); - } - String toJson() => json.encode(toMap()); - factory EntryMatcherConfig.fromJson(String source) => - EntryMatcherConfig.fromMap(json.decode(source) as Map); - @override String toString() { return 'EntryMatcherConfig(matcherType: $matcherType, customMatcher: $customMatcher, urlMatchMethod: $urlMatchMethod, weight: $weight, actionOnMatch: $actionOnMatch, actionOnNoMatch: $actionOnNoMatch)'; diff --git a/lib/src/kee_vault_model/field.dart b/lib/src/kee_vault_model/field.dart index c99f0c3..399a936 100644 --- a/lib/src/kee_vault_model/field.dart +++ b/lib/src/kee_vault_model/field.dart @@ -18,6 +18,30 @@ class Field { this.matcherConfigs, }); + factory Field.fromMap(Map? map) { + if (map == null) { + return Field(); + } + return Field( + uuid: map['uuid'] as String?, + name: map['name'] as String?, + valuePath: map['valuePath'] as String?, + value: map['value'] as String?, + page: map['page'] as int? ?? 1, + type: FieldType.values.firstWhereOrNull((v) => v.name == map['type']), + placeholderHandling: PlaceholderHandling.values + .firstWhereOrNull((v) => v.name == map['placeholderHandling']), + matcherConfigs: List.from((map['matcherConfigs'] + as List?) + ?.cast>() + .map((x) => FieldMatcherConfig.fromMap(x)) ?? + []), + ); + } + + factory Field.fromJson(String source) => + Field.fromMap(json.decode(source) as Map?); + Field copyWith({ String? uuid, String? name, @@ -55,32 +79,8 @@ class Field { }; } - factory Field.fromMap(Map? map) { - if (map == null) { - return Field(); - } - return Field( - uuid: map['uuid'] as String?, - name: map['name'] as String?, - valuePath: map['valuePath'] as String?, - value: map['value'] as String?, - page: map['page'] as int? ?? 1, - type: FieldType.values.firstWhereOrNull((v) => v.name == map['type']), - placeholderHandling: PlaceholderHandling.values - .firstWhereOrNull((v) => v.name == map['placeholderHandling']), - matcherConfigs: List.from((map['matcherConfigs'] - as List?) - ?.cast>() - .map((x) => FieldMatcherConfig.fromMap(x)) ?? - []), - ); - } - String toJson() => json.encode(toMap()); - factory Field.fromJson(String source) => - Field.fromMap(json.decode(source) as Map?); - @override String toString() { return 'Field(uuid: $uuid, name: $name, valuePath: $valuePath, value: $value, page: $page, type: $type, placeholderHandling: $placeholderHandling, matcherConfigs: $matcherConfigs)'; diff --git a/lib/src/kee_vault_model/field_matcher.dart b/lib/src/kee_vault_model/field_matcher.dart index 8186688..b165696 100644 --- a/lib/src/kee_vault_model/field_matcher.dart +++ b/lib/src/kee_vault_model/field_matcher.dart @@ -15,6 +15,29 @@ class FieldMatcher { this.minLength, }); + factory FieldMatcher.fromMap(Map? map) { + if (map == null) { + return FieldMatcher(); + } + + return FieldMatcher( + matchLogic: MatcherLogic.values + .firstWhereOrNull((v) => v.name == map['matchLogic']), + ids: (map['ids'] as List?)?.cast() ?? [], + names: (map['names'] as List?)?.cast() ?? [], + types: (map['types'] as List?)?.cast() ?? [], + queries: (map['queries'] as List?)?.cast() ?? [], + labels: (map['labels'] as List?)?.cast() ?? [], + autocompleteValues: + (map['autocompleteValues'] as List?)?.cast() ?? [], + maxLength: map['maxLength'] as int?, + minLength: map['minLength'] as int?, + ); + } + + factory FieldMatcher.fromJson(String source) => + FieldMatcher.fromMap(json.decode(source) as Map?); + FieldMatcher copyWith({ MatcherLogic? matchLogic, List? ids, @@ -55,31 +78,8 @@ class FieldMatcher { }; } - factory FieldMatcher.fromMap(Map? map) { - if (map == null) { - return FieldMatcher(); - } - - return FieldMatcher( - matchLogic: MatcherLogic.values - .firstWhereOrNull((v) => v.name == map['matchLogic']), - ids: (map['ids'] as List?)?.cast() ?? [], - names: (map['names'] as List?)?.cast() ?? [], - types: (map['types'] as List?)?.cast() ?? [], - queries: (map['queries'] as List?)?.cast() ?? [], - labels: (map['labels'] as List?)?.cast() ?? [], - autocompleteValues: - (map['autocompleteValues'] as List?)?.cast() ?? [], - maxLength: map['maxLength'] as int?, - minLength: map['minLength'] as int?, - ); - } - String toJson() => json.encode(toMap()); - factory FieldMatcher.fromJson(String source) => - FieldMatcher.fromMap(json.decode(source) as Map?); - @override String toString() { return 'FieldMatcher(matchLogic: $matchLogic, ids: $ids, names: $names, types: $types, queries: $queries, labels: $labels, autocompleteValues: $autocompleteValues, maxLength: $maxLength, minLength: $minLength)'; diff --git a/lib/src/kee_vault_model/field_matcher_config.dart b/lib/src/kee_vault_model/field_matcher_config.dart index ddb36cb..3a919b2 100644 --- a/lib/src/kee_vault_model/field_matcher_config.dart +++ b/lib/src/kee_vault_model/field_matcher_config.dart @@ -13,6 +13,26 @@ class FieldMatcherConfig { this.actionOnMatch, }); + factory FieldMatcherConfig.fromMap(Map? map) { + if (map == null) { + return FieldMatcherConfig(); + } + + return FieldMatcherConfig( + matcherType: FieldMatcherType.values + .firstWhereOrNull((v) => v.name == map['matchLogic']), + customMatcher: map['customMatcher'] != null + ? FieldMatcher.fromMap(map['customMatcher'] as Map) + : null, + weight: map['weight'] as int?, + actionOnMatch: MatchAction.values + .firstWhereOrNull((v) => v.name == map['actionOnMatch']), + ); + } + + factory FieldMatcherConfig.fromJson(String source) => + FieldMatcherConfig.fromMap(json.decode(source) as Map?); + FieldMatcherConfig.forSingleClientMatch( String fft, { String? id, @@ -64,28 +84,8 @@ class FieldMatcherConfig { }; } - factory FieldMatcherConfig.fromMap(Map? map) { - if (map == null) { - return FieldMatcherConfig(); - } - - return FieldMatcherConfig( - matcherType: FieldMatcherType.values - .firstWhereOrNull((v) => v.name == map['matchLogic']), - customMatcher: map['customMatcher'] != null - ? FieldMatcher.fromMap(map['customMatcher'] as Map) - : null, - weight: map['weight'] as int?, - actionOnMatch: MatchAction.values - .firstWhereOrNull((v) => v.name == map['actionOnMatch']), - ); - } - String toJson() => json.encode(toMap()); - factory FieldMatcherConfig.fromJson(String source) => - FieldMatcherConfig.fromMap(json.decode(source) as Map?); - @override String toString() { return 'FieldMatcherConfig(matcherType: $matcherType, customMatcher: $customMatcher, weight: $weight, actionOnMatch: $actionOnMatch)'; diff --git a/lib/src/utils/byte_utils.dart b/lib/src/utils/byte_utils.dart index 9fa7c4a..589beff 100644 --- a/lib/src/utils/byte_utils.dart +++ b/lib/src/utils/byte_utils.dart @@ -105,7 +105,7 @@ class ReaderHelper { } class ReaderHelperDartWeb extends ReaderHelper { - ReaderHelperDartWeb(Uint8List byteData) : super._(byteData); + ReaderHelperDartWeb(super.byteData) : super._(); @override int readUint64() { @@ -184,7 +184,7 @@ class WriterHelper { } class WriterHelperDartWeb extends WriterHelper { - WriterHelperDartWeb([BytesBuilder? output]) : super._(output); + WriterHelperDartWeb([super.output]) : super._(); @override void writeUint64(int value, [LengthWriter? lengthWriter]) { diff --git a/lib/src/utils/guid_service.dart b/lib/src/utils/guid_service.dart index 3af3c92..4e54d6d 100644 --- a/lib/src/utils/guid_service.dart +++ b/lib/src/utils/guid_service.dart @@ -7,6 +7,6 @@ abstract class IGuidService { class GuidService implements IGuidService { @override String newGuid() { - return Uuid().v4(); + return const Uuid().v4(); } } diff --git a/lib/src/utils/scope_functions.dart b/lib/src/utils/scope_functions.dart index a18fa5c..37ef4bc 100644 --- a/lib/src/utils/scope_functions.dart +++ b/lib/src/utils/scope_functions.dart @@ -1,5 +1,6 @@ /// https://github.com/YusukeIwaki/dart-kotlin_flavor/blob/74593dada94bdd8ca78946ad005d3a2624dc833f/lib/scope_functions.dart /// MIT license: https://github.com/YusukeIwaki/dart-kotlin_flavor/blob/74593dada94bdd8ca78946ad005d3a2624dc833f/LICENSE +library; ReturnType run(ReturnType Function() operation) { return operation(); From 313a582a79bdd941eda694cf13939aa61c919e9f Mon Sep 17 00:00:00 2001 From: luckyrat Date: Wed, 21 Feb 2024 11:54:41 +0000 Subject: [PATCH 07/13] wip --- .../kee_vault_model/browser_entry_settings.dart | 2 +- lib/src/kee_vault_model/field_matcher.dart | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/src/kee_vault_model/browser_entry_settings.dart b/lib/src/kee_vault_model/browser_entry_settings.dart index 94137c0..d5e15a1 100644 --- a/lib/src/kee_vault_model/browser_entry_settings.dart +++ b/lib/src/kee_vault_model/browser_entry_settings.dart @@ -208,7 +208,7 @@ class BrowserEntrySettings { behaviour.hashCode ^ authenticationMethods.hashCode ^ realm.hashCode ^ - matcherConfigs.hashCode ^ + const ListEquality().hash(matcherConfigs) ^ //TODO: all other lists? includeUrls.hashCode ^ excludeUrls.hashCode ^ fields.hashCode; diff --git a/lib/src/kee_vault_model/field_matcher.dart b/lib/src/kee_vault_model/field_matcher.dart index b165696..eee86c6 100644 --- a/lib/src/kee_vault_model/field_matcher.dart +++ b/lib/src/kee_vault_model/field_matcher.dart @@ -88,16 +88,16 @@ class FieldMatcher { @override bool operator ==(Object other) { if (identical(this, other)) return true; - final listEquals = const DeepCollectionEquality().equals; + final unOrdDeepEq = const DeepCollectionEquality.unordered().equals; return other is FieldMatcher && other.matchLogic == matchLogic && - listEquals(other.ids, ids) && - listEquals(other.names, names) && - listEquals(other.types, types) && - listEquals(other.queries, queries) && - listEquals(other.labels, labels) && - listEquals(other.autocompleteValues, autocompleteValues) && + unOrdDeepEq(other.ids, ids) && + unOrdDeepEq(other.names, names) && + unOrdDeepEq(other.types, types) && + unOrdDeepEq(other.queries, queries) && + unOrdDeepEq(other.labels, labels) && + unOrdDeepEq(other.autocompleteValues, autocompleteValues) && other.maxLength == maxLength && other.minLength == minLength; } From a28d62a423cc70a05f8325147b2ea102ffad29a4 Mon Sep 17 00:00:00 2001 From: luckyrat Date: Wed, 21 Feb 2024 13:15:03 +0000 Subject: [PATCH 08/13] wip --- lib/src/kdbx_meta.dart | 6 +++--- lib/src/kee_vault_model/browser_entry_settings.dart | 12 ++++++------ .../kee_vault_model/browser_entry_settings_v1.dart | 6 +++--- lib/src/kee_vault_model/entry_matcher.dart | 10 ++++++---- lib/src/kee_vault_model/field.dart | 6 +++--- lib/src/kee_vault_model/field_matcher.dart | 12 ++++++------ pubspec.yaml | 2 +- 7 files changed, 28 insertions(+), 26 deletions(-) diff --git a/lib/src/kdbx_meta.dart b/lib/src/kdbx_meta.dart index 28d2525..70c079d 100644 --- a/lib/src/kdbx_meta.dart +++ b/lib/src/kdbx_meta.dart @@ -463,8 +463,8 @@ class KeeVaultEmbeddedConfig { int get hashCode { return version.hashCode ^ randomId.hashCode ^ - addon.hashCode ^ - vault.hashCode; + const MapEquality().hash(addon) ^ + const MapEquality().hash(vault); } } @@ -582,7 +582,7 @@ class BrowserDbSettings { defaultPlaceholderHandling.hashCode ^ displayPriorityField.hashCode ^ displayGlobalPlaceholderOption.hashCode ^ - matchedURLAccuracyOverrides.hashCode; + const MapEquality().hash(matchedURLAccuracyOverrides); } } diff --git a/lib/src/kee_vault_model/browser_entry_settings.dart b/lib/src/kee_vault_model/browser_entry_settings.dart index d5e15a1..894a5ed 100644 --- a/lib/src/kee_vault_model/browser_entry_settings.dart +++ b/lib/src/kee_vault_model/browser_entry_settings.dart @@ -194,7 +194,7 @@ class BrowserEntrySettings { return o is BrowserEntrySettings && o.version == version && o.behaviour == behaviour && - o.authenticationMethods == authenticationMethods && + unOrdDeepEq(o.authenticationMethods, authenticationMethods) && o.realm == realm && unOrdDeepEq(o.matcherConfigs, matcherConfigs) && unOrdDeepEq(o.includeUrls, includeUrls) && @@ -206,11 +206,11 @@ class BrowserEntrySettings { int get hashCode { return version.hashCode ^ behaviour.hashCode ^ - authenticationMethods.hashCode ^ + const ListEquality().hash(authenticationMethods) ^ realm.hashCode ^ - const ListEquality().hash(matcherConfigs) ^ //TODO: all other lists? - includeUrls.hashCode ^ - excludeUrls.hashCode ^ - fields.hashCode; + const ListEquality().hash(matcherConfigs) ^ + const ListEquality().hash(includeUrls) ^ + const ListEquality().hash(excludeUrls) ^ + const ListEquality().hash(fields); } } diff --git a/lib/src/kee_vault_model/browser_entry_settings_v1.dart b/lib/src/kee_vault_model/browser_entry_settings_v1.dart index d15be94..2416104 100644 --- a/lib/src/kee_vault_model/browser_entry_settings_v1.dart +++ b/lib/src/kee_vault_model/browser_entry_settings_v1.dart @@ -297,9 +297,9 @@ class BrowserEntrySettingsV1 { priority.hashCode ^ hide.hashCode ^ realm.hashCode ^ - includeUrls.hashCode ^ - excludeUrls.hashCode ^ - fields.hashCode; + const ListEquality().hash(includeUrls) ^ + const ListEquality().hash(excludeUrls) ^ + const ListEquality().hash(fields); } BrowserEntrySettings convertToV2(IGuidService guidService) { diff --git a/lib/src/kee_vault_model/entry_matcher.dart b/lib/src/kee_vault_model/entry_matcher.dart index 1541968..e9bb2d2 100644 --- a/lib/src/kee_vault_model/entry_matcher.dart +++ b/lib/src/kee_vault_model/entry_matcher.dart @@ -50,17 +50,19 @@ class EntryMatcher { @override bool operator ==(Object other) { if (identical(this, other)) return true; - final listEquals = const DeepCollectionEquality().equals; + final unOrdDeepEq = const DeepCollectionEquality.unordered().equals; return other is EntryMatcher && other.matchLogic == matchLogic && - listEquals(other.queries, queries) && - listEquals(other.pageTitles, pageTitles); + unOrdDeepEq(other.queries, queries) && + unOrdDeepEq(other.pageTitles, pageTitles); } @override int get hashCode { - return matchLogic.hashCode ^ queries.hashCode ^ pageTitles.hashCode; + return matchLogic.hashCode ^ + const ListEquality().hash(queries) ^ + const ListEquality().hash(pageTitles); } MatcherLogic? matchLogic; // default to Client initially diff --git a/lib/src/kee_vault_model/field.dart b/lib/src/kee_vault_model/field.dart index 399a936..8be53fe 100644 --- a/lib/src/kee_vault_model/field.dart +++ b/lib/src/kee_vault_model/field.dart @@ -89,7 +89,7 @@ class Field { @override bool operator ==(Object other) { if (identical(this, other)) return true; - final listEquals = const DeepCollectionEquality().equals; + final unOrdDeepEq = const DeepCollectionEquality.unordered().equals; return other is Field && other.uuid == uuid && @@ -99,7 +99,7 @@ class Field { other.page == page && other.type == type && other.placeholderHandling == placeholderHandling && - listEquals(other.matcherConfigs, matcherConfigs); + unOrdDeepEq(other.matcherConfigs, matcherConfigs); } @override @@ -111,7 +111,7 @@ class Field { page.hashCode ^ type.hashCode ^ placeholderHandling.hashCode ^ - matcherConfigs.hashCode; + const ListEquality().hash(matcherConfigs); } String? uuid; diff --git a/lib/src/kee_vault_model/field_matcher.dart b/lib/src/kee_vault_model/field_matcher.dart index eee86c6..49e4148 100644 --- a/lib/src/kee_vault_model/field_matcher.dart +++ b/lib/src/kee_vault_model/field_matcher.dart @@ -105,12 +105,12 @@ class FieldMatcher { @override int get hashCode { return matchLogic.hashCode ^ - ids.hashCode ^ - names.hashCode ^ - types.hashCode ^ - queries.hashCode ^ - labels.hashCode ^ - autocompleteValues.hashCode ^ + const ListEquality().hash(ids) ^ + const ListEquality().hash(names) ^ + const ListEquality().hash(types) ^ + const ListEquality().hash(queries) ^ + const ListEquality().hash(labels) ^ + const ListEquality().hash(autocompleteValues) ^ maxLength.hashCode ^ minLength.hashCode; } diff --git a/pubspec.yaml b/pubspec.yaml index ca90fc6..17ceefd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: kdbx description: KeepassX format implementation in pure dart. (kdbx 3.x and 4.x support). -version: 0.6.0+2 +version: 0.6.0+3 homepage: https://github.com/kee-org/kdbx.dart publish_to: none From 82dc44f857553b06949d039272aa9947944965bc Mon Sep 17 00:00:00 2001 From: luckyrat Date: Thu, 22 Feb 2024 15:08:52 +0000 Subject: [PATCH 09/13] Fix url match property name case --- .../browser_entry_settings.dart | 32 +++++++++---------- test/browser_entry_settings_test.dart | 8 +++++ 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/lib/src/kee_vault_model/browser_entry_settings.dart b/lib/src/kee_vault_model/browser_entry_settings.dart index 894a5ed..9d86aeb 100644 --- a/lib/src/kee_vault_model/browser_entry_settings.dart +++ b/lib/src/kee_vault_model/browser_entry_settings.dart @@ -107,36 +107,36 @@ class BrowserEntrySettings { static Map> parseUrls( List includeUrls, List excludeUrls) { - final altURLs = []; - final regExURLs = []; - final blockedURLs = []; - final regExBlockedURLs = []; + final altUrls = []; + final regExUrls = []; + final blockedUrls = []; + final regExBlockedUrls = []; for (final p in includeUrls) { if (p is RegExp) { - regExURLs.add(p.pattern); + regExUrls.add(p.pattern); } else if (p is String) { - altURLs.add(p); + altUrls.add(p); } } for (final p in excludeUrls) { if (p is RegExp) { - regExBlockedURLs.add(p.pattern); + regExBlockedUrls.add(p.pattern); } else if (p is String) { - blockedURLs.add(p); + blockedUrls.add(p); } } return >{ - if (altURLs.isNotEmpty) 'altURLs': altURLs, - if (regExURLs.isNotEmpty) 'regExURLs': regExURLs, - if (blockedURLs.isNotEmpty) 'blockedURLs': blockedURLs, - if (regExBlockedURLs.isNotEmpty) 'regExBlockedURLs': regExBlockedURLs, + if (altUrls.isNotEmpty) 'altUrls': altUrls, + if (regExUrls.isNotEmpty) 'regExUrls': regExUrls, + if (blockedUrls.isNotEmpty) 'blockedUrls': blockedUrls, + if (regExBlockedUrls.isNotEmpty) 'regExBlockedUrls': regExBlockedUrls, }; } static List getIncludeUrls(Map map) { final includeUrls = []; - final altUrls = (map['altURLs'] as List?)?.cast(); - final regExURLs = (map['regExURLs'] as List?)?.cast(); + final altUrls = (map['altUrls'] as List?)?.cast(); + final regExURLs = (map['regExUrls'] as List?)?.cast(); if (altUrls != null) { altUrls.forEach(includeUrls.add); } @@ -150,9 +150,9 @@ class BrowserEntrySettings { static List getExcludeUrls(Map map) { final excludeUrls = []; - final blockedURLs = (map['blockedURLs'] as List?)?.cast(); + final blockedURLs = (map['blockedUrls'] as List?)?.cast(); final regExBlockedURLs = - (map['regExBlockedURLs'] as List?)?.cast(); + (map['regExBlockedUrls'] as List?)?.cast(); if (blockedURLs != null) { blockedURLs.forEach(excludeUrls.add); } diff --git a/test/browser_entry_settings_test.dart b/test/browser_entry_settings_test.dart index a5ba1e8..17ef271 100644 --- a/test/browser_entry_settings_test.dart +++ b/test/browser_entry_settings_test.dart @@ -71,6 +71,10 @@ void main() { testCase( '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"matcherType":"UsernameDefaultHeuristic"}]},{"page":-1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"matcherType":"PasswordDefaultHeuristic"}]}]}', '{"version":1,"priority":0,"hide":false,"hTTPRealm":"","formFieldList":[{"displayName":"KeePass username","name":"","type":"FFTusername","id":"","page":-1,"placeholderHandling":"Default","value":"{USERNAME}"},{"displayName":"KeePass password","name":"","type":"FFTpassword","id":"","page":-1,"placeholderHandling":"Default","value":"{PASSWORD}"}],"alwaysAutoFill":false,"alwaysAutoSubmit":false,"neverAutoFill":false,"neverAutoSubmit":false,"blockDomainOnlyMatch":false,"blockHostnameOnlyMatch":false,"altURLs":[],"regExURLs":[],"blockedURLs":[],"regExBlockedURLs":[]}'); + + testCase( + '{"version":2,"altUrls":["http://test.com/1","http://test.com/2"],"regExUrls":["3","4"],"blockedUrls":["5","6"],"regExBlockedUrls":["7","8"],"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":-1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}]}', + '{"version":1,"priority":0,"hide":false,"hTTPRealm":"","formFieldList":[{"displayName":"KeePass password","name":"password","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default","value":"{PASSWORD}"},{"displayName":"KeePass username","name":"username","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default","value":"{USERNAME}"}],"alwaysAutoFill":false,"alwaysAutoSubmit":false,"neverAutoFill":false,"neverAutoSubmit":false,"blockDomainOnlyMatch":false,"blockHostnameOnlyMatch":false,"altURLs":["http://test.com/1","http://test.com/2"],"regExURLs":["3","4"],"blockedURLs":["5","6"],"regExBlockedURLs":["7","8"]}'); }); test('config v1->v2', () async { @@ -86,6 +90,10 @@ void main() { '{"version":1,"hTTPRealm":"","formFieldList":[{"name":"password","displayName":"KeePass password","value":"{PASSWORD}","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default"},{"name":"username","displayName":"KeePass username","value":"{USERNAME}","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default"}],"alwaysAutoFill":false,"neverAutoFill":false,"alwaysAutoSubmit":false,"neverAutoSubmit":false,"priority":0,"altURLs":[],"hide":false,"blockHostnameOnlyMatch":false,"blockDomainOnlyMatch":false}', '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}]}'); + testCaseToV2( + '{"version":1,"hTTPRealm":"","formFieldList":[{"name":"password","displayName":"KeePass password","value":"{PASSWORD}","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default"},{"name":"username","displayName":"KeePass username","value":"{USERNAME}","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default"}],"alwaysAutoFill":false,"neverAutoFill":false,"alwaysAutoSubmit":false,"neverAutoSubmit":false,"priority":0,"altURLs":["http://test.com/1","http://test.com/2"],"regExURLs":["3","4"],"blockedURLs":["5","6"],"regExBlockedURLs":["7","8"],"hide":false,"blockHostnameOnlyMatch":false,"blockDomainOnlyMatch":false}', + '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}],"altUrls":["http://test.com/1","http://test.com/2"],"regExUrls":["3","4"],"blockedUrls":["5","6"],"regExBlockedUrls":["7","8"]}'); + testCaseToV2('', '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"matcherType":"UsernameDefaultHeuristic"}]},{"page":1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"matcherType":"PasswordDefaultHeuristic"}]}]}'); }); From f8b5408e8dd965afd8dcd06b867cda1fc7ec9935 Mon Sep 17 00:00:00 2001 From: luckyrat Date: Fri, 23 Feb 2024 11:20:09 +0000 Subject: [PATCH 10/13] Add extra format upgrade tests --- lib/src/kdbx_file.dart | 7 +++ pubspec.yaml | 2 +- test/kdbx_upgrade_test.dart | 57 ++++++++++++++++-------- test/test_files/v4.0.kdbx | Bin 0 -> 2497 bytes test/test_files/v4_1-invalid-dates.kdbx | Bin 0 -> 993 bytes 5 files changed, 46 insertions(+), 20 deletions(-) create mode 100644 test/test_files/v4.0.kdbx create mode 100644 test/test_files/v4_1-invalid-dates.kdbx diff --git a/lib/src/kdbx_file.dart b/lib/src/kdbx_file.dart index 16b3885..7eae5aa 100644 --- a/lib/src/kdbx_file.dart +++ b/lib/src/kdbx_file.dart @@ -151,6 +151,13 @@ class KdbxFile { ? header.upgradeMinor(majorVersion, minorVersion) : header.upgrade(majorVersion, minorVersion); + // Do this even we were already in v4.x since some KDBX software (including + // earlier versions of Kee Vault) skipped this step when upgrading from `BI@nXX25sC=$OBEIuwn-g000191V5$lY8OTPUc>|9 zFCB9WivR!s00BY;0000aRaHqu5C8xG?_+J>j44D*k@u;j1LFz|LID5(08=0U006@U zCa(x7<=s^p6A7gRCml4jd$8Ee=R8d(_;%Uq*SG`$0000`1ONa40RR911pxp607(b{ z0006200000000F60000@2mk;8000mG000001OWg509FJ5000vJ00000000008DQhR zrvj)^dw9x2wZmE_Lzx^t%clyg(u(t&ey`DF4ZN}_{1|^SXxtJq*j?6#X%?t%z}=LI z4R0bSL;sD$0buUIGQZ6*dOa0r2HJ#dnG?jIAnG`fziIgcL0n&YU`oM#* zn@L-p{H-&#b;0{7z2%}~&;Q|k0IkAe@Ryx$kBmM112Maxo+cg`9VB((tT-aB;v@Lr zi6^5Vd-D2l3dwMZb~=DP7vC#LG7$*8Y*qXR);fi}Wyea^MPvXWiG!SUBtIpzU*S)u z;J_$FtL^Q$LgD!7Um%l4PUnd^Za#HFvg_bm*FHHii9F#)iFn>4S69QRt01Fc{Z4Ol zt%yd7;{k*)aGo_agH}Ci&HfR(NFItMoC@m*MUflJ;+HNhnSOr zjC5!ul^a}O!^BeF5!kQ5!6gL^GuajW(7tK`%AhkT1*vMZ=<-=zKh!#byF}1Ybl{EG z_Eg^(^~g^>Y26`-L-PIN3Oew9Kk zb>8}HcFh8QDGt55G3L2TGojp9!0AkkEI()Uoz=CxuVOdK6HgWgDi0Y3@Bnb`7GKzv zdm5#z)Ycx_tnj43wsyIrW|7*z?vyY$Xj_D!=qv>-Z=AS{(3c4^c7{o%bWm{KbjxWc zDU{`V9_4}hK(hq9ve4vq`j@cf@E*;;65sLc>cZXA|F(Wz^^x5V!QvF;+}fGY)Z~eJ zHz!G~;coYu_v42`p+H2A`tTYG#w_IT)`x>?=~kZD^m!kvqSTi{m+_>VtLRDJHp$UM zzo}nV;tu~U6&OGVtiM3%@Rx+{qBcuvYy0jb$bD=};%i%%bkA4y0uvg2&_t=AELS5i z3<+VCGvtty$I4Ekw+76=e47YHUQ0*b;X< zLF`G9?X5-rDK!zjgVJz23shToZ2A+zNx#r4WD3Q_y6Dq}I!5hV8*52)5UBuG_8OKE z?O1!P|8kY8b}6S;PoC7GsJfJK;7-gtwJTKHpA_6?3D-5^o4@MB3m;Zx=x!C=#;fIX z-U8t`UNL`Fx$zmq1Y%O(Z;#FtQDhz;jkp?-(FLzzi{yjFOG5@h+S1pDy`$TPIzX+d z!+P*cP!26&6lav0pKJt^{U-hIB9RR-oo$@nSXZk9E*2{bmT=Fy9HQ8zTcvSao;pCQ ze&mC_CIRN*n$H5goFC@F{K@wnZU(!uTS1;|RFzGxH71pql6wXjE3~DQDYjh5%}C7F zNBy36wjcMY6f0Evyzl_!3wrfTQP2C9!t#srKLO&Y2yb2jh*5?nZ#&>g zh~9}6ac+lkm$+kNI?0%pu#=hW8DRQK-kL35hz{IR6a~&Ty1?j%w#2b>MUm_gjpk{9 zmfd=ae?3)2uwTI*C8N8u%?&jcby;s3rbqaZ3(7E>2Wy)t)3&1j?kD-DxYzT`tT8NUiUpNYI=6bqb~m_zK8>@xEr+Vr`&@psipX)hBgRcNWr z4VB?Nh@ugVv)D*R@qdGEW}!tWqg3D)Tp@-71M-YImsb9i~ryvNoa;B zMW`!LrmlnHYt?sV6xjVCPabbP>BxWx(@4` zFYS1{iEj5Yz~?8l-~-m5h1#_JqVeV)BqhO8Mi=#(A{cRrOG8F!A!)63Y%Lk02I=Fk zu~ymbekKgRu(log5OFOZ)eXbJQ>ft}!Y)JPkE=d-Lm z{pbK}-$Jnkt192wh+;pW(O-kPt+&ubgf!RqMg;_cNkfc|J!3B*QCEM)BfDWN?#tY- zqug0I>wSVY4^-rW9&hL-CNQ&ky~guf#v%aXk94!X8DQMhe;T(R3#2lH*t`QO?blz@ zOrDt`noEVDG5by^b@^dd@ev}%T+q_0(3?E~KbsL5DRt*JEbt$GGE<=VWf=}Kw}9kp z{SudGX+{bOyE=WlG|1-dXm0D3tfa%Da&J(mkc2~umX!kLqghqfBXgD^TVr%~nb9)8 zONR+E)|A{viQy@HaQrnsv;a`lt5X|ZoLA_lkz3NZ)Nn6$KfXTd2X5@;>u>3)J~44X z;>=Z#=;_c>z(gb@Ur_vfLDoLoW&N>U+*+Yfdf6sarJag-RL-74A!om%i>|B2{`|^8 zX2Hv6mN5HUUat>v>GdKL|0R>&hh-xP3!WK90*a477jS@3#9!QX-b<`7BRGL-kJmgG zow{kG1#{wOfrKfz(;&FCAaIFeE|Oh)QY3cFjI(1>@+=KBIxl1~VU_NELlJV_uNbAh zJ{d=cajR+P@F5FKQPl9sF%k+e9I7sVSf3^p)`DuJEa@*B0nnQRD>*;8V>cUo4}S}@ LHl7Rm00000h?10x literal 0 HcmV?d00001 diff --git a/test/test_files/v4_1-invalid-dates.kdbx b/test/test_files/v4_1-invalid-dates.kdbx new file mode 100644 index 0000000000000000000000000000000000000000..fa5d919844dfea1fde0a143a1077177aecac6945 GIT binary patch literal 993 zcmZR+xoB4UZ||*)3@i*x0t^fch6g`A+h6D$urGpDG3!s%e`Xe-AR`d7C;*8saWme3 zxa`VXsu|v@y*+>H-Q>2ESyHM?7$mB!Y+l)`u!D?fQJW!mlKuPKhx^}u?S0Y49l;7f;=}X1nlsENT{^t{c(cjI zJqBCPUS4m)9um-dNv7yzY1>B@kjem#*zde1q;jjA1^Ut){n~2vr$G&b}bSm-0nZq6F+`(TD zM`c$rUN~!MFeT=-OQ;@O@8U-VZhB%dX;5}1P zl^T~^^Iw4hpTCv?DF6PKeJb1U`V~2*CtkUHeOxF$Px2C1 zp#Dv^-eX2#e7erkFzvw zwy&{$`D@ASF6EMK8xqco&VBjFMlCAto|i+JGw;0zI=02T&R$w=rnm0<@{`ewlm5n^ z)l_a2d!Bgq#-^pmC;f1LFBqj&eqFzn*LKyG$#1>WCj~F$>B(bV-(7m$BC37u5vv)u zeJaClS=LDJh<`F)p1mj8vc=VHInVo9`!D~#z^43hq5QfFdEF6D94}aHyRnP$%f&AR zE&_9{ZcnZJ=CrEkMU>5%d8@OvzjrHfO6IB0 zs|%OTi}d|eTew;yZjn0MkrFK{t$2GTw~|(&gMT&6A{~^o&HprQsdI|46+2TcwzJW` zsqpNcmpi`;9s01QI&%4dhK{4w=N`?5*#0+mu1yv&0& zmMjs`FmEesx_x?Lk)oXU$_MUdRq<*@mlIUCT)X$wPN*|2bhfS5-8Ehlr@pEEmf7ac zWhqegM!(&y$?bxSwYZ(*t(KOT6|k^ JMiMBY0{~Bmsb>HH literal 0 HcmV?d00001 From 29ffc8bfbc1e188db46ef5b1a38f9d6c42b1aa91 Mon Sep 17 00:00:00 2001 From: luckyrat Date: Mon, 4 Mar 2024 11:20:41 +0000 Subject: [PATCH 11/13] Ensure we always have a display name for custom fields --- .../browser_entry_settings_v1.dart | 15 +++++---- lib/src/kee_vault_model/field.dart | 4 +++ lib/src/utils/guid_service.dart | 12 +++++-- test/browser_entry_settings_test.dart | 32 ++++++++++++------- 4 files changed, 42 insertions(+), 21 deletions(-) diff --git a/lib/src/kee_vault_model/browser_entry_settings_v1.dart b/lib/src/kee_vault_model/browser_entry_settings_v1.dart index 2416104..57849f4 100644 --- a/lib/src/kee_vault_model/browser_entry_settings_v1.dart +++ b/lib/src/kee_vault_model/browser_entry_settings_v1.dart @@ -341,7 +341,7 @@ class BrowserEntrySettingsV1 { final f = Field( valuePath: 'UserName', page: max(ff.page, 1), - uuid: guidService.newGuid(), + uuid: guidService.newGuidAsBase64(), type: FieldType.Text, matcherConfigs: [mc], ); @@ -364,7 +364,7 @@ class BrowserEntrySettingsV1 { final f = Field( valuePath: 'Password', page: max(ff.page, 1), - uuid: guidService.newGuid(), + uuid: guidService.newGuidAsBase64(), type: FieldType.Password, matcherConfigs: [mc]); if (ff.placeholderHandling != PlaceholderHandling.Default.name) { @@ -378,11 +378,14 @@ class BrowserEntrySettingsV1 { id: ff.fieldId, name: ff.name, ); + final newUniqueId = guidService.newGuidAsBase64(); final f = Field( - name: ff.displayName, + name: (ff.displayName?.isNotEmpty ?? false) + ? ff.displayName + : newUniqueId, valuePath: '.', page: max(ff.page, 1), - uuid: guidService.newGuid(), + uuid: newUniqueId, type: Utilities.formFieldTypeToFieldType( ff.type ?? FormFieldType.TEXT), matcherConfigs: [mc], @@ -398,7 +401,7 @@ class BrowserEntrySettingsV1 { if (!usernameFound) { fields.add(Field( valuePath: 'UserName', - uuid: guidService.newGuid(), + uuid: guidService.newGuidAsBase64(), type: FieldType.Text, matcherConfigs: [ FieldMatcherConfig( @@ -408,7 +411,7 @@ class BrowserEntrySettingsV1 { if (!passwordFound) { fields.add(Field( valuePath: 'Password', - uuid: guidService.newGuid(), + uuid: guidService.newGuidAsBase64(), type: FieldType.Password, matcherConfigs: [ FieldMatcherConfig( diff --git a/lib/src/kee_vault_model/field.dart b/lib/src/kee_vault_model/field.dart index 8be53fe..41da765 100644 --- a/lib/src/kee_vault_model/field.dart +++ b/lib/src/kee_vault_model/field.dart @@ -156,6 +156,10 @@ class Field { ffValue = '{USERNAME}'; } + if (displayName?.isEmpty ?? true) { + displayName = uuid; + } + if (ffValue != '') { return BrowserFieldModelV1( name: htmlName, diff --git a/lib/src/utils/guid_service.dart b/lib/src/utils/guid_service.dart index 4e54d6d..b12470e 100644 --- a/lib/src/utils/guid_service.dart +++ b/lib/src/utils/guid_service.dart @@ -1,12 +1,18 @@ +import 'dart:typed_data'; + +import 'package:kdbx/src/utils/byte_utils.dart'; import 'package:uuid/uuid.dart'; abstract class IGuidService { - String newGuid(); + //String newGuid(); + String newGuidAsBase64(); } class GuidService implements IGuidService { @override - String newGuid() { - return const Uuid().v4(); + String newGuidAsBase64() { + final buf = Uint8List(16); + const Uuid().v4buffer(buf); + return buf.encodeBase64(); } } diff --git a/test/browser_entry_settings_test.dart b/test/browser_entry_settings_test.dart index 17ef271..b1dfcfa 100644 --- a/test/browser_entry_settings_test.dart +++ b/test/browser_entry_settings_test.dart @@ -13,8 +13,8 @@ final _logger = Logger('browser_entry_settings_test'); class MockGuidService implements IGuidService { @override - String newGuid() { - return '00000000-0000-0000-0000-000000000000'; + String newGuidAsBase64() { + return 'AAAAAAAAAAAAAAAAAAAAAA=='; } } @@ -57,45 +57,53 @@ void main() { group('BrowserEntrySettings', () { test('config v2->v1', () async { testCase( - '{"version":2,"altUrls":[],"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"},{"matcherType":"Hide"}],"fields":[{"page":-1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":-1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}]}', + '{"version":2,"altUrls":[],"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"},{"matcherType":"Hide"}],"fields":[{"page":-1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":-1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}]}', '{"version":1,"priority":0,"hide":true,"hTTPRealm":"","formFieldList":[{"displayName":"KeePass password","name":"password","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default","value":"{PASSWORD}"},{"displayName":"KeePass username","name":"username","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default","value":"{USERNAME}"}],"alwaysAutoFill":false,"alwaysAutoSubmit":false,"neverAutoFill":false,"neverAutoSubmit":false,"blockDomainOnlyMatch":false,"blockHostnameOnlyMatch":false,"altURLs":[],"regExURLs":[],"blockedURLs":[],"regExBlockedURLs":[]}'); testCase( - '{"version":2,"altUrls":[],"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":-1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}]}', + '{"version":2,"altUrls":[],"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":-1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}]}', '{"version":1,"priority":0,"hide":false,"hTTPRealm":"","formFieldList":[{"displayName":"KeePass password","name":"password","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default","value":"{PASSWORD}"},{"displayName":"KeePass username","name":"username","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default","value":"{USERNAME}"}],"alwaysAutoFill":false,"alwaysAutoSubmit":false,"neverAutoFill":false,"neverAutoSubmit":false,"blockDomainOnlyMatch":false,"blockHostnameOnlyMatch":false,"altURLs":[],"regExURLs":[],"blockedURLs":[],"regExBlockedURLs":[]}'); testCase( - '{"version":2,"altUrls":[],"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":-1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}]}', + '{"version":2,"altUrls":[],"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":-1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}]}', '{"version":1,"priority":0,"hide":false,"hTTPRealm":"","formFieldList":[{"displayName":"KeePass password","name":"password","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default","value":"{PASSWORD}"},{"displayName":"KeePass username","name":"username","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default","value":"{USERNAME}"}],"alwaysAutoFill":false,"alwaysAutoSubmit":false,"neverAutoFill":false,"neverAutoSubmit":false,"blockDomainOnlyMatch":false,"blockHostnameOnlyMatch":false,"altURLs":[],"regExURLs":[],"blockedURLs":[],"regExBlockedURLs":[]}'); testCase( - '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"matcherType":"UsernameDefaultHeuristic"}]},{"page":-1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"matcherType":"PasswordDefaultHeuristic"}]}]}', + '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"matcherType":"UsernameDefaultHeuristic"}]},{"page":-1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"matcherType":"PasswordDefaultHeuristic"}]}]}', '{"version":1,"priority":0,"hide":false,"hTTPRealm":"","formFieldList":[{"displayName":"KeePass username","name":"","type":"FFTusername","id":"","page":-1,"placeholderHandling":"Default","value":"{USERNAME}"},{"displayName":"KeePass password","name":"","type":"FFTpassword","id":"","page":-1,"placeholderHandling":"Default","value":"{PASSWORD}"}],"alwaysAutoFill":false,"alwaysAutoSubmit":false,"neverAutoFill":false,"neverAutoSubmit":false,"blockDomainOnlyMatch":false,"blockHostnameOnlyMatch":false,"altURLs":[],"regExURLs":[],"blockedURLs":[],"regExBlockedURLs":[]}'); testCase( - '{"version":2,"altUrls":["http://test.com/1","http://test.com/2"],"regExUrls":["3","4"],"blockedUrls":["5","6"],"regExBlockedUrls":["7","8"],"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":-1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}]}', + '{"version":2,"altUrls":["http://test.com/1","http://test.com/2"],"regExUrls":["3","4"],"blockedUrls":["5","6"],"regExBlockedUrls":["7","8"],"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":-1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}]}', '{"version":1,"priority":0,"hide":false,"hTTPRealm":"","formFieldList":[{"displayName":"KeePass password","name":"password","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default","value":"{PASSWORD}"},{"displayName":"KeePass username","name":"username","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default","value":"{USERNAME}"}],"alwaysAutoFill":false,"alwaysAutoSubmit":false,"neverAutoFill":false,"neverAutoSubmit":false,"blockDomainOnlyMatch":false,"blockHostnameOnlyMatch":false,"altURLs":["http://test.com/1","http://test.com/2"],"regExURLs":["3","4"],"blockedURLs":["5","6"],"regExBlockedURLs":["7","8"]}'); + + testCase( + '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":0,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"matcherType":"UsernameDefaultHeuristic"}]},{"page":-1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"matcherType":"PasswordDefaultHeuristic"}]},{"uuid":"AAAAAAAAAAAAAAAAAAAAAA==","name":"dis Name","valuePath":".","value":"KEEFOX_CHECKED_FLAG_TRUE","page":3,"type":"Toggle","placeholderHandling":"Disabled","matcherConfigs":[{"customMatcher":{"ids":["www"],"names":["rrr"],"types":["checkbox"],"queries":[]}}]},{"uuid":"AAAAAAAAAAAAAAAAAAAAAA==","name":"","valuePath":".","value":"RadioValue","page":1,"type":"Existing","matcherConfigs":[{"customMatcher":{"ids":["radid"],"names":["radname"],"types":["radio"],"queries":[]}}]}]}', + '{"version":1,"priority":0,"hide":false,"hTTPRealm":"","formFieldList":[{"displayName":"KeePass username","name":"","type":"FFTusername","id":"","page":0,"placeholderHandling":"Default","value":"{USERNAME}"},{"displayName":"KeePass password","name":"","type":"FFTpassword","id":"","page":-1,"placeholderHandling":"Default","value":"{PASSWORD}"},{"displayName":"dis Name","name":"rrr","type":"FFTcheckbox","id":"www","page":3,"placeholderHandling":"Disabled","value":"KEEFOX_CHECKED_FLAG_TRUE"},{"displayName":"AAAAAAAAAAAAAAAAAAAAAA==","name":"radname","type":"FFTradio","id":"radid","page":1,"placeholderHandling":"Default","value":"RadioValue"}],"alwaysAutoFill":false,"alwaysAutoSubmit":false,"neverAutoFill":false,"neverAutoSubmit":false,"blockDomainOnlyMatch":false,"blockHostnameOnlyMatch":false,"altURLs":[],"regExURLs":[],"blockedURLs":[],"regExBlockedURLs":[]}'); }); test('config v1->v2', () async { testCaseToV2( '{"version":1,"hTTPRealm":"","formFieldList":[{"name":"password","displayName":"KeePass password","value":"{PASSWORD}","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default"},{"name":"username","displayName":"KeePass username","value":"{USERNAME}","type":"FFTradio","id":"username","page":-1,"placeholderHandling":"Default"}],"alwaysAutoFill":false,"neverAutoFill":false,"alwaysAutoSubmit":false,"neverAutoSubmit":false,"priority":0,"altURLs":[],"hide":true,"blockHostnameOnlyMatch":false,"blockDomainOnlyMatch":false}', - '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"},{"matcherType":"Hide"}],"fields":[{"page":1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}]}'); + '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"},{"matcherType":"Hide"}],"fields":[{"page":1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}]}'); testCaseToV2( '{"version":1,"hTTPRealm":"","formFieldList":[{"name":"password","displayName":"KeePass password","value":"{PASSWORD}","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default"},{"name":"username","displayName":"KeePass username","value":"{USERNAME}","type":"FFTradio","id":"username","page":-1,"placeholderHandling":"Default"}],"alwaysAutoFill":false,"neverAutoFill":false,"alwaysAutoSubmit":false,"neverAutoSubmit":false,"priority":0,"altURLs":[],"hide":false,"blockHostnameOnlyMatch":false,"blockDomainOnlyMatch":false}', - '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}]}'); + '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}]}'); testCaseToV2( '{"version":1,"hTTPRealm":"","formFieldList":[{"name":"password","displayName":"KeePass password","value":"{PASSWORD}","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default"},{"name":"username","displayName":"KeePass username","value":"{USERNAME}","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default"}],"alwaysAutoFill":false,"neverAutoFill":false,"alwaysAutoSubmit":false,"neverAutoSubmit":false,"priority":0,"altURLs":[],"hide":false,"blockHostnameOnlyMatch":false,"blockDomainOnlyMatch":false}', - '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}]}'); + '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}]}'); testCaseToV2( '{"version":1,"hTTPRealm":"","formFieldList":[{"name":"password","displayName":"KeePass password","value":"{PASSWORD}","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default"},{"name":"username","displayName":"KeePass username","value":"{USERNAME}","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default"}],"alwaysAutoFill":false,"neverAutoFill":false,"alwaysAutoSubmit":false,"neverAutoSubmit":false,"priority":0,"altURLs":["http://test.com/1","http://test.com/2"],"regExURLs":["3","4"],"blockedURLs":["5","6"],"regExBlockedURLs":["7","8"],"hide":false,"blockHostnameOnlyMatch":false,"blockDomainOnlyMatch":false}', - '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}],"altUrls":["http://test.com/1","http://test.com/2"],"regExUrls":["3","4"],"blockedUrls":["5","6"],"regExBlockedUrls":["7","8"]}'); + '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}],"altUrls":["http://test.com/1","http://test.com/2"],"regExUrls":["3","4"],"blockedUrls":["5","6"],"regExBlockedUrls":["7","8"]}'); testCaseToV2('', - '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"UserName","uuid":"00000000-0000-0000-0000-000000000000","type":"Text","matcherConfigs":[{"matcherType":"UsernameDefaultHeuristic"}]},{"page":1,"valuePath":"Password","uuid":"00000000-0000-0000-0000-000000000000","type":"Password","matcherConfigs":[{"matcherType":"PasswordDefaultHeuristic"}]}]}'); + '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"matcherType":"UsernameDefaultHeuristic"}]},{"page":1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"matcherType":"PasswordDefaultHeuristic"}]}]}'); + + testCaseToV2( + '{"version":1,"hTTPRealm":"","formFieldList":[{"name":"password","displayName":"KeePass password","value":"{PASSWORD}","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default"},{"name":"username","displayName":"KeePass username","value":"{USERNAME}","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default"},{"displayName":"dis Name","name":"rrr","type":"FFTcheckbox","id":"www","page":3,"placeholderHandling":"Disabled","value":"KEEFOX_CHECKED_FLAG_TRUE"},{"displayName":"","name":"radname","type":"FFTradio","id":"radid","page":1,"placeholderHandling":"Default","value":"RadioValue"}],"alwaysAutoFill":false,"neverAutoFill":false,"alwaysAutoSubmit":false,"neverAutoSubmit":false,"priority":0,"altURLs":[],"hide":false,"blockHostnameOnlyMatch":false,"blockDomainOnlyMatch":false}', + '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]},{"page":3,"valuePath":".","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Toggle","matcherConfigs":[{"customMatcher":{"ids":["www"],"names":["rrr"],"types":["checkbox"]}}],"name":"dis Name","value":"KEEFOX_CHECKED_FLAG_TRUE","placeholderHandling":"Disabled"},{"page":1,"valuePath":".","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Existing","matcherConfigs":[{"customMatcher":{"ids":["radid"],"names":["radname"],"types":["radio"]}}],"name":"AAAAAAAAAAAAAAAAAAAAAA==","value":"RadioValue"}]}'); }); }); } From e5e55638f4392508437b358e241857cdcd0d36af Mon Sep 17 00:00:00 2001 From: luckyrat Date: Mon, 4 Mar 2024 20:55:10 +0000 Subject: [PATCH 12/13] wip --- .../browser_entry_settings.dart | 4 ++-- lib/src/kee_vault_model/field.dart | 7 ++++--- .../kee_vault_model/field_matcher_config.dart | 2 +- test/browser_entry_settings_test.dart | 21 +++++++++++++++---- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/src/kee_vault_model/browser_entry_settings.dart b/lib/src/kee_vault_model/browser_entry_settings.dart index 9d86aeb..a8d45fd 100644 --- a/lib/src/kee_vault_model/browser_entry_settings.dart +++ b/lib/src/kee_vault_model/browser_entry_settings.dart @@ -32,7 +32,7 @@ class BrowserEntrySettings { version: map['version'] as int? ?? 2, includeUrls: getIncludeUrls(map), excludeUrls: getExcludeUrls(map), - realm: map['hTTPRealm'] as String?, + realm: map['httpRealm'] as String?, authenticationMethods: (map['authenticationMethods'] as List?)?.cast() ?? [], @@ -168,7 +168,7 @@ class BrowserEntrySettings { return { 'version': version, 'authenticationMethods': authenticationMethods, - if (realm?.isNotEmpty ?? false) 'hTTPRealm': realm, + if (realm?.isNotEmpty ?? false) 'httpRealm': realm, 'matcherConfigs': matcherConfigs.map((x) => x.toMap()).toList(), if (fields != null) 'fields': fields?.map((x) => x.toMap()).toList(), if (behaviour != null && behaviour != BrowserAutoFillBehaviour.Default) diff --git a/lib/src/kee_vault_model/field.dart b/lib/src/kee_vault_model/field.dart index 41da765..9bead61 100644 --- a/lib/src/kee_vault_model/field.dart +++ b/lib/src/kee_vault_model/field.dart @@ -136,12 +136,13 @@ class Field { final customMatcherConfig = matcherConfigs?.firstWhereOrNull((mc) => mc.customMatcher != null); if (customMatcherConfig != null) { - htmlName = customMatcherConfig.customMatcher?.names[0] ?? ''; - htmlId = customMatcherConfig.customMatcher?.ids[0] ?? ''; + htmlName = + customMatcherConfig.customMatcher?.names.elementAtOrNull(0) ?? ''; + htmlId = customMatcherConfig.customMatcher?.ids.elementAtOrNull(0) ?? ''; if (customMatcherConfig.customMatcher?.types != null) { htmlType = Utilities.formFieldTypeFromHtmlTypeOrFieldType( - customMatcherConfig.customMatcher!.types[0], + customMatcherConfig.customMatcher!.types.elementAtOrNull(0) ?? '', type ?? FieldType.Text); } } diff --git a/lib/src/kee_vault_model/field_matcher_config.dart b/lib/src/kee_vault_model/field_matcher_config.dart index 3a919b2..d972f7e 100644 --- a/lib/src/kee_vault_model/field_matcher_config.dart +++ b/lib/src/kee_vault_model/field_matcher_config.dart @@ -20,7 +20,7 @@ class FieldMatcherConfig { return FieldMatcherConfig( matcherType: FieldMatcherType.values - .firstWhereOrNull((v) => v.name == map['matchLogic']), + .firstWhereOrNull((v) => v.name == map['matcherType']), customMatcher: map['customMatcher'] != null ? FieldMatcher.fromMap(map['customMatcher'] as Map) : null, diff --git a/test/browser_entry_settings_test.dart b/test/browser_entry_settings_test.dart index b1dfcfa..4666065 100644 --- a/test/browser_entry_settings_test.dart +++ b/test/browser_entry_settings_test.dart @@ -54,6 +54,19 @@ void main() { expect(sut, expectedResult); } + void testCaseRT(String persistedV2) { + final bes = BrowserEntrySettings.fromJson(persistedV2, + minimumMatchAccuracy: MatchAccuracy.Domain); + final configV1 = bes.convertToV1(); + final jsonV1 = configV1.toJson(); + final besV1 = BrowserEntrySettingsV1.fromJson(jsonV1, + minimumMatchAccuracy: MatchAccuracy.Domain); + final configV2 = besV1.convertToV2(MockGuidService()); + final sut = configV2.toJson(); + + expect(sut, persistedV2); + } + group('BrowserEntrySettings', () { test('config v2->v1', () async { testCase( @@ -65,8 +78,8 @@ void main() { '{"version":1,"priority":0,"hide":false,"hTTPRealm":"","formFieldList":[{"displayName":"KeePass password","name":"password","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default","value":"{PASSWORD}"},{"displayName":"KeePass username","name":"username","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default","value":"{USERNAME}"}],"alwaysAutoFill":false,"alwaysAutoSubmit":false,"neverAutoFill":false,"neverAutoSubmit":false,"blockDomainOnlyMatch":false,"blockHostnameOnlyMatch":false,"altURLs":[],"regExURLs":[],"blockedURLs":[],"regExBlockedURLs":[]}'); testCase( - '{"version":2,"altUrls":[],"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":-1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}]}', - '{"version":1,"priority":0,"hide":false,"hTTPRealm":"","formFieldList":[{"displayName":"KeePass password","name":"password","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default","value":"{PASSWORD}"},{"displayName":"KeePass username","name":"username","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default","value":"{USERNAME}"}],"alwaysAutoFill":false,"alwaysAutoSubmit":false,"neverAutoFill":false,"neverAutoSubmit":false,"blockDomainOnlyMatch":false,"blockHostnameOnlyMatch":false,"altURLs":[],"regExURLs":[],"blockedURLs":[],"regExBlockedURLs":[]}'); + '{"version":2,"httpRealm":"re","altUrls":[],"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"],"queries":[]}}]},{"page":-1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"],"queries":[]}}]}]}', + '{"version":1,"priority":0,"hide":false,"hTTPRealm":"re","formFieldList":[{"displayName":"KeePass password","name":"password","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default","value":"{PASSWORD}"},{"displayName":"KeePass username","name":"username","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default","value":"{USERNAME}"}],"alwaysAutoFill":false,"alwaysAutoSubmit":false,"neverAutoFill":false,"neverAutoSubmit":false,"blockDomainOnlyMatch":false,"blockHostnameOnlyMatch":false,"altURLs":[],"regExURLs":[],"blockedURLs":[],"regExBlockedURLs":[]}'); testCase( '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":-1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"matcherType":"UsernameDefaultHeuristic"}]},{"page":-1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"matcherType":"PasswordDefaultHeuristic"}]}]}', @@ -91,8 +104,8 @@ void main() { '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}]}'); testCaseToV2( - '{"version":1,"hTTPRealm":"","formFieldList":[{"name":"password","displayName":"KeePass password","value":"{PASSWORD}","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default"},{"name":"username","displayName":"KeePass username","value":"{USERNAME}","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default"}],"alwaysAutoFill":false,"neverAutoFill":false,"alwaysAutoSubmit":false,"neverAutoSubmit":false,"priority":0,"altURLs":[],"hide":false,"blockHostnameOnlyMatch":false,"blockDomainOnlyMatch":false}', - '{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}]}'); + '{"version":1,"hTTPRealm":"re","formFieldList":[{"name":"password","displayName":"KeePass password","value":"{PASSWORD}","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default"},{"name":"username","displayName":"KeePass username","value":"{USERNAME}","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default"}],"alwaysAutoFill":false,"neverAutoFill":false,"alwaysAutoSubmit":false,"neverAutoSubmit":false,"priority":0,"altURLs":[],"hide":false,"blockHostnameOnlyMatch":false,"blockDomainOnlyMatch":false}', + '{"version":2,"authenticationMethods":["password"],"httpRealm":"re","matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"Password","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Password","matcherConfigs":[{"customMatcher":{"ids":["password"],"names":["password"],"types":["password"]}}]},{"page":1,"valuePath":"UserName","uuid":"AAAAAAAAAAAAAAAAAAAAAA==","type":"Text","matcherConfigs":[{"customMatcher":{"ids":["username"],"names":["username"],"types":["text"]}}]}]}'); testCaseToV2( '{"version":1,"hTTPRealm":"","formFieldList":[{"name":"password","displayName":"KeePass password","value":"{PASSWORD}","type":"FFTpassword","id":"password","page":-1,"placeholderHandling":"Default"},{"name":"username","displayName":"KeePass username","value":"{USERNAME}","type":"FFTusername","id":"username","page":-1,"placeholderHandling":"Default"}],"alwaysAutoFill":false,"neverAutoFill":false,"alwaysAutoSubmit":false,"neverAutoSubmit":false,"priority":0,"altURLs":["http://test.com/1","http://test.com/2"],"regExURLs":["3","4"],"blockedURLs":["5","6"],"regExBlockedURLs":["7","8"],"hide":false,"blockHostnameOnlyMatch":false,"blockDomainOnlyMatch":false}', From 8b06fec89ef67d1beb4792c6464579584fb34e30 Mon Sep 17 00:00:00 2001 From: luckyrat Date: Wed, 6 Mar 2024 16:21:23 +0000 Subject: [PATCH 13/13] wip --- lib/src/kdbx_file.dart | 4 +-- .../browser_field_model_v1.dart | 31 +------------------ lib/src/utils/guid_service.dart | 1 - 3 files changed, 3 insertions(+), 33 deletions(-) diff --git a/lib/src/kdbx_file.dart b/lib/src/kdbx_file.dart index 7eae5aa..28fd4cc 100644 --- a/lib/src/kdbx_file.dart +++ b/lib/src/kdbx_file.dart @@ -151,9 +151,9 @@ class KdbxFile { ? header.upgradeMinor(majorVersion, minorVersion) : header.upgrade(majorVersion, minorVersion); - // Do this even we were already in v4.x since some KDBX software (including + // Do this even if we were already in v4.x since some KDBX software (including // earlier versions of Kee Vault) skipped this step when upgrading from