From 07e325e467155fbb78cde2fe022578a280558aa3 Mon Sep 17 00:00:00 2001 From: Kevin Yue Date: Fri, 13 Dec 2024 10:58:39 +0000 Subject: [PATCH] upgrade gpauth --- Cargo.lock | 36 + Cargo.toml | 2 +- apps/gpauth/Cargo.toml | 10 +- apps/gpauth/gen/schemas/acl-manifests.json | 1 + apps/gpauth/gen/schemas/capabilities.json | 1 + apps/gpauth/gen/schemas/desktop-schema.json | 1756 +++++++++++++++++++ apps/gpauth/gen/schemas/linux-schema.json | 1756 +++++++++++++++++++ apps/gpauth/src/auth_messenger.rs | 52 + apps/gpauth/src/auth_window.rs | 589 ++----- apps/gpauth/src/auth_window1.rs | 523 ++++++ apps/gpauth/src/cli.rs | 215 +-- apps/gpauth/src/cli1.rs | 251 +++ apps/gpauth/src/common.rs | 135 ++ apps/gpauth/src/lib.rs | 7 + apps/gpauth/src/main.rs | 1 - apps/gpauth/src/unix.rs | 133 ++ apps/gpauth/tauri.conf.json | 47 +- apps/gpclient/src/cli.rs | 39 +- crates/gpapi/src/auth.rs | 78 +- crates/gpapi/src/clap/mod.rs | 27 + crates/gpapi/src/error.rs | 20 +- crates/gpapi/src/gateway/login.rs | 2 +- crates/gpapi/src/portal/config.rs | 2 +- crates/gpapi/src/portal/prelogin.rs | 4 +- 24 files changed, 4980 insertions(+), 707 deletions(-) create mode 100644 apps/gpauth/gen/schemas/acl-manifests.json create mode 100644 apps/gpauth/gen/schemas/capabilities.json create mode 100644 apps/gpauth/gen/schemas/desktop-schema.json create mode 100644 apps/gpauth/gen/schemas/linux-schema.json create mode 100644 apps/gpauth/src/auth_messenger.rs create mode 100644 apps/gpauth/src/auth_window1.rs create mode 100644 apps/gpauth/src/cli1.rs create mode 100644 apps/gpauth/src/common.rs create mode 100644 apps/gpauth/src/lib.rs create mode 100644 apps/gpauth/src/unix.rs diff --git a/Cargo.lock b/Cargo.lock index 5ebeaad..1c487f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1562,6 +1562,27 @@ dependencies = [ "whoami", ] +[[package]] +name = "gpauth" +version = "2.3.9" +dependencies = [ + "anyhow", + "clap", + "compile-time", + "env_logger", + "gpapi", + "html-escape", + "log", + "regex", + "serde_json", + "tauri", + "tauri-build", + "tempfile", + "tokio", + "tokio-util", + "webkit2gtk", +] + [[package]] name = "gpclient" version = "2.3.9" @@ -1733,6 +1754,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + [[package]] name = "html5ever" version = "0.26.0" @@ -4942,6 +4972,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + [[package]] name = "utf8_iter" version = "1.0.4" diff --git a/Cargo.toml b/Cargo.toml index fad197c..a176cf2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "2" -members = ["crates/*", "apps/gpclient", "apps/gpservice", "apps/gpgui-helper/src-tauri"] +members = ["crates/*", "apps/gpclient", "apps/gpservice", "apps/gpauth", "apps/gpgui-helper/src-tauri"] [workspace.package] rust-version = "1.70" diff --git a/apps/gpauth/Cargo.toml b/apps/gpauth/Cargo.toml index 9a8acd0..9f712c2 100644 --- a/apps/gpauth/Cargo.toml +++ b/apps/gpauth/Cargo.toml @@ -6,7 +6,7 @@ edition.workspace = true license.workspace = true [build-dependencies] -tauri-build = { version = "1.5", features = [] } +tauri-build = { version = "2", features = [] } [dependencies] gpapi = { path = "../../crates/gpapi", features = [ @@ -14,6 +14,9 @@ gpapi = { path = "../../crates/gpapi", features = [ "clap", "browser-auth", ] } + +tauri = { workspace = true } + anyhow.workspace = true clap.workspace = true env_logger.workspace = true @@ -24,6 +27,7 @@ tokio.workspace = true tokio-util.workspace = true tempfile.workspace = true html-escape = "0.2.13" -webkit2gtk = "0.18.2" -tauri = { workspace = true, features = ["http-all"] } compile-time.workspace = true + +[target.'cfg(not(target_os = "macos"))'.dependencies] +webkit2gtk = "2" diff --git a/apps/gpauth/gen/schemas/acl-manifests.json b/apps/gpauth/gen/schemas/acl-manifests.json new file mode 100644 index 0000000..6cd367a --- /dev/null +++ b/apps/gpauth/gen/schemas/acl-manifests.json @@ -0,0 +1 @@ +{"core":{"default_permission":{"identifier":"default","description":"Default core plugins set which includes:\n- 'core:path:default'\n- 'core:event:default'\n- 'core:window:default'\n- 'core:webview:default'\n- 'core:app:default'\n- 'core:image:default'\n- 'core:resources:default'\n- 'core:menu:default'\n- 'core:tray:default'\n","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file diff --git a/apps/gpauth/gen/schemas/capabilities.json b/apps/gpauth/gen/schemas/capabilities.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/apps/gpauth/gen/schemas/capabilities.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/apps/gpauth/gen/schemas/desktop-schema.json b/apps/gpauth/gen/schemas/desktop-schema.json new file mode 100644 index 0000000..f1cad26 --- /dev/null +++ b/apps/gpauth/gen/schemas/desktop-schema.json @@ -0,0 +1,1756 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nOn multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThis is only required when using on multiwebview contexts, by default all child webviews of a window that matches [`Self::windows`] are linked.\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set which includes:\n- 'core:path:default'\n- 'core:event:default'\n- 'core:window:default'\n- 'core:webview:default'\n- 'core:app:default'\n- 'core:image:default'\n- 'core:resources:default'\n- 'core:menu:default'\n- 'core:tray:default'\n", + "type": "string", + "const": "core:default" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:app:default" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide" + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show" + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon" + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name" + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme" + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version" + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version" + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide" + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show" + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon" + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name" + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme" + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version" + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:event:default" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit" + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to" + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen" + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten" + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit" + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to" + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen" + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:image:default" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes" + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path" + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new" + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba" + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size" + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes" + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path" + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new" + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba" + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:menu:default" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append" + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default" + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get" + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert" + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked" + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled" + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items" + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new" + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup" + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend" + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove" + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at" + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator" + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu" + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp" + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu" + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp" + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked" + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled" + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon" + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text" + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text" + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append" + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default" + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get" + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert" + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked" + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled" + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items" + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new" + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup" + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend" + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove" + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at" + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator" + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu" + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp" + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu" + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp" + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked" + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled" + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon" + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text" + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:path:default" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename" + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname" + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname" + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute" + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join" + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize" + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve" + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory" + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename" + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname" + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname" + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute" + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join" + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize" + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve" + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:resources:default" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close" + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:tray:default" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id" + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new" + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id" + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon" + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template" + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu" + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click" + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path" + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title" + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip" + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible" + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id" + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new" + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id" + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon" + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template" + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu" + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click" + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path" + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title" + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip" + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:webview:default" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data" + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview" + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window" + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews" + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools" + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print" + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent" + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus" + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position" + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size" + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom" + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close" + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide" + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position" + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show" + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size" + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data" + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview" + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window" + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews" + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools" + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print" + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent" + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus" + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position" + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size" + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom" + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close" + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide" + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position" + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show" + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:window:default" + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors" + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close" + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create" + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor" + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position" + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy" + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows" + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide" + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position" + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size" + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize" + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable" + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated" + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled" + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused" + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen" + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable" + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized" + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable" + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized" + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable" + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible" + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize" + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize" + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point" + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position" + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size" + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor" + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention" + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor" + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom" + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top" + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable" + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected" + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab" + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon" + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position" + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible" + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations" + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects" + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled" + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus" + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen" + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon" + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events" + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size" + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable" + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size" + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable" + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position" + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar" + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable" + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow" + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size" + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints" + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar" + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme" + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title" + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style" + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces" + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show" + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging" + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging" + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme" + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title" + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize" + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize" + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize" + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors" + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center" + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close" + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create" + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor" + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position" + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy" + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows" + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide" + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position" + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size" + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize" + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable" + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated" + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled" + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused" + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen" + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable" + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized" + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable" + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized" + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable" + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible" + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize" + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize" + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point" + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position" + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size" + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor" + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention" + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor" + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom" + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top" + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable" + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected" + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab" + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon" + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position" + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible" + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations" + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects" + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled" + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus" + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen" + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon" + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events" + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size" + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable" + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size" + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable" + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position" + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar" + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable" + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow" + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size" + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints" + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar" + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme" + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title" + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style" + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces" + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show" + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging" + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging" + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme" + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title" + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize" + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize" + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize" + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + } + } +} \ No newline at end of file diff --git a/apps/gpauth/gen/schemas/linux-schema.json b/apps/gpauth/gen/schemas/linux-schema.json new file mode 100644 index 0000000..f1cad26 --- /dev/null +++ b/apps/gpauth/gen/schemas/linux-schema.json @@ -0,0 +1,1756 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nOn multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThis is only required when using on multiwebview contexts, by default all child webviews of a window that matches [`Self::windows`] are linked.\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set which includes:\n- 'core:path:default'\n- 'core:event:default'\n- 'core:window:default'\n- 'core:webview:default'\n- 'core:app:default'\n- 'core:image:default'\n- 'core:resources:default'\n- 'core:menu:default'\n- 'core:tray:default'\n", + "type": "string", + "const": "core:default" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:app:default" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide" + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show" + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon" + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name" + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme" + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version" + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version" + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide" + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show" + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon" + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name" + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme" + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version" + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:event:default" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit" + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to" + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen" + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten" + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit" + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to" + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen" + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:image:default" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes" + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path" + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new" + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba" + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size" + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes" + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path" + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new" + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba" + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:menu:default" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append" + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default" + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get" + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert" + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked" + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled" + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items" + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new" + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup" + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend" + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove" + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at" + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator" + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu" + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp" + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu" + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp" + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked" + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled" + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon" + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text" + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text" + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append" + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default" + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get" + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert" + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked" + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled" + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items" + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new" + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup" + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend" + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove" + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at" + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator" + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu" + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp" + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu" + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp" + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked" + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled" + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon" + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text" + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:path:default" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename" + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname" + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname" + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute" + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join" + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize" + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve" + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory" + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename" + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname" + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname" + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute" + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join" + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize" + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve" + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:resources:default" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close" + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:tray:default" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id" + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new" + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id" + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon" + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template" + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu" + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click" + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path" + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title" + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip" + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible" + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id" + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new" + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id" + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon" + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template" + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu" + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click" + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path" + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title" + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip" + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:webview:default" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data" + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview" + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window" + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews" + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools" + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print" + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent" + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus" + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position" + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size" + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom" + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close" + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide" + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position" + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show" + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size" + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data" + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview" + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window" + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews" + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools" + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print" + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent" + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus" + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position" + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size" + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom" + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close" + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide" + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position" + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show" + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size" + }, + { + "description": "Default permissions for the plugin.", + "type": "string", + "const": "core:window:default" + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors" + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close" + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create" + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor" + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position" + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy" + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows" + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide" + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position" + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size" + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize" + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable" + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated" + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled" + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused" + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen" + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable" + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized" + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable" + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized" + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable" + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible" + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize" + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize" + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point" + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position" + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size" + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor" + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention" + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor" + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom" + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top" + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable" + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected" + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab" + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon" + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position" + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible" + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations" + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects" + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled" + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus" + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen" + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon" + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events" + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size" + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable" + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size" + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable" + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position" + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar" + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable" + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow" + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size" + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints" + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar" + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme" + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title" + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style" + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces" + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show" + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging" + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging" + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme" + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title" + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize" + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize" + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize" + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors" + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center" + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close" + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create" + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor" + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position" + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy" + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows" + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide" + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position" + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size" + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize" + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable" + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated" + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled" + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused" + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen" + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable" + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized" + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable" + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized" + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable" + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible" + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize" + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize" + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point" + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position" + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size" + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor" + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention" + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor" + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom" + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top" + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable" + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected" + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab" + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon" + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position" + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible" + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations" + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects" + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled" + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus" + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen" + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon" + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events" + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size" + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable" + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size" + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable" + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position" + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar" + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable" + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow" + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size" + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints" + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar" + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme" + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title" + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style" + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces" + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show" + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging" + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging" + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme" + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title" + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize" + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize" + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize" + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + } + } +} \ No newline at end of file diff --git a/apps/gpauth/src/auth_messenger.rs b/apps/gpauth/src/auth_messenger.rs new file mode 100644 index 0000000..f805361 --- /dev/null +++ b/apps/gpauth/src/auth_messenger.rs @@ -0,0 +1,52 @@ +use gpapi::auth::SamlAuthData; +use log::error; +use tokio::sync::{mpsc, RwLock}; + +pub enum AuthError { + /// Failed to load page due to TLS error + TlsError, + /// 1. Found auth data in headers/body but it's invalid + /// 2. Loaded an empty page, failed to load page. etc. + Invalid, + /// No auth data found in headers/body + NotFound, + /// Other errors + Other, +} + +pub type AuthResult = anyhow::Result; + +pub struct AuthMessenger { + tx: mpsc::UnboundedSender, + rx: RwLock>, +} + +impl AuthMessenger { + pub fn new() -> Self { + let (tx, rx) = mpsc::unbounded_channel(); + + Self { + tx, + rx: RwLock::new(rx), + } + } + + pub async fn recv_auth_data(&self) -> AuthResult { + let mut rx = self.rx.write().await; + rx.recv().await.unwrap_or_else(|| Err(AuthError::Other)) + } + + pub fn send_auth_result(&self, result: AuthResult) { + if let Err(err) = self.tx.send(result) { + error!("Failed to send auth result: {}", err); + } + } + + pub fn send_auth_error(&self, err: AuthError) { + self.send_auth_result(Err(err)); + } + + pub fn send_auth_data(&self, data: SamlAuthData) { + self.send_auth_result(Ok(data)); + } +} diff --git a/apps/gpauth/src/auth_window.rs b/apps/gpauth/src/auth_window.rs index c326195..1aefcf8 100644 --- a/apps/gpauth/src/auth_window.rs +++ b/apps/gpauth/src/auth_window.rs @@ -1,288 +1,186 @@ -use std::{ - rc::Rc, - sync::Arc, - time::{Duration, Instant}, -}; +use std::{sync::Arc, time::Instant}; use anyhow::bail; use gpapi::{ auth::SamlAuthData, - error::AuthDataParseError, + error::PortalError, gp_params::GpParams, portal::{prelogin, Prelogin}, - utils::{redact::redact_uri, window::WindowExt}, }; use log::{info, warn}; -use regex::Regex; -use tauri::{AppHandle, Window, WindowEvent, WindowUrl}; -use tokio::sync::{mpsc, oneshot, RwLock}; +use tauri::{AppHandle, WebviewUrl, WebviewWindow}; +use tokio::sync::oneshot; use tokio_util::sync::CancellationToken; -use webkit2gtk::{ - gio::Cancellable, - glib::{GString, TimeSpan}, - LoadEvent, SettingsExt, TLSErrorsPolicy, URIResponse, URIResponseExt, WebContextExt, WebResource, WebResourceExt, - WebView, WebViewExt, WebsiteDataManagerExtManual, WebsiteDataTypes, + +use crate::{ + auth_messenger::{AuthError, AuthMessenger}, + common::{AuthRequest, AuthSettings}, + platform_impl, }; -enum AuthDataError { - /// Failed to load page due to TLS error - TlsError, - /// 1. Found auth data in headers/body but it's invalid - /// 2. Loaded an empty page, failed to load page. etc. - Invalid, - /// No auth data found in headers/body - NotFound, -} - -type AuthResult = Result; - -pub(crate) struct AuthWindow<'a> { - app_handle: AppHandle, +pub struct AuthWindow<'a> { + app_handle: &'a AppHandle, server: &'a str, - saml_request: &'a str, - user_agent: &'a str, - gp_params: Option, + gp_params: Option<&'a GpParams>, + saml_request: Option<&'a str>, clean: bool, } impl<'a> AuthWindow<'a> { - pub fn new(app_handle: AppHandle) -> Self { + pub fn new(app_handle: &'a AppHandle, server: &'a str) -> Self { Self { app_handle, - server: "", - saml_request: "", - user_agent: "", + server, gp_params: None, + saml_request: None, clean: false, } } - pub fn server(mut self, server: &'a str) -> Self { - self.server = server; + pub fn with_gp_params(mut self, gp_params: &'a GpParams) -> Self { + self.gp_params = Some(gp_params); self } - pub fn saml_request(mut self, saml_request: &'a str) -> Self { - self.saml_request = saml_request; + pub fn with_saml_request(mut self, saml_request: &'a str) -> Self { + self.saml_request = Some(saml_request); self } - pub fn user_agent(mut self, user_agent: &'a str) -> Self { - self.user_agent = user_agent; - self - } - - pub fn gp_params(mut self, gp_params: GpParams) -> Self { - self.gp_params.replace(gp_params); - self - } - - pub fn clean(mut self, clean: bool) -> Self { + pub fn with_clean(mut self, clean: bool) -> Self { self.clean = clean; self } - pub async fn open(&self) -> anyhow::Result { - info!("Open auth window, user_agent: {}", self.user_agent); - - let window = Window::builder(&self.app_handle, "auth_window", WindowUrl::default()) + pub async fn authenticate(&self) -> anyhow::Result { + let auth_window = WebviewWindow::builder(self.app_handle, "auth_window", WebviewUrl::default()) .title("GlobalProtect Login") - // .user_agent(self.user_agent) .focused(true) - .visible(false) + .visible(true) .center() .build()?; - let window = Arc::new(window); - let cancel_token = CancellationToken::new(); - let cancel_token_clone = cancel_token.clone(); - - window.on_window_event(move |event| { - if let WindowEvent::CloseRequested { .. } = event { - cancel_token_clone.cancel(); - } - }); - - let window_clone = Arc::clone(&window); - let timeout_secs = 15; - tokio::spawn(async move { - tokio::time::sleep(Duration::from_secs(timeout_secs)).await; - let visible = window_clone.is_visible().unwrap_or(false); - if !visible { - info!("Try to raise auth window after {} seconds", timeout_secs); - raise_window(&window_clone); - } - }); - tokio::select! { - _ = cancel_token.cancelled() => { - bail!("Auth cancelled"); - } - saml_result = self.auth_loop(&window) => { - window.close()?; - saml_result + _ = cancel_token.cancelled() => bail!("Authentication cancelled"), + result = self.auth_loop(&auth_window, &cancel_token) => { + auth_window.close()?; + result } } } - async fn auth_loop(&self, window: &Arc) -> anyhow::Result { - let saml_request = self.saml_request.to_string(); - let (auth_result_tx, mut auth_result_rx) = mpsc::unbounded_channel::(); - let raise_window_cancel_token: Arc>> = Default::default(); - let gp_params = self.gp_params.as_ref().unwrap(); - let tls_err_policy = if gp_params.ignore_tls_errors() { - TLSErrorsPolicy::Ignore - } else { - TLSErrorsPolicy::Fail - }; - + async fn auth_loop( + &self, + auth_window: &WebviewWindow, + cancel_token: &CancellationToken, + ) -> anyhow::Result { if self.clean { - clear_webview_cookies(window).await?; + self.clear_webview_data(&auth_window).await?; } - let raise_window_cancel_token_clone = Arc::clone(&raise_window_cancel_token); - window.with_webview(move |wv| { - let wv = wv.inner(); - - if let Some(context) = wv.context() { - context.set_tls_errors_policy(tls_err_policy); - } - - if let Some(settings) = wv.settings() { - let ua = settings.user_agent().unwrap_or("".into()); - info!("Auth window user agent: {}", ua); - } - - // Load the initial SAML request - load_saml_request(&wv, &saml_request); - - let auth_result_tx_clone = auth_result_tx.clone(); - wv.connect_load_changed(move |wv, event| { - if event == LoadEvent::Started { - let Ok(mut cancel_token) = raise_window_cancel_token_clone.try_write() else { - return; - }; - - // Cancel the raise window task - if let Some(cancel_token) = cancel_token.take() { - cancel_token.cancel(); - } - return; - } - - if event != LoadEvent::Finished { - return; - } - - if let Some(main_resource) = wv.main_resource() { - let uri = main_resource.uri().unwrap_or("".into()); - - if uri.is_empty() { - warn!("Loaded an empty uri"); - send_auth_result(&auth_result_tx_clone, Err(AuthDataError::Invalid)); - return; - } - - info!("Loaded uri: {}", redact_uri(&uri)); - if uri.starts_with("globalprotectcallback:") { - return; - } - - read_auth_data(&main_resource, auth_result_tx_clone.clone()); - } - }); - - let auth_result_tx_clone = auth_result_tx.clone(); - wv.connect_load_failed_with_tls_errors(move |_wv, uri, cert, err| { - let redacted_uri = redact_uri(uri); - warn!( - "Failed to load uri: {} with error: {}, cert: {}", - redacted_uri, err, cert - ); - - send_auth_result(&auth_result_tx_clone, Err(AuthDataError::TlsError)); - true - }); - - wv.connect_load_failed(move |_wv, _event, uri, err| { - let redacted_uri = redact_uri(uri); - if !uri.starts_with("globalprotectcallback:") { - warn!("Failed to load uri: {} with error: {}", redacted_uri, err); - } - // NOTE: Don't send error here, since load_changed event will be triggered after this - // send_auth_result(&auth_result_tx, Err(AuthDataError::Invalid)); - // true to stop other handlers from being invoked for the event. false to propagate the event further. - true - }); - })?; - - let portal = self.server.to_string(); + let auth_messenger = self.setup_auth_window(&auth_window, cancel_token).await?; loop { - if let Some(auth_result) = auth_result_rx.recv().await { - match auth_result { - Ok(auth_data) => return Ok(auth_data), - Err(AuthDataError::TlsError) => bail!("TLS error: certificate verify failed"), - Err(AuthDataError::NotFound) => { - info!("No auth data found, it may not be the /SAML20/SP/ACS endpoint"); - - // The user may need to interact with the auth window, raise it in 3 seconds - if !window.is_visible().unwrap_or(false) { - let window = Arc::clone(window); - let cancel_token = CancellationToken::new(); - - raise_window_cancel_token.write().await.replace(cancel_token.clone()); - - tokio::spawn(async move { - let delay_secs = 1; - - info!("Raise window in {} second(s)", delay_secs); - tokio::select! { - _ = tokio::time::sleep(Duration::from_secs(delay_secs)) => { - raise_window(&window); - } - _ = cancel_token.cancelled() => { - info!("Raise window cancelled"); - } - } - }); - } - } - Err(AuthDataError::Invalid) => { - info!("Got invalid auth data, retrying..."); - - window.with_webview(|wv| { - let wv = wv.inner(); - wv.run_javascript(r#" - var loading = document.createElement("div"); - loading.innerHTML = '
Got invalid token, retrying...
'; - loading.style = "position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.85); z-index: 99999;"; - document.body.appendChild(loading); - "#, - Cancellable::NONE, - |_| info!("Injected loading element successfully"), - ); - })?; - - let saml_request = portal_prelogin(&portal, gp_params).await?; - window.with_webview(move |wv| { - let wv = wv.inner(); - load_saml_request(&wv, &saml_request); - })?; - } - } + match auth_messenger.recv_auth_data().await { + Ok(auth_data) => return Ok(auth_data), + Err(AuthError::TlsError) => bail!(PortalError::TlsError), + Err(AuthError::NotFound) => self.handle_not_found(auth_window).await, + Err(AuthError::Invalid) => self.retry_auth(auth_window).await?, + Err(AuthError::Other) => bail!("Unknown error"), } } } -} -fn raise_window(window: &Arc) { - let visible = window.is_visible().unwrap_or(false); - if !visible { - if let Err(err) = window.raise() { - warn!("Failed to raise window: {}", err); + async fn clear_webview_data(&self, auth_window: &WebviewWindow) -> anyhow::Result<()> { + info!("Clearing webview data..."); + + let (tx, rx) = oneshot::channel::>(); + let now = Instant::now(); + auth_window.with_webview(|webview| { + platform_impl::clear_data(&webview.inner(), |result| { + if let Err(result) = tx.send(result) { + warn!("Failed to send clear data result: {:?}", result); + } + }) + })?; + + rx.await??; + info!("Webview data cleared in {:?}", now.elapsed()); + + Ok(()) + } + + async fn setup_auth_window( + &self, + auth_window: &WebviewWindow, + cancel_token: &CancellationToken, + ) -> anyhow::Result> { + info!("Setting up auth window..."); + + let cancel_token = cancel_token.clone(); + auth_window.on_window_event(move |event| { + if let tauri::WindowEvent::CloseRequested { .. } = event { + cancel_token.cancel(); + } + }); + + let saml_request = self.saml_request.expect("SAML request not set").to_string(); + let gp_params = self.gp_params.expect("GP params not set"); + + let auth_messenger = Arc::new(AuthMessenger::new()); + let auth_messenger_clone = Arc::clone(&auth_messenger); + let ignore_tls_errors = gp_params.ignore_tls_errors(); + + let (tx, rx) = oneshot::channel::>(); + auth_window.with_webview(move |webview| { + let auth_settings = AuthSettings { + auth_request: AuthRequest::new(&saml_request), + auth_messenger: auth_messenger_clone, + ignore_tls_errors, + }; + let result = platform_impl::setup_webview(&webview.inner(), auth_settings); + if let Err(result) = tx.send(result) { + warn!("Failed to send setup auth window result: {:?}", result); + } + })?; + + rx.await??; + info!("Auth window setup completed"); + + Ok(auth_messenger) + } + + async fn handle_not_found(&self, auth_window: &WebviewWindow) { + info!("No auth data found, it may not be the /SAML20/SP/ACS endpoint"); + + let visible = auth_window.is_visible().unwrap_or(false); + if visible { + return; } + + info!("Displaying the window in 3 seconds"); + + // todo!("Display the window in 3 seconds") + } + + async fn retry_auth(&self, auth_window: &WebviewWindow) -> anyhow::Result<()> { + info!("Retrying authentication..."); + + auth_window.eval( r#" + var loading = document.createElement("div"); + loading.innerHTML = '
Got invalid token, retrying...
'; + loading.style = "position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.85); z-index: 99999;"; + document.body.appendChild(loading); + "#)?; + + let saml_request = portal_prelogin(&self.server, self.gp_params.unwrap()).await?; + auth_window.with_webview(move |webview| { + let auth_request = AuthRequest::new(&saml_request); + platform_impl::load_auth_request(&webview.inner(), &auth_request); + })?; + + Ok(()) } } @@ -292,232 +190,3 @@ pub async fn portal_prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Resu Prelogin::Standard(_) => bail!("Received non-SAML prelogin response"), } } - -fn send_auth_result(auth_result_tx: &mpsc::UnboundedSender, auth_result: AuthResult) { - if let Err(err) = auth_result_tx.send(auth_result) { - warn!("Failed to send auth event: {}", err); - } -} - -fn load_saml_request(wv: &Rc, saml_request: &str) { - if saml_request.starts_with("http") { - info!("Load the SAML request as URI..."); - wv.load_uri(saml_request); - } else { - info!("Load the SAML request as HTML..."); - wv.load_html(saml_request, None); - } -} - -fn read_auth_data_from_headers(response: &URIResponse) -> AuthResult { - response.http_headers().map_or_else( - || { - info!("No headers found in response"); - Err(AuthDataError::NotFound) - }, - |mut headers| match headers.get("saml-auth-status") { - Some(status) if status == "1" => { - let username = headers.get("saml-username").map(GString::into); - let prelogin_cookie = headers.get("prelogin-cookie").map(GString::into); - let portal_userauthcookie = headers.get("portal-userauthcookie").map(GString::into); - - if SamlAuthData::check(&username, &prelogin_cookie, &portal_userauthcookie) { - return Ok(SamlAuthData::new( - username.unwrap(), - prelogin_cookie, - portal_userauthcookie, - )); - } - - info!("Found invalid auth data in headers"); - Err(AuthDataError::Invalid) - } - Some(status) => { - info!("Found invalid SAML status: {} in headers", status); - Err(AuthDataError::Invalid) - } - None => { - info!("No saml-auth-status header found"); - Err(AuthDataError::NotFound) - } - }, - ) -} - -fn read_auth_data_from_body(main_resource: &WebResource, callback: F) -where - F: FnOnce(Result) + Send + 'static, -{ - main_resource.data(Cancellable::NONE, |data| match data { - Ok(data) => { - let html = String::from_utf8_lossy(&data); - callback(read_auth_data_from_html(&html)); - } - Err(err) => { - info!("Failed to read response body: {}", err); - callback(Err(AuthDataParseError::Invalid)) - } - }); -} - -fn read_auth_data_from_html(html: &str) -> Result { - if html.contains("Temporarily Unavailable") { - info!("Found 'Temporarily Unavailable' in HTML, auth failed"); - return Err(AuthDataParseError::Invalid); - } - - SamlAuthData::from_html(html).or_else(|err| { - if let Some(gpcallback) = extract_gpcallback(html) { - info!("Found gpcallback from html..."); - SamlAuthData::from_gpcallback(&gpcallback) - } else { - Err(err) - } - }) -} - -fn extract_gpcallback(html: &str) -> Option { - let re = Regex::new(r#"globalprotectcallback:[^"]+"#).unwrap(); - re.captures(html) - .and_then(|captures| captures.get(0)) - .map(|m| html_escape::decode_html_entities(m.as_str()).to_string()) -} - -fn read_auth_data(main_resource: &WebResource, auth_result_tx: mpsc::UnboundedSender) { - let Some(response) = main_resource.response() else { - info!("No response found in main resource"); - send_auth_result(&auth_result_tx, Err(AuthDataError::Invalid)); - return; - }; - - info!("Trying to read auth data from response headers..."); - - match read_auth_data_from_headers(&response) { - Ok(auth_data) => { - info!("Got auth data from headers"); - send_auth_result(&auth_result_tx, Ok(auth_data)); - } - Err(AuthDataError::Invalid) => { - info!("Found invalid auth data in headers, trying to read from body..."); - read_auth_data_from_body(main_resource, move |auth_result| { - // Since we have already found invalid auth data in headers, which means this could be the `/SAML20/SP/ACS` endpoint - // any error result from body should be considered as invalid, and trigger a retry - let auth_result = auth_result.map_err(|err| { - info!("Failed to read auth data from body: {}", err); - AuthDataError::Invalid - }); - send_auth_result(&auth_result_tx, auth_result); - }); - } - Err(AuthDataError::NotFound) => { - info!("No auth data found in headers, trying to read from body..."); - - let is_acs_endpoint = main_resource.uri().map_or(false, |uri| uri.contains("/SAML20/SP/ACS")); - - read_auth_data_from_body(main_resource, move |auth_result| { - // If the endpoint is `/SAML20/SP/ACS` and no auth data found in body, it should be considered as invalid - let auth_result = auth_result.map_err(|err| { - info!("Failed to read auth data from body: {}", err); - - if !is_acs_endpoint && matches!(err, AuthDataParseError::NotFound) { - AuthDataError::NotFound - } else { - AuthDataError::Invalid - } - }); - - send_auth_result(&auth_result_tx, auth_result) - }); - } - Err(AuthDataError::TlsError) => { - // NOTE: This is unreachable - info!("TLS error found in headers, trying to read from body..."); - send_auth_result(&auth_result_tx, Err(AuthDataError::TlsError)); - } - } -} - -pub(crate) async fn clear_webview_cookies(window: &Window) -> anyhow::Result<()> { - let (tx, rx) = oneshot::channel::>(); - - window.with_webview(|wv| { - let send_result = move |result: Result<(), String>| { - if let Err(err) = tx.send(result) { - info!("Failed to send result: {:?}", err); - } - }; - - let wv = wv.inner(); - let context = match wv.context() { - Some(context) => context, - None => { - send_result(Err("No webview context found".into())); - return; - } - }; - let data_manager = match context.website_data_manager() { - Some(manager) => manager, - None => { - send_result(Err("No data manager found".into())); - return; - } - }; - - let now = Instant::now(); - data_manager.clear( - WebsiteDataTypes::COOKIES, - TimeSpan(0), - Cancellable::NONE, - move |result| match result { - Err(err) => { - send_result(Err(err.to_string())); - } - Ok(_) => { - info!("Cookies cleared in {} ms", now.elapsed().as_millis()); - send_result(Ok(())); - } - }, - ); - })?; - - rx.await?.map_err(|err| anyhow::anyhow!(err)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn extract_gpcallback_some() { - let html = r#" - - - "#; - - assert_eq!( - extract_gpcallback(html).as_deref(), - Some("globalprotectcallback:PGh0bWw+PCEtLSA8c") - ); - } - - #[test] - fn extract_gpcallback_cas() { - let html = r#" - - "#; - - assert_eq!( - extract_gpcallback(html).as_deref(), - Some("globalprotectcallback:cas-as=1&un=xyz@email.com&token=very_long_string") - ); - } - - #[test] - fn extract_gpcallback_none() { - let html = r#" - - "#; - - assert_eq!(extract_gpcallback(html), None); - } -} diff --git a/apps/gpauth/src/auth_window1.rs b/apps/gpauth/src/auth_window1.rs new file mode 100644 index 0000000..c326195 --- /dev/null +++ b/apps/gpauth/src/auth_window1.rs @@ -0,0 +1,523 @@ +use std::{ + rc::Rc, + sync::Arc, + time::{Duration, Instant}, +}; + +use anyhow::bail; +use gpapi::{ + auth::SamlAuthData, + error::AuthDataParseError, + gp_params::GpParams, + portal::{prelogin, Prelogin}, + utils::{redact::redact_uri, window::WindowExt}, +}; +use log::{info, warn}; +use regex::Regex; +use tauri::{AppHandle, Window, WindowEvent, WindowUrl}; +use tokio::sync::{mpsc, oneshot, RwLock}; +use tokio_util::sync::CancellationToken; +use webkit2gtk::{ + gio::Cancellable, + glib::{GString, TimeSpan}, + LoadEvent, SettingsExt, TLSErrorsPolicy, URIResponse, URIResponseExt, WebContextExt, WebResource, WebResourceExt, + WebView, WebViewExt, WebsiteDataManagerExtManual, WebsiteDataTypes, +}; + +enum AuthDataError { + /// Failed to load page due to TLS error + TlsError, + /// 1. Found auth data in headers/body but it's invalid + /// 2. Loaded an empty page, failed to load page. etc. + Invalid, + /// No auth data found in headers/body + NotFound, +} + +type AuthResult = Result; + +pub(crate) struct AuthWindow<'a> { + app_handle: AppHandle, + server: &'a str, + saml_request: &'a str, + user_agent: &'a str, + gp_params: Option, + clean: bool, +} + +impl<'a> AuthWindow<'a> { + pub fn new(app_handle: AppHandle) -> Self { + Self { + app_handle, + server: "", + saml_request: "", + user_agent: "", + gp_params: None, + clean: false, + } + } + + pub fn server(mut self, server: &'a str) -> Self { + self.server = server; + self + } + + pub fn saml_request(mut self, saml_request: &'a str) -> Self { + self.saml_request = saml_request; + self + } + + pub fn user_agent(mut self, user_agent: &'a str) -> Self { + self.user_agent = user_agent; + self + } + + pub fn gp_params(mut self, gp_params: GpParams) -> Self { + self.gp_params.replace(gp_params); + self + } + + pub fn clean(mut self, clean: bool) -> Self { + self.clean = clean; + self + } + + pub async fn open(&self) -> anyhow::Result { + info!("Open auth window, user_agent: {}", self.user_agent); + + let window = Window::builder(&self.app_handle, "auth_window", WindowUrl::default()) + .title("GlobalProtect Login") + // .user_agent(self.user_agent) + .focused(true) + .visible(false) + .center() + .build()?; + + let window = Arc::new(window); + + let cancel_token = CancellationToken::new(); + let cancel_token_clone = cancel_token.clone(); + + window.on_window_event(move |event| { + if let WindowEvent::CloseRequested { .. } = event { + cancel_token_clone.cancel(); + } + }); + + let window_clone = Arc::clone(&window); + let timeout_secs = 15; + tokio::spawn(async move { + tokio::time::sleep(Duration::from_secs(timeout_secs)).await; + let visible = window_clone.is_visible().unwrap_or(false); + if !visible { + info!("Try to raise auth window after {} seconds", timeout_secs); + raise_window(&window_clone); + } + }); + + tokio::select! { + _ = cancel_token.cancelled() => { + bail!("Auth cancelled"); + } + saml_result = self.auth_loop(&window) => { + window.close()?; + saml_result + } + } + } + + async fn auth_loop(&self, window: &Arc) -> anyhow::Result { + let saml_request = self.saml_request.to_string(); + let (auth_result_tx, mut auth_result_rx) = mpsc::unbounded_channel::(); + let raise_window_cancel_token: Arc>> = Default::default(); + let gp_params = self.gp_params.as_ref().unwrap(); + let tls_err_policy = if gp_params.ignore_tls_errors() { + TLSErrorsPolicy::Ignore + } else { + TLSErrorsPolicy::Fail + }; + + if self.clean { + clear_webview_cookies(window).await?; + } + + let raise_window_cancel_token_clone = Arc::clone(&raise_window_cancel_token); + window.with_webview(move |wv| { + let wv = wv.inner(); + + if let Some(context) = wv.context() { + context.set_tls_errors_policy(tls_err_policy); + } + + if let Some(settings) = wv.settings() { + let ua = settings.user_agent().unwrap_or("".into()); + info!("Auth window user agent: {}", ua); + } + + // Load the initial SAML request + load_saml_request(&wv, &saml_request); + + let auth_result_tx_clone = auth_result_tx.clone(); + wv.connect_load_changed(move |wv, event| { + if event == LoadEvent::Started { + let Ok(mut cancel_token) = raise_window_cancel_token_clone.try_write() else { + return; + }; + + // Cancel the raise window task + if let Some(cancel_token) = cancel_token.take() { + cancel_token.cancel(); + } + return; + } + + if event != LoadEvent::Finished { + return; + } + + if let Some(main_resource) = wv.main_resource() { + let uri = main_resource.uri().unwrap_or("".into()); + + if uri.is_empty() { + warn!("Loaded an empty uri"); + send_auth_result(&auth_result_tx_clone, Err(AuthDataError::Invalid)); + return; + } + + info!("Loaded uri: {}", redact_uri(&uri)); + if uri.starts_with("globalprotectcallback:") { + return; + } + + read_auth_data(&main_resource, auth_result_tx_clone.clone()); + } + }); + + let auth_result_tx_clone = auth_result_tx.clone(); + wv.connect_load_failed_with_tls_errors(move |_wv, uri, cert, err| { + let redacted_uri = redact_uri(uri); + warn!( + "Failed to load uri: {} with error: {}, cert: {}", + redacted_uri, err, cert + ); + + send_auth_result(&auth_result_tx_clone, Err(AuthDataError::TlsError)); + true + }); + + wv.connect_load_failed(move |_wv, _event, uri, err| { + let redacted_uri = redact_uri(uri); + if !uri.starts_with("globalprotectcallback:") { + warn!("Failed to load uri: {} with error: {}", redacted_uri, err); + } + // NOTE: Don't send error here, since load_changed event will be triggered after this + // send_auth_result(&auth_result_tx, Err(AuthDataError::Invalid)); + // true to stop other handlers from being invoked for the event. false to propagate the event further. + true + }); + })?; + + let portal = self.server.to_string(); + + loop { + if let Some(auth_result) = auth_result_rx.recv().await { + match auth_result { + Ok(auth_data) => return Ok(auth_data), + Err(AuthDataError::TlsError) => bail!("TLS error: certificate verify failed"), + Err(AuthDataError::NotFound) => { + info!("No auth data found, it may not be the /SAML20/SP/ACS endpoint"); + + // The user may need to interact with the auth window, raise it in 3 seconds + if !window.is_visible().unwrap_or(false) { + let window = Arc::clone(window); + let cancel_token = CancellationToken::new(); + + raise_window_cancel_token.write().await.replace(cancel_token.clone()); + + tokio::spawn(async move { + let delay_secs = 1; + + info!("Raise window in {} second(s)", delay_secs); + tokio::select! { + _ = tokio::time::sleep(Duration::from_secs(delay_secs)) => { + raise_window(&window); + } + _ = cancel_token.cancelled() => { + info!("Raise window cancelled"); + } + } + }); + } + } + Err(AuthDataError::Invalid) => { + info!("Got invalid auth data, retrying..."); + + window.with_webview(|wv| { + let wv = wv.inner(); + wv.run_javascript(r#" + var loading = document.createElement("div"); + loading.innerHTML = '
Got invalid token, retrying...
'; + loading.style = "position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.85); z-index: 99999;"; + document.body.appendChild(loading); + "#, + Cancellable::NONE, + |_| info!("Injected loading element successfully"), + ); + })?; + + let saml_request = portal_prelogin(&portal, gp_params).await?; + window.with_webview(move |wv| { + let wv = wv.inner(); + load_saml_request(&wv, &saml_request); + })?; + } + } + } + } + } +} + +fn raise_window(window: &Arc) { + let visible = window.is_visible().unwrap_or(false); + if !visible { + if let Err(err) = window.raise() { + warn!("Failed to raise window: {}", err); + } + } +} + +pub async fn portal_prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result { + match prelogin(portal, gp_params).await? { + Prelogin::Saml(prelogin) => Ok(prelogin.saml_request().to_string()), + Prelogin::Standard(_) => bail!("Received non-SAML prelogin response"), + } +} + +fn send_auth_result(auth_result_tx: &mpsc::UnboundedSender, auth_result: AuthResult) { + if let Err(err) = auth_result_tx.send(auth_result) { + warn!("Failed to send auth event: {}", err); + } +} + +fn load_saml_request(wv: &Rc, saml_request: &str) { + if saml_request.starts_with("http") { + info!("Load the SAML request as URI..."); + wv.load_uri(saml_request); + } else { + info!("Load the SAML request as HTML..."); + wv.load_html(saml_request, None); + } +} + +fn read_auth_data_from_headers(response: &URIResponse) -> AuthResult { + response.http_headers().map_or_else( + || { + info!("No headers found in response"); + Err(AuthDataError::NotFound) + }, + |mut headers| match headers.get("saml-auth-status") { + Some(status) if status == "1" => { + let username = headers.get("saml-username").map(GString::into); + let prelogin_cookie = headers.get("prelogin-cookie").map(GString::into); + let portal_userauthcookie = headers.get("portal-userauthcookie").map(GString::into); + + if SamlAuthData::check(&username, &prelogin_cookie, &portal_userauthcookie) { + return Ok(SamlAuthData::new( + username.unwrap(), + prelogin_cookie, + portal_userauthcookie, + )); + } + + info!("Found invalid auth data in headers"); + Err(AuthDataError::Invalid) + } + Some(status) => { + info!("Found invalid SAML status: {} in headers", status); + Err(AuthDataError::Invalid) + } + None => { + info!("No saml-auth-status header found"); + Err(AuthDataError::NotFound) + } + }, + ) +} + +fn read_auth_data_from_body(main_resource: &WebResource, callback: F) +where + F: FnOnce(Result) + Send + 'static, +{ + main_resource.data(Cancellable::NONE, |data| match data { + Ok(data) => { + let html = String::from_utf8_lossy(&data); + callback(read_auth_data_from_html(&html)); + } + Err(err) => { + info!("Failed to read response body: {}", err); + callback(Err(AuthDataParseError::Invalid)) + } + }); +} + +fn read_auth_data_from_html(html: &str) -> Result { + if html.contains("Temporarily Unavailable") { + info!("Found 'Temporarily Unavailable' in HTML, auth failed"); + return Err(AuthDataParseError::Invalid); + } + + SamlAuthData::from_html(html).or_else(|err| { + if let Some(gpcallback) = extract_gpcallback(html) { + info!("Found gpcallback from html..."); + SamlAuthData::from_gpcallback(&gpcallback) + } else { + Err(err) + } + }) +} + +fn extract_gpcallback(html: &str) -> Option { + let re = Regex::new(r#"globalprotectcallback:[^"]+"#).unwrap(); + re.captures(html) + .and_then(|captures| captures.get(0)) + .map(|m| html_escape::decode_html_entities(m.as_str()).to_string()) +} + +fn read_auth_data(main_resource: &WebResource, auth_result_tx: mpsc::UnboundedSender) { + let Some(response) = main_resource.response() else { + info!("No response found in main resource"); + send_auth_result(&auth_result_tx, Err(AuthDataError::Invalid)); + return; + }; + + info!("Trying to read auth data from response headers..."); + + match read_auth_data_from_headers(&response) { + Ok(auth_data) => { + info!("Got auth data from headers"); + send_auth_result(&auth_result_tx, Ok(auth_data)); + } + Err(AuthDataError::Invalid) => { + info!("Found invalid auth data in headers, trying to read from body..."); + read_auth_data_from_body(main_resource, move |auth_result| { + // Since we have already found invalid auth data in headers, which means this could be the `/SAML20/SP/ACS` endpoint + // any error result from body should be considered as invalid, and trigger a retry + let auth_result = auth_result.map_err(|err| { + info!("Failed to read auth data from body: {}", err); + AuthDataError::Invalid + }); + send_auth_result(&auth_result_tx, auth_result); + }); + } + Err(AuthDataError::NotFound) => { + info!("No auth data found in headers, trying to read from body..."); + + let is_acs_endpoint = main_resource.uri().map_or(false, |uri| uri.contains("/SAML20/SP/ACS")); + + read_auth_data_from_body(main_resource, move |auth_result| { + // If the endpoint is `/SAML20/SP/ACS` and no auth data found in body, it should be considered as invalid + let auth_result = auth_result.map_err(|err| { + info!("Failed to read auth data from body: {}", err); + + if !is_acs_endpoint && matches!(err, AuthDataParseError::NotFound) { + AuthDataError::NotFound + } else { + AuthDataError::Invalid + } + }); + + send_auth_result(&auth_result_tx, auth_result) + }); + } + Err(AuthDataError::TlsError) => { + // NOTE: This is unreachable + info!("TLS error found in headers, trying to read from body..."); + send_auth_result(&auth_result_tx, Err(AuthDataError::TlsError)); + } + } +} + +pub(crate) async fn clear_webview_cookies(window: &Window) -> anyhow::Result<()> { + let (tx, rx) = oneshot::channel::>(); + + window.with_webview(|wv| { + let send_result = move |result: Result<(), String>| { + if let Err(err) = tx.send(result) { + info!("Failed to send result: {:?}", err); + } + }; + + let wv = wv.inner(); + let context = match wv.context() { + Some(context) => context, + None => { + send_result(Err("No webview context found".into())); + return; + } + }; + let data_manager = match context.website_data_manager() { + Some(manager) => manager, + None => { + send_result(Err("No data manager found".into())); + return; + } + }; + + let now = Instant::now(); + data_manager.clear( + WebsiteDataTypes::COOKIES, + TimeSpan(0), + Cancellable::NONE, + move |result| match result { + Err(err) => { + send_result(Err(err.to_string())); + } + Ok(_) => { + info!("Cookies cleared in {} ms", now.elapsed().as_millis()); + send_result(Ok(())); + } + }, + ); + })?; + + rx.await?.map_err(|err| anyhow::anyhow!(err)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn extract_gpcallback_some() { + let html = r#" + + + "#; + + assert_eq!( + extract_gpcallback(html).as_deref(), + Some("globalprotectcallback:PGh0bWw+PCEtLSA8c") + ); + } + + #[test] + fn extract_gpcallback_cas() { + let html = r#" + + "#; + + assert_eq!( + extract_gpcallback(html).as_deref(), + Some("globalprotectcallback:cas-as=1&un=xyz@email.com&token=very_long_string") + ); + } + + #[test] + fn extract_gpcallback_none() { + let html = r#" + + "#; + + assert_eq!(extract_gpcallback(html), None); + } +} diff --git a/apps/gpauth/src/cli.rs b/apps/gpauth/src/cli.rs index 3142af5..95caffa 100644 --- a/apps/gpauth/src/cli.rs +++ b/apps/gpauth/src/cli.rs @@ -1,21 +1,17 @@ -use std::{env::temp_dir, fs, os::unix::fs::PermissionsExt}; - use clap::Parser; use gpapi::{ - auth::{SamlAuthData, SamlAuthResult}, - clap::args::Os, + auth::SamlAuthResult, + clap::{args::Os, handle_error, Args}, gp_params::{ClientOs, GpParams}, process::browser_authenticator::BrowserAuthenticator, utils::{env_utils, normalize_server, openssl}, GP_USER_AGENT, }; +use gpauth::auth_window::{portal_prelogin, AuthWindow}; use log::{info, LevelFilter}; use serde_json::json; -use tauri::{App, AppHandle, RunEvent}; +use tauri::RunEvent; use tempfile::NamedTempFile; -use tokio::{io::AsyncReadExt, net::TcpListener}; - -use crate::auth_window::{portal_prelogin, AuthWindow}; const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", compile_time::date_str!(), ")"); @@ -78,65 +74,17 @@ struct Cli { browser: Option, } -impl Cli { - async fn run(&mut self) -> anyhow::Result<()> { - if self.ignore_tls_errors { - info!("TLS errors will be ignored"); - } - - let mut openssl_conf = self.prepare_env()?; - - self.server = normalize_server(&self.server)?; - let gp_params = self.build_gp_params(); - - // Get the initial SAML request - let saml_request = match self.saml_request { - Some(ref saml_request) => saml_request.clone(), - None => portal_prelogin(&self.server, &gp_params).await?, - }; - - let browser_auth = if let Some(browser) = &self.browser { - Some(BrowserAuthenticator::new_with_browser(&saml_request, browser)) - } else if self.default_browser { - Some(BrowserAuthenticator::new(&saml_request)) - } else { - None - }; - - if let Some(browser_auth) = browser_auth { - browser_auth.authenticate()?; - - info!("Please continue the authentication process in the default browser"); - - let auth_result = match wait_auth_data().await { - Ok(auth_data) => SamlAuthResult::Success(auth_data), - Err(err) => SamlAuthResult::Failure(format!("{}", err)), - }; - - info!("Authentication completed"); - - println!("{}", json!(auth_result)); - - return Ok(()); - } - - self.saml_request.replace(saml_request); - - let app = create_app(self.clone())?; - - app.run(move |_app_handle, event| { - if let RunEvent::Exit = event { - if let Some(file) = openssl_conf.take() { - if let Err(err) = file.close() { - info!("Error closing OpenSSL config file: {}", err); - } - } - } - }); - - Ok(()) +impl Args for Cli { + fn fix_openssl(&self) -> bool { + self.fix_openssl } + fn ignore_tls_errors(&self) -> bool { + self.ignore_tls_errors + } +} + +impl Cli { fn prepare_env(&self) -> anyhow::Result> { env_utils::patch_gui_runtime_env(self.hidpi); @@ -150,6 +98,68 @@ impl Cli { Ok(None) } + async fn run(&self) -> anyhow::Result<()> { + if self.ignore_tls_errors { + info!("TLS errors will be ignored"); + } + + let mut openssl_conf = self.prepare_env()?; + + let server = normalize_server(&self.server)?; + let gp_params = self.build_gp_params(); + + let saml_request = match &self.saml_request { + Some(saml_request) => saml_request.to_string(), + None => portal_prelogin(&server, &gp_params).await?, + }; + + let browser_auth = if let Some(browser) = &self.browser { + Some(BrowserAuthenticator::new_with_browser(&saml_request, browser)) + } else if self.default_browser { + Some(BrowserAuthenticator::new(&saml_request)) + } else { + None + }; + + if let Some(browser_auth) = browser_auth { + return Ok(()); + } + + let clean = self.clean; + tauri::Builder::default() + .setup(move |app| { + let app_handle = app.handle().clone(); + + tauri::async_runtime::spawn(async move { + let auth_window = AuthWindow::new(&app_handle, &server) + .with_gp_params(&gp_params) + .with_saml_request(&saml_request) + .with_clean(clean); + + let auth_result = match auth_window.authenticate().await { + Ok(auth_data) => SamlAuthResult::Success(auth_data), + Err(err) => SamlAuthResult::Failure(format!("{}", err)), + }; + + println!("{}", json!(auth_result)); + }); + + Ok(()) + }) + .build(tauri::generate_context!())? + .run(move |_app_handle, event| { + if let RunEvent::Exit = event { + if let Some(file) = openssl_conf.take() { + if let Err(err) = file.close() { + info!("Error closing OpenSSL config file: {}", err); + } + } + } + }); + + Ok(()) + } + fn build_gp_params(&self) -> GpParams { let gp_params = GpParams::builder() .user_agent(&self.user_agent) @@ -161,37 +171,6 @@ impl Cli { gp_params } - - async fn saml_auth(&self, app_handle: AppHandle) -> anyhow::Result { - let auth_window = AuthWindow::new(app_handle) - .server(&self.server) - .user_agent(&self.user_agent) - .gp_params(self.build_gp_params()) - .saml_request(self.saml_request.as_ref().unwrap()) - .clean(self.clean); - - auth_window.open().await - } -} - -fn create_app(cli: Cli) -> anyhow::Result { - let app = tauri::Builder::default() - .setup(|app| { - let app_handle = app.handle(); - - tauri::async_runtime::spawn(async move { - let auth_result = match cli.saml_auth(app_handle.clone()).await { - Ok(auth_data) => SamlAuthResult::Success(auth_data), - Err(err) => SamlAuthResult::Failure(format!("{}", err)), - }; - - println!("{}", json!(auth_result)); - }); - Ok(()) - }) - .build(tauri::generate_context!())?; - - Ok(app) } fn init_logger() { @@ -199,53 +178,13 @@ fn init_logger() { } pub async fn run() { - let mut cli = Cli::parse(); + let cli = Cli::parse(); init_logger(); info!("gpauth started: {}", VERSION); if let Err(err) = cli.run().await { - eprintln!("\nError: {}", err); - - if err.to_string().contains("unsafe legacy renegotiation") && !cli.fix_openssl { - eprintln!("\nRe-run it with the `--fix-openssl` option to work around this issue, e.g.:\n"); - // Print the command - let args = std::env::args().collect::>(); - eprintln!("{} --fix-openssl {}\n", args[0], args[1..].join(" ")); - } - + handle_error(err, &cli); std::process::exit(1); } } - -async fn wait_auth_data() -> anyhow::Result { - // Start a local server to receive the browser authentication data - let listener = TcpListener::bind("127.0.0.1:0").await?; - let port = listener.local_addr()?.port(); - let port_file = temp_dir().join("gpcallback.port"); - - // Write the port to a file - fs::write(&port_file, port.to_string())?; - fs::set_permissions(&port_file, fs::Permissions::from_mode(0o600))?; - - // Remove the previous log file - let callback_log = temp_dir().join("gpcallback.log"); - let _ = fs::remove_file(&callback_log); - - info!("Listening authentication data on port {}", port); - info!( - "If it hangs, please check the logs at `{}` for more information", - callback_log.display() - ); - let (mut socket, _) = listener.accept().await?; - - info!("Received the browser authentication data from the socket"); - let mut data = String::new(); - socket.read_to_string(&mut data).await?; - - // Remove the port file - fs::remove_file(&port_file)?; - - let auth_data = SamlAuthData::from_gpcallback(&data)?; - Ok(auth_data) -} diff --git a/apps/gpauth/src/cli1.rs b/apps/gpauth/src/cli1.rs new file mode 100644 index 0000000..3142af5 --- /dev/null +++ b/apps/gpauth/src/cli1.rs @@ -0,0 +1,251 @@ +use std::{env::temp_dir, fs, os::unix::fs::PermissionsExt}; + +use clap::Parser; +use gpapi::{ + auth::{SamlAuthData, SamlAuthResult}, + clap::args::Os, + gp_params::{ClientOs, GpParams}, + process::browser_authenticator::BrowserAuthenticator, + utils::{env_utils, normalize_server, openssl}, + GP_USER_AGENT, +}; +use log::{info, LevelFilter}; +use serde_json::json; +use tauri::{App, AppHandle, RunEvent}; +use tempfile::NamedTempFile; +use tokio::{io::AsyncReadExt, net::TcpListener}; + +use crate::auth_window::{portal_prelogin, AuthWindow}; + +const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", compile_time::date_str!(), ")"); + +#[derive(Parser, Clone)] +#[command( + version = VERSION, + author, + about = "The authentication component for the GlobalProtect VPN client, supports the SSO authentication method.", + help_template = "\ +{before-help}{name} {version} +{author} + +{about} + +{usage-heading} {usage} + +{all-args}{after-help} + +See 'gpauth -h' for more information. +" +)] +struct Cli { + #[arg(help = "The portal server to authenticate")] + server: String, + + #[arg(long, help = "Treating the server as a gateway")] + gateway: bool, + + #[arg(long, help = "The SAML authentication request")] + saml_request: Option, + + #[arg(long, default_value = GP_USER_AGENT, help = "The user agent to use")] + user_agent: String, + + #[arg(long, default_value = "Linux")] + os: Os, + + #[arg(long)] + os_version: Option, + + #[arg(long, help = "The HiDPI mode, useful for high-resolution screens")] + hidpi: bool, + + #[arg(long, help = "Get around the OpenSSL `unsafe legacy renegotiation` error")] + fix_openssl: bool, + + #[arg(long, help = "Ignore TLS errors")] + ignore_tls_errors: bool, + + #[arg(long, help = "Clean the cache of the embedded browser")] + clean: bool, + + #[arg(long, help = "Use the default browser for authentication")] + default_browser: bool, + + #[arg( + long, + help = "The browser to use for authentication, e.g., `default`, `firefox`, `chrome`, `chromium`, or the path to the browser executable" + )] + browser: Option, +} + +impl Cli { + async fn run(&mut self) -> anyhow::Result<()> { + if self.ignore_tls_errors { + info!("TLS errors will be ignored"); + } + + let mut openssl_conf = self.prepare_env()?; + + self.server = normalize_server(&self.server)?; + let gp_params = self.build_gp_params(); + + // Get the initial SAML request + let saml_request = match self.saml_request { + Some(ref saml_request) => saml_request.clone(), + None => portal_prelogin(&self.server, &gp_params).await?, + }; + + let browser_auth = if let Some(browser) = &self.browser { + Some(BrowserAuthenticator::new_with_browser(&saml_request, browser)) + } else if self.default_browser { + Some(BrowserAuthenticator::new(&saml_request)) + } else { + None + }; + + if let Some(browser_auth) = browser_auth { + browser_auth.authenticate()?; + + info!("Please continue the authentication process in the default browser"); + + let auth_result = match wait_auth_data().await { + Ok(auth_data) => SamlAuthResult::Success(auth_data), + Err(err) => SamlAuthResult::Failure(format!("{}", err)), + }; + + info!("Authentication completed"); + + println!("{}", json!(auth_result)); + + return Ok(()); + } + + self.saml_request.replace(saml_request); + + let app = create_app(self.clone())?; + + app.run(move |_app_handle, event| { + if let RunEvent::Exit = event { + if let Some(file) = openssl_conf.take() { + if let Err(err) = file.close() { + info!("Error closing OpenSSL config file: {}", err); + } + } + } + }); + + Ok(()) + } + + fn prepare_env(&self) -> anyhow::Result> { + env_utils::patch_gui_runtime_env(self.hidpi); + + if self.fix_openssl { + info!("Fixing OpenSSL environment"); + let file = openssl::fix_openssl_env()?; + + return Ok(Some(file)); + } + + Ok(None) + } + + fn build_gp_params(&self) -> GpParams { + let gp_params = GpParams::builder() + .user_agent(&self.user_agent) + .client_os(ClientOs::from(&self.os)) + .os_version(self.os_version.clone()) + .ignore_tls_errors(self.ignore_tls_errors) + .is_gateway(self.gateway) + .build(); + + gp_params + } + + async fn saml_auth(&self, app_handle: AppHandle) -> anyhow::Result { + let auth_window = AuthWindow::new(app_handle) + .server(&self.server) + .user_agent(&self.user_agent) + .gp_params(self.build_gp_params()) + .saml_request(self.saml_request.as_ref().unwrap()) + .clean(self.clean); + + auth_window.open().await + } +} + +fn create_app(cli: Cli) -> anyhow::Result { + let app = tauri::Builder::default() + .setup(|app| { + let app_handle = app.handle(); + + tauri::async_runtime::spawn(async move { + let auth_result = match cli.saml_auth(app_handle.clone()).await { + Ok(auth_data) => SamlAuthResult::Success(auth_data), + Err(err) => SamlAuthResult::Failure(format!("{}", err)), + }; + + println!("{}", json!(auth_result)); + }); + Ok(()) + }) + .build(tauri::generate_context!())?; + + Ok(app) +} + +fn init_logger() { + env_logger::builder().filter_level(LevelFilter::Info).init(); +} + +pub async fn run() { + let mut cli = Cli::parse(); + + init_logger(); + info!("gpauth started: {}", VERSION); + + if let Err(err) = cli.run().await { + eprintln!("\nError: {}", err); + + if err.to_string().contains("unsafe legacy renegotiation") && !cli.fix_openssl { + eprintln!("\nRe-run it with the `--fix-openssl` option to work around this issue, e.g.:\n"); + // Print the command + let args = std::env::args().collect::>(); + eprintln!("{} --fix-openssl {}\n", args[0], args[1..].join(" ")); + } + + std::process::exit(1); + } +} + +async fn wait_auth_data() -> anyhow::Result { + // Start a local server to receive the browser authentication data + let listener = TcpListener::bind("127.0.0.1:0").await?; + let port = listener.local_addr()?.port(); + let port_file = temp_dir().join("gpcallback.port"); + + // Write the port to a file + fs::write(&port_file, port.to_string())?; + fs::set_permissions(&port_file, fs::Permissions::from_mode(0o600))?; + + // Remove the previous log file + let callback_log = temp_dir().join("gpcallback.log"); + let _ = fs::remove_file(&callback_log); + + info!("Listening authentication data on port {}", port); + info!( + "If it hangs, please check the logs at `{}` for more information", + callback_log.display() + ); + let (mut socket, _) = listener.accept().await?; + + info!("Received the browser authentication data from the socket"); + let mut data = String::new(); + socket.read_to_string(&mut data).await?; + + // Remove the port file + fs::remove_file(&port_file)?; + + let auth_data = SamlAuthData::from_gpcallback(&data)?; + Ok(auth_data) +} diff --git a/apps/gpauth/src/common.rs b/apps/gpauth/src/common.rs new file mode 100644 index 0000000..11df0f3 --- /dev/null +++ b/apps/gpauth/src/common.rs @@ -0,0 +1,135 @@ +use std::sync::Arc; + +use gpapi::{ + auth::{AuthDataParseResult, SamlAuthData}, + error::AuthDataParseError, +}; +use log::{info, warn}; +use regex::Regex; + +use crate::auth_messenger::{AuthError, AuthMessenger}; + +pub struct AuthSettings<'a> { + pub auth_request: AuthRequest<'a>, + pub auth_messenger: Arc, + pub ignore_tls_errors: bool, +} + +pub struct AuthRequest<'a>(&'a str); + +impl<'a> AuthRequest<'a> { + pub fn new(auth_request: &'a str) -> Self { + Self(auth_request) + } + + pub fn is_url(&self) -> bool { + self.0.starts_with("http") + } + + pub fn as_str(&self) -> &str { + self.0 + } +} + +/// Trait for handling authentication response +pub trait AuthResponse { + fn get_header(&self, key: &str) -> Option; + fn get_body(&self, cb: F) + where + F: FnOnce(anyhow::Result>) + 'static; + + fn url(&self) -> Option; + + fn is_acs_endpoint(&self) -> bool { + self.url().map_or(false, |url| url.ends_with("/SAML20/SP/ACS")) + } +} + +pub fn read_auth_data(auth_response: &impl AuthResponse, auth_messenger: &Arc) { + let auth_messenger = Arc::clone(auth_messenger); + + match read_from_headers(auth_response) { + Ok(auth_data) => { + info!("Found auth data in headers"); + auth_messenger.send_auth_data(auth_data); + } + Err(header_err) => { + info!("Failed to read auth data from headers: {}", header_err); + + let is_acs_endpoint = auth_response.is_acs_endpoint(); + read_from_body(auth_response, move |auth_result| { + // If the endpoint is `/SAML20/SP/ACS` and no auth data found in body, it should be considered as invalid + let auth_result = auth_result.map_err(move |e| { + info!("Failed to read auth data from body: {}", e); + if is_acs_endpoint || e.is_invalid() || header_err.is_invalid() { + AuthError::Invalid + } else { + AuthError::NotFound + } + }); + + auth_messenger.send_auth_result(auth_result); + }); + } + } +} + +fn read_from_headers(auth_response: &impl AuthResponse) -> AuthDataParseResult { + let Some(status) = auth_response.get_header("saml-auth-status") else { + info!("No SAML auth status found in headers"); + return Err(AuthDataParseError::NotFound); + }; + + if status != "1" { + info!("Found invalid auth status: {}", status); + return Err(AuthDataParseError::Invalid); + } + + let username = auth_response.get_header("saml-username"); + let prelogin_cookie = auth_response.get_header("prelogin-cookie"); + let portal_userauthcookie = auth_response.get_header("portal-userauthcookie"); + + SamlAuthData::new(username, prelogin_cookie, portal_userauthcookie).map_err(|e| { + warn!("Found invalid auth data: {}", e); + AuthDataParseError::Invalid + }) +} + +fn read_from_body(auth_response: &impl AuthResponse, cb: F) +where + F: FnOnce(AuthDataParseResult) + 'static, +{ + auth_response.get_body(|body| match body { + Ok(body) => { + let html = String::from_utf8_lossy(&body); + cb(read_from_html(&html)) + } + Err(err) => { + info!("Failed to read body: {}", err); + cb(Err(AuthDataParseError::Invalid)) + } + }); +} + +fn read_from_html(html: &str) -> AuthDataParseResult { + if html.contains("Temporarily Unavailable") { + info!("Found 'Temporarily Unavailable' in HTML, auth failed"); + return Err(AuthDataParseError::Invalid); + } + + SamlAuthData::from_html(html).or_else(|err| { + if let Some(gpcallback) = extract_gpcallback(html) { + info!("Found gpcallback from html..."); + SamlAuthData::from_gpcallback(&gpcallback) + } else { + Err(err) + } + }) +} + +fn extract_gpcallback(html: &str) -> Option { + let re = Regex::new(r#"globalprotectcallback:[^"]+"#).unwrap(); + re.captures(html) + .and_then(|captures| captures.get(0)) + .map(|m| html_escape::decode_html_entities(m.as_str()).to_string()) +} diff --git a/apps/gpauth/src/lib.rs b/apps/gpauth/src/lib.rs new file mode 100644 index 0000000..dd4b670 --- /dev/null +++ b/apps/gpauth/src/lib.rs @@ -0,0 +1,7 @@ +mod auth_messenger; +mod common; + +pub mod auth_window; + +#[cfg_attr(not(target_os = "macos"), path = "unix.rs")] +mod platform_impl; diff --git a/apps/gpauth/src/main.rs b/apps/gpauth/src/main.rs index 74f86ec..05ae684 100644 --- a/apps/gpauth/src/main.rs +++ b/apps/gpauth/src/main.rs @@ -1,6 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -mod auth_window; mod cli; #[tokio::main] diff --git a/apps/gpauth/src/unix.rs b/apps/gpauth/src/unix.rs new file mode 100644 index 0000000..3e67450 --- /dev/null +++ b/apps/gpauth/src/unix.rs @@ -0,0 +1,133 @@ +use std::sync::Arc; + +use anyhow::bail; +use gpapi::utils::redact::redact_uri; +use log::{info, warn}; +use webkit2gtk::{ + gio::Cancellable, + glib::{GString, TimeSpan}, + LoadEvent, TLSErrorsPolicy, URIResponseExt, WebResource, WebResourceExt, WebView, WebViewExt, WebsiteDataManagerExt, + WebsiteDataManagerExtManual, WebsiteDataTypes, +}; + +use crate::{ + auth_messenger::AuthError, + common::{read_auth_data, AuthRequest, AuthResponse, AuthSettings}, +}; + +impl AuthResponse for WebResource { + fn get_header(&self, key: &str) -> Option { + self + .response() + .and_then(|response| response.http_headers()) + .and_then(|headers| headers.one(key)) + .map(GString::into) + } + + fn get_body(&self, cb: F) + where + F: FnOnce(anyhow::Result>) + 'static, + { + let cancellable = Cancellable::NONE; + self.data(cancellable, |data| cb(data.map_err(|e| anyhow::anyhow!(e)))); + } + + fn url(&self) -> Option { + self.uri().map(GString::into) + } +} + +pub fn clear_data(wv: &WebView, cb: F) +where + F: FnOnce(anyhow::Result<()>) + Send + 'static, +{ + let Some(data_manager) = wv.website_data_manager() else { + cb(Err(anyhow::anyhow!("Failed to get website data manager"))); + return; + }; + + data_manager.clear( + WebsiteDataTypes::COOKIES, + TimeSpan(0), + Cancellable::NONE, + move |result| { + cb(result.map_err(|e| anyhow::anyhow!(e))); + }, + ); +} + +pub fn setup_webview(wv: &WebView, auth_settings: AuthSettings) -> anyhow::Result<()> { + let AuthSettings { + auth_request, + auth_messenger, + ignore_tls_errors, + } = auth_settings; + let auth_messenger_clone = Arc::clone(&auth_messenger); + + let Some(data_manager) = wv.website_data_manager() else { + bail!("Failed to get website data manager"); + }; + + if ignore_tls_errors { + data_manager.set_tls_errors_policy(TLSErrorsPolicy::Ignore); + } + + wv.connect_load_changed(move |wv, event| { + if event == LoadEvent::Started { + // TODO: + return; + } + + if event != LoadEvent::Finished { + return; + } + + let Some(main_resource) = wv.main_resource() else { + return; + }; + + let uri = main_resource.uri().unwrap_or("".into()); + if uri.is_empty() { + warn!("Loaded an empty URI"); + auth_messenger_clone.send_auth_error(AuthError::Invalid); + return; + } + + read_auth_data(&main_resource, &auth_messenger_clone); + }); + + wv.connect_load_failed_with_tls_errors(move |_wv, uri, cert, err| { + let redacted_uri = redact_uri(uri); + warn!( + "Failed to load uri: {} with error: {}, cert: {}", + redacted_uri, err, cert + ); + + auth_messenger.send_auth_error(AuthError::TlsError); + true + }); + + wv.connect_load_failed(move |_wv, _event, uri, err| { + let redacted_uri = redact_uri(uri); + if !uri.starts_with("globalprotectcallback:") { + warn!("Failed to load uri: {} with error: {}", redacted_uri, err); + } + // NOTE: Don't send error here, since load_changed event will be triggered after this + // true to stop other handlers from being invoked for the event. false to propagate the event further. + true + }); + + load_auth_request(wv, &auth_request); + + Ok(()) +} + +pub fn load_auth_request(wv: &WebView, auth_request: &AuthRequest) { + if auth_request.is_url() { + info!("Loading auth request as URI..."); + wv.load_uri(auth_request.as_str()); + } else { + info!("Loading auth request as HTML..."); + wv.load_html(auth_request.as_str(), None); + } +} diff --git a/apps/gpauth/tauri.conf.json b/apps/gpauth/tauri.conf.json index 5eab86d..f446cb7 100644 --- a/apps/gpauth/tauri.conf.json +++ b/apps/gpauth/tauri.conf.json @@ -1,47 +1,16 @@ { - "$schema": "https://cdn.jsdelivr.net/gh/tauri-apps/tauri@tauri-v1.5.0/tooling/cli/schema.json", + "$schema": "https://cdn.jsdelivr.net/gh/tauri-apps/tauri@tauri-v2.1.1/crates/tauri-cli/config.schema.json", "build": { - "distDir": [ - "index.html" - ], - "devPath": [ - "index.html" - ], + "frontendDist": ["index.html"], "beforeDevCommand": "", - "beforeBuildCommand": "", - "withGlobalTauri": false + "beforeBuildCommand": "" }, - "package": { - "productName": "gpauth", - "version": "0.0.0" - }, - "tauri": { - "allowlist": { - "all": false, - "http": { - "all": true, - "request": true, - "scope": [ - "http://*", - "https://*" - ] - } - }, - "bundle": { - "active": true, - "targets": "deb", - "identifier": "com.yuezk.gpauth", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ] - }, + "identifier": "com.yuezk.gpauth", + "productName": "gpauth", + "app": { + "withGlobalTauri": false, "security": { "csp": null - }, - "windows": [] + } } } diff --git a/apps/gpclient/src/cli.rs b/apps/gpclient/src/cli.rs index d4c55ce..005373b 100644 --- a/apps/gpclient/src/cli.rs +++ b/apps/gpclient/src/cli.rs @@ -1,7 +1,10 @@ use std::{env::temp_dir, fs::File}; use clap::{Parser, Subcommand}; -use gpapi::utils::openssl; +use gpapi::{ + clap::{handle_error, Args}, + utils::openssl, +}; use log::{info, LevelFilter}; use tempfile::NamedTempFile; @@ -50,12 +53,25 @@ struct Cli { #[command(subcommand)] command: CliCommand, - #[arg(long, help = "Uses extended compatibility mode for OpenSSL operations to support a broader range of systems and formats.")] + #[arg( + long, + help = "Uses extended compatibility mode for OpenSSL operations to support a broader range of systems and formats." + )] fix_openssl: bool, #[arg(long, help = "Ignore the TLS errors")] ignore_tls_errors: bool, } +impl Args for Cli { + fn fix_openssl(&self) -> bool { + self.fix_openssl + } + + fn ignore_tls_errors(&self) -> bool { + self.ignore_tls_errors + } +} + impl Cli { fn fix_openssl(&self) -> anyhow::Result> { if self.fix_openssl { @@ -113,24 +129,7 @@ pub(crate) async fn run() { info!("gpclient started: {}", VERSION); if let Err(err) = cli.run().await { - eprintln!("\nError: {}", err); - - let err = err.to_string(); - - if err.contains("unsafe legacy renegotiation") && !cli.fix_openssl { - eprintln!("\nRe-run it with the `--fix-openssl` option to work around this issue, e.g.:\n"); - // Print the command - let args = std::env::args().collect::>(); - eprintln!("{} --fix-openssl {}\n", args[0], args[1..].join(" ")); - } - - if err.contains("certificate verify failed") && !cli.ignore_tls_errors { - eprintln!("\nRe-run it with the `--ignore-tls-errors` option to ignore the certificate error, e.g.:\n"); - // Print the command - let args = std::env::args().collect::>(); - eprintln!("{} --ignore-tls-errors {}\n", args[0], args[1..].join(" ")); - } - + handle_error(err, &cli); std::process::exit(1); } } diff --git a/crates/gpapi/src/auth.rs b/crates/gpapi/src/auth.rs index 86ddcd4..dcabde3 100644 --- a/crates/gpapi/src/auth.rs +++ b/crates/gpapi/src/auth.rs @@ -1,11 +1,14 @@ use std::borrow::{Borrow, Cow}; +use anyhow::bail; use log::{info, warn}; use regex::Regex; use serde::{Deserialize, Serialize}; use crate::{error::AuthDataParseError, utils::base64::decode_to_string}; +pub type AuthDataParseResult = anyhow::Result; + #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SamlAuthData { @@ -33,33 +36,51 @@ impl SamlAuthResult { } impl SamlAuthData { - pub fn new(username: String, prelogin_cookie: Option, portal_userauthcookie: Option) -> Self { - Self { - username, - prelogin_cookie, - portal_userauthcookie, - token: None, + pub fn new( + username: Option, + prelogin_cookie: Option, + portal_userauthcookie: Option, + ) -> anyhow::Result { + let username = username.unwrap_or_default(); + if username.is_empty() { + bail!("Invalid username: "); } + + let prelogin_cookie = prelogin_cookie.unwrap_or_default(); + let portal_userauthcookie = portal_userauthcookie.unwrap_or_default(); + + if prelogin_cookie.len() <= 5 && portal_userauthcookie.len() <= 5 { + bail!( + "Invalid prelogin-cookie: {}, portal-userauthcookie: {}", + prelogin_cookie, + portal_userauthcookie + ); + } + + Ok(Self { + username, + prelogin_cookie: Some(prelogin_cookie), + portal_userauthcookie: Some(portal_userauthcookie), + token: None, + }) } - pub fn from_html(html: &str) -> anyhow::Result { + pub fn from_html(html: &str) -> AuthDataParseResult { match parse_xml_tag(html, "saml-auth-status") { - Some(saml_status) if saml_status == "1" => { + Some(status) if status == "1" => { let username = parse_xml_tag(html, "saml-username"); let prelogin_cookie = parse_xml_tag(html, "prelogin-cookie"); let portal_userauthcookie = parse_xml_tag(html, "portal-userauthcookie"); - if SamlAuthData::check(&username, &prelogin_cookie, &portal_userauthcookie) { - Ok(SamlAuthData::new( - username.unwrap(), - prelogin_cookie, - portal_userauthcookie, - )) - } else { - Err(AuthDataParseError::Invalid) - } + SamlAuthData::new(username, prelogin_cookie, portal_userauthcookie).map_err(|e| { + warn!("Failed to parse auth data: {}", e); + AuthDataParseError::Invalid + }) + } + Some(status) => { + warn!("Found invalid auth status: {}", status); + Err(AuthDataParseError::Invalid) } - Some(_) => Err(AuthDataParseError::Invalid), None => Err(AuthDataParseError::NotFound), } } @@ -105,27 +126,6 @@ impl SamlAuthData { pub fn token(&self) -> Option<&str> { self.token.as_deref() } - - pub fn check( - username: &Option, - prelogin_cookie: &Option, - portal_userauthcookie: &Option, - ) -> bool { - let username_valid = username.as_ref().is_some_and(|username| !username.is_empty()); - let prelogin_cookie_valid = prelogin_cookie.as_ref().is_some_and(|val| val.len() > 5); - let portal_userauthcookie_valid = portal_userauthcookie.as_ref().is_some_and(|val| val.len() > 5); - - let is_valid = username_valid && (prelogin_cookie_valid || portal_userauthcookie_valid); - - if !is_valid { - warn!( - "Invalid SAML auth data: username: {:?}, prelogin-cookie: {:?}, portal-userauthcookie: {:?}", - username, prelogin_cookie, portal_userauthcookie - ); - } - - is_valid - } } pub fn parse_xml_tag(html: &str, tag: &str) -> Option { diff --git a/crates/gpapi/src/clap/mod.rs b/crates/gpapi/src/clap/mod.rs index 6e10f4a..74bc6e3 100644 --- a/crates/gpapi/src/clap/mod.rs +++ b/crates/gpapi/src/clap/mod.rs @@ -1 +1,28 @@ +use crate::error::PortalError; + pub mod args; + +pub trait Args { + fn fix_openssl(&self) -> bool; + fn ignore_tls_errors(&self) -> bool; +} + +pub fn handle_error(err: anyhow::Error, args: &impl Args) { + eprintln!("\nError: {}", err); + + let Some(err) = err.downcast_ref::() else { + return; + }; + + if err.is_legacy_openssl_error() && !args.fix_openssl() { + eprintln!("\nRe-run it with the `--fix-openssl` option to work around this issue, e.g.:\n"); + let args = std::env::args().collect::>(); + eprintln!("{} --fix-openssl {}\n", args[0], args[1..].join(" ")); + } + + if err.is_tls_error() && !args.ignore_tls_errors() { + eprintln!("\nRe-run it with the `--ignore-tls-errors` option to ignore the certificate error, e.g.:\n"); + let args = std::env::args().collect::>(); + eprintln!("{} --ignore-tls-errors {}\n", args[0], args[1..].join(" ")); + } +} diff --git a/crates/gpapi/src/error.rs b/crates/gpapi/src/error.rs index f46be52..6af7085 100644 --- a/crates/gpapi/src/error.rs +++ b/crates/gpapi/src/error.rs @@ -7,7 +7,19 @@ pub enum PortalError { #[error("Portal config error: {0}")] ConfigError(String), #[error("Network error: {0}")] - NetworkError(String), + NetworkError(#[from] reqwest::Error), + #[error("TLS error")] + TlsError, +} + +impl PortalError { + pub fn is_legacy_openssl_error(&self) -> bool { + format!("{:?}", self).contains("unsafe legacy renegotiation") + } + + pub fn is_tls_error(&self) -> bool { + matches!(self, PortalError::TlsError) || format!("{:?}", self).contains("certificate verify failed") + } } #[derive(Error, Debug)] @@ -17,3 +29,9 @@ pub enum AuthDataParseError { #[error("Invalid auth data")] Invalid, } + +impl AuthDataParseError { + pub fn is_invalid(&self) -> bool { + matches!(self, AuthDataParseError::Invalid) + } +} diff --git a/crates/gpapi/src/gateway/login.rs b/crates/gpapi/src/gateway/login.rs index 0188e22..d99c0e9 100644 --- a/crates/gpapi/src/gateway/login.rs +++ b/crates/gpapi/src/gateway/login.rs @@ -36,7 +36,7 @@ pub async fn gateway_login(gateway: &str, cred: &Credential, gp_params: &GpParam .form(¶ms) .send() .await - .map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e.to_string())))?; + .map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e)))?; let res = parse_gp_response(res).await.map_err(|err| { warn!("{err}"); diff --git a/crates/gpapi/src/portal/config.rs b/crates/gpapi/src/portal/config.rs index 3db25d6..9be4c76 100644 --- a/crates/gpapi/src/portal/config.rs +++ b/crates/gpapi/src/portal/config.rs @@ -116,7 +116,7 @@ pub async fn retrieve_config(portal: &str, cred: &Credential, gp_params: &GpPara .form(¶ms) .send() .await - .map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e.to_string())))?; + .map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e)))?; let res_xml = parse_gp_response(res).await.or_else(|err| { if err.status == StatusCode::NOT_FOUND { diff --git a/crates/gpapi/src/portal/prelogin.rs b/crates/gpapi/src/portal/prelogin.rs index 4c10d2a..b4076d9 100644 --- a/crates/gpapi/src/portal/prelogin.rs +++ b/crates/gpapi/src/portal/prelogin.rs @@ -116,14 +116,12 @@ pub async fn prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result