From 04dd90c201002709020a1801f96b6d425b55c360 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Mon, 1 Apr 2024 23:35:05 -0700 Subject: [PATCH] windows/linux start --- packages/platform/storage/README.md | 26 ++- .../lib/src/local/local_storage.linux.dart | 61 ++++++ .../lib/src/local/local_storage.windows.dart | 69 +++---- .../lib/src/local/local_storage_darwin.dart | 2 +- .../lib/src/native/windows/winrt/helpers.dart | 43 +++++ .../windows/winrt/icredentialfactory.dart | 66 +++++++ .../windows/winrt/ipasswordcredential.dart | 164 ++++++++++++++++ .../native/windows/winrt/ipasswordvault.dart | 155 +++++++++++++++ .../windows/winrt/passwordcredential.dart | 60 ++++++ .../native/windows/winrt/passwordvault.dart | 53 ++++++ .../src/secure/secure_storage.windows.dart | 178 +++++++----------- packages/platform/storage/pubspec.yaml | 4 + 12 files changed, 726 insertions(+), 155 deletions(-) create mode 100644 packages/platform/storage/lib/src/local/local_storage.linux.dart create mode 100644 packages/platform/storage/lib/src/native/windows/winrt/helpers.dart create mode 100644 packages/platform/storage/lib/src/native/windows/winrt/icredentialfactory.dart create mode 100644 packages/platform/storage/lib/src/native/windows/winrt/ipasswordcredential.dart create mode 100644 packages/platform/storage/lib/src/native/windows/winrt/ipasswordvault.dart create mode 100644 packages/platform/storage/lib/src/native/windows/winrt/passwordcredential.dart create mode 100644 packages/platform/storage/lib/src/native/windows/winrt/passwordvault.dart diff --git a/packages/platform/storage/README.md b/packages/platform/storage/README.md index 660b8fb5..c877f3cf 100644 --- a/packages/platform/storage/README.md +++ b/packages/platform/storage/README.md @@ -1,3 +1,27 @@ # platform_storage -A Dart-only package for accessing platform-native storage functionality. +Provides a unified API for accessing platform-native storage functionality, such as the iOS Keychain and Android SharedPreferences. +Sync and async APIs are provided for all storage operations, where asynchronous APIs use an `Isolate` to perform the operation in +a background thread. + +> See [Web support](#Web) below for more info on how this package behaves in a browser environment. + +## `PlatformStorage` + +All implementations conform to the `PlatformStorage` interface, which provides a simple API for reading and writing key-value pairs. + +There are two flavors of `PlatformStorage` currently: `PlatformLocalStorage` and `PlatformSecureStorage`. Both are constructed with +a required `namespace` parameter and optional `scope`. The `namespace` represents the isolation boundary of the storage values, while +the `scope` is used to separate storage data between different parts of the app, + +It is recommended to use your application or bundle identifier as the `namespace`. + +### `PlatformLocalStorage` + +The local storage APIs are useful for storing non-sensitive data that should persist across app restarts and be deleted alongside the app. + +The platform implementations for `PlatformLocalStorage` are: +- **iOS/macOS**: The `UserDefaults` API with a [suite name](https://developer.apple.com/documentation/foundation/nsuserdefaults/1409957-initwithsuitename#discussion) of `namespace<.scope>`. +- **Android**: The `SharedPreferences` API with a [name](https://developer.android.com/reference/android/content/Context.html#getSharedPreferences(java.lang.String,%20int)) of `namespace<.scope>`. +- **Linux**: The `gsettings` API with a schema path of `namespace<.scope>`. +- **Windows**: The `Windows.Storage.ApplicationData.Current.LocalSettings` API with a container name of `namespace<.scope>`. diff --git a/packages/platform/storage/lib/src/local/local_storage.linux.dart b/packages/platform/storage/lib/src/local/local_storage.linux.dart new file mode 100644 index 00000000..45b8f4bb --- /dev/null +++ b/packages/platform/storage/lib/src/local/local_storage.linux.dart @@ -0,0 +1,61 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:native_synchronization/primitives.dart'; +import 'package:path/path.dart' as p; +import 'package:platform_storage/src/local/local_storage_platform.vm.dart'; +import 'package:platform_storage/src/native/windows/windows_paths.dart'; + +final class LocalStoragePlatformLinux extends LocalStoragePlatform { + LocalStoragePlatformLinux({ + required super.namespace, + super.scope, + }) : super.base(); + + final Mutex _mutex = Mutex(); + late final String _storagePath = p.joinAll([ + // The RoamingAppData folder + PathProviderWindows.getApplicationSupportPath()!, + if (scope != null) '$namespace.$scope.json' else '$namespace.json', + ]); + late final File _storage = File(_storagePath); + + Map _readData() { + if (!_storage.existsSync()) { + return {}; + } + return (jsonDecode(_storage.readAsStringSync()) as Map).cast(); + } + + void _writeData(Map data) { + if (!_storage.existsSync()) { + _storage.createSync(recursive: true); + } + _storage.writeAsStringSync(jsonEncode(data)); + } + + @override + void clear() => _mutex.runLocked(() { + if (_storage.existsSync()) { + _storage.deleteSync(); + } + }); + + @override + String? delete(String key) => _mutex.runLocked(() { + final data = _readData(); + final value = data.remove(key); + _writeData(data); + return value; + }); + + @override + String? read(String key) => _mutex.runLocked(() => _readData()[key]); + + @override + String write(String key, String value) => _mutex.runLocked(() { + final data = _readData()..[key] = value; + _writeData(data); + return value; + }); +} diff --git a/packages/platform/storage/lib/src/local/local_storage.windows.dart b/packages/platform/storage/lib/src/local/local_storage.windows.dart index 7be408ee..926c0161 100644 --- a/packages/platform/storage/lib/src/local/local_storage.windows.dart +++ b/packages/platform/storage/lib/src/local/local_storage.windows.dart @@ -1,59 +1,50 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:path/path.dart' as p; -import 'package:platform_storage/src/native/windows/windows_paths.dart'; import 'package:platform_storage/src/secure/secure_storage_platform.vm.dart'; +import 'package:windows_storage/windows_storage.dart'; -final class SecureStoragePlatformWindows extends SecureStoragePlatform { - SecureStoragePlatformWindows({ +final class LocalStoragePlatformWindows extends SecureStoragePlatform { + LocalStoragePlatformWindows({ required super.namespace, super.scope, }) : super.base(); - late final String _storagePath = p.joinAll([ - // The RoamingAppData folder - PathProviderWindows.getApplicationSupportPath()!, - if (scope != null) ...[namespace, '$scope.json'] else '$namespace.json', - ]); - late final File _storage = File(_storagePath); + final _settings = ApplicationData.current!.roamingSettings!; + late final _container = scope == null + ? _settings + : _settings.createContainer( + scope!, + ApplicationDataCreateDisposition.always, + )!; - Map _readData() { - if (!_storage.existsSync()) { - return {}; - } - return (jsonDecode(_storage.readAsStringSync()) as Map).cast(); - } - - void _writeData(Map data) { - if (!_storage.existsSync()) { - _storage.createSync(recursive: true); - } - _storage.writeAsStringSync(jsonEncode(data)); + @override + String? delete(String key) { + final stored = read(key); + _container.values!.remove(key); + return stored; } @override - void clear() { - if (_storage.existsSync()) { - _storage.deleteSync(); - } - } + String? read(String key) => _container.values!.lookup(key) as String?; @override - String? delete(String key) { - final data = _readData(); - final value = data.remove(key); - _writeData(data); + String write(String key, String value) { + _container.values!.insert(key, value); return value; } @override - String? read(String key) => _readData()[key]; + void clear() { + for (final key in _container.values!.getView().keys) { + _container.values!.remove(key); + } + if (scope case final scope?) { + _settings.deleteContainer(scope); + } + } @override - String write(String key, String value) { - final data = _readData()..[key] = value; - _writeData(data); - return value; + void close() { + _container.close(); + _settings.close(); + super.close(); } } diff --git a/packages/platform/storage/lib/src/local/local_storage_darwin.dart b/packages/platform/storage/lib/src/local/local_storage_darwin.dart index ff6562a6..d1e6bd7d 100644 --- a/packages/platform/storage/lib/src/local/local_storage_darwin.dart +++ b/packages/platform/storage/lib/src/local/local_storage_darwin.dart @@ -19,7 +19,7 @@ final class LocalStoragePlatformDarwin extends LocalStoragePlatform { /// identifier and the scope, which creates a new domain for the user defaults /// which can be shared between apps. /// - /// See the discussion here: https://developer.apple.com/documentation/foundation/nsuserdefaults/1409957-initwithsuitename?language=objc + /// See the discussion here: https://developer.apple.com/documentation/foundation/nsuserdefaults/1409957-initwithsuitename#discussion late final _suiteName = () { final String domain = scope == null ? namespace : '$namespace.$scope'; if (_bundleIdentifier != null && domain == _bundleIdentifier.toString()) { diff --git a/packages/platform/storage/lib/src/native/windows/winrt/helpers.dart b/packages/platform/storage/lib/src/native/windows/winrt/helpers.dart new file mode 100644 index 00000000..aa8210dd --- /dev/null +++ b/packages/platform/storage/lib/src/native/windows/winrt/helpers.dart @@ -0,0 +1,43 @@ +import 'dart:ffi'; + +import 'package:win32/win32.dart'; + +/// @nodoc +typedef IUnknown_AddRef_Native = Uint32 Function(VTablePointer lpVtbl); + +/// @nodoc +typedef IUnknown_Release_Native = IUnknown_AddRef_Native; + +/// @nodoc +typedef IUnknown_QueryInterface_Native = HRESULT Function( + VTablePointer lpVtbl, Pointer riid, Pointer ppvObject); + +/// @nodoc +final class IUnknownVtbl extends Struct { + external Pointer> + QueryInterface; + external Pointer> AddRef; + external Pointer> Release; +} + +/// @nodoc +typedef IInspectable_GetIids_Native = HRESULT Function(VTablePointer lpVtbl, + Pointer iidCount, Pointer> iids); + +/// @nodoc +typedef IInspectable_GetRuntimeClassName_Native = HRESULT Function( + VTablePointer lpVtbl, Pointer className); + +/// @nodoc +typedef IInspectable_GetTrustLevel_Native = HRESULT Function( + VTablePointer lpVtbl, Pointer trustLevel); + +/// @nodoc +final class IInspectableVtbl extends Struct { + external IUnknownVtbl baseVtbl; + external Pointer> GetIids; + external Pointer> + GetRuntimeClassName; + external Pointer> + GetTrustLevel; +} diff --git a/packages/platform/storage/lib/src/native/windows/winrt/icredentialfactory.dart b/packages/platform/storage/lib/src/native/windows/winrt/icredentialfactory.dart new file mode 100644 index 00000000..33b296c9 --- /dev/null +++ b/packages/platform/storage/lib/src/native/windows/winrt/icredentialfactory.dart @@ -0,0 +1,66 @@ +// Copyright (c) 2023, Dart | Windows. Please see the AUTHORS file for details. +// All rights reserved. Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// THIS FILE IS GENERATED AUTOMATICALLY AND SHOULD NOT BE EDITED DIRECTLY. + +// ignore_for_file: constant_identifier_names, non_constant_identifier_names +// ignore_for_file: unnecessary_import, unused_import + +import 'dart:async'; +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; +import 'package:platform_storage/src/native/windows/winrt/helpers.dart'; +import 'package:win32/win32.dart' + hide DocumentProperties, WinRTStringConversion; +import 'package:windows_foundation/internal.dart'; +import 'package:windows_foundation/windows_foundation.dart'; + +import 'passwordcredential.dart'; + +/// @nodoc +const IID_ICredentialFactory = '{54ef13a1-bf26-47b5-97dd-de779b7cad58}'; + +class ICredentialFactory extends IInspectable { + ICredentialFactory.fromPtr(super.ptr) + : _vtable = ptr.ref.vtable.cast<_ICredentialFactoryVtbl>().ref; + + final _ICredentialFactoryVtbl _vtable; + + factory ICredentialFactory.from(IInspectable interface) => + interface.cast(ICredentialFactory.fromPtr, IID_ICredentialFactory); + + PasswordCredential createPasswordCredential( + String resource, String userName, String password) { + final credential = calloc(); + + final hr = _vtable.CreatePasswordCredential.asFunction< + int Function(VTablePointer lpVtbl, int resource, int userName, + int password, Pointer credential)>()( + lpVtbl, + resource.toHString(), + userName.toHString(), + password.toHString(), + credential); + + if (FAILED(hr)) { + free(credential); + throwWindowsException(hr); + } + + return PasswordCredential.fromPtr(credential); + } +} + +final class _ICredentialFactoryVtbl extends Struct { + external IInspectableVtbl baseVtbl; + external Pointer< + NativeFunction< + HRESULT Function( + VTablePointer lpVtbl, + IntPtr resource, + IntPtr userName, + IntPtr password, + Pointer credential)>> CreatePasswordCredential; +} diff --git a/packages/platform/storage/lib/src/native/windows/winrt/ipasswordcredential.dart b/packages/platform/storage/lib/src/native/windows/winrt/ipasswordcredential.dart new file mode 100644 index 00000000..4a7463ec --- /dev/null +++ b/packages/platform/storage/lib/src/native/windows/winrt/ipasswordcredential.dart @@ -0,0 +1,164 @@ +// Copyright (c) 2023, Dart | Windows. Please see the AUTHORS file for details. +// All rights reserved. Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// THIS FILE IS GENERATED AUTOMATICALLY AND SHOULD NOT BE EDITED DIRECTLY. + +// ignore_for_file: constant_identifier_names, non_constant_identifier_names +// ignore_for_file: unnecessary_import, unused_import + +import 'dart:async'; +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; +import 'package:platform_storage/src/native/windows/winrt/helpers.dart'; +import 'package:win32/win32.dart' + hide DocumentProperties, WinRTStringConversion; +import 'package:windows_foundation/internal.dart'; +import 'package:windows_foundation/windows_foundation.dart'; + +/// @nodoc +const IID_IPasswordCredential = '{6ab18989-c720-41a7-a6c1-feadb36329a0}'; + +class IPasswordCredential extends IInspectable { + IPasswordCredential.fromPtr(super.ptr) + : _vtable = ptr.ref.vtable.cast<_IPasswordCredentialVtbl>().ref; + + final _IPasswordCredentialVtbl _vtable; + + factory IPasswordCredential.from(IInspectable interface) => + interface.cast(IPasswordCredential.fromPtr, IID_IPasswordCredential); + + String get resource { + final resource = calloc(); + + try { + final hr = _vtable.get_Resource.asFunction< + int Function(VTablePointer lpVtbl, Pointer resource)>()( + lpVtbl, resource); + + if (FAILED(hr)) throwWindowsException(hr); + + return resource.toDartString(); + } finally { + free(resource); + } + } + + set resource(String resource) { + final hr = _vtable.put_Resource + .asFunction()( + lpVtbl, resource.toHString()); + + if (FAILED(hr)) throwWindowsException(hr); + } + + String get userName { + final userName = calloc(); + + try { + final hr = _vtable.get_UserName.asFunction< + int Function(VTablePointer lpVtbl, Pointer userName)>()( + lpVtbl, userName); + + if (FAILED(hr)) throwWindowsException(hr); + + return userName.toDartString(); + } finally { + free(userName); + } + } + + set userName(String userName) { + final hr = _vtable.put_UserName + .asFunction()( + lpVtbl, userName.toHString()); + + if (FAILED(hr)) throwWindowsException(hr); + } + + String get password { + final password = calloc(); + + try { + final hr = _vtable.get_Password.asFunction< + int Function(VTablePointer lpVtbl, Pointer password)>()( + lpVtbl, password); + + if (FAILED(hr)) throwWindowsException(hr); + + return password.toDartString(); + } finally { + free(password); + } + } + + set password(String password) { + final hr = _vtable.put_Password + .asFunction()( + lpVtbl, password.toHString()); + + if (FAILED(hr)) throwWindowsException(hr); + } + + void retrievePassword() { + final hr = _vtable.RetrievePassword.asFunction< + int Function(VTablePointer lpVtbl)>()(lpVtbl); + + if (FAILED(hr)) throwWindowsException(hr); + } + + IPropertySet? get properties { + final props = calloc(); + + final hr = _vtable.get_Properties.asFunction< + int Function( + VTablePointer lpVtbl, Pointer props)>()(lpVtbl, props); + + if (FAILED(hr)) { + free(props); + throwWindowsException(hr); + } + + if (props.isNull) { + free(props); + return null; + } + + return IPropertySet.fromPtr(props); + } +} + +final class _IPasswordCredentialVtbl extends Struct { + external IInspectableVtbl baseVtbl; + external Pointer< + NativeFunction< + HRESULT Function(VTablePointer lpVtbl, Pointer resource)>> + get_Resource; + external Pointer< + NativeFunction< + HRESULT Function(VTablePointer lpVtbl, IntPtr resource)>> + put_Resource; + external Pointer< + NativeFunction< + HRESULT Function(VTablePointer lpVtbl, Pointer userName)>> + get_UserName; + external Pointer< + NativeFunction< + HRESULT Function(VTablePointer lpVtbl, IntPtr userName)>> + put_UserName; + external Pointer< + NativeFunction< + HRESULT Function(VTablePointer lpVtbl, Pointer password)>> + get_Password; + external Pointer< + NativeFunction< + HRESULT Function(VTablePointer lpVtbl, IntPtr password)>> + put_Password; + external Pointer> + RetrievePassword; + external Pointer< + NativeFunction< + HRESULT Function(VTablePointer lpVtbl, Pointer props)>> + get_Properties; +} diff --git a/packages/platform/storage/lib/src/native/windows/winrt/ipasswordvault.dart b/packages/platform/storage/lib/src/native/windows/winrt/ipasswordvault.dart new file mode 100644 index 00000000..daa07077 --- /dev/null +++ b/packages/platform/storage/lib/src/native/windows/winrt/ipasswordvault.dart @@ -0,0 +1,155 @@ +// Copyright (c) 2023, Dart | Windows. Please see the AUTHORS file for details. +// All rights reserved. Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// THIS FILE IS GENERATED AUTOMATICALLY AND SHOULD NOT BE EDITED DIRECTLY. + +// ignore_for_file: constant_identifier_names, non_constant_identifier_names +// ignore_for_file: unnecessary_import, unused_import + +import 'dart:async'; +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; +import 'package:platform_storage/src/native/windows/winrt/helpers.dart'; +import 'package:win32/win32.dart' + hide DocumentProperties, WinRTStringConversion; +import 'package:windows_foundation/internal.dart'; +import 'package:windows_foundation/windows_foundation.dart'; + +import 'passwordcredential.dart'; + +/// @nodoc +const IID_IPasswordVault = '{61fd2c0b-c8d4-48c1-a54f-bc5a64205af2}'; + +class IPasswordVault extends IInspectable { + IPasswordVault.fromPtr(super.ptr) + : _vtable = ptr.ref.vtable.cast<_IPasswordVaultVtbl>().ref; + + final _IPasswordVaultVtbl _vtable; + + factory IPasswordVault.from(IInspectable interface) => + interface.cast(IPasswordVault.fromPtr, IID_IPasswordVault); + + void add(PasswordCredential? credential) { + final hr = _vtable.Add.asFunction< + int Function(VTablePointer lpVtbl, VTablePointer credential)>()( + lpVtbl, credential.lpVtbl); + + if (FAILED(hr)) throwWindowsException(hr); + } + + void remove(PasswordCredential? credential) { + final hr = _vtable.Remove.asFunction< + int Function(VTablePointer lpVtbl, VTablePointer credential)>()( + lpVtbl, credential.lpVtbl); + + if (FAILED(hr)) throwWindowsException(hr); + } + + PasswordCredential? retrieve(String resource, String userName) { + final credential = calloc(); + + final hr = _vtable.Retrieve.asFunction< + int Function(VTablePointer lpVtbl, int resource, int userName, + Pointer credential)>()( + lpVtbl, resource.toHString(), userName.toHString(), credential); + + if (FAILED(hr)) { + free(credential); + throwWindowsException(hr); + } + + if (credential.isNull) { + free(credential); + return null; + } + + return PasswordCredential.fromPtr(credential); + } + + List findAllByResource(String resource) { + final credentials = calloc(); + + final hr = _vtable.FindAllByResource.asFunction< + int Function(VTablePointer lpVtbl, int resource, + Pointer credentials)>()( + lpVtbl, resource.toHString(), credentials); + + if (FAILED(hr)) { + free(credentials); + throwWindowsException(hr); + } + + return IVectorView.fromPtr(credentials, + iterableIid: '{0d224a66-bad5-5ad5-9ade-1e9f5a60fe73}', + creator: PasswordCredential.fromPtr) + .toList(); + } + + List findAllByUserName(String userName) { + final credentials = calloc(); + + final hr = _vtable.FindAllByUserName.asFunction< + int Function(VTablePointer lpVtbl, int userName, + Pointer credentials)>()( + lpVtbl, userName.toHString(), credentials); + + if (FAILED(hr)) { + free(credentials); + throwWindowsException(hr); + } + + return IVectorView.fromPtr(credentials, + iterableIid: '{0d224a66-bad5-5ad5-9ade-1e9f5a60fe73}', + creator: PasswordCredential.fromPtr) + .toList(); + } + + List retrieveAll() { + final credentials = calloc(); + + final hr = _vtable.RetrieveAll.asFunction< + int Function(VTablePointer lpVtbl, + Pointer credentials)>()(lpVtbl, credentials); + + if (FAILED(hr)) { + free(credentials); + throwWindowsException(hr); + } + + return IVectorView.fromPtr(credentials, + iterableIid: '{0d224a66-bad5-5ad5-9ade-1e9f5a60fe73}', + creator: PasswordCredential.fromPtr) + .toList(); + } +} + +final class _IPasswordVaultVtbl extends Struct { + external IInspectableVtbl baseVtbl; + external Pointer< + NativeFunction< + HRESULT Function(VTablePointer lpVtbl, VTablePointer credential)>> + Add; + external Pointer< + NativeFunction< + HRESULT Function(VTablePointer lpVtbl, VTablePointer credential)>> + Remove; + external Pointer< + NativeFunction< + HRESULT Function(VTablePointer lpVtbl, IntPtr resource, + IntPtr userName, Pointer credential)>> Retrieve; + external Pointer< + NativeFunction< + HRESULT Function(VTablePointer lpVtbl, IntPtr resource, + Pointer credentials)>> FindAllByResource; + external Pointer< + NativeFunction< + HRESULT Function(VTablePointer lpVtbl, IntPtr userName, + Pointer credentials)>> FindAllByUserName; + external Pointer< + NativeFunction< + HRESULT Function( + VTablePointer lpVtbl, Pointer credentials)>> + RetrieveAll; +} diff --git a/packages/platform/storage/lib/src/native/windows/winrt/passwordcredential.dart b/packages/platform/storage/lib/src/native/windows/winrt/passwordcredential.dart new file mode 100644 index 00000000..698155d6 --- /dev/null +++ b/packages/platform/storage/lib/src/native/windows/winrt/passwordcredential.dart @@ -0,0 +1,60 @@ +// Copyright (c) 2023, Dart | Windows. Please see the AUTHORS file for details. +// All rights reserved. Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// THIS FILE IS GENERATED AUTOMATICALLY AND SHOULD NOT BE EDITED DIRECTLY. + +// ignore_for_file: constant_identifier_names, non_constant_identifier_names +// ignore_for_file: unnecessary_import, unused_import + +import 'dart:async'; +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; +import 'package:win32/win32.dart' + hide DocumentProperties, WinRTStringConversion; +import 'package:windows_foundation/internal.dart'; +import 'package:windows_foundation/windows_foundation.dart'; + +import 'icredentialfactory.dart'; +import 'ipasswordcredential.dart'; + +/// Represents the password credential store. +class PasswordCredential extends IInspectable implements IPasswordCredential { + PasswordCredential() : super(activateClass(_className)); + PasswordCredential.fromPtr(super.ptr); + + static const _className = 'Windows.Security.Credentials.PasswordCredential'; + + factory PasswordCredential.createPasswordCredential( + String resource, String userName, String password) => + createActivationFactory( + ICredentialFactory.fromPtr, _className, IID_ICredentialFactory) + .createPasswordCredential(resource, userName, password); + + late final _iPasswordCredential = IPasswordCredential.from(this); + + @override + String get resource => _iPasswordCredential.resource; + + @override + set resource(String resource) => _iPasswordCredential.resource = resource; + + @override + String get userName => _iPasswordCredential.userName; + + @override + set userName(String userName) => _iPasswordCredential.userName = userName; + + @override + String get password => _iPasswordCredential.password; + + @override + set password(String password) => _iPasswordCredential.password = password; + + @override + void retrievePassword() => _iPasswordCredential.retrievePassword(); + + @override + IPropertySet? get properties => _iPasswordCredential.properties; +} diff --git a/packages/platform/storage/lib/src/native/windows/winrt/passwordvault.dart b/packages/platform/storage/lib/src/native/windows/winrt/passwordvault.dart new file mode 100644 index 00000000..e2a822d4 --- /dev/null +++ b/packages/platform/storage/lib/src/native/windows/winrt/passwordvault.dart @@ -0,0 +1,53 @@ +// Copyright (c) 2023, Dart | Windows. Please see the AUTHORS file for details. +// All rights reserved. Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// THIS FILE IS GENERATED AUTOMATICALLY AND SHOULD NOT BE EDITED DIRECTLY. + +// ignore_for_file: constant_identifier_names, non_constant_identifier_names +// ignore_for_file: unnecessary_import, unused_import + +import 'dart:async'; +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; +import 'package:win32/win32.dart' + hide DocumentProperties, WinRTStringConversion; +import 'package:windows_foundation/internal.dart'; +import 'package:windows_foundation/windows_foundation.dart'; + +import 'ipasswordvault.dart'; +import 'passwordcredential.dart'; + +/// Represents a Credential Locker of credentials. Lockers are specific to a +/// user. +class PasswordVault extends IInspectable implements IPasswordVault { + PasswordVault() : super(activateClass(_className)); + PasswordVault.fromPtr(super.ptr); + + static const _className = 'Windows.Security.Credentials.PasswordVault'; + + late final _iPasswordVault = IPasswordVault.from(this); + + @override + void add(PasswordCredential? credential) => _iPasswordVault.add(credential); + + @override + void remove(PasswordCredential? credential) => + _iPasswordVault.remove(credential); + + @override + PasswordCredential? retrieve(String resource, String userName) => + _iPasswordVault.retrieve(resource, userName); + + @override + List findAllByResource(String resource) => + _iPasswordVault.findAllByResource(resource); + + @override + List findAllByUserName(String userName) => + _iPasswordVault.findAllByUserName(userName); + + @override + List retrieveAll() => _iPasswordVault.retrieveAll(); +} diff --git a/packages/platform/storage/lib/src/secure/secure_storage.windows.dart b/packages/platform/storage/lib/src/secure/secure_storage.windows.dart index 998b0284..b3b2e0ff 100644 --- a/packages/platform/storage/lib/src/secure/secure_storage.windows.dart +++ b/packages/platform/storage/lib/src/secure/secure_storage.windows.dart @@ -1,12 +1,6 @@ -import 'dart:convert'; -import 'dart:ffi'; -import 'dart:io'; -import 'dart:typed_data'; - -import 'package:ffi/ffi.dart'; -import 'package:path/path.dart' as p; -import 'package:platform_storage/src/native/windows/windows_paths.dart'; -import 'package:platform_storage/src/secure/secure_storage_exception.dart'; +import 'package:platform_storage/platform_storage.dart'; +import 'package:platform_storage/src/native/windows/winrt/passwordcredential.dart'; +import 'package:platform_storage/src/native/windows/winrt/passwordvault.dart'; import 'package:platform_storage/src/secure/secure_storage_platform.vm.dart'; import 'package:win32/win32.dart'; @@ -16,127 +10,83 @@ final class SecureStoragePlatformWindows extends SecureStoragePlatform { super.scope, }) : super.base(); - late final String _storagePath = p.joinAll([ - // The LocalAppData folder - PathProviderWindows.getApplicationCachePath()!, - if (scope != null) ...[namespace, '$scope.json'] else '$namespace.json', - ]); - late final File _storage = File(_storagePath); + // https://github.com/microsoft/Windows-universal-samples/blob/bb470280e493a888d98ac367edf3b85f8c053c4f/Samples/PasswordVault/cs/Scenario2_Manage.xaml.cs#L25 + static const int _elementNotFound = 0x80070490; - Map _readData() { - if (!_storage.existsSync()) { - return {}; - } - return (jsonDecode(_storage.readAsStringSync()) as Map).cast(); - } + late final _vault = PasswordVault(); + late final _resource = scope == null ? namespace : '$namespace.$scope'; - void _writeData(Map data) { - if (!_storage.existsSync()) { - _storage.createSync(recursive: true); + @override + String? read(String key) { + try { + final credential = _vault.retrieve(_resource, key); + if (credential == null) { + return null; + } + credential.retrievePassword(); + return credential.password; + } on WindowsException catch (e, st) { + if (e.hr == _elementNotFound) { + return null; + } + Error.throwWithStackTrace( + PlatformStorageUnknownException(e.toString()), + st, + ); } - _storage.writeAsStringSync(jsonEncode(data)); } @override - void clear() { - if (_storage.existsSync()) { - _storage.deleteSync(); + String write(String key, String value) { + try { + final credential = PasswordCredential.createPasswordCredential( + _resource, + key, + value, + ); + _vault.add(credential); + return value; + } on WindowsException catch (e, st) { + Error.throwWithStackTrace( + PlatformStorageUnknownException(e.toString()), + st, + ); } } @override String? delete(String key) { - final data = _readData(); - final value = data.remove(key); - _writeData(data); - return value; - } - - @override - String? read(String key) { - final value = _readData()[key]; - if (value == null) { - return null; + try { + final credential = _vault.retrieve(_resource, key); + if (credential == null) { + return null; + } + final password = credential.password; + _vault.remove(credential); + return password; + } on WindowsException catch (e, st) { + if (e.hr == _elementNotFound) { + return null; + } + Error.throwWithStackTrace( + PlatformStorageUnknownException(e.toString()), + st, + ); } - return _decrypt(value); } @override - String write(String key, String value) { - final encrypted = _encrypt(value); - final data = _readData()..[key] = encrypted; - _writeData(data); - return value; - } - - WindowsException get _lastException => - WindowsException(HRESULT_FROM_WIN32(GetLastError())); - - /// A wrapper around [CryptProtectData] for encrypting [Uint8List]. - String _encrypt(String value) { - return using((Arena arena) { - final bytes = utf8.encode(value); - final blob = bytes.allocatePointerInArena(arena); - final dataPtr = arena() - ..ref.cbData = bytes.length - ..ref.pbData = blob; - final encryptedPtr = arena(); - CryptProtectData( - dataPtr, - nullptr, // no label - nullptr, // no added entropy - nullptr, // reserved - nullptr, // no prompt - 0, // default flag - encryptedPtr, - ); - final err = GetLastError(); - if (err != WIN32_ERROR.ERROR_SUCCESS) { - throw SecureStorageUnknownException(_lastException.toString()); - } - final encryptedBlob = encryptedPtr.ref; - final encryptedBytes = - encryptedBlob.pbData.asTypedList(encryptedBlob.cbData); - return base64Encode(encryptedBytes); - }); - } - - /// A wrapper around [CryptUnprotectData] for decrypting a blob. - String _decrypt(String value) { - return using((Arena arena) { - final data = base64Decode(value); - final blob = data.allocatePointerInArena(arena); - final dataPtr = arena() - ..ref.cbData = data.length - ..ref.pbData = blob; - final unencryptedPtr = arena(); - CryptUnprotectData( - dataPtr, - nullptr, // no label - nullptr, // no added entropy - nullptr, // reserved - nullptr, // no prompt - 0, // default flag - unencryptedPtr, - ); - final err = GetLastError(); - if (err != WIN32_ERROR.ERROR_SUCCESS) { - throw SecureStorageUnknownException(_lastException.toString()); + void clear() { + try { + final credentials = _vault.findAllByResource(_resource); + for (final credential in credentials) { + _vault.remove(credential); } - final unencryptedDataBlob = unencryptedPtr.ref; - final unencryptedBlob = unencryptedDataBlob.pbData.asTypedList( - unencryptedDataBlob.cbData, + } on WindowsException catch (e, st) { + Error.throwWithStackTrace( + PlatformStorageUnknownException(e.toString()), + st, ); - return utf8.decode(unencryptedBlob); - }); - } -} - -extension on Uint8List { - /// Alternative to [allocatePointer] from win32, which uses an [Arena]. - Pointer allocatePointerInArena(Arena arena) { - final ptr = arena(length); - ptr.asTypedList(length).setAll(0, this); - return ptr; + } } } diff --git a/packages/platform/storage/pubspec.yaml b/packages/platform/storage/pubspec.yaml index ccf5bcee..10fe4c99 100644 --- a/packages/platform/storage/pubspec.yaml +++ b/packages/platform/storage/pubspec.yaml @@ -8,14 +8,18 @@ environment: dependencies: ffi: ^2.1.2 + gsettings: ^0.2.8 jni: ^0.7.3 logging: ^1.2.0 meta: ^1.11.0 + native_synchronization: ^0.2.0 path: ^1.9.0 stack_trace: ^1.11.1 stream_channel: ^2.1.2 web: ^0.5.1 win32: ^5.4.0 + windows_security: ^0.1.0+1 + windows_storage: ^0.2.0 dev_dependencies: # TODO(dnys1): Use ^12.0.0 when released