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

feat(secure_storage): handle package uninstall/re-install on Linux and Windows #2044

Merged
merged 50 commits into from Sep 15, 2022

Conversation

Jordan-Nelson
Copy link
Contributor

@Jordan-Nelson Jordan-Nelson commented Aug 17, 2022

Issue #, if available:

Changes:

General / Utils

  • Add FileKeyValueStore for file simple key/value storage in the file system
  • Add path_provider in as dependency in amplify_secure_storage to get the application support directory
  • Tests for App re-install and utils

Linux

  • Add required gio bindings to get app ID on linux
  • Add App ID as a namespace for all keys since Linux does not namespace secrets by app (unlike iOS, Android, MacOS)
  • Add accessGroup to linux config as an optional override to App ID to allow for sharing of data across apps
  • Add initialization logic in flutter to clear all data for a given scope upon first initialization of that scope, set flags in file storage to keep track of scopes using FileKeyValueStore

Windows

  • Pass application support dir from flutter package to dart only package
  • Add config option for customer to optionally override the storage location
  • Add Win32 Data Protection API bindings
  • Use the Data Protection APIs to encrypt/decrypt data and FileKeyValueStore to store the data

Motivation:

If a user uninstalls and re-installs an app installed via snap or the Windows app store, the expectation would be that data from their previous install would not persist.

Implementation:

  • Linux: Data is cleared immediately after the app is launched for the first time (after a re-install). Flags are set in application file storage to track the apps first launch post re-install.
  • Windows: Encrypted data is now stored in application file storage. This removes the need to clear the data as it is removed automatically on app uninstall.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@Jordan-Nelson Jordan-Nelson requested a review from a team as a code owner August 17, 2022 20:10
directory: appDirectory,
fileName: 'amplify_secure_storage_scopes.json',
);
fileStore.writeAll({});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this use the in-mem fs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be updated to use in-mem. AmplifySecureStorageDart would also have to be updated to use in-mem for these tests in that case.

This is more of an integration tests since it isn't mocking libsecret / secret service, so I think it is fine as is.

@codecov-commenter
Copy link

codecov-commenter commented Aug 17, 2022

Codecov Report

Merging #2044 (829e82b) into next (60210e7) will not change coverage.
The diff coverage is n/a.

@@           Coverage Diff           @@
##             next    #2044   +/-   ##
=======================================
  Coverage   42.86%   42.86%           
=======================================
  Files         114      114           
  Lines        7502     7502           
=======================================
  Hits         3216     3216           
  Misses       4286     4286           
Flag Coverage Δ
android-unit-tests ∅ <ø> (∅)
flutter-unit-tests 25.08% <ø> (ø)
ios-unit-tests 89.06% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

@Jordan-Nelson Jordan-Nelson changed the title feat(secure_storage): clear keys after reinstall on linux feat(secure_storage): handle package uninstall/re-install on Linux and Windows Sep 1, 2022
@@ -5,6 +5,8 @@
import FlutterMacOS
import Foundation

import path_provider_macos
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need path_provider_macos, but it is brought in as part of path_provider. We could bring in path_provider_linux and path_provider_windows. They are unlisted packages though. Not sure if we want to bring in unlisted packages or not.

// Android is already run on a background thread via pigeon
skip: !zIsWeb && Platform.isAndroid,
);
group('Secure Storage', () => runTests(storageFactory));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is just clean up. It isn't related to this PR. AmplifySecureStorage uses AmplifySecureStorageWorker by default, so these tests were redundant.

@@ -148,31 +155,26 @@ void runStandardTests(
});
});

group('write', () {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: These tests were duplicates.

@haverchuck haverchuck self-requested a review September 8, 2022 00:08
Copy link
Contributor

@dnys1 dnys1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comments. Looks good, though. Thanks!

if (directory != null) {
return FileKeyValueStore(fileName: _fileName, path: directory);
}
return FileKeyValueStore(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the use case for this? Just testing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, tests/examples. Could also be a reasonable fallback if/when we support non-flutter use cases for amplify.

///
/// Used to store encrypted data on Windows. If no value
/// is provided, data will be stored in memory on Windows.
final String? applicationDirectory;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be part of the config?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. This was unnecessary. I had added storagePath to WindowsSecureStorageOptions already. I was passing both to the _dart package, with storagePath taking priority over applicationDirectory. This made sense when I was using applicationDirectory in the _dart package on linux as well to read/write scope names, but with that happening on the flutter side, passing two values for the same thing didn't make sense.

Updated 👍

Future<void> _initializeScope(String? accessGroup) async {
// if accessGroup is set, do not clear data on initialization
// since the data can be shared across applications.
if (accessGroup != null) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we reformat this to not call _initalizeScope if accessGroup is set since the only reason this method has a parameter is to check if it's not null which is kind of confusing.

}
final Uint8List encrypted;
try {
encrypted = Uint8List.fromList(List<int>.from(rawData as List));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this makes two copies of the list (List.from, then Uint8List.fromList). I would do Uint8List.fromList((rawData as List).cast()); to avoid that. Is the only failure because of the cast? It's probably more reasonable to do an is check if that's the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to use .cast() over List.from.

I kept the try/catch block. rawData could not be a list, or it could be a List that contains data other than integers. Either of those would be problematic. I could do an is check to confirm it is a list, but I would still need to cast to get it to List rather than List.

expect(value2, 'test_update');
});
group('read/write/delete can handle key value pairs of varying length', () {
for (var entry in keyValuePairs.entries) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (var entry in keyValuePairs.entries) {
for (final entry in keyValuePairs.entries) {

Don't necessarily need to change since I don't care as much about the style of test code but I thought this was a lint

@Jordan-Nelson Jordan-Nelson merged commit 3632e36 into aws-amplify:next Sep 15, 2022
@Jordan-Nelson Jordan-Nelson deleted the feat/package-uninstall branch September 15, 2022 13:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants