From c04a5d936be9a1dbb1b7499085a9917dab09bf56 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 15:21:37 -0800 Subject: [PATCH 01/23] feat(core): Remaining secure storage bindings --- .gitmodules | 6 ++ packages/celest_core/ffigen.glib.yaml | 38 ++++++++++ packages/celest_core/ffigen.libsecret.yaml | 74 +++++++++++++++++++ .../secure_storage/secure_storage.linux.dart | 0 packages/celest_core/third_party/glib | 1 + packages/celest_core/third_party/libsecret | 1 + packages/celest_core/tool/ffigen.sh | 9 +++ 7 files changed, 129 insertions(+) create mode 100644 .gitmodules create mode 100644 packages/celest_core/ffigen.glib.yaml create mode 100644 packages/celest_core/ffigen.libsecret.yaml create mode 100644 packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart create mode 160000 packages/celest_core/third_party/glib create mode 160000 packages/celest_core/third_party/libsecret diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..d272823b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "packages/celest_core/third_party/glib"] + path = packages/celest_core/third_party/glib + url = https://github.com/GNOME/glib.git +[submodule "packages/celest_core/third_party/libsecret"] + path = packages/celest_core/third_party/libsecret + url = https://github.com/GNOME/libsecret.git diff --git a/packages/celest_core/ffigen.glib.yaml b/packages/celest_core/ffigen.glib.yaml new file mode 100644 index 00000000..d9440768 --- /dev/null +++ b/packages/celest_core/ffigen.glib.yaml @@ -0,0 +1,38 @@ +name: Glib +description: | + Bindings for glib on Linux. + + Regenerate bindings with `dart run ffigen --config=ffigen.glib.yaml`. +language: c +output: lib/src/native/linux/glib.ffi.dart +compiler-opts: + - -I./third_party/glib/ + - -I./third_party/glib/glib/ +headers: + entry-points: + - third_party/glib/glib/glib.h + - third_party/glib/glib/glib-object.h + - third_party/glib/gio/gio.h +preamble: | + // ignore_for_file: type=lint + // ignore_for_file: return_of_invalid_type + // ignore_for_file: unnecessary_non_null_assertion +comments: + style: any + length: full + +exclude-all-by-default: true +functions: + include: + - g_hash_table_new + - g_hash_table_insert + - g_hash_table_destroy +structs: + include: + - _GError + - _GCancellable + - _GObject + rename: + "_GError": GError + "_GCancellable": GCancellable + "_GObject": GObject diff --git a/packages/celest_core/ffigen.libsecret.yaml b/packages/celest_core/ffigen.libsecret.yaml new file mode 100644 index 00000000..a26a60cb --- /dev/null +++ b/packages/celest_core/ffigen.libsecret.yaml @@ -0,0 +1,74 @@ +name: Libsecret +description: | + Bindings for Libsecret on Linux. + + Regenerate bindings with `dart run ffigen --config=ffigen.libsecret.yaml`. +language: c +output: lib/src/native/linux/libsecret.ffi.dart +compiler-opts: + - -I./third_party/glib/ + - -I./third_party/glib/glib/ +headers: + entry-points: + - third_party/libsecret/libsecret/secret.h + - third_party/libsecret/libsecret/secret-schema.h + - third_party/libsecret/libsecret/secret-password.h +preamble: | + // ignore_for_file: type=lint + // ignore_for_file: return_of_invalid_type + // ignore_for_file: unnecessary_non_null_assertion +library-imports: + glib: package:celest_core/src/native/linux/glib.ffi.dart +comments: + style: any + length: full + +exclude-all-by-default: true +functions: + include: + - secret_password_storev_sync + - secret_password_lookupv_sync + - secret_password_clearv_sync + - secret_password_free +structs: + include: + - SecretSchema + - SecretSchemaAttribute +enums: + include: + - SecretSchemaAttributeType + - SecretSchemaFlags +macros: + include: + - SECRET_COLLECTION_DEFAULT +type-map: + typedefs: + GHashTable: + lib: glib + c-type: GHashTable + dart-type: GHashTable + GError: + lib: glib + c-type: GError + dart-type: GError + GCancellable: + lib: glib + c-type: GCancellable + dart-type: GCancellable + gpointer: + lib: glib + c-type: gpointer + dart-type: gpointer + gboolean: + lib: glib + c-type: gboolean + dart-type: int + gchar: + lib: pkg_ffi + c-type: Utf8 + dart-type: Char + gint: + lib: glib + c-type: gint + dart-type: int + diff --git a/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart b/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart new file mode 100644 index 00000000..e69de29b diff --git a/packages/celest_core/third_party/glib b/packages/celest_core/third_party/glib new file mode 160000 index 00000000..095142f5 --- /dev/null +++ b/packages/celest_core/third_party/glib @@ -0,0 +1 @@ +Subproject commit 095142f5042038d7ba3e80176f3a0a014afeb40d diff --git a/packages/celest_core/third_party/libsecret b/packages/celest_core/third_party/libsecret new file mode 160000 index 00000000..a86c93d6 --- /dev/null +++ b/packages/celest_core/third_party/libsecret @@ -0,0 +1 @@ +Subproject commit a86c93d6f9ec4b4476eff1bbb4fe01ef50f03430 diff --git a/packages/celest_core/tool/ffigen.sh b/packages/celest_core/tool/ffigen.sh index 10438207..c3169fba 100755 --- a/packages/celest_core/tool/ffigen.sh +++ b/packages/celest_core/tool/ffigen.sh @@ -8,9 +8,18 @@ flutter pub get flutter build apk popd +echo "Checking out submodules..." +git submodule update --init + echo "Generating FFI bindings..." dart run ffigen --config=ffigen.core_foundation.yaml dart run ffigen --config=ffigen.security.yaml +if "$(uname)" == "Darwin"; then + echo "Skipping Linux bindings." >&2 +else + dart run ffigen --config=ffigen.glib.yaml + dart run ffigen --config=ffigen.libsecret.yaml +fi echo "Generating JNI bindings..." dart run jnigen --config=jnigen.yaml From 8211dd9b4fa1cb6518e6c445c6735dc1b97d1156 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 15:51:25 -0800 Subject: [PATCH 02/23] remove submodules --- .gitmodules | 6 ------ packages/celest_core/third_party/glib | 1 - packages/celest_core/third_party/libsecret | 1 - 3 files changed, 8 deletions(-) delete mode 100644 .gitmodules delete mode 160000 packages/celest_core/third_party/glib delete mode 160000 packages/celest_core/third_party/libsecret diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index d272823b..00000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "packages/celest_core/third_party/glib"] - path = packages/celest_core/third_party/glib - url = https://github.com/GNOME/glib.git -[submodule "packages/celest_core/third_party/libsecret"] - path = packages/celest_core/third_party/libsecret - url = https://github.com/GNOME/libsecret.git diff --git a/packages/celest_core/third_party/glib b/packages/celest_core/third_party/glib deleted file mode 160000 index 095142f5..00000000 --- a/packages/celest_core/third_party/glib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 095142f5042038d7ba3e80176f3a0a014afeb40d diff --git a/packages/celest_core/third_party/libsecret b/packages/celest_core/third_party/libsecret deleted file mode 160000 index a86c93d6..00000000 --- a/packages/celest_core/third_party/libsecret +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a86c93d6f9ec4b4476eff1bbb4fe01ef50f03430 From 785d7c645e7104b456bd0a9453ba6c4de9d16393 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 15:53:04 -0800 Subject: [PATCH 03/23] Add Linux bindings --- packages/celest_core/ffigen.glib.yaml | 16 +- packages/celest_core/ffigen.libsecret.yaml | 7 +- .../lib/src/native/linux/glib.ffi.dart | 169 +++++++++++++++ .../lib/src/native/linux/libsecret.ffi.dart | 194 ++++++++++++++++++ packages/celest_core/tool/ffigen.sh | 12 +- 5 files changed, 380 insertions(+), 18 deletions(-) create mode 100644 packages/celest_core/lib/src/native/linux/glib.ffi.dart create mode 100644 packages/celest_core/lib/src/native/linux/libsecret.ffi.dart diff --git a/packages/celest_core/ffigen.glib.yaml b/packages/celest_core/ffigen.glib.yaml index d9440768..308d2f37 100644 --- a/packages/celest_core/ffigen.glib.yaml +++ b/packages/celest_core/ffigen.glib.yaml @@ -5,14 +5,11 @@ description: | Regenerate bindings with `dart run ffigen --config=ffigen.glib.yaml`. language: c output: lib/src/native/linux/glib.ffi.dart -compiler-opts: - - -I./third_party/glib/ - - -I./third_party/glib/glib/ headers: entry-points: - - third_party/glib/glib/glib.h - - third_party/glib/glib/glib-object.h - - third_party/glib/gio/gio.h + - /usr/include/glib-2.0/glib.h + - /usr/include/glib-2.0/glib-object.h + - /usr/include/glib-2.0/gio/gio.h preamble: | // ignore_for_file: type=lint // ignore_for_file: return_of_invalid_type @@ -22,6 +19,11 @@ comments: length: full exclude-all-by-default: true +typedefs: + include: + - gboolean + - gint + - gpointer functions: include: - g_hash_table_new @@ -30,9 +32,11 @@ functions: structs: include: - _GError + - _GHashTable - _GCancellable - _GObject rename: "_GError": GError + "_GHashTable": GHashTable "_GCancellable": GCancellable "_GObject": GObject diff --git a/packages/celest_core/ffigen.libsecret.yaml b/packages/celest_core/ffigen.libsecret.yaml index a26a60cb..d3ebea78 100644 --- a/packages/celest_core/ffigen.libsecret.yaml +++ b/packages/celest_core/ffigen.libsecret.yaml @@ -5,14 +5,9 @@ description: | Regenerate bindings with `dart run ffigen --config=ffigen.libsecret.yaml`. language: c output: lib/src/native/linux/libsecret.ffi.dart -compiler-opts: - - -I./third_party/glib/ - - -I./third_party/glib/glib/ headers: entry-points: - - third_party/libsecret/libsecret/secret.h - - third_party/libsecret/libsecret/secret-schema.h - - third_party/libsecret/libsecret/secret-password.h + - /usr/include/libsecret-1/libsecret/secret.h preamble: | // ignore_for_file: type=lint // ignore_for_file: return_of_invalid_type diff --git a/packages/celest_core/lib/src/native/linux/glib.ffi.dart b/packages/celest_core/lib/src/native/linux/glib.ffi.dart new file mode 100644 index 00000000..f7a39af7 --- /dev/null +++ b/packages/celest_core/lib/src/native/linux/glib.ffi.dart @@ -0,0 +1,169 @@ +// ignore_for_file: type=lint +// ignore_for_file: return_of_invalid_type +// ignore_for_file: unnecessary_non_null_assertion + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +import 'dart:ffi' as ffi; + +/// Bindings for glib on Linux. +/// +/// Regenerate bindings with `dart run ffigen --config=ffigen.glib.yaml`. +/// +class Glib { + /// Holds the symbol lookup function. + final ffi.Pointer Function(String symbolName) + _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + Glib(ffi.DynamicLibrary dynamicLibrary) : _lookup = dynamicLibrary.lookup; + + /// The symbols are looked up with [lookup]. + Glib.fromLookup( + ffi.Pointer Function(String symbolName) + lookup) + : _lookup = lookup; + + ffi.Pointer g_hash_table_new( + ffi.Pointer< + ffi.NativeFunction)>> + hash_func, + ffi.Pointer< + ffi.NativeFunction< + gboolean Function( + ffi.Pointer, ffi.Pointer)>> + key_equal_func, + ) { + return _g_hash_table_new( + hash_func, + key_equal_func, + ); + } + + late final _g_hash_table_newPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer< + ffi.NativeFunction< + ffi.UnsignedInt Function(ffi.Pointer)>>, + ffi.Pointer< + ffi.NativeFunction< + gboolean Function(ffi.Pointer, + ffi.Pointer)>>)>>('g_hash_table_new'); + late final _g_hash_table_new = _g_hash_table_newPtr.asFunction< + ffi.Pointer Function( + ffi.Pointer< + ffi + .NativeFunction)>>, + ffi.Pointer< + ffi.NativeFunction< + gboolean Function( + ffi.Pointer, ffi.Pointer)>>)>(); + + void g_hash_table_destroy( + ffi.Pointer hash_table, + ) { + return _g_hash_table_destroy( + hash_table, + ); + } + + late final _g_hash_table_destroyPtr = + _lookup)>>( + 'g_hash_table_destroy'); + late final _g_hash_table_destroy = _g_hash_table_destroyPtr + .asFunction)>(); + + int g_hash_table_insert( + ffi.Pointer hash_table, + gpointer key, + gpointer value, + ) { + return _g_hash_table_insert( + hash_table, + key, + value, + ); + } + + late final _g_hash_table_insertPtr = _lookup< + ffi.NativeFunction< + gboolean Function(ffi.Pointer, gpointer, + gpointer)>>('g_hash_table_insert'); + late final _g_hash_table_insert = _g_hash_table_insertPtr + .asFunction, gpointer, gpointer)>(); +} + +final class GError extends ffi.Struct { + @ffi.UnsignedInt() + external int domain; + + @gint() + external int code; + + external ffi.Pointer message; +} + +typedef gint = ffi.Int; +typedef Dartgint = int; + +final class GHashTable extends ffi.Opaque {} + +typedef gboolean = gint; +typedef gpointer = ffi.Pointer; + +/// GObject: +/// +/// The base object type. +/// +/// All the fields in the `GObject` structure are private to the implementation +/// and should never be accessed directly. +/// +/// Since GLib 2.72, all #GObjects are guaranteed to be aligned to at least the +/// alignment of the largest basic GLib type (typically this is #guint64 or +/// #gdouble). If you need larger alignment for an element in a #GObject, you +/// should allocate it on the heap (aligned), or arrange for your #GObject to be +/// appropriately padded. This guarantee applies to the #GObject (or derived) +/// struct, the #GObjectClass (or derived) struct, and any private data allocated +/// by G_ADD_PRIVATE(). +final class GObject extends ffi.Struct { + external _GTypeInstance g_type_instance; + + /// (atomic) + @ffi.UnsignedInt() + external int ref_count; + + external ffi.Pointer<_GData> qdata; +} + +/// GTypeInstance: +/// +/// An opaque structure used as the base of all type instances. +final class _GTypeInstance extends ffi.Struct { + /// < private > + external ffi.Pointer<_GTypeClass> g_class; +} + +/// Basic Type Structures +/// / +/// /** +/// GTypeClass: +/// +/// An opaque structure used as the base of all classes. +final class _GTypeClass extends ffi.Struct { + /// < private > + @ffi.UnsignedLong() + external int g_type; +} + +final class _GData extends ffi.Opaque {} + +final class GCancellable extends ffi.Struct { + external GObject parent_instance; + + /// < private > + external ffi.Pointer<_GCancellablePrivate> priv; +} + +final class _GCancellablePrivate extends ffi.Opaque {} diff --git a/packages/celest_core/lib/src/native/linux/libsecret.ffi.dart b/packages/celest_core/lib/src/native/linux/libsecret.ffi.dart new file mode 100644 index 00000000..a585cb67 --- /dev/null +++ b/packages/celest_core/lib/src/native/linux/libsecret.ffi.dart @@ -0,0 +1,194 @@ +// ignore_for_file: type=lint +// ignore_for_file: return_of_invalid_type +// ignore_for_file: unnecessary_non_null_assertion + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +import 'dart:ffi' as ffi; +import 'package:celest_core/src/native/linux/glib.ffi.dart' as glib; +import 'package:ffi/ffi.dart' as pkg_ffi; + +/// Bindings for Libsecret on Linux. +/// +/// Regenerate bindings with `dart run ffigen --config=ffigen.libsecret.yaml`. +/// +class Libsecret { + /// Holds the symbol lookup function. + final ffi.Pointer Function(String symbolName) + _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + Libsecret(ffi.DynamicLibrary dynamicLibrary) + : _lookup = dynamicLibrary.lookup; + + /// The symbols are looked up with [lookup]. + Libsecret.fromLookup( + ffi.Pointer Function(String symbolName) + lookup) + : _lookup = lookup; + + int secret_password_storev_sync( + ffi.Pointer schema, + ffi.Pointer attributes, + ffi.Pointer collection, + ffi.Pointer label, + ffi.Pointer password, + ffi.Pointer cancellable, + ffi.Pointer> error, + ) { + return _secret_password_storev_sync( + schema, + attributes, + collection, + label, + password, + cancellable, + error, + ); + } + + late final _secret_password_storev_syncPtr = _lookup< + ffi.NativeFunction< + glib.gboolean Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>)>>( + 'secret_password_storev_sync'); + late final _secret_password_storev_sync = + _secret_password_storev_syncPtr.asFunction< + int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>)>(); + + ffi.Pointer secret_password_lookupv_sync( + ffi.Pointer schema, + ffi.Pointer attributes, + ffi.Pointer cancellable, + ffi.Pointer> error, + ) { + return _secret_password_lookupv_sync( + schema, + attributes, + cancellable, + error, + ); + } + + late final _secret_password_lookupv_syncPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>)>>( + 'secret_password_lookupv_sync'); + late final _secret_password_lookupv_sync = + _secret_password_lookupv_syncPtr.asFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>)>(); + + int secret_password_clearv_sync( + ffi.Pointer schema, + ffi.Pointer attributes, + ffi.Pointer cancellable, + ffi.Pointer> error, + ) { + return _secret_password_clearv_sync( + schema, + attributes, + cancellable, + error, + ); + } + + late final _secret_password_clearv_syncPtr = _lookup< + ffi.NativeFunction< + glib.gboolean Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>)>>( + 'secret_password_clearv_sync'); + late final _secret_password_clearv_sync = + _secret_password_clearv_syncPtr.asFunction< + int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>)>(); + + void secret_password_free( + ffi.Pointer password, + ) { + return _secret_password_free( + password, + ); + } + + late final _secret_password_freePtr = + _lookup)>>( + 'secret_password_free'); + late final _secret_password_free = _secret_password_freePtr + .asFunction)>(); +} + +abstract class SecretSchemaAttributeType { + static const int SECRET_SCHEMA_ATTRIBUTE_STRING = 0; + static const int SECRET_SCHEMA_ATTRIBUTE_INTEGER = 1; + static const int SECRET_SCHEMA_ATTRIBUTE_BOOLEAN = 2; +} + +final class SecretSchemaAttribute extends ffi.Struct { + external ffi.Pointer name; + + @ffi.Int32() + external int type; +} + +abstract class SecretSchemaFlags { + static const int SECRET_SCHEMA_NONE = 0; + static const int SECRET_SCHEMA_DONT_MATCH_NAME = 2; +} + +final class SecretSchema extends ffi.Struct { + external ffi.Pointer name; + + @ffi.Int32() + external int flags; + + @ffi.Array.multi([32]) + external ffi.Array attributes; + + /// + @glib.gint() + external int reserved; + + external glib.gpointer reserved1; + + external glib.gpointer reserved2; + + external glib.gpointer reserved3; + + external glib.gpointer reserved4; + + external glib.gpointer reserved5; + + external glib.gpointer reserved6; + + external glib.gpointer reserved7; +} + +const String SECRET_COLLECTION_DEFAULT = 'default'; diff --git a/packages/celest_core/tool/ffigen.sh b/packages/celest_core/tool/ffigen.sh index c3169fba..dee1ce1a 100755 --- a/packages/celest_core/tool/ffigen.sh +++ b/packages/celest_core/tool/ffigen.sh @@ -8,17 +8,17 @@ flutter pub get flutter build apk popd -echo "Checking out submodules..." -git submodule update --init - echo "Generating FFI bindings..." dart run ffigen --config=ffigen.core_foundation.yaml dart run ffigen --config=ffigen.security.yaml -if "$(uname)" == "Darwin"; then +if !command -v pkg-config >&/dev/null; then echo "Skipping Linux bindings." >&2 else - dart run ffigen --config=ffigen.glib.yaml - dart run ffigen --config=ffigen.libsecret.yaml + GLIB_OPTS=$(pkg-config --cflags-only-I glib-2.0) + dart run ffigen --config=ffigen.glib.yaml --compiler-opts="$GLIB_OPTS" + + LIBSECRET_OPTS=$(pkg-config --cflags-only-I libsecret-1) + dart run ffigen --config=ffigen.libsecret.yaml --compiler-opts="$LIBSECRET_OPTS" fi echo "Generating JNI bindings..." From e2a842496fb7ac0e374b8414f95daeae965149fd Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 15:57:11 -0800 Subject: [PATCH 04/23] Add missing functions --- packages/celest_core/ffigen.glib.yaml | 2 ++ .../lib/src/native/linux/glib.ffi.dart | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/packages/celest_core/ffigen.glib.yaml b/packages/celest_core/ffigen.glib.yaml index 308d2f37..47e17e05 100644 --- a/packages/celest_core/ffigen.glib.yaml +++ b/packages/celest_core/ffigen.glib.yaml @@ -29,6 +29,8 @@ functions: - g_hash_table_new - g_hash_table_insert - g_hash_table_destroy + - g_application_get_default + - g_application_get_application_id structs: include: - _GError diff --git a/packages/celest_core/lib/src/native/linux/glib.ffi.dart b/packages/celest_core/lib/src/native/linux/glib.ffi.dart index f7a39af7..0033492c 100644 --- a/packages/celest_core/lib/src/native/linux/glib.ffi.dart +++ b/packages/celest_core/lib/src/native/linux/glib.ffi.dart @@ -93,6 +93,32 @@ class Glib { gpointer)>>('g_hash_table_insert'); late final _g_hash_table_insert = _g_hash_table_insertPtr .asFunction, gpointer, gpointer)>(); + + ffi.Pointer g_application_get_application_id( + ffi.Pointer<_GApplication> application, + ) { + return _g_application_get_application_id( + application, + ); + } + + late final _g_application_get_application_idPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer<_GApplication>)>>('g_application_get_application_id'); + late final _g_application_get_application_id = + _g_application_get_application_idPtr.asFunction< + ffi.Pointer Function(ffi.Pointer<_GApplication>)>(); + + ffi.Pointer<_GApplication> g_application_get_default() { + return _g_application_get_default(); + } + + late final _g_application_get_defaultPtr = + _lookup Function()>>( + 'g_application_get_default'); + late final _g_application_get_default = _g_application_get_defaultPtr + .asFunction Function()>(); } final class GError extends ffi.Struct { @@ -167,3 +193,12 @@ final class GCancellable extends ffi.Struct { } final class _GCancellablePrivate extends ffi.Opaque {} + +final class _GApplication extends ffi.Struct { + /// < private > + external GObject parent_instance; + + external ffi.Pointer<_GApplicationPrivate> priv; +} + +final class _GApplicationPrivate extends ffi.Opaque {} From dea2e314f56c5c1ce3b2c2e47222519a609a614e Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 16:14:22 -0800 Subject: [PATCH 05/23] Add Linux impl --- .../secure_storage/secure_storage.linux.dart | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart b/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart index e69de29b..dd327a8f 100644 --- a/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart +++ b/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart @@ -0,0 +1,120 @@ +import 'dart:ffi'; +import 'dart:io'; + +import 'package:celest_core/src/native/linux/glib.ffi.dart'; +import 'package:celest_core/src/native/linux/libsecret.ffi.dart'; +import 'package:celest_core/src/secure_storage/secure_storage_platform.vm.dart'; +import 'package:ffi/ffi.dart'; + +final class SecureStoragePlatformLinux extends SecureStoragePlatform { + SecureStoragePlatformLinux({ + required this.scope, + }) : super.base(); + + final String scope; + + final _glibDylib = DynamicLibrary.open('libglib-2.0.so.0'); + late final _glib = Glib(_glibDylib); + + final _libSecretDylib = DynamicLibrary.open('libsecret-1.so.0'); + late final _libSecret = Libsecret(_libSecretDylib); + + late final _gStrHashPointer = + _glibDylib.lookup)>>( + 'g_str_hash'); + + late final String _appName = () { + final application = _glib.g_application_get_default(); + if (application == nullptr) { + return File('/proc/self/exe').resolveSymbolicLinksSync(); + } + return _glib + .g_application_get_application_id(application) + .cast() + .toDartString(); + }(); + + String _labelFor(String key) => '$scope/$key'; + Pointer _schemaFor(Arena arena) => arena() + ..ref.name = _appName.toNativeUtf8(allocator: arena) + ..ref.flags = SecretSchemaFlags.SECRET_SCHEMA_NONE + ..ref.attributes[0].name = 'key'.toNativeUtf8(allocator: arena) + ..ref.attributes[0].type = + SecretSchemaAttributeType.SECRET_SCHEMA_ATTRIBUTE_STRING; + + Pointer _attributes({ + required String key, + required Arena arena, + }) { + final hashTable = _glib.g_hash_table_new(_gStrHashPointer, nullptr); + _glib.g_hash_table_insert( + hashTable, + 'key'.toNativeUtf8(allocator: arena).cast(), + key.toNativeUtf8(allocator: arena).cast(), + ); + arena.onReleaseAll(() => _glib.g_hash_table_destroy(hashTable)); + return hashTable; + } + + @override + void clear() => using((arena) { + final schema = _schemaFor(arena); + final attributes = _attributes(key: '', arena: arena); + _libSecret.secret_password_clearv_sync( + schema, + attributes, + nullptr, + nullptr, + ); + }); + + @override + String? delete(String key) => using((arena) { + final secret = read(key); + final schema = _schemaFor(arena); + final attributes = _attributes(key: key, arena: arena); + _libSecret.secret_password_clearv_sync( + schema, + attributes, + nullptr, + nullptr, + ); + return secret; + }); + + @override + String? read(String key) => using((arena) { + final attributes = _attributes(key: key, arena: arena); + final schema = _schemaFor(arena); + final result = _libSecret.secret_password_lookupv_sync( + schema, + attributes, + nullptr, + nullptr, + ); + if (result == nullptr) { + return null; + } + arena.onReleaseAll(() => _libSecret.secret_password_free(result)); + return result.toDartString(); + }); + + @override + String write(String key, String value) { + using((arena) { + final label = _labelFor(key).toNativeUtf8(allocator: arena); + final secret = value.toNativeUtf8(allocator: arena); + final attributes = _attributes(key: key, arena: arena); + _libSecret.secret_password_storev_sync( + _schemaFor(arena), + attributes, + nullptr, + label, + secret, + nullptr, + nullptr, + ); + }); + return value; + } +} From d8d86986d6e2d25de294f27b436d87ada1a9485c Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 16:23:56 -0800 Subject: [PATCH 06/23] Finish Linux impl --- .../secure_storage/secure_storage.linux.dart | 23 +++++++++++-------- .../secure_storage_platform.vm.dart | 4 ++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart b/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart index dd327a8f..99d50322 100644 --- a/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart +++ b/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart @@ -16,6 +16,9 @@ final class SecureStoragePlatformLinux extends SecureStoragePlatform { final _glibDylib = DynamicLibrary.open('libglib-2.0.so.0'); late final _glib = Glib(_glibDylib); + final _gioDylib = DynamicLibrary.open('libgio-2.0.so'); + late final _gio = Glib(_gioDylib); + final _libSecretDylib = DynamicLibrary.open('libsecret-1.so.0'); late final _libSecret = Libsecret(_libSecretDylib); @@ -24,11 +27,11 @@ final class SecureStoragePlatformLinux extends SecureStoragePlatform { 'g_str_hash'); late final String _appName = () { - final application = _glib.g_application_get_default(); + final application = _gio.g_application_get_default(); if (application == nullptr) { return File('/proc/self/exe').resolveSymbolicLinksSync(); } - return _glib + return _gio .g_application_get_application_id(application) .cast() .toDartString(); @@ -43,15 +46,17 @@ final class SecureStoragePlatformLinux extends SecureStoragePlatform { SecretSchemaAttributeType.SECRET_SCHEMA_ATTRIBUTE_STRING; Pointer _attributes({ - required String key, + String? key, required Arena arena, }) { final hashTable = _glib.g_hash_table_new(_gStrHashPointer, nullptr); - _glib.g_hash_table_insert( - hashTable, - 'key'.toNativeUtf8(allocator: arena).cast(), - key.toNativeUtf8(allocator: arena).cast(), - ); + if (key != null) { + _glib.g_hash_table_insert( + hashTable, + 'key'.toNativeUtf8(allocator: arena).cast(), + key.toNativeUtf8(allocator: arena).cast(), + ); + } arena.onReleaseAll(() => _glib.g_hash_table_destroy(hashTable)); return hashTable; } @@ -59,7 +64,7 @@ final class SecureStoragePlatformLinux extends SecureStoragePlatform { @override void clear() => using((arena) { final schema = _schemaFor(arena); - final attributes = _attributes(key: '', arena: arena); + final attributes = _attributes(arena: arena); _libSecret.secret_password_clearv_sync( schema, attributes, diff --git a/packages/celest_core/lib/src/secure_storage/secure_storage_platform.vm.dart b/packages/celest_core/lib/src/secure_storage/secure_storage_platform.vm.dart index 5b5d63db..9fb69cb6 100644 --- a/packages/celest_core/lib/src/secure_storage/secure_storage_platform.vm.dart +++ b/packages/celest_core/lib/src/secure_storage/secure_storage_platform.vm.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:celest_core/src/secure_storage/secure_storage.android.dart'; import 'package:celest_core/src/secure_storage/secure_storage.dart'; import 'package:celest_core/src/secure_storage/secure_storage.darwin.dart'; +import 'package:celest_core/src/secure_storage/secure_storage.linux.dart'; import 'package:meta/meta.dart'; abstract base class SecureStoragePlatform implements SecureStorage { @@ -15,6 +16,9 @@ abstract base class SecureStoragePlatform implements SecureStorage { if (Platform.isAndroid) { return SecureStoragePlatformAndroid(scope: scope ?? _defaultScope); } + if (Platform.isLinux) { + return SecureStoragePlatformLinux(scope: scope ?? _defaultScope); + } throw UnsupportedError('This platform is not yet supported.'); } From d77448bdb7792490850f478dcdb33c83ca5489df Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 16:25:31 -0800 Subject: [PATCH 07/23] Test Linux in CI --- .github/workflows/celest_core.yaml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/.github/workflows/celest_core.yaml b/.github/workflows/celest_core.yaml index 589ce335..1d0f3f40 100644 --- a/.github/workflows/celest_core.yaml +++ b/.github/workflows/celest_core.yaml @@ -79,6 +79,12 @@ jobs: with: cache: true - name: Get Packages + working-directory: packages/celest_core + run: dart pub get + - name: Test + working-directory: packages/celest_core + run: dart test + - name: Get Packages (Example) working-directory: packages/celest_core/example run: flutter pub get - name: Enable KVM @@ -94,3 +100,26 @@ jobs: api-level: 31 arch: x86_64 script: cd packages/celest_core/example && flutter test -d emulator integration_test/secure_storage_test.dart + test_linux: + needs: analyze_and_format + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Git Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # 4.1.1 + - name: Setup Flutter + uses: subosito/flutter-action@62f096cacda5168a3bd7b95793373be14fa4fbaf # 2.13.0 + with: + cache: true + - name: Get Packages + working-directory: packages/celest_core + run: dart pub get + - name: Test + working-directory: packages/celest_core + run: dart test + - name: Get Packages (Example) + working-directory: packages/celest_core/example + run: flutter pub get + - name: Test (Linux) + working-directory: packages/celest_core/example + run: flutter test -d linux integration_test/secure_storage_test.dart From 13b22eb2bc34474198ccab1b56e9e4d9a05625aa Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 16:33:38 -0800 Subject: [PATCH 08/23] try fix --- .github/workflows/celest_core.yaml | 6 ------ .../example/integration_test/secure_storage_shared.dart | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/celest_core.yaml b/.github/workflows/celest_core.yaml index 1d0f3f40..ae243811 100644 --- a/.github/workflows/celest_core.yaml +++ b/.github/workflows/celest_core.yaml @@ -78,12 +78,6 @@ jobs: uses: subosito/flutter-action@62f096cacda5168a3bd7b95793373be14fa4fbaf # 2.13.0 with: cache: true - - name: Get Packages - working-directory: packages/celest_core - run: dart pub get - - name: Test - working-directory: packages/celest_core - run: dart test - name: Get Packages (Example) working-directory: packages/celest_core/example run: flutter pub get diff --git a/packages/celest_core/example/integration_test/secure_storage_shared.dart b/packages/celest_core/example/integration_test/secure_storage_shared.dart index cdf4f1b1..4561692c 100644 --- a/packages/celest_core/example/integration_test/secure_storage_shared.dart +++ b/packages/celest_core/example/integration_test/secure_storage_shared.dart @@ -97,7 +97,7 @@ final _random = Random(); Iterable<(int, String)> get _largeKeyValuePairs sync* { for (final length in const [100, 1000, 10000]) { final string = String.fromCharCodes( - List.generate(length, (_) => _random.nextInt(255) + 1), + List.generate(length, (_) => _random.nextInt(94) + 33), ); yield (length, string); } From af2886d59d00d07d98f5fab30495f8ab3695d9a0 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 16:55:20 -0800 Subject: [PATCH 09/23] Add missing function --- packages/celest_core/ffigen.glib.yaml | 1 + .../lib/src/native/linux/glib.ffi.dart | 14 ++++++++ .../secure_storage/secure_storage.linux.dart | 33 +++++++++++++++++-- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/celest_core/ffigen.glib.yaml b/packages/celest_core/ffigen.glib.yaml index 47e17e05..0af4a67b 100644 --- a/packages/celest_core/ffigen.glib.yaml +++ b/packages/celest_core/ffigen.glib.yaml @@ -31,6 +31,7 @@ functions: - g_hash_table_destroy - g_application_get_default - g_application_get_application_id + - g_error_free structs: include: - _GError diff --git a/packages/celest_core/lib/src/native/linux/glib.ffi.dart b/packages/celest_core/lib/src/native/linux/glib.ffi.dart index 0033492c..b516a959 100644 --- a/packages/celest_core/lib/src/native/linux/glib.ffi.dart +++ b/packages/celest_core/lib/src/native/linux/glib.ffi.dart @@ -25,6 +25,20 @@ class Glib { lookup) : _lookup = lookup; + void g_error_free( + ffi.Pointer error, + ) { + return _g_error_free( + error, + ); + } + + late final _g_error_freePtr = + _lookup)>>( + 'g_error_free'); + late final _g_error_free = + _g_error_freePtr.asFunction)>(); + ffi.Pointer g_hash_table_new( ffi.Pointer< ffi.NativeFunction)>> diff --git a/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart b/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart index 99d50322..1c87d284 100644 --- a/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart +++ b/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:celest_core/src/native/linux/glib.ffi.dart'; import 'package:celest_core/src/native/linux/libsecret.ffi.dart'; +import 'package:celest_core/src/secure_storage/secure_storage_exception.dart'; import 'package:celest_core/src/secure_storage/secure_storage_platform.vm.dart'; import 'package:ffi/ffi.dart'; @@ -65,12 +66,19 @@ final class SecureStoragePlatformLinux extends SecureStoragePlatform { void clear() => using((arena) { final schema = _schemaFor(arena); final attributes = _attributes(arena: arena); + final err = arena>(); _libSecret.secret_password_clearv_sync( schema, attributes, nullptr, nullptr, ); + final error = err.value; + if (error != nullptr) { + arena.onReleaseAll(() => _glib.g_error_free(error)); + final message = error.ref.message.cast().toDartString(); + throw SecureStorageUnknownException(message); + } }); @override @@ -78,12 +86,19 @@ final class SecureStoragePlatformLinux extends SecureStoragePlatform { final secret = read(key); final schema = _schemaFor(arena); final attributes = _attributes(key: key, arena: arena); + final err = arena>(); _libSecret.secret_password_clearv_sync( schema, attributes, nullptr, nullptr, ); + final error = err.value; + if (error != nullptr) { + arena.onReleaseAll(() => _glib.g_error_free(error)); + final message = error.ref.message.cast().toDartString(); + throw SecureStorageUnknownException(message); + } return secret; }); @@ -91,12 +106,19 @@ final class SecureStoragePlatformLinux extends SecureStoragePlatform { String? read(String key) => using((arena) { final attributes = _attributes(key: key, arena: arena); final schema = _schemaFor(arena); + final err = arena>(); final result = _libSecret.secret_password_lookupv_sync( schema, attributes, nullptr, - nullptr, + err, ); + final error = err.value; + if (error != nullptr) { + arena.onReleaseAll(() => _glib.g_error_free(error)); + final message = error.ref.message.cast().toDartString(); + throw SecureStorageUnknownException(message); + } if (result == nullptr) { return null; } @@ -110,6 +132,7 @@ final class SecureStoragePlatformLinux extends SecureStoragePlatform { final label = _labelFor(key).toNativeUtf8(allocator: arena); final secret = value.toNativeUtf8(allocator: arena); final attributes = _attributes(key: key, arena: arena); + final err = arena>(); _libSecret.secret_password_storev_sync( _schemaFor(arena), attributes, @@ -117,8 +140,14 @@ final class SecureStoragePlatformLinux extends SecureStoragePlatform { label, secret, nullptr, - nullptr, + err, ); + final error = err.value; + if (error != nullptr) { + arena.onReleaseAll(() => _glib.g_error_free(error)); + final message = error.ref.message.cast().toDartString(); + throw SecureStorageUnknownException(message); + } }); return value; } From 96695acc6702df4cb6612374404dbcd42f913301 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 17:01:37 -0800 Subject: [PATCH 10/23] Install libsecret --- .github/workflows/celest_core.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/celest_core.yaml b/.github/workflows/celest_core.yaml index ae243811..96abc81e 100644 --- a/.github/workflows/celest_core.yaml +++ b/.github/workflows/celest_core.yaml @@ -105,6 +105,8 @@ jobs: uses: subosito/flutter-action@62f096cacda5168a3bd7b95793373be14fa4fbaf # 2.13.0 with: cache: true + - name: Install dependencies + run: apt-get update && apt-get install -y libsecret-1-dev - name: Get Packages working-directory: packages/celest_core run: dart pub get From c127a9dd5c5954c7b8bbcad0a3902ea205dfce79 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 17:07:22 -0800 Subject: [PATCH 11/23] clean up --- .github/workflows/celest_core.yaml | 2 +- .../secure_storage/secure_storage.linux.dart | 101 +++++++++--------- 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/.github/workflows/celest_core.yaml b/.github/workflows/celest_core.yaml index 96abc81e..f03c044e 100644 --- a/.github/workflows/celest_core.yaml +++ b/.github/workflows/celest_core.yaml @@ -106,7 +106,7 @@ jobs: with: cache: true - name: Install dependencies - run: apt-get update && apt-get install -y libsecret-1-dev + run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev - name: Get Packages working-directory: packages/celest_core run: dart pub get diff --git a/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart b/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart index 1c87d284..c50cc425 100644 --- a/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart +++ b/packages/celest_core/lib/src/secure_storage/secure_storage.linux.dart @@ -66,19 +66,15 @@ final class SecureStoragePlatformLinux extends SecureStoragePlatform { void clear() => using((arena) { final schema = _schemaFor(arena); final attributes = _attributes(arena: arena); - final err = arena>(); - _libSecret.secret_password_clearv_sync( - schema, - attributes, - nullptr, - nullptr, + _check( + (err) => _libSecret.secret_password_clearv_sync( + schema, + attributes, + nullptr, + err, + ), + arena: arena, ); - final error = err.value; - if (error != nullptr) { - arena.onReleaseAll(() => _glib.g_error_free(error)); - final message = error.ref.message.cast().toDartString(); - throw SecureStorageUnknownException(message); - } }); @override @@ -86,19 +82,15 @@ final class SecureStoragePlatformLinux extends SecureStoragePlatform { final secret = read(key); final schema = _schemaFor(arena); final attributes = _attributes(key: key, arena: arena); - final err = arena>(); - _libSecret.secret_password_clearv_sync( - schema, - attributes, - nullptr, - nullptr, + _check( + (err) => _libSecret.secret_password_clearv_sync( + schema, + attributes, + nullptr, + err, + ), + arena: arena, ); - final error = err.value; - if (error != nullptr) { - arena.onReleaseAll(() => _glib.g_error_free(error)); - final message = error.ref.message.cast().toDartString(); - throw SecureStorageUnknownException(message); - } return secret; }); @@ -106,19 +98,15 @@ final class SecureStoragePlatformLinux extends SecureStoragePlatform { String? read(String key) => using((arena) { final attributes = _attributes(key: key, arena: arena); final schema = _schemaFor(arena); - final err = arena>(); - final result = _libSecret.secret_password_lookupv_sync( - schema, - attributes, - nullptr, - err, + final result = _check( + (err) => _libSecret.secret_password_lookupv_sync( + schema, + attributes, + nullptr, + err, + ), + arena: arena, ); - final error = err.value; - if (error != nullptr) { - arena.onReleaseAll(() => _glib.g_error_free(error)); - final message = error.ref.message.cast().toDartString(); - throw SecureStorageUnknownException(message); - } if (result == nullptr) { return null; } @@ -132,23 +120,34 @@ final class SecureStoragePlatformLinux extends SecureStoragePlatform { final label = _labelFor(key).toNativeUtf8(allocator: arena); final secret = value.toNativeUtf8(allocator: arena); final attributes = _attributes(key: key, arena: arena); - final err = arena>(); - _libSecret.secret_password_storev_sync( - _schemaFor(arena), - attributes, - nullptr, - label, - secret, - nullptr, - err, + _check( + (err) => _libSecret.secret_password_storev_sync( + _schemaFor(arena), + attributes, + nullptr, + label, + secret, + nullptr, + err, + ), + arena: arena, ); - final error = err.value; - if (error != nullptr) { - arena.onReleaseAll(() => _glib.g_error_free(error)); - final message = error.ref.message.cast().toDartString(); - throw SecureStorageUnknownException(message); - } }); return value; } + + R _check( + R Function(Pointer> err) action, { + required Arena arena, + }) { + final err = arena>(); + final result = action(err); + final error = err.value; + if (error != nullptr) { + arena.onReleaseAll(() => _glib.g_error_free(error)); + final message = error.ref.message.cast().toDartString(); + throw SecureStorageUnknownException(message); + } + return result; + } } From c41012526c8c83f7900c56a40475b1adb5a60881 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 17:13:54 -0800 Subject: [PATCH 12/23] setup keyring --- .github/workflows/celest_core.yaml | 5 +++-- packages/celest_core/tool/setup-ci.sh | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100755 packages/celest_core/tool/setup-ci.sh diff --git a/.github/workflows/celest_core.yaml b/.github/workflows/celest_core.yaml index f03c044e..46e52487 100644 --- a/.github/workflows/celest_core.yaml +++ b/.github/workflows/celest_core.yaml @@ -105,8 +105,9 @@ jobs: uses: subosito/flutter-action@62f096cacda5168a3bd7b95793373be14fa4fbaf # 2.13.0 with: cache: true - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev + - name: Setup Test Environment + working-directory: packages/celest_core + run: tool/setup-ci.sh - name: Get Packages working-directory: packages/celest_core run: dart pub get diff --git a/packages/celest_core/tool/setup-ci.sh b/packages/celest_core/tool/setup-ci.sh new file mode 100755 index 00000000..f986e842 --- /dev/null +++ b/packages/celest_core/tool/setup-ci.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + + sudo apt-get update && sudo apt-get install -y libsecret-1-dev gnome-keyring + + # If running in headless mode, re-run script in dbus session. + if [ -z $DBUS_SESSION_BUS_ADDRESS && -n $1 ]; then + exec dbus-run-session -- $@ + fi + + # Set up keyring in CI env + if [ -n $CI ]; then + echo 'password' | gnome-keyring-daemon --start --replace --daemonize --unlock + fi +fi From 2b25c90ac220c328f31f7b59457df8053d5c8a35 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 17:50:48 -0800 Subject: [PATCH 13/23] Add Windows impl --- .../src/native/windows/windows_folders.dart | 246 ++++++++++++++++ .../lib/src/native/windows/windows_paths.dart | 268 ++++++++++++++++++ .../secure_storage.windows.dart | 139 +++++++++ .../secure_storage_platform.vm.dart | 4 + packages/celest_core/pubspec.yaml | 2 + 5 files changed, 659 insertions(+) create mode 100644 packages/celest_core/lib/src/native/windows/windows_folders.dart create mode 100644 packages/celest_core/lib/src/native/windows/windows_paths.dart create mode 100644 packages/celest_core/lib/src/secure_storage/secure_storage.windows.dart diff --git a/packages/celest_core/lib/src/native/windows/windows_folders.dart b/packages/celest_core/lib/src/native/windows/windows_folders.dart new file mode 100644 index 00000000..44acbc83 --- /dev/null +++ b/packages/celest_core/lib/src/native/windows/windows_folders.dart @@ -0,0 +1,246 @@ +// Copied from: +// https://github.com/flutter/packages/blob/36a7b99381f85e86914e82c75fc7d9038ed96cca/packages/path_provider/path_provider_windows/lib/src/folders.dart + +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of 'windows_paths.dart'; + +// ignore_for_file: non_constant_identifier_names + +// ignore: avoid_classes_with_only_static_members +/// A class containing the GUID references for each of the documented Windows +/// known folders. A property of this class may be passed to the `getPath` +/// method in the [PathProviderWindows] class to retrieve a known folder from +/// Windows. +class WindowsKnownFolder { + /// The file system directory that is used to store administrative tools for + /// an individual user. The MMC will save customized consoles to this + /// directory, and it will roam with the user. + static String get AdminTools => FOLDERID_AdminTools; + + /// The file system directory that acts as a staging area for files waiting to + /// be written to a CD. A typical path is C:\Documents and + /// Settings\username\Local Settings\Application Data\Microsoft\CD Burning. + static String get CDBurning => FOLDERID_CDBurning; + + /// The file system directory that contains administrative tools for all users + /// of the computer. + static String get CommonAdminTools => FOLDERID_CommonAdminTools; + + /// The file system directory that contains the directories for the common + /// program groups that appear on the Start menu for all users. A typical path + /// is C:\Documents and Settings\All Users\Start Menu\Programs. + static String get CommonPrograms => FOLDERID_CommonPrograms; + + /// The file system directory that contains the programs and folders that + /// appear on the Start menu for all users. A typical path is C:\Documents and + /// Settings\All Users\Start Menu. + static String get CommonStartMenu => FOLDERID_CommonStartMenu; + + /// The file system directory that contains the programs that appear in the + /// Startup folder for all users. A typical path is C:\Documents and + /// Settings\All Users\Start Menu\Programs\Startup. + static String get CommonStartup => FOLDERID_CommonStartup; + + /// The file system directory that contains the templates that are available + /// to all users. A typical path is C:\Documents and Settings\All + /// Users\Templates. + static String get CommonTemplates => FOLDERID_CommonTemplates; + + /// The virtual folder that represents My Computer, containing everything on + /// the local computer: storage devices, printers, and Control Panel. The + /// folder can also contain mapped network drives. + static String get ComputerFolder => FOLDERID_ComputerFolder; + + /// The virtual folder that represents Network Connections, that contains + /// network and dial-up connections. + static String get ConnectionsFolder => FOLDERID_ConnectionsFolder; + + /// The virtual folder that contains icons for the Control Panel applications. + static String get ControlPanelFolder => FOLDERID_ControlPanelFolder; + + /// The file system directory that serves as a common repository for Internet + /// cookies. A typical path is C:\Documents and Settings\username\Cookies. + static String get Cookies => FOLDERID_Cookies; + + /// The virtual folder that represents the Windows desktop, the root of the + /// namespace. + static String get Desktop => FOLDERID_Desktop; + + /// The virtual folder that represents the My Documents desktop item. + static String get Documents => FOLDERID_Documents; + + /// The file system directory that serves as a repository for Internet + /// downloads. + static String get Downloads => FOLDERID_Downloads; + + /// The file system directory that serves as a common repository for the + /// user's favorite items. A typical path is C:\Documents and + /// Settings\username\Favorites. + static String get Favorites => FOLDERID_Favorites; + + /// A virtual folder that contains fonts. A typical path is C:\Windows\Fonts. + static String get Fonts => FOLDERID_Fonts; + + /// The file system directory that serves as a common repository for Internet + /// history items. + static String get History => FOLDERID_History; + + /// The file system directory that serves as a common repository for temporary + /// Internet files. A typical path is C:\Documents and Settings\username\Local + /// Settings\Temporary Internet Files. + static String get InternetCache => FOLDERID_InternetCache; + + /// A virtual folder for Internet Explorer. + static String get InternetFolder => FOLDERID_InternetFolder; + + /// The file system directory that serves as a data repository for local + /// (nonroaming) applications. A typical path is C:\Documents and + /// Settings\username\Local Settings\Application Data. + static String get LocalAppData => FOLDERID_LocalAppData; + + /// The file system directory that serves as a common repository for music + /// files. A typical path is C:\Documents and Settings\User\My Documents\My + /// Music. + static String get Music => FOLDERID_Music; + + /// A file system directory that contains the link objects that may exist in + /// the My Network Places virtual folder. A typical path is C:\Documents and + /// Settings\username\NetHood. + static String get NetHood => FOLDERID_NetHood; + + /// The folder that represents other computers in your workgroup. + static String get NetworkFolder => FOLDERID_NetworkFolder; + + /// The file system directory that serves as a common repository for image + /// files. A typical path is C:\Documents and Settings\username\My + /// Documents\My Pictures. + static String get Pictures => FOLDERID_Pictures; + + /// The file system directory that contains the link objects that can exist in + /// the Printers virtual folder. A typical path is C:\Documents and + /// Settings\username\PrintHood. + static String get PrintHood => FOLDERID_PrintHood; + + /// The virtual folder that contains installed printers. + static String get PrintersFolder => FOLDERID_PrintersFolder; + + /// The user's profile folder. A typical path is C:\Users\username. + /// Applications should not create files or folders at this level. + static String get Profile => FOLDERID_Profile; + + /// The file system directory that contains application data for all users. A + /// typical path is C:\Documents and Settings\All Users\Application Data. This + /// folder is used for application data that is not user specific. For + /// example, an application can store a spell-check dictionary, a database of + /// clip art, or a log file in the CSIDL_COMMON_APPDATA folder. This + /// information will not roam and is available to anyone using the computer. + static String get ProgramData => FOLDERID_ProgramData; + + /// The Program Files folder. A typical path is C:\Program Files. + static String get ProgramFiles => FOLDERID_ProgramFiles; + + /// The common Program Files folder. A typical path is C:\Program + /// Files\Common. + static String get ProgramFilesCommon => FOLDERID_ProgramFilesCommon; + + /// On 64-bit systems, a link to the common Program Files folder. A typical path is + /// C:\Program Files\Common Files. + static String get ProgramFilesCommonX64 => FOLDERID_ProgramFilesCommonX64; + + /// On 64-bit systems, a link to the 32-bit common Program Files folder. A + /// typical path is C:\Program Files (x86)\Common Files. On 32-bit systems, a + /// link to the Common Program Files folder. + static String get ProgramFilesCommonX86 => FOLDERID_ProgramFilesCommonX86; + + /// On 64-bit systems, a link to the Program Files folder. A typical path is + /// C:\Program Files. + static String get ProgramFilesX64 => FOLDERID_ProgramFilesX64; + + /// On 64-bit systems, a link to the 32-bit Program Files folder. A typical + /// path is C:\Program Files (x86). On 32-bit systems, a link to the Common + /// Program Files folder. + static String get ProgramFilesX86 => FOLDERID_ProgramFilesX86; + + /// The file system directory that contains the user's program groups (which + /// are themselves file system directories). + static String get Programs => FOLDERID_Programs; + + /// The file system directory that contains files and folders that appear on + /// the desktop for all users. A typical path is C:\Documents and Settings\All + /// Users\Desktop. + static String get PublicDesktop => FOLDERID_PublicDesktop; + + /// The file system directory that contains documents that are common to all + /// users. A typical path is C:\Documents and Settings\All Users\Documents. + static String get PublicDocuments => FOLDERID_PublicDocuments; + + /// The file system directory that serves as a repository for music files + /// common to all users. A typical path is C:\Documents and Settings\All + /// Users\Documents\My Music. + static String get PublicMusic => FOLDERID_PublicMusic; + + /// The file system directory that serves as a repository for image files + /// common to all users. A typical path is C:\Documents and Settings\All + /// Users\Documents\My Pictures. + static String get PublicPictures => FOLDERID_PublicPictures; + + /// The file system directory that serves as a repository for video files + /// common to all users. A typical path is C:\Documents and Settings\All + /// Users\Documents\My Videos. + static String get PublicVideos => FOLDERID_PublicVideos; + + /// The file system directory that contains shortcuts to the user's most + /// recently used documents. A typical path is C:\Documents and + /// Settings\username\My Recent Documents. + static String get Recent => FOLDERID_Recent; + + /// The virtual folder that contains the objects in the user's Recycle Bin. + static String get RecycleBinFolder => FOLDERID_RecycleBinFolder; + + /// The file system directory that contains resource data. A typical path is + /// C:\Windows\Resources. + static String get ResourceDir => FOLDERID_ResourceDir; + + /// The file system directory that serves as a common repository for + /// application-specific data. A typical path is C:\Documents and + /// Settings\username\Application Data. + static String get RoamingAppData => FOLDERID_RoamingAppData; + + /// The file system directory that contains Send To menu items. A typical path + /// is C:\Documents and Settings\username\SendTo. + static String get SendTo => FOLDERID_SendTo; + + /// The file system directory that contains Start menu items. A typical path + /// is C:\Documents and Settings\username\Start Menu. + static String get StartMenu => FOLDERID_StartMenu; + + /// The file system directory that corresponds to the user's Startup program + /// group. The system starts these programs whenever the associated user logs + /// on. A typical path is C:\Documents and Settings\username\Start + /// Menu\Programs\Startup. + static String get Startup => FOLDERID_Startup; + + /// The Windows System folder. A typical path is C:\Windows\System32. + static String get System => FOLDERID_System; + + /// The 32-bit Windows System folder. On 32-bit systems, this is typically + /// C:\Windows\system32. On 64-bit systems, this is typically + /// C:\Windows\syswow64. + static String get SystemX86 => FOLDERID_SystemX86; + + /// The file system directory that serves as a common repository for document + /// templates. A typical path is C:\Documents and Settings\username\Templates. + static String get Templates => FOLDERID_Templates; + + /// The file system directory that serves as a common repository for video + /// files. A typical path is C:\Documents and Settings\username\My + /// Documents\My Videos. + static String get Videos => FOLDERID_Videos; + + /// The Windows directory or SYSROOT. This corresponds to the %windir% or + /// %SYSTEMROOT% environment variables. A typical path is C:\Windows. + static String get Windows => FOLDERID_Windows; +} diff --git a/packages/celest_core/lib/src/native/windows/windows_paths.dart b/packages/celest_core/lib/src/native/windows/windows_paths.dart new file mode 100644 index 00000000..7ccc48fc --- /dev/null +++ b/packages/celest_core/lib/src/native/windows/windows_paths.dart @@ -0,0 +1,268 @@ +// Copied from: +// https://github.com/flutter/packages/blob/36a7b99381f85e86914e82c75fc7d9038ed96cca/packages/path_provider/path_provider_windows/lib/src/path_provider_windows_real.dart + +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: prefer_asserts_with_message + +import 'dart:ffi'; +import 'dart:io'; + +import 'package:ffi/ffi.dart'; +import 'package:meta/meta.dart'; +import 'package:path/path.dart' as path; +import 'package:win32/win32.dart'; + +part 'windows_folders.dart'; + +/// Constant for en-US language used in VersionInfo keys. +@visibleForTesting +const String languageEn = '0409'; + +/// Constant for CP1252 encoding used in VersionInfo keys +@visibleForTesting +const String encodingCP1252 = '04e4'; + +/// Constant for Unicode encoding used in VersionInfo keys +@visibleForTesting +const String encodingUnicode = '04b0'; + +/// Wraps the Win32 VerQueryValue API call. +/// +/// This class exists to allow injecting alternate metadata in tests without +/// building multiple custom test binaries. +@visibleForTesting +class VersionInfoQuerier { + /// Returns the value for [key] in [versionInfo]s in section with given + /// language and encoding, or null if there is no such entry, + /// or if versionInfo is null. + /// + /// See https://docs.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource + /// for list of possible language and encoding values. + String? getStringValue( + Pointer? versionInfo, + String key, { + required String language, + required String encoding, + }) { + assert(language.isNotEmpty); + assert(encoding.isNotEmpty); + if (versionInfo == null) { + return null; + } + final keyPath = TEXT('\\StringFileInfo\\$language$encoding\\$key'); + final length = calloc(); + final valueAddress = calloc>(); + try { + if (VerQueryValue(versionInfo, keyPath, valueAddress, length) == 0) { + return null; + } + return valueAddress.value.toDartString(); + } finally { + calloc + ..free(keyPath) + ..free(length) + ..free(valueAddress); + } + } +} + +/// The Windows implementation of path provider. +/// +/// This class implements the `package:path_provider` functionality for Windows. +class PathProviderWindows { + /// The object to use for performing VerQueryValue calls. + @visibleForTesting + static VersionInfoQuerier versionInfoQuerier = VersionInfoQuerier(); + + /// This is typically the same as the TMP environment variable. + static String? getTemporaryPath() { + final buffer = calloc(MAX_PATH + 1).cast(); + String path; + + try { + final length = GetTempPath(MAX_PATH, buffer); + + if (length == 0) { + final error = GetLastError(); + throw WindowsException(error); + } else { + path = buffer.toDartString(); + + // GetTempPath adds a trailing backslash, but SHGetKnownFolderPath does + // not. Strip off trailing backslash for consistency with other methods + // here. + if (path.endsWith(r'\')) { + path = path.substring(0, path.length - 1); + } + } + + // Ensure that the directory exists, since GetTempPath doesn't. + final directory = Directory(path); + if (!directory.existsSync()) { + directory.createSync(recursive: true); + } + + return path; + } finally { + calloc.free(buffer); + } + } + + static String? getApplicationSupportPath() => + _createApplicationSubdirectory(WindowsKnownFolder.RoamingAppData); + + static String? getApplicationDocumentsPath() => + getPath(WindowsKnownFolder.Documents); + + static String? getApplicationCachePath() => + _createApplicationSubdirectory(WindowsKnownFolder.LocalAppData); + + static String? getDownloadsPath() => getPath(WindowsKnownFolder.Downloads); + + /// Retrieve any known folder from Windows. + /// + /// folderID is a GUID that represents a specific known folder ID, drawn from + /// [WindowsKnownFolder]. + static String? getPath(String folderID) { + final pathPtrPtr = calloc>(); + final knownFolderID = calloc()..ref.setGUID(folderID); + + try { + final hr = SHGetKnownFolderPath( + knownFolderID, + KF_FLAG_DEFAULT, + NULL, + pathPtrPtr, + ); + + if (FAILED(hr)) { + if (hr == E_INVALIDARG || hr == E_FAIL) { + throw WindowsException(hr); + } + return null; + } + + final path = pathPtrPtr.value.toDartString(); + return path; + } finally { + calloc + ..free(pathPtrPtr) + ..free(knownFolderID); + } + } + + static String? _getStringValue(Pointer? infoBuffer, String key) => + versionInfoQuerier.getStringValue( + infoBuffer, + key, + language: languageEn, + encoding: encodingCP1252, + ) ?? + versionInfoQuerier.getStringValue( + infoBuffer, + key, + language: languageEn, + encoding: encodingUnicode, + ); + + /// Returns the relative path string to append to the root directory returned + /// by Win32 APIs for application storage (such as RoamingAppDir) to get a + /// directory that is unique to the application. + /// + /// The convention is to use company-name\product-name\. This will use that if + /// possible, using the data in the VERSIONINFO resource, with the following + /// fallbacks: + /// - If the company name isn't there, that component will be dropped. + /// - If the product name isn't there, it will use the exe's filename (without + /// extension). + static String _getApplicationSpecificSubdirectory() { + String? companyName; + String? productName; + + final moduleNameBuffer = wsalloc(MAX_PATH + 1); + final unused = calloc(); + Pointer? infoBuffer; + try { + // Get the module name. + final moduleNameLength = GetModuleFileName(0, moduleNameBuffer, MAX_PATH); + if (moduleNameLength == 0) { + final error = GetLastError(); + throw WindowsException(error); + } + + // From that, load the VERSIONINFO resource + final infoSize = GetFileVersionInfoSize(moduleNameBuffer, unused); + if (infoSize != 0) { + infoBuffer = calloc(infoSize); + if (GetFileVersionInfo(moduleNameBuffer, 0, infoSize, infoBuffer) == + 0) { + calloc.free(infoBuffer); + infoBuffer = null; + } + } + companyName = + _sanitizedDirectoryName(_getStringValue(infoBuffer, 'CompanyName')); + productName = + _sanitizedDirectoryName(_getStringValue(infoBuffer, 'ProductName')); + + // If there was no product name, use the executable name. + productName ??= + path.basenameWithoutExtension(moduleNameBuffer.toDartString()); + + return companyName != null + ? path.join(companyName, productName) + : productName; + } finally { + calloc + ..free(moduleNameBuffer) + ..free(unused); + if (infoBuffer != null) { + calloc.free(infoBuffer); + } + } + } + + /// Makes [rawString] safe as a directory component. See + /// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions + /// + /// If after sanitizing the string is empty, returns null. + static String? _sanitizedDirectoryName(String? rawString) { + if (rawString == null) { + return null; + } + var sanitized = rawString + // Replace banned characters. + .replaceAll(RegExp(r'[<>:"/\\|?*]'), '_') + // Remove trailing whitespace. + .trimRight() + // Ensure that it does not end with a '.'. + .replaceAll(RegExp(r'[.]+$'), ''); + const kMaxComponentLength = 255; + if (sanitized.length > kMaxComponentLength) { + sanitized = sanitized.substring(0, kMaxComponentLength); + } + return sanitized.isEmpty ? null : sanitized; + } + + static String? _createApplicationSubdirectory(String folderId) { + final baseDir = getPath(folderId); + if (baseDir == null) { + return null; + } + final directory = + Directory(path.join(baseDir, _getApplicationSpecificSubdirectory())); + // Ensure that the directory exists if possible, since it will on other + // platforms. If the name is longer than MAXPATH, creating will fail, so + // skip that step; it's up to the client to decide what to do with the path + // in that case (e.g., using a short path). + if (directory.path.length <= MAX_PATH) { + if (!directory.existsSync()) { + directory.createSync(recursive: true); + } + } + return directory.path; + } +} diff --git a/packages/celest_core/lib/src/secure_storage/secure_storage.windows.dart b/packages/celest_core/lib/src/secure_storage/secure_storage.windows.dart new file mode 100644 index 00000000..92023231 --- /dev/null +++ b/packages/celest_core/lib/src/secure_storage/secure_storage.windows.dart @@ -0,0 +1,139 @@ +import 'dart:convert'; +import 'dart:ffi'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:celest_core/src/native/windows/windows_paths.dart'; +import 'package:celest_core/src/secure_storage/secure_storage_exception.dart'; +import 'package:celest_core/src/secure_storage/secure_storage_platform.vm.dart'; +import 'package:path/path.dart' as p; +import 'package:ffi/ffi.dart'; +import 'package:win32/win32.dart'; + +final class SecureStoragePlatformWindows extends SecureStoragePlatform { + SecureStoragePlatformWindows({ + required this.scope, + }) : super.base(); + + final String scope; + late final File _storage = File( + p.join(PathProviderWindows.getApplicationSupportPath()!, '$scope.json'), + ); + + 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() { + if (_storage.existsSync()) { + _storage.deleteSync(); + } + } + + @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; + } + 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 != 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 != ERROR_SUCCESS) { + throw SecureStorageUnknownException(_lastException.toString()); + } + final unencryptedDataBlob = unencryptedPtr.ref; + final unencryptedBlob = unencryptedDataBlob.pbData.asTypedList( + unencryptedDataBlob.cbData, + ); + 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/celest_core/lib/src/secure_storage/secure_storage_platform.vm.dart b/packages/celest_core/lib/src/secure_storage/secure_storage_platform.vm.dart index 9fb69cb6..a4ddb92a 100644 --- a/packages/celest_core/lib/src/secure_storage/secure_storage_platform.vm.dart +++ b/packages/celest_core/lib/src/secure_storage/secure_storage_platform.vm.dart @@ -4,6 +4,7 @@ import 'package:celest_core/src/secure_storage/secure_storage.android.dart'; import 'package:celest_core/src/secure_storage/secure_storage.dart'; import 'package:celest_core/src/secure_storage/secure_storage.darwin.dart'; import 'package:celest_core/src/secure_storage/secure_storage.linux.dart'; +import 'package:celest_core/src/secure_storage/secure_storage.windows.dart'; import 'package:meta/meta.dart'; abstract base class SecureStoragePlatform implements SecureStorage { @@ -19,6 +20,9 @@ abstract base class SecureStoragePlatform implements SecureStorage { if (Platform.isLinux) { return SecureStoragePlatformLinux(scope: scope ?? _defaultScope); } + if (Platform.isWindows) { + return SecureStoragePlatformWindows(scope: scope ?? _defaultScope); + } throw UnsupportedError('This platform is not yet supported.'); } diff --git a/packages/celest_core/pubspec.yaml b/packages/celest_core/pubspec.yaml index 3918892a..009693fe 100644 --- a/packages/celest_core/pubspec.yaml +++ b/packages/celest_core/pubspec.yaml @@ -13,6 +13,8 @@ dependencies: jni: ^0.7.2 meta: ^1.10.0 os_detect: ^2.0.1 + path: ^1.9.0 + win32: ^5.2.0 dev_dependencies: # TODO(dnys1): Use ^12.0.0 when released From 6540ca1dd6bc72cb049868e2f727caf5d6b4932a Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 18:43:33 -0800 Subject: [PATCH 14/23] clean up --- .../lib/src/secure_storage/secure_storage.windows.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/celest_core/lib/src/secure_storage/secure_storage.windows.dart b/packages/celest_core/lib/src/secure_storage/secure_storage.windows.dart index 92023231..b2171d0b 100644 --- a/packages/celest_core/lib/src/secure_storage/secure_storage.windows.dart +++ b/packages/celest_core/lib/src/secure_storage/secure_storage.windows.dart @@ -61,7 +61,7 @@ final class SecureStoragePlatformWindows extends SecureStoragePlatform { @override String write(String key, String value) { final encrypted = _encrypt(value); - final data = _readData()..[key] = encrypted;; + final data = _readData()..[key] = encrypted; _writeData(data); return value; } From 53a1f7c9bdfd0d6b4631743ab325958627e8b0b5 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 18:59:33 -0800 Subject: [PATCH 15/23] install build deps --- .github/workflows/celest_core.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/celest_core.yaml b/.github/workflows/celest_core.yaml index 46e52487..2d013c2f 100644 --- a/.github/workflows/celest_core.yaml +++ b/.github/workflows/celest_core.yaml @@ -105,6 +105,8 @@ jobs: uses: subosito/flutter-action@62f096cacda5168a3bd7b95793373be14fa4fbaf # 2.13.0 with: cache: true + - name: Install Build Dependencies + run: sudo apt-get update && sudo apt-get install -y clang cmake git ninja-build pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev - name: Setup Test Environment working-directory: packages/celest_core run: tool/setup-ci.sh From b993f27b1a1b229a6c6d1724678f41ec9891aa5c Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 19:15:54 -0800 Subject: [PATCH 16/23] logging --- .github/workflows/celest_core.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/celest_core.yaml b/.github/workflows/celest_core.yaml index 2d013c2f..5e43abb4 100644 --- a/.github/workflows/celest_core.yaml +++ b/.github/workflows/celest_core.yaml @@ -121,4 +121,4 @@ jobs: run: flutter pub get - name: Test (Linux) working-directory: packages/celest_core/example - run: flutter test -d linux integration_test/secure_storage_test.dart + run: flutter test --verbose -d linux integration_test/secure_storage_test.dart From 4238f0051841767982f3c78eaa63b2a0316bc623 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 19:20:43 -0800 Subject: [PATCH 17/23] fix linux ci --- .github/workflows/celest_core.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/celest_core.yaml b/.github/workflows/celest_core.yaml index 5e43abb4..f1566221 100644 --- a/.github/workflows/celest_core.yaml +++ b/.github/workflows/celest_core.yaml @@ -121,4 +121,11 @@ jobs: run: flutter pub get - name: Test (Linux) working-directory: packages/celest_core/example - run: flutter test --verbose -d linux integration_test/secure_storage_test.dart + run: | + # Headless tests require virtual display for the linux tests to run. + # from https://github.com/fluttercommunity/plus_plugins/blob/main/.github/workflows/scripts/integration-test.sh + export DISPLAY=:99 + sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & + # Needed for WebSocket connections in API GraphQL subscriptions. + sudo systemctl start NetworkManager.service + flutter test -d linux integration_test/secure_storage_test.dart From bc59b6116d2cea998541ba695105205c0abfc4d5 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 19:20:43 -0800 Subject: [PATCH 18/23] fix linux ci --- .github/workflows/celest_core.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/celest_core.yaml b/.github/workflows/celest_core.yaml index f1566221..e2b9bd94 100644 --- a/.github/workflows/celest_core.yaml +++ b/.github/workflows/celest_core.yaml @@ -123,9 +123,6 @@ jobs: working-directory: packages/celest_core/example run: | # Headless tests require virtual display for the linux tests to run. - # from https://github.com/fluttercommunity/plus_plugins/blob/main/.github/workflows/scripts/integration-test.sh export DISPLAY=:99 sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & - # Needed for WebSocket connections in API GraphQL subscriptions. - sudo systemctl start NetworkManager.service flutter test -d linux integration_test/secure_storage_test.dart From 7b165be6170c88bb6e269b2745f1d0d0dfe05df9 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 19:22:12 -0800 Subject: [PATCH 19/23] add windows ci --- .github/workflows/celest_core.yaml | 55 ++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/.github/workflows/celest_core.yaml b/.github/workflows/celest_core.yaml index e2b9bd94..0d3de1e9 100644 --- a/.github/workflows/celest_core.yaml +++ b/.github/workflows/celest_core.yaml @@ -126,3 +126,58 @@ jobs: export DISPLAY=:99 sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & flutter test -d linux integration_test/secure_storage_test.dart + test_linux: + needs: analyze_and_format + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Git Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # 4.1.1 + - name: Setup Flutter + uses: subosito/flutter-action@62f096cacda5168a3bd7b95793373be14fa4fbaf # 2.13.0 + with: + cache: true + - name: Install Build Dependencies + run: sudo apt-get update && sudo apt-get install -y clang cmake git ninja-build pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev + - name: Setup Test Environment + working-directory: packages/celest_core + run: tool/setup-ci.sh + - name: Get Packages + working-directory: packages/celest_core + run: dart pub get + - name: Test + working-directory: packages/celest_core + run: dart test + - name: Get Packages (Example) + working-directory: packages/celest_core/example + run: flutter pub get + - name: Test (Linux) + working-directory: packages/celest_core/example + run: | + # Headless tests require virtual display for the linux tests to run. + export DISPLAY=:99 + sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & + flutter test -d linux integration_test/secure_storage_test.dart + test_windows: + needs: analyze_and_format + runs-on: windows-latest + timeout-minutes: 15 + steps: + - name: Git Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # 4.1.1 + - name: Setup Flutter + uses: subosito/flutter-action@62f096cacda5168a3bd7b95793373be14fa4fbaf # 2.13.0 + with: + cache: true + - name: Get Packages + working-directory: packages/celest_core + run: dart pub get + - name: Test + working-directory: packages/celest_core + run: dart test + - name: Get Packages (Example) + working-directory: packages/celest_core/example + run: flutter pub get + - name: Test (Windows) + working-directory: packages/celest_core/example + run: flutter test -d windows integration_test/secure_storage_test.dart From 0df9e9c714e828d27c6845ddda87be3b0248c97d Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 19:23:08 -0800 Subject: [PATCH 20/23] fix --- .github/workflows/celest_core.yaml | 32 ------------------------------ 1 file changed, 32 deletions(-) diff --git a/.github/workflows/celest_core.yaml b/.github/workflows/celest_core.yaml index 0d3de1e9..87c4dfee 100644 --- a/.github/workflows/celest_core.yaml +++ b/.github/workflows/celest_core.yaml @@ -126,38 +126,6 @@ jobs: export DISPLAY=:99 sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & flutter test -d linux integration_test/secure_storage_test.dart - test_linux: - needs: analyze_and_format - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Git Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # 4.1.1 - - name: Setup Flutter - uses: subosito/flutter-action@62f096cacda5168a3bd7b95793373be14fa4fbaf # 2.13.0 - with: - cache: true - - name: Install Build Dependencies - run: sudo apt-get update && sudo apt-get install -y clang cmake git ninja-build pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev - - name: Setup Test Environment - working-directory: packages/celest_core - run: tool/setup-ci.sh - - name: Get Packages - working-directory: packages/celest_core - run: dart pub get - - name: Test - working-directory: packages/celest_core - run: dart test - - name: Get Packages (Example) - working-directory: packages/celest_core/example - run: flutter pub get - - name: Test (Linux) - working-directory: packages/celest_core/example - run: | - # Headless tests require virtual display for the linux tests to run. - export DISPLAY=:99 - sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & - flutter test -d linux integration_test/secure_storage_test.dart test_windows: needs: analyze_and_format runs-on: windows-latest From 83918c8800a62130dbcbd56a9c2d7bce4f3f8536 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 19:27:21 -0800 Subject: [PATCH 21/23] no windows cache --- .github/workflows/celest_core.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/celest_core.yaml b/.github/workflows/celest_core.yaml index 87c4dfee..9208361b 100644 --- a/.github/workflows/celest_core.yaml +++ b/.github/workflows/celest_core.yaml @@ -136,7 +136,7 @@ jobs: - name: Setup Flutter uses: subosito/flutter-action@62f096cacda5168a3bd7b95793373be14fa4fbaf # 2.13.0 with: - cache: true + cache: false - name: Get Packages working-directory: packages/celest_core run: dart pub get From e1a5734f2098572123fed71c76b8b913801eb370 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 19:38:16 -0800 Subject: [PATCH 22/23] no windows pub get example --- .github/workflows/celest_core.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/celest_core.yaml b/.github/workflows/celest_core.yaml index 9208361b..6c7e2881 100644 --- a/.github/workflows/celest_core.yaml +++ b/.github/workflows/celest_core.yaml @@ -136,10 +136,10 @@ jobs: - name: Setup Flutter uses: subosito/flutter-action@62f096cacda5168a3bd7b95793373be14fa4fbaf # 2.13.0 with: - cache: false + cache: true - name: Get Packages working-directory: packages/celest_core - run: dart pub get + run: dart pub get --no-example - name: Test working-directory: packages/celest_core run: dart test From e12e2440efd8da10cd4433f5ba2e4cb9585eacc5 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 6 Mar 2024 19:41:59 -0800 Subject: [PATCH 23/23] disable windows ci --- .github/workflows/celest_core.yaml | 48 ++++++++++++++++-------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/.github/workflows/celest_core.yaml b/.github/workflows/celest_core.yaml index 6c7e2881..1d748201 100644 --- a/.github/workflows/celest_core.yaml +++ b/.github/workflows/celest_core.yaml @@ -126,26 +126,28 @@ jobs: export DISPLAY=:99 sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & flutter test -d linux integration_test/secure_storage_test.dart - test_windows: - needs: analyze_and_format - runs-on: windows-latest - timeout-minutes: 15 - steps: - - name: Git Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # 4.1.1 - - name: Setup Flutter - uses: subosito/flutter-action@62f096cacda5168a3bd7b95793373be14fa4fbaf # 2.13.0 - with: - cache: true - - name: Get Packages - working-directory: packages/celest_core - run: dart pub get --no-example - - name: Test - working-directory: packages/celest_core - run: dart test - - name: Get Packages (Example) - working-directory: packages/celest_core/example - run: flutter pub get - - name: Test (Windows) - working-directory: packages/celest_core/example - run: flutter test -d windows integration_test/secure_storage_test.dart + # TODO: Re-enable + # Need to fix this: Git error. Command: `git clone --mirror https://github.com/dart-lang/native /c/Users/runneradmin/.pub-cache\git\cache\native-647c69ed8027da6d6def6bc40efa87cf1a2f76aa` + # test_windows: + # needs: analyze_and_format + # runs-on: windows-latest + # timeout-minutes: 15 + # steps: + # - name: Git Checkout + # uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # 4.1.1 + # - name: Setup Flutter + # uses: subosito/flutter-action@62f096cacda5168a3bd7b95793373be14fa4fbaf # 2.13.0 + # with: + # cache: true + # - name: Get Packages + # working-directory: packages/celest_core + # run: dart pub get --no-example + # - name: Test + # working-directory: packages/celest_core + # run: dart test + # - name: Get Packages (Example) + # working-directory: packages/celest_core/example + # run: flutter pub get + # - name: Test (Windows) + # working-directory: packages/celest_core/example + # run: flutter test -d windows integration_test/secure_storage_test.dart