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};
8use derive::Store;
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, Store, 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::access::ac::new(&self.name);
87				txn.set(
88					key,
89					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				)
96				.await?;
97				// Clear the cache
98				txn.clear();
99				// Ok all good
100				Ok(Value::None)
101			}
102			Base::Ns => {
103				// Fetch the transaction
104				let txn = ctx.tx();
105				// Check if the definition exists
106				if txn.get_ns_access(opt.ns()?, &self.name).await.is_ok() {
107					if self.if_not_exists {
108						return Ok(Value::None);
109					} else if !self.overwrite {
110						return Err(Error::AccessNsAlreadyExists {
111							ac: self.name.to_string(),
112							ns: opt.ns()?.into(),
113						});
114					}
115				}
116				// Process the statement
117				let key = crate::key::namespace::access::ac::new(opt.ns()?, &self.name);
118				txn.get_or_add_ns(opt.ns()?, opt.strict).await?;
119				txn.set(
120					key,
121					DefineAccessStatement {
122						// Don't persist the `IF NOT EXISTS` clause to schema
123						if_not_exists: false,
124						overwrite: false,
125						..self.clone()
126					},
127				)
128				.await?;
129				// Clear the cache
130				txn.clear();
131				// Ok all good
132				Ok(Value::None)
133			}
134			Base::Db => {
135				// Fetch the transaction
136				let txn = ctx.tx();
137				// Check if the definition exists
138				if txn.get_db_access(opt.ns()?, opt.db()?, &self.name).await.is_ok() {
139					if self.if_not_exists {
140						return Ok(Value::None);
141					} else if !self.overwrite {
142						return Err(Error::AccessDbAlreadyExists {
143							ac: self.name.to_string(),
144							ns: opt.ns()?.into(),
145							db: opt.db()?.into(),
146						});
147					}
148				}
149				// Process the statement
150				let key = crate::key::database::access::ac::new(opt.ns()?, opt.db()?, &self.name);
151				txn.get_or_add_ns(opt.ns()?, opt.strict).await?;
152				txn.get_or_add_db(opt.ns()?, opt.db()?, opt.strict).await?;
153				txn.set(
154					key,
155					DefineAccessStatement {
156						// Don't persist the `IF NOT EXISTS` clause to schema
157						if_not_exists: false,
158						overwrite: false,
159						..self.clone()
160					},
161				)
162				.await?;
163				// Clear the cache
164				txn.clear();
165				// Ok all good
166				Ok(Value::None)
167			}
168			// Other levels are not supported
169			_ => Err(Error::InvalidLevel(self.base.to_string())),
170		}
171	}
172}
173
174impl Display for DefineAccessStatement {
175	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
176		write!(f, "DEFINE ACCESS",)?;
177		if self.if_not_exists {
178			write!(f, " IF NOT EXISTS")?
179		}
180		if self.overwrite {
181			write!(f, " OVERWRITE")?
182		}
183		// The specific access method definition is displayed by AccessType
184		write!(f, " {} ON {} TYPE {}", self.name, self.base, self.kind)?;
185		// The additional authentication clause
186		if let Some(ref v) = self.authenticate {
187			write!(f, " AUTHENTICATE {v}")?
188		}
189		// Always print relevant durations so defaults can be changed in the future
190		// If default values were not printed, exports would not be forward compatible
191		// None values need to be printed, as they are different from the default values
192		write!(f, " DURATION")?;
193		if self.kind.can_issue_grants() {
194			write!(
195				f,
196				" FOR GRANT {},",
197				match self.duration.grant {
198					Some(dur) => format!("{}", dur),
199					None => "NONE".to_string(),
200				}
201			)?;
202		}
203		if self.kind.can_issue_tokens() {
204			write!(
205				f,
206				" FOR TOKEN {},",
207				match self.duration.token {
208					Some(dur) => format!("{}", dur),
209					None => "NONE".to_string(),
210				}
211			)?;
212		}
213		write!(
214			f,
215			" FOR SESSION {}",
216			match self.duration.session {
217				Some(dur) => format!("{}", dur),
218				None => "NONE".to_string(),
219			}
220		)?;
221		if let Some(ref v) = self.comment {
222			write!(f, " COMMENT {v}")?
223		}
224		Ok(())
225	}
226}
227
228impl InfoStructure for DefineAccessStatement {
229	fn structure(self) -> Value {
230		Value::from(map! {
231			"name".to_string() => self.name.structure(),
232			"base".to_string() => self.base.structure(),
233			"authenticate".to_string(), if let Some(v) = self.authenticate => v.structure(),
234			"duration".to_string() => Value::from(map!{
235				"session".to_string() => self.duration.session.into(),
236				"grant".to_string(), if self.kind.can_issue_grants() => self.duration.grant.into(),
237				"token".to_string(), if self.kind.can_issue_tokens() => self.duration.token.into(),
238			}),
239			"kind".to_string() => self.kind.structure(),
240			"comment".to_string(), if let Some(v) = self.comment => v.into(),
241		})
242	}
243}