Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): add test permissions to Deno.test #10188

Merged
merged 36 commits into from
Apr 25, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7fbc519
feat(cli): add test permissions to Deno.TestDefinition
caspervonb Apr 14, 2021
83a4469
Prefer const
caspervonb Apr 14, 2021
edf2c1a
Tag comment
caspervonb Apr 14, 2021
71cee3b
Better permission denied tests
caspervonb Apr 14, 2021
c6a4906
Fixup hrtime argument
caspervonb Apr 14, 2021
c3115f8
Fixup test
caspervonb Apr 14, 2021
917f5cb
Better test coverage
caspervonb Apr 14, 2021
5e873e1
Require explicit initialization of test ops
caspervonb Apr 14, 2021
587eb56
s/request/pledge/
caspervonb Apr 14, 2021
f9191aa
s/restore_test_permissions/permissions/
caspervonb Apr 14, 2021
65e2549
Format
caspervonb Apr 15, 2021
8fc8eed
s/requestTestPermissions/pledgeTestPermissions
caspervonb Apr 15, 2021
2a469aa
Lint
caspervonb Apr 15, 2021
ed9d07a
Re-use worker permissions as test permissions
caspervonb Apr 15, 2021
8a8cbc9
Format
caspervonb Apr 15, 2021
d04f19e
Lint
caspervonb Apr 15, 2021
d719813
revert test_util.ts changes
caspervonb Apr 15, 2021
001773f
Add token
caspervonb Apr 15, 2021
8cf960c
Lint
caspervonb Apr 16, 2021
ac8e8ee
Mark permissions as unstable
caspervonb Apr 16, 2021
d8429ef
Move type definition to unstable
caspervonb Apr 16, 2021
c87220f
Doc comment
caspervonb Apr 16, 2021
8cff7e1
Merge branch 'main' into feat-add-test-permissions
bartlomieju Apr 21, 2021
d95c6b0
Merge branch 'main' into feat-add-test-permissions
caspervonb Apr 22, 2021
f896194
Error if restore is called without any permissions to take
caspervonb Apr 25, 2021
4228ab6
Clarify outdated comment
caspervonb Apr 25, 2021
ac6af79
Merge branch 'main' into feat-add-test-permissions
caspervonb Apr 25, 2021
27568e2
Use Uuid inplace of Value where applicable
caspervonb Apr 25, 2021
5f07ce1
Update docs
caspervonb Apr 25, 2021
da70412
Add missing defaults doc comments
caspervonb Apr 25, 2021
34c46ea
Fix rogue u keystroke
caspervonb Apr 25, 2021
6251f43
Return ()
caspervonb Apr 25, 2021
0b9e8b6
Add Bartek's comment note
caspervonb Apr 25, 2021
5a4b659
More net doc comments
caspervonb Apr 25, 2021
83fbff0
Fixup examples
caspervonb Apr 25, 2021
afa31da
Merge branch 'main' into feat-add-test-permissions
caspervonb Apr 25, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions cli/dts/lib.deno.ns.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ declare namespace Deno {
* See: https://no-color.org/ */
export const noColor: boolean;

export interface TestPermissions {
read?: string[] | boolean;
caspervonb marked this conversation as resolved.
Show resolved Hide resolved
write?: string[] | boolean;
net?: string[] | boolean;
env?: string[] | boolean;
run?: string[] | boolean;
plugin?: boolean;
hrtime?: boolean;
}

export interface TestDefinition {
fn: () => void | Promise<void>;
name: string;
Expand All @@ -112,6 +122,10 @@ declare namespace Deno {
/** Ensure the test case does not prematurely cause the process to exit,
* for example via a call to `Deno.exit`. Defaults to true. */
sanitizeExit?: boolean;

/** Ensure the test runs with the given permission set.
* These permissions can not escalate beyond the currently active permissions. */
permissions?: TestPermissions;
}

/** Register a test which will be run when `deno test` is used on the command
Expand Down
1 change: 1 addition & 0 deletions cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ pub fn create_main_worker(
// above
ops::errors::init(js_runtime);
ops::runtime_compiler::init(js_runtime);
ops::test_runner::init(js_runtime);
caspervonb marked this conversation as resolved.
Show resolved Hide resolved
}
worker.bootstrap(&options);

Expand Down
1 change: 1 addition & 0 deletions cli/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

pub mod errors;
pub mod runtime_compiler;
pub mod test_runner;

use deno_core::error::AnyError;
use deno_core::op_async;
Expand Down
234 changes: 234 additions & 0 deletions cli/ops/test_runner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
use deno_core::url::Url;
use deno_core::OpState;
use deno_core::ZeroCopyBuf;
use deno_runtime::permissions::PermissionState;
use deno_runtime::permissions::Permissions;
use deno_runtime::permissions::PermissionsOptions;
use serde::Deserialize;
use std::path::PathBuf;

pub fn init(rt: &mut deno_core::JsRuntime) {
super::reg_sync(
rt,
"op_request_test_permissions",
caspervonb marked this conversation as resolved.
Show resolved Hide resolved
op_request_test_permissions,
);
super::reg_sync(
rt,
"op_restore_test_permissions",
op_restore_test_permissions,
);
}

fn test_permission_error(name: &str, info: Option<&str>) -> AnyError {
custom_error(
"PermissionDenied",
format!(
"Requires {}, run test again with the --allow-{} flag",
format!(
"{} access{}",
name,
info.map_or(String::new(), |info| { format!(" to {}", info) }),
),
name
),
)
}

struct RestoreTestPermissions(Permissions);

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RequestTestPermissionsArgs {
read: Option<Vec<String>>,
write: Option<Vec<String>>,
net: Option<Vec<String>>,
env: Option<Vec<String>>,
run: Option<Vec<String>>,
hrtime: Option<bool>,
plugin: Option<bool>,
}

pub fn op_request_test_permissions(
state: &mut OpState,
args: Value,
_zero_copy: Option<ZeroCopyBuf>,
) -> Result<Value, AnyError> {
lucacasonato marked this conversation as resolved.
Show resolved Hide resolved
let args: RequestTestPermissionsArgs = serde_json::from_value(args)?;

let mut permissions = state.borrow::<Permissions>().clone();
state
.put::<RestoreTestPermissions>(RestoreTestPermissions(permissions.clone()));

let allow_read = if let Some(paths) = args.read {
let mut allow_read = Vec::new();

if paths.is_empty() {
if permissions.read.request(None) != PermissionState::Granted {
return Err(test_permission_error("read", None));
}
} else {
for path in paths {
let path = PathBuf::from(path);
if permissions.read.request(Some(&path)) != PermissionState::Granted {
return Err(test_permission_error("read", None));
}

allow_read.push(path)
}
}

Some(allow_read)
} else {
None
};

let allow_write = if let Some(paths) = args.write {
let mut allow_write = Vec::new();

if paths.is_empty() {
if permissions.write.request(None) != PermissionState::Granted {
return Err(test_permission_error("write", None));
}
} else {
for path in paths {
let path = PathBuf::from(path);
if permissions.write.request(Some(&path)) != PermissionState::Granted {
return Err(test_permission_error("write", None));
}

allow_write.push(path)
}
}

Some(allow_write)
} else {
None
};

let allow_net = if let Some(hosts) = args.net {
let mut allow_net = Vec::new();

if hosts.is_empty() {
if permissions.net.request::<String>(None) != PermissionState::Granted {
return Err(test_permission_error("net", None));
}
} else {
for host in hosts {
let url = Url::parse(&format!("http://{}", host))?;
let hostname = url.host_str().unwrap().to_string();
let port = url.port();

if permissions.net.request(Some(&(&hostname, port)))
!= PermissionState::Granted
{
return Err(test_permission_error("net", None));
}

allow_net.push(host);
}
}

Some(allow_net)
} else {
None
};

let allow_env = if let Some(names) = args.env {
let mut allow_env = Vec::new();

if names.is_empty() {
if permissions.env.request(None) != PermissionState::Granted {
return Err(test_permission_error("env", None));
}
} else {
for name in names {
if permissions.env.request(Some(&name)) != PermissionState::Granted {
return Err(test_permission_error("env", None));
}

allow_env.push(name);
}
}

Some(allow_env)
} else {
None
};

let allow_run = if let Some(commands) = args.run {
let mut allow_run = Vec::new();

if commands.is_empty() {
if permissions.run.request(None) != PermissionState::Granted {
return Err(test_permission_error("run", None));
}
} else {
for command in commands {
if permissions.run.request(Some(&command)) != PermissionState::Granted {
return Err(test_permission_error("run", None));
}

allow_run.push(command);
}
}

Some(allow_run)
} else {
None
};

let allow_hrtime = if args.plugin.unwrap_or(false) {
if permissions.hrtime.request() != PermissionState::Granted {
return Err(test_permission_error("hrtime", None));
}

true
} else {
false
};

let allow_plugin = if args.plugin.unwrap_or(false) {
if permissions.plugin.request() != PermissionState::Granted {
return Err(test_permission_error("plugin", None));
}

true
} else {
false
};

let permissions = Permissions::from_options(&PermissionsOptions {
allow_read,
allow_write,
allow_net,
allow_env,
allow_run,
allow_hrtime,
allow_plugin,
prompt: false,
});

state.put::<Permissions>(permissions);

Ok(json!({}))
}

pub fn op_restore_test_permissions(
state: &mut OpState,
_args: Value,
_zero_copy: Option<ZeroCopyBuf>,
) -> Result<Value, AnyError> {
let restore_test_permissions =
state.borrow::<RestoreTestPermissions>().clone().0.clone();
state.put::<Permissions>(restore_test_permissions);

Ok(json!({}))
}
12 changes: 12 additions & 0 deletions cli/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2395,6 +2395,18 @@ mod integration {
output: "test/deno_test.out",
});

itest!(allow_all {
args: "test --allow-all test/allow_all.ts",
exit_code: 0,
output: "test/allow_all.out",
});

itest!(allow_none {
args: "test test/allow_none.ts",
exit_code: 1,
output: "test/allow_none.out",
});

itest!(fail_fast {
args: "test --fail-fast test/test_runner_test.ts",
exit_code: 1,
Expand Down
8 changes: 8 additions & 0 deletions cli/tests/test/allow_all.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[WILDCARD]
running 4 tests
test readGranted ... ok [WILDCARD]
test readRevoked ... ok [WILDCARD]
test readGrantedAgain ... ok [WILDCARD]
test readRevokedAgain ... ok [WILDCARD]

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD]
44 changes: 44 additions & 0 deletions cli/tests/test/allow_all.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { assertEquals } from "../../../test_util/std/testing/asserts.ts";

// TODO(DO NOT MERGE) test all the things
caspervonb marked this conversation as resolved.
Show resolved Hide resolved

Deno.test({
name: `readGranted`,
async fn() {
const status = await Deno.permissions.query({ name: "read" });
assertEquals(status.state, "granted");
},
});

Deno.test({
name: `readRevoked`,
permissions: {
read: false,
},
async fn() {
const status = await Deno.permissions.query({ name: "read" });
assertEquals(status.state, "prompt");
},
});

Deno.test({
name: `readGrantedAgain`,
permissions: {
read: [],
},
async fn() {
const status = await Deno.permissions.query({ name: "read" });
assertEquals(status.state, "granted");
},
});

Deno.test({
name: `readRevokedAgain`,
permissions: {
read: false,
},
async fn() {
const status = await Deno.permissions.query({ name: "read" });
assertEquals(status.state, "prompt");
},
});
18 changes: 18 additions & 0 deletions cli/tests/test/allow_none.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[WILDCARD]
running 1 tests
test permissionDenied ... FAILED [WILDCARD]

failures:

permissionDenied
PermissionDenied: Requires read access, run test again with the --allow-read flag
[WILDCARD]

failures:

permissionDenied

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD]



15 changes: 15 additions & 0 deletions cli/tests/test/allow_none.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { assertEquals } from "../../../test_util/std/testing/asserts.ts";

const status = await Deno.permissions.query({ name: "read" });
assertEquals(status.state, "prompt");

Deno.test({
name: `permissionDenied`,
permissions: {
read: true,
},
async fn() {
const status = await Deno.permissions.query({ name: "read" });
assertEquals(status.state, "granted");
},
});
Loading