Port #4335 to 2.0.0-alpha (#4340)

This commit is contained in:
Gerard Guillemas Martos 2024-07-11 21:35:30 +02:00 committed by GitHub
parent 339975b030
commit bc665120ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 209 additions and 0 deletions

View file

@ -18,6 +18,21 @@ impl<'a> Document<'a> {
if self.id.is_some() { if self.id.is_some() {
// Should we run permissions checks? // Should we run permissions checks?
if opt.check_perms(stm.into())? { if opt.check_perms(stm.into())? {
// Check that record authentication matches session
if opt.auth.is_record() {
let ns = opt.ns()?;
if opt.auth.level().ns() != Some(ns) {
return Err(Error::NsNotAllowed {
ns: ns.into(),
});
}
let db = opt.db()?;
if opt.auth.level().db() != Some(db) {
return Err(Error::DbNotAllowed {
db: db.into(),
});
}
}
// Get the table // Get the table
let tb = self.tb(ctx, opt).await?; let tb = self.tb(ctx, opt).await?;
// Get the permission clause // Get the permission clause

View file

@ -1472,6 +1472,200 @@ async fn session_reauthentication_expired() {
server.finish().unwrap(); server.finish().unwrap();
} }
#[test(tokio::test)]
async fn session_use_change_database() {
// Setup database server
let (addr, mut server) = common::start_server_with_defaults().await.unwrap();
// Connect to WebSocket
let mut socket = Socket::connect(&addr, SERVER, FORMAT).await.unwrap();
// Authenticate the connection as a root level system user
let _ = socket.send_message_signin(USER, PASS, None, None, None).await.unwrap();
// Check that we have root access
socket.send_message_query("INFO FOR ROOT").await.unwrap();
// Specify a namespace and database
socket.send_message_use(Some(NS), Some("original")).await.unwrap();
// Define a scope on the original database
socket
.send_message_query(
r#"
DEFINE USER user ON DATABASE PASSWORD "secret" ROLES VIEWER
;"#,
)
.await
.unwrap();
// Create resource that requires an authenticated record user to query
socket
.send_message_query(
r#"
DEFINE TABLE user SCHEMALESS
PERMISSIONS FOR select, create, update, delete NONE
;"#,
)
.await
.unwrap();
socket
.send_message_query(
r#"
CREATE user:1 CONTENT { name: "original", pass: crypto::argon2::generate("original") }
;"#,
)
.await
.unwrap();
// Change to a different database
socket.send_message_use(Some(NS), Some("different")).await.unwrap();
// Create the same user table with a user record with the same identifier
socket
.send_message_query(
r#"
DEFINE TABLE user SCHEMALESS
PERMISSIONS FOR select, create, update, delete NONE
;"#,
)
.await
.unwrap();
socket
.send_message_query(
r#"
CREATE user:1 CONTENT { name: "different", pass: crypto::argon2::generate("different") }
;"#,
)
.await
.unwrap();
// Sign in to original database as user
let res = socket
.send_request(
"signin",
json!(
[{
"ns": NS,
"db": "original",
"user": "user",
"pass": "secret",
}]
),
)
.await;
assert!(res.is_ok(), "result: {:?}", res);
let res = res.unwrap();
assert!(res.is_object(), "result: {:?}", res);
let res = res.as_object().unwrap();
// Verify response contains no error
assert!(res.keys().all(|k| ["id", "result"].contains(&k.as_str())), "result: {:?}", res);
// Verify that the authenticated session corresponds with the original user
let res = socket.send_message_query("SELECT VALUE name FROM user:1").await.unwrap();
assert_eq!(res[0]["result"], json!(["original"]), "result: {:?}", res);
// Swtich to the different database without signing in again
socket.send_message_use(Some(NS), Some("different")).await.unwrap();
// Verify that the authenticated session is unable to query data
let res = socket.send_message_query("SELECT VALUE name FROM user:1").await.unwrap();
// The query succeeds but the results does not contain the value with permissions
assert_eq!(res[0]["status"], "OK", "result: {:?}", res);
assert_eq!(res[0]["result"], json!([]), "result: {:?}", res);
// Test passed
server.finish().unwrap();
}
#[test(tokio::test)]
async fn session_use_change_database_scope() {
// Setup database server
let (addr, mut server) = common::start_server_with_defaults().await.unwrap();
// Connect to WebSocket
let mut socket = Socket::connect(&addr, SERVER, FORMAT).await.unwrap();
// Authenticate the connection as a root level system user
let _ = socket.send_message_signin(USER, PASS, None, None, None).await.unwrap();
// Check that we have root access
socket.send_message_query("INFO FOR ROOT").await.unwrap();
// Specify a namespace and database
socket.send_message_use(Some(NS), Some("original")).await.unwrap();
// Define a user record access method on the original database
socket
.send_message_query(
r#"
DEFINE ACCESS user ON DATABASE TYPE RECORD
SIGNIN ( SELECT * FROM user WHERE name = $name AND crypto::argon2::compare(pass, $pass) )
DURATION FOR SESSION 24h, FOR TOKEN 24h
;"#,
)
.await
.unwrap();
// Create resource that requires an authenticated record user to query
socket
.send_message_query(
r#"
DEFINE TABLE user SCHEMALESS
PERMISSIONS FOR select, create, update, delete WHERE id = $auth
;"#,
)
.await
.unwrap();
socket
.send_message_query(
r#"
CREATE user:1 CONTENT { name: "original", pass: crypto::argon2::generate("original") }
;"#,
)
.await
.unwrap();
// Change to a different database
socket.send_message_use(Some(NS), Some("different")).await.unwrap();
// Create the same user table with a user record with the same identifier
socket
.send_message_query(
r#"
DEFINE TABLE user SCHEMALESS
PERMISSIONS FOR select, create, update, delete WHERE id = $auth
;"#,
)
.await
.unwrap();
socket
.send_message_query(
r#"
CREATE user:1 CONTENT { name: "different", pass: crypto::argon2::generate("different") }
;"#,
)
.await
.unwrap();
// Sign in to original database as user
let res = socket
.send_request(
"signin",
json!(
[{
"ns": NS,
"db": "original",
"ac": "user",
"name": "original",
"pass": "original",
}]
),
)
.await;
assert!(res.is_ok(), "result: {:?}", res);
let res = res.unwrap();
assert!(res.is_object(), "result: {:?}", res);
let res = res.as_object().unwrap();
// Verify response contains no error
assert!(res.keys().all(|k| ["id", "result"].contains(&k.as_str())), "result: {:?}", res);
// Verify that the authenticated session corresponds with the original user
let res = socket.send_message_query("SELECT VALUE name FROM $auth").await.unwrap();
assert_eq!(res[0]["result"], json!(["original"]), "result: {:?}", res);
// Swtich to the different database without signing in again
socket.send_message_use(Some(NS), Some("different")).await.unwrap();
// Verify that the authenticated session is unable to query data
let res = socket.send_message_query("SELECT VALUE name FROM $auth").await.unwrap();
// The following statement would be true when the bug was present:
// assert_eq!(res[0]["result"], json!(["different"]), "result: {:?}", res);
assert_eq!(res[0]["status"], "ERR", "result: {:?}", res);
assert_eq!(
res[0]["result"], "You don't have permission to change to the different database",
"result: {:?}",
res
);
// Test passed
server.finish().unwrap();
}
#[test(tokio::test)] #[test(tokio::test)]
async fn run_functions() { async fn run_functions() {
// Setup database server // Setup database server