surrealpatch/lib/tests/access.rs
Gerard Guillemas Martos c3d788ff4a
Add BEARER access type and its basic grant management (#4302)
Co-authored-by: Emmanuel Keller <emmanuel.keller@surrealdb.com>
Co-authored-by: Micha de Vries <micha@devrie.sh>
2024-08-13 16:38:17 +00:00

629 lines
30 KiB
Rust

mod parse;
use parse::Parse;
mod helpers;
use helpers::new_ds;
use regex::Regex;
use surrealdb::dbs::Session;
use surrealdb::iam::Role;
use surrealdb::sql::Value;
#[tokio::test]
async fn access_bearer_database() -> () {
// TODO(gguillemas): Remove this once bearer access is no longer experimental.
std::env::set_var("SURREAL_EXPERIMENTAL_BEARER_ACCESS", "true");
let sql = "
-- Initial setup
DEFINE ACCESS api ON DATABASE TYPE BEARER;
DEFINE USER tobie ON DATABASE PASSWORD 'secret' ROLES EDITOR;
INFO FOR DB;
-- Should succeed
ACCESS api ON DATABASE GRANT FOR USER tobie;
ACCESS api ON DATABASE GRANT FOR USER tobie;
ACCESS api GRANT FOR USER tobie;
ACCESS api GRANT FOR USER tobie;
ACCESS api ON DATABASE LIST;
ACCESS api LIST;
-- Should fail
ACCESS invalid ON DATABASE GRANT FOR USER tobie;
ACCESS invalid GRANT FOR USER tobie;
ACCESS api ON DATABASE GRANT FOR USER invalid;
ACCESS api GRANT FOR USER invalid;
ACCESS invalid ON DATABASE LIST;
ACCESS invalid LIST;
";
let dbs = new_ds().await.unwrap();
let ses = Session::owner().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await.unwrap();
assert_eq!(res.len(), 15);
// Consume the results of the setup statements
res.remove(0).result.unwrap();
res.remove(0).result.unwrap();
// Ensure the access method was created as expected
let tmp = res.remove(0).result.unwrap().to_string();
let ok =
Regex::new(r"\{ accesses: \{ api: 'DEFINE ACCESS api ON DATABASE TYPE BEARER DURATION FOR GRANT NONE, FOR TOKEN 1h, FOR SESSION NONE' \}, .* \}")
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok =
Regex::new(r"\{ ac: 'api', creation: .*, expiration: NONE, grant: \{ .* \}, id: .*, revocation: NONE, subject: \{ user: 'tobie' \} \}")
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok =
Regex::new(r"\{ ac: 'api', creation: .*, expiration: NONE, grant: \{ .* \}, id: .*, revocation: NONE, subject: \{ user: 'tobie' \} \}")
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok =
Regex::new(r"\{ ac: 'api', creation: .*, expiration: NONE, grant: \{ .* \}, id: .*, revocation: NONE, subject: \{ user: 'tobie' \} \}")
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok =
Regex::new(r"\{ ac: 'api', creation: .*, expiration: NONE, grant: \{ .* \}, id: .*, revocation: NONE, subject: \{ user: 'tobie' \} \}")
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok = Regex::new(
r"\[\{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}\]",
)
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok = Regex::new(
r"\[\{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}\]",
)
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(
tmp.to_string(),
"The access method 'invalid' does not exist in the database 'test'"
);
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(
tmp.to_string(),
"The access method 'invalid' does not exist in the database 'test'"
);
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(tmp.to_string(), "The user 'invalid' does not exist in the database 'test'");
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(tmp.to_string(), "The user 'invalid' does not exist in the database 'test'");
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(
tmp.to_string(),
"The access method 'invalid' does not exist in the database 'test'"
);
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(
tmp.to_string(),
"The access method 'invalid' does not exist in the database 'test'"
);
}
#[tokio::test]
async fn access_bearer_namespace() {
// TODO(gguillemas): Remove this once bearer access is no longer experimental.
std::env::set_var("SURREAL_EXPERIMENTAL_BEARER_ACCESS", "true");
let sql = "
-- Initial setup
DEFINE ACCESS api ON NAMESPACE TYPE BEARER;
DEFINE USER tobie ON NAMESPACE PASSWORD 'secret' ROLES EDITOR;
INFO FOR NS;
-- Should succeed
ACCESS api ON NAMESPACE GRANT FOR USER tobie;
ACCESS api ON NAMESPACE GRANT FOR USER tobie;
ACCESS api GRANT FOR USER tobie;
ACCESS api GRANT FOR USER tobie;
ACCESS api ON NAMESPACE LIST;
ACCESS api LIST;
-- Should fail
ACCESS invalid ON NAMESPACE GRANT FOR USER tobie;
ACCESS invalid GRANT FOR USER tobie;
ACCESS api ON NAMESPACE GRANT FOR USER invalid;
ACCESS api GRANT FOR USER invalid;
ACCESS invalid ON NAMESPACE LIST;
ACCESS invalid LIST;
";
let dbs = new_ds().await.unwrap();
let ses = Session::owner().with_ns("test");
let res = &mut dbs.execute(sql, &ses, None).await.unwrap();
assert_eq!(res.len(), 15);
// Consume the results of the setup statements
res.remove(0).result.unwrap();
res.remove(0).result.unwrap();
// Ensure the access method was created as expected
let tmp = res.remove(0).result.unwrap().to_string();
let ok =
Regex::new(r"\{ accesses: \{ api: 'DEFINE ACCESS api ON NAMESPACE TYPE BEARER DURATION FOR GRANT NONE, FOR TOKEN 1h, FOR SESSION NONE' \}, .* \}")
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok =
Regex::new(r"\{ ac: 'api', creation: .*, expiration: NONE, grant: \{ .* \}, id: .*, revocation: NONE, subject: \{ user: 'tobie' \} \}")
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok =
Regex::new(r"\{ ac: 'api', creation: .*, expiration: NONE, grant: \{ .* \}, id: .*, revocation: NONE, subject: \{ user: 'tobie' \} \}")
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok =
Regex::new(r"\{ ac: 'api', creation: .*, expiration: NONE, grant: \{ .* \}, id: .*, revocation: NONE, subject: \{ user: 'tobie' \} \}")
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok =
Regex::new(r"\{ ac: 'api', creation: .*, expiration: NONE, grant: \{ .* \}, id: .*, revocation: NONE, subject: \{ user: 'tobie' \} \}")
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok = Regex::new(
r"\[\{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}\]",
)
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok = Regex::new(
r"\[\{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}\]",
)
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(
tmp.to_string(),
"The access method 'invalid' does not exist in the namespace 'test'"
);
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(
tmp.to_string(),
"The access method 'invalid' does not exist in the namespace 'test'"
);
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(tmp.to_string(), "The user 'invalid' does not exist in the namespace 'test'");
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(tmp.to_string(), "The user 'invalid' does not exist in the namespace 'test'");
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(
tmp.to_string(),
"The access method 'invalid' does not exist in the namespace 'test'"
);
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(
tmp.to_string(),
"The access method 'invalid' does not exist in the namespace 'test'"
);
}
#[tokio::test]
async fn access_bearer_root() {
// TODO(gguillemas): Remove this once bearer access is no longer experimental.
std::env::set_var("SURREAL_EXPERIMENTAL_BEARER_ACCESS", "true");
let sql = "
-- Initial setup
DEFINE ACCESS api ON ROOT TYPE BEARER;
DEFINE USER tobie ON ROOT PASSWORD 'secret' ROLES EDITOR;
INFO FOR ROOT;
-- Should succeed
ACCESS api ON ROOT GRANT FOR USER tobie;
ACCESS api ON ROOT GRANT FOR USER tobie;
ACCESS api GRANT FOR USER tobie;
ACCESS api GRANT FOR USER tobie;
ACCESS api ON ROOT LIST;
ACCESS api LIST;
-- Should fail
ACCESS invalid ON ROOT GRANT FOR USER tobie;
ACCESS invalid GRANT FOR USER tobie;
ACCESS api ON ROOT GRANT FOR USER invalid;
ACCESS api GRANT FOR USER invalid;
ACCESS invalid ON ROOT LIST;
ACCESS invalid LIST;
";
let dbs = new_ds().await.unwrap();
let ses = Session::owner();
let res = &mut dbs.execute(sql, &ses, None).await.unwrap();
assert_eq!(res.len(), 15);
// Consume the results of the setup statements
res.remove(0).result.unwrap();
res.remove(0).result.unwrap();
// Ensure the access method was created as expected
let tmp = res.remove(0).result.unwrap().to_string();
let ok =
Regex::new(r"\{ accesses: \{ api: 'DEFINE ACCESS api ON ROOT TYPE BEARER DURATION FOR GRANT NONE, FOR TOKEN 1h, FOR SESSION NONE' \}, .* \}")
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok =
Regex::new(r"\{ ac: 'api', creation: .*, expiration: NONE, grant: \{ .* \}, id: .*, revocation: NONE, subject: \{ user: 'tobie' \} \}")
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok =
Regex::new(r"\{ ac: 'api', creation: .*, expiration: NONE, grant: \{ .* \}, id: .*, revocation: NONE, subject: \{ user: 'tobie' \} \}")
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok =
Regex::new(r"\{ ac: 'api', creation: .*, expiration: NONE, grant: \{ .* \}, id: .*, revocation: NONE, subject: \{ user: 'tobie' \} \}")
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok =
Regex::new(r"\{ ac: 'api', creation: .*, expiration: NONE, grant: \{ .* \}, id: .*, revocation: NONE, subject: \{ user: 'tobie' \} \}")
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok = Regex::new(
r"\[\{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}\]",
)
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap().to_string();
let ok = Regex::new(
r"\[\{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}, \{ ac: 'api', .*, grant: \{ id: .*, key: '\[REDACTED\]' \}, .* \}\]",
)
.unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(tmp.to_string(), "The root access method 'invalid' does not exist");
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(tmp.to_string(), "The root access method 'invalid' does not exist");
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(tmp.to_string(), "The root user 'invalid' does not exist");
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(tmp.to_string(), "The root user 'invalid' does not exist");
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(tmp.to_string(), "The root access method 'invalid' does not exist");
//
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(tmp.to_string(), "The root access method 'invalid' does not exist");
}
#[tokio::test]
async fn access_bearer_revoke_db() -> () {
// TODO(gguillemas): Remove this once bearer access is no longer experimental.
std::env::set_var("SURREAL_EXPERIMENTAL_BEARER_ACCESS", "true");
let sql = "
-- Initial setup
DEFINE ACCESS api ON DATABASE TYPE BEARER;
DEFINE USER tobie ON DATABASE PASSWORD 'secret' ROLES EDITOR;
ACCESS api ON DATABASE GRANT FOR USER tobie;
";
let dbs = new_ds().await.unwrap();
let ses = Session::owner().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await.unwrap();
// Consume the results of the setup statements
res.remove(0).result.unwrap();
res.remove(0).result.unwrap();
// Retrieve the generated bearer key
let tmp = res.remove(0).result.unwrap().to_string();
let re =
Regex::new(r"\{ ac: 'api', creation: .*, expiration: NONE, grant: \{ id: '(.*)', key: .* \}, id: .*, revocation: NONE, subject: \{ user: 'tobie' \} \}")
.unwrap();
let kid = re.captures(&tmp).unwrap().get(1).unwrap().as_str();
// Revoke bearer key
let res = &mut dbs.execute(&format!("ACCESS api REVOKE `{kid}`"), &ses, None).await.unwrap();
let tmp = res.remove(0).result.unwrap().to_string();
let ok = Regex::new(r"\{ ac: 'api', .*, revocation: d'.*', .* \}").unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
// Attempt to revoke bearer key again
let res = &mut dbs.execute(&format!("ACCESS api REVOKE `{kid}`"), &ses, None).await.unwrap();
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(tmp.to_string(), "This access grant has been revoked");
}
#[tokio::test]
async fn access_bearer_revoke_ns() -> () {
// TODO(gguillemas): Remove this once bearer access is no longer experimental.
std::env::set_var("SURREAL_EXPERIMENTAL_BEARER_ACCESS", "true");
let sql = "
-- Initial setup
DEFINE ACCESS api ON NAMESPACE TYPE BEARER;
DEFINE USER tobie ON NAMESPACE PASSWORD 'secret' ROLES EDITOR;
ACCESS api ON NAMESPACE GRANT FOR USER tobie;
";
let dbs = new_ds().await.unwrap();
let ses = Session::owner().with_ns("test");
let res = &mut dbs.execute(sql, &ses, None).await.unwrap();
// Consume the results of the setup statements
res.remove(0).result.unwrap();
res.remove(0).result.unwrap();
// Retrieve the generated bearer key
let tmp = res.remove(0).result.unwrap().to_string();
let re =
Regex::new(r"\{ ac: 'api', creation: .*, expiration: NONE, grant: \{ id: '(.*)', key: .* \}, id: .*, revocation: NONE, subject: \{ user: 'tobie' \} \}")
.unwrap();
let kid = re.captures(&tmp).unwrap().get(1).unwrap().as_str();
// Revoke bearer key
let res = &mut dbs.execute(&format!("ACCESS api REVOKE `{kid}`"), &ses, None).await.unwrap();
let tmp = res.remove(0).result.unwrap().to_string();
let ok = Regex::new(r"\{ ac: 'api', .*, revocation: d'.*', .* \}").unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
// Attempt to revoke bearer key again
let res = &mut dbs.execute(&format!("ACCESS api REVOKE `{kid}`"), &ses, None).await.unwrap();
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(tmp.to_string(), "This access grant has been revoked");
}
#[tokio::test]
async fn access_bearer_revoke_root() -> () {
// TODO(gguillemas): Remove this once bearer access is no longer experimental.
std::env::set_var("SURREAL_EXPERIMENTAL_BEARER_ACCESS", "true");
let sql = "
-- Initial setup
DEFINE ACCESS api ON ROOT TYPE BEARER;
DEFINE USER tobie ON ROOT PASSWORD 'secret' ROLES EDITOR;
ACCESS api ON ROOT GRANT FOR USER tobie;
";
let dbs = new_ds().await.unwrap();
let ses = Session::owner();
let res = &mut dbs.execute(sql, &ses, None).await.unwrap();
// Consume the results of the setup statements
res.remove(0).result.unwrap();
res.remove(0).result.unwrap();
// Retrieve the generated bearer key
let tmp = res.remove(0).result.unwrap().to_string();
let re =
Regex::new(r"\{ ac: 'api', creation: .*, expiration: NONE, grant: \{ id: '(.*)', key: .* \}, id: .*, revocation: NONE, subject: \{ user: 'tobie' \} \}")
.unwrap();
let kid = re.captures(&tmp).unwrap().get(1).unwrap().as_str();
// Revoke bearer key
let res = &mut dbs.execute(&format!("ACCESS api REVOKE `{kid}`"), &ses, None).await.unwrap();
let tmp = res.remove(0).result.unwrap().to_string();
let ok = Regex::new(r"\{ ac: 'api', .*, revocation: d'.*', .* \}").unwrap();
assert!(ok.is_match(&tmp), "Output '{}' doesn't match regex '{}'", tmp, ok);
// Attempt to revoke bearer key again
let res = &mut dbs.execute(&format!("ACCESS api REVOKE `{kid}`"), &ses, None).await.unwrap();
let tmp = res.remove(0).result.unwrap_err();
assert_eq!(tmp.to_string(), "This access grant has been revoked");
}
//
// Permissions
//
#[tokio::test]
async fn permissions_access_grant_db() {
// TODO(gguillemas): Remove this once bearer access is no longer experimental.
std::env::set_var("SURREAL_EXPERIMENTAL_BEARER_ACCESS", "true");
let tests = vec![
// Root level
((().into(), Role::Owner), ("NS", "DB"), true, "owner at root level should be able to issue a grant"),
((().into(), Role::Editor), ("NS", "DB"), false, "editor at root level should not be able to issue a grant"),
((().into(), Role::Viewer), ("NS", "DB"), false, "viewer at root level should not be able to issue a grant"),
// Namespace level
((("NS",).into(), Role::Owner), ("NS", "DB"), true, "owner at namespace level should be able to issue a grant on its namespace"),
((("NS",).into(), Role::Owner), ("OTHER_NS", "DB"), false, "owner at namespace level should not be able to issue a grant on another namespace"),
((("NS",).into(), Role::Editor), ("NS", "DB"), false, "editor at namespace level should not be able to issue a grant on its namespace"),
((("NS",).into(), Role::Editor), ("OTHER_NS", "DB"), false, "editor at namespace level should not be able to issue a grant on another namespace"),
((("NS",).into(), Role::Viewer), ("NS", "DB"), false, "viewer at namespace level should not be able to issue a grant on its namespace"),
((("NS",).into(), Role::Viewer), ("OTHER_NS", "DB"), false, "viewer at namespace level should not be able to issue a grant on another namespace"),
// Database level
((("NS", "DB").into(), Role::Owner), ("NS", "DB"), true, "owner at database level should be able to issue a grant on its database"),
((("NS", "DB").into(), Role::Owner), ("NS", "OTHER_DB"), false, "owner at database level should not be able to issue a grant on another database"),
((("NS", "DB").into(), Role::Owner), ("OTHER_NS", "DB"), false, "owner at database level should not be able to issue a grant on another namespace even if the database name matches"),
((("NS", "DB").into(), Role::Editor), ("NS", "DB"), false, "editor at database level should not be able to issue a grant on its database"),
((("NS", "DB").into(), Role::Editor), ("NS", "OTHER_DB"), false, "editor at database level should not be able to issue a grant on another database"),
((("NS", "DB").into(), Role::Editor), ("OTHER_NS", "DB"), false, "editor at database level should not be able to issue a grant on another namespace even if the database name matches"),
((("NS", "DB").into(), Role::Viewer), ("NS", "DB"), false, "viewer at database level should not be able to issue a grant on its database"),
((("NS", "DB").into(), Role::Viewer), ("NS", "OTHER_DB"), false, "viewer at database level should not be able to issue a grant on another database"),
((("NS", "DB").into(), Role::Viewer), ("OTHER_NS", "DB"), false, "viewer at database level should not be able to issue a grant on another namespace even if the database name matches"),
];
let statement = "ACCESS api ON DATABASE GRANT FOR USER tobie";
for ((level, role), (ns, db), should_succeed, msg) in tests.into_iter() {
let sess = Session::for_level(level, role).with_ns(ns).with_db(db);
let sess_setup =
Session::for_level(("NS", "DB").into(), Role::Owner).with_ns("NS").with_db("DB");
let statement_setup =
"DEFINE ACCESS api ON DATABASE TYPE BEARER; DEFINE USER tobie ON DATABASE ROLES OWNER";
{
let ds = new_ds().await.unwrap().with_auth_enabled(true);
let mut resp = ds.execute(&statement_setup, &sess_setup, None).await.unwrap();
let res = resp.remove(0).output();
assert!(res.is_ok(), "Error setting up access method: {:?}", res);
let res = resp.remove(0).output();
assert!(res.is_ok(), "Error setting up user: {:?}", res);
let mut resp = ds.execute(statement, &sess, None).await.unwrap();
let res = resp.remove(0).output();
if should_succeed {
assert!(res.is_ok(), "{}: {:?}", msg, res);
assert_ne!(res.unwrap(), Value::parse("[]"), "{}", msg);
} else {
let err = res.unwrap_err().to_string();
assert!(
err.contains("Not enough permissions to perform this action"),
"{}: {}",
msg,
err
)
}
}
}
}
#[tokio::test]
async fn permissions_access_grant_ns() {
// TODO(gguillemas): Remove this once bearer access is no longer experimental.
std::env::set_var("SURREAL_EXPERIMENTAL_BEARER_ACCESS", "true");
let tests = vec![
// Root level
((().into(), Role::Owner), ("NS", "DB"), true, "owner at root level should be able to issue a grant"),
((().into(), Role::Editor), ("NS", "DB"), false, "editor at root level should not be able to issue a grant"),
((().into(), Role::Viewer), ("NS", "DB"), false, "viewer at root level should not be able to issue a grant"),
// Namespace level
((("NS",).into(), Role::Owner), ("NS", "DB"), true, "owner at namespace level should be able to issue a grant on its namespace"),
((("NS",).into(), Role::Owner), ("OTHER_NS", "DB"), false, "owner at namespace level should not be able to issue a grant on another namespace"),
((("NS",).into(), Role::Editor), ("NS", "DB"), false, "editor at namespace level should not be able to issue a grant on its namespace"),
((("NS",).into(), Role::Editor), ("OTHER_NS", "DB"), false, "editor at namespace level should not be able to issue a grant on another namespace"),
((("NS",).into(), Role::Viewer), ("NS", "DB"), false, "viewer at namespace level should not be able to issue a grant on its namespace"),
((("NS",).into(), Role::Viewer), ("OTHER_NS", "DB"), false, "viewer at namespace level should not be able to issue a grant on another namespace"),
// Database level
((("NS", "DB").into(), Role::Owner), ("NS", "DB"), false, "owner at database level should not be able to issue a grant on its database"),
((("NS", "DB").into(), Role::Owner), ("NS", "OTHER_DB"), false, "owner at database level should not be able to issue a grant on another database"),
((("NS", "DB").into(), Role::Owner), ("OTHER_NS", "DB"), false, "owner at database level should not be able to issue a grant on another namespace even if the database name matches"),
((("NS", "DB").into(), Role::Editor), ("NS", "DB"), false, "editor at database level should not be able to issue a grant on its database"),
((("NS", "DB").into(), Role::Editor), ("NS", "OTHER_DB"), false, "editor at database level should not be able to issue a grant on another database"),
((("NS", "DB").into(), Role::Editor), ("OTHER_NS", "DB"), false, "editor at database level should not be able to issue a grant on another namespace even if the database name matches"),
((("NS", "DB").into(), Role::Viewer), ("NS", "DB"), false, "viewer at database level should not be able to issue a grant on its database"),
((("NS", "DB").into(), Role::Viewer), ("NS", "OTHER_DB"), false, "viewer at database level should not be able to issue a grant on another database"),
((("NS", "DB").into(), Role::Viewer), ("OTHER_NS", "DB"), false, "viewer at database level should not be able to issue a grant on another namespace even if the database name matches"),
];
let statement = "ACCESS api ON NAMESPACE GRANT FOR USER tobie";
for ((level, role), (ns, db), should_succeed, msg) in tests.into_iter() {
let sess = Session::for_level(level, role).with_ns(ns).with_db(db);
let sess_setup =
Session::for_level(("NS",).into(), Role::Owner).with_ns("NS").with_db("DB");
let statement_setup = "DEFINE ACCESS api ON NAMESPACE TYPE BEARER; DEFINE USER tobie ON NAMESPACE ROLES OWNER";
{
let ds = new_ds().await.unwrap().with_auth_enabled(true);
let mut resp = ds.execute(&statement_setup, &sess_setup, None).await.unwrap();
let res = resp.remove(0).output();
assert!(res.is_ok(), "Error setting up access method: {:?}", res);
let res = resp.remove(0).output();
assert!(res.is_ok(), "Error setting up user: {:?}", res);
let mut resp = ds.execute(statement, &sess, None).await.unwrap();
let res = resp.remove(0).output();
if should_succeed {
assert!(res.is_ok(), "{}: {:?}", msg, res);
assert_ne!(res.unwrap(), Value::parse("[]"), "{}", msg);
} else {
let err = res.unwrap_err().to_string();
assert!(
err.contains("Not enough permissions to perform this action"),
"{}: {}",
msg,
err
)
}
}
}
}
#[tokio::test]
async fn permissions_access_grant_root() {
// TODO(gguillemas): Remove this once bearer access is no longer experimental.
std::env::set_var("SURREAL_EXPERIMENTAL_BEARER_ACCESS", "true");
let tests = vec![
// Root level
((().into(), Role::Owner), ("NS", "DB"), true, "owner at root level should be able to issue a grant"),
((().into(), Role::Editor), ("NS", "DB"), false, "editor at root level should not be able to issue a grant"),
((().into(), Role::Viewer), ("NS", "DB"), false, "viewer at root level should not be able to issue a grant"),
// Namespace level
((("NS",).into(), Role::Owner), ("NS", "DB"), false, "owner at namespace level should not be able to issue a grant on its namespace"),
((("NS",).into(), Role::Owner), ("OTHER_NS", "DB"), false, "owner at namespace level should not be able to issue a grant on another namespace"),
((("NS",).into(), Role::Editor), ("NS", "DB"), false, "editor at namespace level should not be able to issue a grant on its namespace"),
((("NS",).into(), Role::Editor), ("OTHER_NS", "DB"), false, "editor at namespace level should not be able to issue a grant on another namespace"),
((("NS",).into(), Role::Viewer), ("NS", "DB"), false, "viewer at namespace level should not be able to issue a grant on its namespace"),
((("NS",).into(), Role::Viewer), ("OTHER_NS", "DB"), false, "viewer at namespace level should not be able to issue a grant on another namespace"),
// Database level
((("NS", "DB").into(), Role::Owner), ("NS", "DB"), false, "owner at database level should not be able to issue a grant on its database"),
((("NS", "DB").into(), Role::Owner), ("NS", "OTHER_DB"), false, "owner at database level should not be able to issue a grant on another database"),
((("NS", "DB").into(), Role::Owner), ("OTHER_NS", "DB"), false, "owner at database level should not be able to issue a grant on another namespace even if the database name matches"),
((("NS", "DB").into(), Role::Editor), ("NS", "DB"), false, "editor at database level should not be able to issue a grant on its database"),
((("NS", "DB").into(), Role::Editor), ("NS", "OTHER_DB"), false, "editor at database level should not be able to issue a grant on another database"),
((("NS", "DB").into(), Role::Editor), ("OTHER_NS", "DB"), false, "editor at database level should not be able to issue a grant on another namespace even if the database name matches"),
((("NS", "DB").into(), Role::Viewer), ("NS", "DB"), false, "viewer at database level should not be able to issue a grant on its database"),
((("NS", "DB").into(), Role::Viewer), ("NS", "OTHER_DB"), false, "viewer at database level should not be able to issue a grant on another database"),
((("NS", "DB").into(), Role::Viewer), ("OTHER_NS", "DB"), false, "viewer at database level should not be able to issue a grant on another namespace even if the database name matches"),
];
let statement = "ACCESS api ON ROOT GRANT FOR USER tobie";
for ((level, role), (ns, db), should_succeed, msg) in tests.into_iter() {
let sess = Session::for_level(level, role).with_ns(ns).with_db(db);
let sess_setup = Session::for_level(().into(), Role::Owner).with_ns("NS").with_db("DB");
let statement_setup =
"DEFINE ACCESS api ON ROOT TYPE BEARER; DEFINE USER tobie ON ROOT ROLES OWNER";
{
let ds = new_ds().await.unwrap().with_auth_enabled(true);
let mut resp = ds.execute(&statement_setup, &sess_setup, None).await.unwrap();
let res = resp.remove(0).output();
assert!(res.is_ok(), "Error setting up access method: {:?}", res);
let res = resp.remove(0).output();
assert!(res.is_ok(), "Error setting up user: {:?}", res);
let mut resp = ds.execute(statement, &sess, None).await.unwrap();
let res = resp.remove(0).output();
if should_succeed {
assert!(res.is_ok(), "{}: {:?}", msg, res);
assert_ne!(res.unwrap(), Value::parse("[]"), "{}", msg);
} else {
let err = res.unwrap_err().to_string();
assert!(
err.contains("Not enough permissions to perform this action"),
"{}: {}",
msg,
err
)
}
}
}
}