rew_extensions/ext/web/
permissions.rs

1use std::{
2  borrow::Cow,
3  collections::HashSet,
4  path::{Path, PathBuf},
5  sync::{Arc, RwLock},
6};
7
8pub use deno_permissions::PermissionDeniedError;
9
10pub fn oops<T>(msg: impl std::fmt::Display) -> Result<T, PermissionDeniedError> {
11  Err(PermissionDeniedError::Fatal {
12    access: msg.to_string(),
13  })
14}
15
16/// The default permissions manager for the web related extensions
17///
18/// Allows all operations
19#[derive(Debug, Clone, Copy, Default)]
20pub struct DefaultWebPermissions;
21impl WebPermissions for DefaultWebPermissions {
22  fn allow_hrtime(&self) -> bool {
23    true
24  }
25
26  fn check_url(
27    &self,
28    url: &deno_core::url::Url,
29    api_name: &str,
30  ) -> Result<(), PermissionDeniedError> {
31    Ok(())
32  }
33
34  fn check_open<'a>(
35    &self,
36    resolved: bool,
37    read: bool,
38    write: bool,
39    path: &'a Path,
40    api_name: &str,
41  ) -> Option<std::borrow::Cow<'a, Path>> {
42    Some(Cow::Borrowed(path))
43  }
44
45  fn check_read<'a>(
46    &self,
47    p: &'a Path,
48    api_name: Option<&str>,
49  ) -> Result<Cow<'a, Path>, PermissionDeniedError> {
50    Ok(Cow::Borrowed(p))
51  }
52
53  fn check_read_all(&self, api_name: Option<&str>) -> Result<(), PermissionDeniedError> {
54    Ok(())
55  }
56
57  fn check_read_blind(
58    &self,
59    p: &Path,
60    display: &str,
61    api_name: &str,
62  ) -> Result<(), PermissionDeniedError> {
63    Ok(())
64  }
65
66  fn check_write<'a>(
67    &self,
68    p: &'a Path,
69    api_name: Option<&str>,
70  ) -> Result<Cow<'a, Path>, PermissionDeniedError> {
71    Ok(Cow::Borrowed(p))
72  }
73
74  fn check_write_all(&self, api_name: &str) -> Result<(), PermissionDeniedError> {
75    Ok(())
76  }
77
78  fn check_write_blind(
79    &self,
80    p: &Path,
81    display: &str,
82    api_name: &str,
83  ) -> Result<(), PermissionDeniedError> {
84    Ok(())
85  }
86
87  fn check_write_partial(
88    &self,
89    path: &str,
90    api_name: &str,
91  ) -> Result<std::path::PathBuf, PermissionDeniedError> {
92    Ok(PathBuf::from(path))
93  }
94
95  fn check_host(
96    &self,
97    host: &str,
98    port: Option<u16>,
99    api_name: &str,
100  ) -> Result<(), PermissionDeniedError> {
101    Ok(())
102  }
103
104  fn check_sys(
105    &self,
106    kind: SystemsPermissionKind,
107    api_name: &str,
108  ) -> Result<(), PermissionDeniedError> {
109    Ok(())
110  }
111
112  fn check_env(&self, var: &str) -> Result<(), PermissionDeniedError> {
113    Ok(())
114  }
115
116  fn check_exec(&self) -> Result<(), PermissionDeniedError> {
117    Ok(())
118  }
119}
120
121// Inner container for the allowlist permission set
122#[derive(Clone, Default, Debug)]
123#[allow(clippy::struct_excessive_bools)]
124struct AllowlistWebPermissionsSet {
125  pub hrtime: bool,
126  pub exec: bool,
127  pub read_all: bool,
128  pub write_all: bool,
129  pub url: HashSet<String>,
130  pub openr_paths: HashSet<String>,
131  pub openw_paths: HashSet<String>,
132  pub envs: HashSet<String>,
133  pub sys: HashSet<SystemsPermissionKind>,
134  pub read_paths: HashSet<String>,
135  pub write_paths: HashSet<String>,
136  pub hosts: HashSet<String>,
137}
138
139/// Permissions manager for the web related extensions
140///
141/// Allows only operations that are explicitly enabled
142///
143/// Uses interior mutability to allow changing the permissions at runtime
144#[derive(Clone, Default, Debug)]
145pub struct AllowlistWebPermissions(Arc<RwLock<AllowlistWebPermissionsSet>>);
146impl AllowlistWebPermissions {
147  /// Create a new instance with nothing allowed by default
148  #[must_use]
149  pub fn new() -> Self {
150    Self(Arc::new(RwLock::new(AllowlistWebPermissionsSet::default())))
151  }
152
153  fn borrow(&self) -> std::sync::RwLockReadGuard<AllowlistWebPermissionsSet> {
154    self.0.read().expect("Could not lock permissions")
155  }
156
157  fn borrow_mut(&self) -> std::sync::RwLockWriteGuard<AllowlistWebPermissionsSet> {
158    self.0.write().expect("Could not lock permissions")
159  }
160
161  /// Set the `hrtime` permission
162  ///
163  /// If true, timers will be allowed to use high resolution time
164  pub fn set_hrtime(&self, value: bool) {
165    self.borrow_mut().hrtime = value;
166  }
167
168  /// Set the `exec` permission
169  ///
170  /// If true, FFI execution will be allowed
171  pub fn set_exec(&self, value: bool) {
172    self.borrow_mut().exec = value;
173  }
174
175  /// Set the `read_all` permission
176  ///
177  /// If false all reads will be denied
178  pub fn set_read_all(&self, value: bool) {
179    self.borrow_mut().read_all = value;
180  }
181
182  /// Set the `write_all` permission
183  ///
184  /// If false all writes will be denied
185  pub fn set_write_all(&self, value: bool) {
186    self.borrow_mut().write_all = value;
187  }
188
189  /// Whitelist a path for opening
190  ///
191  /// If `read` is true, the path will be allowed to be opened for reading  
192  /// If `write` is true, the path will be allowed to be opened for writing
193  pub fn allow_open(&self, path: &str, read: bool, write: bool) {
194    if read {
195      self.borrow_mut().openr_paths.insert(path.to_string());
196    }
197    if write {
198      self.borrow_mut().openw_paths.insert(path.to_string());
199    }
200  }
201
202  /// Whitelist a URL
203  pub fn allow_url(&self, url: &str) {
204    self.borrow_mut().url.insert(url.to_string());
205  }
206
207  /// Blacklist a URL
208  pub fn deny_url(&self, url: &str) {
209    self.borrow_mut().url.remove(url);
210  }
211
212  /// Whitelist a path for reading
213  pub fn allow_read(&self, path: &str) {
214    self.borrow_mut().read_paths.insert(path.to_string());
215  }
216
217  /// Blacklist a path for reading
218  pub fn deny_read(&self, path: &str) {
219    self.borrow_mut().read_paths.remove(path);
220  }
221
222  /// Whitelist a path for writing
223  pub fn allow_write(&self, path: &str) {
224    self.borrow_mut().write_paths.insert(path.to_string());
225  }
226
227  /// Blacklist a path for writing
228  pub fn deny_write(&self, path: &str) {
229    self.borrow_mut().write_paths.remove(path);
230  }
231
232  /// Whitelist a host
233  pub fn allow_host(&self, host: &str) {
234    self.borrow_mut().hosts.insert(host.to_string());
235  }
236
237  /// Blacklist a host
238  pub fn deny_host(&self, host: &str) {
239    self.borrow_mut().hosts.remove(host);
240  }
241
242  /// Whitelist an environment variable
243  pub fn allow_env(&self, var: &str) {
244    self.borrow_mut().envs.insert(var.to_string());
245  }
246
247  /// Blacklist an environment variable
248  pub fn deny_env(&self, var: &str) {
249    self.borrow_mut().envs.remove(var);
250  }
251
252  /// Whitelist a system operation
253  pub fn allow_sys(&self, kind: SystemsPermissionKind) {
254    self.borrow_mut().sys.insert(kind);
255  }
256
257  /// Blacklist a system operation
258  pub fn deny_sys(&self, kind: SystemsPermissionKind) {
259    self.borrow_mut().sys.remove(&kind);
260  }
261}
262impl WebPermissions for AllowlistWebPermissions {
263  fn allow_hrtime(&self) -> bool {
264    self.borrow().hrtime
265  }
266
267  fn check_host(
268    &self,
269    host: &str,
270    port: Option<u16>,
271    api_name: &str,
272  ) -> Result<(), PermissionDeniedError> {
273    if self.borrow().hosts.contains(host) {
274      Ok(())
275    } else {
276      oops(host)?
277    }
278  }
279
280  fn check_url(
281    &self,
282    url: &deno_core::url::Url,
283    api_name: &str,
284  ) -> Result<(), PermissionDeniedError> {
285    if self.borrow().url.contains(url.as_str()) {
286      Ok(())
287    } else {
288      oops(url)?
289    }
290  }
291
292  fn check_read<'a>(
293    &self,
294    p: &'a Path,
295    api_name: Option<&str>,
296  ) -> Result<Cow<'a, Path>, PermissionDeniedError> {
297    let inst = self.borrow();
298    if inst.read_all && inst.read_paths.contains(p.to_str().unwrap()) {
299      Ok(Cow::Borrowed(p))
300    } else {
301      oops(p.display())?
302    }
303  }
304
305  fn check_write<'a>(
306    &self,
307    p: &'a Path,
308    api_name: Option<&str>,
309  ) -> Result<Cow<'a, Path>, PermissionDeniedError> {
310    let inst = self.borrow();
311    if inst.write_all && inst.write_paths.contains(p.to_str().unwrap()) {
312      Ok(Cow::Borrowed(p))
313    } else {
314      oops(p.display())?
315    }
316  }
317
318  fn check_open<'a>(
319    &self,
320    resolved: bool,
321    read: bool,
322    write: bool,
323    path: &'a Path,
324    api_name: &str,
325  ) -> Option<std::borrow::Cow<'a, Path>> {
326    let path = path.to_str().unwrap();
327    if read && !self.borrow().openr_paths.contains(path) {
328      return None;
329    }
330    if write && !self.borrow().openw_paths.contains(path) {
331      return None;
332    }
333    Some(Cow::Borrowed(path.as_ref()))
334  }
335
336  fn check_read_all(&self, api_name: Option<&str>) -> Result<(), PermissionDeniedError> {
337    if self.borrow().read_all {
338      Ok(())
339    } else {
340      oops("read_all")?
341    }
342  }
343
344  fn check_read_blind(
345    &self,
346    p: &Path,
347    display: &str,
348    api_name: &str,
349  ) -> Result<(), PermissionDeniedError> {
350    if !self.borrow().read_all {
351      return oops("read_all")?;
352    }
353    self.check_read(p, Some(api_name))?;
354    Ok(())
355  }
356
357  fn check_write_all(&self, api_name: &str) -> Result<(), PermissionDeniedError> {
358    if self.borrow().write_all {
359      Ok(())
360    } else {
361      oops("write_all")?
362    }
363  }
364
365  fn check_write_blind(
366    &self,
367    path: &Path,
368    display: &str,
369    api_name: &str,
370  ) -> Result<(), PermissionDeniedError> {
371    self.check_write(Path::new(path), Some(api_name))?;
372    Ok(())
373  }
374
375  fn check_write_partial(
376    &self,
377    path: &str,
378    api_name: &str,
379  ) -> Result<std::path::PathBuf, PermissionDeniedError> {
380    let p = self.check_write(Path::new(path), Some(api_name))?;
381    Ok(p.into_owned())
382  }
383
384  fn check_sys(
385    &self,
386    kind: SystemsPermissionKind,
387    api_name: &str,
388  ) -> Result<(), PermissionDeniedError> {
389    if self.borrow().sys.contains(&kind) {
390      Ok(())
391    } else {
392      oops(kind.as_str())?
393    }
394  }
395
396  fn check_env(&self, var: &str) -> Result<(), PermissionDeniedError> {
397    if self.borrow().envs.contains(var) {
398      Ok(())
399    } else {
400      oops(var)?
401    }
402  }
403
404  fn check_exec(&self) -> Result<(), PermissionDeniedError> {
405    if self.borrow().exec {
406      Ok(())
407    } else {
408      oops("ffi")?
409    }
410  }
411}
412
413/// Trait managing the permissions for the web related extensions
414///
415/// See [`DefaultWebPermissions`] for a default implementation that allows-all
416pub trait WebPermissions: std::fmt::Debug + Send + Sync {
417  /// Check if `hrtime` is allowed
418  ///
419  /// If true, timers will be allowed to use high resolution time
420  fn allow_hrtime(&self) -> bool;
421
422  /// Check if a URL is allowed to be used by fetch or websocket
423  ///
424  /// # Errors
425  /// If an error is returned, the operation will be denied with the error message as the reason
426  fn check_url(
427    &self,
428    url: &deno_core::url::Url,
429    api_name: &str,
430  ) -> Result<(), PermissionDeniedError>;
431
432  /// Check if a path is allowed to be opened by fs
433  ///
434  /// If the path is allowed, the returned path will be used instead
435  fn check_open<'a>(
436    &self,
437    resolved: bool,
438    read: bool,
439    write: bool,
440    path: &'a Path,
441    api_name: &str,
442  ) -> Option<std::borrow::Cow<'a, Path>>;
443
444  /// Check if a path is allowed to be read by fetch or net
445  ///
446  /// # Errors
447  /// If an error is returned, the operation will be denied with the error message as the reason
448  fn check_read<'a>(
449    &self,
450    p: &'a Path,
451    api_name: Option<&str>,
452  ) -> Result<Cow<'a, Path>, PermissionDeniedError>;
453
454  /// Check if all paths are allowed to be read by fs
455  ///
456  /// Used by `deno_fs` for `op_fs_symlink`
457  ///
458  /// # Errors
459  /// If an error is returned, the operation will be denied with the error message as the reason
460  fn check_read_all(&self, api_name: Option<&str>) -> Result<(), PermissionDeniedError>;
461
462  /// Check if a path is allowed to be read by fs
463  ///
464  /// # Errors
465  /// If an error is returned, the operation will be denied with the error message as the reason
466  fn check_read_blind(
467    &self,
468    p: &Path,
469    display: &str,
470    api_name: &str,
471  ) -> Result<(), PermissionDeniedError>;
472
473  /// Check if a path is allowed to be written to by net
474  ///
475  /// # Errors
476  /// If an error is returned, the operation will be denied with the error message as the reason
477  fn check_write<'a>(
478    &self,
479    p: &'a Path,
480    api_name: Option<&str>,
481  ) -> Result<Cow<'a, Path>, PermissionDeniedError>;
482
483  /// Check if all paths are allowed to be written to by fs
484  ///
485  /// Used by `deno_fs` for `op_fs_symlink`
486  ///
487  /// # Errors
488  /// If an error is returned, the operation will be denied with the error message as the reason
489  fn check_write_all(&self, api_name: &str) -> Result<(), PermissionDeniedError>;
490
491  /// Check if a path is allowed to be written to by fs
492  ///
493  /// # Errors
494  /// If an error is returned, the operation will be denied with the error message as the reason
495  fn check_write_blind(
496    &self,
497    p: &Path,
498    display: &str,
499    api_name: &str,
500  ) -> Result<(), PermissionDeniedError>;
501
502  /// Check if a path is allowed to be written to by fs
503  ///
504  /// # Errors
505  /// If an error is returned, the operation will be denied with the error message as the reason
506  fn check_write_partial(
507    &self,
508    path: &str,
509    api_name: &str,
510  ) -> Result<std::path::PathBuf, PermissionDeniedError>;
511
512  /// Check if a host is allowed to be connected to by net
513  ///
514  /// # Errors
515  /// If an error is returned, the operation will be denied with the error message as the reason
516  fn check_host(
517    &self,
518    host: &str,
519    port: Option<u16>,
520    api_name: &str,
521  ) -> Result<(), PermissionDeniedError>;
522
523  /// Check if a system operation is allowed
524  ///
525  /// # Errors
526  /// If an error is returned, the operation will be denied with the error message as the reason
527  fn check_sys(
528    &self,
529    kind: SystemsPermissionKind,
530    api_name: &str,
531  ) -> Result<(), PermissionDeniedError>;
532
533  /// Check if an environment variable is allowed to be accessed
534  ///
535  /// Used by remote KV store (`deno_kv`)
536  ///
537  /// # Errors
538  /// If an error is returned, the operation will be denied with the error message as the reason
539  fn check_env(&self, var: &str) -> Result<(), PermissionDeniedError>;
540
541  /// Check if FFI execution is allowed
542  ///
543  /// # Errors
544  /// If an error is returned, the operation will be denied with the error message as the reason
545  fn check_exec(&self) -> Result<(), PermissionDeniedError>;
546}
547
548macro_rules! impl_sys_permission_kinds {
549    ($($kind:ident($name:literal)),+ $(,)?) => {
550        /// Knows systems permission checks performed by deno
551        ///
552        /// This list is updated manually using:
553        /// <https://github.com/search?q=repo%3Adenoland%2Fdeno+check_sys%28%22&type=code>
554        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
555        pub enum SystemsPermissionKind {
556            $(
557                #[doc = stringify!($kind)]
558                $kind,
559            )+
560
561            /// A custom permission kind
562            Other(String),
563        }
564        impl SystemsPermissionKind {
565            /// Create a new instance from a string
566            #[must_use]
567            pub fn new(s: &str) -> Self {
568                match s {
569                    $( $name => Self::$kind, )+
570                    _ => Self::Other(s.to_string()),
571                }
572            }
573
574            /// Get the string representation of the permission
575            #[must_use]
576            pub fn as_str(&self) -> &str {
577                match self {
578                    $( Self::$kind => $name, )+
579                    Self::Other(s) => &s,
580                }
581            }
582        }
583    };
584}
585
586impl_sys_permission_kinds!(
587  LoadAvg("loadavg"),
588  Hostname("hostname"),
589  OsRelease("osRelease"),
590  Networkinterfaces("networkInterfaces"),
591  StatFs("statfs"),
592  GetPriority("getPriority"),
593  SystemMemoryInfo("systemMemoryInfo"),
594  Gid("gid"),
595  Uid("uid"),
596  OsUptime("osUptime"),
597  SetPriority("setPriority"),
598  UserInfo("userInfo"),
599  GetEGid("getegid"),
600  Cpus("cpus"),
601  HomeDir("homeDir"),
602  Inspector("inspector"),
603);
604
605#[derive(Clone, Debug)]
606pub struct PermissionsContainer(pub Arc<dyn WebPermissions>);
607impl deno_web::TimersPermission for PermissionsContainer {
608  fn allow_hrtime(&mut self) -> bool {
609    self.0.allow_hrtime()
610  }
611}