Skip to main content

rew_extensions/ext/web/
permissions.rs

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