-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Sync skip deletes <do not merge> #2131
base: trunk
Are you sure you want to change the base?
Changes from 4 commits
e577b31
2187883
1a05096
5714380
e9a6356
6d706a7
0c53be2
832f142
b5bac00
38b11da
e949f00
2b9285b
8e3f568
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import 'package:at_persistence_secondary_server/src/log/commitlog/commit_entry.dart'; | ||
import 'package:at_persistence_secondary_server/src/log/commitlog/sync/sync_keys_fetch_strategy.dart'; | ||
|
||
class FetchAllKeysStrategy extends SyncKeysFetchStrategy { | ||
@override | ||
bool shouldIncludeEntryInSyncResponse( | ||
CommitEntry commitEntry, int commitId, String regex, | ||
{List<String>? enrolledNamespace}) { | ||
return commitEntry.commitId! >= commitId && | ||
super.shouldIncludeKeyInSyncResponse(commitEntry.atKey!, regex, | ||
enrolledNamespace: enrolledNamespace); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import 'package:at_persistence_secondary_server/src/log/commitlog/commit_entry.dart'; | ||
import 'package:at_persistence_secondary_server/src/log/commitlog/sync/sync_keys_fetch_strategy.dart'; | ||
|
||
class SkipDeleteStrategy extends SyncKeysFetchStrategy { | ||
@override | ||
bool shouldIncludeEntryInSyncResponse( | ||
CommitEntry commitEntry, int commitId, String regex, | ||
{List<String>? enrolledNamespace}) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dont we need call to super.shouldIncludeEntryInSyncResponse()? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i kept the method in base class abstract |
||
return commitEntry.commitId! >= commitId && | ||
super.shouldIncludeKeyInSyncResponse(commitEntry.atKey!, regex, | ||
enrolledNamespace: enrolledNamespace) && | ||
commitEntry.operation != CommitOp.DELETE; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import 'package:at_commons/at_commons.dart'; | ||
import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart'; | ||
import 'package:at_utils/at_utils.dart'; | ||
|
||
abstract class SyncKeysFetchStrategy { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we call it NamespaceHonoringKeyFetchStrategy? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Honoring namespace is one of the checks done. regex match and skipping delete commits are the other two checks. In the future if we have any other checks not related to namespace we may have to rename this class |
||
final _logger = AtSignLogger('SyncKeysFetchStrategy'); | ||
bool shouldIncludeEntryInSyncResponse( | ||
CommitEntry commitEntry, int commitId, String regex, | ||
{List<String>? enrolledNamespace}); | ||
|
||
bool shouldIncludeKeyInSyncResponse(String atKey, String regex, | ||
{List<String>? enrolledNamespace}) { | ||
return isNamespaceAuthorised(atKey, enrolledNamespace) && | ||
(keyMatchesRegex(atKey, regex) || alwaysIncludeInSync(atKey)); | ||
} | ||
|
||
bool isNamespaceAuthorised( | ||
String atKeyAsString, List<String>? enrolledNamespace) { | ||
// This is work-around for : https://github.com/atsign-foundation/at_server/issues/1570 | ||
if (atKeyAsString.toLowerCase() == 'configkey') { | ||
return true; | ||
} | ||
late AtKey atKey; | ||
try { | ||
atKey = AtKey.fromString(atKeyAsString); | ||
} on InvalidSyntaxException catch (_) { | ||
_logger.warning( | ||
'_isNamespaceAuthorized found an invalid key "$atKeyAsString" in the commit log. Returning false'); | ||
return false; | ||
} | ||
String? keyNamespace = atKey.namespace; | ||
// If enrolledNamespace is null or keyNamespace is null, fallback to | ||
// existing behaviour - the key is authorized for the client to receive. So return true. | ||
if (enrolledNamespace == null || | ||
enrolledNamespace.isEmpty || | ||
(keyNamespace == null || keyNamespace.isEmpty)) { | ||
return true; | ||
} | ||
if (enrolledNamespace.contains('*') || | ||
enrolledNamespace.contains(keyNamespace)) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
bool keyMatchesRegex(String atKey, String regex) { | ||
return RegExp(regex).hasMatch(atKey); | ||
} | ||
|
||
/// match keys which have to included in sync irrespective of whether regex matches | ||
/// e.g @bob:shared_key@alice, shared_key.bob@alice, public:publickey@alice, | ||
/// public:phone@alice (public key without namespace) | ||
bool alwaysIncludeInSync(String atKey) { | ||
return (atKey.contains(AtConstants.atEncryptionSharedKey) && | ||
RegexUtil.keyType(atKey, false) == KeyType.reservedKey) || | ||
atKey.startsWith(AtConstants.atEncryptionPublicKey) || | ||
(atKey.startsWith('public:') && !atKey.contains('.')); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -840,6 +840,33 @@ void main() async { | |
expect(commitEntriesMap.containsKey('public:phone.wavi@alice'), false); | ||
expect(commitEntriesMap.containsKey('public:location@alice'), true); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add another test which both uses a regex and sets skipDeletes: true There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
test( | ||
'A test to verify delete commit entries are NOT returned when skipDeletes is true', | ||
() async { | ||
var commitLogInstance = | ||
await (AtCommitLogManagerImpl.getInstance().getCommitLog('@alice')); | ||
var commitLogKeystore = commitLogInstance!.commitLogKeyStore; | ||
await commitLogKeystore.add(CommitEntry( | ||
'test_key_true_1@alice', CommitOp.UPDATE, DateTime.now())); | ||
await commitLogKeystore.add(CommitEntry( | ||
'test_key_true_2@alice', CommitOp.DELETE, DateTime.now())); | ||
await commitLogKeystore.add(CommitEntry( | ||
'test_key_true_3@alice', CommitOp.DELETE, DateTime.now())); | ||
await commitLogKeystore.add(CommitEntry( | ||
'test_key_true_4@alice', CommitOp.UPDATE, DateTime.now())); | ||
Iterator<MapEntry<String, CommitEntry>>? changes = commitLogInstance | ||
.commitLogKeyStore | ||
.getEntries(-1, skipDeletes: true); | ||
Map<String?, CommitEntry> commitEntriesMap = {}; | ||
while (changes.moveNext()) { | ||
var commitEntry = changes.current.value; | ||
commitEntriesMap[commitEntry.atKey] = commitEntry; | ||
} | ||
expect(commitEntriesMap.containsKey('test_key_true_1@alice'), true); | ||
expect(commitEntriesMap.containsKey('test_key_true_2@alice'), false); | ||
expect(commitEntriesMap.containsKey('test_key_true_3@alice'), false); | ||
expect(commitEntriesMap.containsKey('test_key_true_4@alice'), true); | ||
}); | ||
}); | ||
tearDown(() async => await tearDownFunc()); | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,7 +43,8 @@ class SyncProgressiveVerbHandler extends AbstractVerbHandler { | |
// Get entries to sync | ||
var commitEntryIterator = atCommitLog!.getEntries( | ||
int.parse(verbParams[AtConstants.fromCommitSequence]!) + 1, | ||
regex: verbParams['regex']); | ||
regex: verbParams['regex'], | ||
skipDeletes: verbParams['skipDeletes'] == 'true'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. VerbSyntax also needs this parameter, otherwise regex validation would fail. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes. those changes are in at_commons |
||
|
||
List<KeyStoreEntry> syncResponse = []; | ||
await prepareResponse(capacity, syncResponse, commitEntryIterator, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -469,6 +469,39 @@ void main() { | |
expect(syncResponse[3]['operation'], '*'); | ||
}); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add another test which both uses a regex and sets skipDeletes: true There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
test( | ||
'test to verify delete commit entries are not sent when skipDeletes is true', | ||
() async { | ||
await secondaryPersistenceStore! | ||
.getSecondaryKeyStore() | ||
?.put('test_key_1@alice', AtData()..data = 'alice'); | ||
await secondaryPersistenceStore! | ||
.getSecondaryKeyStore() | ||
?.remove('test_key_1@alice'); | ||
await secondaryPersistenceStore! | ||
.getSecondaryKeyStore() | ||
?.put('test_key_2@alice', AtData()..data = 'alice'); | ||
await secondaryPersistenceStore! | ||
.getSecondaryKeyStore() | ||
?.remove('test_key_2@alice'); | ||
var syncProgressiveVerbHandler = SyncProgressiveVerbHandler( | ||
secondaryPersistenceStore!.getSecondaryKeyStore()!); | ||
var response = Response(); | ||
var inBoundSessionId = '_6665436c-29ff-481b-8dc6-129e89199718'; | ||
var atConnection = InboundConnectionImpl(mockSocket, inBoundSessionId); | ||
atConnection.metaData.isAuthenticated = true; | ||
var syncVerbParams = HashMap<String, String>(); | ||
syncVerbParams.putIfAbsent(AtConstants.fromCommitSequence, () => '-1'); | ||
syncVerbParams.putIfAbsent('skipDeletes', () => 'true'); | ||
await syncProgressiveVerbHandler.processVerb( | ||
response, syncVerbParams, atConnection); | ||
List syncResponse = jsonDecode(response.data!); | ||
for (var entry in syncResponse) { | ||
expect(entry['atKey'] != 'test_key_1@alice', true); | ||
expect(entry['atKey'] != 'test_key_2@alice', true); | ||
} | ||
}); | ||
|
||
test( | ||
'test to verify only entries matching the regex are added to sync response', | ||
() async { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can make this a strategy.. where FetchAllKeysStrategy is to get all and SkipDeletesStrategy is to skip deletes, instead of if-else code might look more readable.