rustyscript/ext/web/
permissions.rs

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