Ensure permissions are enforced on edge in and out fields (#2465)

This commit is contained in:
Tobie Morgan Hitchcock 2023-08-19 23:19:16 +01:00 committed by GitHub
parent 136d8f8eee
commit e9ef8855cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 186 additions and 36 deletions

View file

@ -35,12 +35,19 @@ impl Session {
self.ns = Some(ns.to_owned());
self
}
/// Set the selected database for the session
pub fn with_db(mut self, db: &str) -> Session {
self.db = Some(db.to_owned());
self
}
/// Set the selected database for the session
pub fn with_sc(mut self, sc: &str) -> Session {
self.sc = Some(sc.to_owned());
self
}
// Set the realtime functionality of the session
pub fn with_rt(mut self, rt: bool) -> Session {
self.rt = rt;
@ -51,14 +58,17 @@ impl Session {
pub(crate) fn ns(&self) -> Option<Arc<str>> {
self.ns.as_deref().map(Into::into)
}
/// Retrieves the selected database
pub(crate) fn db(&self) -> Option<Arc<str>> {
self.db.as_deref().map(Into::into)
}
/// Checks if live queries are allowed
pub(crate) fn live(&self) -> bool {
self.rt
}
/// Convert a session into a runtime
pub(crate) fn context<'a>(&self, mut ctx: Context<'a>) -> Context<'a> {
// Add scope auth data
@ -88,8 +98,9 @@ impl Session {
/// Create a system session for a given level and role
pub fn for_level(level: Level, role: Role) -> Session {
// Create a new session
let mut sess = Session::default();
// Set the session details
match level {
Level::Root => {
sess.au = Arc::new(Auth::for_root(role));
@ -108,6 +119,22 @@ impl Session {
sess
}
/// Create a scoped session for a given NS and DB
pub fn for_scope(ns: &str, db: &str, sc: &str, rid: Value) -> Session {
Session {
au: Arc::new(Auth::for_sc(rid.to_string(), ns, db, sc)),
rt: false,
ip: None,
or: None,
id: None,
ns: Some(ns.to_owned()),
db: Some(db.to_owned()),
sc: Some(sc.to_owned()),
tk: None,
sd: Some(rid),
}
}
/// Create a system session for the root level with Owner role
pub fn owner() -> Session {
Session::for_level(Level::Root, Role::Owner)

View file

@ -13,31 +13,68 @@ impl<'a> Document<'a> {
txn: &Transaction,
stm: &Statement<'_>,
) -> Result<Value, Error> {
// Check if allowed
self.allow(ctx, opt, txn, stm).await?;
// Alter record data
self.alter(ctx, opt, txn, stm).await?;
// Merge fields data
self.field(ctx, opt, txn, stm).await?;
// Reset fields data
self.reset(ctx, opt, txn, stm).await?;
// Clean fields data
self.clean(ctx, opt, txn, stm).await?;
// Check if allowed
self.allow(ctx, opt, txn, stm).await?;
// Store record edges
self.edges(ctx, opt, txn, stm).await?;
// Store index data
self.index(ctx, opt, txn, stm).await?;
// Store record data
self.store(ctx, opt, txn, stm).await?;
// Run table queries
self.table(ctx, opt, txn, stm).await?;
// Run lives queries
self.lives(ctx, opt, txn, stm).await?;
// Run event queries
self.event(ctx, opt, txn, stm).await?;
// Yield document
self.pluck(ctx, opt, txn, stm).await
// Check current record
match self.current.doc.is_some() {
// Create new edge
false => {
// Store record edges
self.edges(ctx, opt, txn, stm).await?;
// Alter record data
self.alter(ctx, opt, txn, stm).await?;
// Merge fields data
self.field(ctx, opt, txn, stm).await?;
// Reset fields data
self.reset(ctx, opt, txn, stm).await?;
// Clean fields data
self.clean(ctx, opt, txn, stm).await?;
// Check if allowed
self.allow(ctx, opt, txn, stm).await?;
// Store index data
self.index(ctx, opt, txn, stm).await?;
// Store record data
self.store(ctx, opt, txn, stm).await?;
// Run table queries
self.table(ctx, opt, txn, stm).await?;
// Run lives queries
self.lives(ctx, opt, txn, stm).await?;
// Run change feeds queries
self.changefeeds(ctx, opt, txn, stm).await?;
// Run event queries
self.event(ctx, opt, txn, stm).await?;
// Yield document
self.pluck(ctx, opt, txn, stm).await
}
// Update old edge
true => {
// Check if allowed
self.allow(ctx, opt, txn, stm).await?;
// Store record edges
self.edges(ctx, opt, txn, stm).await?;
// Alter record data
self.alter(ctx, opt, txn, stm).await?;
// Merge fields data
self.field(ctx, opt, txn, stm).await?;
// Reset fields data
self.reset(ctx, opt, txn, stm).await?;
// Clean fields data
self.clean(ctx, opt, txn, stm).await?;
// Check if allowed
self.allow(ctx, opt, txn, stm).await?;
// Store index data
self.index(ctx, opt, txn, stm).await?;
// Store record data
self.store(ctx, opt, txn, stm).await?;
// Run table queries
self.table(ctx, opt, txn, stm).await?;
// Run lives queries
self.lives(ctx, opt, txn, stm).await?;
// Run change feeds queries
self.changefeeds(ctx, opt, txn, stm).await?;
// Run event queries
self.event(ctx, opt, txn, stm).await?;
// Yield document
self.pluck(ctx, opt, txn, stm).await
}
}
}
}

View file

@ -66,6 +66,10 @@ impl Auth {
Self::new(Actor::new("system_auth".into(), vec![role], (ns, db).into()))
}
pub fn for_sc(rid: String, ns: &str, db: &str, sc: &str) -> Self {
Self::new(Actor::new(rid, vec![], (ns, db, sc).into()))
}
//
// Permission checks
//

View file

@ -123,20 +123,20 @@ impl From<()> for Level {
}
impl From<(&str,)> for Level {
fn from(val: (&str,)) -> Self {
Level::Namespace(val.0.to_owned())
fn from((ns,): (&str,)) -> Self {
Level::Namespace(ns.to_owned())
}
}
impl From<(&str, &str)> for Level {
fn from(val: (&str, &str)) -> Self {
Level::Database(val.0.to_owned(), val.1.to_owned())
fn from((ns, db): (&str, &str)) -> Self {
Level::Database(ns.to_owned(), db.to_owned())
}
}
impl From<(&str, &str, &str)> for Level {
fn from(val: (&str, &str, &str)) -> Self {
Level::Scope(val.0.to_owned(), val.1.to_owned(), val.2.to_owned())
fn from((ns, db, sc): (&str, &str, &str)) -> Self {
Level::Scope(ns.to_owned(), db.to_owned(), sc.to_owned())
}
}

View file

@ -1,3 +1,5 @@
use super::verify::verify_creds;
use super::{Actor, Level};
use crate::cnf::SERVER_NAME;
use crate::dbs::Session;
use crate::err::Error;
@ -10,9 +12,6 @@ use chrono::{Duration, Utc};
use jsonwebtoken::{encode, EncodingKey};
use std::sync::Arc;
use super::verify::verify_creds;
use super::{Actor, Level};
pub async fn signin(
kvs: &Datastore,
session: &mut Session,

View file

@ -3,6 +3,7 @@ use parse::Parse;
use surrealdb::dbs::Session;
use surrealdb::err::Error;
use surrealdb::kvs::Datastore;
use surrealdb::sql::Thing;
use surrealdb::sql::Value;
#[tokio::test]
@ -507,3 +508,85 @@ async fn field_definition_value_reference_with_future() -> Result<(), Error> {
//
Ok(())
}
#[tokio::test]
async fn field_definition_edge_permissions() -> Result<(), Error> {
let sql = "
DEFINE TABLE user SCHEMAFULL;
DEFINE TABLE business SCHEMAFULL;
DEFINE FIELD owner ON TABLE business TYPE record<user>;
DEFINE TABLE contact SCHEMAFULL PERMISSIONS FOR create WHERE in.owner.id = $auth.id;
INSERT INTO user (id, name) VALUES (user:one, 'John'), (user:two, 'Lucy');
INSERT INTO business (id, owner) VALUES (business:one, user:one), (business:two, user:two);
";
let dbs = Datastore::new("memory").await?.with_auth_enabled(true);
let ses = Session::owner().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 6);
//
let tmp = res.remove(0).result;
assert!(tmp.is_ok());
//
let tmp = res.remove(0).result;
assert!(tmp.is_ok());
//
let tmp = res.remove(0).result;
assert!(tmp.is_ok());
//
let tmp = res.remove(0).result;
assert!(tmp.is_ok());
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: user:one,
},
{
id: user:two,
},
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: business:one,
owner: user:one,
},
{
id: business:two,
owner: user:two,
},
]",
);
assert_eq!(tmp, val);
//
let sql = "
RELATE business:one->contact:one->business:two;
RELATE business:two->contact:two->business:one;
";
let ses = Session::for_scope("test", "test", "test", Thing::from(("user", "one")).into());
let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 2);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: contact:one,
in: business:one,
out: business:two,
},
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("[]");
assert_eq!(tmp, val);
//
Ok(())
}