#![recursion_limit = "256"]
#![allow(clippy::unwrap_used)]
use surrealdb_core::iam::Level;
use surrealdb_core::syn;
use surrealdb_types::{Array, RecordId, Value};
mod helpers;
use anyhow::Result;
use helpers::new_ds;
use surrealdb_core::dbs::Session;
use surrealdb_core::iam::Role;
use crate::helpers::skip_ok;
#[tokio::test]
async fn create_or_insert_with_permissions() -> Result<()> {
let sql = "
DEFINE TABLE user SCHEMAFULL PERMISSIONS FULL;
CREATE user:test;
DEFINE TABLE demo SCHEMAFULL PERMISSIONS FOR select, create WHERE user = $auth.id;
DEFINE FIELD user ON TABLE demo VALUE $auth.id;
DEFINE TABLE OVERWRITE foo SCHEMAFULL PERMISSIONS FOR select,create WHERE TRUE;
DEFINE FUNCTION OVERWRITE fn::client::foo() { RETURN CREATE ONLY foo:bar CONTENT {};};
";
let (_, dbs) = new_ds("test", "test", true).await?;
let ses = Session::owner().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 6);
skip_ok(res, 4)?;
let sql = "
CREATE demo SET id = demo:one;
INSERT INTO demo (id) VALUES (demo:two);
fn::client::foo();
";
let ses = Session::for_record(
"test",
"test",
"test",
Value::RecordId(RecordId::new("user", "test".to_string())),
);
let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 3);
let tmp = res.remove(0).result?;
let val = syn::value(
"[
{
id: demo:one,
user: user:test,
},
]",
)
.unwrap();
assert_eq!(tmp, val);
let tmp = res.remove(0).result?;
let val = syn::value(
"[
{
id: demo:two,
user: user:test,
},
]",
)
.unwrap();
assert_eq!(tmp, val);
let tmp = res.remove(0).result?;
let val = syn::value("{ id: foo:bar}").unwrap();
assert_eq!(tmp, val);
Ok(())
}
async fn common_permissions_checks(auth_enabled: bool) {
let tests = vec![
(
(Level::Root, Role::Owner),
("NS", "DB"),
true,
"owner at root level should be able to create a new record",
),
(
(Level::Root, Role::Editor),
("NS", "DB"),
true,
"editor at root level should be able to create a new record",
),
(
(Level::Root, Role::Viewer),
("NS", "DB"),
false,
"viewer at root level should not be able to create a new record",
),
(
(Level::Namespace("NS".to_string()), Role::Owner),
("NS", "DB"),
true,
"owner at namespace level should be able to create a new record on its namespace",
),
(
(Level::Namespace("NS".to_string()), Role::Owner),
("OTHER_NS", "DB"),
false,
"owner at namespace level should not be able to create a new record on another namespace",
),
(
(Level::Namespace("NS".to_string()), Role::Editor),
("NS", "DB"),
true,
"editor at namespace level should be able to create a new record on its namespace",
),
(
(Level::Namespace("NS".to_string()), Role::Editor),
("OTHER_NS", "DB"),
false,
"editor at namespace level should not be able to create a new record on another namespace",
),
(
(Level::Namespace("NS".to_string()), Role::Viewer),
("NS", "DB"),
false,
"viewer at namespace level should not be able to create a new record on its namespace",
),
(
(Level::Namespace("NS".to_string()), Role::Viewer),
("OTHER_NS", "DB"),
false,
"viewer at namespace level should not be able to create a new record on another namespace",
),
(
(Level::Database("NS".to_string(), "DB".to_string()), Role::Owner),
("NS", "DB"),
true,
"owner at database level should be able to create a new record on its database",
),
(
(Level::Database("NS".to_string(), "DB".to_string()), Role::Owner),
("NS", "OTHER_DB"),
false,
"owner at database level should not be able to create a new record on another database",
),
(
(Level::Database("NS".to_string(), "DB".to_string()), Role::Owner),
("OTHER_NS", "DB"),
false,
"owner at database level should not be able to create a new record on another namespace even if the database name matches",
),
(
(Level::Database("NS".to_string(), "DB".to_string()), Role::Editor),
("NS", "DB"),
true,
"editor at database level should be able to create a new record on its database",
),
(
(Level::Database("NS".to_string(), "DB".to_string()), Role::Editor),
("NS", "OTHER_DB"),
false,
"editor at database level should not be able to create a new record on another database",
),
(
(Level::Database("NS".to_string(), "DB".to_string()), Role::Editor),
("OTHER_NS", "DB"),
false,
"editor at database level should not be able to create a new record on another namespace even if the database name matches",
),
(
(Level::Database("NS".to_string(), "DB".to_string()), Role::Viewer),
("NS", "DB"),
false,
"viewer at database level should not be able to create a new record on its database",
),
(
(Level::Database("NS".to_string(), "DB".to_string()), Role::Viewer),
("NS", "OTHER_DB"),
false,
"viewer at database level should not be able to create a new record on another database",
),
(
(Level::Database("NS".to_string(), "DB".to_string()), Role::Viewer),
("OTHER_NS", "DB"),
false,
"viewer at database level should not be able to create a new record on another namespace even if the database name matches",
),
];
let statement = "CREATE person";
for ((level, role), (ns, db), should_succeed, msg) in tests {
let sess = Session::for_level(level, role).with_ns(ns).with_db(db);
{
let (_, ds) = new_ds("NS", "DB", auth_enabled).await.unwrap();
ds.execute(&format!("USE NS {ns} DB {db}"), &sess, None).await.unwrap();
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::Array(Array::new()), "{}", msg);
} else if res.is_ok() {
assert_eq!(res.unwrap(), Value::Array(Array::new()), "{}", msg);
} else {
let err = res.unwrap_err();
assert!(
err.is_not_allowed() || err.is_not_found(),
"{msg}: expected NotAllowed or NotFound, got {err}"
)
}
}
{
let (_, ds) = new_ds("NS", "DB", auth_enabled).await.unwrap();
ds.execute(
"DEFINE NS OTHER_NS; USE NS OTHER_NS; DEFINE DB DB; USE NS NS; DEFINE DB OTHER_DB;",
&Session::owner().with_ns("NS").with_db("DB"),
None,
)
.await
.unwrap();
ds.execute(&format!("USE NS {ns} DB {db}"), &sess, None).await.unwrap();
let mut resp = ds
.execute("CREATE person", &Session::owner().with_ns("NS").with_db("DB"), None)
.await
.unwrap();
let res = resp.remove(0).output();
assert!(
res.is_ok() && res.unwrap() != Value::Array(Array::new()),
"unexpected error creating person record"
);
let mut resp = ds
.execute("CREATE person", &Session::owner().with_ns("OTHER_NS").with_db("DB"), None)
.await
.unwrap();
let res = resp.remove(0).output();
assert!(
res.is_ok() && res.unwrap() != Value::Array(Array::new()),
"unexpected error creating person record"
);
let mut resp = ds
.execute("CREATE person", &Session::owner().with_ns("NS").with_db("OTHER_DB"), None)
.await
.unwrap();
let res = resp.remove(0).output();
assert!(
res.is_ok() && res.unwrap() != Value::Array(Array::new()),
"unexpected error creating person record"
);
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::Array(Array::new()), "{}", msg);
} else if res.is_ok() {
assert_eq!(res.unwrap(), Value::Array(Array::new()), "{}", msg);
} else {
let err = res.unwrap_err();
assert!(err.is_not_allowed(), "{msg}: expected NotAllowed, got {err}")
}
}
}
}
#[tokio::test]
async fn check_permissions_auth_enabled() {
let auth_enabled = true;
common_permissions_checks(auth_enabled).await;
{
let (_, ds) = new_ds("NS", "DB", auth_enabled).await.unwrap();
let mut resp = ds
.execute("CREATE person", &Session::default().with_ns("NS").with_db("DB"), None)
.await
.unwrap();
let res = resp.remove(0).output();
assert_eq!(
res.unwrap(),
Value::Array(Array::new()),
"anonymous user should get empty result when creating table with auth enabled"
);
}
{
let (_, ds) = new_ds("NS", "DB", auth_enabled).await.unwrap();
let mut resp = ds
.execute(
"DEFINE TABLE person PERMISSIONS NONE",
&Session::owner().with_ns("NS").with_db("DB"),
None,
)
.await
.unwrap();
let res = resp.remove(0).output();
assert!(res.is_ok(), "failed to create table: {:?}", res);
let mut resp = ds
.execute("CREATE person", &Session::default().with_ns("NS").with_db("DB"), None)
.await
.unwrap();
let res = resp.remove(0).output();
assert!(
res.unwrap() == Value::Array(Array::new()),
"{}",
"anonymous user should not be able to create a new record if the table exists but has no permissions"
);
}
{
let (_, ds) = new_ds("NS", "DB", auth_enabled).await.unwrap();
let mut resp = ds
.execute(
"DEFINE TABLE person PERMISSIONS FULL",
&Session::owner().with_ns("NS").with_db("DB"),
None,
)
.await
.unwrap();
let res = resp.remove(0).output();
assert!(res.is_ok(), "failed to create table: {:?}", res);
let mut resp = ds
.execute("CREATE person", &Session::default().with_ns("NS").with_db("DB"), None)
.await
.unwrap();
let res = resp.remove(0).output();
assert!(
res.unwrap() != Value::Array(Array::new()),
"{}",
"anonymous user should be able to create a new record if the table exists and grants full permissions"
);
}
}
#[tokio::test]
async fn check_permissions_auth_disabled() {
let auth_enabled = false;
common_permissions_checks(auth_enabled).await;
{
let (_, ds) = new_ds("NS", "DB", auth_enabled).await.unwrap();
let mut resp = ds
.execute("CREATE person", &Session::default().with_ns("NS").with_db("DB"), None)
.await
.unwrap();
let res = resp.remove(0).output();
assert!(
res.unwrap() != Value::Array(Array::new()),
"{}",
"anonymous user should be able to create the table"
);
}
{
let (_, ds) = new_ds("NS", "DB", auth_enabled).await.unwrap();
let mut resp = ds
.execute(
"DEFINE TABLE person PERMISSIONS NONE",
&Session::owner().with_ns("NS").with_db("DB"),
None,
)
.await
.unwrap();
let res = resp.remove(0).output();
assert!(res.is_ok(), "failed to create table: {:?}", res);
let mut resp = ds
.execute("CREATE person", &Session::default().with_ns("NS").with_db("DB"), None)
.await
.unwrap();
let res = resp.remove(0).output();
assert!(
res.unwrap() != Value::Array(Array::new()),
"{}",
"anonymous user should not be able to create a new record if the table exists but has no permissions"
);
}
{
let (_, ds) = new_ds("NS", "DB", auth_enabled).await.unwrap();
let mut resp = ds
.execute(
"DEFINE TABLE person PERMISSIONS FULL",
&Session::owner().with_ns("NS").with_db("DB"),
None,
)
.await
.unwrap();
let res = resp.remove(0).output();
assert!(res.is_ok(), "failed to create table: {:?}", res);
let mut resp = ds
.execute("CREATE person", &Session::default().with_ns("NS").with_db("DB"), None)
.await
.unwrap();
let res = resp.remove(0).output();
assert!(
res.unwrap() != Value::Array(Array::new()),
"{}",
"anonymous user should be able to create a new record if the table exists and grants full permissions"
);
}
}