surrealdb_core/sql/statements/define/
access.rs

1use crate::ctx::Context;
2use crate::dbs::Options;
3use crate::doc::CursorDoc;
4use crate::err::Error;
5use crate::iam::{Action, ResourceKind};
6use crate::sql::statements::info::InfoStructure;
7use crate::sql::{access::AccessDuration, AccessType, Base, Ident, Strand, Value};
8
9use rand::distributions::Alphanumeric;
10use rand::Rng;
11use revision::revisioned;
12use serde::{Deserialize, Serialize};
13use std::fmt::{self, Display};
14
15#[revisioned(revision = 3)]
16#[derive(Clone, Default, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
17#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
18#[non_exhaustive]
19pub struct DefineAccessStatement {
20	pub name: Ident,
21	pub base: Base,
22	pub kind: AccessType,
23	#[revision(start = 2)]
24	pub authenticate: Option<Value>,
25	pub duration: AccessDuration,
26	pub comment: Option<Strand>,
27	pub if_not_exists: bool,
28	#[revision(start = 3)]
29	pub overwrite: bool,
30}
31
32impl DefineAccessStatement {
33	/// Generate a random key to be used to sign session tokens
34	/// This key will be used to sign tokens issued with this access method
35	/// This value is used by default in every access method other than JWT
36	pub(crate) fn random_key() -> String {
37		rand::thread_rng().sample_iter(&Alphanumeric).take(128).map(char::from).collect::<String>()
38	}
39
40	/// Returns a version of the statement where potential secrets are redacted
41	/// This function should be used when displaying the statement to datastore users
42	/// This function should NOT be used when displaying the statement for export purposes
43	pub fn redacted(&self) -> DefineAccessStatement {
44		let mut das = self.clone();
45		das.kind = match das.kind {
46			AccessType::Jwt(ac) => AccessType::Jwt(ac.redacted()),
47			AccessType::Record(mut ac) => {
48				ac.jwt = ac.jwt.redacted();
49				AccessType::Record(ac)
50			}
51			AccessType::Bearer(mut ac) => {
52				ac.jwt = ac.jwt.redacted();
53				AccessType::Bearer(ac)
54			}
55		};
56		das
57	}
58}
59
60impl DefineAccessStatement {
61	/// Process this type returning a computed simple Value
62	pub(crate) async fn compute(
63		&self,
64		ctx: &Context,
65		opt: &Options,
66		_doc: Option<&CursorDoc>,
67	) -> Result<Value, Error> {
68		// Allowed to run?
69		opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?;
70		// Check the statement type
71		match &self.base {
72			Base::Root => {
73				// Fetch the transaction
74				let txn = ctx.tx();
75				// Check if access method already exists
76				if txn.get_root_access(&self.name).await.is_ok() {
77					if self.if_not_exists {
78						return Ok(Value::None);
79					} else if !self.overwrite {
80						return Err(Error::AccessRootAlreadyExists {
81							ac: self.name.to_string(),
82						});
83					}
84				}
85				// Process the statement
86				let key = crate::key::root::ac::new(&self.name);
87				txn.set(
88					key,
89					revision::to_vec(&DefineAccessStatement {
90						// Don't persist the `IF NOT EXISTS` clause to schema
91						if_not_exists: false,
92						overwrite: false,
93						..self.clone()
94					})?,
95					None,
96				)
97				.await?;
98				// Clear the cache
99				txn.clear();
100				// Ok all good
101				Ok(Value::None)
102			}
103			Base::Ns => {
104				// Fetch the transaction
105				let txn = ctx.tx();
106				// Check if the definition exists
107				if txn.get_ns_access(opt.ns()?, &self.name).await.is_ok() {
108					if self.if_not_exists {
109						return Ok(Value::None);
110					} else if !self.overwrite {
111						return Err(Error::AccessNsAlreadyExists {
112							ac: self.name.to_string(),
113							ns: opt.ns()?.into(),
114						});
115					}
116				}
117				// Process the statement
118				let key = crate::key::namespace::ac::new(opt.ns()?, &self.name);
119				txn.get_or_add_ns(opt.ns()?, opt.strict).await?;
120				txn.set(
121					key,
122					revision::to_vec(&DefineAccessStatement {
123						// Don't persist the `IF NOT EXISTS` clause to schema
124						if_not_exists: false,
125						overwrite: false,
126						..self.clone()
127					})?,
128					None,
129				)
130				.await?;
131				// Clear the cache
132				txn.clear();
133				// Ok all good
134				Ok(Value::None)
135			}
136			Base::Db => {
137				// Fetch the transaction
138				let txn = ctx.tx();
139				// Check if the definition exists
140				let (ns, db) = opt.ns_db()?;
141				if txn.get_db_access(ns, db, &self.name).await.is_ok() {
142					if self.if_not_exists {
143						return Ok(Value::None);
144					} else if !self.overwrite {
145						return Err(Error::AccessDbAlreadyExists {
146							ac: self.name.to_string(),
147							ns: ns.into(),
148							db: db.into(),
149						});
150					}
151				}
152				// Process the statement
153				let key = crate::key::database::ac::new(ns, db, &self.name);
154				txn.get_or_add_ns(ns, opt.strict).await?;
155				txn.get_or_add_db(ns, db, opt.strict).await?;
156				txn.set(
157					key,
158					revision::to_vec(&DefineAccessStatement {
159						// Don't persist the `IF NOT EXISTS` clause to schema
160						if_not_exists: false,
161						overwrite: false,
162						..self.clone()
163					})?,
164					None,
165				)
166				.await?;
167				// Clear the cache
168				txn.clear();
169				// Ok all good
170				Ok(Value::None)
171			}
172			// Other levels are not supported
173			_ => Err(Error::InvalidLevel(self.base.to_string())),
174		}
175	}
176}
177
178impl Display for DefineAccessStatement {
179	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
180		write!(f, "DEFINE ACCESS",)?;
181		if self.if_not_exists {
182			write!(f, " IF NOT EXISTS")?
183		}
184		if self.overwrite {
185			write!(f, " OVERWRITE")?
186		}
187		// The specific access method definition is displayed by AccessType
188		write!(f, " {} ON {} TYPE {}", self.name, self.base, self.kind)?;
189		// The additional authentication clause
190		if let Some(ref v) = self.authenticate {
191			write!(f, " AUTHENTICATE {v}")?
192		}
193		// Always print relevant durations so defaults can be changed in the future
194		// If default values were not printed, exports would not be forward compatible
195		// None values need to be printed, as they are different from the default values
196		write!(f, " DURATION")?;
197		if self.kind.can_issue_grants() {
198			write!(
199				f,
200				" FOR GRANT {},",
201				match self.duration.grant {
202					Some(dur) => format!("{}", dur),
203					None => "NONE".to_string(),
204				}
205			)?;
206		}
207		if self.kind.can_issue_tokens() {
208			write!(
209				f,
210				" FOR TOKEN {},",
211				match self.duration.token {
212					Some(dur) => format!("{}", dur),
213					None => "NONE".to_string(),
214				}
215			)?;
216		}
217		write!(
218			f,
219			" FOR SESSION {}",
220			match self.duration.session {
221				Some(dur) => format!("{}", dur),
222				None => "NONE".to_string(),
223			}
224		)?;
225		if let Some(ref v) = self.comment {
226			write!(f, " COMMENT {v}")?
227		}
228		Ok(())
229	}
230}
231
232impl InfoStructure for DefineAccessStatement {
233	fn structure(self) -> Value {
234		Value::from(map! {
235			"name".to_string() => self.name.structure(),
236			"base".to_string() => self.base.structure(),
237			"authenticate".to_string(), if let Some(v) = self.authenticate => v.structure(),
238			"duration".to_string() => Value::from(map!{
239				"session".to_string() => self.duration.session.into(),
240				"grant".to_string(), if self.kind.can_issue_grants() => self.duration.grant.into(),
241				"token".to_string(), if self.kind.can_issue_tokens() => self.duration.token.into(),
242			}),
243			"kind".to_string() => self.kind.structure(),
244			"comment".to_string(), if let Some(v) = self.comment => v.into(),
245		})
246	}
247}