1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-29 16:30:56 -05:00
denoland-deno/runtime/permissions.rs

1968 lines
60 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::colors;
use crate::fs_util::resolve_from_cwd;
use deno_core::error::custom_error;
use deno_core::error::uri_error;
use deno_core::error::AnyError;
#[cfg(test)]
use deno_core::parking_lot::Mutex;
use deno_core::serde::Deserialize;
use deno_core::serde::Serialize;
use deno_core::url;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
use log;
use std::collections::HashSet;
use std::fmt;
use std::hash::Hash;
use std::path::{Path, PathBuf};
#[cfg(test)]
use std::sync::atomic::AtomicBool;
#[cfg(test)]
use std::sync::atomic::Ordering;
const PERMISSION_EMOJI: &str = "⚠️";
lazy_static::lazy_static! {
static ref DEBUG_LOG_ENABLED: bool = log::log_enabled!(log::Level::Debug);
}
/// Tri-state value for storing permission state
#[derive(PartialEq, Debug, Clone, Copy, Deserialize, PartialOrd)]
pub enum PermissionState {
Granted = 0,
Prompt = 1,
Denied = 2,
}
impl PermissionState {
#[inline(always)]
fn log_perm_access(name: &str, info: Option<&str>) {
// Eliminates log overhead (when logging is disabled),
// log_enabled!(Debug) check in a hot path still has overhead
// TODO(AaronO): generalize or upstream this optimization
if *DEBUG_LOG_ENABLED {
log::debug!(
"{}",
colors::bold(&format!(
"{} Granted {}",
PERMISSION_EMOJI,
Self::fmt_access(name, info)
))
);
}
}
fn fmt_access(name: &str, info: Option<&str>) -> String {
format!(
"{} access{}",
name,
info.map_or(String::new(), |info| { format!(" to {}", info) }),
)
}
fn error(name: &str, info: Option<&str>) -> AnyError {
custom_error(
"PermissionDenied",
format!(
"Requires {}, run again with the --allow-{} flag",
Self::fmt_access(name, info),
name
),
)
}
/// Check the permission state. bool is whether a prompt was issued.
fn check(
self,
name: &str,
info: Option<&str>,
prompt: bool,
) -> (Result<(), AnyError>, bool) {
match self {
PermissionState::Granted => {
Self::log_perm_access(name, info);
(Ok(()), false)
}
PermissionState::Prompt if prompt => {
let msg = Self::fmt_access(name, info);
if permission_prompt(&msg) {
Self::log_perm_access(name, info);
(Ok(()), true)
} else {
(Err(Self::error(name, info)), true)
}
}
_ => (Err(Self::error(name, info)), false),
}
}
}
impl fmt::Display for PermissionState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PermissionState::Granted => f.pad("granted"),
PermissionState::Prompt => f.pad("prompt"),
PermissionState::Denied => f.pad("denied"),
}
}
}
impl Default for PermissionState {
fn default() -> Self {
PermissionState::Prompt
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct UnitPermission {
pub name: &'static str,
pub description: &'static str,
pub state: PermissionState,
pub prompt: bool,
}
impl UnitPermission {
pub fn query(&self) -> PermissionState {
self.state
}
pub fn request(&mut self) -> PermissionState {
if self.state == PermissionState::Prompt {
if permission_prompt(&format!("access to {}", self.description)) {
self.state = PermissionState::Granted;
} else {
self.state = PermissionState::Denied;
}
}
self.state
}
pub fn revoke(&mut self) -> PermissionState {
if self.state == PermissionState::Granted {
self.state = PermissionState::Prompt;
}
self.state
}
pub fn check(&mut self) -> Result<(), AnyError> {
let (result, prompted) = self.state.check(self.name, None, self.prompt);
if prompted {
if result.is_ok() {
self.state = PermissionState::Granted;
} else {
self.state = PermissionState::Denied;
}
}
result
}
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct UnaryPermission<T: Eq + Hash> {
#[serde(skip)]
pub name: &'static str,
#[serde(skip)]
pub description: &'static str,
pub global_state: PermissionState,
pub granted_list: HashSet<T>,
pub denied_list: HashSet<T>,
#[serde(skip)]
pub prompt: bool,
}
#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Deserialize)]
pub struct ReadDescriptor(pub PathBuf);
#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Deserialize)]
pub struct WriteDescriptor(pub PathBuf);
#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Deserialize)]
pub struct NetDescriptor(pub String, pub Option<u16>);
impl NetDescriptor {
fn new<T: AsRef<str>>(host: &&(T, Option<u16>)) -> Self {
NetDescriptor(host.0.as_ref().to_string(), host.1)
}
pub fn from_string(host: String) -> Self {
let url = url::Url::parse(&format!("http://{}", host)).unwrap();
let hostname = url.host_str().unwrap().to_string();
NetDescriptor(hostname, url.port())
}
}
impl fmt::Display for NetDescriptor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&match self.1 {
None => self.0.clone(),
Some(port) => format!("{}:{}", self.0, port),
})
}
}
#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Deserialize)]
pub struct EnvDescriptor(pub String);
#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Deserialize)]
pub struct RunDescriptor(pub String);
#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Deserialize)]
pub struct FfiDescriptor(pub String);
impl UnaryPermission<ReadDescriptor> {
pub fn query(&self, path: Option<&Path>) -> PermissionState {
let path = path.map(|p| resolve_from_cwd(p).unwrap());
if self.global_state == PermissionState::Denied
&& match path.as_ref() {
None => true,
Some(path) => self
.denied_list
.iter()
.any(|path_| path_.0.starts_with(path)),
}
{
PermissionState::Denied
} else if self.global_state == PermissionState::Granted
|| match path.as_ref() {
None => false,
Some(path) => self
.granted_list
.iter()
.any(|path_| path.starts_with(&path_.0)),
}
{
PermissionState::Granted
} else {
PermissionState::Prompt
}
}
pub fn request(&mut self, path: Option<&Path>) -> PermissionState {
if let Some(path) = path {
let (resolved_path, display_path) = resolved_and_display_path(path);
let state = self.query(Some(&resolved_path));
if state == PermissionState::Prompt {
if permission_prompt(&format!(
"read access to \"{}\"",
display_path.display()
)) {
self.granted_list.insert(ReadDescriptor(resolved_path));
PermissionState::Granted
} else {
self.denied_list.insert(ReadDescriptor(resolved_path));
self.global_state = PermissionState::Denied;
PermissionState::Denied
}
} else if state == PermissionState::Granted {
self.granted_list.insert(ReadDescriptor(resolved_path));
PermissionState::Granted
} else {
state
}
} else {
let state = self.query(None);
if state == PermissionState::Prompt {
if permission_prompt("read access") {
self.granted_list.clear();
self.global_state = PermissionState::Granted;
PermissionState::Granted
} else {
self.global_state = PermissionState::Denied;
PermissionState::Denied
}
} else {
state
}
}
}
pub fn revoke(&mut self, path: Option<&Path>) -> PermissionState {
if let Some(path) = path {
let path = resolve_from_cwd(path).unwrap();
self
.granted_list
.retain(|path_| !path.starts_with(&path_.0));
} else {
self.granted_list.clear();
}
if self.global_state == PermissionState::Granted {
self.global_state = PermissionState::Prompt;
}
self.query(path)
}
pub fn check(&mut self, path: &Path) -> Result<(), AnyError> {
let (resolved_path, display_path) = resolved_and_display_path(path);
let (result, prompted) = self.query(Some(&resolved_path)).check(
self.name,
Some(&format!("\"{}\"", display_path.display())),
self.prompt,
);
if prompted {
if result.is_ok() {
self.granted_list.insert(ReadDescriptor(resolved_path));
} else {
self.denied_list.insert(ReadDescriptor(resolved_path));
self.global_state = PermissionState::Denied;
}
}
result
}
/// As `check()`, but permission error messages will anonymize the path
/// by replacing it with the given `display`.
pub fn check_blind(
&mut self,
path: &Path,
display: &str,
) -> Result<(), AnyError> {
let resolved_path = resolve_from_cwd(path).unwrap();
let (result, prompted) = self.query(Some(&resolved_path)).check(
self.name,
Some(&format!("<{}>", display)),
self.prompt,
);
if prompted {
if result.is_ok() {
self.granted_list.insert(ReadDescriptor(resolved_path));
} else {
self.denied_list.insert(ReadDescriptor(resolved_path));
self.global_state = PermissionState::Denied;
}
}
result
}
}
impl UnaryPermission<WriteDescriptor> {
pub fn query(&self, path: Option<&Path>) -> PermissionState {
let path = path.map(|p| resolve_from_cwd(p).unwrap());
if self.global_state == PermissionState::Denied
&& match path.as_ref() {
None => true,
Some(path) => self
.denied_list
.iter()
.any(|path_| path_.0.starts_with(path)),
}
{
PermissionState::Denied
} else if self.global_state == PermissionState::Granted
|| match path.as_ref() {
None => false,
Some(path) => self
.granted_list
.iter()
.any(|path_| path.starts_with(&path_.0)),
}
{
PermissionState::Granted
} else {
PermissionState::Prompt
}
}
pub fn request(&mut self, path: Option<&Path>) -> PermissionState {
if let Some(path) = path {
let (resolved_path, display_path) = resolved_and_display_path(path);
let state = self.query(Some(&resolved_path));
if state == PermissionState::Prompt {
if permission_prompt(&format!(
"write access to \"{}\"",
display_path.display()
)) {
self.granted_list.insert(WriteDescriptor(resolved_path));
PermissionState::Granted
} else {
self.denied_list.insert(WriteDescriptor(resolved_path));
self.global_state = PermissionState::Denied;
PermissionState::Denied
}
} else if state == PermissionState::Granted {
self.granted_list.insert(WriteDescriptor(resolved_path));
PermissionState::Granted
} else {
state
}
} else {
let state = self.query(None);
if state == PermissionState::Prompt {
if permission_prompt("write access") {
self.granted_list.clear();
self.global_state = PermissionState::Granted;
PermissionState::Granted
} else {
self.global_state = PermissionState::Denied;
PermissionState::Denied
}
} else {
state
}
}
}
pub fn revoke(&mut self, path: Option<&Path>) -> PermissionState {
if let Some(path) = path {
let path = resolve_from_cwd(path).unwrap();
self
.granted_list
.retain(|path_| !path.starts_with(&path_.0));
} else {
self.granted_list.clear();
}
if self.global_state == PermissionState::Granted {
self.global_state = PermissionState::Prompt;
}
self.query(path)
}
pub fn check(&mut self, path: &Path) -> Result<(), AnyError> {
let (resolved_path, display_path) = resolved_and_display_path(path);
let (result, prompted) = self.query(Some(&resolved_path)).check(
self.name,
Some(&format!("\"{}\"", display_path.display())),
self.prompt,
);
if prompted {
if result.is_ok() {
self.granted_list.insert(WriteDescriptor(resolved_path));
} else {
self.denied_list.insert(WriteDescriptor(resolved_path));
self.global_state = PermissionState::Denied;
}
}
result
}
}
impl UnaryPermission<NetDescriptor> {
pub fn query<T: AsRef<str>>(
&self,
host: Option<&(T, Option<u16>)>,
) -> PermissionState {
if self.global_state == PermissionState::Denied
&& match host.as_ref() {
None => true,
Some(host) => match host.1 {
None => self
.denied_list
.iter()
.any(|host_| host.0.as_ref() == host_.0),
Some(_) => self.denied_list.contains(&NetDescriptor::new(host)),
},
}
{
PermissionState::Denied
} else if self.global_state == PermissionState::Granted
|| match host.as_ref() {
None => false,
Some(host) => {
self.granted_list.contains(&NetDescriptor::new(&&(
host.0.as_ref().to_string(),
None,
)))
|| self.granted_list.contains(&NetDescriptor::new(host))
}
}
{
PermissionState::Granted
} else {
PermissionState::Prompt
}
}
pub fn request<T: AsRef<str>>(
&mut self,
host: Option<&(T, Option<u16>)>,
) -> PermissionState {
if let Some(host) = host {
let state = self.query(Some(host));
let host = NetDescriptor::new(&host);
if state == PermissionState::Prompt {
if permission_prompt(&format!("network access to \"{}\"", host)) {
self.granted_list.insert(host);
PermissionState::Granted
} else {
self.denied_list.insert(host);
self.global_state = PermissionState::Denied;
PermissionState::Denied
}
} else if state == PermissionState::Granted {
self.granted_list.insert(host);
PermissionState::Granted
} else {
state
}
} else {
let state = self.query::<&str>(None);
if state == PermissionState::Prompt {
if permission_prompt("network access") {
self.granted_list.clear();
self.global_state = PermissionState::Granted;
PermissionState::Granted
} else {
self.global_state = PermissionState::Denied;
PermissionState::Denied
}
} else {
state
}
}
}
pub fn revoke<T: AsRef<str>>(
&mut self,
host: Option<&(T, Option<u16>)>,
) -> PermissionState {
if let Some(host) = host {
if host.1.is_some() {
self
.granted_list
.remove(&NetDescriptor(host.0.as_ref().to_string(), host.1));
}
self
.granted_list
.remove(&NetDescriptor(host.0.as_ref().to_string(), None));
} else {
self.granted_list.clear();
}
if self.global_state == PermissionState::Granted {
self.global_state = PermissionState::Prompt;
}
self.query(host)
}
pub fn check<T: AsRef<str>>(
&mut self,
host: &(T, Option<u16>),
) -> Result<(), AnyError> {
let new_host = NetDescriptor::new(&host);
let (result, prompted) = self.query(Some(host)).check(
self.name,
Some(&format!("\"{}\"", new_host)),
self.prompt,
);
if prompted {
if result.is_ok() {
self.granted_list.insert(new_host);
} else {
self.denied_list.insert(new_host);
self.global_state = PermissionState::Denied;
}
}
result
}
pub fn check_url(&mut self, url: &url::Url) -> Result<(), AnyError> {
let hostname = url
.host_str()
.ok_or_else(|| uri_error("Missing host"))?
.to_string();
let display_host = match url.port() {
None => hostname.clone(),
Some(port) => format!("{}:{}", hostname, port),
};
let host = &(&hostname, url.port_or_known_default());
let (result, prompted) = self.query(Some(host)).check(
self.name,
Some(&format!("\"{}\"", display_host)),
self.prompt,
);
if prompted {
if result.is_ok() {
self.granted_list.insert(NetDescriptor::new(&host));
} else {
self.denied_list.insert(NetDescriptor::new(&host));
self.global_state = PermissionState::Denied;
}
}
result
}
}
impl UnaryPermission<EnvDescriptor> {
pub fn query(&self, env: Option<&str>) -> PermissionState {
#[cfg(windows)]
let env = env.map(|env| env.to_uppercase());
#[cfg(windows)]
let env = env.as_deref();
if self.global_state == PermissionState::Denied
&& match env {
None => true,
Some(env) => self.denied_list.iter().any(|env_| env_.0 == env),
}
{
PermissionState::Denied
} else if self.global_state == PermissionState::Granted
|| match env {
None => false,
Some(env) => self.granted_list.iter().any(|env_| env_.0 == env),
}
{
PermissionState::Granted
} else {
PermissionState::Prompt
}
}
pub fn request(&mut self, env: Option<&str>) -> PermissionState {
if let Some(env) = env {
let env = if cfg!(windows) {
env.to_uppercase()
} else {
env.to_string()
};
let state = self.query(Some(&env));
if state == PermissionState::Prompt {
if permission_prompt(&format!("env access to \"{}\"", env)) {
self.granted_list.insert(EnvDescriptor(env));
PermissionState::Granted
} else {
self.denied_list.insert(EnvDescriptor(env));
self.global_state = PermissionState::Denied;
PermissionState::Denied
}
} else if state == PermissionState::Granted {
self.granted_list.insert(EnvDescriptor(env));
PermissionState::Granted
} else {
state
}
} else {
let state = self.query(None);
if state == PermissionState::Prompt {
if permission_prompt("env access") {
self.granted_list.clear();
self.global_state = PermissionState::Granted;
PermissionState::Granted
} else {
self.global_state = PermissionState::Denied;
PermissionState::Denied
}
} else {
state
}
}
}
pub fn revoke(&mut self, env: Option<&str>) -> PermissionState {
if let Some(env) = env {
#[cfg(windows)]
let env = env.to_uppercase();
self.granted_list.remove(&EnvDescriptor(env.to_string()));
} else {
self.granted_list.clear();
}
if self.global_state == PermissionState::Granted {
self.global_state = PermissionState::Prompt;
}
self.query(env)
}
pub fn check(&mut self, env: &str) -> Result<(), AnyError> {
#[cfg(windows)]
let env = &env.to_uppercase();
let (result, prompted) = self.query(Some(env)).check(
self.name,
Some(&format!("\"{}\"", env)),
self.prompt,
);
if prompted {
if result.is_ok() {
self.granted_list.insert(EnvDescriptor(env.to_string()));
} else {
self.denied_list.insert(EnvDescriptor(env.to_string()));
self.global_state = PermissionState::Denied;
}
}
result
}
pub fn check_all(&mut self) -> Result<(), AnyError> {
let (result, prompted) =
self.query(None).check(self.name, Some("all"), self.prompt);
if prompted {
if result.is_ok() {
self.global_state = PermissionState::Granted;
} else {
self.global_state = PermissionState::Denied;
}
}
result
}
}
impl UnaryPermission<RunDescriptor> {
pub fn query(&self, cmd: Option<&str>) -> PermissionState {
if self.global_state == PermissionState::Denied
&& match cmd {
None => true,
Some(cmd) => self.denied_list.iter().any(|cmd_| cmd_.0 == cmd),
}
{
PermissionState::Denied
} else if self.global_state == PermissionState::Granted
|| match cmd {
None => false,
Some(cmd) => self.granted_list.iter().any(|cmd_| cmd_.0 == cmd),
}
{
PermissionState::Granted
} else {
PermissionState::Prompt
}
}
pub fn request(&mut self, cmd: Option<&str>) -> PermissionState {
if let Some(cmd) = cmd {
let state = self.query(Some(cmd));
if state == PermissionState::Prompt {
if permission_prompt(&format!("run access to \"{}\"", cmd)) {
self.granted_list.insert(RunDescriptor(cmd.to_string()));
PermissionState::Granted
} else {
self.denied_list.insert(RunDescriptor(cmd.to_string()));
self.global_state = PermissionState::Denied;
PermissionState::Denied
}
} else if state == PermissionState::Granted {
self.granted_list.insert(RunDescriptor(cmd.to_string()));
PermissionState::Granted
} else {
state
}
} else {
let state = self.query(None);
if state == PermissionState::Prompt {
if permission_prompt("run access") {
self.granted_list.clear();
self.global_state = PermissionState::Granted;
PermissionState::Granted
} else {
self.global_state = PermissionState::Denied;
PermissionState::Denied
}
} else {
state
}
}
}
pub fn revoke(&mut self, cmd: Option<&str>) -> PermissionState {
if let Some(cmd) = cmd {
self.granted_list.remove(&RunDescriptor(cmd.to_string()));
} else {
self.granted_list.clear();
}
if self.global_state == PermissionState::Granted {
self.global_state = PermissionState::Prompt;
}
self.query(cmd)
}
pub fn check(&mut self, cmd: &str) -> Result<(), AnyError> {
let (result, prompted) = self.query(Some(cmd)).check(
self.name,
Some(&format!("\"{}\"", cmd)),
self.prompt,
);
if prompted {
if result.is_ok() {
self.granted_list.insert(RunDescriptor(cmd.to_string()));
} else {
self.denied_list.insert(RunDescriptor(cmd.to_string()));
self.global_state = PermissionState::Denied;
}
}
result
}
pub fn check_all(&mut self) -> Result<(), AnyError> {
let (result, prompted) =
self.query(None).check(self.name, Some("all"), self.prompt);
if prompted {
if result.is_ok() {
self.global_state = PermissionState::Granted;
} else {
self.global_state = PermissionState::Denied;
}
}
result
}
}
impl UnaryPermission<FfiDescriptor> {
pub fn query(&self, lib: Option<&str>) -> PermissionState {
if self.global_state == PermissionState::Denied
&& match lib {
None => true,
Some(lib) => self.denied_list.iter().any(|lib_| lib_.0 == lib),
}
{
PermissionState::Denied
} else if self.global_state == PermissionState::Granted
|| match lib {
None => false,
Some(lib) => self.granted_list.iter().any(|lib_| lib_.0 == lib),
}
{
PermissionState::Granted
} else {
PermissionState::Prompt
}
}
pub fn request(&mut self, lib: Option<&str>) -> PermissionState {
if let Some(lib) = lib {
let state = self.query(Some(lib));
if state == PermissionState::Prompt {
if permission_prompt(&format!("ffi access to \"{}\"", lib)) {
self.granted_list.insert(FfiDescriptor(lib.to_string()));
PermissionState::Granted
} else {
self.denied_list.insert(FfiDescriptor(lib.to_string()));
self.global_state = PermissionState::Denied;
PermissionState::Denied
}
} else if state == PermissionState::Granted {
self.granted_list.insert(FfiDescriptor(lib.to_string()));
PermissionState::Granted
} else {
state
}
} else {
let state = self.query(None);
if state == PermissionState::Prompt {
if permission_prompt("ffi access") {
self.granted_list.clear();
self.global_state = PermissionState::Granted;
PermissionState::Granted
} else {
self.global_state = PermissionState::Denied;
PermissionState::Denied
}
} else {
state
}
}
}
pub fn revoke(&mut self, lib: Option<&str>) -> PermissionState {
if let Some(lib) = lib {
self.granted_list.remove(&FfiDescriptor(lib.to_string()));
} else {
self.granted_list.clear();
}
if self.global_state == PermissionState::Granted {
self.global_state = PermissionState::Prompt;
}
self.query(lib)
}
pub fn check(&mut self, lib: &str) -> Result<(), AnyError> {
let (result, prompted) = self.query(Some(lib)).check(
self.name,
Some(&format!("\"{}\"", lib)),
self.prompt,
);
if prompted {
if result.is_ok() {
self.granted_list.insert(FfiDescriptor(lib.to_string()));
} else {
self.denied_list.insert(FfiDescriptor(lib.to_string()));
self.global_state = PermissionState::Denied;
}
}
result
}
pub fn check_all(&mut self) -> Result<(), AnyError> {
let (result, prompted) =
self.query(None).check(self.name, Some("all"), self.prompt);
if prompted {
if result.is_ok() {
self.global_state = PermissionState::Granted;
} else {
self.global_state = PermissionState::Denied;
}
}
result
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Permissions {
pub read: UnaryPermission<ReadDescriptor>,
pub write: UnaryPermission<WriteDescriptor>,
pub net: UnaryPermission<NetDescriptor>,
pub env: UnaryPermission<EnvDescriptor>,
pub run: UnaryPermission<RunDescriptor>,
pub ffi: UnaryPermission<FfiDescriptor>,
pub hrtime: UnitPermission,
}
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
pub struct PermissionsOptions {
pub allow_env: Option<Vec<String>>,
pub allow_hrtime: bool,
pub allow_net: Option<Vec<String>>,
pub allow_ffi: Option<Vec<String>>,
pub allow_read: Option<Vec<PathBuf>>,
pub allow_run: Option<Vec<String>>,
pub allow_write: Option<Vec<PathBuf>>,
pub prompt: bool,
}
impl Permissions {
pub fn new_read(
state: &Option<Vec<PathBuf>>,
prompt: bool,
) -> UnaryPermission<ReadDescriptor> {
UnaryPermission::<ReadDescriptor> {
name: "read",
description: "read the file system",
global_state: global_state_from_option(state),
granted_list: resolve_read_allowlist(state),
denied_list: Default::default(),
prompt,
}
}
pub fn new_write(
state: &Option<Vec<PathBuf>>,
prompt: bool,
) -> UnaryPermission<WriteDescriptor> {
UnaryPermission::<WriteDescriptor> {
name: "write",
description: "write to the file system",
global_state: global_state_from_option(state),
granted_list: resolve_write_allowlist(state),
denied_list: Default::default(),
prompt,
}
}
pub fn new_net(
state: &Option<Vec<String>>,
prompt: bool,
) -> UnaryPermission<NetDescriptor> {
UnaryPermission::<NetDescriptor> {
name: "net",
description: "network",
global_state: global_state_from_option(state),
granted_list: state
.as_ref()
.map(|v| {
v.iter()
.map(|x| NetDescriptor::from_string(x.clone()))
.collect()
})
.unwrap_or_else(HashSet::new),
denied_list: Default::default(),
prompt,
}
}
pub fn new_env(
state: &Option<Vec<String>>,
prompt: bool,
) -> UnaryPermission<EnvDescriptor> {
UnaryPermission::<EnvDescriptor> {
name: "env",
description: "environment variables",
global_state: global_state_from_option(state),
granted_list: state
.as_ref()
.map(|v| {
v.iter()
.map(|x| {
EnvDescriptor(if cfg!(windows) {
x.to_uppercase()
} else {
x.clone()
})
})
.collect()
})
.unwrap_or_else(HashSet::new),
denied_list: Default::default(),
prompt,
}
}
pub fn new_run(
state: &Option<Vec<String>>,
prompt: bool,
) -> UnaryPermission<RunDescriptor> {
UnaryPermission::<RunDescriptor> {
name: "run",
description: "run a subprocess",
global_state: global_state_from_option(state),
granted_list: state
.as_ref()
.map(|v| v.iter().map(|x| RunDescriptor(x.clone())).collect())
.unwrap_or_else(HashSet::new),
denied_list: Default::default(),
prompt,
}
}
pub fn new_ffi(
state: &Option<Vec<String>>,
prompt: bool,
) -> UnaryPermission<FfiDescriptor> {
UnaryPermission::<FfiDescriptor> {
name: "ffi",
description: "load a dynamic library",
global_state: global_state_from_option(state),
granted_list: state
.as_ref()
.map(|v| v.iter().map(|x| FfiDescriptor(x.clone())).collect())
.unwrap_or_else(HashSet::new),
denied_list: Default::default(),
prompt,
}
}
pub fn new_hrtime(state: bool, prompt: bool) -> UnitPermission {
unit_permission_from_flag_bool(
state,
"hrtime",
"high precision time",
prompt,
)
}
pub fn from_options(opts: &PermissionsOptions) -> Self {
Self {
read: Permissions::new_read(&opts.allow_read, opts.prompt),
write: Permissions::new_write(&opts.allow_write, opts.prompt),
net: Permissions::new_net(&opts.allow_net, opts.prompt),
env: Permissions::new_env(&opts.allow_env, opts.prompt),
run: Permissions::new_run(&opts.allow_run, opts.prompt),
ffi: Permissions::new_ffi(&opts.allow_ffi, opts.prompt),
hrtime: Permissions::new_hrtime(opts.allow_hrtime, opts.prompt),
}
}
pub fn allow_all() -> Self {
Self {
read: Permissions::new_read(&Some(vec![]), false),
write: Permissions::new_write(&Some(vec![]), false),
net: Permissions::new_net(&Some(vec![]), false),
env: Permissions::new_env(&Some(vec![]), false),
run: Permissions::new_run(&Some(vec![]), false),
ffi: Permissions::new_ffi(&Some(vec![]), false),
hrtime: Permissions::new_hrtime(true, false),
}
}
/// A helper function that determines if the module specifier is a local or
/// remote, and performs a read or net check for the specifier.
pub fn check_specifier(
&mut self,
specifier: &ModuleSpecifier,
) -> Result<(), AnyError> {
match specifier.scheme() {
"file" => match specifier.to_file_path() {
Ok(path) => self.read.check(&path),
Err(_) => Err(uri_error(format!(
"Invalid file path.\n Specifier: {}",
specifier
))),
},
"data" => Ok(()),
"blob" => Ok(()),
_ => self.net.check_url(specifier),
}
}
}
impl deno_net::NetPermissions for Permissions {
fn check_net<T: AsRef<str>>(
&mut self,
host: &(T, Option<u16>),
) -> Result<(), AnyError> {
self.net.check(host)
}
fn check_read(&mut self, path: &Path) -> Result<(), AnyError> {
self.read.check(path)
}
fn check_write(&mut self, path: &Path) -> Result<(), AnyError> {
self.write.check(path)
}
}
impl deno_fetch::FetchPermissions for Permissions {
fn check_net_url(&mut self, url: &url::Url) -> Result<(), AnyError> {
self.net.check_url(url)
}
fn check_read(&mut self, path: &Path) -> Result<(), AnyError> {
self.read.check(path)
}
}
impl deno_timers::TimersPermission for Permissions {
fn allow_hrtime(&mut self) -> bool {
self.hrtime.check().is_ok()
}
fn check_unstable(&self, state: &OpState, api_name: &'static str) {
crate::ops::check_unstable(state, api_name);
}
}
impl deno_websocket::WebSocketPermissions for Permissions {
fn check_net_url(&mut self, url: &url::Url) -> Result<(), AnyError> {
self.net.check_url(url)
}
}
impl deno_ffi::FfiPermissions for Permissions {
fn check(&mut self, path: &str) -> Result<(), AnyError> {
self.ffi.check(path)
}
}
fn unit_permission_from_flag_bool(
flag: bool,
name: &'static str,
description: &'static str,
prompt: bool,
) -> UnitPermission {
UnitPermission {
name,
description,
state: if flag {
PermissionState::Granted
} else {
PermissionState::Prompt
},
prompt,
}
}
fn global_state_from_option<T>(flag: &Option<Vec<T>>) -> PermissionState {
if matches!(flag, Some(v) if v.is_empty()) {
PermissionState::Granted
} else {
PermissionState::Prompt
}
}
pub fn resolve_read_allowlist(
allow: &Option<Vec<PathBuf>>,
) -> HashSet<ReadDescriptor> {
if let Some(v) = allow {
v.iter()
.map(|raw_path| {
ReadDescriptor(resolve_from_cwd(Path::new(&raw_path)).unwrap())
})
.collect()
} else {
HashSet::new()
}
}
pub fn resolve_write_allowlist(
allow: &Option<Vec<PathBuf>>,
) -> HashSet<WriteDescriptor> {
if let Some(v) = allow {
v.iter()
.map(|raw_path| {
WriteDescriptor(resolve_from_cwd(Path::new(&raw_path)).unwrap())
})
.collect()
} else {
HashSet::new()
}
}
/// Arbitrary helper. Resolves the path from CWD, and also gets a path that
/// can be displayed without leaking the CWD when not allowed.
fn resolved_and_display_path(path: &Path) -> (PathBuf, PathBuf) {
let resolved_path = resolve_from_cwd(path).unwrap();
let display_path = path.to_path_buf();
(resolved_path, display_path)
}
/// Shows the permission prompt and returns the answer according to the user input.
/// This loops until the user gives the proper input.
#[cfg(not(test))]
fn permission_prompt(message: &str) -> bool {
if !atty::is(atty::Stream::Stdin) || !atty::is(atty::Stream::Stderr) {
return false;
};
#[cfg(unix)]
fn clear_stdin() {
let r = unsafe { libc::tcflush(0, libc::TCIFLUSH) };
assert_eq!(r, 0);
}
#[cfg(not(unix))]
fn clear_stdin() {
use winapi::shared::minwindef::TRUE;
use winapi::shared::minwindef::UINT;
use winapi::shared::minwindef::WORD;
use winapi::shared::ntdef::WCHAR;
use winapi::um::processenv::GetStdHandle;
use winapi::um::winbase::STD_INPUT_HANDLE;
use winapi::um::wincon::FlushConsoleInputBuffer;
use winapi::um::wincon::PeekConsoleInputW;
use winapi::um::wincon::WriteConsoleInputW;
use winapi::um::wincontypes::INPUT_RECORD;
use winapi::um::wincontypes::KEY_EVENT;
use winapi::um::winnt::HANDLE;
use winapi::um::winuser::MapVirtualKeyW;
use winapi::um::winuser::MAPVK_VK_TO_VSC;
use winapi::um::winuser::VK_RETURN;
unsafe {
let stdin = GetStdHandle(STD_INPUT_HANDLE);
// emulate an enter key press to clear any line buffered console characters
emulate_enter_key_press(stdin);
// read the buffered line or enter key press
read_stdin_line();
// check if our emulated key press was executed
if is_input_buffer_empty(stdin) {
// if so, move the cursor up to prevent a blank line
move_cursor_up();
} else {
// the emulated key press is still pending, so a buffered line was read
// and we can flush the emulated key press
flush_input_buffer(stdin);
}
}
unsafe fn flush_input_buffer(stdin: HANDLE) {
let success = FlushConsoleInputBuffer(stdin);
if success != TRUE {
panic!(
"Error flushing console input buffer: {}",
std::io::Error::last_os_error().to_string()
)
}
}
unsafe fn emulate_enter_key_press(stdin: HANDLE) {
// https://github.com/libuv/libuv/blob/a39009a5a9252a566ca0704d02df8dabc4ce328f/src/win/tty.c#L1121-L1131
let mut input_record: INPUT_RECORD = std::mem::zeroed();
input_record.EventType = KEY_EVENT;
input_record.Event.KeyEvent_mut().bKeyDown = TRUE;
input_record.Event.KeyEvent_mut().wRepeatCount = 1;
input_record.Event.KeyEvent_mut().wVirtualKeyCode = VK_RETURN as WORD;
input_record.Event.KeyEvent_mut().wVirtualScanCode =
MapVirtualKeyW(VK_RETURN as UINT, MAPVK_VK_TO_VSC) as WORD;
*input_record.Event.KeyEvent_mut().uChar.UnicodeChar_mut() =
'\r' as WCHAR;
let mut record_written = 0;
let success =
WriteConsoleInputW(stdin, &input_record, 1, &mut record_written);
if success != TRUE {
panic!(
"Error emulating enter key press: {}",
std::io::Error::last_os_error().to_string()
)
}
}
unsafe fn is_input_buffer_empty(stdin: HANDLE) -> bool {
let mut buffer = Vec::with_capacity(1);
let mut events_read = 0;
let success =
PeekConsoleInputW(stdin, buffer.as_mut_ptr(), 1, &mut events_read);
if success != TRUE {
panic!(
"Error peeking console input buffer: {}",
std::io::Error::last_os_error().to_string()
)
}
events_read == 0
}
fn move_cursor_up() {
use std::io::Write;
write!(std::io::stderr(), "\x1B[1A").expect("expected to move cursor up");
}
fn read_stdin_line() {
let mut input = String::new();
let stdin = std::io::stdin();
stdin.read_line(&mut input).expect("expected to read line");
}
}
// For security reasons we must consume everything in stdin so that previously
// buffered data cannot effect the prompt.
clear_stdin();
let opts = "[y/n (y = yes allow, n = no deny)] ";
let msg = format!(
"{} Deno requests {}. Allow? {}",
PERMISSION_EMOJI, message, opts
);
// print to stderr so that if deno is > to a file this is still displayed.
eprint!("{}", colors::bold(&msg));
loop {
let mut input = String::new();
let stdin = std::io::stdin();
let result = stdin.read_line(&mut input);
if result.is_err() {
return false;
};
let ch = match input.chars().next() {
None => return false,
Some(v) => v,
};
match ch.to_ascii_lowercase() {
'y' => return true,
'n' => return false,
_ => {
// If we don't get a recognized option try again.
let msg_again = format!("Unrecognized option '{}' {}", ch, opts);
eprint!("{}", colors::bold(&msg_again));
}
};
}
}
// When testing, permission prompt returns the value of STUB_PROMPT_VALUE
// which we set from the test functions.
#[cfg(test)]
fn permission_prompt(_message: &str) -> bool {
STUB_PROMPT_VALUE.load(Ordering::SeqCst)
}
#[cfg(test)]
lazy_static::lazy_static! {
/// Lock this when you use `set_prompt_result` in a test case.
static ref PERMISSION_PROMPT_GUARD: Mutex<()> = Mutex::new(());
}
#[cfg(test)]
static STUB_PROMPT_VALUE: AtomicBool = AtomicBool::new(true);
#[cfg(test)]
fn set_prompt_result(value: bool) {
STUB_PROMPT_VALUE.store(value, Ordering::SeqCst);
}
#[cfg(test)]
mod tests {
use super::*;
use deno_core::resolve_url_or_path;
// Creates vector of strings, Vec<String>
macro_rules! svec {
($($x:expr),*) => (vec![$($x.to_string()),*]);
}
#[test]
fn check_paths() {
let allowlist = vec![
PathBuf::from("/a/specific/dir/name"),
PathBuf::from("/a/specific"),
PathBuf::from("/b/c"),
];
let mut perms = Permissions::from_options(&PermissionsOptions {
allow_read: Some(allowlist.clone()),
allow_write: Some(allowlist),
..Default::default()
});
// Inside of /a/specific and /a/specific/dir/name
assert!(perms.read.check(Path::new("/a/specific/dir/name")).is_ok());
assert!(perms.write.check(Path::new("/a/specific/dir/name")).is_ok());
// Inside of /a/specific but outside of /a/specific/dir/name
assert!(perms.read.check(Path::new("/a/specific/dir")).is_ok());
assert!(perms.write.check(Path::new("/a/specific/dir")).is_ok());
// Inside of /a/specific and /a/specific/dir/name
assert!(perms
.read
.check(Path::new("/a/specific/dir/name/inner"))
.is_ok());
assert!(perms
.write
.check(Path::new("/a/specific/dir/name/inner"))
.is_ok());
// Inside of /a/specific but outside of /a/specific/dir/name
assert!(perms.read.check(Path::new("/a/specific/other/dir")).is_ok());
assert!(perms
.write
.check(Path::new("/a/specific/other/dir"))
.is_ok());
// Exact match with /b/c
assert!(perms.read.check(Path::new("/b/c")).is_ok());
assert!(perms.write.check(Path::new("/b/c")).is_ok());
// Sub path within /b/c
assert!(perms.read.check(Path::new("/b/c/sub/path")).is_ok());
assert!(perms.write.check(Path::new("/b/c/sub/path")).is_ok());
// Sub path within /b/c, needs normalizing
assert!(perms
.read
.check(Path::new("/b/c/sub/path/../path/."))
.is_ok());
assert!(perms
.write
.check(Path::new("/b/c/sub/path/../path/."))
.is_ok());
// Inside of /b but outside of /b/c
assert!(perms.read.check(Path::new("/b/e")).is_err());
assert!(perms.write.check(Path::new("/b/e")).is_err());
// Inside of /a but outside of /a/specific
assert!(perms.read.check(Path::new("/a/b")).is_err());
assert!(perms.write.check(Path::new("/a/b")).is_err());
}
#[test]
fn test_check_net_with_values() {
let mut perms = Permissions::from_options(&PermissionsOptions {
allow_net: Some(svec![
"localhost",
"deno.land",
"github.com:3000",
"127.0.0.1",
"172.16.0.2:8000",
"www.github.com:443"
]),
..Default::default()
});
let domain_tests = vec![
("localhost", 1234, true),
("deno.land", 0, true),
("deno.land", 3000, true),
("deno.lands", 0, false),
("deno.lands", 3000, false),
("github.com", 3000, true),
("github.com", 0, false),
("github.com", 2000, false),
("github.net", 3000, false),
("127.0.0.1", 0, true),
("127.0.0.1", 3000, true),
("127.0.0.2", 0, false),
("127.0.0.2", 3000, false),
("172.16.0.2", 8000, true),
("172.16.0.2", 0, false),
("172.16.0.2", 6000, false),
("172.16.0.1", 8000, false),
// Just some random hosts that should err
("somedomain", 0, false),
("192.168.0.1", 0, false),
];
for (host, port, is_ok) in domain_tests {
assert_eq!(is_ok, perms.net.check(&(host, Some(port))).is_ok());
}
}
#[test]
fn test_check_net_only_flag() {
let mut perms = Permissions::from_options(&PermissionsOptions {
allow_net: Some(svec![]), // this means `--allow-net` is present without values following `=` sign
..Default::default()
});
let domain_tests = vec![
("localhost", 1234),
("deno.land", 0),
("deno.land", 3000),
("deno.lands", 0),
("deno.lands", 3000),
("github.com", 3000),
("github.com", 0),
("github.com", 2000),
("github.net", 3000),
("127.0.0.1", 0),
("127.0.0.1", 3000),
("127.0.0.2", 0),
("127.0.0.2", 3000),
("172.16.0.2", 8000),
("172.16.0.2", 0),
("172.16.0.2", 6000),
("172.16.0.1", 8000),
("somedomain", 0),
("192.168.0.1", 0),
];
for (host, port) in domain_tests {
assert!(perms.net.check(&(host, Some(port))).is_ok());
}
}
#[test]
fn test_check_net_no_flag() {
let mut perms = Permissions::from_options(&PermissionsOptions {
allow_net: None,
..Default::default()
});
let domain_tests = vec![
("localhost", 1234),
("deno.land", 0),
("deno.land", 3000),
("deno.lands", 0),
("deno.lands", 3000),
("github.com", 3000),
("github.com", 0),
("github.com", 2000),
("github.net", 3000),
("127.0.0.1", 0),
("127.0.0.1", 3000),
("127.0.0.2", 0),
("127.0.0.2", 3000),
("172.16.0.2", 8000),
("172.16.0.2", 0),
("172.16.0.2", 6000),
("172.16.0.1", 8000),
("somedomain", 0),
("192.168.0.1", 0),
];
for (host, port) in domain_tests {
assert!(!perms.net.check(&(host, Some(port))).is_ok());
}
}
#[test]
fn test_check_net_url() {
let mut perms = Permissions::from_options(&PermissionsOptions {
allow_net: Some(svec![
"localhost",
"deno.land",
"github.com:3000",
"127.0.0.1",
"172.16.0.2:8000",
"www.github.com:443"
]),
..Default::default()
});
let url_tests = vec![
// Any protocol + port for localhost should be ok, since we don't specify
("http://localhost", true),
("https://localhost", true),
("https://localhost:4443", true),
("tcp://localhost:5000", true),
("udp://localhost:6000", true),
// Correct domain + any port and protocol should be ok incorrect shouldn't
("https://deno.land/std/example/welcome.ts", true),
("https://deno.land:3000/std/example/welcome.ts", true),
("https://deno.lands/std/example/welcome.ts", false),
("https://deno.lands:3000/std/example/welcome.ts", false),
// Correct domain + port should be ok all other combinations should err
("https://github.com:3000/denoland/deno", true),
("https://github.com/denoland/deno", false),
("https://github.com:2000/denoland/deno", false),
("https://github.net:3000/denoland/deno", false),
// Correct ipv4 address + any port should be ok others should err
("tcp://127.0.0.1", true),
("https://127.0.0.1", true),
("tcp://127.0.0.1:3000", true),
("https://127.0.0.1:3000", true),
("tcp://127.0.0.2", false),
("https://127.0.0.2", false),
("tcp://127.0.0.2:3000", false),
("https://127.0.0.2:3000", false),
// Correct address + port should be ok all other combinations should err
("tcp://172.16.0.2:8000", true),
("https://172.16.0.2:8000", true),
("tcp://172.16.0.2", false),
("https://172.16.0.2", false),
("tcp://172.16.0.2:6000", false),
("https://172.16.0.2:6000", false),
("tcp://172.16.0.1:8000", false),
("https://172.16.0.1:8000", false),
// Testing issue #6531 (Network permissions check doesn't account for well-known default ports) so we dont regress
("https://www.github.com:443/robots.txt", true),
];
for (url_str, is_ok) in url_tests {
let u = url::Url::parse(url_str).unwrap();
assert_eq!(is_ok, perms.net.check_url(&u).is_ok());
}
}
#[test]
fn check_specifiers() {
let read_allowlist = if cfg!(target_os = "windows") {
vec![PathBuf::from("C:\\a")]
} else {
vec![PathBuf::from("/a")]
};
let mut perms = Permissions::from_options(&PermissionsOptions {
allow_read: Some(read_allowlist),
allow_net: Some(svec!["localhost"]),
..Default::default()
});
let mut fixtures = vec![
(
resolve_url_or_path("http://localhost:4545/mod.ts").unwrap(),
true,
),
(
resolve_url_or_path("http://deno.land/x/mod.ts").unwrap(),
false,
),
(
resolve_url_or_path("data:text/plain,Hello%2C%20Deno!").unwrap(),
true,
),
];
if cfg!(target_os = "windows") {
fixtures
.push((resolve_url_or_path("file:///C:/a/mod.ts").unwrap(), true));
fixtures
.push((resolve_url_or_path("file:///C:/b/mod.ts").unwrap(), false));
} else {
fixtures.push((resolve_url_or_path("file:///a/mod.ts").unwrap(), true));
fixtures.push((resolve_url_or_path("file:///b/mod.ts").unwrap(), false));
}
for (specifier, expected) in fixtures {
assert_eq!(perms.check_specifier(&specifier).is_ok(), expected);
}
}
#[test]
fn check_invalid_specifiers() {
let mut perms = Permissions::allow_all();
let mut test_cases = vec![];
if cfg!(target_os = "windows") {
test_cases.push("file://");
test_cases.push("file:///");
} else {
test_cases.push("file://remotehost/");
}
for url in test_cases {
assert!(perms
.check_specifier(&resolve_url_or_path(url).unwrap())
.is_err());
}
}
#[test]
fn test_query() {
let perms1 = Permissions::allow_all();
let perms2 = Permissions {
read: UnaryPermission {
global_state: PermissionState::Prompt,
..Permissions::new_read(&Some(vec![PathBuf::from("/foo")]), false)
},
write: UnaryPermission {
global_state: PermissionState::Prompt,
..Permissions::new_write(&Some(vec![PathBuf::from("/foo")]), false)
},
net: UnaryPermission {
global_state: PermissionState::Prompt,
..Permissions::new_net(&Some(svec!["127.0.0.1:8000"]), false)
},
env: UnaryPermission {
global_state: PermissionState::Prompt,
..Permissions::new_env(&Some(svec!["HOME"]), false)
},
run: UnaryPermission {
global_state: PermissionState::Prompt,
..Permissions::new_run(&Some(svec!["deno"]), false)
},
ffi: UnaryPermission {
global_state: PermissionState::Prompt,
..Permissions::new_ffi(&Some(svec!["deno"]), false)
},
hrtime: UnitPermission {
state: PermissionState::Prompt,
..Default::default()
},
};
#[rustfmt::skip]
{
assert_eq!(perms1.read.query(None), PermissionState::Granted);
assert_eq!(perms1.read.query(Some(Path::new("/foo"))), PermissionState::Granted);
assert_eq!(perms2.read.query(None), PermissionState::Prompt);
assert_eq!(perms2.read.query(Some(Path::new("/foo"))), PermissionState::Granted);
assert_eq!(perms2.read.query(Some(Path::new("/foo/bar"))), PermissionState::Granted);
assert_eq!(perms1.write.query(None), PermissionState::Granted);
assert_eq!(perms1.write.query(Some(Path::new("/foo"))), PermissionState::Granted);
assert_eq!(perms2.write.query(None), PermissionState::Prompt);
assert_eq!(perms2.write.query(Some(Path::new("/foo"))), PermissionState::Granted);
assert_eq!(perms2.write.query(Some(Path::new("/foo/bar"))), PermissionState::Granted);
assert_eq!(perms1.net.query::<&str>(None), PermissionState::Granted);
assert_eq!(perms1.net.query(Some(&("127.0.0.1", None))), PermissionState::Granted);
assert_eq!(perms2.net.query::<&str>(None), PermissionState::Prompt);
assert_eq!(perms2.net.query(Some(&("127.0.0.1", Some(8000)))), PermissionState::Granted);
assert_eq!(perms1.env.query(None), PermissionState::Granted);
assert_eq!(perms1.env.query(Some(&"HOME".to_string())), PermissionState::Granted);
assert_eq!(perms2.env.query(None), PermissionState::Prompt);
assert_eq!(perms2.env.query(Some(&"HOME".to_string())), PermissionState::Granted);
assert_eq!(perms1.run.query(None), PermissionState::Granted);
assert_eq!(perms1.run.query(Some(&"deno".to_string())), PermissionState::Granted);
assert_eq!(perms2.run.query(None), PermissionState::Prompt);
assert_eq!(perms2.run.query(Some(&"deno".to_string())), PermissionState::Granted);
assert_eq!(perms1.ffi.query(None), PermissionState::Granted);
assert_eq!(perms1.ffi.query(Some(&"deno".to_string())), PermissionState::Granted);
assert_eq!(perms2.ffi.query(None), PermissionState::Prompt);
assert_eq!(perms2.ffi.query(Some(&"deno".to_string())), PermissionState::Granted);
assert_eq!(perms1.hrtime.query(), PermissionState::Granted);
assert_eq!(perms2.hrtime.query(), PermissionState::Prompt);
};
}
#[test]
fn test_request() {
let mut perms: Permissions = Default::default();
#[rustfmt::skip]
{
let _guard = PERMISSION_PROMPT_GUARD.lock();
set_prompt_result(true);
assert_eq!(perms.read.request(Some(Path::new("/foo"))), PermissionState::Granted);
assert_eq!(perms.read.query(None), PermissionState::Prompt);
set_prompt_result(false);
assert_eq!(perms.read.request(Some(Path::new("/foo/bar"))), PermissionState::Granted);
set_prompt_result(false);
assert_eq!(perms.write.request(Some(Path::new("/foo"))), PermissionState::Denied);
assert_eq!(perms.write.query(Some(Path::new("/foo/bar"))), PermissionState::Prompt);
set_prompt_result(true);
assert_eq!(perms.write.request(None), PermissionState::Denied);
set_prompt_result(true);
assert_eq!(perms.net.request(Some(&("127.0.0.1", None))), PermissionState::Granted);
set_prompt_result(false);
assert_eq!(perms.net.request(Some(&("127.0.0.1", Some(8000)))), PermissionState::Granted);
set_prompt_result(true);
assert_eq!(perms.env.request(Some(&"HOME".to_string())), PermissionState::Granted);
assert_eq!(perms.env.query(None), PermissionState::Prompt);
set_prompt_result(false);
assert_eq!(perms.env.request(Some(&"HOME".to_string())), PermissionState::Granted);
set_prompt_result(true);
assert_eq!(perms.run.request(Some(&"deno".to_string())), PermissionState::Granted);
assert_eq!(perms.run.query(None), PermissionState::Prompt);
set_prompt_result(false);
assert_eq!(perms.run.request(Some(&"deno".to_string())), PermissionState::Granted);
set_prompt_result(true);
assert_eq!(perms.ffi.request(Some(&"deno".to_string())), PermissionState::Granted);
assert_eq!(perms.ffi.query(None), PermissionState::Prompt);
set_prompt_result(false);
assert_eq!(perms.ffi.request(Some(&"deno".to_string())), PermissionState::Granted);
set_prompt_result(false);
assert_eq!(perms.hrtime.request(), PermissionState::Denied);
set_prompt_result(true);
assert_eq!(perms.hrtime.request(), PermissionState::Denied);
};
}
#[test]
fn test_revoke() {
let mut perms = Permissions {
read: UnaryPermission {
global_state: PermissionState::Prompt,
..Permissions::new_read(
&Some(vec![PathBuf::from("/foo"), PathBuf::from("/foo/baz")]),
false,
)
},
write: UnaryPermission {
global_state: PermissionState::Prompt,
..Permissions::new_write(
&Some(vec![PathBuf::from("/foo"), PathBuf::from("/foo/baz")]),
false,
)
},
net: UnaryPermission {
global_state: PermissionState::Prompt,
..Permissions::new_net(
&Some(svec!["127.0.0.1", "127.0.0.1:8000"]),
false,
)
},
env: UnaryPermission {
global_state: PermissionState::Prompt,
..Permissions::new_env(&Some(svec!["HOME"]), false)
},
run: UnaryPermission {
global_state: PermissionState::Prompt,
..Permissions::new_run(&Some(svec!["deno"]), false)
},
ffi: UnaryPermission {
global_state: PermissionState::Prompt,
..Permissions::new_ffi(&Some(svec!["deno"]), false)
},
hrtime: UnitPermission {
state: PermissionState::Denied,
..Default::default()
},
};
#[rustfmt::skip]
{
assert_eq!(perms.read.revoke(Some(Path::new("/foo/bar"))), PermissionState::Prompt);
assert_eq!(perms.read.query(Some(Path::new("/foo"))), PermissionState::Prompt);
assert_eq!(perms.read.query(Some(Path::new("/foo/baz"))), PermissionState::Granted);
assert_eq!(perms.write.revoke(Some(Path::new("/foo/bar"))), PermissionState::Prompt);
assert_eq!(perms.write.query(Some(Path::new("/foo"))), PermissionState::Prompt);
assert_eq!(perms.write.query(Some(Path::new("/foo/baz"))), PermissionState::Granted);
assert_eq!(perms.net.revoke(Some(&("127.0.0.1", Some(9000)))), PermissionState::Prompt);
assert_eq!(perms.net.query(Some(&("127.0.0.1", None))), PermissionState::Prompt);
assert_eq!(perms.net.query(Some(&("127.0.0.1", Some(8000)))), PermissionState::Granted);
assert_eq!(perms.env.revoke(Some(&"HOME".to_string())), PermissionState::Prompt);
assert_eq!(perms.run.revoke(Some(&"deno".to_string())), PermissionState::Prompt);
assert_eq!(perms.ffi.revoke(Some(&"deno".to_string())), PermissionState::Prompt);
assert_eq!(perms.hrtime.revoke(), PermissionState::Denied);
};
}
#[test]
fn test_check() {
let mut perms = Permissions {
read: Permissions::new_read(&None, true),
write: Permissions::new_write(&None, true),
net: Permissions::new_net(&None, true),
env: Permissions::new_env(&None, true),
run: Permissions::new_run(&None, true),
ffi: Permissions::new_ffi(&None, true),
hrtime: Permissions::new_hrtime(false, true),
};
let _guard = PERMISSION_PROMPT_GUARD.lock();
set_prompt_result(true);
assert!(perms.read.check(Path::new("/foo")).is_ok());
set_prompt_result(false);
assert!(perms.read.check(Path::new("/foo")).is_ok());
assert!(perms.read.check(Path::new("/bar")).is_err());
set_prompt_result(true);
assert!(perms.write.check(Path::new("/foo")).is_ok());
set_prompt_result(false);
assert!(perms.write.check(Path::new("/foo")).is_ok());
assert!(perms.write.check(Path::new("/bar")).is_err());
set_prompt_result(true);
assert!(perms.net.check(&("127.0.0.1", Some(8000))).is_ok());
set_prompt_result(false);
assert!(perms.net.check(&("127.0.0.1", Some(8000))).is_ok());
assert!(perms.net.check(&("127.0.0.1", Some(8001))).is_err());
assert!(perms.net.check(&("127.0.0.1", None)).is_err());
assert!(perms.net.check(&("deno.land", Some(8000))).is_err());
assert!(perms.net.check(&("deno.land", None)).is_err());
set_prompt_result(true);
assert!(perms.run.check("cat").is_ok());
set_prompt_result(false);
assert!(perms.run.check("cat").is_ok());
assert!(perms.run.check("ls").is_err());
set_prompt_result(true);
assert!(perms.env.check("HOME").is_ok());
set_prompt_result(false);
assert!(perms.env.check("HOME").is_ok());
assert!(perms.env.check("PATH").is_err());
set_prompt_result(true);
assert!(perms.hrtime.check().is_ok());
set_prompt_result(false);
assert!(perms.hrtime.check().is_ok());
}
#[test]
fn test_check_fail() {
let mut perms = Permissions {
read: Permissions::new_read(&None, true),
write: Permissions::new_write(&None, true),
net: Permissions::new_net(&None, true),
env: Permissions::new_env(&None, true),
run: Permissions::new_run(&None, true),
ffi: Permissions::new_ffi(&None, true),
hrtime: Permissions::new_hrtime(false, true),
};
let _guard = PERMISSION_PROMPT_GUARD.lock();
set_prompt_result(false);
assert!(perms.read.check(Path::new("/foo")).is_err());
set_prompt_result(true);
assert!(perms.read.check(Path::new("/foo")).is_err());
assert!(perms.read.check(Path::new("/bar")).is_ok());
set_prompt_result(false);
assert!(perms.read.check(Path::new("/bar")).is_ok());
set_prompt_result(false);
assert!(perms.write.check(Path::new("/foo")).is_err());
set_prompt_result(true);
assert!(perms.write.check(Path::new("/foo")).is_err());
assert!(perms.write.check(Path::new("/bar")).is_ok());
set_prompt_result(false);
assert!(perms.write.check(Path::new("/bar")).is_ok());
set_prompt_result(false);
assert!(perms.net.check(&("127.0.0.1", Some(8000))).is_err());
set_prompt_result(true);
assert!(perms.net.check(&("127.0.0.1", Some(8000))).is_err());
assert!(perms.net.check(&("127.0.0.1", Some(8001))).is_ok());
assert!(perms.net.check(&("deno.land", Some(8000))).is_ok());
set_prompt_result(false);
assert!(perms.net.check(&("127.0.0.1", Some(8001))).is_ok());
assert!(perms.net.check(&("deno.land", Some(8000))).is_ok());
set_prompt_result(false);
assert!(perms.run.check("cat").is_err());
set_prompt_result(true);
assert!(perms.run.check("cat").is_err());
assert!(perms.run.check("ls").is_ok());
set_prompt_result(false);
assert!(perms.run.check("ls").is_ok());
set_prompt_result(false);
assert!(perms.env.check("HOME").is_err());
set_prompt_result(true);
assert!(perms.env.check("HOME").is_err());
assert!(perms.env.check("PATH").is_ok());
set_prompt_result(false);
assert!(perms.env.check("PATH").is_ok());
set_prompt_result(false);
assert!(perms.hrtime.check().is_err());
set_prompt_result(true);
assert!(perms.hrtime.check().is_err());
}
#[test]
#[cfg(windows)]
fn test_env_windows() {
let mut perms = Permissions::allow_all();
perms.env = UnaryPermission {
global_state: PermissionState::Prompt,
..Permissions::new_env(&Some(svec!["HOME"]), false)
};
set_prompt_result(true);
assert!(perms.env.check("HOME").is_ok());
set_prompt_result(false);
assert!(perms.env.check("HOME").is_ok());
assert!(perms.env.check("hOmE").is_ok());
assert_eq!(
perms.env.revoke(Some(&"HomE".to_string())),
PermissionState::Prompt
);
}
}