2023-07-15 16:26:36 +00:00
mod parse ;
use parse ::Parse ;
2023-08-11 23:22:53 +00:00
use surrealdb ::dbs ::{ Action , Notification , Session } ;
2023-07-15 16:26:36 +00:00
use surrealdb ::err ::Error ;
2023-07-29 18:47:25 +00:00
use surrealdb ::iam ::Role ;
2023-07-15 16:26:36 +00:00
use surrealdb ::kvs ::Datastore ;
2023-08-11 23:22:53 +00:00
use surrealdb ::sql ::{ Id , Thing , Value } ;
2023-07-15 16:26:36 +00:00
#[ tokio::test ]
2023-07-29 18:47:25 +00:00
async fn delete ( ) -> Result < ( ) , Error > {
2023-07-15 16:26:36 +00:00
let sql = "
2023-07-29 18:47:25 +00:00
CREATE person :test SET name = ' Tester ' ;
DELETE person :test ;
SELECT * FROM person ;
2023-07-15 16:26:36 +00:00
" ;
let dbs = Datastore ::new ( " memory " ) . await ? ;
2023-07-29 18:47:25 +00:00
let ses = Session ::owner ( ) . with_ns ( " test " ) . with_db ( " test " ) ;
2023-07-15 16:26:36 +00:00
let res = & mut dbs . execute ( sql , & ses , None ) . await ? ;
2023-07-29 18:47:25 +00:00
assert_eq! ( res . len ( ) , 3 ) ;
//
let tmp = res . remove ( 0 ) . result ? ;
let val = Value ::parse (
" [
{
id : person :test ,
name : ' Tester '
}
] " ,
) ;
assert_eq! ( tmp , val ) ;
2023-07-15 16:26:36 +00:00
//
2023-07-29 18:47:25 +00:00
let tmp = res . remove ( 0 ) . result ? ;
let val = Value ::parse ( " [] " ) ;
assert_eq! ( tmp , val ) ;
2023-07-15 16:26:36 +00:00
//
let tmp = res . remove ( 0 ) . result ? ;
let val = Value ::parse ( " [] " ) ;
assert_eq! ( tmp , val ) ;
2023-07-29 18:47:25 +00:00
//
2023-07-15 16:26:36 +00:00
Ok ( ( ) )
}
2023-07-29 18:47:25 +00:00
//
// Permissions
//
async fn common_permissions_checks ( auth_enabled : bool ) {
let tests = vec! [
// Root level
( ( ( ) . into ( ) , Role ::Owner ) , ( " NS " , " DB " ) , true , " owner at root level should be able to delete a record " ) ,
( ( ( ) . into ( ) , Role ::Editor ) , ( " NS " , " DB " ) , true , " editor at root level should be able to delete a record " ) ,
( ( ( ) . into ( ) , Role ::Viewer ) , ( " NS " , " DB " ) , false , " viewer at root level should not be able to delete a record " ) ,
// Namespace level
( ( ( " NS " , ) . into ( ) , Role ::Owner ) , ( " NS " , " DB " ) , true , " owner at namespace level should be able to delete a record on its namespace " ) ,
( ( ( " NS " , ) . into ( ) , Role ::Owner ) , ( " OTHER_NS " , " DB " ) , false , " owner at namespace level should not be able to delete a record on another namespace " ) ,
( ( ( " NS " , ) . into ( ) , Role ::Editor ) , ( " NS " , " DB " ) , true , " editor at namespace level should be able to delete a record on its namespace " ) ,
( ( ( " NS " , ) . into ( ) , Role ::Editor ) , ( " OTHER_NS " , " DB " ) , false , " editor at namespace level should not be able to delete a record on another namespace " ) ,
( ( ( " NS " , ) . into ( ) , Role ::Viewer ) , ( " NS " , " DB " ) , false , " viewer at namespace level should not be able to delete a record on its namespace " ) ,
( ( ( " NS " , ) . into ( ) , Role ::Viewer ) , ( " OTHER_NS " , " DB " ) , false , " viewer at namespace level should not be able to delete a record on another namespace " ) ,
// Database level
( ( ( " NS " , " DB " ) . into ( ) , Role ::Owner ) , ( " NS " , " DB " ) , true , " owner at database level should be able to delete a record on its database " ) ,
( ( ( " NS " , " DB " ) . into ( ) , Role ::Owner ) , ( " NS " , " OTHER_DB " ) , false , " owner at database level should not be able to delete a record on another database " ) ,
( ( ( " NS " , " DB " ) . into ( ) , Role ::Owner ) , ( " OTHER_NS " , " DB " ) , false , " owner at database level should not be able to delete a record on another namespace even if the database name matches " ) ,
( ( ( " NS " , " DB " ) . into ( ) , Role ::Editor ) , ( " NS " , " DB " ) , true , " editor at database level should be able to delete a record on its database " ) ,
( ( ( " NS " , " DB " ) . into ( ) , Role ::Editor ) , ( " NS " , " OTHER_DB " ) , false , " editor at database level should not be able to delete a record on another database " ) ,
( ( ( " NS " , " DB " ) . into ( ) , Role ::Editor ) , ( " OTHER_NS " , " DB " ) , false , " editor at database level should not be able to delete a record 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 delete a record on its database " ) ,
( ( ( " NS " , " DB " ) . into ( ) , Role ::Viewer ) , ( " NS " , " OTHER_DB " ) , false , " viewer at database level should not be able to delete a record on another database " ) ,
( ( ( " NS " , " DB " ) . into ( ) , Role ::Viewer ) , ( " OTHER_NS " , " DB " ) , false , " viewer at database level should not be able to delete a record on another namespace even if the database name matches " ) ,
] ;
let statement = " DELETE person:test " ;
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 ds = Datastore ::new ( " memory " ) . await . unwrap ( ) . with_auth_enabled ( auth_enabled ) ;
let mut resp = ds
. execute ( " CREATE person:test " , & Session ::owner ( ) . with_ns ( " NS " ) . with_db ( " DB " ) , None )
. await
. unwrap ( ) ;
let res = resp . remove ( 0 ) . output ( ) ;
assert! (
res . is_ok ( ) & & res . unwrap ( ) ! = Value ::parse ( " [] " ) ,
" unexpected error creating person record "
) ;
let mut resp = ds
. execute (
" CREATE person:test " ,
& 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 ::parse ( " [] " ) ,
" unexpected error creating person record "
) ;
let mut resp = ds
. execute (
" CREATE person:test " ,
& 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 ::parse ( " [] " ) ,
" unexpected error creating person record "
) ;
// Run the test
let mut resp = ds . execute ( statement , & sess , None ) . await . unwrap ( ) ;
let res = resp . remove ( 0 ) . output ( ) ;
assert! ( res . is_ok ( ) , " delete should not fail " ) ;
if should_succeed {
// Verify the record has been deleted
let mut resp = ds
. execute (
" SELECT * FROM person:test " ,
& Session ::owner ( ) . with_ns ( " NS " ) . with_db ( " DB " ) ,
None ,
)
. await
. unwrap ( ) ;
let res = resp . remove ( 0 ) . output ( ) ;
assert! ( res . is_ok ( ) & & res . unwrap ( ) = = Value ::parse ( " [] " ) , " {} " , msg ) ;
} else {
// Verify the record has not been deleted in any DB
let mut resp = ds
. execute (
" SELECT * FROM person:test " ,
& Session ::owner ( ) . with_ns ( " NS " ) . with_db ( " DB " ) ,
None ,
)
. await
. unwrap ( ) ;
let res = resp . remove ( 0 ) . output ( ) ;
assert! ( res . is_ok ( ) & & res . unwrap ( ) ! = Value ::parse ( " [] " ) , " {} " , msg ) ;
let mut resp = ds
. execute (
" SELECT * FROM person:test " ,
& 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 ::parse ( " [] " ) , " {} " , msg ) ;
let mut resp = ds
. execute (
" SELECT * FROM person:test " ,
& 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 ::parse ( " [] " ) , " {} " , msg ) ;
}
}
}
}
#[ tokio::test ]
async fn check_permissions_auth_enabled ( ) {
let auth_enabled = true ;
//
// Test common scenarios
//
common_permissions_checks ( auth_enabled ) . await ;
//
// Test Anonymous user
//
let statement = " DELETE person:test " ;
// When the table exists but grants no permissions
{
let ds = Datastore ::new ( " memory " ) . await . unwrap ( ) . with_auth_enabled ( auth_enabled ) ;
let mut resp = ds
. execute (
" DEFINE TABLE person PERMISSIONS NONE; CREATE person:test " ,
& 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 res = resp . remove ( 0 ) . output ( ) ;
assert! ( res . is_ok ( ) & & res . unwrap ( ) ! = Value ::parse ( " [] " ) , " {} " , " failed to create record " ) ;
let mut resp = ds
. execute ( statement , & Session ::default ( ) . with_ns ( " NS " ) . with_db ( " DB " ) , None )
. await
. unwrap ( ) ;
let res = resp . remove ( 0 ) . output ( ) ;
assert! ( res . is_ok ( ) , " delete should succeed even if it doesn't really delete anything " ) ;
// Verify the record has not been deleted
let mut resp = ds
. execute (
" SELECT * FROM person:test " ,
& Session ::owner ( ) . with_ns ( " NS " ) . with_db ( " DB " ) ,
None ,
)
. await
. unwrap ( ) ;
let res = resp . remove ( 0 ) . output ( ) ;
assert! (
res . is_ok ( ) & & res . unwrap ( ) ! = Value ::parse ( " [] " ) ,
" {} " ,
" anonymous user should not be able to delete a record if the table has no permissions "
) ;
}
// When the table exists and grants full permissions
{
let ds = Datastore ::new ( " memory " ) . await . unwrap ( ) . with_auth_enabled ( auth_enabled ) ;
let mut resp = ds
. execute (
" DEFINE TABLE person PERMISSIONS FULL; CREATE person:test " ,
& 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 res = resp . remove ( 0 ) . output ( ) ;
assert! ( res . is_ok ( ) & & res . unwrap ( ) ! = Value ::parse ( " [] " ) , " {} " , " failed to create record " ) ;
let mut resp = ds
. execute ( statement , & Session ::default ( ) . with_ns ( " NS " ) . with_db ( " DB " ) , None )
. await
. unwrap ( ) ;
let res = resp . remove ( 0 ) . output ( ) ;
assert! ( res . is_ok ( ) , " delete should succeed even if it doesn't really delete anything " ) ;
// Verify the record has been deleted
let mut resp = ds
. execute (
" SELECT * FROM person:test " ,
& Session ::owner ( ) . with_ns ( " NS " ) . with_db ( " DB " ) ,
None ,
)
. await
. unwrap ( ) ;
let res = resp . remove ( 0 ) . output ( ) ;
assert! (
res . is_ok ( ) & & res . unwrap ( ) = = Value ::parse ( " [] " ) ,
" {} " ,
" anonymous user should be able to delete a record if the table has full permissions "
) ;
}
}
#[ tokio::test ]
async fn check_permissions_auth_disabled ( ) {
let auth_enabled = false ;
//
// Test common scenarios
//
common_permissions_checks ( auth_enabled ) . await ;
//
// Test Anonymous user
//
let statement = " DELETE person:test " ;
// When the table exists but grants no permissions
{
let ds = Datastore ::new ( " memory " ) . await . unwrap ( ) . with_auth_enabled ( auth_enabled ) ;
let mut resp = ds
. execute (
" DEFINE TABLE person PERMISSIONS NONE; CREATE person:test; " ,
& 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 res = resp . remove ( 0 ) . output ( ) ;
assert! ( res . is_ok ( ) & & res . unwrap ( ) ! = Value ::parse ( " [] " ) , " {} " , " failed to create record " ) ;
let mut resp = ds
. execute ( statement , & Session ::default ( ) . with_ns ( " NS " ) . with_db ( " DB " ) , None )
. await
. unwrap ( ) ;
let res = resp . remove ( 0 ) . output ( ) ;
assert! ( res . is_ok ( ) , " delete should succeed even if it doesn't really delete anything " ) ;
// Verify the record has been deleted
let mut resp = ds
. execute (
" SELECT * FROM person:test " ,
& Session ::owner ( ) . with_ns ( " NS " ) . with_db ( " DB " ) ,
None ,
)
. await
. unwrap ( ) ;
let res = resp . remove ( 0 ) . output ( ) ;
assert! (
res . is_ok ( ) & & res . unwrap ( ) = = Value ::parse ( " [] " ) ,
" {} " ,
" anonymous user should be able to delete a record if the table has no permissions "
) ;
}
{
let ds = Datastore ::new ( " memory " ) . await . unwrap ( ) . with_auth_enabled ( auth_enabled ) ;
// When the table exists and grants full permissions
let mut resp = ds
. execute (
" DEFINE TABLE person PERMISSIONS FULL; CREATE person:test; " ,
& 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 res = resp . remove ( 0 ) . output ( ) ;
assert! ( res . is_ok ( ) & & res . unwrap ( ) ! = Value ::parse ( " [] " ) , " {} " , " failed to create record " ) ;
let mut resp = ds
. execute ( statement , & Session ::default ( ) . with_ns ( " NS " ) . with_db ( " DB " ) , None )
. await
. unwrap ( ) ;
let res = resp . remove ( 0 ) . output ( ) ;
assert! ( res . is_ok ( ) , " delete should succeed even if it doesn't really delete anything " ) ;
// Verify the record has been deleted
let mut resp = ds
. execute (
" SELECT * FROM person:test " ,
& Session ::owner ( ) . with_ns ( " NS " ) . with_db ( " DB " ) ,
None ,
)
. await
. unwrap ( ) ;
let res = resp . remove ( 0 ) . output ( ) ;
assert! (
res . is_ok ( ) & & res . unwrap ( ) = = Value ::parse ( " [] " ) ,
" {} " ,
" anonymous user should be able to delete a record if the table has full permissions "
) ;
}
}
2023-08-11 23:22:53 +00:00
#[ tokio::test ]
async fn delete_filtered_live_notification ( ) -> Result < ( ) , Error > {
let sql = "
CREATE person :test SET value = 50 ;
LIVE SELECT * FROM person WHERE value < 100 ;
DELETE person :test ;
" ;
let dbs = Datastore ::new ( " memory " ) . await ? . with_notifications ( ) ;
let ses = Session ::owner ( ) . with_ns ( " test " ) . with_db ( " test " ) . with_rt ( true ) ;
let res = & mut dbs . execute ( sql , & ses , None ) . await ? ;
assert_eq! ( res . len ( ) , 3 ) ;
//
let tmp = res . remove ( 0 ) . result ? ;
let expected_record = Value ::parse (
" [
{
id : person :test ,
value : 50
}
] " ,
) ;
assert_eq! ( tmp , expected_record ) ;
//
let live_id = res . remove ( 0 ) . result ? ;
let live_id = match live_id {
Value ::Uuid ( id ) = > id ,
_ = > panic! ( " expected uuid " ) ,
} ;
//
let tmp = res . remove ( 0 ) . result ? ;
let val = Value ::parse ( " [] " ) ;
assert_eq! ( tmp , val ) ;
//
let notifications = dbs . notifications ( ) ;
let notifications = match notifications {
Some ( notifications ) = > notifications ,
None = > panic! ( " expected notifications " ) ,
} ;
let not = notifications . recv_blocking ( ) . unwrap ( ) ;
assert_eq! (
not ,
Notification {
id : live_id ,
action : Action ::Delete ,
result : Value ::Thing ( Thing {
tb : " person " . to_string ( ) ,
id : Id ::String ( " test " . to_string ( ) ) ,
} ) ,
}
) ;
Ok ( ( ) )
}