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 79e450f..7610644 100644 --- a/lib/src/kee_vault_model/browser_entry_settings_v1.dart +++ b/lib/src/kee_vault_model/browser_entry_settings_v1.dart @@ -1,10 +1,17 @@ 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, @@ -290,4 +297,112 @@ class BrowserEntrySettingsV1 { 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/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 index 4cc19c4..dc1f7db 100644 --- a/test/browser_entry_settings_test.dart +++ b/test/browser_entry_settings_test.dart @@ -1,7 +1,8 @@ import 'package:clock/clock.dart'; -import 'package:kdbx/kdbx.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'; @@ -10,6 +11,13 @@ 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); @@ -28,13 +36,7 @@ void main() { now = DateTime.fromMillisecondsSinceEpoch(0); }); - testCase(String persistedV2, String expectedResult) async { - //final name = "" + expectedResult + persistedV2; - - // final file = await TestUtil.createSimpleFile(proceedSeconds); - // final entry = file.body.rootGroup.getAllEntries().values.first; - // entry.setCustomData("KPRPC JSON", persistedV2); - + void testCase(String persistedV2, String expectedResult) { final bes = BrowserEntrySettings.fromJson(persistedV2, minimumMatchAccuracy: MatchAccuracy.Domain); final configV1 = bes.convertToV1(); @@ -43,23 +45,42 @@ void main() { 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 { - await testCase( + 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":[]}'); - await testCase( + 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":[]}'); - await testCase( + 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":[]}'); - await testCase( + 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\":[]}}]}]}"); + }); }); }