1use std::fmt;
2use std::str::FromStr;
3use std::sync::Arc;
4
5use chrono::Utc;
6use surrealdb_types::ToSql;
7use uuid::Uuid;
8
9use crate::iam::{Auth, Level, Role};
10use crate::types::{PublicValue, PublicVariables};
11use crate::val::Value;
12
13#[derive(Clone, Debug, Default, Eq, PartialEq)]
15pub struct Session {
16 pub au: Arc<Auth>,
18 pub rt: bool,
20 pub ip: Option<String>,
22 pub or: Option<String>,
24 pub id: Option<Uuid>,
26 pub ns: Option<String>,
28 pub db: Option<String>,
30 pub ac: Option<String>,
32 pub tk: Option<PublicValue>,
34 pub rd: Option<PublicValue>,
36 pub exp: Option<i64>,
38 pub variables: PublicVariables,
40 pub new_planner_strategy: NewPlannerStrategy,
42 pub redact_volatile_explain_attrs: bool,
45}
46
47#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
48pub enum NewPlannerStrategy {
49 #[default]
51 BestEffortReadOnlyStatements,
52 ComputeOnly,
54 AllReadOnlyStatements,
57}
58
59impl fmt::Display for NewPlannerStrategy {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 Self::BestEffortReadOnlyStatements => f.write_str("best-effort"),
63 Self::ComputeOnly => f.write_str("compute-only"),
64 Self::AllReadOnlyStatements => f.write_str("all-read-only"),
65 }
66 }
67}
68
69impl FromStr for NewPlannerStrategy {
70 type Err = String;
71
72 fn from_str(s: &str) -> Result<Self, Self::Err> {
73 match s {
74 "best-effort" => Ok(Self::BestEffortReadOnlyStatements),
75 "compute-only" => Ok(Self::ComputeOnly),
76 "all-read-only" => Ok(Self::AllReadOnlyStatements),
77 _ => Err(format!(
78 "unknown planner strategy: '{s}' (expected 'best-effort', 'compute-only', or 'all-read-only')"
79 )),
80 }
81 }
82}
83
84impl Session {
85 pub fn with_ns(mut self, ns: &str) -> Session {
87 self.ns = Some(ns.to_owned());
88 self
89 }
90
91 pub fn with_db(mut self, db: &str) -> Session {
93 self.db = Some(db.to_owned());
94 self
95 }
96
97 pub fn with_ac(mut self, ac: &str) -> Session {
99 self.ac = Some(ac.to_owned());
100 self
101 }
102
103 pub fn with_rt(mut self, rt: bool) -> Session {
105 self.rt = rt;
106 self
107 }
108
109 pub fn new_planner_strategy(mut self, strategy: NewPlannerStrategy) -> Session {
111 self.new_planner_strategy = strategy;
112 self
113 }
114
115 pub(crate) fn ns(&self) -> Option<Arc<str>> {
117 self.ns.as_deref().map(Into::into)
118 }
119
120 pub(crate) fn db(&self) -> Option<Arc<str>> {
122 self.db.as_deref().map(Into::into)
123 }
124
125 pub(crate) fn live(&self) -> bool {
127 self.rt
128 }
129
130 pub(crate) fn expired(&self) -> bool {
132 match self.exp {
133 Some(exp) => Utc::now().timestamp() > exp,
134 None => false,
136 }
137 }
138
139 pub(crate) fn values(&self) -> Vec<(&'static str, Value)> {
140 use crate::sql::expression::convert_public_value_to_internal;
141
142 let access = self.ac.clone().map(|x| x.into()).unwrap_or(Value::None);
143 let auth = self.rd.clone().map(convert_public_value_to_internal).unwrap_or(Value::None);
144 let token = self.tk.clone().map(convert_public_value_to_internal).unwrap_or(Value::None);
145 let session = Value::from(map! {
146 "ac".to_string() => access.clone(),
147 "exp".to_string() => self.exp.map(Value::from).unwrap_or(Value::None),
148 "db".to_string() => self.db.clone().map(|x| x.into()).unwrap_or(Value::None),
149 "id".to_string() => self.id.map(|x| Value::Uuid(x.into())).unwrap_or(Value::None),
150 "ip".to_string() => self.ip.clone().map(|x| x.into()).unwrap_or(Value::None),
151 "ns".to_string() => self.ns.clone().map(|x| x.into()).unwrap_or(Value::None),
152 "or".to_string() => self.or.clone().map(|x| x.into()).unwrap_or(Value::None),
153 "rd".to_string() => auth.clone(),
154 "tk".to_string() => token.clone(),
155 });
156
157 vec![("access", access), ("auth", auth), ("token", token), ("session", session)]
158 }
159
160 pub fn for_level(level: Level, role: Role) -> Session {
162 let mut sess = Session::default();
164 match level {
166 Level::Root => {
167 sess.au = Arc::new(Auth::for_root(role));
168 }
169 Level::Namespace(ns) => {
170 sess.au = Arc::new(Auth::for_ns(role, &ns));
171 sess.ns = Some(ns);
172 }
173 Level::Database(ns, db) => {
174 sess.au = Arc::new(Auth::for_db(role, &ns, &db));
175 sess.ns = Some(ns);
176 sess.db = Some(db);
177 }
178 _ => {}
179 }
180 sess
181 }
182
183 pub fn for_record(ns: &str, db: &str, ac: &str, rid: PublicValue) -> Session {
185 Session {
186 ac: Some(ac.to_owned()),
187 au: Arc::new(Auth::for_record(rid.to_sql(), ns, db, ac)),
188 rt: false,
189 ip: None,
190 or: None,
191 id: None,
192 ns: Some(ns.to_owned()),
193 db: Some(db.to_owned()),
194 tk: None,
195 rd: Some(rid),
196 exp: None,
197 variables: Default::default(),
198 new_planner_strategy: NewPlannerStrategy::default(),
199 redact_volatile_explain_attrs: false,
200 }
201 }
202
203 pub fn owner() -> Session {
205 Session::for_level(Level::Root, Role::Owner)
206 }
207
208 pub fn editor() -> Session {
210 Session::for_level(Level::Root, Role::Editor)
211 }
212
213 pub fn viewer() -> Session {
215 Session::for_level(Level::Root, Role::Viewer)
216 }
217}