surrealdb_core/dbs/
options.rs

1use crate::cnf::MAX_COMPUTATION_DEPTH;
2use crate::dbs::Notification;
3use crate::err::Error;
4use crate::iam::{Action, Auth, ResourceKind};
5use crate::sql::statements::define::DefineTableStatement;
6use crate::sql::Base;
7use async_channel::Sender;
8use std::sync::Arc;
9use uuid::Uuid;
10
11/// An Options is passed around when processing a set of query
12/// statements.
13///
14/// An Options contains specific information for how
15/// to process each particular statement, including the record
16/// version to retrieve, whether futures should be processed, and
17/// whether field/event/table queries should be processed (useful
18/// when importing data, where these queries might fail).
19#[derive(Clone, Debug)]
20pub struct Options {
21	/// The current Node ID of the datastore instance
22	id: Option<Uuid>,
23	/// The currently selected Namespace
24	ns: Option<Arc<str>>,
25	/// The currently selected Database
26	db: Option<Arc<str>>,
27	/// Approximately how large is the current call stack?
28	dive: u32,
29	/// Connection authentication data
30	pub(crate) auth: Arc<Auth>,
31	/// Is authentication enabled on this datastore?
32	pub(crate) auth_enabled: bool,
33	/// Whether live queries can be used?
34	pub(crate) live: bool,
35	/// Should we force tables/events to re-run?
36	pub(crate) force: Force,
37	/// Should we run permissions checks?
38	pub(crate) perms: bool,
39	/// Should we error if tables don't exist?
40	pub(crate) strict: bool,
41	/// Should we process field queries?
42	pub(crate) import: bool,
43	/// Should we process function futures?
44	pub(crate) futures: Futures,
45	/// The data version as nanosecond timestamp
46	pub(crate) version: Option<u64>,
47	/// The channel over which we send notifications
48	pub(crate) sender: Option<Sender<Notification>>,
49}
50
51#[derive(Clone, Debug)]
52#[non_exhaustive]
53pub enum Force {
54	All,
55	None,
56	Table(Arc<[DefineTableStatement]>),
57}
58
59#[derive(Copy, Clone, Debug)]
60pub enum Futures {
61	Disabled,
62	Enabled,
63	Never,
64}
65
66impl Default for Options {
67	fn default() -> Self {
68		Options::new()
69	}
70}
71
72impl Options {
73	/// Create a new Options object
74	pub fn new() -> Options {
75		Options {
76			id: None,
77			ns: None,
78			db: None,
79			dive: *MAX_COMPUTATION_DEPTH,
80			live: false,
81			perms: true,
82			force: Force::None,
83			strict: false,
84			import: false,
85			futures: Futures::Disabled,
86			auth_enabled: true,
87			sender: None,
88			auth: Arc::new(Auth::default()),
89			version: None,
90		}
91	}
92
93	// --------------------------------------------------
94
95	/// Specify which Namespace should be used for
96	/// code which uses this `Options` object.
97	pub fn set_ns(&mut self, ns: Option<Arc<str>>) {
98		self.ns = ns
99	}
100
101	/// Specify which Database should be used for
102	/// code which uses this `Options` object.
103	pub fn set_db(&mut self, db: Option<Arc<str>>) {
104		self.db = db
105	}
106
107	// --------------------------------------------------
108
109	/// Set the maximum depth a computation can reach.
110	pub fn with_max_computation_depth(mut self, depth: u32) -> Self {
111		self.dive = depth;
112		self
113	}
114
115	/// Set the Node ID for subsequent code which uses
116	/// this `Options`, with support for chaining.
117	pub fn with_id(mut self, id: Uuid) -> Self {
118		self.id = Some(id);
119		self
120	}
121
122	/// Specify which Namespace should be used for code which
123	/// uses this `Options`, with support for chaining.
124	pub fn with_ns(mut self, ns: Option<Arc<str>>) -> Self {
125		self.ns = ns;
126		self
127	}
128
129	/// Specify which Database should be used for code which
130	/// uses this `Options`, with support for chaining.
131	pub fn with_db(mut self, db: Option<Arc<str>>) -> Self {
132		self.db = db;
133		self
134	}
135
136	/// Specify the authentication options for subsequent
137	/// code which uses this `Options`, with chaining.
138	pub fn with_auth(mut self, auth: Arc<Auth>) -> Self {
139		self.auth = auth;
140		self
141	}
142
143	/// Specify whether live queries are supported for
144	/// code which uses this `Options`, with chaining.
145	pub fn with_live(mut self, live: bool) -> Self {
146		self.live = live;
147		self
148	}
149
150	/// Specify whether permissions should be run for
151	/// code which uses this `Options`, with chaining.
152	pub fn with_perms(mut self, perms: bool) -> Self {
153		self.perms = perms;
154		self
155	}
156
157	/// Specify wether tables/events should re-run
158	pub fn with_force(mut self, force: Force) -> Self {
159		self.force = force;
160		self
161	}
162
163	/// Sepecify if we should error when a table does not exist
164	pub fn with_strict(mut self, strict: bool) -> Self {
165		self.strict = strict;
166		self
167	}
168
169	/// Specify if we are currently importing data
170	pub fn with_import(mut self, import: bool) -> Self {
171		self.set_import(import);
172		self
173	}
174
175	/// Specify if we are currently importing data
176	pub fn set_import(&mut self, import: bool) {
177		self.import = import;
178	}
179
180	/// Specify if we should process futures
181	pub fn with_futures(mut self, futures: bool) -> Self {
182		self.set_futures(futures);
183		self
184	}
185
186	pub fn set_futures(&mut self, futures: bool) {
187		self.futures = match self.futures {
188			Futures::Never => Futures::Never,
189			_ => match futures {
190				true => Futures::Enabled,
191				false => Futures::Disabled,
192			},
193		};
194	}
195
196	/// Specify if we should never process futures
197	pub fn with_futures_never(mut self) -> Self {
198		self.set_futures_never();
199		self
200	}
201
202	/// Specify if we should never process futures
203	pub fn set_futures_never(&mut self) {
204		self.futures = Futures::Never;
205	}
206
207	/// Create a new Options object with auth enabled
208	pub fn with_auth_enabled(mut self, auth_enabled: bool) -> Self {
209		self.auth_enabled = auth_enabled;
210		self
211	}
212
213	// Set the version
214	pub fn with_version(mut self, version: Option<u64>) -> Self {
215		self.version = version;
216		self
217	}
218
219	// --------------------------------------------------
220
221	/// Create a new Options object for a subquery
222	pub fn new_with_auth(&self, auth: Arc<Auth>) -> Self {
223		Self {
224			sender: self.sender.clone(),
225			auth,
226			ns: self.ns.clone(),
227			db: self.db.clone(),
228			force: self.force.clone(),
229			perms: self.perms,
230			..*self
231		}
232	}
233
234	/// Create a new Options object for a subquery
235	pub fn new_with_perms(&self, perms: bool) -> Self {
236		Self {
237			sender: self.sender.clone(),
238			auth: self.auth.clone(),
239			ns: self.ns.clone(),
240			db: self.db.clone(),
241			force: self.force.clone(),
242			perms,
243			..*self
244		}
245	}
246
247	/// Create a new Options object for a subquery
248	pub fn new_with_force(&self, force: Force) -> Self {
249		Self {
250			sender: self.sender.clone(),
251			auth: self.auth.clone(),
252			ns: self.ns.clone(),
253			db: self.db.clone(),
254			force,
255			..*self
256		}
257	}
258
259	/// Create a new Options object for a subquery
260	pub fn new_with_strict(&self, strict: bool) -> Self {
261		Self {
262			sender: self.sender.clone(),
263			auth: self.auth.clone(),
264			ns: self.ns.clone(),
265			db: self.db.clone(),
266			force: self.force.clone(),
267			strict,
268			..*self
269		}
270	}
271
272	/// Create a new Options object for a subquery
273	pub fn new_with_import(&self, import: bool) -> Self {
274		Self {
275			sender: self.sender.clone(),
276			auth: self.auth.clone(),
277			ns: self.ns.clone(),
278			db: self.db.clone(),
279			force: self.force.clone(),
280			import,
281			..*self
282		}
283	}
284
285	/// Create a new Options object for a subquery
286	pub fn new_with_futures(&self, futures: bool) -> Self {
287		Self {
288			sender: self.sender.clone(),
289			auth: self.auth.clone(),
290			ns: self.ns.clone(),
291			db: self.db.clone(),
292			force: self.force.clone(),
293			futures: match self.futures {
294				Futures::Never => Futures::Never,
295				_ => match futures {
296					true => Futures::Enabled,
297					false => Futures::Disabled,
298				},
299			},
300			..*self
301		}
302	}
303
304	/// Create a new Options object for a subquery
305	pub fn new_with_sender(&self, sender: Sender<Notification>) -> Self {
306		Self {
307			auth: self.auth.clone(),
308			ns: self.ns.clone(),
309			db: self.db.clone(),
310			force: self.force.clone(),
311			sender: Some(sender),
312			..*self
313		}
314	}
315
316	// Get currently selected base
317	pub fn selected_base(&self) -> Result<Base, Error> {
318		match (self.ns.as_ref(), self.db.as_ref()) {
319			(None, None) => Ok(Base::Root),
320			(Some(_), None) => Ok(Base::Ns),
321			(Some(_), Some(_)) => Ok(Base::Db),
322			(None, Some(_)) => Err(Error::NsEmpty),
323		}
324	}
325
326	/// Create a new Options object for a function/subquery/future/etc.
327	///
328	/// The parameter is the approximate cost of the operation (more concretely, the size of the
329	/// stack frame it uses relative to a simple function call). When in doubt, use a value of 1.
330	pub fn dive(&self, cost: u8) -> Result<Self, Error> {
331		if self.dive < cost as u32 {
332			return Err(Error::ComputationDepthExceeded);
333		}
334		Ok(Self {
335			sender: self.sender.clone(),
336			auth: self.auth.clone(),
337			ns: self.ns.clone(),
338			db: self.db.clone(),
339			force: self.force.clone(),
340			dive: self.dive - cost as u32,
341			..*self
342		})
343	}
344
345	// --------------------------------------------------
346
347	/// Get current Node ID
348	#[inline(always)]
349	pub fn id(&self) -> Result<Uuid, Error> {
350		self.id.ok_or_else(|| fail!("No Node ID is specified"))
351	}
352
353	/// Get currently selected NS
354	#[inline(always)]
355	pub fn ns(&self) -> Result<&str, Error> {
356		self.ns.as_deref().ok_or(Error::NsEmpty)
357	}
358
359	/// Get currently selected DB
360	#[inline(always)]
361	pub fn db(&self) -> Result<&str, Error> {
362		self.db.as_deref().ok_or(Error::DbEmpty)
363	}
364
365	/// Get currently selected NS and DB
366	#[inline(always)]
367	pub fn ns_db(&self) -> Result<(&str, &str), Error> {
368		Ok((self.ns()?, self.db()?))
369	}
370
371	/// Check whether this request supports realtime queries
372	#[inline(always)]
373	pub fn realtime(&self) -> Result<(), Error> {
374		if !self.live {
375			return Err(Error::RealtimeDisabled);
376		}
377		Ok(())
378	}
379
380	// Validate Options for Namespace
381	#[inline(always)]
382	pub fn valid_for_ns(&self) -> Result<(), Error> {
383		if self.ns.is_none() {
384			return Err(Error::NsEmpty);
385		}
386		Ok(())
387	}
388
389	// Validate Options for Database
390	#[inline(always)]
391	pub fn valid_for_db(&self) -> Result<(), Error> {
392		if self.ns.is_none() {
393			return Err(Error::NsEmpty);
394		}
395		if self.db.is_none() {
396			return Err(Error::DbEmpty);
397		}
398		Ok(())
399	}
400
401	/// Check if the current auth is allowed to perform an action on a given resource
402	pub fn is_allowed(&self, action: Action, res: ResourceKind, base: &Base) -> Result<(), Error> {
403		// Validate the target resource and base
404		let res = match base {
405			Base::Root => res.on_root(),
406			Base::Ns => res.on_ns(self.ns()?),
407			Base::Db => {
408				let (ns, db) = self.ns_db()?;
409				res.on_db(ns, db)
410			}
411			// TODO(gguillemas): This variant is kept in 2.0.0 for backward compatibility. Drop in 3.0.0.
412			Base::Sc(_) => {
413				// We should not get here, the scope base is only used in parsing for backward compatibility.
414				return Err(Error::InvalidAuth);
415			}
416		};
417
418		// If auth is disabled, allow all actions for anonymous users
419		if !self.auth_enabled && self.auth.is_anon() {
420			return Ok(());
421		}
422
423		self.auth.is_allowed(action, &res).map_err(Error::IamError)
424	}
425
426	/// Checks the current server configuration, and
427	/// user authentication information to determine
428	/// whether we need to process table permissions
429	/// on each document.
430	///
431	/// This method is repeatedly called during the
432	/// document processing operations, and so the
433	/// performance of this function is important.
434	/// We decided to bypass the system cedar auth
435	/// system as a temporary solution until the
436	/// new authorization system is optimised.
437	pub fn check_perms(&self, action: Action) -> Result<bool, Error> {
438		// Check if permissions are enabled for this sub-process
439		if !self.perms {
440			return Ok(false);
441		}
442		// Check if server auth is disabled
443		if !self.auth_enabled && self.auth.is_anon() {
444			return Ok(false);
445		}
446		// Check the action to determine if we need to check permissions
447		match action {
448			// This is a request to edit a resource
449			Action::Edit => {
450				// Check if the actor is allowed to edit
451				let allowed = self.auth.has_editor_role();
452				// Today all users have at least View
453				// permissions, so if the target database
454				// belongs to the user's level, we don't
455				// need to check any table permissions.
456				let (ns, db) = self.ns_db()?;
457				let db_in_actor_level = self.auth.is_root()
458					|| self.auth.is_ns_check(ns)
459					|| self.auth.is_db_check(ns, db);
460				// If either of the above checks are false
461				// then we need to check table permissions
462				Ok(!allowed || !db_in_actor_level)
463			}
464			// This is a request to view a resource
465			Action::View => {
466				// Check if the actor is allowed to view
467				let allowed = self.auth.has_viewer_role();
468				// Today, Owner and Editor roles have
469				// Edit permissions, so if the target
470				// database belongs to the user's level
471				// we don't need to check table permissions.
472				let (ns, db) = self.ns_db()?;
473				let db_in_actor_level = self.auth.is_root()
474					|| self.auth.is_ns_check(ns)
475					|| self.auth.is_db_check(ns, db);
476				// If either of the above checks are false
477				// then we need to check table permissions
478				Ok(!allowed || !db_in_actor_level)
479			}
480		}
481	}
482}
483
484#[cfg(test)]
485mod tests {
486
487	use super::*;
488	use crate::iam::Role;
489
490	#[test]
491	fn is_allowed() {
492		// With auth disabled
493		{
494			let opts = Options::default().with_auth_enabled(false);
495
496			// When no NS is provided and it targets the NS base, it should return an error
497			opts.is_allowed(Action::View, ResourceKind::Any, &Base::Ns).unwrap_err();
498			// When no DB is provided and it targets the DB base, it should return an error
499			opts.is_allowed(Action::View, ResourceKind::Any, &Base::Db).unwrap_err();
500			opts.clone()
501				.with_db(Some("db".into()))
502				.is_allowed(Action::View, ResourceKind::Any, &Base::Db)
503				.unwrap_err();
504
505			// When a root resource is targeted, it succeeds
506			opts.is_allowed(Action::View, ResourceKind::Any, &Base::Root).unwrap();
507			// When a NS resource is targeted and NS was provided, it succeeds
508			opts.clone()
509				.with_ns(Some("ns".into()))
510				.is_allowed(Action::View, ResourceKind::Any, &Base::Ns)
511				.unwrap();
512			// When a DB resource is targeted and NS and DB was provided, it succeeds
513			opts.clone()
514				.with_ns(Some("ns".into()))
515				.with_db(Some("db".into()))
516				.is_allowed(Action::View, ResourceKind::Any, &Base::Db)
517				.unwrap();
518		}
519
520		// With auth enabled
521		{
522			let opts = Options::default()
523				.with_auth_enabled(true)
524				.with_auth(Auth::for_root(Role::Owner).into());
525
526			// When no NS is provided and it targets the NS base, it should return an error
527			opts.is_allowed(Action::View, ResourceKind::Any, &Base::Ns).unwrap_err();
528			// When no DB is provided and it targets the DB base, it should return an error
529			opts.is_allowed(Action::View, ResourceKind::Any, &Base::Db).unwrap_err();
530			opts.clone()
531				.with_db(Some("db".into()))
532				.is_allowed(Action::View, ResourceKind::Any, &Base::Db)
533				.unwrap_err();
534
535			// When a root resource is targeted, it succeeds
536			opts.is_allowed(Action::View, ResourceKind::Any, &Base::Root).unwrap();
537			// When a NS resource is targeted and NS was provided, it succeeds
538			opts.clone()
539				.with_ns(Some("ns".into()))
540				.is_allowed(Action::View, ResourceKind::Any, &Base::Ns)
541				.unwrap();
542			// When a DB resource is targeted and NS and DB was provided, it succeeds
543			opts.clone()
544				.with_ns(Some("ns".into()))
545				.with_db(Some("db".into()))
546				.is_allowed(Action::View, ResourceKind::Any, &Base::Db)
547				.unwrap();
548		}
549	}
550
551	#[test]
552	pub fn execute_futures() {
553		let mut opts = Options::default().with_futures(false);
554
555		// Futures should be disabled
556		assert!(matches!(opts.futures, Futures::Disabled));
557
558		// Allow setting to true
559		opts = opts.with_futures(true);
560		assert!(matches!(opts.futures, Futures::Enabled));
561
562		// Set to never and disallow setting to true
563		opts = opts.with_futures_never();
564		opts = opts.with_futures(true);
565		assert!(matches!(opts.futures, Futures::Never));
566	}
567}