1use std::{path::Path, str::FromStr};
8
9use crate::{acl::Identifier, platform::Target};
10use serde::{
11 Deserialize, Deserializer, Serialize,
12 de::{Error, IntoDeserializer},
13};
14use serde_untagged::UntaggedEnumVisitor;
15
16use super::Scopes;
17
18#[derive(Debug, Clone, PartialEq, Serialize)]
21#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
22#[serde(untagged)]
23pub enum PermissionEntry {
24 PermissionRef(Identifier),
26 ExtendedPermission {
28 identifier: Identifier,
30 #[serde(default, flatten)]
32 scope: Scopes,
33 },
34}
35
36impl PermissionEntry {
37 pub fn identifier(&self) -> &Identifier {
39 match self {
40 Self::PermissionRef(identifier) => identifier,
41 Self::ExtendedPermission {
42 identifier,
43 scope: _,
44 } => identifier,
45 }
46 }
47}
48
49impl<'de> Deserialize<'de> for PermissionEntry {
50 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
51 where
52 D: Deserializer<'de>,
53 {
54 #[derive(Deserialize)]
55 struct ExtendedPermissionStruct {
56 identifier: Identifier,
57 #[serde(default, flatten)]
58 scope: Scopes,
59 }
60
61 UntaggedEnumVisitor::new()
62 .string(|string| {
63 let de = string.into_deserializer();
64 Identifier::deserialize(de).map(Self::PermissionRef)
65 })
66 .map(|map| {
67 let ext_perm = map.deserialize::<ExtendedPermissionStruct>()?;
68 Ok(Self::ExtendedPermission {
69 identifier: ext_perm.identifier,
70 scope: ext_perm.scope,
71 })
72 })
73 .deserialize(deserializer)
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
110pub struct Capability {
111 pub identifier: String,
118 #[serde(default)]
127 pub description: String,
128 #[serde(default, skip_serializing_if = "Option::is_none")]
146 pub remote: Option<CapabilityRemote>,
147 #[serde(default = "default_capability_local")]
149 pub local: bool,
150 #[serde(default, skip_serializing_if = "Vec::is_empty")]
163 pub windows: Vec<String>,
164 #[serde(default, skip_serializing_if = "Vec::is_empty")]
174 pub webviews: Vec<String>,
175 #[cfg_attr(feature = "schema", schemars(schema_with = "unique_permission"))]
195 pub permissions: Vec<PermissionEntry>,
196 #[serde(skip_serializing_if = "Option::is_none")]
204 pub platforms: Option<Vec<Target>>,
205}
206
207impl Capability {
208 pub fn is_active(&self, target: &Target) -> bool {
210 self
211 .platforms
212 .as_ref()
213 .map(|platforms| platforms.contains(target))
214 .unwrap_or(true)
215 }
216}
217
218#[cfg(feature = "schema")]
219fn unique_permission(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
220 let items = serde_json::Value::from(generator.subschema_for::<PermissionEntry>());
221 schemars::json_schema!({
222 "type": "array",
223 "uniqueItems": true,
224 "items": items
225 })
226}
227
228fn default_capability_local() -> bool {
229 true
230}
231
232#[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord, Hash)]
234#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
235#[serde(rename_all = "camelCase")]
236pub struct CapabilityRemote {
237 pub urls: Vec<String>,
244}
245
246#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
248#[cfg_attr(feature = "schema", schemars(untagged))]
249#[cfg_attr(test, derive(Debug, PartialEq))]
250pub enum CapabilityFile {
251 Capability(Capability),
253 List(Vec<Capability>),
255 NamedList {
257 capabilities: Vec<Capability>,
259 },
260}
261
262impl CapabilityFile {
263 pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, super::Error> {
265 let path = path.as_ref();
266 let capability_file =
267 std::fs::read_to_string(path).map_err(|e| super::Error::ReadFile(e, path.into()))?;
268 let ext = path.extension().unwrap().to_string_lossy().to_string();
269 let file: Self = match ext.as_str() {
270 "toml" => toml::from_str(&capability_file)?,
271 "json" => serde_json::from_str(&capability_file)?,
272 #[cfg(feature = "config-json5")]
273 "json5" => json5::from_str(&capability_file)?,
274 _ => return Err(super::Error::UnknownCapabilityFormat(ext)),
275 };
276 Ok(file)
277 }
278}
279
280impl<'de> Deserialize<'de> for CapabilityFile {
281 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
282 where
283 D: Deserializer<'de>,
284 {
285 UntaggedEnumVisitor::new()
286 .seq(|seq| seq.deserialize::<Vec<Capability>>().map(Self::List))
287 .map(|map| {
288 #[derive(Deserialize)]
289 struct CapabilityNamedList {
290 capabilities: Vec<Capability>,
291 }
292
293 let value: serde_json::Map<String, serde_json::Value> = map.deserialize()?;
294 if value.contains_key("capabilities") {
295 serde_json::from_value::<CapabilityNamedList>(value.into())
296 .map(|named| Self::NamedList {
297 capabilities: named.capabilities,
298 })
299 .map_err(|e| serde_untagged::de::Error::custom(e.to_string()))
300 } else {
301 serde_json::from_value::<Capability>(value.into())
302 .map(Self::Capability)
303 .map_err(|e| serde_untagged::de::Error::custom(e.to_string()))
304 }
305 })
306 .deserialize(deserializer)
307 }
308}
309
310impl FromStr for CapabilityFile {
311 type Err = super::Error;
312
313 fn from_str(s: &str) -> Result<Self, Self::Err> {
314 serde_json::from_str(s)
315 .or_else(|_| toml::from_str(s))
316 .map_err(Into::into)
317 }
318}
319
320#[cfg(any(feature = "build", feature = "build-2"))]
321mod build {
322 use std::convert::identity;
323
324 use proc_macro2::TokenStream;
325 use quote::{ToTokens, TokenStreamExt, quote};
326
327 use super::*;
328 use crate::{literal_struct, tokens::*};
329
330 impl ToTokens for CapabilityRemote {
331 fn to_tokens(&self, tokens: &mut TokenStream) {
332 let urls = vec_lit(&self.urls, str_lit);
333 literal_struct!(
334 tokens,
335 ::tauri::utils::acl::capability::CapabilityRemote,
336 urls
337 );
338 }
339 }
340
341 impl ToTokens for PermissionEntry {
342 fn to_tokens(&self, tokens: &mut TokenStream) {
343 let prefix = quote! { ::tauri::utils::acl::capability::PermissionEntry };
344
345 tokens.append_all(match self {
346 Self::PermissionRef(id) => {
347 quote! { #prefix::PermissionRef(#id) }
348 }
349 Self::ExtendedPermission { identifier, scope } => {
350 quote! { #prefix::ExtendedPermission {
351 identifier: #identifier,
352 scope: #scope
353 } }
354 }
355 });
356 }
357 }
358
359 impl ToTokens for Capability {
360 fn to_tokens(&self, tokens: &mut TokenStream) {
361 let identifier = str_lit(&self.identifier);
362 let description = str_lit(&self.description);
363 let remote = opt_lit(self.remote.as_ref());
364 let local = self.local;
365 let windows = vec_lit(&self.windows, str_lit);
366 let webviews = vec_lit(&self.webviews, str_lit);
367 let permissions = vec_lit(&self.permissions, identity);
368 let platforms = opt_vec_lit(self.platforms.as_ref(), identity);
369
370 literal_struct!(
371 tokens,
372 ::tauri::utils::acl::capability::Capability,
373 identifier,
374 description,
375 remote,
376 local,
377 windows,
378 webviews,
379 permissions,
380 platforms
381 );
382 }
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use crate::acl::{Identifier, Scopes};
389
390 use super::{Capability, CapabilityFile, PermissionEntry};
391
392 #[test]
393 fn permission_entry_de() {
394 let identifier = Identifier::try_from("plugin:perm".to_string()).unwrap();
395 let identifier_json = serde_json::to_string(&identifier).unwrap();
396 assert_eq!(
397 serde_json::from_str::<PermissionEntry>(&identifier_json).unwrap(),
398 PermissionEntry::PermissionRef(identifier.clone())
399 );
400
401 assert_eq!(
402 serde_json::from_value::<PermissionEntry>(serde_json::json!({
403 "identifier": identifier,
404 "allow": [],
405 "deny": null
406 }))
407 .unwrap(),
408 PermissionEntry::ExtendedPermission {
409 identifier,
410 scope: Scopes {
411 allow: Some(vec![]),
412 deny: None
413 }
414 }
415 );
416 }
417
418 #[test]
419 fn capability_file_de() {
420 let capability = Capability {
421 identifier: "test".into(),
422 description: "".into(),
423 remote: None,
424 local: true,
425 windows: vec![],
426 webviews: vec![],
427 permissions: vec![],
428 platforms: None,
429 };
430 let capability_json = serde_json::to_string(&capability).unwrap();
431
432 assert_eq!(
433 serde_json::from_str::<CapabilityFile>(&capability_json).unwrap(),
434 CapabilityFile::Capability(capability.clone())
435 );
436
437 assert_eq!(
438 serde_json::from_str::<CapabilityFile>(&format!("[{capability_json}]")).unwrap(),
439 CapabilityFile::List(vec![capability.clone()])
440 );
441
442 assert_eq!(
443 serde_json::from_str::<CapabilityFile>(&format!(
444 "{{ \"capabilities\": [{capability_json}] }}"
445 ))
446 .unwrap(),
447 CapabilityFile::NamedList {
448 capabilities: vec![capability]
449 }
450 );
451 }
452}