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