Add query session and authentication logic

This commit is contained in:
Tobie Morgan Hitchcock 2022-01-13 07:29:41 +00:00
parent 9e5e6efa6d
commit c51e60e706
6 changed files with 400 additions and 40 deletions

59
src/dbs/auth.rs Normal file
View file

@ -0,0 +1,59 @@
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
pub enum Level {
No,
Kv,
Ns,
Db,
Sc,
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
pub enum Auth {
No,
Kv,
Ns(String),
Db(String, String),
Sc(String, String, String),
}
impl Default for Auth {
fn default() -> Self {
Auth::No
}
}
impl Auth {
pub fn check(&self, level: Level) -> bool {
match self {
Auth::No => match level {
Level::No => true,
_ => false,
},
Auth::Kv => match level {
Level::No => true,
Level::Kv => true,
_ => false,
},
Auth::Ns(_) => match level {
Level::No => true,
Level::Kv => true,
Level::Ns => true,
_ => false,
},
Auth::Db(_, _) => match level {
Level::No => true,
Level::Kv => true,
Level::Ns => true,
Level::Db => true,
_ => false,
},
Auth::Sc(_, _, _) => match level {
Level::No => true,
Level::Kv => true,
Level::Ns => true,
Level::Db => true,
Level::Sc => true,
},
}
}
}

View file

@ -1,29 +1,45 @@
use crate::ctx::Context;
use crate::dbs::executor::Executor; use crate::dbs::executor::Executor;
use crate::dbs::response::Responses; use crate::dbs::response::Responses;
use crate::dbs::session::Session;
use crate::err::Error; use crate::err::Error;
use crate::sql; use crate::sql;
use crate::sql::query::Query; use crate::sql::query::Query;
use crate::sql::value::Value;
use std::collections::HashMap; use std::collections::HashMap;
pub type Vars<'a> = Option<HashMap<&'a str, String>>; pub type Variables = Option<HashMap<String, Value>>;
pub fn execute(txt: &str, vars: Vars) -> Result<Responses, Error> { pub async fn execute(txt: &str, session: Session, vars: Variables) -> Result<Responses, Error> {
// Parse the SQL query into an AST // Create a new query executor
let mut exe = Executor::new();
// Create a new execution context
let ctx = session.context();
// Parse the SQL query text
let ast = sql::parse(txt)?; let ast = sql::parse(txt)?;
// Create a new execution context // Process all statements
let ctx = Context::background().freeze(); exe.ns = session.ns;
// Create a new query executor exe.db = session.db;
let exe = Executor::new(); exe.execute(ctx, ast)
// Process all of the queries
exe.execute(&ctx, ast)
} }
pub fn process(ast: Query, vars: Vars) -> Result<Responses, Error> { pub async fn process(ast: Query, session: Session, vars: Variables) -> Result<Responses, Error> {
// Create a new execution context
let ctx = Context::background().freeze();
// Create a new query executor // Create a new query executor
let exe = Executor::new(); let mut exe = Executor::new();
// Process all of the queries // Store session info on context
exe.execute(&ctx, ast) let ctx = session.context();
// Process all statements
exe.ns = session.ns;
exe.db = session.db;
exe.execute(ctx, ast)
}
pub fn export(session: Session) -> Result<String, Error> {
// Create a new query executor
let mut exe = Executor::new();
// Create a new execution context
let ctx = session.context();
// Process database export
exe.ns = session.ns;
exe.db = session.db;
exe.export(ctx)
} }

View file

@ -1,50 +1,163 @@
use crate::ctx::Context;
use crate::dbs::response::{Response, Responses}; use crate::dbs::response::{Response, Responses};
use crate::dbs::Auth;
use crate::dbs::Level;
use crate::dbs::Options;
use crate::dbs::Process; use crate::dbs::Process;
use crate::dbs::Runtime; use crate::dbs::Runtime;
use crate::err::Error; use crate::err::Error;
use crate::sql::query::Query; use crate::sql::query::Query;
use crate::sql::statement::Statement;
use crate::sql::value::Value;
use std::time::Instant; use std::time::Instant;
#[derive(Clone, Debug, Default, Eq, PartialEq)] #[derive(Debug, Default)]
pub struct Executor { pub struct Executor {
pub id: String, pub id: Option<String>,
pub ns: String, pub ns: Option<String>,
pub db: String, pub db: Option<String>,
pub txn: Option<()>,
pub err: Option<Error>,
} }
impl Executor { impl Executor {
pub fn new() -> Executor { pub fn new() -> Executor {
Executor { Executor {
id: String::from("id"), id: None,
ns: String::from("ns"), ns: None,
db: String::from("db"), db: None,
..Executor::default()
} }
} }
pub fn execute(&self, ctx: &Runtime, qry: Query) -> Result<Responses, Error> { pub fn check(&mut self, opt: &Options, level: Level) -> Result<(), Error> {
let mut r: Vec<Response> = vec![]; if opt.auth.check(level) == false {
return Err(Error::QueryPermissionsError);
}
if self.ns.is_none() {
return Err(Error::NsError);
}
if self.db.is_none() {
return Err(Error::DbError);
}
Ok(())
}
pub fn export(&mut self, ctx: Runtime) -> Result<String, Error> {
todo!()
}
pub fn execute(&mut self, mut ctx: Runtime, qry: Query) -> Result<Responses, Error> {
// Initialise array of responses
let mut out: Vec<Response> = vec![];
// Create a new options
let mut opt = Options::new(&Auth::No);
// Process all statements in query
for stm in qry.statements().iter() { for stm in qry.statements().iter() {
// Reset errors
if self.txn.is_none() {
self.err = None;
}
// Get the statement start time // Get the statement start time
let now = Instant::now(); let now = Instant::now();
// Process a single statement // Process a single statement
let res = stm.process(&ctx, self, None); let res = match stm {
// Specify runtime options
Statement::Option(stm) => {
match &stm.name.name[..] {
"FIELD_QUERIES" => opt = opt.fields(stm.what),
"EVENT_QUERIES" => opt = opt.events(stm.what),
"TABLE_QUERIES" => opt = opt.tables(stm.what),
"IMPORT" => opt = opt.import(stm.what),
_ => break,
}
continue;
}
// Begin a new transaction
Statement::Begin(stm) => {
let res = stm.process(&ctx, &opt, self, None);
self.err = res.err();
continue;
}
// Cancel a running transaction
Statement::Cancel(stm) => {
let res = stm.process(&ctx, &opt, self, None);
self.err = res.err();
continue;
}
// Commit a running transaction
Statement::Commit(stm) => {
let res = stm.process(&ctx, &opt, self, None);
self.err = res.err();
continue;
}
// Process param definition statements
Statement::Set(stm) => {
match stm.process(&ctx, &opt, self, None) {
Ok(val) => {
let mut new = Context::new(&ctx);
let key = stm.name.to_owned();
new.add_value(key, val);
ctx = new.freeze();
}
_ => break,
}
Ok(Value::None)
}
// Process all other normal statements
_ => {
// Enable context override
let mut ctx = Context::new(&ctx).freeze();
// Specify statement timeout
if let Some(timeout) = stm.timeout() {
let mut new = Context::new(&ctx);
new.add_timeout(timeout);
ctx = new.freeze();
}
// Process statement
let res = stm.process(&ctx, &opt, self, None);
// Catch statement timeout
if let Some(timeout) = stm.timeout() {
if ctx.is_timedout() {
self.err = Some(Error::QueryTimeoutError {
timer: timeout,
});
}
}
// Continue with result
res
}
};
// Get the statement end time // Get the statement end time
let dur = now.elapsed(); let dur = now.elapsed();
// Check transaction errors
r.push(Response { match &self.err {
sql: format!("{}", stm), Some(e) => out.push(Response {
time: format!("{:?}", dur), time: format!("{:?}", dur),
status: match res { status: String::from("ERR"),
Ok(_) => String::from("OK"), detail: Some(format!("{}", e)),
Err(_) => String::from("ERR"), result: None,
}, }),
result: match res { None => {
Ok(v) => Some(v), // Format responses
Err(_) => None, match res {
}, Ok(v) => out.push(Response {
}) time: format!("{:?}", dur),
status: String::from("OK"),
detail: None,
result: v.output(),
}),
Err(e) => out.push(Response {
time: format!("{:?}", dur),
status: String::from("ERR"),
detail: Some(format!("{}", e)),
result: None,
}),
} }
Ok(Responses(r)) }
}
}
// Return responses
Ok(Responses(out))
} }
} }

View file

@ -1,13 +1,19 @@
mod auth;
mod dbs; mod dbs;
mod executor; mod executor;
mod iterator; mod iterator;
mod options;
mod process; mod process;
mod response; mod response;
mod runtime; mod runtime;
mod session;
pub use self::auth::*;
pub use self::dbs::*; pub use self::dbs::*;
pub use self::executor::*; pub use self::executor::*;
pub use self::iterator::*; pub use self::iterator::*;
pub use self::options::*;
pub use self::process::*; pub use self::process::*;
pub use self::response::*; pub use self::response::*;
pub use self::runtime::*; pub use self::runtime::*;
pub use self::session::*;

117
src/dbs/options.rs Normal file
View file

@ -0,0 +1,117 @@
use crate::cnf;
use crate::dbs::Auth;
use crate::err::Error;
use crate::sql::version::Version;
// An Options is passed around when processing a set of query
// statements. An Options contains specific information for how
// to process each particular statement, including the record
// version to retrieve, whether futures should be processed, and
// whether field/event/table queries should be processed (useful
// when importing data, where these queries might fail).
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Options<'a> {
pub auth: &'a Auth,
pub dive: usize, // How many subqueries have we gone into?
pub force: bool, // Should we force tables/events to re-run?
pub fields: bool, // Should we process field queries?
pub events: bool, // Should we process event queries?
pub tables: bool, // Should we process table queries?
pub futures: bool, // Should we process function futures?
pub version: Option<&'a Version>, // Current
}
impl<'a> Default for Options<'a> {
fn default() -> Self {
Options::new(&Auth::No)
}
}
impl<'a> Options<'a> {
// Create a new Options object
pub fn new(auth: &'a Auth) -> Options<'a> {
Options {
auth,
dive: 0,
force: false,
fields: true,
events: true,
tables: true,
futures: false,
version: None,
}
}
// Create a new Options object for a subquery
pub fn dive(&self) -> Result<Options<'a>, Error> {
if self.dive < cnf::MAX_RECURSIVE_QUERIES {
Ok(Options {
dive: self.dive + 1,
..*self
})
} else {
Err(Error::RecursiveSubqueryError {
limit: self.dive,
})
}
}
// Create a new Options object for a subquery
pub fn force(&self, v: bool) -> Options<'a> {
Options {
force: v,
..*self
}
}
// Create a new Options object for a subquery
pub fn fields(&self, v: bool) -> Options<'a> {
Options {
fields: v,
..*self
}
}
// Create a new Options object for a subquery
pub fn events(&self, v: bool) -> Options<'a> {
Options {
events: v,
..*self
}
}
// Create a new Options object for a subquery
pub fn tables(&self, v: bool) -> Options<'a> {
Options {
tables: v,
..*self
}
}
// Create a new Options object for a subquery
pub fn import(&self, v: bool) -> Options<'a> {
Options {
fields: v,
events: v,
tables: v,
..*self
}
}
// Create a new Options object for a subquery
pub fn futures(&self, v: bool) -> Options<'a> {
Options {
futures: v,
..*self
}
}
// Create a new Options object for a subquery
pub fn version(&self, v: Option<&'a Version>) -> Options<'a> {
Options {
version: v,
..*self
}
}
}

49
src/dbs/session.rs Normal file
View file

@ -0,0 +1,49 @@
use crate::ctx::Context;
use crate::dbs::Auth;
use crate::dbs::Runtime;
use crate::sql::value::Value;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Session {
pub au: Auth, // Authentication info
pub ip: Option<String>, // Session ip address
pub or: Option<String>, // Session origin
pub id: Option<String>, // Session id
pub ns: Option<String>, // Namespace
pub db: Option<String>, // Database
pub sc: Option<String>, // Scope
pub sd: Option<Value>, // Scope auth data
}
impl Session {
pub fn context(&self) -> Runtime {
let mut ctx = Context::background();
// Add session value
let key = String::from("session");
let val: Value = self.into();
ctx.add_value(key, val);
// Add scope value
let key = String::from("scope");
let val: Value = self.sc.to_owned().into();
ctx.add_value(key, val);
// Add auth data
let key = String::from("auth");
let val: Value = self.sd.to_owned().into();
ctx.add_value(key, val);
// Output context
ctx.freeze()
}
}
impl Into<Value> for &Session {
fn into(self) -> Value {
Value::from(map! {
"ip".to_string() => self.ip.to_owned().into(),
"or".to_string() => self.or.to_owned().into(),
"id".to_string() => self.id.to_owned().into(),
"ns".to_string() => self.ns.to_owned().into(),
"db".to_string() => self.db.to_owned().into(),
"sc".to_string() => self.sc.to_owned().into(),
})
}
}