From 9f33d5dc27ebae90b6aabfd01c7aa989ec560dc5 Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Sun, 10 Sep 2023 11:41:28 +0100 Subject: [PATCH] Ensure scope `SIGNUP` and `SIGNIN` works with guest access disabled (#2663) --- lib/src/iam/signin.rs | 4 +-- lib/src/iam/signup.rs | 2 +- lib/src/kvs/ds.rs | 81 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/lib/src/iam/signin.rs b/lib/src/iam/signin.rs index 8f5787b7..a8fa85ed 100644 --- a/lib/src/iam/signin.rs +++ b/lib/src/iam/signin.rs @@ -116,9 +116,9 @@ pub async fn sc( // Setup the query params let vars = Some(vars.0); // Setup the system session for finding the signin record - let sess = Session::viewer().with_ns(&ns).with_db(&db); + let sess = Session::editor().with_ns(&ns).with_db(&db); // Compute the value with the params - match kvs.compute(val, &sess, vars).await { + match kvs.evaluate(val, &sess, vars).await { // The signin value succeeded Ok(val) => match val.record() { // There is a record returned diff --git a/lib/src/iam/signup.rs b/lib/src/iam/signup.rs index 01ad70bb..1668ec67 100644 --- a/lib/src/iam/signup.rs +++ b/lib/src/iam/signup.rs @@ -59,7 +59,7 @@ pub async fn sc( // Setup the system session for creating the signup record let sess = Session::editor().with_ns(&ns).with_db(&db); // Compute the value with the params - match kvs.compute(val, &sess, vars).await { + match kvs.evaluate(val, &sess, vars).await { // The signin value succeeded Ok(val) => match val.record() { // There is a record returned diff --git a/lib/src/kvs/ds.rs b/lib/src/kvs/ds.rs index 803040a6..ef9aca4b 100644 --- a/lib/src/kvs/ds.rs +++ b/lib/src/kvs/ds.rs @@ -787,7 +787,7 @@ impl Datastore { vars: Variables, ) -> Result, Error> { // Check if anonymous actors can execute queries when auth is enabled - // TODO(sgirones): Check this as part of the authoritzation layer + // TODO(sgirones): Check this as part of the authorisation layer if self.auth_enabled && sess.au.is_anon() && !self.capabilities.allows_guest_access() { return Err(IamError::NotAllowed { actor: "anonymous".to_string(), @@ -803,8 +803,8 @@ impl Datastore { .with_db(sess.db()) .with_live(sess.live()) .with_auth(sess.au.clone()) - .with_auth_enabled(self.auth_enabled) - .with_strict(self.strict); + .with_strict(self.strict) + .with_auth_enabled(self.auth_enabled); // Create a new query executor let mut exe = Executor::new(self); // Create a default context @@ -852,7 +852,7 @@ impl Datastore { vars: Variables, ) -> Result { // Check if anonymous actors can compute values when auth is enabled - // TODO(sgirones): Check this as part of the authoritzation layer + // TODO(sgirones): Check this as part of the authorisation layer if self.auth_enabled && !self.capabilities.allows_guest_access() { return Err(IamError::NotAllowed { actor: "anonymous".to_string(), @@ -868,8 +868,77 @@ impl Datastore { .with_db(sess.db()) .with_live(sess.live()) .with_auth(sess.au.clone()) - .with_auth_enabled(self.auth_enabled) - .with_strict(self.strict); + .with_strict(self.strict) + .with_auth_enabled(self.auth_enabled); + // Create a default context + let mut ctx = Context::default(); + // Set context capabilities + ctx.add_capabilities(self.capabilities.clone()); + // Set the global query timeout + if let Some(timeout) = self.query_timeout { + ctx.add_timeout(timeout); + } + // Setup the notification channel + if let Some(channel) = &self.notification_channel { + ctx.add_notifications(Some(&channel.0)); + } + // Start an execution context + let ctx = sess.context(ctx); + // Store the query variables + let ctx = vars.attach(ctx)?; + // Start a new transaction + let txn = self.transaction(val.writeable(), false).await?.enclose(); + // Compute the value + let res = val.compute(&ctx, &opt, &txn, None).await; + // Store any data + match (res.is_ok(), val.writeable()) { + // If the compute was successful, then commit if writeable + (true, true) => txn.lock().await.commit().await?, + // Cancel if the compute was an error, or if readonly + (_, _) => txn.lock().await.cancel().await?, + }; + // Return result + res + } + + /// Evaluates a SQL [`Value`] without checking authenticating config + /// This is used in very specific cases, where we do not need to check + /// whether authentication is enabled, or guest access is disabled. + /// For example, this is used when processing a SCOPE SIGNUP or SCOPE + /// SIGNIN clause, which still needs to work without guest access. + /// + /// ```rust,no_run + /// use surrealdb::kvs::Datastore; + /// use surrealdb::err::Error; + /// use surrealdb::dbs::Session; + /// use surrealdb::sql::Future; + /// use surrealdb::sql::Value; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Error> { + /// let ds = Datastore::new("memory").await?; + /// let ses = Session::owner(); + /// let val = Value::Future(Box::new(Future::from(Value::Bool(true)))); + /// let res = ds.evaluate(val, &ses, None).await?; + /// Ok(()) + /// } + /// ``` + #[instrument(level = "debug", skip_all)] + pub async fn evaluate( + &self, + val: Value, + sess: &Session, + vars: Variables, + ) -> Result { + // Create a new query options + let opt = Options::default() + .with_id(self.id.0) + .with_ns(sess.ns()) + .with_db(sess.db()) + .with_live(sess.live()) + .with_auth(sess.au.clone()) + .with_strict(self.strict) + .with_auth_enabled(self.auth_enabled); // Create a default context let mut ctx = Context::default(); // Set context capabilities