diff --git a/.github/workflows/check-generated-files.yml b/.github/workflows/check-generated-files.yml index 9ffe3992cbc..58f470977e5 100644 --- a/.github/workflows/check-generated-files.yml +++ b/.github/workflows/check-generated-files.yml @@ -37,6 +37,8 @@ jobs: - 'core/tauri-utils/src/config.rs' - 'tooling/cli/schema.json' - 'core/tauri-config-schema/schema.json' + - 'core/tauri-acl-schema/*.json' + api: runs-on: ubuntu-latest @@ -70,8 +72,11 @@ jobs: with: workspaces: core -> ../target - - name: generate schema.json + - name: generate config schema run: cargo build --manifest-path ./core/tauri-config-schema/Cargo.toml + - name: generate ACL schema + run: cargo build --manifest-path ./core/tauri-acl-schema/Cargo.toml + - name: check schema run: ./.scripts/ci/has-diff.sh diff --git a/Cargo.lock b/Cargo.lock index 85ed1de852e..319cc055a87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3583,6 +3583,17 @@ dependencies = [ "windows 0.56.0", ] +[[package]] +name = "tauri-acl-schema" +version = "0.0.0" +dependencies = [ + "schemars", + "serde", + "serde_json", + "tauri-utils", + "url", +] + [[package]] name = "tauri-build" version = "2.0.0-beta.17" diff --git a/Cargo.toml b/Cargo.toml index 4dd3286e0d4..ab555d85712 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "core/tauri-build", "core/tauri-codegen", "core/tauri-config-schema", + "core/tauri-acl-schema", "core/tauri-plugin", # integration tests diff --git a/core/tauri-acl-schema/Cargo.toml b/core/tauri-acl-schema/Cargo.toml new file mode 100644 index 00000000000..7321973ceea --- /dev/null +++ b/core/tauri-acl-schema/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "tauri-acl-schema" +version = "0.0.0" +edition = "2021" +publish = false + +[build-dependencies] +tauri-utils = { features = [ "schema" ], path = "../tauri-utils" } +schemars = { version = "0.8", features = ["url", "preserve_order"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +url = { version = "2.3", features = ["serde"] } diff --git a/core/tauri-acl-schema/build.rs b/core/tauri-acl-schema/build.rs new file mode 100644 index 00000000000..e5f40f80b5d --- /dev/null +++ b/core/tauri-acl-schema/build.rs @@ -0,0 +1,34 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{ + error::Error, + fs::File, + io::{BufWriter, Write}, + path::PathBuf, +}; + +use schemars::schema::RootSchema; + +pub fn main() -> Result<(), Box> { + let cap_schema = schemars::schema_for!(tauri_utils::acl::capability::Capability); + let perm_schema = schemars::schema_for!(tauri_utils::acl::Permission); + let scope_schema = schemars::schema_for!(tauri_utils::acl::Scopes); + + let crate_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?); + + write_schema_file(cap_schema, crate_dir.join("capability-schema.json"))?; + write_schema_file(perm_schema, crate_dir.join("permission-schema.json"))?; + write_schema_file(scope_schema, crate_dir.join("scope-schema.json"))?; + + Ok(()) +} + +fn write_schema_file(schema: RootSchema, outpath: PathBuf) -> Result<(), Box> { + let schema_str = serde_json::to_string_pretty(&schema).unwrap(); + let mut schema_file = BufWriter::new(File::create(outpath)?); + write!(schema_file, "{schema_str}")?; + + Ok(()) +} diff --git a/core/tauri-acl-schema/capability-schema.json b/core/tauri-acl-schema/capability-schema.json new file mode 100644 index 00000000000..809e1d8ca76 --- /dev/null +++ b/core/tauri-acl-schema/capability-schema.json @@ -0,0 +1,233 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Capability", + "description": "A grouping and boundary mechanism developers can use to separate windows or plugins functionality from each other at runtime.\n\nIf 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 trust groups and reduce impact of vulnerabilities in certain plugins or windows. Windows can be added to a capability by exact name or glob patterns like *, admin-* or main-window.", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.", + "type": "string" + }, + "description": { + "description": "Description of the capability.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.", + "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 uses this capability. Can be a glob pattern.\n\nOn multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that uses 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.", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability. Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + } + }, + "platforms": { + "description": "Target platforms this capability applies. By default all platforms are affected by this capability.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + }, + "definitions": { + "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", + "required": [ + "identifier" + ], + "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.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ] + }, + "Identifier": { + "type": "string" + }, + "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/core/tauri-acl-schema/permission-schema.json b/core/tauri-acl-schema/permission-schema.json new file mode 100644 index 00000000000..f3a43ebc9eb --- /dev/null +++ b/core/tauri-acl-schema/permission-schema.json @@ -0,0 +1,205 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Permission", + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + }, + "definitions": { + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "A restriction of the command/endpoint functionality.\n\nIt can be of any serde serializable type and is used for allowing or preventing certain actions inside a Tauri command.\n\nThe scope is passed to the command and handled/enforced by the command itself.", + "type": "object", + "properties": { + "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.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "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/core/tauri-acl-schema/scope-schema.json b/core/tauri-acl-schema/scope-schema.json new file mode 100644 index 00000000000..8acead754f4 --- /dev/null +++ b/core/tauri-acl-schema/scope-schema.json @@ -0,0 +1,84 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Scopes", + "description": "A restriction of the command/endpoint functionality.\n\nIt can be of any serde serializable type and is used for allowing or preventing certain actions inside a Tauri command.\n\nThe scope is passed to the command and handled/enforced by the command itself.", + "type": "object", + "properties": { + "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.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + }, + "definitions": { + "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" + } + ] + } + } +} \ No newline at end of file diff --git a/core/tauri-acl-schema/src/main.rs b/core/tauri-acl-schema/src/main.rs new file mode 100644 index 00000000000..53374aabbc9 --- /dev/null +++ b/core/tauri-acl-schema/src/main.rs @@ -0,0 +1,14 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! [![](https://github.com/tauri-apps/tauri/raw/dev/.github/splash.png)](https://tauri.app) +//! +//! Hosts the schema for the Tauri configuration file. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] + +fn main() {} diff --git a/core/tauri-config-schema/schema.json b/core/tauri-config-schema/schema.json index a21612215d4..2e82ff262b8 100644 --- a/core/tauri-config-schema/schema.json +++ b/core/tauri-config-schema/schema.json @@ -1074,7 +1074,7 @@ ] }, "Capability": { - "description": "a grouping and boundary mechanism developers can use to separate windows or plugins functionality from each other at runtime.\n\nIf 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 trust groups and reduce impact of vulnerabilities in certain plugins or windows. Windows can be added to a capability by exact name or glob patterns like *, admin-* or main-window.", + "description": "A grouping and boundary mechanism developers can use to separate windows or plugins functionality from each other at runtime.\n\nIf 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 trust groups and reduce impact of vulnerabilities in certain plugins or windows. Windows can be added to a capability by exact name or glob patterns like *, admin-* or main-window.", "type": "object", "required": [ "identifier", diff --git a/core/tauri-utils/src/acl/capability.rs b/core/tauri-utils/src/acl/capability.rs index 39425dc1714..feb2af47c56 100644 --- a/core/tauri-utils/src/acl/capability.rs +++ b/core/tauri-utils/src/acl/capability.rs @@ -42,7 +42,7 @@ impl PermissionEntry { } } -/// a grouping and boundary mechanism developers can use to separate windows or plugins functionality from each other at runtime. +/// A grouping and boundary mechanism developers can use to separate windows or plugins functionality from each other at runtime. /// /// If a window is not matching any capability then it has no access to the IPC layer at all. /// diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index a21612215d4..2e82ff262b8 100644 --- a/tooling/cli/schema.json +++ b/tooling/cli/schema.json @@ -1074,7 +1074,7 @@ ] }, "Capability": { - "description": "a grouping and boundary mechanism developers can use to separate windows or plugins functionality from each other at runtime.\n\nIf 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 trust groups and reduce impact of vulnerabilities in certain plugins or windows. Windows can be added to a capability by exact name or glob patterns like *, admin-* or main-window.", + "description": "A grouping and boundary mechanism developers can use to separate windows or plugins functionality from each other at runtime.\n\nIf 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 trust groups and reduce impact of vulnerabilities in certain plugins or windows. Windows can be added to a capability by exact name or glob patterns like *, admin-* or main-window.", "type": "object", "required": [ "identifier",