Skip to content
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

fix: option to delete local secondary when remote has been reset #1033

Open
wants to merge 40 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d9523b2
feat: introduce isSecondaryReset() in AtClientSpec
srieteja Apr 29, 2023
fc7306c
feat: implement isSecondaryReset() and deleteLocalSecondaryStorageWit…
srieteja Apr 29, 2023
aec9bda
test: added tests for the new methods
srieteja Apr 29, 2023
32b98be
Merge branch 'trunk' into reset_local_secondary
srieteja May 15, 2023
7daf11b
build: add dependency_ovveride for at_commons
srieteja May 15, 2023
435f25e
Merge remote-tracking branch 'origin/reset_local_secondary' into rese…
srieteja May 15, 2023
c71d587
Merge branch 'trunk' into reset_local_secondary
srieteja May 15, 2023
702160e
Merge branch 'trunk' into reset_local_secondary
srieteja Jun 7, 2023
f31908d
Merge branch 'trunk' into reset_local_secondary
srieteja Jul 9, 2023
e24604a
use lookup verb builder instead of PLookup verb builder
srieteja Jul 9, 2023
518dc63
fix analyzer issues
srieteja Jul 9, 2023
1f35536
run dart formatter
srieteja Jul 9, 2023
005c6e8
build: introduce at_commons dependency overrides for tests
srieteja Jul 9, 2023
191dfc0
refactor: merge methods _deleteHiveStorage() and _deleteCommitLogStorage
srieteja Jul 19, 2023
b2a8d55
Merge branch 'trunk' into reset_local_secondary
srieteja Jul 19, 2023
bf92190
refactor: isSecondaryReset() now returns Future<bool>
srieteja Jul 19, 2023
216eec8
feat: introduce deleteLocalStorageWithConsent in at_client_spec
srieteja Jul 19, 2023
6b0efb8
feat: deleteLocalStorageWithConsent now fetches hiveStoragePath and c…
srieteja Jul 19, 2023
09c1a40
Merge remote-tracking branch 'origin/reset_local_secondary' into rese…
srieteja Jul 19, 2023
eccd624
test: modify test according to new changes
srieteja Jul 19, 2023
ac018d0
test: modify test according to new changes
srieteja Jul 19, 2023
f7349c9
tests: fix unit tests
srieteja Jul 31, 2023
80e5ab9
feat: introduce and consume menthod to stop instances of commitLog an…
srieteja Jul 31, 2023
4263b82
Update pubspec.yaml
srieteja Aug 16, 2023
17e346f
Update pubspec.yaml
srieteja Aug 16, 2023
a8ec40e
refactor: introduce option to close storage manager
srieteja Aug 16, 2023
5babc71
feat: consume stop storage_manager and improve isReset check
srieteja Aug 16, 2023
c8d9306
Merge branch 'trunk' into reset_local_secondary
srieteja Aug 16, 2023
572db9f
fix: analyzer issues
srieteja Aug 17, 2023
0b6188a
Merge remote-tracking branch 'origin/reset_local_secondary' into rese…
srieteja Aug 17, 2023
bb6abc0
Merge branch 'trunk' into reset_local_secondary
srieteja Sep 4, 2023
22d5654
Merge branch 'trunk' into reset_local_secondary
srieteja Sep 4, 2023
eda3be1
Merge branch 'trunk' into reset_local_secondary
srieteja Sep 5, 2023
c143e5c
Merge branch 'trunk' into reset_local_secondary
srieteja Oct 12, 2023
cbeddcf
Merge branch 'trunk' into reset_local_secondary
srieteja Oct 31, 2023
12ea910
build: remove at_commons dependency override
srieteja Oct 31, 2023
55b4ce2
Merge remote-tracking branch 'origin/reset_local_secondary' into rese…
srieteja Oct 31, 2023
b12f479
chore: fix dart analyzer issues
srieteja Oct 31, 2023
4002f21
Merge branch 'trunk' into reset_local_secondary
srieteja Sep 11, 2024
db4e12a
fix: update usage of PLookupBuilder
srieteja Sep 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 111 additions & 3 deletions packages/at_client/lib/src/client/at_client_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class AtClientImpl implements AtClient, AtSignChangeListener {
RemoteSecondary? _remoteSecondary;
AtClientCommitLogCompaction? _atClientCommitLogCompaction;
AtClientConfig? _atClientConfig;
StorageManager? _storageManager;
static final upperCaseRegex = RegExp(r'[A-Z]');

PutRequestTransformer putRequestTransformer = PutRequestTransformer();
Expand Down Expand Up @@ -197,8 +198,8 @@ class AtClientImpl implements AtClient, AtSignChangeListener {
Future<void> _init() async {
if (_preference!.isLocalStoreRequired) {
if (_localSecondaryKeyStore == null) {
var storageManager = StorageManager(preference);
await storageManager.init(_atSign, preference!.keyStoreSecret);
_storageManager = StorageManager(preference);
await _storageManager?.init(_atSign, preference!.keyStoreSecret);
}

_localSecondary = LocalSecondary(this, keyStore: _localSecondaryKeyStore);
Expand Down Expand Up @@ -548,7 +549,9 @@ class AtClientImpl implements AtClient, AtSignChangeListener {
// will not be null.
if (verbBuilder.value.length > _preference!.maxDataSize) {
throw BufferOverFlowException(
'The length of value exceeds the maximum allowed length. Maximum buffer size is ${_preference!.maxDataSize} bytes. Found ${value.toString().length} bytes');
'The length of value exceeds the maximum allowed length.'
' Maximum buffer size is ${_preference!.maxDataSize} bytes.'
' Found ${value.toString().length} bytes');
}

Secondary secondary = SecondaryManager.getSecondary(this, verbBuilder);
Expand Down Expand Up @@ -966,6 +969,111 @@ class AtClientImpl implements AtClient, AtSignChangeListener {
}
}

@override
Future<bool> isSecondaryReset() async {
String? localPublicKey, remotePublicKey;
_logger.finer('Performing Remote Secondary reset check');
// Fetch EncryptionPublicKey from LocalSecondary
try {
localPublicKey = await getLocalSecondary()
?.getEncryptionPublicKey(getCurrentAtSign()!);
} on Exception catch (e) {
_logger.severe(
'Exception caused fetch EncryptionPublicKey from LocalSecondary.'
' Unable to complete reset check | Cause $e');
return false;
}
// Fetch EncryptionPublicKey from RemoteSecondary
AtKey encPublicKey = AtKey.fromString('publickey$getCurrentAtSign()');
try {
PLookupVerbBuilder plookup = PLookupVerbBuilder()
srieteja marked this conversation as resolved.
Show resolved Hide resolved
..atKey = encPublicKey;
remotePublicKey = await getRemoteSecondary()?.executeVerb(plookup);
} on Exception catch (e) {
_logger.info('Caught exception during public key lookup | $e');
_logger.info('Retrying fetch public key');
// try fetching the ENCRYPTION_PUB_KEY using lookup verb
// this fallback is for when reset status check is performed on an
//unauthenticated connection
LookupVerbBuilder lookup = LookupVerbBuilder()
..atKey = encPublicKey;
remotePublicKey = await getRemoteSecondary()?.executeVerb(lookup);
}

// secondary response is in format 'data:publickey'. Removing 'data:' from response
if (remotePublicKey!.contains('data:')) {
remotePublicKey = remotePublicKey.replaceFirst('data:', '');
} else {
_logger.info(
'Fetched potential invalid remote Public encryption key: $remotePublicKey');
remotePublicKey = null;
}

if (localPublicKey.isNull) {
_logger.severe('Could not fetch EncryptionPublicKey from LocalSecondary.'
' Unable to complete reset check');
return false;
} else if (remotePublicKey.isNull) {
_logger.severe('Could not fetch EncryptionPublicKey from RemoteSecondary.'
' Unable to complete reset check');
return false;
}

if (localPublicKey != remotePublicKey) {
_logger.shout(
'AtEncryptionPublicKey on local secondary and remote secondary are different.'
'This indicates remote secondary has been reset.'
'Please delete localStorage and restart the client');
_logger.info('To delete localSecondary, call '
'AtClientImpl.deleteLocalSecondaryStorageWithConsent() with user consent');
_logger.finer('EncryptionPublicKey on LocalSecondary: $localPublicKey');
_logger.finer('EncryptionPublicKey on RemoteSecondary: $remotePublicKey');
return true;
}
_logger.info('Remote Secondary is NOT reset. Status ok');
return false;
}

@override
Future<void> deleteLocalSecondaryStorageWithConsent(
{required bool userConsentToDeleteLocalStorage}) async {
_logger.shout(
'Consent to delete LocalSecondary storage received: $userConsentToDeleteLocalStorage');
if (!userConsentToDeleteLocalStorage) {
throw AtClientException.message(
'User consent not provided. Unable to delete local storage without consent');
}
await StorageManager(_preference).close(_atSign);
try {
_deleteLocalStorage(_preference!.commitLogPath!, isHiveStorage: false);
} on Exception catch (e) {
_logger.finer('Unable to delete CommitLog storage | Cause: $e');
throw AtIOException(e.toString());
}
// Delete hive storage
try {
_deleteLocalStorage(_preference!.hiveStoragePath!, isHiveStorage: true);
} on Exception catch (e) {
_logger.finer('Unable to delete hive storage | Cause: $e');
throw AtIOException(e.toString());
}
}

void _deleteLocalStorage(String storageDirectory,
{required bool isHiveStorage}) {
String storageType = isHiveStorage ? 'hive' : 'commitLog';
_logger.info('Deleting $storageType storage at path: $storageDirectory');

Directory storageDir = Directory(storageDirectory);
if (!storageDir.existsSync()) {
throw AtClientException.message(
'$storageType storage not found at path: $storageDirectory.'
' Please provide a valid $storageType storage directory path');
}
Directory(storageDirectory).deleteSync(recursive: true);
_logger.info('Successfully deleted $storageType storage');
}

// TODO v4 - remove the follow methods in version 4 of at_client package

@override
Expand Down
14 changes: 14 additions & 0 deletions packages/at_client/lib/src/client/at_client_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,20 @@ abstract class AtClient {
Function streamCompletionCallBack,
Function streamReceiveCallBack);

/// Performs a check to see if RemoteSecondary has been reset
/// returns true if the RemoteSecondary has been reset, false otherwise
Future<bool> isSecondaryReset();

/// Deletes a client's local secondary storage
///
/// [To be used when remote secondary has been reset]
///
/// Requires user consent passed as a method parameter.
///
/// Performs deletion only if consent is true
void deleteLocalSecondaryStorageWithConsent(
{required bool userConsentToDeleteLocalStorage});

/// Sets a Semi Permanent Passcode(SPP) in the secondary server key-store.
/// A Semi Permanent Passcode (SPP) is 6 character alpha-numeric for submitting
/// an enrollment request. Only the connections which have access to manage
Expand Down
37 changes: 37 additions & 0 deletions packages/at_client/lib/src/manager/storage_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,41 @@ class StorageManager {
.scheduleKeyExpireTask(preferences?.expiryCheckTimeInterval.inMinutes);
isStorageInitialized = true;
}

Future<void> stopInstances(String currentAtSign) async {
AtCommitLog? atCommitLog = await AtCommitLogManagerImpl.getInstance()
.getCommitLog(currentAtSign,
commitLogPath: preferences?.commitLogPath, enableCommitId: false);

HivePersistenceManager hiveManager =
SecondaryPersistenceStoreFactory.getInstance()
.getSecondaryPersistenceStore(currentAtSign)!
.getHivePersistenceManager()!;

SecondaryKeyStore hiveKeyStore =
SecondaryPersistenceStoreFactory.getInstance()
.getSecondaryPersistenceStore(currentAtSign)!
.getSecondaryKeyStore()!;

await (hiveKeyStore.commitLog as AtCommitLog).close();
await hiveManager.close();
await atCommitLog?.close();
}

Future<void> close(String currentAtSign) async {
var commitLogPath = preferences!.commitLogPath;

var atCommitLog = await AtCommitLogManagerImpl.getInstance().getCommitLog(
currentAtSign,
commitLogPath: commitLogPath,
enableCommitId: false);

// ignore: await_only_futures
await atCommitLog?.close;

var manager = SecondaryPersistenceStoreFactory.getInstance()
.getSecondaryPersistenceStore(currentAtSign)!
.getHivePersistenceManager()!;
await manager.close();
}
}
1 change: 1 addition & 0 deletions packages/at_client/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ dependencies:
meta: ^1.8.0
version: ^3.0.2


dev_dependencies:
lints: ^4.0.0
test: ^1.21.4
Expand Down
46 changes: 46 additions & 0 deletions packages/at_client/test/at_client_impl_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:at_client/src/response/response.dart';
import 'package:at_client/src/service/enrollment_service_impl.dart';
import 'package:at_client/src/service/notification_service_impl.dart';
import 'package:at_client/src/service/sync_service_impl.dart';
import 'package:at_commons/at_builders.dart';
import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';
Expand All @@ -29,6 +30,9 @@ class MockAtCompactionJob extends Mock implements AtCompactionJob {

class MockRemoteSecondary extends Mock implements RemoteSecondary {}

class MockSecondaryKeystore extends Mock implements SecondaryKeyStore {}


void main() {
group('A group of at client impl create tests', () {
final String atSign = '@alice';
Expand Down Expand Up @@ -260,6 +264,48 @@ void main() {
});
});

group('Group of tests verify client behaviour on remote secondary reset', () {
registerFallbackValue(MockRemoteSecondary());
registerFallbackValue(LookupVerbBuilder());

RemoteSecondary mockRemoteSecondary = MockRemoteSecondary();
SecondaryKeyStore mockKeystore = MockSecondaryKeystore();
AtClient client;

test('Verify isSecondaryReset() functionality - negative case', () async {
AtData responseObj = AtData()
..data = 'incorrectLocalEncPublicKey';
when(() => mockRemoteSecondary.executeVerb(any())).thenAnswer(
(invocation) => Future.value('data:incorrectRemoteEncPublicKey'));
when(() => mockKeystore.get(any()))
.thenAnswer((invocation) => Future.value(responseObj));

client = await AtClientImpl.create('@alice47', 'resetLocalTest',
AtClientPreference()
..isLocalStoreRequired = true,
remoteSecondary: mockRemoteSecondary,
localSecondaryKeyStore: mockKeystore);
expect(await client.isSecondaryReset(), true);
});

test('Verify isSecondaryReset() functionality - positive case', () async {
AtData responseObj = AtData()
..data = 'correctEncPublicKey';
when(() => mockRemoteSecondary.executeVerb(any()))
.thenAnswer((invocation) => Future.value('data:correctEncPublicKey'));
when(() => mockKeystore.get(any()))
.thenAnswer((invocation) => Future.value(responseObj));

client = await AtClientImpl.create('@alice47', 'resetLocalTest',
AtClientPreference()
..isLocalStoreRequired = true,
remoteSecondary: mockRemoteSecondary,
localSecondaryKeyStore: mockKeystore);
expect(await client.isSecondaryReset(), false);
});
});


group('A group of tests related to apkam/enrollments', () {
test(
'A test to verify enrollmentId is set in atClient after calling setCurrentAtSign',
Expand Down
3 changes: 2 additions & 1 deletion tests/at_functional_test/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ dependencies:
dev_dependencies:
test: ^1.24.3
lints: ^2.0.0
coverage: ^1.5.0
at_demo_data: ^1.0.1
coverage: ^1.5.0
Loading