Skip to content

Commit 83a68de

Browse files
authored
refactor(core): allow referencing capabilities on the Tauri config file (#8797)
* refactor(core): capabilities must be referenced on the Tauri config file * add all capabilities by default * refactor(cli): reference all capabilities by default
1 parent 0cb0a15 commit 83a68de

14 files changed

Lines changed: 744 additions & 15 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"tauri-build": patch:breaking
3+
"tauri-utils": patch:enhance
4+
"tauri-codegen": patch:enhance
5+
---
6+
7+
Added a new configuration option `tauri.conf.json > app > security > capabilities` to reference existing capabilities and inline new ones. If it is empty, all capabilities are still included preserving the current behavior.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@tauri-apps/cli": patch:enhance
3+
"tauri-cli": patch:enhance
4+
---
5+
6+
Update app template following capabilities configuration change.

core/tauri-build/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
567567
out_dir.join(PLUGIN_MANIFESTS_FILE_NAME),
568568
serde_json::to_string(&plugin_manifests)?,
569569
)?;
570+
570571
let capabilities = if let Some(pattern) = attributes.capabilities_path_pattern {
571572
parse_capabilities(pattern)?
572573
} else {

core/tauri-codegen/src/context.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use tauri_utils::acl::capability::Capability;
1515
use tauri_utils::acl::plugin::Manifest;
1616
use tauri_utils::acl::resolved::Resolved;
1717
use tauri_utils::assets::AssetKey;
18-
use tauri_utils::config::{Config, FrontendDist, PatternKind};
18+
use tauri_utils::config::{CapabilityEntry, Config, FrontendDist, PatternKind};
1919
use tauri_utils::html::{
2020
inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node,
2121
};
@@ -381,14 +381,35 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
381381
};
382382

383383
let capabilities_file_path = out_dir.join(CAPABILITIES_FILE_NAME);
384-
let capabilities: BTreeMap<String, Capability> = if capabilities_file_path.exists() {
384+
let mut capabilities_from_files: BTreeMap<String, Capability> = if capabilities_file_path.exists()
385+
{
385386
let capabilities_file =
386387
std::fs::read_to_string(capabilities_file_path).expect("failed to read capabilities");
387388
serde_json::from_str(&capabilities_file).expect("failed to parse capabilities")
388389
} else {
389390
Default::default()
390391
};
391392

393+
let capabilities = if config.app.security.capabilities.is_empty() {
394+
capabilities_from_files
395+
} else {
396+
let mut capabilities = BTreeMap::new();
397+
for capability_entry in &config.app.security.capabilities {
398+
match capability_entry {
399+
CapabilityEntry::Inlined(capability) => {
400+
capabilities.insert(capability.identifier.clone(), capability.clone());
401+
}
402+
CapabilityEntry::Reference(id) => {
403+
let capability = capabilities_from_files
404+
.remove(id)
405+
.unwrap_or_else(|| panic!("capability with identifier {id} not found"));
406+
capabilities.insert(id.clone(), capability);
407+
}
408+
}
409+
}
410+
capabilities
411+
};
412+
392413
let resolved_acl = Resolved::resolve(acl, capabilities, target).expect("failed to resolve ACL");
393414

394415
Ok(quote!({

core/tauri-config-schema/schema.json

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"enable": false,
4141
"scope": []
4242
},
43+
"capabilities": [],
4344
"dangerousDisableAssetCspModification": false,
4445
"freezePrototype": false,
4546
"pattern": {
@@ -158,6 +159,7 @@
158159
"enable": false,
159160
"scope": []
160161
},
162+
"capabilities": [],
161163
"dangerousDisableAssetCspModification": false,
162164
"freezePrototype": false,
163165
"pattern": {
@@ -878,6 +880,14 @@
878880
"$ref": "#/definitions/PatternKind"
879881
}
880882
]
883+
},
884+
"capabilities": {
885+
"description": "List of capabilities that are enabled on the application.\n\nIf the list is empty, all capabilities are included.",
886+
"default": [],
887+
"type": "array",
888+
"items": {
889+
"$ref": "#/definitions/CapabilityEntry"
890+
}
881891
}
882892
},
883893
"additionalProperties": false
@@ -1040,6 +1050,264 @@
10401050
}
10411051
]
10421052
},
1053+
"CapabilityEntry": {
1054+
"description": "A capability entry which can be either an inlined capability or a reference to a capability defined on its own file.",
1055+
"anyOf": [
1056+
{
1057+
"description": "An inlined capability.",
1058+
"allOf": [
1059+
{
1060+
"$ref": "#/definitions/Capability"
1061+
}
1062+
]
1063+
},
1064+
{
1065+
"description": "Reference to a capability identifier.",
1066+
"type": "string"
1067+
}
1068+
]
1069+
},
1070+
"Capability": {
1071+
"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.",
1072+
"type": "object",
1073+
"required": [
1074+
"identifier",
1075+
"permissions",
1076+
"windows"
1077+
],
1078+
"properties": {
1079+
"identifier": {
1080+
"description": "Identifier of the capability.",
1081+
"type": "string"
1082+
},
1083+
"description": {
1084+
"description": "Description of the capability.",
1085+
"default": "",
1086+
"type": "string"
1087+
},
1088+
"context": {
1089+
"description": "Execution context of the capability.\n\nAt runtime, Tauri filters the IPC command together with the context to determine whether it is allowed or not and its scope.",
1090+
"default": "local",
1091+
"allOf": [
1092+
{
1093+
"$ref": "#/definitions/CapabilityContext"
1094+
}
1095+
]
1096+
},
1097+
"windows": {
1098+
"description": "List of windows that uses this capability. Can be a glob pattern.",
1099+
"type": "array",
1100+
"items": {
1101+
"type": "string"
1102+
}
1103+
},
1104+
"permissions": {
1105+
"description": "List of permissions attached to this capability. Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.",
1106+
"type": "array",
1107+
"items": {
1108+
"$ref": "#/definitions/PermissionEntry"
1109+
}
1110+
},
1111+
"platforms": {
1112+
"description": "Target platforms this capability applies. By default all platforms applies.",
1113+
"default": [
1114+
"linux",
1115+
"macOS",
1116+
"windows",
1117+
"android",
1118+
"iOS"
1119+
],
1120+
"type": "array",
1121+
"items": {
1122+
"$ref": "#/definitions/Target"
1123+
}
1124+
}
1125+
}
1126+
},
1127+
"CapabilityContext": {
1128+
"description": "Context of the capability.",
1129+
"oneOf": [
1130+
{
1131+
"description": "Capability refers to local URL usage.",
1132+
"type": "string",
1133+
"enum": [
1134+
"local"
1135+
]
1136+
},
1137+
{
1138+
"description": "Capability refers to remote usage.",
1139+
"type": "object",
1140+
"required": [
1141+
"remote"
1142+
],
1143+
"properties": {
1144+
"remote": {
1145+
"type": "object",
1146+
"required": [
1147+
"domains"
1148+
],
1149+
"properties": {
1150+
"domains": {
1151+
"description": "Remote domains this capability refers to. Can use glob patterns.",
1152+
"type": "array",
1153+
"items": {
1154+
"type": "string"
1155+
}
1156+
}
1157+
}
1158+
}
1159+
},
1160+
"additionalProperties": false
1161+
}
1162+
]
1163+
},
1164+
"PermissionEntry": {
1165+
"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.",
1166+
"anyOf": [
1167+
{
1168+
"description": "Reference a permission or permission set by identifier.",
1169+
"allOf": [
1170+
{
1171+
"$ref": "#/definitions/Identifier"
1172+
}
1173+
]
1174+
},
1175+
{
1176+
"description": "Reference a permission or permission set by identifier and extends its scope.",
1177+
"type": "object",
1178+
"required": [
1179+
"identifier"
1180+
],
1181+
"properties": {
1182+
"identifier": {
1183+
"description": "Identifier of the permission or permission set.",
1184+
"allOf": [
1185+
{
1186+
"$ref": "#/definitions/Identifier"
1187+
}
1188+
]
1189+
},
1190+
"allow": {
1191+
"description": "Data that defines what is allowed by the scope.",
1192+
"type": [
1193+
"array",
1194+
"null"
1195+
],
1196+
"items": {
1197+
"$ref": "#/definitions/Value"
1198+
}
1199+
},
1200+
"deny": {
1201+
"description": "Data that defines what is denied by the scope.",
1202+
"type": [
1203+
"array",
1204+
"null"
1205+
],
1206+
"items": {
1207+
"$ref": "#/definitions/Value"
1208+
}
1209+
}
1210+
}
1211+
}
1212+
]
1213+
},
1214+
"Identifier": {
1215+
"type": "string"
1216+
},
1217+
"Value": {
1218+
"description": "All supported ACL values.",
1219+
"anyOf": [
1220+
{
1221+
"description": "Represents a null JSON value.",
1222+
"type": "null"
1223+
},
1224+
{
1225+
"description": "Represents a [`bool`].",
1226+
"type": "boolean"
1227+
},
1228+
{
1229+
"description": "Represents a valid ACL [`Number`].",
1230+
"allOf": [
1231+
{
1232+
"$ref": "#/definitions/Number"
1233+
}
1234+
]
1235+
},
1236+
{
1237+
"description": "Represents a [`String`].",
1238+
"type": "string"
1239+
},
1240+
{
1241+
"description": "Represents a list of other [`Value`]s.",
1242+
"type": "array",
1243+
"items": {
1244+
"$ref": "#/definitions/Value"
1245+
}
1246+
},
1247+
{
1248+
"description": "Represents a map of [`String`] keys to [`Value`]s.",
1249+
"type": "object",
1250+
"additionalProperties": {
1251+
"$ref": "#/definitions/Value"
1252+
}
1253+
}
1254+
]
1255+
},
1256+
"Number": {
1257+
"description": "A valid ACL number.",
1258+
"anyOf": [
1259+
{
1260+
"description": "Represents an [`i64`].",
1261+
"type": "integer",
1262+
"format": "int64"
1263+
},
1264+
{
1265+
"description": "Represents a [`f64`].",
1266+
"type": "number",
1267+
"format": "double"
1268+
}
1269+
]
1270+
},
1271+
"Target": {
1272+
"description": "Platform target.",
1273+
"oneOf": [
1274+
{
1275+
"description": "MacOS.",
1276+
"type": "string",
1277+
"enum": [
1278+
"macOS"
1279+
]
1280+
},
1281+
{
1282+
"description": "Windows.",
1283+
"type": "string",
1284+
"enum": [
1285+
"windows"
1286+
]
1287+
},
1288+
{
1289+
"description": "Linux.",
1290+
"type": "string",
1291+
"enum": [
1292+
"linux"
1293+
]
1294+
},
1295+
{
1296+
"description": "Android.",
1297+
"type": "string",
1298+
"enum": [
1299+
"android"
1300+
]
1301+
},
1302+
{
1303+
"description": "iOS.",
1304+
"type": "string",
1305+
"enum": [
1306+
"iOS"
1307+
]
1308+
}
1309+
]
1310+
},
10431311
"TrayIconConfig": {
10441312
"description": "Configuration for application tray icon.\n\nSee more: <https://tauri.app/v1/api/config#trayiconconfig>",
10451313
"type": "object",

0 commit comments

Comments
 (0)