Ensure permissions are enforced on edge in
and out
fields (#2465)
This commit is contained in:
parent
136d8f8eee
commit
e9ef8855cf
6 changed files with 186 additions and 36 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
//
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue