tauri_utils/acl/
manifest.rs

1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! Plugin ACL types.
6
7use std::{collections::BTreeMap, num::NonZeroU64};
8
9use super::{Permission, PermissionSet};
10#[cfg(feature = "schema")]
11use schemars::schema::*;
12use serde::{Deserialize, Serialize};
13
14/// The default permission set of the plugin.
15///
16/// Works similarly to a permission with the "default" identifier.
17#[derive(Debug, Deserialize, Serialize)]
18#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
19pub struct DefaultPermission {
20  /// The version of the permission.
21  pub version: Option<NonZeroU64>,
22
23  /// Human-readable description of what the permission does.
24  /// Tauri convention is to use <h4> headings in markdown content
25  /// for Tauri documentation generation purposes.
26  pub description: Option<String>,
27
28  /// All permissions this set contains.
29  pub permissions: Vec<String>,
30}
31
32/// Permission file that can define a default permission, a set of permissions or a list of inlined permissions.
33#[derive(Debug, Deserialize, Serialize)]
34#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
35pub struct PermissionFile {
36  /// The default permission set for the plugin
37  pub default: Option<DefaultPermission>,
38
39  /// A list of permissions sets defined
40  #[serde(default, skip_serializing_if = "Vec::is_empty")]
41  pub set: Vec<PermissionSet>,
42
43  /// A list of inlined permissions
44  #[serde(default)]
45  pub permission: Vec<Permission>,
46}
47
48/// Plugin manifest.
49#[derive(Debug, Serialize, Deserialize, Default)]
50pub struct Manifest {
51  /// Default permission.
52  pub default_permission: Option<PermissionSet>,
53  /// Plugin permissions.
54  pub permissions: BTreeMap<String, Permission>,
55  /// Plugin permission sets.
56  pub permission_sets: BTreeMap<String, PermissionSet>,
57  /// The global scope schema.
58  pub global_scope_schema: Option<serde_json::Value>,
59}
60
61impl Manifest {
62  /// Creates a new manifest from the given plugin permission files and global scope schema.
63  pub fn new(
64    permission_files: Vec<PermissionFile>,
65    global_scope_schema: Option<serde_json::Value>,
66  ) -> Self {
67    let mut manifest = Self {
68      default_permission: None,
69      permissions: BTreeMap::new(),
70      permission_sets: BTreeMap::new(),
71      global_scope_schema,
72    };
73
74    for permission_file in permission_files {
75      if let Some(default) = permission_file.default {
76        manifest.default_permission.replace(PermissionSet {
77          identifier: "default".into(),
78          description: default
79            .description
80            .unwrap_or_else(|| "Default plugin permissions.".to_string()),
81          permissions: default.permissions,
82        });
83      }
84
85      for permission in permission_file.permission {
86        let key = permission.identifier.clone();
87        manifest.permissions.insert(key, permission);
88      }
89
90      for set in permission_file.set {
91        let key = set.identifier.clone();
92        manifest.permission_sets.insert(key, set);
93      }
94    }
95
96    manifest
97  }
98}
99
100#[cfg(feature = "schema")]
101type ScopeSchema = (Schema, schemars::Map<String, Schema>);
102
103#[cfg(feature = "schema")]
104impl Manifest {
105  /// Return scope schema and extra schema definitions for this plugin manifest.
106  pub fn global_scope_schema(&self) -> Result<Option<ScopeSchema>, super::Error> {
107    self
108      .global_scope_schema
109      .as_ref()
110      .map(|s| {
111        serde_json::from_value::<RootSchema>(s.clone()).map(|s| {
112          // convert RootSchema to Schema
113          let scope_schema = Schema::Object(SchemaObject {
114            array: Some(Box::new(ArrayValidation {
115              items: Some(Schema::Object(s.schema).into()),
116              ..Default::default()
117            })),
118            ..Default::default()
119          });
120
121          (scope_schema, s.definitions)
122        })
123      })
124      .transpose()
125      .map_err(Into::into)
126  }
127}
128
129#[cfg(feature = "build")]
130mod build {
131  use proc_macro2::TokenStream;
132  use quote::{quote, ToTokens, TokenStreamExt};
133  use std::convert::identity;
134
135  use super::*;
136  use crate::{literal_struct, tokens::*};
137
138  impl ToTokens for DefaultPermission {
139    fn to_tokens(&self, tokens: &mut TokenStream) {
140      let version = opt_lit_owned(self.version.as_ref().map(|v| {
141        let v = v.get();
142        quote!(::core::num::NonZeroU64::new(#v).unwrap())
143      }));
144      let description = opt_str_lit(self.description.as_ref());
145      let permissions = vec_lit(&self.permissions, str_lit);
146      literal_struct!(
147        tokens,
148        ::tauri::utils::acl::plugin::DefaultPermission,
149        version,
150        description,
151        permissions
152      )
153    }
154  }
155
156  impl ToTokens for Manifest {
157    fn to_tokens(&self, tokens: &mut TokenStream) {
158      let default_permission = opt_lit(self.default_permission.as_ref());
159
160      let permissions = map_lit(
161        quote! { ::std::collections::BTreeMap },
162        &self.permissions,
163        str_lit,
164        identity,
165      );
166
167      let permission_sets = map_lit(
168        quote! { ::std::collections::BTreeMap },
169        &self.permission_sets,
170        str_lit,
171        identity,
172      );
173
174      let global_scope_schema =
175        opt_lit_owned(self.global_scope_schema.as_ref().map(json_value_lit));
176
177      literal_struct!(
178        tokens,
179        ::tauri::utils::acl::manifest::Manifest,
180        default_permission,
181        permissions,
182        permission_sets,
183        global_scope_schema
184      )
185    }
186  }
187}